# ACCESSIBILITY SPECIFICATION

**Phase 2 contract.** Written 2026-08-09. Governing prompt:
`md/06_HUD_INTERACTION_FEEDBACK_ACCESSIBILITY.md` §6.

**The instruction being executed.** *"Explore without changing default Classic mechanics … For each
accessibility option document whether it is presentation-only / input-equivalent / mechanical assist
/ score-invalidating assist. If an assist changes challenge, label it."*

**The stance this document takes.** Defender is a hard game on purpose, and its difficulty is part of
what is being preserved. Accessibility work here is therefore split cleanly: everything that removes
a *barrier to perceiving or operating* the game is offered freely, on by default where the platform
signals it, and costs the player nothing. Everything that removes a *barrier to succeeding* is
offered too — because a game nobody can finish is not preserved either — but it is labeled, it is
recorded in the session metadata, and it does not enter the same score table. Nobody is excluded and
nobody is deceived.

**Binding upstream.** `ARCHITECTURE.md`, `HUD_RADAR_SPEC.md`, `INTERACTION_FEEDBACK_SPEC.md`,
`DISPLAY_GEOMETRY.md`.

---

## 1. The four classes, defined precisely

The classification is not a vibe. Each class has a mechanical test, and every option in §3 has been
run against it.

### Class P — **Presentation-only**

> The simulation is bit-identical. Running the same `{seed, inputSequence, tickCount}` with the
> option on and off produces identical state at every tick.

**Test:** `assertReplayIdentical(replay, {option: on}, {option: off})` — identical score-event stream,
identical per-frame entity counts, identical final RNG state.
**Score effect:** none. Scores are fully comparable. Replays are interchangeable.
**Label in UI:** none required.

### Class I — **Input-equivalent**

> The simulation is untouched, and the option changes only how the seven boolean input lines are
> produced from the user's hardware. **The produced sequence of input states must lie inside the set
> of sequences a cabinet's switches could have produced.**

That last clause is the whole test, and it excludes more than it first appears. A *hold-to-toggle*
converter for thrust is Class I, because the resulting thrust line is still an arbitrary boolean the
cabinet's toggle-switch-shaped brain could have produced. **Auto-fire is not**, because it produces
a fire-line pattern at a rate no human hand on a cabinet button could sustain, and firing rate is a
difficulty parameter.

**Test:** with the option on, record the seven-line input trace; assert every state transition is
attributable to a user action or to a bounded state machine over user actions, with no timer-driven
transitions at a rate above 10 Hz.
**Score effect:** none. Scores comparable. Replays interchangeable (a replay stores the *input
lines*, not the key events, so the converter is upstream of the recording).
**Label in UI:** none required.

### Class M — **Mechanical assist**

> The simulation behaves differently. A rule, a rate, a constant or a gate has changed.

**Score effect:** the run is **flagged**. It is scored, it is shown, it goes into a separate
"Assisted" table, and its metadata names every assist that was active. It is not deleted and it is
not sneered at.
**Label in UI:** required, at three points — in the settings row itself, as a persistent marker in
the status band while active, and on the score screen. Wording is specified per option.

### Class S — **Score-invalidating assist**

> The simulation behaves differently in a way that removes the run's meaning as a Defender
> performance: unlimited resources, invulnerability, time manipulation, or direct state editing.

**Score effect:** **no score is recorded at all.** The Hall of Fame is not offered. Replay metadata
carries `scored: false` and the reason.
**Label in UI:** required, and the status-band marker is persistent and unmissable.

### The one-way ratchet

**Enabling a Class M or S option mid-run immediately and permanently downgrades that run.** Turning
it back off does not restore the run's status. This is the only way the classification means
anything. A run's class is `max(class of every option enabled at any point during the run)`.

---

## 2. Platform defaults — what is on before the player touches anything

| Signal | Effect | Class |
|---|---|---|
| `prefers-reduced-motion: reduce` | Screen shake off (`shakeScale = 0`); page-transition animations off; the shell's own motion disabled. **The game's own motion — the ship, the camera ease, the scanner cadence — is untouched**, because it is the game. | P |
| `prefers-contrast: more` | High-contrast scanner palette (A-08) on; shell UI contrast raised. | P |
| `prefers-color-scheme` | Shell chrome only. The game canvas is always black-surrounded (`DISPLAY_GEOMETRY.md` §5.1 rule 5). | P |
| `prefers-reduced-transparency` | Shell overlays become opaque. | P |
| Screen reader detected (heuristic: focus moves without pointer events) | Nothing is auto-enabled. **We do not guess at disability.** The shell is fully navigable and announced regardless; see §6. | — |

**Nothing in Class M or S is ever enabled by a platform signal.** A media query cannot consent to a
difficulty change.

---

## 3. The option matrix

Every option from `md/06` §6, in its order, plus the ones the analysis showed to be missing.

| ID | Option | Class | Default | Label required |
|---|---|---|---|---|
| A-01 | Full remapping (keyboard, gamepad) | **I** | on | no |
| A-02 | Sticky / hold-alternative for Thrust | **I** | off | no |
| A-02b | Sticky / hold-alternative for Up and Down | **I** | off | no |
| A-03 | Auto-fire | **M** | off | **yes** |
| A-04 | One-button mode (single key cycles-and-fires) | **S** | off | **yes** |
| A-05 | Reduced flash | **P** | off (on if reduced-motion) | no |
| A-06 | Reduced bloom | **P** | off | no |
| A-07 | Reduced screen shake | **P** | off (on if reduced-motion) | no |
| A-08 | High-contrast scanner | **P** | off (on if prefers-contrast) | no |
| A-09 | Color-vision palettes (4 variants) | **P** | off | no |
| A-10 | Larger HUD (1×, 1.5×, 2×) | **P** | 1× | no |
| A-11 | Audio cue reinforcement | **P** | off | no |
| A-12 | Captions / event text | **P** | off (one caption on by default in Enhanced) | no |
| A-13 | Haptic cue reinforcement | **P** | on when a gamepad is present | no |
| A-14 | Pause-friendly practice mode | **S** | off | **yes** |
| A-15 | Configurable hyperspace flashing | **P** | standard | no |
| A-16 | Photosensitivity-safe presentation | **P** | off | no |
| A-17 | Slow motion (0.75× / 0.5× / 0.25×) | **S** | off | **yes** |
| A-18 | Extra lives (3 / 5 / 9) | **M** | 3 (factory) | **yes** if ≠ factory |
| A-19 | Invulnerability | **S** | off | **yes** |
| A-20 | Hyperspace never kills | **M** | off | **yes** |
| A-21 | Scanner smoothing (60 Hz blips) | **P** | off | **yes**¹ |
| A-22 | Larger blips | **P** | off | no |
| A-23 | Input latency compensation | — | **not offered** | — |
| A-24 | Aim assist / target lock | — | **not offered** | — |
| A-25 | Shell keyboard navigation & screen-reader support | **P** | always on | no |
| A-26 | Shell text scaling & reflow | **P** | follows the browser | no |
| A-27 | Audio: master / effects / drone levels, mono downmix | **P** | 100 / 100 / 100, stereo | no |
| A-28 | Pause on focus loss | **P** | on | no |
| A-29 | Confirm-before-hyperspace | **I** | off | no |

¹ A-21 is Class P by the mechanical test — the core is untouched — but it is labeled anyway, because
a veteran will feel it and is entitled to be told. See `HUD_RADAR_SPEC.md` §9.3. This is the only
Class P option that carries a label, and the exception is deliberate.

---

## 4. Per-option specification

---

### A-01 · Full remapping — **Class I**, default on

Fully specified in `INTERACTION_FEEDBACK_SPEC.md` §6.5. Summary of the accessibility-relevant
properties:

- Bindings key off `KeyboardEvent.code` (physical position), labels come from
  `navigator.keyboard.getLayoutMap()`. A player on any layout gets the same hand shape and correct
  labels.
- Up to **3 bindings per action**, so a player can bind both a key and a gamepad button and use
  whichever hand is available that day.
- **Any action may be left unbound** — a warning, never an error.
- **Modifier-only bindings are permitted** (`LeftShift`, `RightAlt`) for players who cannot reach the
  letter keys.
- No chord bindings. A chord is a two-finger requirement, which is exactly the barrier remapping
  exists to remove.
- Remapping is reachable **from the pause menu without leaving the run** and takes effect on the next
  frame.

**Why Class I:** the six switch lines are produced from different physical sources; the lines
themselves are unchanged.

---

### A-02 · Sticky / hold-alternative for Thrust — **Class I**, default off

**Problem.** Thrust is the most-held control in the game. Sustained holding is painful or impossible
for players with reduced grip strength, tremor, or a single-switch input device.

**Behavior.** Three modes, selectable:

| Mode | Semantics |
|---|---|
| `hold` (default) | The line follows the key. Cabinet behavior. |
| `toggle` | A press flips the line. A second press flips it back. |
| `latch` | A press sets the line; **any other gameplay input clears it**. Thrust ends when you do something else, which is how thrust is used in practice. |

**Why Class I and not Class M.** The thrust line is still an arbitrary boolean over time. The
converter is a bounded state machine driven entirely by user edges with **no timer-driven
transitions**. The cabinet's thrust button could have been held down with a matchstick and the game
would behave identically. The *difficulty* of holding a button is not a Defender mechanic; the
*consequence* of the line being high is, and that is untouched.

**Interaction with the feedback layer.** In `toggle` and `latch`, the input overlay and the Control
Trainer show thrust as a **latched** state (a filled indicator rather than a pressed one), so the
player can always see the line's true value. This is required — a latched control the player cannot
see is a new failure mode.

**Edge cases.** On window blur, latched thrust is **cleared** along with every other held input
(`SHELL_AND_LIFECYCLE_SPEC.md` §5.2) — a latched line surviving a blur means returning to a ship at
terminal velocity. On death, the latch is cleared. On gamepad disconnect, cleared.

---

### A-02b · Sticky Up / Down — **Class I**, default off

Same three modes for the two stick lines. Additional rule: **`toggle` and `latch` on Up and Down are
mutually exclusive** — setting one clears the other, because the cabinet's 2-way stick could not
report both. In `latch`, pressing the opposite direction clears the first.

---

### A-03 · Auto-fire — **Class M**, default off, **labeled**

**Behavior.** While the fire binding is held, a fire edge is synthesized every **N frames**, where
N is selectable: 4 (15 Hz), 6 (10 Hz), 8 (7.5 Hz). Default 6 when enabled. All three sit below the
**20 Hz trigger ceiling** a human hand reaches on the cabinet button (`INPUT_MAPPING.md` §5.2), which
is why the fastest option is 15 Hz and not higher — auto-fire exists to remove a motor barrier, not
to exceed the hand.

**Why Class M and not Class I.** The rate is timer-driven, not user-driven, and it exceeds what a
hand on a cabinet button sustains. More importantly, **the laser has an on-screen cap and a cadence**
(`CONTROL_SYSTEM_SPEC.md` owns the numbers), so firing rate is a real difficulty parameter. Changing
it changes the game.

**Label.** Settings row: *"Auto-fire — changes firing rate. Runs are marked as assisted."* Status
band while active: `AUTO` in the deviation line. Score screen: *"Assisted: auto-fire (10 Hz)."*

**Why offer it at all.** Rapid repeated actuation is the single most common motor barrier in arcade
games and the one most likely to cause injury. A player who cannot tap cannot play Defender at all
without it. Marking the run is a fair price; excluding the player is not.

---

### A-04 · One-button mode — **Class S**, default off, **labeled**

**Behavior.** A single switch (or key) cycles through a radial action list and actuates. Intended
for single-switch and sip-and-puff users.

**Why Class S.** It cannot produce simultaneous inputs, so it cannot produce Defender's core
maneuvers; conversely it makes each action free of timing pressure. The resulting game is
recognisably different in both directions. There is no honest way to call the score comparable.

**Design.** Dwell-to-select with a configurable dwell (0.4–2.0 s), a fixed action order (Thrust, Fire,
Up, Down, Reverse, Smart Bomb, Hyperspace), a visible radial selector on the presented surface, and
**the simulation is paused while the selector is open** — which is why this is Class S rather than
merely M.

**Label.** Persistent `1-BTN` marker; score screen states *"Not scored: one-button mode."*

---

### A-05 · Reduced flash — **Class P**, default off (on when `prefers-reduced-motion`)

**What it changes.** Only the four full-screen luminance events cataloged in `HUD_RADAR_SPEC.md`
§4.12. See §5 of this document for the complete analysis and the exact substitutions.

**What it does not change.** Sprite-level animation, the laser's 37-color cycle, the palette
cyclers, explosion particles. Those are handled by A-16 (photosensitivity-safe mode), which is
stricter.

**Why Class P.** `pcram` is a *shadow buffer*; the simulation writes it and the renderer reads it.
Substituting what the renderer does with those values changes no simulation state.

---

### A-06 · Reduced bloom — **Class P**, default off

Enhanced renderer only. Three levels: full, reduced (50 % radius, 40 % intensity), none. Classic
Mode has no bloom at all, so this control is disabled and grayed in Classic with the explanation
*"Classic Mode has no bloom."*

**Why it matters beyond taste.** Bloom on a 1980 vector-ish palette with saturated primaries on black
produces glare that measurably reduces legibility for players with cataracts, corneal scarring, or
photophobia. "None" must be a real zero, not a small value.

---

### A-07 · Reduced screen shake — **Class P**, default off (on when `prefers-reduced-motion`)

`shakeScale` slider 0.0–1.5, default 1.0, and a single "off" control that sets 0.0. At 0.0 **no
translation is applied at all** — not a small one.

Shake is Enhanced-only (`INTERACTION_FEEDBACK_SPEC.md` D-2), so in Classic this control is disabled
with the explanation *"Classic Mode has no screen shake."* That sentence is also a small piece of
preservation education, which is the point of the Lab.

---

### A-08 · High-contrast scanner — **Class P**, default off (on when `prefers-contrast: more`)

**Problem.** The scanner is 128 × 32 pixels showing up to 100 objects in seven colors, presented at
roughly 4× on a modern panel. The original palette was chosen for a phosphor CRT and several pairs
are close in luminance: green `#00AE00` (L ≈ 0.30) against gray `#8989A0` (L ≈ 0.29) is a **1.02:1
luminance ratio** — effectively identical for a player with low vision.

**Behavior.** Blip colors are remapped within the same hue families to maximize pairwise
separation, **preserving three invariants**:

1. **Class count is unchanged** — seven visible classes, seven colors.
2. **The shimmer/steady distinction is preserved.** Pod and Mutant use animated slots; their
   remapped colors must still animate. This is the highest-value discrimination on the scanner
   (`HUD_RADAR_SPEC.md` §4.6) and losing it would be a net accessibility *loss*.
3. **Relative hue families are preserved** — the Lander stays yellow-over-green, the Bomber stays
   magenta — so a player switching between modes is not relearning.

**The remap:**

| Class | Original | High-contrast | Contrast vs black |
|---|---|---|---|
| Lander | `#FFAE00` / `#00AE00` | `#FFC63C` / `#37D437` | 9.1 / 8.4 |
| Mutant | cycler / `#00AE00` | cycler (brightened) / `#37D437` | — / 8.4 |
| Baiter | `#00AE00` | `#00FF6E` | 12.6 |
| Bomber | `#FF00FF` | `#FF6BFF` | 8.9 |
| Pod | cycler | cycler (brightened, wider range) | — |
| Swarmer | `#FF0000` / `#FFAE00` | `#FF5B4A` / `#FFC63C` | 6.5 / 12.0 |
| Humanoid | `#8989A0` | `#E8E8FF` | 16.9 |

The humanoid gets the largest lift deliberately: finding humanoids is the game's central task and
gray-on-black at 2 × 2 pixels is the hardest read on the display.

**Why Class P.** Palette only. Every mapping is applied at present time; the framebuffer nibbles are
unchanged.

---

### A-09 · Color-vision palettes — **Class P**, default off

Four variants, each a full 16-entry palette replacement applied at present time:

| Variant | Target | Strategy |
|---|---|---|
| `deuteranopia` | red-green, ~6 % of men | Red and green separated on the blue/yellow axis; the Lander's yellow-over-green becomes yellow-over-cyan. |
| `protanopia` | red-green, red-dim | Reds lifted in luminance; the Swarmer's red/yellow speckle becomes violet/yellow. |
| `tritanopia` | blue-yellow, rare | Blue bezel shifted toward red; yellow shifted toward white. |
| `monochrome` | full achromatopsia, and CRT-monochrome authenticity | Every class mapped to a distinct **luminance** step ≥ 12 % apart, and the two cycler classes given a **distinct blink pattern** rather than a color cycle. |

**The `monochrome` variant is the hard one and the most important.** With hue removed, seven classes
must be distinguished by luminance and motion alone. The specification:

| Class | Luminance | Pattern |
|---|---|---|
| Humanoid | 100 % | steady |
| Player marker | 100 % | steady (already white, already a distinct shape) |
| Pod | 85 % | **blink 2 on / 2 off** |
| Mutant | 70 % | **blink 4 on / 4 off** |
| Baiter | 55 % | steady |
| Bomber | 40 % | steady |
| Lander | 28 % over 55 % | two-tone, steady |
| Swarmer | 28 % | steady |

Blink periods are in **presented frames**, not simulation frames, and are driven by the presentation
clock — so they are Class P even though the scanner itself updates at 7.5 Hz.

**Rule.** No color-vision palette may reduce the number of distinguishable classes below seven. T-14
asserts this by pairwise ΔE (or Δ-luminance for `monochrome`) across all 21 class pairs.

---

### A-10 · Larger HUD — **Class P**, default 1×

Scales the score, ships, smart bombs and (optionally) the scanner into an overlay drawn on the
**presented** surface, at 1×, 1.5× or 2×. The framebuffer band is still drawn underneath and is
simply covered.

**Hard constraints, from `HUD_RADAR_SPEC.md` §9.2:**

- The **6-digit score cap, the 5-ship cap and the 3-bomb cap are preserved at every scale.** Showing
  a seventh ship because there is now room is new information and is forbidden.
- The scanner may be scaled but **its 2048-px world compression and its player-centering may not
  change**. A 2× scanner shows the same world at twice the size, not more of it.
- At 2× on a 4:3 presentation the enlarged HUD covers roughly the top 18 % of the playfield. **The
  overlay is drawn with 70 % opacity over the playfield region** so nothing is fully hidden, and the
  player is told this in the setting's description.

---

### A-11 · Audio cue reinforcement — **Class P**, default off

Adds a distinct short cue for events that are visually communicated but have **no sound at all** in
the original. From the silent-event inventory (`INTERACTION_FEEDBACK_SPEC.md` §4.23):

| Event | Cue | Why it earns one |
|---|---|---|
| Reverse fires | R-cue, 40 ms click, pitched by facing | The only control with no audio confirmation |
| Hyperspace departs | H-cue, 250 ms downward glide | 15 silent black frames read as a crash |
| Mutation | two-note rise, 120 ms, once per mutation | Silent, and it changes the threat class |
| Planet restored | three-note rise, 300 ms | Silent, and it is a major state change |
| Input refused | N-cue, 60 ms thud | Silent, and it is indistinguishable from a dropped input |
| Bonus ship at the display cap | 200 ms chime layered under `RPSND` | The HUD does not change at the cap |

All cues are synthesized in the same `AudioWorklet` as the game's sounds but on a **separate gain
bus** with its own level control, so they can be balanced against the game or muted independently.
They are **never** routed through the six-bit command bus — they are not Williams sounds and must not
contaminate the audio regression corpus (`md/07` §8).

**Why Class P.** Nothing in the simulation is read or written; the cue layer subscribes to the same
event stream the renderer does.

---

### A-12 · Captions / event text — **Class P**, default off (one exception)

Short uppercase strings in the game's own 6 × 8 font, drawn on the presented surface in a fixed
two-line region **below the playfield** (in the letterbox/pillarbox margin where one exists, or in
the bottom 16 presented rows otherwise). Never over the scanner.

**The caption set:**

```
ABDUCTION          HUMANOID CAUGHT        HUMANOID LOST
HUMANOID RETURNED  MUTANT                 BAITER
EXTRA SHIP + BOMB  SMART BOMB             NO SMART BOMBS
HYPERSPACE         WAVE n COMPLETE        PLANET DESTROYED
PLANET RESTORED    SHIP LOST              GAME OVER
```

Rules:

- **At most 2 lines**, newest at the bottom, each held **60 frames** then removed.
- **Priority-ordered**: `PLANET DESTROYED` > `SHIP LOST` > `GAME OVER` > humanoid events > everything
  else. A higher-priority caption displaces the older line rather than queueing behind it.
- **`PLANET DESTROYED` is on by default in Enhanced Mode** (`INTERACTION_FEEDBACK_SPEC.md` §4.19) —
  it is a permanent, game-altering state change communicated in Classic almost entirely by a visual
  sequence a low-vision player cannot read.
- Captions are also emitted to an **ARIA live region** with `aria-live="polite"` (`assertive` for
  `SHIP LOST` and `GAME OVER`), so a screen-reader user gets the same stream. See §6.
- Caption text scales with A-10.

---

### A-13 · Haptic cue reinforcement — **Class P**, default on when a gamepad is present

The effect table is in `INTERACTION_FEEDBACK_SPEC.md` §7.1. Accessibility-specific rules:

- `hapticScale = 0` **makes no gamepad API calls at all** — some assistive controllers mis-handle
  zero-magnitude effects.
- Haptics are **never one of the two channels required by the redundancy floor**
  (`INTERACTION_FEEDBACK_SPEC.md` §5.3). They are always additive.
- Haptics must never affect timing (R-3). T-43 there enforces it.

---

### A-14 · Pause-friendly practice mode — **Class S**, default off, **labeled**

**Behavior.** The simulation may be paused **at any moment, including mid-death and mid-hyperspace**,
and single-stepped forward one frame at a time. Enemy spawns are unchanged; nothing is easier except
that time stops when you need it to.

**Why Class S and not M.** Unlimited think time removes the reaction-speed component entirely, which
is a large fraction of what Defender measures. Being honest about that is better than pretending
pausing is free.

**Why offer it.** For players with slower processing speed, for players learning the scanner, and for
anyone studying the reconstruction — which is, after all, the Lab's stated purpose. It is also the
same machinery the Lab's frame-advance uses (`SHELL_AND_LIFECYCLE_SPEC.md` §8.2); the difference is
only whether a score is claimed afterwards.

**Label.** `PRACTICE` persistent marker; score screen states *"Not scored: practice mode."*

**Not to be confused with ordinary pause** (A-28), which is a lifecycle feature, freezes everything,
and is Class P.

---

### A-15 · Configurable hyperspace flashing — **Class P**, default `standard`

Three settings:

| Setting | Departure (15 frames) | Arrival (40 frames) |
|---|---|---|
| `standard` | screen goes black, as the original | `APVCT` appear effect at full intensity |
| `soft` | black fades in over 4 frames instead of cutting | appear effect at 50 % intensity, no high-frequency component |
| `minimal` | screen dims to 20 %, never fully black | a static outline that fades in over 40 frames, no flicker |

**The frame budget is identical in all three (15 + 40 = 55 frames).** Only what is drawn changes.
This is what makes it Class P and it is a hard constraint — a builder who "improves" the timing has
changed a mechanic.

---

### A-16 · Photosensitivity-safe presentation — **Class P**, default off

The strictest presentation mode. Fully specified in §5.

---

### A-17 · Slow motion — **Class S**, default off, **labeled**

**Behavior.** The **host** calls `step()` at 0.75×, 0.5× or 0.25× of the 60.09615 Hz rate. **The
simulation is unchanged** — every constant, every tick, every RNG call is identical; only wall-clock
pacing differs.

**Why Class S despite the simulation being bit-identical.** This is the case that shows why the
classification is about *the run*, not about *the code*. A replay recorded at 0.25× replays
identically at 1×. But the human who produced the input sequence had four times as long to produce
it, and the score is therefore not a Defender score. Class S is a statement about the performance,
not about determinism.

**Interaction with A-14.** Both may be on. Both are S. The run is unscored either way.

---

### A-18 · Extra lives — **Class M**, default 3, **labeled if ≠ factory**

The operator's `NSHIP` CMOS setting, exposed. Values 3 (factory), 5, 9. Remember that **starting
smart bombs follow the ships setting** (L-024), so choosing 5 ships also gives 5 smart bombs —
which the UI must state, because it is surprising and it is authentic.

At the factory value this is not an assist at all and carries no label. At 5 or 9 it is Class M:
*"More ships — changes difficulty. Runs are marked as assisted."*

**This is the one Class M option a real operator could set on a real cabinet**, which is why it is M
rather than S: high scores set on a 5-ship machine were and are a real category.

---

### A-19 · Invulnerability — **Class S**, default off, **labeled**

Sets `STATUS` bit 4 (player image collision inactive) permanently. The ship still cannot pass through
terrain (there is no terrain collision to begin with — L-144) and hyperspace still cannot kill
(because `PLEND` is never reached). Every other mechanic runs.

Offered because it is the only way some players will ever see wave 20, and because it is
indispensable for studying late-wave behavior. Unscored, unmissably marked.

---

### A-20 · Hyperspace never kills — **Class M**, default off, **labeled**

Suppresses the `LSEED > 192` test. **Nothing else changes** — the 15-frame black, the random
destination, the zeroed velocity, the 40-frame materialise all stay.

**Why M and not S.** It removes one 24.6 % risk from one rarely-used action. That is a genuine
difficulty change but it is bounded and local; the rest of the game is intact. A run with it on is
still recognisably a Defender run.

Label: *"Hyperspace is safe — changes difficulty (normally fatal 1 time in 4). Runs are marked as
assisted."*

---

### A-21 · Scanner smoothing — **Class P**, default off, **labeled anyway**

Blips render at their true world position every presented frame instead of stepping on the 8-frame
cadence. See `HUD_RADAR_SPEC.md` §9.3. Unavailable in Classic Mode.

Labeled despite being Class P because a veteran will feel it. This is the exception that proves the
labeling rule is about honesty, not about mechanics.

---

### A-22 · Larger blips — **Class P**, default off

2 × 2 framebuffer blips presented at 3 × 3 or 4 × 4 device-scaled pixels, centered on the true
position. Positions and count are unchanged, so no information is added. At 4 × 4, adjacent blips
can visually merge — the setting's description says so, and the default remains 2 × 2.

---

### A-23 · Input latency compensation — **not offered**

**Deliberately absent.** Predicting input to reduce apparent latency means running the simulation
ahead and rolling back, which breaks determinism, breaks replay, and breaks the one thing this
project is for. The correct answer to latency is to have less of it: a fixed 16.640 ms step, input
sampled at the top of the step, one presented frame of buffering, and no smoothing
(`md/06` §10 — *"do not smooth controls at the expense of Defender feel"*).

**What is offered instead:** the Lab overlay reports measured end-to-end latency
(`SHELL_AND_LIFECYCLE_SPEC.md` §7.2) so a player can see and fix their own environment.

---

### A-24 · Aim assist / target lock — **not offered**

There is nothing to assist. Defender's laser is a horizontal beam at the ship's altitude; "aiming" is
flying to the right row. An assist would have to move the ship, which is playing the game for the
player. Requests for this are answered with A-19 (invulnerability) and A-17 (slow motion), which are
honest about what they do.

---

### A-25 · Shell keyboard navigation and screen-reader support — **Class P**, always on

Not optional and not a setting. See §6.

---

### A-26 · Text scaling and reflow — **Class P**, follows the browser

All shell chrome (the launch page, the pause menu, the settings, the score screen, the Lab overlay's
text) uses relative units and reflows to 200 % zoom and to a 320 px viewport without horizontal
scrolling, per WCAG 1.4.4 and 1.4.10. **The game canvas is exempt** — it is a fixed-aspect raster and
scaling it is A-10's job.

---

### A-27 · Audio controls — **Class P**

- **Master**, **Effects** and **Background drone** levels, 0–100 each. The drone gets its own control
  because it is continuous, low-frequency, and the most likely single element to cause fatigue or to
  mask speech for a player using a screen reader simultaneously.
- **Mono downmix** — collapses the Enhanced stereo pan. Required for single-sided hearing loss, and
  it is also the authentic configuration (the cabinet had one speaker).
- **Cue bus level** (A-11) — separate from Effects.
- All levels are applied at the Web Audio graph's output gain nodes. The command stream is untouched,
  so the audio regression corpus is unaffected.

---

### A-28 · Pause on focus loss — **Class P**, default on

Lifecycle feature; specified in `SHELL_AND_LIFECYCLE_SPEC.md` §5.2. Listed here because turning it
**off** is an accessibility-relevant choice for players using external assistive software that steals
focus. With it off, the game continues to run on blur but audio is still suspended and a visible
"RUNNING IN BACKGROUND" marker appears on return.

---

### A-29 · Confirm-before-hyperspace — **Class I**, default off

Hyperspace kills 24.6 % of the time and is bound to the pinky key. An accidental press is a lost
ship. With this on, hyperspace requires **two presses within 40 frames**; the first press produces
the N-cue and a 40-frame HUD prompt.

**Why Class I.** It changes how the hyperspace line is produced from the user's key — a bounded state
machine over user edges, no timer-driven transitions. The simulation sees an ordinary single
hyperspace edge. It makes the game slightly *harder* if anything (the action takes longer), which is
a useful sanity check on the classification.

---

## 5. Photosensitivity — the analysis, and what A-05 and A-16 actually do

### 5.1 The measurement

We use the WCAG 2.3.1 general flash threshold as the operative rule: **no more than three flashes in
any one-second period**, where a "flash" is a pair of opposing changes in relative luminance of
≥ 10 % (with the darker state below 0.80 relative luminance) occupying more than 25 % of a
10-degree visual field — approximated, as WCAG allows, as **more than 25 % of the presented area**.

The reconstruction measures this directly rather than trusting the analysis:

```js
// Runs on the PRESENTED surface, once per presented frame, in the feedback layer.
// Never on the frame path of step(). Sampled at 1/16 resolution.
luma      = meanRelativeLuminance(presentedFrame, sampleGrid)
areaAbove = fractionOfArea(|luma_t − luma_{t−1}| >= 0.10)
if areaAbove > 0.25 and min(luma_t, luma_{t−1}) < 0.80:  recordTransition()
flashesPerSecond = transitionsInTrailing60Frames / 2
```

`HUD_RADAR_SPEC.md` T-53 asserts the budget; this is the instrument it asserts against.

### 5.2 Defender's actual flash inventory, measured against that rule

| Source | Flashes | Rate | Area | Verdict |
|---|---|---|---|---|
| **Planet destruction** — 16 full-screen color flashes, gaps of ~2 + `RMAX(i/8+1)` frames | 16 | **~15 Hz for the first half**, decelerating | 100 % | **Far over threshold. The worst event in the game by a wide margin.** |
| **Smart bomb** — `COM PCRAM` ×4 at 2-frame intervals | 2 | 15 Hz burst | 100 % | 2 flashes < 3, so a single bomb passes. **Two bombs within one second = 4 flashes: over.** Reachable — the debounce allows ~20 frames between bombs. |
| **Player death, step 4** — `PCRAM = $FF` for 2 frames | 1 | one-shot | 100 % | Passes alone. Combined with a smart bomb in the same second: over. |
| **Player death, step 3** — ship strobes blank/draw at 2 frames through 8 colors | 8 | 30 Hz | **small** (an 8 × 6 byte-column sprite ≈ 0.2 % of area) | Under the area threshold. Passes. |
| **Laser / score color cycle** — slot 1 stepped every 2 frames through `COLTAB` | continuous | 30 Hz | laser: a thin full-width beam ≈ 1–2 % of area | Under the area threshold. Passes — **but see A-16.** |
| **Pod / Mutant / mine cyclers** — random `COLTAB` entry every 6 frames | continuous | 10 Hz | small | Passes. |
| **Hall of Fame cursor** — 15-frame toggle | continuous | 2 Hz | tiny | Passes. |
| **Player-up flash (`FLPUP`)** — 20-frame period | continuous | 3 Hz | tiny | Passes. |
| **Enhanced bloom + explosion particles** | varies | varies | varies | **Must be measured, not assumed.** The instrument in §5.1 runs in Enhanced too. |

**Conclusion: Defender in its original form exceeds the WCAG flash threshold in exactly two
situations — planet destruction (badly) and repeated smart bombs (marginally).** Everything else is
under, mostly because the flashing elements are small. That is a specific, bounded problem with a
specific, bounded fix, which is much better news than "the whole game flashes".

### 5.3 A-05 Reduced Flash — the substitutions

| Event | Standard | Reduced Flash |
|---|---|---|
| Smart bomb | 2 × full-screen white inversions, 2 frames each | **One 20 % white wash held for 8 frames.** Same duration, same beat count in the audio, one gentle rise instead of two hard inversions. |
| Player death step 4 | 2 frames of `#FFFFFF` | **2 frames of 25 % white wash.** |
| Planet destruction | 16 random-color full-screen flashes | **16 luminance lifts of 12 %, 2 frames each, preserving the deceleration curve exactly.** The rhythm — which is the information — is intact; the amplitude is not. |
| Hyperspace | per A-15 | forced to `soft` |

**The frame budgets are identical in every row.** Reduced Flash changes amplitude, never timing.

### 5.4 A-16 Photosensitivity-safe presentation — the strict mode

Everything in A-05, plus:

1. **A hard governor.** The measured `flashesPerSecond` from §5.1 is clamped to **≤ 3**. If a
   transition would exceed the budget, the presented frame's luminance delta is scaled down until it
   falls below the 10 % threshold. This is a real feedback loop on the presented surface, not a
   static ruleset, so it also covers Enhanced bloom and particle effects that nobody analyzed.
2. **Slot-1 cycling clamped.** The laser/score color cycle is limited to a change every **8
   presented frames** (7.5 Hz) instead of every 2 (30 Hz), and the `COLTAB` walk is restricted to
   entries within a 25 % luminance band. The color still moves — the signature is preserved — but
   the temporal frequency drops out of the 3–60 Hz risk band's dense region.
3. **All cyclers clamped** the same way.
4. **Explosion particle brightness capped** at 70 % and bloom forced to "none".
5. **A pre-play notice.** Before the first run in a session with A-16 **off**, the launch page shows
   a single dismissible line: *"This reconstruction reproduces the original's screen flashes. A
   photosensitivity-safe mode is available in Settings."* It is one line, it is dismissible forever,
   and it appears before the game starts — not as a modal over a running game.

**Why Class P despite the governor.** The governor reads the presented framebuffer and writes the
presented framebuffer. It never reads simulation state and never writes it. T-11 asserts replay
identity with the governor active.

---

## 6. Screen readers, keyboard navigation, and the parts that are not the game

The canvas is not accessible and cannot be made accessible — a 292 × 240 raster of a 1981 arcade
game has no semantic structure to expose. **Everything around it must be perfect**, and the event
stream must be narrated.

### 6.1 The shell

| Requirement | Implementation |
|---|---|
| Every control reachable by keyboard | Native `<button>`, `<a>`, `<input>` elements only. No `div` with a click handler anywhere in the shell. |
| Visible focus | A 2 px `--accent` outline with 2 px offset on `:focus-visible`, on every interactive element, at ≥ 3:1 against its background. |
| Logical tab order | DOM order is reading order. No positive `tabindex`. |
| Landmarks | `<header>`, `<nav>`, `<main>`, `<footer>`; the game region is `<main>` with an `aria-label`. |
| Headings | One `<h1>`; no level skipped. |
| Focus trapping in dialogs | The pause menu and the settings panel trap focus, restore it on close, and close on `Escape`. |
| Names | Every control has an accessible name that matches its visible label. |
| Contrast | All shell text ≥ 4.5:1; large text and UI components ≥ 3:1, in both themes. The site's own `--accent` is already darkened on light backgrounds for this reason (`styles.css` line 19). |
| Zoom | 200 % zoom and a 320 px viewport with no horizontal scroll. |
| Motion | All shell animation respects `prefers-reduced-motion`. |

### 6.2 The canvas element

```html
<main>
  <canvas id="screen"
          role="img"
          aria-label="Defender game screen. Use the Lab overlay or the event log for a text account of play."
          tabindex="0"></canvas>
  <div id="event-log" role="log" aria-live="polite" aria-atomic="false"></div>
  <div id="alert-log" role="status" aria-live="assertive"></div>
</main>
```

The canvas is focusable so keyboard input has a target, and focusing it is what arms the input layer
(`SHELL_AND_LIFECYCLE_SPEC.md` §3.3).

### 6.3 The narrated event stream

The caption set (A-12) is emitted to `#event-log` (`polite`) with two exceptions routed to
`#alert-log` (`assertive`): `SHIP LOST` and `GAME OVER`. In addition, a **state summary** is emitted
on every wave start and every life start:

```
WAVE 4. THREE SHIPS. THREE SMART BOMBS. TEN HUMANOIDS. SCORE 18450.
```

This is on whenever captions are on, and it is the closest thing to a playable-by-ear Defender that
is honest to attempt. **We do not claim the game is playable without sight.** We claim that a
screen-reader user can follow a session, understand the state, operate every control, and read every
word of the case study — which is the achievable goal and is stated as such in the settings text.

### 6.4 The Lab overlay

The Lab instrumentation overlay (`SHELL_AND_LIFECYCLE_SPEC.md` §7) is drawn on the presented surface
for legibility, **and** mirrored into a visually-hidden `<table>` with proper headers, updated at 4 Hz
(not 60 — a live region updating 60 times a second is unusable). This is how a screen-reader user
inspects the reconstruction, which is the Lab's actual purpose.

---

## 7. The labeling contract

### 7.1 Where labels appear

Every Class M and Class S option, and A-21, appears in **three** places:

1. **In the settings row**, as a sentence, not an icon. Format:
   `<option name> — <what it changes>. Runs are marked as assisted.` or `… Runs are not scored.`
2. **In the status band**, as a persistent 3–8 character marker in a fixed deviation line drawn on
   the presented surface below the score. Multiple markers concatenate: `AUTO PRAC SLOW`.
3. **On the score screen and in exported replay metadata**, as a full list with values.

### 7.2 The wording rules

- **Say what changes, not that it is "easier".** *"Auto-fire — changes firing rate"* not
  *"Auto-fire — easier"*. The player knows why they turned it on.
- **Never use a warning color or an alarm icon.** The marker uses the same `--accent` as every other
  piece of Lab furniture. An assist is a legitimate configuration, not an error.
- **Never require a confirmation dialog to enable an assist.** The one-way ratchet (§1) is explained
  once in the settings header; a dialog per toggle is friction aimed at the wrong people.
- **State the ratchet exactly once, at the top of the assists section:** *"Turning any of these on
  marks the current run. Turning it back off does not unmark it."*

### 7.3 The score tables

Three tables, all shown, all reachable:

| Table | Contains |
|---|---|
| **Hall of Fame** | Class P and Class I runs only. The authentic table. Eight rows, three initials, the original entry sequence and the original organ tunes. |
| **Assisted** | Class M runs, each row annotated with its assists. |
| — | Class S runs are **not recorded**. The score screen shows the score and says why it was not recorded. |

Being in the Assisted table is not a lesser thing; it is a different thing, and the UI says so:
*"Assisted runs — scored with the settings that were on."*

---

## 8. Acceptance tests

### Classification integrity

**T-01 · Every Class P option is replay-identical.** For each of A-05, A-06, A-07, A-08, A-09, A-10,
A-11, A-12, A-13, A-15, A-16, A-21, A-22, A-26, A-27, A-28: run a 3000-frame replay with the option
off and on. Assert identical score-event streams, identical per-frame entity counts, identical final
RNG state. 16 × 2 runs.

**T-02 · All Class P options simultaneously are replay-identical.** Same replay, every Class P option
enabled at its most extreme value. Assert identity.

**T-03 · Every Class I option produces a cabinet-legal input trace.** For A-02, A-02b, A-29: drive
the converter with a scripted key sequence and assert that every transition in the seven-line output
is attributable to a user edge and that **no transition occurs on a frame with no user edge**, except
those the state machine's documented rules produce (blur-clear, death-clear, disconnect-clear).

**T-04 · Auto-fire is correctly Class M.** With A-03 at 10 Hz, run the same replay with it on and
off. Assert the score-event streams **differ** (proving it is not Class P) and that the run is
flagged `assisted`.

**T-05 · The ratchet is one-way.** Start a run. Enable A-03 at frame 500. Disable it at frame 600.
Assert the run's class at game over is `M`, not `P`.

**T-06 · Class S runs record no score.** Complete a run with A-14 on. Assert no Hall of Fame prompt,
`replay.metadata.scored === false`, and a stated reason.

**T-07 · Class M runs are scored into the Assisted table only.** Complete a run with A-18 = 5 ships.
Assert the score appears in the Assisted table with `["extra lives: 5"]` and does **not** appear in
the Hall of Fame.

**T-08 · Slow motion does not change the simulation.** Record a replay at 0.25×. Replay it at 1×.
Assert byte-identical state at every tick — **and** assert the run is nonetheless `scored: false`.

### Presentation

**T-09 · Caps survive every scale.** With A-10 at 2×, set lives = 9, bombs = 7, score =
`12 34 56 78`. Assert the presented HUD shows **5 ships, 3 bombs, and the six digits `345678`**.

**T-10 · Reduced flash preserves timing.** Fire a smart bomb with A-05 on and off. Assert the event
occupies exactly 8 frames in both cases and that the audio command stream is identical.

**T-11 · The photosensitivity governor is replay-safe.** Run the planet-destruction sequence with
A-16 on. Assert ≤ 3 measured flashes per second throughout, **and** assert the simulation state at
every tick is identical to the A-16-off run.

**T-12 · The flash budget is met in normal play.** Run a 10 000-frame recorded session including two
smart bombs 30 frames apart and one planet destruction, with A-16 on. Assert the measured
`flashesPerSecond` never exceeds 3.

**T-13 · The flash budget is exceeded without A-16 — and we know where.** Same session with A-16
off. Assert the budget is exceeded during the planet-destruction sequence and during the smart-bomb
pair, and **nowhere else**. *(This test documents the hazard rather than hiding it, and it fails
loudly if a future change introduces a new one.)*

**T-14 · Color-vision palettes preserve seven distinguishable classes.** For each of the four
variants, compute ΔE2000 (or Δ relative luminance for `monochrome`) across all 21 pairs of class
colors. Assert every pair exceeds the variant's threshold (ΔE ≥ 20, or Δ luminance ≥ 0.12), and that
the Pod and Mutant classes retain a temporal signature.

**T-15 · High-contrast scanner keeps the shimmer.** With A-08 on, assert the Pod and Mutant blip
colors still change between successive presented frames and the other five do not.

**T-16 · Humanoid contrast lift.** With A-08 on, assert the humanoid blip's contrast ratio against
`#000000` is ≥ 15:1.

**T-17 · Bloom "none" is zero.** With A-06 at "none", render a frame with a maximum-brightness
explosion. Assert every pixel outside the source geometry is unchanged from a no-bloom reference.

**T-18 · Shake off is zero.** With `shakeScale = 0`, run 3000 frames including 20 explosions. Assert
the presented surface's translation is exactly `(0, 0)` on every frame.

### Input

**T-19 · Sticky thrust clears on blur.** Latch thrust in `toggle` mode. Blur the window. Assert the
thrust line is low on the blur frame and that the input overlay shows it unlatched.

**T-20 · Sticky thrust clears on death.** Latch thrust. Kill the player. Assert the line is low.

**T-21 · Sticky Up/Down are mutually exclusive.** Latch Up. Press Down. Assert Up clears and Down
latches, and that the two lines are never simultaneously high.

**T-22 · Confirm-before-hyperspace needs two presses.** With A-29 on, press hyperspace once. Assert
no hyperspace occurred and the N-cue fired. Press again within 40 frames. Assert exactly one
hyperspace. Press once, wait 41 frames, press again. Assert **no** hyperspace occurred.

**T-23 · One-button mode pauses during selection.** With A-04 on, open the selector. Assert `step()`
is not called while it is open and that the tick count does not advance.

**T-24 · Modifier-only bindings work.** Bind Thrust to `LeftShift`. Assert holding it drives the
thrust line and that no browser shortcut is triggered.

### Labeling

**T-25 · Every M and S option is labeled in three places.** For each of A-03, A-04, A-14, A-17,
A-18 (at 5 and 9), A-19, A-20, and A-21: enable it, and assert (a) the settings row text matches the
specified format, (b) the status-band deviation line contains its marker, (c) the score screen lists
it. 9 options × 3 assertions.

**T-26 · Factory lives carry no label.** With A-18 = 3, assert no marker appears and the run is
Class P.

**T-27 · No warning colors on assists.** Assert every assist marker and settings row uses
`--accent` and that no assist control has `role="alert"` or a warning icon.

**T-28 · Markers concatenate.** Enable A-03, A-14 and A-17. Assert the deviation line reads
`AUTO PRAC SLOW` in that fixed order.

### Screen readers and keyboard

**T-29 · Keyboard-only traversal.** With no pointer events, assert every shell control is reachable
by `Tab`, that focus is always visible, and that the tab order matches DOM order.

**T-30 · Dialogs trap and restore focus.** Open the pause menu with `Escape`. Assert focus enters
the dialog, cannot leave by `Tab`, and returns to the invoking control on close.

**T-31 · Live regions carry the event stream.** With A-12 on, catch a humanoid. Assert
`#event-log` gains "HUMANOID CAUGHT". Lose a ship. Assert `#alert-log` — not `#event-log` — gains
"SHIP LOST".

**T-32 · State summary on wave start.** Assert `#event-log` receives a summary matching
`WAVE \d+\. \w+ SHIPS?\. \w+ SMART BOMBS?\. \w+ HUMANOIDS?\. SCORE \d+\.` at every wave start.

**T-33 · The Lab table mirrors the overlay at 4 Hz.** Assert the hidden table's contents match the
overlay's fields and that it updates no more than 4 times per second.

**T-34 · Contrast.** Assert every shell text/background pair meets 4.5:1 (3:1 for large text and UI
components) in both themes, computed, not eyeballed.

**T-35 · Zoom and reflow.** At 200 % zoom and at a 320 px viewport, assert no horizontal scrollbar on
the shell and that no control is clipped.

**T-36 · Reduced motion.** With `prefers-reduced-motion: reduce`, assert `shakeScale === 0`, all
shell animations are disabled, and **the game's own camera ease and scanner cadence are unchanged**.

---

## 9. Known deviations and open items

**D-1 · Class M and S options are additions the machine did not have.** A-03, A-04, A-14, A-17,
A-19, A-20 are not preservation; they are access. Each is off by default, each is labeled, each is
excluded from the authentic score table. A-18 is the exception — it is a real operator setting.

**D-2 · The `monochrome` color-vision variant adds a temporal signature** (blink patterns) that the
original did not have, to preserve the seven-class distinction when hue is removed. It is Class P by
the mechanical test but it is a genuine visual addition and is recorded as such.

**D-3 · The photosensitivity governor (A-16) can alter the presented image in ways not derivable
from the original.** It is a feedback loop with no reference implementation. It is off by default and
it is the strictest mode, chosen deliberately over a static ruleset because a static ruleset cannot
cover Enhanced effects nobody has written yet.

**D-4 · Captions and the state summary (A-12, §6.3) use the game's font but are not the game's
text.** Nothing like them existed. They are drawn outside the 292 × 240 window wherever the
letterbox permits, so they never contaminate a screenshot of the playfield — but a full-window
capture will contain them, and evidence screenshots must be taken with captions off.

**O-1 · The high-contrast palette (A-08) has not been validated with low-vision users.** The contrast
ratios are computed and the hue families are preserved, which is the defensible engineering answer,
but the actual question is whether a player can tell a Baiter from a Lander at a glance. **Interim:
ship it, with the ratios documented.** **What would settle it:** the accessibility-specialist critic
in `md/06`'s gauntlet, plus one low-vision playtester, with class-identification accuracy recorded.

**O-2 · The flash inventory in §5.2 is computed from source behavior, not measured from a running
build.** Every frame count is source- or ROM-confirmed, but **the areas and luminances are estimates
and nothing has been observed running** (Phase 1 gate, carried risk 3). The 25 %-area judgments for
the laser cycle and the death strobe are the least certain and the ones that decide whether those
events need clamping at all. **Interim: implement the measurement instrument in §5.1 and let it
decide at run time** — which is why the instrument exists rather than a static table. **What would
settle it:** run T-12 and T-13 against the first playable build and record the measured values in
`docs/qa/GAUNTLET_LOG.md`.

**O-3 · The Class I boundary for sticky thrust is a judgment call.** The argument in A-02 is that a
matchstick in the cabinet's thrust button produces the same line. A critic could reasonably say that
sustained holding is part of the physical challenge. **Interim: Class I, unlabelled.** **What would
settle it:** the accessibility specialist and the veteran player disagreeing in the gauntlet, and the
adjudicator ruling. If it is reclassified to M, only the label and the score table change — no code
moves.

**O-4 · Auto-fire's rate options (4/6/8 frames) are not calibrated against the laser's actual
cadence and on-screen cap**, which `CONTROL_SYSTEM_SPEC.md` owns. The 20 Hz hand ceiling is known
(`INPUT_MAPPING.md` §5.2) but the *simulation's* shot cadence is not restated here. If the cap makes
15 Hz
indistinguishable from 10 Hz, the fastest option is pointless; if the cadence gates below 7.5 Hz, the
slowest is a *penalty*. **Interim: ship all three and default to 6.** **What would settle it:** read
the laser cadence from the control spec once it lands and re-derive the three values from it, in the
same commit that changes them here.

**O-5 · Two-player accessibility is unspecified.** Defender's two-player mode is alternating, so both
players share one settings profile and one classification. Whether one player's assist should mark
the *other* player's score is unresolved. **Interim: it does — the run's class is the session's
class.** **What would settle it:** a decision, not evidence. Flagged so it is made deliberately.

**O-6 · No assistive-technology testing has been done.** Every §6 requirement is derived from WCAG
and from platform APIs. **Interim: implement to the checklist and test with VoiceOver + Safari and
NVDA + Firefox before Phase 3 closes.** **What would settle it:** those two passes, logged.
