Skip to content

Accessibility as a Disclosure Surface — ADR-101…103 and directive D-6.1

Accessibility as a Disclosure Surface — ADR-101…103 and directive D-6.1

Date: 2026-08-06 · Written in English per the 2026-08-01 convention change.

Disposes of Phase6_Accessibility_Review.md — findings A11Y-F-01A11Y-F-05, the last angle on Audit_Angles_Tracker.md’s candidate list.

Three ADRs and one directive for five findings. A11Y-F-01 and A11Y-F-02 are ruled together in ADR-101 and the pairing is not a convenience: F-02 is a rule and F-01 is the absence of its enforcement point, so splitting them would produce an ADR that ADR-045 would immediately fail. A11Y-F-05 is a directive because it decides nothing — it writes down a consequence the existing rules already have.

This file continues the R-numbered sequence for filing purposes only.


ADR-101 — The Announcement Stream Is a Channel, and Its Schedule Is a Function of Tick Arrival

Status: Accepted · Date: 2026-08-06 · Amends 027 (adds a Guardrail 7 obligation) · interacts with 034, 053 · disposes of A11Y-F-01, A11Y-F-02

Context

ADR-034 clause 3 already rules that a correction’s timing must carry no information: the mask crossfade’s duration and easing are a pure function of time since mask arrival and must not vary with the magnitude of the delta, because otherwise “how fast the fog moved” distinguishes something was there from nothing was there.

An ARIA live region is that same channel with a different transducer. Three carriers, and none of them is content:

  • Rate. Six announcements in a second says the mask advanced a long way; silence says it did not. The Focus-tier cadence plus ADR-033’s one-tick lag is already a coarse clock; speech turns it into an audible one.
  • Ordering. Announcements serialise into one spoken queue, so the order of two entities is a datum the visual path never exposes — on screen they simply appear together.
  • Interrupting silence. aria-live="assertive" pre-empts. A user who learns that interruptions occur only for one class of event infers the class from the interruption, before the sentence completes.

Why the existing controls do not reach it. §14.7’s per-viewer property names the semantic mirror and asserts containment: no entity outside the disclosed set. A mirror is a structure and can be sampled; an announcement stream is a sequence, and containment over a DOM tree cannot see rate, order or silence. §14.7 already draws exactly this distinction for the visual path, where revealed-set containment and correction-timing-carries-no-information are two separate properties precisely because the first cannot detect the second. The accessibility path was given the first and not the second.

Decision

  1. Announcement scheduling is a function of tick arrival, not of what changed. The announcer emits on a fixed cadence derived from the mask cadence; it does not emit sooner because more changed, and does not fall silent because less did.
  2. A tick with nothing to announce still advances the schedule. Silence must be indistinguishable from “nothing changed”, which requires the schedule to be observable without reference to content.
  3. Ordering within a batch is a stable function of entity identity, not of the order in which changes arrived or of proximity, distance or threat. Arrival order is an artefact of server iteration and would leak it.
  4. assertive is reserved for events whose class is already known to the player — their own action’s result, a turn transition, a connection state change. It may never be used for a disclosure event, because pre-emption is itself a one-bit signal that fires before any word is spoken.
  5. The rate limit is a floor as well as a ceiling. A batching window bounds the maximum, and the fixed schedule bounds the minimum informativeness of silence. A ceiling alone leaves the silence carrier untouched, which is the half a naive rate limit misses.

Alternatives Considered and Why Rejected

  • Announce immediately and rely on the per-viewer content filter. Rejected: this is exactly the position ADR-034 clause 3 rejected for the crossfade. Content filtering is orthogonal to timing, and the finding exists because the filter was mistaken for sufficient.
  • Rate-limit announcements and stop there. Rejected as the half-measure above. It bounds the burst carrier and leaves ordering and silence intact.
  • Add jitter. Rejected: randomised delay reduces the channel’s rate without closing it, is unfalsifiable as a control (how much jitter is enough?), and degrades the experience of every user to obscure a signal a fixed schedule removes outright.
  • Declare the announcement stream out of scope until a11y is implemented. Rejected on the same reasoning R2-F-41 used for the mirror’s data source: this determines where the announcer gets its schedule, and that is not a decision a later phase can revisit cheaply.

Consequences (including negative)

  • A screen-reader user learns of a change up to one batching window later than a sighted user sees it. That is a real accessibility cost, and it is accepted deliberately in exchange for the channel — the alternative is a faster path that leaks. It is stated so nobody later “optimises” the delay away without knowing what it was for.
  • The announcer needs its own clock, which is a component the frontend does not have and which must not be the render loop’s.
  • Clauses 1 and 2 are in tension with a chatty interface. A fixed schedule that never falls silent risks announcing “nothing” audibly. The resolution is that the schedule is fixed while the utterance may be empty; an empty utterance is silence at a predictable instant, not silence that means something.
  • This does not make the accessible experience equal. It makes it non-leaking. Whether it is good is a question for users, not for this record.

Rights-holders (ADR-079)

None. No new class of data is retained; this governs the scheduling of already-disclosed values.

Enforcement

  • The Guardrail 7 accessibility row gains announcement scheduling as a named behaviour, so ADR-064’s rule applies: a cell whose test does not exist is treated as blank, and a blank cell blocks release. This is the Amends 027 link.
  • A generated property over the announcement stream, alongside §14.7’s containment property over the mirror — this is A11Y-F-01’s enforcement point. For a generated sequence of mask updates, the emitted schedule must be identical under permutation of the contents of those updates. A stream property, not a snapshot property, because the defect is a sequence.
  • The assertive restriction is a lint over the mirror’s emitters, not a review item: the set of event classes permitted to pre-empt is enumerable, and anything else using assertive fails the build.

Honest scope. The frontend suite does not exist, so none of the above runs today. This is the same gap §14.7 already records for its three presentation-containment properties, and it is recorded the same way rather than presented as coverage.


ADR-102 — maskTick Is a Gate, Not a Value, and Channel (a) Holds It

Status: Accepted · Date: 2026-08-06 · Amends 053 · Depends-on 092 (the mask travels as its own message, which is what separates the two ticks on the wire) · disposes of A11Y-F-03

Context

§8.2 item 4 gates entity disclosure on the older of transformTick and maskTick, because pipelining would otherwise draw an entity one tick before the fog that conceals it — “a 50 ms disclosure of exactly the information §9.2 exists to protect”.

Three facts intersect and nothing joined them:

  1. The rule is phrased about “a client”, and the failure it describes is a drawing one.
  2. ADR-053 splits arrival into two channels; the semantic mirror is channel (a), DOM-facing, published by the Network Worker.
  3. ADR-092 moved the fog mask out of the tick snapshot onto its own message and cadence, because embedding it cost 50×.

So transform state and mask now arrive as separate messages and the gate requires holding both. ADR-053’s attribution rule settles which channel owns a value that appears in both DOM and world space — hit points are its worked example — and maskTick is not a value. It is a gate on other values, and the attribution rule has nothing to say about it.

This is recorded failure mode #2 in its exact form: §8.2 asserts the outcome, ADR-053 governs the mechanism, and nobody owns the join.

Decision

  1. The gate is a property of the arrival authority, not of a consumer. The Network Worker holds the current maskTick and does not publish an entity on either channel until the older of the two ticks covers it. Gating in the Render Worker and the mirror separately would be two implementations of one security property — the shape ADR-017 rejects for geometry.
  2. ADR-053’s attribution rule is extended from values to gates. A datum that governs whether another datum may be published belongs to the arrival authority, regardless of which channel consumes the thing it governs.
  3. The mirror never receives an ungated entity, so the accessibility path cannot re-open the window the visual path closes. This is the clause A11Y-F-03 exists for.
  4. maskTick is not itself published to either consumer. A consumer that could read it could infer that a mask arrived, which is the ADR-101 channel by another route.

Alternatives Considered and Why Rejected

  • Gate in each consumer. Rejected: two implementations of one security property, and the accessibility one would be written last, by whoever builds the mirror, against a rule stated in §8.2 in drawing language they may reasonably read as not applying to text.
  • Publish maskTick to consumers and let them gate. Rejected by clause 4 — it hands every consumer a mask-arrival signal, and one of them is a speech stream.
  • Merge the mask back into the tick snapshot so there is one tick. Rejected: ADR-092 measured that at 50× the transform payload. The coupling this ADR closes is cheaper than the coupling that would reintroduce.
  • Treat it as covered because §8.2 says “a client”. Rejected, and this is the alternative most likely to be re-proposed. The sentence is general and the ownership is not; a rule with no holder is the thing this corpus keeps finding under the name “nobody owns the join”.

Consequences (including negative)

  • The Network Worker becomes stateful in a way ADR-053 did not describe. It already holds connection and version state; it now holds a tick watermark, and the mirror’s liveness depends on the mask cadence rather than only on the tick.
  • An entity whose mask is stale is invisible on both channels. That is correct and worth stating: a screen-reader user and a sighted user go blind to the same entity at the same instant, which is the parity property, not a bug to be smoothed over on one channel.
  • MaskStaleness acquires a second consumer. §5.2 already meters it; it now also bounds how far behind the DOM mirror may run, so the existing metric needs no new sibling.

Rights-holders (ADR-079)

None.

Enforcement

  • A structural assertion in §14.4’s dependency-cruiser list: no module outside the Network Worker may import the gate, which is the import-graph form of clause 1.
  • The gate is exercised by §14.7’s per-viewer property once the mirror exists, because a generated state with a stale mask must produce a mirror that omits the entity.
  • Clause 4 is a type property: maskTick does not appear on the channel (a) message contract, so a consumer cannot read what it is not sent.

ADR-103 — Semantic Parity Needs a Non-Visual Expression, Not a Fourth Profile

Status: Accepted · Date: 2026-08-06 · Amends 061 · interacts with 027, 068 · disposes of A11Y-F-04

Context

ADR-061 requires presentation effects to target semantic parity, each declaring an equivalent expression per profile: a Sanity distortion is a screen-space post-process on A, a border-tint substitution on B, an icon and text cue on C. Per-effect × per-profile is a blank-blocks-release row in the Guardrail 7 matrix.

A screen-reader user on Profile A receives the post-process, which is to say nothing. ADR-061’s own stated failure applies with one word changed: a Keeper narrating “you all feel the world twist” while the screen-reader player has been shown nothing.

The structural problem is that the two matrices have different axes. Profiles resolve by hardware capability; a blind player with a good GPU resolves to Profile A, correctly, because the machine can render it. A screen reader is not a hardware capability, which is why Guardrail 7 models it as a row — a capability that may be absent — while ADR-061’s obligation is enumerated along the column axis.

An effect can therefore satisfy ADR-061 completely — three profiles, three expressions, no blank cells — and reach nobody using assistive technology. This is the enumeration-axis failure mode: ADR-061’s list is complete with respect to the axis it chose.

Decision

  1. Every presentation effect declares a non-visual expression in addition to its per-profile visual ones. It is a fourth declaration on the same effect, not a fourth profile — profiles remain a hardware-capability resolution and gain no accessibility semantics.
  2. The non-visual expression is text on the semantic mirror, subject to ADR-101’s schedule and to the same per-viewer filter as every other disclosure.
  3. “None” is not an admissible declaration, on the same principle by which ADR-068 refuses a UI descriptor with no viable C-Companion rendering and ADR-005 refuses a bundle with no 2D bake. An effect that cannot be expressed non-visually is an effect that cannot ship.
  4. The declaration is authored with the effect, by whoever authors the effect. Retrofitting it centrally would put a writer who has never seen the effect in charge of what it means.

Alternatives Considered and Why Rejected

  • Add a fourth profile (Profile D / accessible). Rejected, and it is the obvious proposal. Profiles are resolved from hardware capability, so a “profile” for assistive technology would either mis-resolve for a blind player on a good GPU or require the resolver to detect assistive technology — which is both unreliable and a fingerprinting surface. It also implies the player loses the visual profile, when many screen-reader users are sighted.
  • Derive the non-visual expression from the effect’s name or metadata. Rejected as a fabrication: “Sanity distortion” tells a generator nothing about what the player should be told has happened. This is ADR-045’s invented alternative problem in a different medium.
  • Let the Keeper narrate it. Rejected because it is the failure ADR-061 was written about, not a fix for it — the Keeper’s narration is precisely what is left dangling when a player received nothing.
  • Make it advisory rather than blocking. Rejected: ADR-027’s blank-blocks-release rule exists because a matrix of good intentions is prose, and ADR-064 had to strengthen it once already.

Consequences (including negative)

  • Authoring cost rises for every effect, permanently. Accepted: the alternative is a parity guarantee with a hole in it, and the hole is the population least able to work around it.
  • Some effects will be genuinely hard to express, and clause 3 means those effects do not ship rather than shipping silently inaccessible. That will be unpopular at least once.
  • Guardrail 7’s matrix grows a dimension. It was effect × profile; it is now effect × (profile ∪ non-visual). The cell count rises by one column, not by a factor.

Rights-holders (ADR-079)

None.

Enforcement

  • Registration-time refusal, the mechanism ADR-068 and ADR-005 already use: an effect declaration without a non-visual expression is refused at registration, not at render.
  • A blank-blocks-release cell per effect in the Guardrail 7 matrix, with ADR-064’s strengthening applying unchanged — a cell whose test does not exist is treated as blank.

Directive D-6.1 — Keyboard Input Is Permanently on the Server-Driven Path, and This Is Written Down

Disposes of A11Y-F-05. Not an ADR, because it decides nothing.

ADR-050 acquires an ephemeral lease when “the drag / draw / dice interaction begins” and renews it while the interaction is live. Keyboard-driven movement is discrete, with arbitrary pauses, so it does not present a continuous interaction to acquire against.

The architecture already handles this correctly: §5.1.1 rule 2 says a client that fails to acquire a lease “does not predict; it falls back to server-driven movement”. Nothing is broken and nothing needs deciding, which is why this is a directive.

What was missing is that the consequence is systematic and unstated:

  1. Keyboard-only users are permanently on the server-driven path. Their movement is correct and one round trip less responsive. The Guardrail 7 accessibility row must say so, because a row whose behaviour is “undefined” cannot be read as covering it.
  2. A lease expiring mid-interaction renders as ADR-050’s deliberate return to staleAnchor, which §5.1.1 rule 6 requires be presented “as a deliberate return with a brief explanation rather than as a glitch”. That explanation is currently a visual affordance. Under ADR-103 it needs a non-visual expression like any other; under ADR-101 it is scheduled like any other announcement.
  3. Do not “fix” this by lengthening the lease for keyboard input. A lease held across an arbitrary pause is a lease held by an absent client, which is what the 2 s expiry exists to prevent, and ADR-067 already releases leases on visibilitychange for the same reason.

Enforcement: the Guardrail 7 accessibility row, which is a blank-blocks-release cell under ADR-027 as strengthened by ADR-064.


§11 Index Lines

ADR-101: The ARIA announcement stream is a disclosure channel in its own right — rate, ordering and interrupting silence carry information that a containment property over the mirror cannot see, which is the same separation §14.7 already makes between revealed-set containment and ADR-034's correction-timing rule; announcement scheduling is therefore a function of tick arrival rather than of what changed, a tick with nothing to announce still advances the schedule so silence is uninformative, batch ordering is a stable function of entity identity rather than of arrival order, and assertive pre-emption is reserved for event classes the player already knows about because pre-emption is a one-bit signal before any word is spoken. (Amends ADR-027; interacts with ADR-034, ADR-053.)

ADR-102: maskTick is a gate rather than a value, so ADR-053's attribution rule — which settles ownership of values appearing in both DOM and world space — did not reach it; the Network Worker holds the gate and publishes an entity on neither channel until the older of transformTick and maskTick covers it, rather than each consumer gating separately, which would be two implementations of one security property with the accessibility one written last against a rule phrased in drawing language; maskTick is not published to consumers, because a mask-arrival signal is ADR-101's channel by another route. (Amends ADR-053; Depends-on ADR-092.)

ADR-103: Semantic parity gains a non-visual expression per effect rather than a fourth profile, because profiles resolve from hardware capability and a screen reader is not one — so an effect can satisfy ADR-061 completely across three profiles and reach nobody using assistive technology; "none" is not an admissible declaration, on the same principle by which ADR-068 refuses a descriptor with no viable C-Companion rendering, and the declaration is authored with the effect because retrofitting it centrally puts a writer who has never seen the effect in charge of what it means. (Amends ADR-061.)