# EVIDENCE LEDGER — Williams DEFENDER (1981)

**This file is the project's single source of truth about what we actually know.**

Every load-bearing mechanical claim established in Phase 1 appears here exactly once, with its
evidence and its confidence label. Claims are grouped by confidence, strongest first.
**INFERRED and UNRESOLVED entries are grouped at the end and flagged.** Nothing in Part E or Part F
may be implemented as though it were established original Defender behavior.

**Revision column.** Unless stated otherwise every claim is scoped to **Red Label** (MAME set
`defender`). Claims marked *all-Williams* hold across White/Blue/Green/Red.

**Implementation location** is `TBD` throughout — Phase 1 is research only. Phase 2 fills it in.

Ledger IDs are stable. Cite them from code comments and from later documents.

**Label-ownership rule (added 2026-08-09).** A claim's confidence label is owned by **this file
only**. Every other document in `docs/research/` must state the fact and cite the `L-nnn` id rather
than re-asserting a label of its own. **Amended 2026-08-09 (iteration 2):** the original wording
added "where a sibling document already carries a label, it is a cached copy and this file wins".
That clause is **withdrawn** — it legitimised exactly the duplicated labels that produced F-04,
H-05 and R-05, and it made the rule unfalsifiable. A sibling label is now simply a **defect**, to
be replaced with the `L-nnn` citation. This rule exists because Phase 1 shipped three separate
label drifts (`SOURCE_MAP_CORE` §2.3 vs `SOURCE_MAP` §4.1; `MAME_HARDWARE_CROSSWALK` §3 vs §11;
and the X3 palette dispute) that all traced to the same cause.

**Glossary — "tick" (owned here, under `L-004`/`L-002`).** Throughout this corpus **one tick = one
scheduler pass = one video frame = 16.640 ms** (`L-002`, `L-004`). Every per-tick physics constant
(`ΔV16 = −V16/64`, `τ = 64 ticks`, ease `2 px/tick`, reverse pan `80 ticks`, shell lifetime
`160 ticks`, `V16 = 192 → 6.0 px/tick`) is **per frame**. The hardware IRQ period (4.160 ms,
240.3846 Hz, `L-003`) is **never** called a tick. A reimplementation that drives the tick at the
IRQ rate runs the game four times too fast.

**Part G** at the end of this file holds entries added or reversed during the 2026-08-09 Phase 1
defect-closure pass. Read it before trusting any palette, revision-provenance or frame-pacing figure
copied out of an earlier document.

---

## Part 0 — Contradictions found between researchers and how they were resolved

Seven specialists produced overlapping coverage. Eleven substantive conflicts were found. Each was
adjudicated against primary evidence, not by averaging. The Red Label ROM images were recovered
from mwenge git history (`90aab9e`) and verified byte-for-byte against
`ROM_START(defender)`; wherever possible the ROM was the arbiter.

| # | Topic | Position A | Position B | Resolution | Residual risk |
|---|---|---|---|---|---|
| X1 | Scheduler tick rate | `srcmap-core`: **UNRESOLVED** — the ROM may hold a prefixed `SYNC` at `EXEC0`, which would run the scheduler 4× per frame and multiply every `NAP` constant by four | `lineage`: the ROM holds `BEQ EXEC0` | **B.** Extracted `defend.2` reads `$E7C3: 96 5D` (`LDA TIMER`), `$E7C5: 27 FC` (`BEQ −4 → $E7C3`), `$E7C7: 0F 5D` (`CLR TIMER`). The `PatchROM` write at `Makefile:62–63` restores the branch the assembler could not emit. **One `EXEC` pass per frame; no rescale.** | None. Verified in the ROM and corroborated by the `GEXEC` audit-meter cross-check (L-004). |
| X2 | `PLD21` row 3 (player ship, left-facing odd phase) | `srcmap-support`, `revision`: use the ROM value `$C300` | `lineage`: the true value is `$6600`; `$C300` is a build hack | **B.** `defend.11` (bank 2, where sprites live) `[0x700] = 66`; `defend.12` (bank 1) `[0x700] = C3`, which is `amode1`'s `ORG $CF00 / FCB $C3 ;CHECKBYTE`. Sprite draws force bank 2 (`defa7.src:1952`, `defb6.src:1327`). `unknown.bin`, verified byte-identical to `defend.11[0x450:0x800]`, supplies that region — so the assembled `$C300` never reaches IC11. **`$6600`.** | None. |
| X3 | Palette blue ceiling | `mame`: B levels `{00,5F,A0,FF}`; WHITE = (255,255,255); GRAY `$A4` = (137,137,**160**) | `srcmap-support`: blue caps at 217; GRAY = (137,137,137) | **A. This resolution was REVERSED on 2026-08-09** — the original ruling for B was wrong. Analytic proof, no `resnet.cpp` needed: for a bit-to-Vcc ladder with `pulldown = 0` and `pullup = 0`, the per-bit output is `Vout_n = Vcc·(1/R_n)/Σ_j(1/R_j)`, so **Σ_n Vout_n = Vcc for *every* such network, independent of the resistor values**. RG: `0.147509+0.316092+0.536399 = 1.000·Vcc`. B: `0.370787+0.629213 = 1.000·Vcc`. Both networks therefore have the same `max_out` (5.0 V), which makes the shared-vs-per-network normalization question **moot** — `scale = 255/5 = 51` either way. B weights = `{94.55, 160.45}` → levels **`{0x00, 0x5F, 0xA0, 0xFF}`**. The `{0,81,137,217}` figure was the *three*-resistor RG network's weights for 560 Ω and 330 Ω (81, 137, 81+137), which is not what the function computes for a two-resistor network. | None. The proof is closed-form and does not depend on reading `resnet.cpp`. The "GRAY must be neutral" argument that drove the original ruling is a red herring: a 3-3-2 DAC with these ladders cannot produce a neutral gray at `$A4` under **any** normalization, so the authors' label is simply the nearest color name. `MAME_HARDWARE_CROSSWALK.md` §4/§11 is the canonical table. |
| X4 | Framebuffer width | `srcmap-core`: 152 byte-columns / 304×256 | `srcmap-support`: 156 byte-columns / 312×256 | **Both, at different layers.** Software screen region `$0000–$9BFF` = 156 columns (`SCRCLR`, `BORDER`); hardware framebuffer = byte-columns 0–151 = `$0000–$97FF` = 304×256 px, established from the **game** (`blk71.src:99`, `$2600` = 304 px) and from the right-laser limit `CMPX #$9800`; **visible = 292×240 px, byte-columns 6–151, rows 7–246**. All three stated separately in `SOURCE_MAP.md` §3.1 and in `DISPLAY_GEOMETRY.md` §1. **Amended 2026-08-09:** the original wording justified the 304-px extent with "`williams_v.cpp` reads to `x=303`". That is only half the loop bound — `screen_update` starts at `cliprect.min_x & ~1` = 12, so MAME reads **only** byte-columns 6–151. The MAME citation supports the *visible* extent; the *framebuffer* extent rests on the game's own constants. | None. The number was right; the evidence line was not. |
| X5 | Sound command width | `mame`, `lineage`: 6 bits | `audio`, `revision`: 5 bits | **Both, at different layers.** Game transmits a 6-bit inverted field on PB0–5 (`ANDB #$3F ;DONT TURN ON LEDS`); PB6–7 are LED outputs; the sound board decodes 5 (`ANDA #$1F`). PB5 is a decoder don't-care. Idle = `$3F`. **Amended 2026-08-09:** this adjudication settled the layering but missed a separate error — `mame` had also published a wrong **code count**, "a Defender reimplementation needs 64 distinct sound command codes". The true count is **31 real codes plus idle**, and commands are **active-low** (`$3F − C`). Corrected in `MAME_HARDWARE_CROSSWALK.md` §7 and recorded as **L-139**. | None. |
| X6 | Sound `$12` (astronaut-lift / `BON2`) | `srcmap-enemy`: the suck-in plays sound `$12` | `audio`: `$12` is dead — a register bug | **B.** Verified at `defb6.src:815–822`: `CLRD` zeroes **B**; `LDA #$12` loads **A**; `SNDOUT` is documented `*B=SND TO MAKE` and transmits `(~B)&$3F` = `$3F` — the idle pattern, twice, producing no CB1 edge. **`BON2`, `BONV`, `BONSND` and the `B2FLG` state machine are unreachable in gameplay.** The only sound at the suck-in is the `$0E` `LSKSND` ladder. | **None.** Closed 2026-08-09: the bug is byte-identical in all four Williams revisions (L-163, Q-23) — `$12` was never audible on any shipped machine. |
| X7 | Sound ROM checkbyte | `audio`: unresolved, `$FE` or `$FF` | `lineage`: `$FF` | **B.** `defend.snd[0] = $FF`, verified; CRC `fefd5b48` matches MAME. `historicalsource/VSNDRM1.SRC:169`'s `$FE` would fail the sound ROM's own self-check. | None. |
| X8 | `ROMMAP` contents | `srcmap-support`: all 24 bytes are `$00`, making the ROM test a no-op | `revision`: real values matching the Red layout | **Both — different copies of the file.** `romf8.src:626` is all zeros; the **shipping** copy `defb6.src:1828` has the real values. `romf8.src` is not shipped (L-011). | None. |
| X9 | "The source reassembles to the Red dumps bit-for-bit" | `revision`: yes | `lineage`: no — 46 % of IC11 comes from a binary, plus six patches | **B.** The **pipeline** reproduces the images; the **source** does not. `revision`'s claim is overstated and must not be repeated. | None. |
| X10 | Is the recovered source "the Red Label source"? | `revision`: yes, gate passes | `lineage`, `srcmap-core`: it is a slightly earlier revision | **Both, precisely stated.** Red Label is the correct canonical *baseline*; the recovered *text* sits a few edits short of the exact shipped Red build (four enumerated signals, `SOURCE_MAP.md` §2.1). Operational rule is identical either way: **ROM wins over source**. | None. |
| X11 | PatchROM patch count | `revision`: four | `lineage`: six | **B.** Six sites across four files; enumerated in `SOURCE_MAP.md` §2.2 and all six verified present in the extracted images. | None. |

Two further researcher statements were corrected without a counterpart:

- `srcmap-support` and `revision` both repeated the mwenge README's claim that `P1LAS`→`P1LAT` was
  "a typo that was bug-fixed". It is a RASM two-namespace disambiguation with **zero** behavioral
  content. Verified: `defend.1[0x62B] = 8E 0F 14` (`LDX #$0F14`) and `[0x638] = 8E 71 14`
  (`LDX #$7114`), matching `historicalsource/PHR6.SRC:156–157`. Do not propagate the claim that
  Defender had a laser-display bug.
- MAME's driver comment that Red has "*much* improved enemy AI" is **uncited** and is contradicted
  by Williams' own revision chart (`INFO.SRC:15–23`), which lists only checksums, `$D000`
  packaging, and cocktail support. Do not implement speculative Red-only AI.

---

## Part A — MULTI_SOURCE_CONFIRMED

Two or more independent classes of evidence agree. These are the claims the reconstruction may be
built on without further work.

---

### L-001 · Canonical ROM set
**Mechanic** Project baseline
**Claim** The canonical target is the Williams Red Label ROM set = MAME set `defender`, and the
fourteen images are recoverable and verified.
**Revision** Red Label
**Evidence** Extracted from mwenge git blob `90aab9e:orig/defender-redlabel/*` (deleted in
`f77226b`). Recomputed CRC32/SHA-1 for all fourteen files match `ROM_START(defender)` exactly:
`defend.1 c3e52d7e`, `.2 89b75984`, `.3 94f51e9b`, `.4 9a72348b`, `.6 65f4efd1`, `.7 339e092e`,
`.8 b649e306`, `.9 6870e8a5`, `.10 a543b167`, `.11 9deaf6d9`, `.12 f1f88938`, `.snd fefd5b48`,
`decoder.2 8dd98da5`, `decoder.3 c3f45f70`.
**Source** `mame-williams/williams.cpp:1985–2007`
**Secondary** Red Label ROM chart `orig/RedLabelROMChart.png`; Williams `INFO.SRC:15–23`
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Re-extract and re-hash; assert all fourteen match.
**Notes** Copy these images into the project as durable ground truth before the git history is lost
again. Every disputed claim in Phase 1 was adjudicated against them.

---

### L-002 · Frame rate
**Mechanic** Timebase
**Claim** Frame period is exactly **16.640 ms**; refresh is exactly **3125/52 = 60.09615… Hz**;
16 640 CPU cycles per frame. Not 60 Hz, not 16.667 ms.
**Revision** all-Williams
**Evidence** `set_raw(MASTER_CLOCK*2/3, 512, 6, 298, 260, 7, 247)` with `MASTER_CLOCK = 12 MHz`;
pixel clock 8 MHz, htotal 512, vtotal 260 → 8e6/133120.
**Source** `williams.cpp:1531, 1556`
**Secondary** `GEXEC` audit-meter arithmetic (L-004) requires ~60 Hz independently.
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Run 3600 ticks; assert elapsed = 59.90 s ± 1 frame.
**Notes** On a 60.000 Hz host the game runs `60.0961538 − 60 = 0.0961538` frames/s fast, so one
extra tick is owed every `1/0.0961538 = 10.4 s` — i.e. roughly every **624** host callbacks, not
every 62. (Cross-check: 96 ms/min ÷ 16.64 ms = 5.77 extra frames/min → 60/5.77 = 10.4 s.) Do not
hard-code the count; **accumulate 16.640 ms and emit `floor(accum / 16.640)` ticks per callback**.
Double-ticking every 62 callbacks would run the game at 60.97 Hz — 1.45 % fast, ten times the error
it was meant to correct, and enough to shift every sound-sequencer and `NAP` timer.

---

### L-003 · Interrupt structure
**Mechanic** Timebase / IRQ
**Claim** The only enabled interrupt is PIA1 CB1 driven by VA11 (scanline bit 5) on the **falling**
edge → **four IRQs per frame at scanlines 0, 64, 128, 192** (4.160 ms, 240.3846 Hz). COUNT240 (CA1)
is wired by MAME but never enabled: **Defender takes no VBLANK interrupt.**
**Revision** all-Williams
**Evidence** MAME: `m_pia[1]->cb1_w(BIT(scanline,5))` every 32 scanlines; `ca1_w(scanline>=240)`.
Source: `PIATAB` CR1 = `$05` (b0 = CB1 enabled, b1 = 0 = falling edge); `RESET` leaves CRA = `$14`
(CA1 disabled) and nothing anywhere sets CRA bit 0.
**Source** `defa7.src:967–968` (`PIATAB`), `romf8.src:76–81` (`RESET`)
**Secondary** `williams_m.cpp:19–38`
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Count IRQ entries per frame = 4; assert scanline of each ∈ {0,64,128,192}.
**Notes** Any CPU-budget model must schedule interrupts at raster lines, not frame boundaries.

---

### L-004 · Scheduler tick = one video frame
**Mechanic** Scheduler
**Claim** `IFLG` gates the four IRQs to two working halves; `TIMER` increments once per frame at
scanline 0; `EXEC` waits on `TIMER`; therefore **one scheduler pass = one frame = 16.640 ms and
`NAP n` = n frames**.
**Revision** Red Label
**Evidence** IRQ: `LDA VERTCT / CMPA #128 / BLO I0`, upper half `LDA IFLG / BNE IRQX / INC IFLG`,
lower half `LDB IFLG / BEQ IRQX / CLR IFLG / INC TIMER`. `SLEEP` header: `A=SLEEP TIME X 16MSEC`.
In-code cross-check: `GEXEC` does `NAP 15` per pass and bumps audit meter 6 every 240 passes, into a
CMOS field labeled `BLLTIM RMB 4 ;BALL TIME IN MINS`. 240 × 15 = 3600 ticks must equal one minute.
**Source** `defa7.src:1931–2004` (`IRQ`), `defa7.src:1715–1731` (`GEXEC`), `phr6.src:170`
**Secondary** L-002, L-005
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Instrument `EXEC` entries per frame = 1. Independently, video-measure the `FLPUP` score
flash at the start of a life: 3.0 Hz, not 12 Hz.
**Notes** A scheduler running at the IRQ rate would make the operator's minutes meter run 4× fast.
This is why the meter is decisive evidence.

---

### L-005 · `EXEC0` is a spin on `TIMER` — the `FCB $11,$13` hazard
**Mechanic** Scheduler
**Claim** The Red Label ROM contains `BEQ EXEC0` at the frame-sync point. The mwenge source's
`FCB $11,$13` is a two-byte placeholder, restored by `PatchROM.py`.
**Revision** Red Label
**Evidence** `defend.2` at `$E7C0`: `5F 9F 63 | 96 5D | 27 FC | 0F 5D` =
`CLRB / STX CRPROC / LDA TIMER / BEQ −4 → $E7C3 / CLR TIMER`. `Makefile:62–63`:
`./PatchROM.py redlabel/defend.2 0x07c5,'27fc'`. `historicalsource/DEFA7.SRC:3049` reads
`BEQ EXEC0`.
**Source** `defa7.src:3046–3050`; `historicalsource-defender/DEFA7.SRC:3049`
**Secondary** ROM disassembly (this synthesis); `Makefile:62–63`
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Disassemble `$E7C3–$E7C8`; assert `96 5D 27 FC 0F 5D`.
**Notes** **Severe transcription hazard.** Anyone implementing from the mwenge source as written
omits the frame-sync wait entirely. This closes the largest Phase 1 open question; **no timing
constant anywhere in the project needs rescaling.**

---

### L-006 · `STX [,Y]` at `blk71.src:191`
**Mechanic** Terrain rendering
**Claim** The terrain fast-output path uses `STX [,Y]`. `blk71.src:191`'s `COMF` is a 2-byte
placeholder restored by `PatchROM.py`. The `--6309` assembler flag is **not** evidence of a 6309
CPU — Williams shipped an MC6809E.
**Revision** Red Label
**Evidence** `defend.6[0x0DE] = AF B4` (`$AF` = `STX` indexed, postbyte `$B4` = indirect
zero-offset on Y). `Makefile:79–82`: `0x00de,'afb4'`. `COMF` is 6309 `$11 $53` — also 2 bytes,
hence usable as filler.
**Source** `blk71.src:191`; `historicalsource-defender/BLK71.SRC:191`
**Secondary** ROM byte verification (this synthesis)
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Assert `defend.6[0x0DE:0x0E0] == AF B4`.
**Notes** Second of the two "restored real instruction" patches. See also L-005.

---

### L-007 · ROM self-test algorithm
**Mechanic** Diagnostics / integrity
**Claim** The comprehensive ROM test is: start an 8-bit accumulator at the **IC number**, then
`ADCB` (add-with-carry) every byte of the 2 KB block; the result must equal `$80`.
**Revision** Red Label
**Evidence** `TFR U,D ;INITIALIZE CHECKSUM TO ROM NUMBER / SUBB #… / LSRB / CLRA ;CLEAR CARRY /
ROM2 ADCB ,X+ / LEAY -1,Y / BNE ROM2 / CMPB #$80`. Executed over all thirteen 2 KB blocks of the
extracted images: the required starting index is exactly 1, 4, 2, 2, 3, 3, 6, 7, 8, 9, 10, 11, 12 —
the IC numbers, in every case.
**Source** `defb6.src:1629–1660` (`ROM2`)
**Secondary** Arithmetic over the verified ROM images (this synthesis)
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Re-run the sum; assert index == IC number for all thirteen blocks.
**Notes** Not a plain sum and not an XOR. This result also independently proves each checkbyte's
chip assignment, which is how X2 was closed.

---

### L-008 · Bank map
**Mechanic** Memory
**Claim** `$C000–$CFFF` is a 16-entry bank view selected by writing the low nibble to any address in
`$D000–$DFFF`. Bank 0 = I/O; 1 = attract + scanner (IC9/IC12); 2 = text, font **and object sprites**
(IC8/IC11); 3 = diagnostics (IC7/IC10); 7 = terrain, player explosion, wave table (IC6, upper half
unpopulated). Banks 4–6 unmapped. Fixed ROM `$D000–$FFFF`: IC1 `$D000`, IC4 `$D800`, IC2 `$E000`,
IC3 `$F000`.
**Revision** Red Label
**Evidence** MAME map + `bank_select_w(data & 0x0f)` + banked region load offsets. Source block
comments in `phr6.src:23–70`. The game's own `ROMMAP` decodes to the same layout including
"ROM 5 = no ROM".
**Source** `phr6.src:11–70`; `defb6.src:1828–1839` (`ROMMAP`)
**Secondary** `williams.cpp:494–507, 1992–1999`; `williams_m.cpp:311–344`; Red Label ROM chart
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Decode `ROMMAP` programmatically; assert it matches MAME's load offsets.
**Notes** `MAPCR` (`$A036`) shadows the bank so the IRQ can save/restore it.

---

### L-009 · Framebuffer format
**Mechanic** Rendering
**Claim** 4 bpp packed, **column-major**: `address = (x>>1)·256 + y`; the **high nibble is the left
(even-x) pixel**. Three extents: software screen region `$0000–$9BFF` (156 byte-columns); the
framebuffer the video hardware addresses, columns 0–151 (`$0000–$97FF`) = 304 × 256 px; **visible
292 × 240 px, x 12–303, y 7–246, byte-columns 6–151**. MAME renders **only the visible window** —
`screen_update` runs `for (x = cliprect.min_x & ~1; x <= cliprect.max_x; x += 2)` with
`set_visarea(12, 303, 7, 246)`, so byte-columns 0–5 and rows 0–6 / 247–255 are never read by
`screen_update` at all. The 304-px framebuffer width is established by the **game**, not by MAME:
`blk71.src:99 ADDD #$2610 ;START ON RIGHT EDGE OF SCREEN`, and `$2600` = 9728 world units ÷ 32
units/px = 304 px.
**Revision** all-Williams
**Evidence** `source = &m_videoram[y]; pix = source[(x/2)*256]; dest[x+0] = pens[pix>>4];
dest[x+1] = pens[pix & 0x0f]`. Every blit advances a column with `LEAX $100,X`. `SCRCLR` clears from
`$9C00` down; `BORDER` sweeps `CMPX #$9C00`; the right-hand laser dies at `CMPX #$9800`.
`set_visarea(12, 303, 7, 246)`.
**Source** `defa7.src:906–911` (`BORDER`), `defa7.src:2802`; `defb6.src:1414` (`BLKCLR`)
**Secondary** `williams_v.cpp:210–219`; `williams.cpp:1601`
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Write a known pattern at `(x,y)`; assert it lands at `(x>>1)*256 + y` with the correct
nibble.
**Notes** A row-major buffer will produce garbage. Byte-columns 152–155 are written but never
scanned. Byte-columns 0–5 (`$0000–$05FF`) **are** written by the game and addressed by the video
hardware but fall outside the visible window — they are left-edge overscan, not dead RAM. Both
guard bands matter to the blit routines: see `DISPLAY_GEOMETRY.md` §1. Superseded wording: an
earlier revision of this entry said "columns 0–5 are scanned out but fall outside the visible
area" and cited `williams_v.cpp:210–219` for the 304-px figure; that citation only supports the
**visible** extent.

---

### L-010 · No blitter
**Mechanic** Rendering
**Claim** Defender has **no blitter**. 100 % of rendering is CPU stores into the framebuffer.
**Revision** all-Williams
**Evidence** `williams.cpp:44` "blitter (Stargate and Defender do not have blitter)";
`williams_v.cpp:14–15, 57`; `defender_main_map` has no `$CA00` entry unlike `main_map_blitter`; no
reference to `$CA00` exists anywhere in the 6809 source.
**Source** (absence across the whole source tree)
**Secondary** `williams_v.cpp:14, 57`; `williams.cpp:44, 494–507`
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** grep the source tree for `$CA00`; assert zero hits.
**Notes** Draw cost is CPU time. The executive sheds load under overrun (L-030) — a reimplementation
drawing instantly will never degrade the way the original does.

---

### L-011 · `romf8.src` is not shipped
**Mechanic** Build / system primitives
**Claim** `romf8.src`'s output is discarded. The **shipping** copies of `RESET`, `CWRIT`, `COFF`,
`BLKCLR`, `SCRCLR`, `ROMMAP` and the `$FFF0` hardware vectors are in `defb6.src`, and differ from
`romf8`'s by an added bank-2 swap that is functionally required.
**Revision** Red Label
**Evidence** `romf8.src:63` declares `ORG $FB00 ;TEMPORARY!!!!!!`; its `ROMMAP` (line 626) is all
zeros while `defb6.src:1828` has the real values; its `CWRIT` (line 407) goes straight to
`LDD ,Y` while `defb6.src:1323–1331` first does `LDA MAPCR / PSHS A / LDA #2 / STA MAPCR / STA MAPC`.
No `Makefile` chain entry extracts `roms.o` at the offset corresponding to `$F800–$FFFF`; `defend.3`
takes its whole content from the `defa7/defb6/amode1` object plus `samexap7`.
**Source** `defb6.src:1323, 1372, 1414, 1463, 1828, 2249`; `romf8.src:63, 407, 626`
**Secondary** `Makefile:26–48, 66–69`
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Assert `defend.3`'s `$FFF0` vectors match `defb6.src:2250–2252`.
**Notes** Read `romf8.src` for `RESET`, the power-up self-test and `LJSR`; **implement from
`defb6.src`**. Resolves X8.

---

### L-012 · `PLD21` row 3 = `$6600`
**Mechanic** Sprite data (player ship, left-facing, odd phase)
**Claim** The player ship's left-facing odd-phase image row 3 word 3 is **`$6600`**, not `$C300`.
**Revision** Red Label
**Evidence** `defend.11` (bank 2) `[0x6FC:0x702] = 00 00 00 FE 66 00`, exactly
`FDB $0000,$00FE,$6600`. `defend.12` (bank 1) `[0x700] = C3` — that is `amode1.src:1312–1315`'s
`ORG $CF00 / PUT $2F00 / FCB $C3 ;CHECKBYTE`. Object sprites are read from bank 2
(`LDA #2 / STA MAPC ;SELECT OBJECTS`; `CWRIT` force-selects bank 2). `src/unknown.bin` is verified
byte-identical to `defend.11[0x450:0x800]`, so the mwenge-modified value never reaches IC11.
**Source** `historicalsource-defender/DEFB6.SRC:2170` (`PLD21`); `defb6.src:2170`;
`amode1.src:1312–1315`; `defa7.src:1952–1953`; `defb6.src:1327–1330`
**Secondary** ROM byte verification (this synthesis)
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Decode `PLD21` from `defend.11[0x6E8:0x708]`; render both phases; assert the odd-phase image
is the even-phase image shifted one pixel.
**Notes** Resolves X2. Taking mwenge's `$C300` puts color indices C and 3 where 6 and 6 belong.
This is the only genuine sprite-data divergence found between source and ROM.

---

### L-013 · Sound interface contract
**Mechanic** Audio
**Claim** The game writes a **6-bit inverted** command field to PIA1 port B (`$CC02`) bits 0–5;
bits 6–7 are diagnostic **LED outputs**. `$3F` is the idle pattern and writing it is what arms the
next interrupt. The sound board decodes only **five** bits, so PB5 is a decoder don't-care.
Command *C* is transmitted as byte `$3F − C`.
**Revision** all-Williams
**Evidence** `phr6.src:132–135`: `PIA1 EQU $CC02 / *B0-B5 SOUND / *B6-B7 LEDS / *CB2 IRQ`.
`SNDOUT`: `LDA #$3F / STA SOUND / COMB / ANDB #$3F ;DONT TURN ON LEDS / STB SOUND`.
MAME: `snd_cmd_w` passes `data|0xC0`; `deferred_snd_cmd_w` does
`cb1_w((param == 0xff) ? 0 : 1)`. Sound side: `LDAA SOUND+2 / COMA ;INVERT INPUT / ANDA #$1F ;MASK GARB`.
**Source** `defa7.src:696–704` (`SNDOUT`); `phr6.src:132–135`; `vsndrm1.src:914–919`
**Secondary** `williams_m.cpp:162–175`
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Send `$3F` then `$3E`; assert the sound board decodes command 1. Send `$3E` twice with no
intervening `$3F`; assert only one trigger.
**Notes** Resolves X5. The mandatory write-`$3F`-then-command sequence is the trigger mechanism, not
politeness. An event-list-driven reimplementation must model the release write.

---

### L-014 · Sound DAC is 8-bit
**Mechanic** Audio
**Claim** The DAC is an **8-bit** MC1408 fed from sound-PIA port A. The "6-bit" quantity is the
command bus, not the converter.
**Revision** all-Williams
**Evidence** `LDAA #$FF / STAA 0,X   SET SIDE A AS OUTPUTS` programs DDRA = `$FF`; the program
writes full-scale bytes. MAME: `MC1408(config, "dac", 0)` with
`m_pia[2]->writepa_handler().set("dac", dac_byte_interface::data_w)`.
**Source** `vsndrm1.src:175–176`
**Secondary** `williams.cpp:1563, 1581`
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Assert output codes span 0–255.
**Notes** `sample = (v − 128)/128`. Quantising to 6 bits adds noise the original does not have.

---

### L-015 · Sound engine clock and the absence of a sample rate
**Mechanic** Audio
**Claim** There is **no sample-rate timer**. Every DAC write is issued from inside a delay loop, so
pitch is pure 6800 cycle counting at **E = 3.579545 MHz / 4 = 894 886.25 Hz**.
**Revision** all-Williams
**Evidence** CRA is written `#$3C` = "CA2 SET INIT HIGH, NO IRQS" — the PIA timer/handshake is
unused. MAME: `M6808(config, m_soundcpu, SOUND_CLOCK); // internal clock divider of 4, effective
frequency is 894.886kHz`.
**Source** `vsndrm1.src:180–181`
**Secondary** `williams.cpp:1532, 1540`
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Two internal validations both pass: the author's own `BRA GOUT SYNC 36` pad measures
exactly 36 cycles, and `ORGAN1`'s 72+d cycle cost turns `NOTTAB`'s twelve delay values into a
correct 12-tone equal-tempered scale (143/76 = 1.882 ≈ 2^(11/12)).
**Notes** An AudioWorklet must emulate the cycle counter and hold each DAC code for the exact cycle
count. Oscillator-node approximation loses the characteristic quantisation aliasing.

---

### L-016 · Sound ROM checkbyte and the note-table artifact
**Mechanic** Audio / build
**Claim** `defend.snd[0] = $FF` (mwenge's value; `historicalsource`'s `$FE` would fail the sound
ROM's own self-check). `defend.snd[0x5B6] = $8B` is an **original-toolchain arithmetic artifact**
where the mathematically correct product is `$8A`.
**Revision** all-Williams (the sound ROM is identical across White/Blue/Green/Red)
**Evidence** ROM byte verification. `Makefile:115–118` comment documents the `$508B`/`$508A`
discrepancy in `ORGTAB`. Recomputing the `CKSUM` routine over the ROM yields `$FF`.
**Source** `vsndrm1.src:145–174`; `historicalsource-williams-soundroms/VSNDRM1.SRC:169`
**Secondary** `defend.snd` bytes 0 and `0x5B6` (this synthesis); `williams.cpp:2002, 2026, 2048, 2071`
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Assert `defend.snd[0] == 0xFF` and `[0x5B6] == 0x8B`.
**Notes** Resolves X7. Reproduce the ROM value, not the correct one — that note is one LSB off by
design-accident.

---

### L-017 · Sound is revision-invariant
**Mechanic** Audio
**Claim** The sound ROM is byte-identical across all four Williams revisions.
**Revision** all-Williams
**Evidence** `williams.cpp:2002, 2026, 2048, 2071` all load CRC `fefd5b48` / SHA1
`ceb0d18483f0691978c604db94417e6941ad7ff2`. Only the Taito set differs (`f122d9c9`).
**Source** —
**Secondary** MAME ROM definitions
**Confidence** MAME_CONFIRMED (recorded here because it scopes many other claims)
**Impl** TBD
**Test** —
**Notes** All audio work can proceed against `vsndrm1.src` without revision anxiety.

---

### L-018 · World geometry
**Mechanic** Coordinates
**Claim** World X is **unsigned 16-bit, 32 units per pixel, 64 per byte-column**. The world is the
full 65 536-unit space = **2048 pixels** and wraps for free because every visibility test is an
unsigned 16-bit difference. Visible object window = `150·64` = 300 px.
**Revision** Red Label
**Evidence** `OPROC`: `LDD OX16,X / SUBD BGL / CMPD #150*64 / BHS OPLP` then `ASLB/ROLA` ×2 puts the
byte-column in A. Scanner: `LDD BGL / SUBD #$8000-(150*32)`, then `>>10` into a 64-entry table →
64 × 32 px = 2048 px. Terrain: `TLEN EQU $100` = 256 bytes = 2048 bits, one bit per pixel, and
`BGINIT` advances one bit per `$20` = 32 `BGL` units → 2048 × 32 = 65 536. Spawn writes
`PLABX = $800 + BGL` for screen column `$20` (pixel 64): 64 × 32 = `$800`.
**Source** `defa7.src:2527–2543` (`OPROC`), `defa7.src:1269–1276`; `amode1.src:1199–1273` (`SCNR`);
`blk71.src:18, 106–116`
**Secondary** three mutually independent derivations (object, scanner, terrain)
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Fly one full lap; assert the terrain silhouette and every object position repeat exactly.
**Notes** Store world X as `uint16` and let it wrap. Never use a signed comparison for visibility.
The world is ~6.8 visible screens wide.

---

### L-019 · Pre-shifted sprite pairs
**Mechanic** Rendering
**Claim** Every animated sprite is stored **twice**, pre-shifted by one pixel (`OBJP0` even phase,
`OBJP1` odd phase), selected by a carry bit. This is how 1-pixel horizontal motion comes out of a
2-pixels-per-byte buffer.
**Revision** Red Label
**Evidence** Descriptor is `[W, H, OBJP0, OBJP1, OBJWRT, OBJDEL]` = 10 bytes. `OPROC` does
`ASLB/ROLA` ×2 then a third `ASLB` commented `;SAVE PHASE`; `ON86` does `BCC ON860 / LEAY 2,Y` then
`LDS OBJP0,Y`. Proof by data: `ASTD10` line 0 = `$33 $00`, `ASTD11` line 0 = `$03 $30` — the same
image shifted one nibble.
**Source** `phr6.src:559–567`; `defa7.src:2523–2536, 374–378`; `defb6.src:2011–2014`
**Secondary** ROM sprite data
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Decode both phases of every animated sprite; assert phase 1 == phase 0 >> 4 nibbles.
**Notes** Non-animated shapes (`LASP1`, `PLAMIN`, `SBPIC`, `TEREX`) have only one data pointer and
are drawn via `CWRIT` at 2-px granularity.

---

### L-020 · Palette hardware
**Mechanic** Color
**Claim** 16 write-only palette bytes at `$C000–$C00F`, format `BBGGGRRR` (bit7..0 = B1 B0 G2 G1 G0
R2 R1 R0), expanded through resistor ladders 1200/560/330 Ω (R and G) and 560/330 Ω (B). Levels:
R/G `{0,38,81,118,137,174,217,255}`, **B `{0,95,160,255}`** (`{0x00,0x5F,0xA0,0xFF}`). **Every
channel reaches full scale; `$FF` WHITE is pure `#FFFFFF`.**
**Revision** all-Williams
**Evidence** `compute_resistor_weights(0, 255, −1.0, 3, resistances_rg, …, 3, resistances_rg, …,
2, resistances_b, …)` (`williams_v.cpp:340–350`), both networks passed with `pulldown = 0` and
`pullup = 0`; `r = combine_weights(weights_r, BIT(i,0..2))`, `g` uses bits 3–5, `b` uses bits 6–7
(`williams_v.cpp:354–360`). For a bit-to-Vcc ladder with no pull-up or pull-down the per-bit output
reduces algebraically to `Vout_n = Vcc·(1/R_n)/Σ_j(1/R_j)`, hence **`Σ_n Vout_n = Vcc` for any such
network regardless of resistor values**: RG `0.147509+0.316092+0.536399 = 1`, B
`0.370787+0.629213 = 1`. Both networks therefore share the same `max_out` (5.0 V) and the same
scale factor `255/5 = 51`, so shared-vs-per-network normalization cannot change the answer.
Per-bit weights: R/G `{37.615, 80.604, 136.781}`, B `{94.551, 160.449}`.
**Source** `williams_v.cpp:340–363`
**Secondary** `defb6.src:1876–1891` (`CRTAB`) — decodes to `$15` BROWN → (174,81,0);
`$81` BLUE → (38,0,160); `$C7` PURPLE → (255,0,255); `$FF` WHITE → (255,255,255);
`$A4` "GRAY" → (137,137,160).
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Assert `Σ` of each network's per-bit weights = 255 exactly; assert `$FF` decodes to
`#FFFFFF` and `$C7` to `#FF00FF`.
**Notes** **Reverses X3** (2026-08-09). The earlier ruling gave B `{0,81,137,217}`, a "warm white"
at `$FF`, and an instruction not to color-correct it — all three were wrong. `{81,137,217}` are the
*RG* network's weights for 560 Ω / 330 Ω / both, borrowed into a two-resistor network they do not
belong to. The "`CRTAB $A4` is labeled GRAY so it must be neutral" argument is invalid on physical
grounds: `$A4` selects the 330 Ω bit in all three channels, but that bit sits in a three-resistor
divider on R and G (`2.682 V`) and a two-resistor divider on B (`3.146 V`). Those are different
voltages on the real board, and no choice of normalization can equalise them, because normalization
is a single multiply applied to every channel alike. `$A4` is therefore a **blue-tinted** gray on
real hardware, and the author's label is simply the closest available name for (137,137,160). The
only palette bytes that are exactly neutral are `$00` (black) and `$FF` (white).
Canonical table: `MAME_HARDWARE_CROSSWALK.md` §4 and §11.
`OPEN_QUESTIONS.md` Q-03 is closed by the analytic proof above; `resnet.cpp` is not needed.

**UNDISCLOSED PREMISE, recorded 2026-08-09 (iteration 3) — `PHASE1_GATE_FINAL.md` F-4 / SC-12.**
This entry's decisive premise is *"a bit-to-Vcc ladder with no pull-up or pull-down"*. Three lines
above the code cited as **Source**, `williams_v.cpp:346` says: *"there really are pullup/pulldown
resistors, but this situation is complicated by the use of transistors, so we ignore that."* MAME is
asserting a simplification, not a schematic. A real pull-down to ground would depress the
two-resistor blue network more than the three-resistor R/G network — which is qualitatively the X3
position this entry reverses. **No Defender schematic has been read by anyone in this corpus.** The
adjudicator re-derived the arithmetic and confirmed every figure, and disputed only the strength of
the label; `MAME_HARDWARE_CROSSWALK.md` §"Channel level tables" independently labels the same
tables `INFERRED`. The label above is **left unchanged here deliberately** — iteration 3's remit was
corpus hygiene, and silently re-grading a claim under a hygiene pass is the move this project
forbids. Retiering L-020 (to `INFERRED`, or to `MULTI_SOURCE_CONFIRMED` with the third source named
explicitly) is **F-4, still open, due before Phase 3**. Until then: the numbers are reliable, the
`MULTI_SOURCE_CONFIRMED` stamp is not.

---

### L-021 · Animated palette slots
**Mechanic** Color
**Claim** Slots 1, A, B, C, D, E, F are rewritten at run time by four separate processes. Sprites
are authored against animated slots; a static palette makes them look dead.
**Revision** Red Label
**Evidence** Slot 1 (laser): `COLR` steps a 37-entry `COLTAB` every 2 frames. Slots A and C:
`CBOMB` picks a random `COLTAB` entry every 6 frames and also flips the mine bitmap
`BMBD10`↔`BMBD20`. Slots D/E/F: `TIECOL` rotates a 3×3 table every 6 frames. Slot B: `MONO`
rewrites an object's nibbles to `$B` for whole-object flashing. The `PCRAM` shadow is pushed to
hardware `CRAM` once per frame in the IRQ.
**Source** `defa7.src:3024–3040` (`COLR`), `1442–1490` (`MONO`), `1966–1978`;
`defb6.src:1195–1211` (`TIECOL`), `1213–1230` (`CBOMB`)
**Secondary** `williams_v.cpp` palette path
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Log every write to `PCRAM` over one wave; classify by slot.
**Notes** Bomber uses D/E/F, Pod uses C/D/E/F, Mutant uses C, player ship uses D/E/F for a stripe,
mines use A. Palette upload is atomic at the top of the frame, so a once-per-frame snapshot is
behaviorally equivalent to MAME's per-scanline rendering.

---

### L-022 · No DIP switches
**Mechanic** Configuration
**Claim** Defender has **no game DIP switches**. Every operator setting lives in 4-bit
battery-backed CMOS at `$C400–$C4FF`, edited from a 28-function coin-door menu.
**Revision** all-Williams
**Evidence** `INPUT_PORTS_START(defender)` contains only controls and coin-door lines — no
`PORT_DIPNAME` — while `colony7` and `jin` on the same machine config do declare DIPs.
`cmos_4bit_w` stores `data | 0xf0`. Menu list `MSGAUD` at `romc8.src:716–774`.
**Source** `phr6.src:161–204`; `romc8.src:716–821`
**Secondary** `williams.cpp:762–790, 1543`; `williams_m.cpp:233–237`
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** CMOS reads return the stored low nibble OR `$F0`.
**Notes** Expose an emulated bookkeeping/adjustment mode, not a fake DIP bank.

---

### L-023 · Factory CMOS defaults
**Mechanic** Configuration
**Claim** 3 ships, replay @ 10 000 (repeating), 25 ¢ coinage, `GA1` = `$05` initial difficulty,
`GA2` = `$15` difficulty ceiling, `GA3` = `$01` **unused**, `GA4` = `$05` planet-restore wave.
**Revision** Red Label
**Evidence** `DEFALT` table. The July 1981 later-boards operator manual (16P-3001-103 R-T) prints
F18 = 5, F19 = 15, F20 = "NOT USED", F21 = 5 — matching exactly. The two earlier manuals print
F18 = 0, F19 = 10 and label F20 "BACKGROUND SOUND".
**Source** `romc8.src:782–821` (`DEFALT`)
**Secondary** MANUAL_CONFIRMED against three fetched operator manuals
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Cold-boot CMOS; assert all fourteen adjustable functions match.
**Notes** Seeding from the *earlier* manuals gives a measurably easier machine than factory-fresh
Red. Function 20 must be a no-op.

---

### L-024 · Ships, smart bombs and humanoids come from one place
**Mechanic** Player inventory
**Claim** Starting smart bombs = the operator **ships** setting (not a fixed 3); starting humanoids
is a hard-coded 10.
**Revision** Red Label
**Evidence** `LDX #NSHIP / JSR RCMOSA / ANDA #$F / STA P1LAS / LDB #10 / STD P1SBC` — `STD` writes
`A` (= `NSHIP & $F`) to `P1SBC` and `B` (= 10) to `P1TRG`.
**Source** `defa7.src:1142–1147` (`START`); `phr6.src:444–447`
**Secondary** Manual: "SMART BOMB Switch — … A maximum of 3\* per play" with an adjustable asterisk
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Set ships to 5; assert 5 smart bombs at spawn.
**Notes** The HUD caps the smart-bomb icon display at 3 (L-046), which is why the manual's asterisk
reads the way it does.

---

### L-025 · Bonus ship and bonus smart bomb are awarded together
**Mechanic** Scoring
**Claim** At every replay threshold (default every 10 000 points, repeating forever) the player gets
**one ship and one smart bomb**. Setting the replay level to 0 disables the award.
**Revision** Red Label
**Evidence** `SCRX`: `LDD REPLA / BEQ SCRXX` … `BSR RCHK / BLO SCRXX` … advance the threshold by
`REPLA` (BCD) … `INC PLAS-1,X / INC PSBC-1,X / JSR LDISP / JSR SBDISP / LDD #RPSND / JSR SNDLD /
LDB #5 / JSR AUDIT`. `DEFALT` ships `REPLAY = $01,$00 ;REPLAY @10,000`.
**Source** `defa7.src:511–535` (`SCRX`); `romc8.src:801`
**Secondary** Manual GAME PLAY text: "Bonus ships and Smart Bombs provided every 10,000\* points."
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Score 10 000; assert both counters increment and audit meter 5 bumps.
**Notes** There is no separate smart-bomb threshold. Comparison is on the low 6 BCD digits.

---

### L-026 · Scoring table
**Mechanic** Scoring
**Claim** Lander 150, Mutant 150, Swarmer 150, Baiter 200, Bomber 250, Pod 1000, **mine 25**;
catch a falling humanoid 250 (uncaught safe landing) / 500 (caught in flight), deposit 500;
end-of-wave bonus 100 × min(wave, 5) per surviving humanoid.
**Revision** Red Label
**Evidence** Kill-routine macro constants: `KILP $0115` (`LKILL`, `SCZKIL`), `LDD #$0115`
(`MSWKIL`) = 150; `KILP $0120` (`UFOKIL`) = 200; `KILO $0125` (`TIEKIL`) = 250; `KILO $0210`
(`PRBKIL`) = 1000; `LDD #$25` (`BKIL`) = 25. `SCORE` takes `A` = decimal exponent, `B` = BCD
mantissa. **Independently** from the attract-mode display data: LANDER/N150, MUTANT/N150, POD/N1000,
BOMBER/N250, SWARMER/N150, BAITER/N200.
**Source** `defb6.src:82, 118, 190, 499, 506, 625, 922, 1120`; `defa7.src:477, 2700`;
`mess0.src:265–282`
**Secondary** the two derivations are independent of each other
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Kill one of each; assert the score deltas.
**Notes** No Williams operator manual contains a scoring table (L-090) — the legend was on the
cabinet instruction card. The 25-point mine value appears in no community table. A full humanoid
rescue is **1000**, paid as two separate 500 events.

---

### L-027 · Enemy internal-name mapping
**Mechanic** Naming
**Claim** `LND` = Lander, `SCZ`/"SCHITZO" = **Mutant**, `TIE` = **Bomber**, `PRB`/"PROBE" = **Pod**,
`MSW` = Swarmer, `UFO` = **Baiter**, `AST` = humanoid/astronaut.
**Revision** Red Label
**Evidence** `amode1.src:1136–1178` pairs, **by index**, the `TEXTAB` message vectors with `PICTS`,
`XS` and `BLIPS`; `AMOD12` indexes them all from one base at offsets `OLEN`, `2·OLEN`, `3·OLEN`.
Point values in `mess0.src:265–282` match the kill routines independently.
**Source** `amode1.src:1136–1178, 622–664`; `mess0.src:265–282`
**Secondary** kill-routine sound labels (`UFHSND`, `TIHSND`, `PRHSND`, `SCHSND`, `LHSND`)
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** —
**Notes** Every tuning constant named `SCZ*`, `TIE*`, `PRB*`, `UFO*` in `phr6.src` and `WVTAB` must
be read with this mapping.

---

### L-028 · Input map
**Mechanic** Input
**Claim** All inputs are **active HIGH**. `$CC04` bits 0–7 = Fire, Thrust, Smart Bomb, Hyperspace,
Start2, Start1, Reverse, Down; `$CC06` bit 0 = Up; `$CC00` bits 0–5 = Auto Up, Advance, Right Coin,
High Score Reset, Left Coin, Center Coin. There is **no left/right** — a 2-way up/down switch plus a
separate Reverse button.
**Revision** all-Williams
**Evidence** MAME `INPUT_PORTS_START(defender)` with `IP_ACTIVE_HIGH` throughout; `IN0` → PIA0 port
A, `IN1` → PIA0 port B, `IN2` → PIA1 port A. Independently listed in the source's PIA block.
**Source** `phr6.src:121–149`
**Secondary** `williams.cpp:762–786, 502–503`; operator manual Switch Test enumerations
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Switch-test screen enumerates all nine.
**Notes** UP is on a different PIA port from everything else. See L-062 for edge vs level semantics.

---

### L-029 · Beam-racing, not double buffering
**Mechanic** Rendering
**Claim** Flicker is avoided by drawing each object twice per frame in two vertical bands that chase
the beam, using `XXX1`/`XXX2`/`XXX3` derived from `VERTCT`. There is no back buffer and no
per-frame screen clear.
**Revision** Red Label
**Evidence** IRQ reads `VERTCT` (`$C800`), branches on `CMPA #128`; the upper half computes
`LDA VERTCT / SUBA #8 / CMPA #$A8 / BLS IRQ1 / LDA #$A8 / STA XXX2` and passes `LDD XXX2` to `OPROC`
and `PRDISP`; the lower half passes `LDD XXX1`. `XXX1/XXX2/XXX3` are three consecutive bytes so the
two `LDD`s yield overlapping windows. `XXX1` init `$FF70`, `XXX3` init 0.
**Source** `defa7.src:1931–1999, 1009–1010, 2509–2521`; `phr6.src:285–287`
**Secondary** `williams_v.cpp:485–489` (`VERTCT` = `vpos() & 0xfc`); `williams_m.cpp:19–29`
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Instrument which band each object falls in over a frame.
**Notes** Objects are erased and redrawn only when the beam is safely past them. An object's band
membership therefore decides whether `OBJX,X` is non-zero at any given instant, and **`OBJX ≠ 0` is
the eligibility test for both collision (L-047) and the smart bomb (L-052)** — `COL1 LDD OBJX,X /
BEQ COLLP` and `SBMB0 … LDD OBJX,X / BEQ SBMB2`. *(Corrected 2026-08-09, iteration 2: this line
previously cited **L-039**, which does not exist and never did — the numbering runs L-038 → L-040.
The claim is real; it is carried by L-047 and L-052, which is what it now cites. `L-039` is a
permanently retired id: do not reuse it.)*

---

### L-030 · The overload governor is real, visible behavior
**Mechanic** Scheduler
**Claim** When an `EXEC` pass overruns a frame, the game **cuts the starfield from 16 stars to 3**
and **teleports a random `OTYP=0` object off-screen**. This is original behavior, not an emulator
artifact.
**Revision** Red Label
**Evidence** `EXEC00 ASLA / ADDA OVCNT / SUBA #4 / BPL EXEC01 / CLRA / EXEC01 STA OVCNT /
CMPA #2 / BLO EXEC1 / LDB #3 / STB STRCNT ;CUT DOWN STARS` … then
`LDD SEED / ANDA #$3F / ADDA #$60 / ADDD OX16,X / STD OX16,X ;HYPER HIM OUT OF THERE`, and the
object is moved to `IPTR`. `TERBLO` deliberately forces `OVCNT = 8` (`;PHONY OVERLOAD`).
**Source** `defa7.src:3051–3090` (`EXEC00`–`EXEC03`); `defb6.src:439–494` (`TERBLO`)
**Secondary** L-010 (draw cost is CPU cost)
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Force `OVCNT ≥ 2`; assert `STRCNT` drops to 3 and one object migrates.
**Notes** A fixed-speed reimplementation never triggers this, so the game will feel denser than the
original. The planet-destruction sequence's stutter is deliberate.

---

### L-031 · Thrust and vertical are level-polled, not edge-triggered
**Mechanic** Input
**Claim** THRUST and the up/down stick have `FDB 0` entries in the switch table and are read
**level-wise** from the latched PIA sample every frame. Everything else is edge-triggered.
**Revision** Red Label
**Evidence** `SWTAB` has `FDB 0 / FCB 0,0` at the THRUST (bit 1) and DOWN (bit 7) slots. `PLAYER`
reads `LDA PIA21 / BITA #$02` for thrust, `LDA PIA31 / LSRA / BCS PLAUP` for up, `LDA PIA21 / BMI
PLADN` for down.
**Source** `defb6.src:1845–1860` (`SWTAB`); `defa7.src:2360, 2443–2447`
**Secondary** `williams.cpp:762–786` (identical bit assignment)
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Hold thrust; assert continuous acceleration. Hold fire; assert exactly one shot (L-062).
**Notes** The sample read is the **previous** frame's — one frame of input latency is built in. UP
wins over DOWN if both are asserted.

---

### L-032 · Process tick, wave-manager cadence
**Mechanic** Scheduler / waves
**Claim** `GEXEC` (the wave manager) runs every **15 frames** = 0.2496 s. Every `NAP` constant in
the game converts directly to frames.
**Revision** Red Label
**Evidence** `GEXEC` `NAP 15` per pass, `GTIME` incremented per pass, audit meter 6 at
`GTIME = 240`. 240 × 15 = 3600 frames = 59.9 s.
**Source** `defa7.src:1647–1731` (`GEXEC`)
**Secondary** L-002, L-004, L-005
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Count frames between `GEXEC` passes.
**Notes** `NAP` values in use: Lander 6, Mutant 3, Swarmer 3, Bomber 1, Baiter 6, humanoid walk 2,
fall 4, carry 1, `GEXEC` 15, score popup 50.

---

### L-033 · Attract-mode "TODAYS SOUND" is a two-write organ protocol
**Mechanic** Audio / attract
**Claim** `$24` selects organ mode (`$1B` = `ORGANT`), then `$3E` plays Phantom of the Opera or
`$3D` plays Bach's Toccata if the score took the #1 spot. The two-write protocol is required.
**Revision** Red Label
**Evidence** `amode1.src:148–165` writes `$3F`, `$24 ;SELECT ORGAN`, delay, `$3F`, then `$3E
;TODAYS SOUND - PHANTOM` or `$3D ;HIGH SCORE SOUND - TOCCATA`. Sound board: `JMPTBL` index 13 =
`ORGANT` (`$1B`); `ORGTAB` holds a 3-note PHANTOM and a 34-note TACCATA. `ORGANT` sets `ORGFLG`
negative so the **next** command byte is a tune index, not a sound number.
**Source** `amode1.src:148–165`; `vsndrm1.src:914–940, 1025–1069` (mwenge basis; the same IRQ
dispatch is `VSNDRM1.SRC:909–935` in the historicalsource tree — **+5 lines**, do not mix)
**Secondary** ROM verification: `defend.9[0x57] = 86 3E`, `[0x61] = 86 3D`, `[0x63] = 8E CC 02`,
`[0x66] = C6 3F`, `[0x6B] = C6 24` (this synthesis)
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Assert the four literals in `defend.9`.
**Notes** The `$3E/$3D/$3F/$24` forms are the ROM values. `historicalsource`'s `$FE/$FD/$FF/$E4`
decode to the same sound numbers but would light both diagnostic LEDs — an earlier revision
predating the LED-mask discipline.

---

### L-034 · Green derives from Blue; Red shares nothing with Green
**Mechanic** Revision history
**Claim** Release order is **White → Blue → Green → Red**. Eight of eleven Blue code images survive
bit-identical into Green (only `$D000` packaging and the `$F000` ROM differ). Red shares **zero**
images with Green — it is a complete re-release.
**Revision** all
**Evidence** `williams.cpp:2032–2052` vs `2009–2030`: `defeng02.bin d184ab6b` in both at `$E000`;
all seven banked CRCs identical. Red's eleven CRCs share nothing with Green's. Williams'
`INFO.SRC:15–23` defines the same ordering by content.
**Source** `historicalsource-defender/INFO.SRC:15–23`
**Secondary** `williams.cpp:1985–2052`
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** —
**Notes** Any claimed Blue→Green behavioral delta must be located in the `$F000` ROM. See Q-06 for
the Green→Red question.

---

### L-035 · Off-screen objects keep simulating
**Mechanic** Entities
**Claim** Objects on the inactive list are updated once per 8 frames at **8× velocity**, so they
arrive where they "should" have been. The active window is 500 px wide starting 100 px left of the
screen. Y **wraps** top-to-bottom rather than clamping.
**Revision** Red Label
**Evidence** `ISC1 LDD OYV,X / ASLB / ROLA ×3 / ADDD OY16,X`, same ×8 for X. `ISCAN`:
`LDD BGL / SUBD #100*32 / STD XTEMP … CMPD #500*32`. `SCPROC` cycle is `NAP 2 + NAP 2 + NAP 4` =
8 ticks.
**Source** `defa7.src:3300–3374` (`SCPROC`, `OSCAN`, `ISCAN`)
**Secondary** the scanner draws from **both** `OPTR` and `IPTR` (`amode1.src:1237–1242`), which only
makes sense if inactive objects are live
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Scroll away from an enemy for 10 s; return; assert its position matches continuous motion.
**Notes** Enemies do not freeze off-screen. This materially affects perceived enemy density.

---

### L-036 · Scanner shows off-screen objects
**Mechanic** Scanner
**Claim** The scanner draws blips from **both** the active (`OPTR`) and inactive (`IPTR`) lists.
**Revision** Red Label
**Evidence** `LDX #OPTR / LDU #SETAB / BSR SCNR3 / LDX #IPTR / BSR SCNR3 / STU SETEND`.
**Source** `amode1.src:1237–1242` (`SCNR`)
**Secondary** L-035
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Fly away from a Lander group; assert they remain on the scanner.
**Notes** This is the device's entire purpose. A reimplementation that renders only simulated
objects shows an empty scanner in exactly the situations the player needs it.

---

### L-037 · Scanner geometry and cadence
**Mechanic** Scanner
**Claim** Byte-columns `$30`–`$6F` (48–111), scanlines 8–39 (128 × 32 px), object blips on rows
12–37. Compresses the **entire 2048-px world** at 32 world px per byte-column. Permanently
player-centered via a `−$8000` half-world offset (free wraparound). **Redrawn only every 8 frames.**
Blip = 2 px × 2 scanlines, color word per class.
**Revision** Red Label
**Evidence** `SCANH EQU YMIN-34` = 8, `SCANER EQU $3008`. `MTLP` loops `$30` to `+64`. Mapping:
`XTEMP = BGL − $8000 + 150·32`; `column = $30 + ((OX16 − XTEMP) >> 10)`;
`row = (SCANH−1) + (OY16msb >> 3)`. Blip: `LDD OBJCOL,X / STD [,U++]`. Cadence from `SCPROC`.
**Source** `amode1.src:1182–1276`; `phr6.src:158–159`; `defa7.src:3300–3308`
**Secondary** `BLIPS` table `amode1.src:1173–1178` matches the per-class `OBI` macros in `defb6.src`
independently
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Count frames between scanner updates.
**Notes** The 8-frame cadence is visible — the scanner steps and lags. **Do not smooth it.** The
player's own marker is a separate 3-pixel cross derived from **screen** position, so it slides within
the white bracket as the ship moves.

---

### L-038 · The player marker on the scanner is screen-derived
**Mechanic** Scanner
**Claim** The player's scanner marker is computed from `PLAXC` (screen position), not world position,
so it slides within its bracket as the ship moves left/right on screen.
**Revision** Red Label
**Evidence** `LDD PLAXC / LSRA ×4 / LSRB ×3 / ADDD #$4B00+SCANH-1` then writes `$9099` at row r,
`$90` at r+2, `$09` at column−1 row r+1.
**Source** `amode1.src:1243–1259`
**Secondary** contrast with the object path at `amode1.src:1262–1271`, which uses `OX16` (world)
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Thrust to terminal velocity; assert the marker moves within the bracket.
**Notes** Easy to get wrong by "fixing" the marker to scanner center.

---

## Part B — SOURCE_CONFIRMED

Read directly in the 6809 source with a citable label. Not independently corroborated, but the
source is the shipped program's own text and these carry no known dispute.

---

### L-040 · Reverse writes exactly one variable
**Mechanic** Player
**Claim** `REV` two's-complement negates `PLADIR` into `NPLAD` and does nothing else. Velocity,
screen position, camera position and vertical state are all untouched.
**Revision** Red Label
**Evidence** `REV LDA REVFLG / BNE REVX / INC REVFLG / LDD PLADIR / COMB / COMA / ADDD #1 /
STD NPLAD / REV1 NAP 2,REV2 / REV2 LDA PIA21 / BITA #$40 / BNE REV1 / NAP 5,REVX1 /
REVX1 CLR REVFLG / REVX JMP SUCIDE` — the entire routine.
**Source** `defa7.src:3157–3171` (`REV`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Reverse at terminal velocity; assert velocity is unchanged that frame.
**Notes** The visible slide across the screen after a reverse is produced entirely downstream by the
camera base swap (L-043), not by `REV`.

---

### L-041 · Horizontal thrust and drag
**Mechanic** Player physics
**Claim** `PLAXV` is a **24-bit** accumulator whose top 16 bits are the velocity `V16`. Thrust adds
`PLADIR` (= ±`$0300`) at the 24-bit LSB → **ΔV16 = ±3.0 per tick**. Drag adds `−4·V16` at the same
position → **ΔV16 = −V16/64 per tick** (τ = 64 ticks ≈ 1.07 s). Acceleration is symmetric in both
facings.
**Revision** Red Label
**Evidence** Thrust: `LDD PLADIR / … ADDD PLAXV+1 / STD PLAXV+1 / LDA ITEMP / ADCA PLAXV /
STA PLAXV`. Drag: `LDD PLAXV / COMA / COMB / ADDD #1 / … ASLB / ROLA / ASLB / ROLA / ADDD PLAXV+1`.
`PLAXV RMB 3`. `PLADIR` initialized `LDD #$0300` at spawn and negated by `REV`.
**Source** `defa7.src:2342–2371` (`PLAYER`); `phr6.src:332`
**Confidence** SOURCE_CONFIRMED *(the derived rates are INFERRED — see L-100)*
**Impl** TBD **Test** From rest, log `V16` per tick for 200 ticks.
**Notes** `PLAXV RMB 3` is the single easiest thing to get wrong.

---

### L-042 · Vertical motion has an instantaneous kick and instantaneous release
**Mechanic** Player physics
**Claim** First tick of a direction snaps `PLAYV` to ±`$0100` (1 row/tick); subsequent ticks add ±8
up to ±`$0200` (2 rows/tick); **releasing the stick sets velocity to 0 in one tick**. Limits: row
≤ 43 (`YMIN+1`) and ≥ 238.
**Revision** Red Label
**Evidence** `LDD #0 / BRA PYV1` on neutral; `PLAUP1 LDD #-$100`; `ADDD #-8 / CMPD #-$200 / BGE PYV1
/ LDD #-$200`; `CMPB #YMIN+1 / BLS PLAYX` and `CMPB #238 / BHS PLAYX`.
**Source** `defa7.src:2441–2476` (`PLAYER` vertical)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Tap up for one frame; assert exactly 1 row of movement then a stop.
**Notes** No vertical inertia on release. 32 ticks (0.53 s) from 1 to 2 rows/tick. Hitting a limit
takes an early exit that skips the `PLAY16` store but leaves `PLAYV` at its old value.

---

### L-043 · Camera base positions and the forward lean
**Mechanic** Camera
**Claim** The ship sits at byte-column `$20` (pixel 64) facing right and `$70` (pixel 224) facing
left, plus a forward lean of `V16/8` byte-columns **applied only when the velocity sign matches the
facing**. Drifting backwards after a reverse zeroes the lean entirely.
**Revision** Red Label
**Evidence** `LDA PLADIR / BMI PV1 / LDA #$20 / TSTB / BMI PV1A / BRA PV2 / PV1 LDA #$70 / TSTB /
BMI PV2 / PV1A CLR PCX+1 / CLR PCX / PV2 LDB PCX+1 / ADDA PCX / STA PCX`, with `PCX` = `V16·32` in
1/256-column units.
**Source** `defa7.src:2373–2396` (`PLAY1`–`PV2`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Thrust from rest facing right; assert the ship moves *toward* screen center.
**Notes** Counter-intuitive and frequently "corrected" by mistake: **accelerating moves the ship
forward on screen, reducing the forward view.**

---

### L-044 · Camera easing preserves world X exactly
**Mechanic** Camera
**Claim** The camera eases at exactly **1 byte-column (2 px) per tick**, with a matching ±`$40`
world-unit scroll compensation (`BGDELT`) that preserves the ship's world X through the pan. A full
reverse pan is **80 ticks ≈ 1.33 s**.
**Revision** Red Label
**Evidence** `PV2A SUBD PLAX16 / BEQ PV9 / BLO PV3 / CMPD #$100 / BLS PV9 / LDD #$40 / STD BGDELT /
LDD #$100 / ADDD PLAX16` (mirrored), then `PV12 STD PLAXV / ADDD BGL / SUBD BGDELT / STD BGL`. Base
separation `$70 − $20` = `$50` = 80 columns.
**Source** `defa7.src:2397–2431`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Reverse at rest; count frames until the camera settles = 80.
**Notes** `BGL += V16 − BGDELT` while screen X moves ±2 px, so world X advances by exactly `V16`
every tick regardless of easing state. Within one column of target the ship snaps exactly on,
including its sub-column fraction.

---

### L-045 · Three distinct X representations
**Mechanic** Coordinates
**Claim** World X (`OX16`, 16-bit, 32 units/px), player screen X (`PLAX16`, **8.8 in byte-columns**),
and shell X (`OX16` as 8.8 whose initial fraction is the *other* axis's coordinate, with `OBJX`
literally the screen address). Vertical is **screen-space only** — there is no world Y and scrolling
never affects it.
**Revision** Red Label
**Evidence** `POUT2 LDA PLAX16+1 / ASLA` uses bit 7 of the low byte as the pixel phase;
`LDD PLAX16 / LSRA / RORB / LSRA / RORB / ANDB #$E0 / ADDD BGL / STD PLABX` converts to world.
`GETSHL`: `STD OBJX,X / STD OX16,X / EXG A,B / STD OY16,X`. `SHELL` scrolls shells explicitly by
`4·(BGLX − BGL)`.
**Source** `defa7.src:2321–2326, 2433–2440` (player); `defa7.src:2557–2643` (`GETSHL`, `SHELL`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Assert `PLABX` tracks world position across a full pan.
**Notes** Conflating these is the most likely source of a wrong-feeling ship. Lasers are a **fourth**
case: screen space with **no** scroll compensation at all (L-051).

---

### L-046 · HUD layout
**Mechanic** UI
**Claim** Score is 6 BCD digits at 8 px pitch with a 2-digit minimum (leading zeros erased).
**Ships cap at 5 icons laid out horizontally** (`ADDA #$06` = +6 byte-columns).
**Smart bombs cap at 3 icons stacked VERTICALLY** (`ADDB #4` = +4 scanlines).
**Revision** Red Label
**Evidence** `SCRTR0`: `LDB #6`, leading zeros erased with `COFF` unless `CMPB #2 / BLS SCT3A`.
`LDSP`: `CMPA #5 / BLS / LDA #5`, `BLKCLR $2006`, `ADDA #$06`. `SBDSP`: `CMPA #3 / BLS / LDA #3`,
`BLKCLR $030B`, `ADDB #4`. Positions from `phr6.src:150–157`.
**Source** `defa7.src:551–591` (`SCRTR0`), `842–859` (`LDSP`), `884–901` (`SBDSP`);
`phr6.src:150–157`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Award 6 ships and 5 smart bombs; assert 5 ship icons horizontally, 3 bomb
icons vertically.
**Notes** `ADDA` = columns, `ADDB` = scanlines. Almost always got wrong.

---

### L-047 · Collision model
**Mechanic** Collision
**Claim** Screen-space AABB reject, then a byte-granular overlap scan requiring **both** sprites'
bytes to be non-zero. Granularity is **2 px horizontally, 1 px vertically**. **Both sprites are
always read from their even-phase image** regardless of the phase actually drawn. Objects with
`OBJX == 0` (not drawn this frame) cannot be hit at all.
**Revision** Red Label
**Evidence** `COL1 LDD OBJX,X / BEQ COLLP / CMPA LRX / BHS COLLP / CMPB LRY / BHS COLLP /
ADDD [OPICT,X] …` then `IC50 LDA B,U / BEQ IC6 / LDA B,Y / BEQ IC6`, with `LDU OBJP0,U` and
`LDY OBJP0,Y`.
**Source** `defa7.src:2907–3020` (`COLIDE`, esp. 2975–2979)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Fire at an enemy at odd and even pixel phases; assert identical hit boxes.
**Notes** True per-pixel coordinates give a systematically different hitbox than the original's
even-phase 2-px-quantised test.

---

### L-048 · The collision point becomes the explosion center
**Mechanic** Collision / VFX
**Claim** `COLIDE` back-solves the exact screen address of the colliding byte into `CENTMP`, which
`EXST` uses as the explosion's center of expansion (falling back to the geometric center if out of
bounds).
**Revision** Red Label
**Evidence** `FC1`/`FC2` subtract `OBJH` repeatedly to convert the data offset back to a screen
address, `STY CENTMP`, then `JSR [OCVECT,X]`. `EXST` validates `CENTMP` against the sprite bounds.
**Source** `defa7.src:2904–3011`; `samexap7.src:103–116` (`EXST`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Shoot a Lander in the nose; assert an asymmetric burst.
**Notes** Several call sites deliberately override `CENTMP` before exploding (absorbed and splatted
humanoids). Bounding-box collision changes both hit feel *and* explosion appearance.

---

### L-049 · Player collision lags the physics
**Mechanic** Collision
**Claim** Player collision runs once per `EXEC` pass using `PLAXC`/`PLAYC` — the position as last
**drawn**, not the position just computed.
**Revision** Red Label
**Evidence** `COLCHK LDA STATUS / BITA #$10 / BNE COLCX / LDD PLAXC / LDU #PLAPIC / TST PLADIR /
BPL COLC1 / LDU #PLBPIC … INC PCFLG / JSR COLIDE`.
**Source** `defa7.src:3132–3153` (`COLCHK`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** —
**Notes** Do not test against `NPLAXC`/`NPLAYC`. `PCFLG` lets per-object kill routines distinguish a
player collision from a laser hit — `ASTKIL` uses it; `BKIL` does not (L-053).

---

### L-050 · Shells and mines are a separate, indestructible list
**Mechanic** Entities
**Claim** Enemy shells and mines live on `SPTR`, which **neither the laser nor the smart bomb ever
walks**. They can only be cleared by hyperspace. Cap 20.
**Revision** Red Label
**Evidence** `COLIDE LDX #OPTR` is the only entry the laser uses; `SBMB00 LDX OPTR`. Only `COLCHK`
reaches shells, via `LDX #SPTR / JSR COL0`. `HYP02 LDX SPTR / BEQ KB2 / JSR KILSHL / BRA HYP02`.
**Source** `defa7.src:2907, 2778–2788, 3185, 3145–3146, 3220–3224`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Smart-bomb a screen full of mines; assert none die.
**Notes** Model shells in a separate list the laser does not scan.

---

### L-051 · The laser is screen-space and does not scroll
**Mechanic** Laser
**Claim** The laser is a **process that draws directly into the framebuffer in screen space** and is
never scrolled. Max 4 concurrent. Head advances **4 byte-columns (8 px) per tick**; tail erases
1 column per tick, so the beam grows 6 px longer each tick. Right boundary column `$98`, left
column 5. Body color is palette slot 1, which `COLR` animates.
**Revision** Red Label
**Evidence** `LASR LEAX $704,X … LASR1 STB ,X / LEAX $100,X / DECA / BNE LASR1` (4 columns of `$11`)
then `LDB #$99 / STB ,X`; sparkle loop writes 3 columns; `CLR [PD+4,U] / INC PD+4,U`; `NAP 1,LASR0`.
No `BGL` term anywhere in `LASR`/`LASL`. `LFIRE LDA LFLG / CMPA #4 / BHS LFIREX`.
**Source** `defa7.src:2763–2895` (`LFIRE`, `LASR`, `LASL`, `LCOL`); `defb6.src:1940–1942` (`LASP1`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Fire, then scroll; assert the beam stays put on screen.
**Notes** `LASP1 FCB 8,1 / LASD10 FDB $FFFF ×4` is a **collision mask only**, used by `LCOL`. The
head/tail asymmetry is what makes it a growing streak rather than a fixed bolt. The right-hand
collision box is offset 6 columns behind the head; the left-hand box is not offset at all.

---

### L-052 · Smart bomb scope
**Mechanic** Smart bomb
**Claim** Kills only objects on the **active** list that are currently rendered (`OBJX ≠ 0`) and have
`OTYP < 2`. Humanoids (`$10`), score popups (`$11`) and mid-appear objects (bit 1) are exempt.
Mines and shells survive. Kills invoke each victim's own `OCVECT`, so they score identically to shot
kills. Inventory comes from `NSHIP` (L-024).
**Revision** Red Label
**Evidence** `SBMB0 BEQ SBMBX / LDD OBJX,X / BEQ SBMB2 / LDA OTYP,X / CMPA #$02 / BHS SBMB2 /
JSR [OCVECT,X] / BRA SBMB00` — restarts the walk after each kill because the list mutates. It walks
`OPTR` only.
**Source** `defa7.src:3185–3195` (`SBOMB`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Smart-bomb with a Lander mid-grab (`OTYP=1`); assert it dies and releases the
humanoid.
**Notes** Area of effect is "whatever `OPROC` drew" — horizontally within 300 px of the screen's left
edge **and** inside the vertical band currently being redrawn, so an on-screen enemy can survive by
one frame (L-029).

---

### L-053 · The shell that kills you pays you 25 points
**Mechanic** Scoring quirk
**Claim** `BKIL` (the shared shell collision vector) does **not** check `PCFLG`, so the player is
awarded 25 points for the mine or shell that kills him.
**Revision** Red Label
**Evidence** `BKIL LDD #$25 / JSR SCORE / DEC BMBCNT / JSR KILSHL`. No `PCFLG` test on the path.
Contrast `ASTKIL`, which begins `LDA PCFLG ;PLAYER?`.
**Source** `defa7.src:2700–2719` (`BKIL`); `defb6.src:384` (`ASTKIL`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Fly into a mine; assert +25 before the death sequence.
**Notes** Preserve it. It is a genuine original quirk.

---

### L-054 · Hyperspace
**Mechanic** Player
**Claim** Destination world X is the full 16-bit `SEED:HSEED`; facing is bit 0 of `HSEED`; row is
`(HSEED>>1) + 42`; velocity is **fully zeroed**; death occurs when `LSEED > 192` — **63/256 =
24.61 %** measured over the actual RNG cycle.
**Revision** Red Label
**Evidence** `LDD SEED / STD BGL / STD BGLX / LSRB / BCC HYP0 / LDD #$2000 / LDX #$0300 …
HYP0 LDX #-$0300 / LDD #$7000 / HYP00 STD PLAX16 / STX NPLAD / LDB HSEED / LSRB / ADDB #YMIN /
STB PLAY16` … `HYP2 … LDA LSEED / CMPA #192 / LBHI PLEND`.
**Source** `defa7.src:3213–3278` (`HYPER`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** 10 000 hyperspaces; assert death rate 24.6 % ± 0.5 %.
**Notes** The ship always re-enters at the base column for its new facing with zero velocity, so no
easing occurs. Landing row range 42–169 — never the lower playfield.

---

### L-055 · Wave-complete accounting
**Mechanic** Waves
**Claim** Wave completion is `WVCHK == 0` over exactly seven counters:
`LNDCNT + LNDRES + TIECNT + PRBCNT + SWCNT + SCZCNT + SCZRES`. **Mines, shells and Baiters do not
gate the wave.** The `+1` ship awarded at wave end is canceled by the `−1` every life-start
performs, so **clearing a wave grants no net ship and costs no life**.
**Revision** Red Label
**Evidence** `WVCHK LDA LNDCNT / ADDA LNDRES / ADDA TIECNT / ADDA PRBCNT / ADDA SWCNT / ADDA SCZCNT
/ ADDA SCZRES`. `GEX0` on zero: `… JSR BONUS / LDX PLRX / INC PLAS,X / JMP PLSTR0`; `PLSTR5` does
`DEC PLAS,X`.
**Source** `defa7.src:1748–1755` (`WVCHK`), `1657–1666` (`GEX0`), `1267` (`PLSTR5`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Clear a wave with mines on screen; assert it completes.
**Notes** Model lives as "spend on spawn, refund on wave clear", not "spend on death".

---

### L-056 · Wave parameter table and the inter-wave ramp
**Mechanic** Waves / difficulty
**Claim** `WVTAB` is **23 rows × 8 bytes** (`MAX, MIN, INTRADELT, INTERDELT, W1..W4`), byte-aligned
with the 23-byte `ELIST`. `GETWV` loads column `min(wave, 4)` then applies
`min(GA1 + max(wave−4, 0), GA2)` inter-wave delta steps to the **freshly reloaded** base — so
parameters never accumulate across waves. With factory defaults the step count is
5, 5, 5, 5, 6, 7, … 15 and **saturates from wave 14 onward**.
**Revision** Red Label
**Evidence** `WVTAB` header `*MAX,MIN,INTRADELT,INTERDELT / *W1,W2,W3,W4`; 23 rows in the same order
as `ELIST`; `GETWV2` copies one byte per row stepping U by 8. `GETWV3`: `PULS A / SUBA #4 / BHS
GTWV30 / CLRA / GTWV30 STA XTEMP`, then `GA1` added and `GA2` used as a ceiling; `GETWV4 … BSR WDELT
/ DECA / BNE GETWV4`.
**Source** `blk71.src:674–723` (`WVTAB`); `phr6.src:382–406` (`ELIST`);
`defa7.src:1849–1901` (`GETWV`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Assert wave 1 = 15 Landers only; wave 2 = 20 Landers + 3 Bombers + 1 Pod;
wave 3 = 20/4/3; wave 4+ = 20/5/4.
**Notes** The raw W1..W4 columns are **never played as printed** — even wave 1 is pre-ramped 5 steps.
`SCZRES` and `SWMRES` are 0 in every column: Mutants and Swarmers are never table-spawned.

---

### L-057 · A second, intra-wave difficulty ramp
**Mechanic** Difficulty
**Claim** Every **40 `GEXEC` ticks (600 frames ≈ 10.0 s)** a second ramp mutates the **live** `ELIST`
and **persists across deaths within the wave**.
**Revision** Red Label
**Evidence** `GEXEC` sets `LDA #40 / STA PD,U` at entry; each pass `DEC PD,U / BNE GEX6 / LDB #2 /
LDY #ELIST / JSR WDELT / LDA #40 / STA PD,U`. `B=2` selects `INTRADELT`. `PLSAV` copies `ELIST` into
`PENEMY` on death; `PLRES` copies it back.
**Source** `defa7.src:1647–1649, 1723–1731, 1491–1512, 1579–1585`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Camp in a wave for 60 s; assert Lander XV has risen by 18.
**Notes** Per 10 s: Lander XV +3, Lander YV LSB +`$10`, `LDSTIM` −4, `SZYV` LSB +8, `SZXV` +8,
`SZSTIM` −2, `SWXV` +8, `SWSTIM` −2, `UFOTIM` −12, `UFSTIM` −1, `UFOSK` −12. **Camping makes a wave
materially harder and the effect survives losing a life.**

---

### L-058 · Planet restore
**Mechanic** Waves
**Claim** Every `GA4`-th wave (factory 5) the humanoid count is **assigned** to 10 — not topped up —
and it happens at the end of the preceding wave, after the survivor bonus is paid. `GA4 = 0` is
trapped and disables the feature.
**Revision** Red Label
**Evidence** `GETWV`: `INC PWAV,X` first, then `LDX #GA1+6 / JSR RCMOSA / STA XTEMP / TSTA /
BEQ GTWV01 / GTWV00 SUBA XTEMP / BLO GTWV01 / BNE GTWV00 / LDA #10 / STA PTARG,X`.
**Source** `defa7.src:1849–1863` (`GETWV`); `romc8.src:814` (`GA4 = $05`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Lose all humanoids on wave 3; assert 10 present on wave 5.
**Notes** The zero case must be trapped or the loop never terminates.

---

### L-059 · Planet destruction is a persistent global state change
**Mechanic** Waves
**Claim** When the last humanoid dies, `ASTCLR` spawns `TERBLO`, which **permanently** disables the
terrain via `STATUS` bit 1; thereafter `LANDST` spawns **Mutants instead of Landers**, and `STCHK`
recomputes `STATUS` bit 1 at every player start so the terrain stays off.
**Revision** Red Label
**Evidence** `ASTCLR DEC ASTCNT / BNE ASTCX / NEWP TERBLO,STYPE`. `TERBLO` sets `STATUS |= 2` (the
IRQ then skips `BGOUT`), erases the terrain and the 64-entry scanner terrain table, then runs 16
flash/explosion iterations while forcing `OVCNT = 8` (`;PHONY OVERLOAD`).
`LANDST: LNDST0 TST ASTCNT / BNE LNDST1 / JMP SCZS0`.
**Source** `defb6.src:419–494` (`ASTCLR`, `TERBLO`), `654–656` (`LNDST0`);
`defa7.src:1317–1324, 1984–1987`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Kill all 10 humanoids; assert terrain gone and only Mutants thereafter.
**Notes** The frame stutter during the sequence is deliberate (L-030).

---

### L-060 · Humanoid economy
**Mechanic** Humanoids
**Claim** Catching a falling humanoid pays **500** and depositing it pays a **second 500**. A
humanoid that falls safely uncaught pays **250**. Shooting a humanoid pays **nothing**. Re-touching
a carried humanoid pays nothing.
**Revision** Red Label
**Evidence** `AKIL1` (PCFLG set, process not yet `AFALL2`) does `NEWP P500` and switches `PADDR` to
`AFALL2`. `P500 LDD #$0150` = 500. `ALAND0` (carried to ground) starts a second `P500`; `ALAND`
(safe uncaught landing) starts `P250 LDD #$0125` = 250. `ASTK1` has no `JSR SCORE` on its path. The
no-rescore guard is `CMPD #AFALL2`.
**Source** `defb6.src:388–397, 398–417, 496–529, 958–975`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Full rescue; assert 500 at catch and 500 at deposit.
**Notes** A full rescue is **1000**, split into two events at distinct moments.

---

### L-061 · Lander abduction mechanics
**Mechanic** Humanoids
**Claim** Target selection is a **global round-robin cursor `TPTR`** over `TLIST` — not nearest, not
random. The Lander **never steers horizontally during the hunt**; it keeps its spawn velocity and
only converges in X after a coarse 32-px block test passes. Cruise altitude is ground − 50 px with a
20 px dead band. The mutation threshold is `OY16 ≤ YMIN+8` = **screen row 50**, and reaching it
starts an interruptible per-frame suck-in that destroys the humanoid before mutating.
**Revision** Red Label
**Evidence** `GTARG LDX TPTR / GT1 LEAX 2,X / CMPX #TLIST+64 / BLO GT2 / LDX #TLIST / GT2 LDD ,X /
BNE GT3 … GT3 STX TPTR`. `LANDS0` sets only `OYV`. Proximity: `LDA OX16,X / ANDA #$FC / STA XTEMP /
LDA OX16,Y / ANDA #$FC / CMPA XTEMP / BEQ LANDG0`. `LANDF LDA OY16,X / CMPA #YMIN+8 / BLS LANDFX`.
**Source** `defb6.src:628–647` (`GTARG`), `691–736` (`LANDS0`, `LANDG0`), `794–839` (`LANDF`,
`LNDFXA`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Spawn 5 Landers with 5 humanoids; assert each takes a different target.
**Notes** A nearest-target heuristic makes squads converge on one humanoid; the original
deliberately spreads the attack. Reproducing "seek the target horizontally" makes abductions far too
efficient.

---

### L-062 · Firing cadence — no autofire
**Mechanic** Input
**Claim** A rising-edge detector requiring **two consecutive zero samples**. No repeat timer, no
autofire. Switches are sampled once per frame and **only the lowest set bit is decoded per call**,
so simultaneous presses serialise over successive frames.
**Revision** Red Label
**Evidence** `SSC0 LDA PIA21 / ORA PIA22 / COMA / LDB PIA21 / STB PIA22 / LDB PIA2 / STB PIA21 /
LDB PIA3 / STB PIA31 / ANDA PIA21 / BEQ CSCAN` then `SW0 ADDB #4 / LSRA / BCC SW0`. `SNDSEQ` falls
through into `SSCAN` with no `RTS`, and is `JSR`ed once per frame from the IRQ.
**Source** `defa7.src:757–805` (`SSCAN`), `1943`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Hold fire for 60 frames; assert exactly one shot.
**Notes** A player must release for two full frames before a new shot registers.

---

### L-063 · Enemy bullets are constant-TIME interceptors
**Mechanic** Enemy fire
**Claim** Shell velocity is **4× the distance to the player at the moment of firing**, so every shot
takes ~**64 frames (~1.07 s)** regardless of range. There is a ±16 px aim error in both axes, and
~53 % of shots add `PLAXV × 4` as lead. Global cap 20; default lifetime 20 `SHSCAN` ticks (~2.7 s).
**Revision** Red Label
**Evidence** `SHOOT`: `B = (SEED & $1F) − $10 / ADDB PLAXC / SUBB OBJX,X / SEX / ASLB ROLA / ASLB
ROLA / STD OXV,X` (×4), same for Y with `LSEED`; plus `LDB SEED / CMPB #120 / BLS SHOOT0`.
**Source** `defb6.src:531–570` (`SHOOT`); `defa7.src:2557–2643`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Fire from 20 px and from 200 px; assert both arrive in ~64 frames.
**Notes** Close-range enemy fire is slow and dodgeable; long-range fire is fast. A constant-speed
projectile changes the game's threat model completely.

---

### L-064 · Bomber squad behavior
**Mechanic** Enemies
**Claim** The Bomber squad is a **super-process that updates only ONE randomly chosen member per
frame**, spawns exactly **half a world away** from the player (`$8000` = 1024 px), and holds a
16–32 px vertical standoff from the player's altitude. Off screen, Bombers random-walk a cruise
altitude clamped to rows 64–104. Mine drop is gated by `LDA LSEED / ANDA #$7 / BNE TIEX`.
**Revision** Red Label
**Evidence** `TIEST` uses `MSPROC` with members in `PD/PD2/PD4/PD6` and spawns at
`PLABX + 1.5·index·256 + $8000` at row `$50`. `TIE LDA SEED / ANDA #$6 / ADDA #PD / LDX A,U`. On
screen: `a ≥ $20` → `OYV += −$10`; `a ≤ $10` → `OYV += +$10`; between → unchanged.
**Source** `defb6.src:977–1132` (`TIEST`, `TIE`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Assert a 4-strong squad updates each member ~15×/s.
**Notes** **A lone surviving Bomber updates 60×/s and is measurably more agile than a full squad.**

---

### L-065 · Baiter behavior
**Mechanic** Enemies
**Claim** The Baiter **copies the player's velocity** and adds a fixed 2 px/frame term — which is why
it can always catch a thrusting player. It re-seeks only probabilistically, gated by `UFOSK`
(**inverted from its name: higher = less aggressive**), and only when the 3-frame image cycle wraps
(every 18 frames). Baiters spawn **on screen**, unlike every other enemy. Cap 12 alive; **excluded
from the wave-complete test**.
**Revision** Red Label
**Evidence** `UFONV0`: `XTEMP = $4001` negated by the sign of `(OX16 − PLABX)`;
`ADDD #20*32 / CMPD #40*32 / BLS UFONV3` skips X within ±20 px, else `LDB XTEMP / SEX / ADDD PLAXV /
STD OXV,X`. Y: `CLRB / LDA XTEMP+1 / ADDD PLAYV / ASRA / RORB / STD OYV,X`.
`UFONV LDA SEED / CMPA UFOSK / BLS UFONVX`.
**Source** `defb6.src:25–79` (`UFONV`, `UFONV0`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Thrust to terminal velocity away from a Baiter; assert it closes.
**Notes** Re-seek probability per 18 frames = `(255 − UFOSK)/256`, rising from 22 % at wave 1
(`UFOSK` 200) to 69 % by wave 15 (`UFOSK` 80).

---

### L-066 · Baiter trigger timing
**Mechanic** Enemies
**Claim** The Baiter timer's **pending value and reload are both compressed by the number of enemies
still alive**: ≤ 8 → halved, ≤ 3 → quartered.
**Revision** Red Label
**Evidence** `GEX00`: with `A` = `WVCHK` total, `CMPA #8 / BHI GEX002` then `B = UFOTIM>>1`, and a
second `>>1` if `A ≤ 3`; `INCB / CMPB UFOTMR / BHS GEX002 / STB UFOTMR` only ever shortens. On
expiry: `CMPA #4 / LDA UFOTIM / BHS GEX003 / LSRA / LSRA / JSR RMAX`. Then
`LDA UFOCNT / CMPA #12 / BHS GEX11 / JSR UFOST / INC UFOCNT`.
**Source** `defa7.src:1667–1691` (`GEX00`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** At wave 1, assert the first Baiter at ~47.9 s, cut to 24.2 s at ≤ 8 enemies and
12.2 s at ≤ 3.
**Notes** Tick is `GEXEC`'s 0.2496 s. Baiters never block wave completion.

---

### L-067 · Pod burst distribution
**Mechanic** Enemies
**Claim** A Pod releases 1–7 Swarmers via `RMAX(6)`, a **strongly non-uniform** distribution: 4/5/6/7
each ≈ 25 %, and 1/2/3 at 0.39 % each (mean 5.45). Further clipped by a global 20-Swarmer cap.
**Revision** Red Label
**Evidence** `PRBKIL KILO $0210,PRHSND / LDA #6 / JSR RMAX / LEAY ,X / JSR MMSW / DEC PRBCNT`.
`RMAX JSR RAND / RMAX1 CMPA ,S / BLS RMAXX / LSRA / BRA RMAX1 / RMAXX INCA` — halve until ≤ limit,
then +1. Enumerated over a uniform byte: 1,2,3 → 1/256 each; 4 → 64/256; 5,6,7 → 63/256 each.
`MMSW LDA SWCNT / INCA / CMPA #20 / BHI MMSWX`.
**Source** `defb6.src:116–124` (`PRBKIL`), `141–172` (`MMSW`); `defa7.src:1733–1743` (`RMAX`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Burst 10 000 Pods; assert the distribution.
**Notes** Do not use a uniform 1..7 or a fixed count. `RMAX` is used elsewhere too and has this same
shape everywhere.

---

### L-068 · A Pod has no AI at all
**Mechanic** Enemies
**Claim** `PRBST` creates only an object with a constant drift velocity, integrated by `VELO` with
vertical wrap. **Its entire behavior is its death routine.** `OYV` is forced away from zero.
Spawn X is an absolute world position (pixels 128–639), not relative to the player.
**Revision** Red Label
**Evidence** `PRBST` contains no `NEWP`/`MKPROC`; it does `OBI PRBP1,PRBKIL,$CCCC`, sets
`OX16/OY16/OXV/OYV`, calls `APVCT` and loops. Positive `OYV` → `ORB #$20` (32..63); negative →
`ANDB #$DF` (−64..−33).
**Source** `defb6.src:85–114` (`PRBST`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Watch a Pod for 30 s; assert pure ballistic drift with top-bottom wrap.
**Notes** Pods drift at up to ~1 px/frame in X and 0.125–0.246 px/frame in Y, never vertically
stationary.

---

### L-069 · Swarmer birth
**Mechanic** Enemies
**Claim** `RANDV` gives `OYV` = sign-extended `SEED × 2` (≈ ±1 px/frame) and `OXV` =
`(LSEED & $3F) − $20` (±1 px/frame) from a **single** `RAND` call per child, plus a 0–31 frame
stagger before the child first thinks.
**Revision** Red Label
**Evidence** `RANDV JSR RAND / LDB SEED / SEX / ASLB / ROLA / STD OYV,X / LDB LSEED / ANDB #$3F /
ADDB #-$20 / SEX / STD OXV,X`. `MMSW LDD HSEED / ANDB SWAC / STB PD2,U / ANDA #$1F / STA PTIME,U /
LDA SWSTIM / JSR RMAX / STA PD4,U`.
**Source** `defb6.src:126–139` (`RANDV`), `141–172` (`MMSW`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** —
**Notes** Successive children in one burst have **correlated** velocities (the LFSR advances once per
child). The `PTIME` stagger is what stops a Pod burst moving as a rigid block.

---

### L-070 · Humanoid fall is lethal by VELOCITY, not height
**Mechanic** Humanoids
**Claim** A fall is lethal iff `OYV > $E0` at ground contact — from rest, ~51 px of fall (116
frames). Acceleration is `+8` every 4 frames with terminal `$2F8`. `LKIL1` always zeroes `OYV`, so
the fall always starts from rest.
**Revision** Red Label
**Evidence** `AFALL LDD #8 / ADDD OYV,X / CMPD #$300 / BHS AFALL0 / STD OYV,X` every 4 frames; then
`JSR GETALT / CMPA OY16,X / BHI AFALL1` and `LDD OYV,X / CMPD #$E0 / BLS ALAND` else explode.
`LKIL1 LDD #0 / STD OYV,U`.
**Source** `defb6.src:908–943` (`AFALL`); `defb6.src:905` (`LKIL1`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Shoot a Lander at increasing altitudes; find the lethal threshold.
**Notes** Test the **velocity** at contact, not the height. The ~51 px figure is derived, not a ROM
constant.

---

### L-071 · A Lander whose passenger dies vanishes silently
**Mechanic** Enemies / edge case
**Claim** A Lander whose captured humanoid is destroyed mid-ascent is removed with **no explosion, no
score and no sound**, and is returned to the unspawned reserve pool — so it will be re-dripped later
in the same wave.
**Revision** Red Label
**Evidence** `LNDFXA LDD [PD4,U] / BNE LNDFX0 / JSR KILOFF / DEC LNDCNT / INC LNDRES / JMP SUCIDE`.
`KILOFF` is `KILLOB` + `OFSHIT` only.
**Source** `defb6.src:805–812` (`LNDFXA`); `defb6.src:1150–1160` (`KILOFF`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** —
**Notes** `LNDRES` is part of `WVCHK` (L-055), so this changes both the enemy budget and the wave
length. Easily missed.

---

### L-072 · Mines
**Mechanic** Enemies
**Claim** Bomber mines are stationary in world space with a **1–32 tick random lifetime** (0.13–4.3 s)
and a 10-shell drop gate. They cannot be shot (L-050); touching one pays 25 and then kills you
(L-053).
**Revision** Red Label
**Evidence** `BOMBST LDA BMBCNT / CMPA #10 / BHS BMBSTX` and `LDA SEED / ANDA #$1F / INCA /
STA ODATA,X`.
**Source** `defb6.src:1134–1149` (`BOMBST`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** —
**Notes** `BOMBST` also contains dead code — a doubled sign-extended `HSEED` that is never stored.

---

### L-073 · Explosion engine (enemies)
**Mechanic** VFX
**Claim** Not a particle system. `EWRITE` cuts the dying object's own sprite into **2 × 2-pixel
tiles** and re-blits each at `centre + (dx·SIZE, dy·2·SIZE)`, preserving each tile's original color
bytes. `SIZE` ramps `$01 → $30` by `+$AA` per frame over exactly **72 frames (1.198 s)**; the
sequence is 1,2,2,3,4,4,5,… — not a smooth ramp. Appear is the same engine backwards over **47
frames (0.782 s)**.
**Revision** Red Label
**Evidence** `EWRITE`: `XSTART = CENTER.x − XOFF·SIZE` then `ADDA SIZE` per column;
`DSIZE = SIZE·2`, `YSTART = CENTER.y − YOFF·DSIZE − FLAVOR` then `ADDB DSIZE`; write chain
`STD ,--U / LDY ,X++ / STY [,U]`. `EXST` sets `RSIZE = $0100`; `EXPU` does `ADDD #$AA / STD RSIZE,Y
/ CMPA #$30 / BLS EXPU3`. `APST` sets `RSIZE = $AF00`; `EXPU4` does `SUBD #$100 / BPL EXPU6`.
**Source** `samexap7.src:90–91, 131–164, 248–255, 271–311, 365`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Kill a Lander; assert 1.20 s and recognizable Lander-colored tiles.
**Notes** Apparent flicker comes from **animated palette entries** (L-021), not from the engine. A
generic spark system will look wrong.

---

### L-074 · Explosion slots can starve
**Mechanic** VFX
**Claim** Only **16** explosion/appear RAM slots exist (`$9C00–$9FFF`, `RAMSIZ = $40`), and an
in-progress *appear* blocks reuse of its slot. If every slot is an appear, the explosion is silently
dropped. `EXST` also refuses if the object is more than ~304 px from `BGL`.
**Revision** Red Label
**Evidence** `RAMALS` spans `$9C00–$9FFF` with `RAMSIZ = $40`. `EXST1`–`EXST3` round-robin from
`LSEXPL`, skipping slots whose `RSIZE` is negative (appear) and erasing/reusing live explosions;
`EXST3 CMPY LSEXPL / BEQ EXST8` quits with no explosion.
**Source** `samexap7.src:69–89`; `phr6.src:113–114, 208–209`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Burst a Pod into 7 appearing Swarmers while killing 3 Landers; watch for
missing explosions.
**Notes** Also constrains the erase table to 26 tiles per slot, which every Defender sprite (max 24)
respects.

---

### L-075 · Player death explosion is a different engine
**Mechanic** VFX
**Claim** The **player's** death is a **128-particle** system in `blk71.src` with a rejection test
that keeps the debris field circular, and a 15-entry color ramp lasting **112 frames (1.87 s)**.
**Revision** Red Label
**Evidence** `PNBITS EQU $80`; `TABLE RMB 10*PNBITS` (10 bytes/particle: screen addr, X16, Y16, XV,
YV). Init seeds LFSRs `$0808` and `$1732` and rejects any particle where
`|vx| + |vy|/2 ≥ $016A` (`;CHECK FOR CORNERS`). Update: 1 frame per step; kill if `Y < $2A` or
`X > $98`. `PXCOL FCB $FF,$7F,$3F,$37,$2F,$27,$1F,$17,7,6,5,4,3,2,0`; 56 frames on the first entry
then 4 each.
**Source** `blk71.src:26, 78, 564–672`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Die; assert 1.87 s and a round burst.
**Notes** Particles draw `$BBBB` (slot B) on even phase, `$0B0B`/`$B0B0` across two columns on odd
phase. **The corner rejection is what makes the burst round instead of square.** Do not conflate
with L-073.

---

### L-076 · Terrain is a bit stream, not a heightmap
**Mechanic** Terrain
**Claim** `TDATA` is 256 bytes = **2048 bits, one bit per pixel of a ±1 random walk**, wrapping to
close the loop exactly at the world width. Not a vertex list, not a ROM heightmap. A 1024-entry
altitude table is **derived at init**.
**Revision** Red Label
**Evidence** `TLEN EQU $100`. Four bit-walkers rotate the byte MSB-first and wrap at `TDATA+TLEN`.
Vertical accumulators `LOFF`/`ROFF` seeded to `$E0` = 224; MSB set = UP (`DEC LOFF`), clear = DOWN.
**Source** `blk71.src:18, 95–149, 375–406 (BGALT), 412–506, 510–525 (TDATA)`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Replay the walk; assert it closes on itself after 2048 px.
**Notes** **Copy the 256 bytes verbatim.** Any procedural or spline reconstruction produces a
different mountain range.

---

### L-077 · Terrain rendering
**Mechanic** Terrain
**Claim** Two pre-built "flavour" tables of 152 × 3-byte entries, **double-mapped as ring buffers**,
selected by `BGL` bit 5 for 1-pixel phase. Output is a 1-pixel-wide brown (color index 7) polyline,
2 lit pixels per byte-column, erased via a 152-entry address table `STBL`. **There is no screen
clear per frame.**
**Revision** Red Label
**Evidence** `TERTF0`/`TERTF1 RMB $390` each; `ADDL01` writes each entry twice, `$1C8` bytes apart.
Flavour at build `LDA #$20 / BITA BGLX+1`, at draw `BITB #$20`. Entry = (scanline, then `$7007` for
UP or `$0770` for DOWN). Output loop uses **S as a source pointer** and the column counter A doubles
as the screen address high byte; 8× unrolled, 152 columns.
**Source** `blk71.src:69–73, 180–254`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** —
**Notes** S is **not a stack** during the loop; interrupts must be considered. The double-mapped ring
buffer is what removes wrap logic from the inner loop.

---

### L-078 · Stars
**Mechanic** Background
**Claim** 16 stars scrolling at exactly **half** the terrain rate (2:1 parallax), each a single pixel
whose left/right position within its byte flips with scroll phase. Cut to 3 under CPU overload.
**Revision** Red Label
**Evidence** `STOUT`: `LDD BGL / ANDB #$80 / STD ITEMP / LDD BGLX / ANDB #$80 / SUBD ITEMP / ASLB /
ROLA` — quantises to `$80` (4 world px) then doubles, so 4 world px of scroll = 1 byte-column of
star movement. Phase mask `ITEMP2` = `$F0` or `$0F` chosen by `BGL` bit 6. `STRCNT = 16` from
`STINIT`.
**Source** `defa7.src:2073–2160` (`STINIT`, `STOUT`); `defa7.src:3065–3067`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Scroll one screen; assert stars move half as far as the terrain.
**Notes** The drop to 3 stars is a visible symptom of load, not a bug (L-030).

---

### L-079 · Character font and text engine
**Mechanic** Text
**Claim** 45 glyph descriptors, mostly 6 × 8 px; `I` is 4 px, `M` and `W` are 8 px. Text is **not**
pre-shifted, so it positions at **2-pixel granularity only**. `'@'` is the blank block used for Hall
of Fame initials and leading-zero substitution; `'/'` terminates a word and never renders.
**Revision** Red Label
**Evidence** `CHRTBL` = 4-byte entries `FDB widthheight, pointer`: `$0108` punctuation, `$0308`
digits/letters, `$0208` for I, `$0408` for M and W. One data pointer per entry; `CWRIT` copies bytes
verbatim with no shift. ASCII map: `' '`→0, `'!'`→1, `','`→2, `'.'`→4, `'0'-'9'`→6..15, `':'`→16,
`'@'`→18 (blank), `'A'-'Z'`→19..44, everything else → `'?'`.
**Source** `mess0.src:442–486 (CHRTBL), 491–650, 790–804`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Render the full charset; snap all text to even pixel columns.
**Notes** —

---

### L-080 · Message format
**Mechanic** Text
**Claim** A stream of 16-bit words in which the **sign bit** distinguishes a word pointer (≥ `$8000`,
pointing into `$C000+` ROM) from an inline opcode (0–8). Nine layout opcodes plus a shared word
dictionary. Defaults: 1 byte-column character spacing, 10-scanline line spacing.
**Revision** Red Label
**Evidence** `LDU ,X++ / BMI TEXT6`. Opcodes `RSP, RLS, HMT, HMC, VMT, VMC, RTC, RLF, PIC`;
dispatch `INSTBL`. `LDX #$010A / STX CHARSP`. `INITSS`/`HALLD` point at **RAM** addresses so live
data renders through the same path.
**Source** `mess0.src:658–666, 748–749, 774–775, 308–438, 822`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** —
**Notes** Reimplement the opcode stream rather than baking strings — Hall of Fame rows, credit count
and score displays all reuse the renderer via RAM-resident pseudo-words.

---

### L-081 · Three separate logo compression schemes
**Mechanic** Attract mode
**Claim** "WILLIAMS" is a **turtle-graphics command stream**; "DEFENDER" is a **run-length nibble
stream** expanded at run time into a 120 × 24 px block; the copyright line is a separate 5 × 7
**vertical-slice** font. None of the three is a plain bitmap.
**Revision** Red Label
**Evidence** `LOGO`: bytes ≤ `$AA` carry two 4-bit moves each (bit7 col−1, bit6 col+1, bit5 row−1,
bit4 row+1), lighting a pixel after each; `$FE` absolute reposition, `$FD` quit, `$FF` nop.
`DEFNNN`: each nibble = 2-bit color + 2-bit run length, with `$0-$3` nibbles acting as length
prefixes (`LEN = (LEN+n)*4`); expands into 60 byte-columns × 24 scanlines. `COPYRT` reads `CPRTAB`
as 40 byte-columns, each byte a vertical 8-px slice LSB-at-top.
**Source** `amode1.src:740–786, 857–875, 913–974, 1015–1133`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Expand all three; compare against a MAME attract capture.
**Notes** The DEFENDER logo is further animated in as 15 separate 4 × 12 objects via `APVCT` before
being restored as one 60 × 24 block.

---

### L-082 · Object and process table layouts
**Mechanic** Entities / scheduler
**Claim** Object stride **23 bytes**, pool **95**. Process stride **15 bytes**, **75** slots, plus
**5 "super process" slots of 23 bytes**. `MKPROC` inserts the new process **immediately after the
current one with `PTIME = 1`, so it runs later in the SAME dispatch pass**. Pool exhaustion calls
`ERROR`, which hard-resets the machine.
**Revision** Red Label
**Evidence** `OSIZE EQU $17` with `RMB (95)*OSIZE`; `PSIZE EQU 15` with `RMB 15*(75)`;
`SPSIZE EQU PSIZE+8` with `SPTAB RMB SPSIZE*5`. `MKPROC LDA #1 / STA PTIME,U / LDX [CRPROC] /
STU [CRPROC] / STX ,U`; `DISP1 DEC PTIME,U / BNE DISP2`.
**Source** `phr6.src:469–532`; `defa7.src:72–87 (MKPROC), 3119–3128 (DISP)`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Assert `COLCHK`'s `PLEND` executes in the same frame it is created.
**Notes** Same-pass execution is **load-bearing**.

---

### L-083 · Object types
**Mechanic** Entities
**Claim** `OTYP`: 0 = normal/hyperable; 1 = Lander picking up (non-hyperable); 2 (bit 1) = appearing
(non-hyperable); `$10` = humanoid (non-hyperable, **non-smart-bombable**); `$11` = score popup.
**Revision** Red Label
**Evidence** `phr6.src:491–496` comment block, consumed by `SBOMB` (`CMPA #$02 / BHS SBMB2`) and by
the overload culler (`OTYP = 0` only).
**Source** `phr6.src:491–496`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** — **Notes** Drives L-030 and L-052.

---

### L-084 · Anti-tamper trap
**Mechanic** Protection
**Claim** `SBLNK` corrupts base-page RAM at a random address unless `WCURS` holds `$6245`.
**Revision** Red Label
**Evidence** `defa7.src:2159–2199` (`SBLNK`), reached from the normal star/blank path.
**Source** `defa7.src:2159–2199`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** —
**Notes** Historically interesting; a reimplementation may omit it, but must then not be surprised
that a bit-exact RAM trace diverges from a patched ROM.

---

### L-085 · Operator adjustments are not range-clamped
**Mechanic** Configuration
**Claim** `BMPNUP`/`BMPNDN` are pure BCD increment/decrement with **no bounds test**. `NSHIP` is
masked with `ANDA #$F` only at the point of use, so an operator can set 10–15 ships or 0.
**Revision** Red Label
**Evidence** `BMPNUP JSR RCAV / ADDA #$01 / DAA / WCAV`; `BMPNDN ADDA #$99 / DAA` (REPLAY special-
cased to step by 10). `ALTER` gates *which* functions are alterable, not their values.
`defa7.src:1144` `ANDA #$F`.
**Source** `romc8.src:218–253` (`BMPNUP`), `256+` (`ALTER`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Set ships to 16; assert wrap and the low-nibble mask.
**Notes** The manuals' printed ranges are advisory. Reproduce the wrap, do not clamp.

---

### L-086 · Factory high-score table
**Mechanic** Attract mode
**Claim** Eight seeded entries: DRJ 21 270, SAM 18 315, LED 15 920, PGD 14 285, CRB 12 520,
MRS 11 035, SSR 8 265, TMH 6 010.
**Revision** Red Label
**Evidence** `ROMC8.SRC:782–797`: `FCB $02,$12,$70 / FCC 'DRJ'` etc. — 3-byte BCD.
**Source** `romc8.src:782–797` (`DEFALT`)
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** Cold-boot; assert all eight.
**Notes** DRJ = "Dr. J" (Eugene Jarvis); SAM = Sam Dicker.

---

### L-087 · Copyright is 1980
**Mechanic** Attract mode
**Claim** The displayed copyright is **1980**, not 1981. The internal title string is
`D E F E N D E R  1.0` across every revision, so it is **not** a revision indicator.
**Revision** all-Williams
**Evidence** `CRIGHT FCC 'COPYRIGHT 1980 - WILLIAMS ELECTRONICS'`; `AMODE1.SRC:1120` comment
`"COPYRIGHT[C][P] 1980" DATA`; `BLK71.SRC:1` and `PHR6.SRC:1` `TTL 'D E F E N D E R 1.0'`.
**Source** `romc8.src:836`; `amode1.src:1120`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** — **Notes** MAME lists all Defender sets as `GAME( 1980, … )`.

---

### L-088 · Cocktail support
**Mechanic** Cabinet
**Claim** The Red software carries a full second interrupt handler `IRQB` for the flipped screen, a
`$38`/`$39` orientation word written to `$C3FC`, and per-player screen switching. The player mux is
**CB2 of the `$CC04` PIA** (control register `$CC07`).
**Revision** Red Label
**Evidence** `phr6.src:15` `WDATA EQU $38 ;NORMAL SCREEN;($39=FLIPPED)`; `phr6.src:148`
`*B7 INPUT 1=COCKTAIL`; `phr6.src:149` `*CB2 OUTPUT SWITCH MUX CONTROL 1=PLAYER1,0=PLAYER2`;
`defa7.src:1190` `LDA PIA3 ;COCKTAIL? / BPL P1SW0 / LDX #IRQB / LDD #$3934`;
`defa7.src:2006` `*INVERTED IRQ FOR SCREEN FLIP`; `PSW1` writes `$3C`/`$34` to `$CC07`.
**Source** `defa7.src:1190–1235, 2006–2069`; `phr6.src:15, 148–149`
**Confidence** SOURCE_CONFIRMED
**Impl** TBD **Test** —
**Notes** **MAME does not render the flip** (`m_cocktail` is written but read by no renderer) and
wires nothing to the mux — so cocktail behavior is only partially observable through MAME. Its
header comment attributing the mux to `$CC03` is wrong.

---

## Part C — MAME_CONFIRMED

Established from the vendored MAME driver only. These describe hardware behavior as MAME models
it; where the source could speak too, the claim has been promoted into Part A.

---

### L-089 · Red is the only revision with two decoder PROMs
**Claim** `ROM_START(defender)` loads `decoder.2` + `decoder.3` into a `0x0400` region; White, Blue
and Green each load a single `decoder.1` into `0x0200`. Driver header: the second PROM exists "to
allow video inversion for the cocktail table". The PROMs are **not hooked up** in MAME and do not
affect emulated behavior.
**Source** `williams.cpp:29–33, 2004–2006, 2028, 2050, 2073`
**Confidence** MAME_CONFIRMED
**Impl** TBD **Test** — **Notes** Corroborates Red as the final revision independently of any
label-order claim.

---

### L-090 · Sound board RAM
**Claim** Defender's sound board has **no external MC6810 RAM** — only the 6808's 128 bytes of
internal RAM. `defender_sound_map` maps `0x0000–0x007f` only, where the generic Williams sound map
also maps `0x0080–0x00ff`.
**Source** `williams.cpp:676–681, 694`; corroborated by `vsndrm1.src:13` `ENDRAM EQU $7F`
**Confidence** MAME_CONFIRMED
**Impl** TBD **Test** — **Notes** 128 bytes of state total. Assuming 256 will not match the
original's voice-allocation limits.

---

### L-091 · Watchdog and cocktail flip share a register
**Claim** The real register is `$C3FC` and its data bit 0 is the cocktail screen-flip control.
MAME's `$C3FF` watchdog entry is shadowed by the later-installed `$C010–$C01F` mirror (whose mirror
set includes both `$C3FC` and `$C3FF`), and the watchdog is created with **no timeout**.
**Source** `williams.cpp:497–499, 1551`; `williams_m.cpp:247–252, 335–338`; `phr6.src:14–15`
**Confidence** MAME_CONFIRMED
**Impl** TBD **Test** —
**Notes** **Do not implement a firing watchdog.** The game kicks with `$38`; MAME's handler demands
`$39`, so a data-sensitive watchdog would reset an upright machine continuously.

---

### L-092 · MAME's `IN2` bit 6 (TILT) is inert
**Claim** PA6/PA7 of the `$CC00` PIA are **outputs** driving the coin-door LED digit, and the game
masks them off when reading (`LDB PIA0 / ANDB #$3F`). MAME nonetheless declares `IPT_TILT` there.
The real slam switch is on **CA2**, polled via the CRA interrupt flag with a 60-frame debounce, and
MAME wires nothing to it.
**Source** `williams.cpp:783, 1574–1578`; `romf8.src:70–71`; `defa7.src:762–766, 809–816`
**Confidence** MAME_CONFIRMED (with SOURCE_CONFIRMED corroboration)
**Impl** TBD **Test** — **Notes** Do not implement tilt on that bit.

---

### L-093 · MAME driver path
**Claim** The Williams driver lives at `src/mame/williams/`, not `src/mame/midway/`. The project
spec is out of date.
**Source** `williams.h:8` (`MAME_WILLIAMS_WILLIAMS_H`); `williams.cpp:3988`
**Confidence** MAME_CONFIRMED
**Impl** — **Test** — **Notes** Update the spec's file references.

---

### L-094 · The Taito release differs only in three ROMs
**Claim** `defenderj` shares eight code images bit-for-bit with Williams Red. The differences are:
a single 4 K 2532 at `$D000`; a replacement for Williams IC9 (banked `0x0000` = **attract-mode
branding**); and a different sound ROM.
**Source** `williams.cpp:2077–2098`; mwenge `Makefile:92–95`
**Confidence** MAME_CONFIRMED
**Impl** — **Test** — **Notes** Localises "attract mode differences" to IC9.

---

## Part D — MANUAL_CONFIRMED

From operator/service manuals actually fetched and read.

---

### L-095 · No operator manual contains a scoring table
**Claim** All three manuals (16P-3000-103, 16P-3001T-103, 16P-3001-103 R-T) were read end to end.
The only gameplay numerics printed are BONUS SHIP LEVEL 10 000, SHIPS PER GAME 3, and "SMART BOMB
Switch … A maximum of 3\* per play". **No enemy point values appear anywhere.**
**Confidence** MANUAL_CONFIRMED
**Impl** — **Test** — **Notes** The "official published scoring table" the reconstruction wanted to
compare against **does not exist in service documentation** — it was on the cabinet instruction card.
L-026 (source-derived) is therefore the authority.

---

### L-096 · Manual generations disagree on difficulty defaults
**Claim** 16P-3000-103 (early boards) and 16P-3001T-103 (cocktail, Mar 1981) both print STARTING
DIFFICULTY = 0 and PROGRESSIVE WAVE DIFFICULTY LIMIT = 10, and label Function 20 "BACKGROUND SOUND".
16P-3001-103 R-T (July 1981, later boards) prints 5 and 15 and relabels Function 20 "NOT USED" —
matching the Red source exactly.
**Confidence** MANUAL_CONFIRMED
**Impl** — **Test** — **Notes** See L-023. See Q-08 for why the cocktail manual carries the old
values.

---

### L-097 · Credit persistence
**Claim** The later (Red-era) manual alone documents: twenty or fewer credits are retained across
power-down; posting 21+ credits and pressing ADVANCE in AUTO-UP twice clears them; changing Function
28 also clears them.
**Confidence** MANUAL_CONFIRMED
**Impl** TBD **Test** — **Notes** Same functional area as the ROM4C/ROM4D pricing bug and the
`CLR CUNITS` / `CLR BUNITS` patches.

---

### L-098 · Control panel legends
**Claim** Identical in all three manuals: UP-DOWN Switch, REVERSE, THRUST, FIRE, HYPERSPACE, SMART
BOMB. The Switch Test enumerates UP, DOWN, REVERSE, 1-PLAYER START, 2-PLAYER START, HYPERSPACE,
SMART BOMB, THRUST, FIRE.
**Confidence** MANUAL_CONFIRMED (triple-confirmed with L-028 and `phr6.src:139–148`)
**Impl** TBD **Test** — **Notes** **Physical button geometry is still unresolved** — see Q-12.

---

## Part E — INFERRED  ⚠️

> **⚠️ NOTHING IN THIS SECTION IS EVIDENCED ORIGINAL BEHAVIOR.**
> These are reasoned derivations from confirmed facts. They are almost certainly right, but each is
> a *calculation*, not a *reading*, and each names the measurement that would confirm it. Do not
> quote any of these as "Defender does X" without the qualifier.

---

### L-100 · Player top speed is 6 px/tick, not 8 — **PROMOTED 2026-08-09, no longer INFERRED**
> **This entry is no longer INFERRED and no longer belongs in Part E.** It is retained here so that
> anyone following an old cross-reference lands on the promotion rather than on the superseded
> label. **Confidence: MULTI_SOURCE_CONFIRMED.** Full derivation: `MOVEMENT_ENVELOPE.md` (Q-04
> closure); re-check commands in `tools/claims.tsv`, run with `tools/rom_peek.py`.

**Claim** Thrust equilibrium is `3 = V16/64` → **V16 = 192 = `$C0` = 6.0 px/tick = 360.58 px/s**,
which is **less** than the hard clamp of `$0100` = 256 = 8 px/tick. **The `±$0100` clamp is
unreachable from every reachable state — it is dead defensive code.**
**Basis** Arithmetic on the SOURCE_CONFIRMED rates in L-041, plus the SOURCE_CONFIRMED clamp
`CMPD #$0100 / BLT PV11 / LDD #$0100` (`defa7.src:2421–2428`).
**Evidence (added at promotion)** `PLAXV` has exactly **five** writers, established by a `grep` of
the source *and* by an opcode scan of all eleven main-CPU ROM images: `defa7.src:1280–1281`
(`PLSTR0` spawn, zeroes all 24 bits), `2355`/`2358` (drag), `2368`/`2371` (thrust), `2428` (the
clamp write-back itself), `3244–3245` (`HYPER`, zeroes all 24 bits). `defb6.src:64, 510, 551` are
reads. **There is no third accelerator.** An exact bit-level simulation of the 24-bit accumulator
(drag `−4·V16` added at the LSB position with `ITEMP` sign extension, then `±$0300` thrust, then the
`PV12` clamp) gives max `|V16| = 192` in both directions and never 193 — at `V16 = 192` the per-tick
delta is `768 − 4·192 = 0` **exactly**, so `N` cannot climb the further 256 counts needed.
Left thrust (`PLADIR = $FD00`) is symmetric at `−192`.
**Confidence** MULTI_SOURCE_CONFIRMED
**Test** Watch `$A0C7` in MAME across a long session including reverses, hyperspace re-entries and
deaths; assert max `|V16| = 192` and that the clamp branch is never taken. This is now a
**regression assertion**, not the closure evidence.
**Notes** A reimplementation that clamps at 256 and picks any larger acceleration produces a
measurably faster ship. Time to 50 % of terminal = 44 ticks, 63 % = 63 ticks ≈ 1.05 s, 90 % = 146
ticks, 99 % = 307 ticks; release from 192 halves in 44 ticks. **§19.4** (what the `PLAXV+2` fraction
byte holds when the clamp fires) is therefore **moot in normal play** — the clamp never fires.

---

### L-101 · Forward lean magnitude
**Claim** Screen offset = `V16 / 8` byte-columns. At thrust terminal (`V16 = 192`) that is **24
byte-columns = 48 pixels**, so the forward view shrinks from 240 px to 192 px facing right.
**Basis** Arithmetic on `PLAY1`'s shift chain (`defa7.src:2373–2382`), which yields
`PCX = V16 × 32` in 1/256-column units.
**Confidence** INFERRED (depends on L-100 for the terminal figure)
**Would be settled by** VIDEO_MEASURED: capture the ship's screen column at rest and at sustained
thrust.

---

### L-102 · Derived velocity units
**Claim** `OX16` uses **32 units = 1 pixel** but `OYV`/`OY16` are 8.8, i.e. **256 units = 1 pixel**.
So `LNDXV = $16` is 0.69 px/frame while `LNDYV = $0070` is 0.44 px/frame.
**Basis** `UFONV1` compares against `#20*32` and `#40*32` as a pixel window; `OPROC` uses
`CMPD #150*64`; `GETALT` shifts `OX16` right 6 to index a 1024-entry table. `LDA OY16,X` yields the
pixel row directly.
**Confidence** INFERRED (the unit *identification* is SOURCE_CONFIRMED; the per-constant px/frame
conversions are arithmetic)
**Would be settled by** Frame-step a Lander in MAME and measure px/frame in both axes.
**Notes** Using one unit for both makes every enemy move at the wrong aspect.

---

### L-103 · Baiter first-appearance timing
**Claim** At wave 1 (`UFOTIM = 192`, tick 0.2496 s) the first Baiter appears at **47.9 s**, cut to
24.2 s once ≤ 8 enemies remain and 12.2 s at ≤ 3; respawn is `RMAX(UFOTIM/4)` below 4 remaining.
**Basis** Arithmetic on L-066 and L-032.
**Confidence** INFERRED
**Would be settled by** VIDEO_MEASURED: time the first Baiter on a fresh wave 1 with no kills.

---

### L-104 · Humanoid lethal fall distance
**Claim** From rest, the lethal threshold `OYV > $E0` corresponds to roughly **51 pixels of fall
(116 frames)**.
**Basis** Integrating L-070's `+8 per 4 frames`.
**Confidence** INFERRED — **the ROM constant is a velocity, not a distance.**
**Would be settled by** Shoot Landers at measured altitudes in MAME; bisect the threshold.

---

### L-105 · Pixel aspect ratio for presentation
**Claim** Presenting the 292 × 240 framebuffer at 4:3 requires a horizontal stretch of exactly
`320/292` = **1.09589:1**.
**Basis** `(4/3) / (292/240) = 960/876 = 320/292`. MAME never calls `set_physical_aspect` for this
screen, so it records no aspect of its own.
**Confidence** INFERRED
**Would be settled by** Read `src/emu/screen.cpp` for the default `m_phys_aspect` when
`set_physical_aspect` is never called; cross-check with a VIDEO_MEASURED capture of a real cabinet
measuring an object known to be square in framebuffer terms.
**Notes** Square-pixel presentation makes the image ~9.6 % too narrow. This affects every judgment
about sprite proportions and how wide the playfield reads.

---

### L-106 · RNG period figures
**Claim** The LFSR (`HSEED:LSEED`, taps bit0^bit3, `x^16+x^13+1`, **non-primitive**) has a measured
period of **57 337**, not 65 535. The full 24-bit state (`SEED:HSEED:LSEED`) has a 501-call tail
then a 114 674-call cycle. Hyperspace death probability over the true cycle is **24.61 %**.
**Basis** SOURCE_CONFIRMED code (`RAND`, `defa7.src:943–962`) plus simulation of that code. The
polynomial and the seeding (`LDD #$A55A / STD HSEED`, `SEED` left 0 by the RAM clear) are
SOURCE_CONFIRMED; the *period figures* are computed.
**Confidence** INFERRED (arithmetic/simulation on confirmed code)
**Would be settled by** Re-run the enumeration independently; assert the same cycle lengths.
**Notes** **The whole game is deterministic from cold boot** — there is no entropy source. `RAND` is
called unconditionally once per `EXEC` pass, and several consumers read `SEED`/`HSEED`/`LSEED`
directly rather than calling `RAND`, so both the state **and the exact call ordering** must be
reproduced to match a trace.

---

### L-107 · Coin-pricing fix level — **CLOSED**
**Claim** MAME's canonical `defend.4` (`9a72348b`) **is ROM4E**, the corrected Red IC4 — not one of
the defective ROM4C / ROM4D revisions. The recovered `defa7.src` is Red-era at this site.
**Evidence** The test proposed in the earlier version of this entry was run. In the extracted Red
set, `defend.4` contains **both** patch sites:

| Site | File offset | CPU addr | Bytes | Decode |
|---|---|---|---|---|
| `START CLR CUNITS ;PATCH ONE` | `0x0061` | `$D861` | `0F 38 12` | `CLR <$38` ; `NOP` |
| `CLR BUNITS` (“ALSO PATCH 2”) | `0x0098` | `$D898` | `0F 39 12` | `CLR <$39` ; `NOP` |

Neither byte sequence occurs anywhere in the White, Blue or Green sets. Those revisions use a
different code shape entirely at the game-start block — `green/defeng04.bin @ 0x005F`,
`blue/wb01.bin @ 0x085F`, `white/rom1.bin @ 0x0953` all read
`8E C4 85 / BD F8 xx / 84 0F / B7 A1 xx / B7 A1 xx …` (two separate `STA`s, no patch `NOP`s), where
Red reads `8E C4 85 / BD F8 22 / 84 0F / B7 A1 C9 / C6 0A / FD A1 CB / 0F 39 12`
(`LDB #10 / STD P1SBC`, matching `defa7.src:1142–1149`).
`INFO.SRC:19–21` states ROM4C/ROM4D are "INCORRECT DUE TO THE MULTIPLE COIN PRICING ERROR" and that
ROM1C/ROM4E is the corrected Red IC4; since `defend.4` carries the fix it cannot be 4C or 4D, and
ROM4E is the only other Red IC4 Williams names.
**Source** `defa7.src:1124, 1142–1149`; `historicalsource-defender/INFO.SRC:19–22`
**Secondary** Red/Green/Blue/White ROM sets extracted from `mwenge-defender` git history,
CRC32-verified against `williams.cpp:1985–2075`
**Confidence** MULTI_SOURCE_CONFIRMED
**Notes** The absence of both patches from White/Blue/Green is consistent with `INFO.SRC:22`'s
separate `ROM4F IS THE GREEN SOFTWARE PRICING FIX` — a corrective ROM MAME does not hold, so MAME's
`defenderg` set is the *pre-fix* Green. Closes Q-20.

---

### L-108 · PIA1 bits 6–7 drive exactly two board LEDs
**Claim** PB6/PB7 of `$CC02` are diagnostic LED outputs and nothing else.
**Basis** `phr6.src:132–135` comment `*B6-B7 LEDS`; `SNDOUT`'s `;DONT TURN ON LEDS`; MAME forcing
`data | 0xC0`. All three are consistent but none is a hardware document.
**Confidence** INFERRED
**Would be settled by** MANUAL_CONFIRMED is achievable: read the Defender operator/service manual or
the later-PCB drawing set for the PIA output wiring.
**Notes** The whole audio interface contract (L-013) rests on the command being 6 bits with 2
non-sound lines above it.

---

## Part F — UNRESOLVED  ⚠️

> **⚠️ THESE ARE NOT KNOWN.** Each entry states exactly what would settle it. Full ranking by
> implementation impact is in `OPEN_QUESTIONS.md`.

---

### L-120 · The `LCOL` patch space — **CLOSED, moved out of the unknowns**
> This entry was in the "not known" section. It is now settled; it is kept here with its answer so
> the question is not reopened. **Confidence: MULTI_SOURCE_CONFIRMED.**

**Claim** The laser collision box is **8 byte-columns × 1 row = 16 × 1 px in every shipped Williams
revision**, and the earlier framing of this question ("16 px or 6 px hitbox") was doubly wrong.

**Evidence, part 1 — `DECB` cannot change the width.** `LCOL` is entered with
`LDD PD,U / SUBA #$06 / BSR LCOL` (`defa7.src:2826–2828`); `D` is a framebuffer address and the
Williams layout is `addr = column·256 + row`, so **`A` = byte-column and `B` = row**. `COLIDE` takes
the box extent from the picture descriptor, not from `D`: `COL0 STD ULX / ADDD OBJW,U / STD LRX`
(`defa7.src:2908–2910`), `OBJW EQU 0` / `OBJH EQU 1` (`phr6.src:561–562`), `LASP1 FCB 8,1`
(`defb6.src:1940`). `ADDD OBJW,U` adds `$0801` — +8 columns, +1 row. A `DECB` in the patch space
shifts the box **up one row**; the width is fixed by `LASP1` and can only change by editing it. The
author's "3 WIDE" meant a 3-row-thick beam, not a 3-column-wide box. `SOURCE_CONFIRMED`.

**Evidence, part 2 — the patch was never applied, and the space only exists in Red.** Searching all
four extracted ROM sets for the `LCOL` prologue `34 46 86 02 97 36 B7 D0 00 35 06`:

| Set | Site | Following bytes |
|---|---|---|
| Red | `defend.2 @ 0x05AB` | `12 12 12 CE F9 6F` — three `NOP`s, **patch not applied** |
| Green | `defeng02.bin @ 0x051D` | `CE F9 8F` — **no patch space** |
| Blue | `defeng02.bin @ 0x051D` | `CE F9 8F` — **no patch space** |
| White | `rom2.bin @ 0x05D5` | `CE F9 62` — **no patch space** |

`LASP1` reads `08 01` at every one of those `LDU` targets (Red `$F96F`, Green/Blue `$F98F`,
White `$F962`).

**Source** `defa7.src:2778–2788, 2826–2828, 2908–2910`; `defb6.src:1940`; `phr6.src:561–562`
**Secondary** all four Williams ROM sets, extracted from `mwenge-defender` git history and
CRC32-verified against `williams.cpp:1985–2075`
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** Hitbox = 16 × 1 px, offset 6 columns behind the head when firing right, at the head when
firing left.
**Test** Assert the laser AABB is 16 px wide and 1 px tall in all cases.

### L-121 · The actual terrain altitude profile
`ALTBL` (`$B300`, 1024 bytes, generated by `BGALT`) drives Lander cruise altitude, humanoid walking
altitude, the landing/splat test and terrain-destruction explosion placement. The generated profile
has not been produced. **Settled by** running `BGALT` over `TDATA` in a simulator, or dumping
`$B300–$B6FF` from MAME at the start of a wave and comparing.

### L-122 · `MTERR`'s provenance
The 64-entry mini-terrain silhouette the scanner draws is not derived from `TDATA` by any code in the
recovered source, so **the scanner's terrain does not match the terrain the player flies over**.
Whether this is intentional or an artifact of a lost tool is unknown. **Settled by** rendering both
`TDATA`-derived altitudes and `MTERR` side by side and documenting the mismatch; optionally checking
whether `MTERR` matches `TDATA` under a lossy transform.

### L-123 · Does `GTARG` overrun `TLIST`?
`GTARG` scans 32 `TLIST` slots but `TLIST` is declared `RMB 40` (20 slots) in **both** source trees.
The 12 extra slots overlap `FISTAB` (the laser exhaust table), whose bytes are non-zero. If a bogus
target can latch, a Lander could write `OYV`/`OCVECT` into arbitrary RAM. **Settled by** a MAME
breakpoint at `GT3` logging any returned `D` outside `OLIST..OLEND` over a long session; if it
fires, single-step `LANDS0` to see whether the `OCVECT+1` comparison can accidentally match
`ASTKIL`'s low byte.

### L-124 · Is the carried-humanoid count really unbounded?
No counter or cap exists in `AKIL1` or `AFALL2`. If several can be carried simultaneously, each pays
500 on deposit — an exploitable scoring behavior a faithful port must reproduce. **Settled by**
catching 4–5 falling humanoids in one pass in MAME and confirming each pays twice; watch the 75-slot
process table for `ERROR`.

### L-125 · Two-player alternation and global VFX state
Each player has independent `PENEMY`/`PTARG`/`PWAV` and `PLSAV`/`PLRES` swap the whole world, but the
16 explosion slots and the shell list are **global**. A leaked appear could hold a slot or restore a
picture into a reused object record. **Settled by** forcing a player-1 death with explosions and
appears in flight during a two-player game and watching `$9C00–$9FFF` and `SPTR` across the
transition.

### L-126 · How often the overload culler fires in real play
L-030 is confirmed to exist; its **frequency** is unknown, and it silently removes live enemies.
**Settled by** instrumenting `OVCNT` and the `EXEC03` path in MAME across a full wave 5 and wave 10,
counting culls per minute.

### L-127 · Does the drone really start only on first thrust release?
The background drone (`BG1`, an FNOISE random walk) is started by command `$0F`, sent from exactly
one place: the thrust-**release** path in `SNDSEQ` (`defa7.src:723–756`). If correct, the board idles
silently until the player thrusts once. **Settled by** a MAME watchpoint on writes to `$CC02` logged
from coin-up through 30 s of wave 1 with no thrust input; assert whether byte `$30` ever appears.

### L-128 · Which MAME release the vendored driver is from
Every MAME_CONFIRMED claim in the project is only as pinned as the driver version, and at least one
(L-091, the `$C3FF` shadowing) is an install-order artifact that could differ between releases.
**Settled by** cloning MAME and searching history for the commit whose `williams.cpp` matches the
on-disk file's hash.

### L-129 · The analogue output stage after the DAC
Reconstruction filter corner, op-amp gain and volume-pot range are undocumented. They set the
perceived brightness of LITE, APPEAR, RADIO and the top of every GWAVE frequency pattern, and
determine how much aliasing from the 30-cycle FNOISE slew clock survives to the speaker. **Settled
by** the Defender operator/service manual sound-board schematic (assembly A-5342-xxxxx), or a
simultaneous scope/spectrum capture of DAC output and speaker output on a real board.

### L-130 · Relative loudness balance across the nine sound engines
Peak-to-peak DAC swings differ hugely by engine (255 for VARI/LITE/FNOISE/HYPER, 240 for SCREAM,
127 for RADIO, 80–112 for the organ). Whether that reads as balanced depends on L-129 and the single
cabinet volume control. **Settled by** a level-matched recording of a known-good cabinet playing the
operator audio test (`$01`–`$1F`), compared against the emulated DAC stream.

### L-131 · Was `RADIO` (`$18`) ever wired up?
The `LGSND` table is fully formed with a sensible priority and duration, labeled "LANDER GRAB", and
appears **exactly once in the whole source — at its own definition**. RADIO is a distinctive
28-second rising sweep no player ever hears. **Settled by** searching each Defender main-ROM image
for the byte pattern `C0 01 20 18 00` and checking for a reference to its address.

### L-132 · Where `historicalsource` obtained the Williams source
Single commit, no README, no provenance note. Without chain of custody, "the recovered Williams
source" rests entirely on internal evidence (`INFO.SRC`'s manifest and dates). **Settled by**
archival research — Internet Archive / Jason Scott source-release announcements. Not resolvable from
code.

### L-133 · The unattributed head of `unknown.bin`
The first `0x40` bytes of `src/unknown.bin` (CPU `$CC50–$CC8F` in bank 2) sit between the end of
`mess0.src`'s output and the start of `defb6.src`'s `$CC90` graphics block. One researcher reports
them as `mess0`'s tail; this was not independently verified. **Settled by** disassembling
`$CC50–$CC8F` from `defend.11` and searching the source tree for any reference into that range.

### L-134 · Part-number discrepancy in the mwenge README
`README.md:229–234` gives IC8 and IC9 the same part number `A5343-09642` and is off by one from IC9
onward relative to the Williams Red Label ROM chart (`orig/RedLabelROMChart.png`, which reads
IC1 09636 … IC12 09646). **The chart is authoritative; the README is not citable for board
documentation.** Recorded here so the error is not propagated.

---

## Part G — Added or reversed in the 2026-08-09 Phase 1 defect-closure pass

Three adversarial critics reviewed the Phase 1 corpus. Every P0 and P1 they raised was taken back to
primary evidence. The entries below record what changed. Where a finding overturned an earlier
adjudication, the earlier ruling is named explicitly so it cannot be cited by accident.

**Enabling fact for most of this section:** all four Williams ROM sets — White, Blue, Green and Red —
are recoverable from `_defender-reference/mwenge-defender/.git`. They were deleted from the working
tree but survive in history. They have since been extracted to `_defender-reference/roms/` (see
`ROM_IMAGE_PROVENANCE.md`); that copy was compared file-by-file against the independent extraction
used for the entries below and all **48 ROM files** are byte-identical. (A 49th extracted path,
`defender-bluelabel/.defeng02.bin.swp`, is a committed vim swap file, not a ROM — see
`ROM_IMAGE_PROVENANCE.md`.) The recovery commands:

```
red    : f77226b66abbe4a43bd8884062bd4477f4bd7cfa^:orig/defender-redlabel/
green  : f10dad86a69528f5063a961fbbea791a3e6def17^:orig/defender-greenlabel/
blue   : f10dad86a69528f5063a961fbbea791a3e6def17^:orig/defender-bluelabel/
white  : f10dad86a69528f5063a961fbbea791a3e6def17^:orig/defender-whitelabel/
```

Every CRC32 was checked against `williams.cpp:1985–2075` and all four sets match exactly
(Red `defend.1` `c3e52d7e` … `defend.snd` `fefd5b48`; Green `defeng01.bin` `6111d74d` …;
Blue `wb01.bin` `0ee1019d` …; White `rom1.bin` `5af871e3` …). Any document that says ROM dumps are
unavailable is stale — see L-140.

---

### L-135 · The `amode1.src` sound constants date the recovered source as pre-final-Red
**Mechanic** Provenance / diagnostics
**Claim** The four hard-coded substitutions mwenge made in `amode1.src` (`#$FE`→`#$3E`,
`#$FD`→`#$3D`, `#$FF`→`#$3F`, `#$E4`→`#$24`) are **a real Williams source revision between Green and
Red**, and they are **not** behaviorally identical.
**Evidence, part 1 — not behaviorally identical.** `$CC02` is PIA1, whose port B the source
documents as `*B0-B5 SOUND` / `*B6-B7 LEDS` (`phr6.src:132–134`); MAME's driver header describes the
same two bits, with CA2 and CB2, as "4 bits to drive the LED 7 segment" (`williams.cpp:356–364`).
`SNDOUT` masks with `ANDB #$3F ;DONT TURN ON LEDS` (`defa7.src:702`) — Williams' own comment
establishes that a **set** bit 6 or 7 drives the LED. The hall-of-fame code in
`AMODE1.SRC:147–163` bypasses `SNDOUT` and writes the byte straight to `$CC02` via `STBXBV`, so
`$FF`/`$E4` drive those two LED lines where `$3F`/`$24` do not. The sound board cannot tell the
difference (`COMA / ANDA #$1F`, `VSNDRM1.SRC:912–913`), but the board's LED output can.
**Evidence, part 2 — the direction is settled by the dumps.** Searching all four sets for the
hall-of-fame sound sequence:

| Set | Site | Bytes |
|---|---|---|
| Red | `defend.9` | `[0x57] 86 3E`, `[0x61] 86 3D`, `[0x63] 8E CC 02`, `[0x66] C6 3F`, `[0x6B] C6 24` |
| Green | `defeng09.bin` | `[0x44] 86 FE`, `[0x4E] 86 FD`, `[0x50] 8E CC 02`, `[0x53] C6 FF`, `[0x58] C6 E4` |
| Blue | `defeng09.bin` | `[0x44] 86 FE`, `[0x4E] 86 FD`, `[0x50] 8E CC 02`, `[0x53] C6 FF`, `[0x58] C6 E4` |
| White | `rom9.bin` | `[0x42] 86 FE`, `[0x4C] 86 FD`, `[0x4E] 8E CC 02`, `[0x51] C6 FF`, `[0x56] C6 E4` |

White, Blue and Green all carry exactly the values in the recovered
`historicalsource-defender/AMODE1.SRC:147–163`. Only Red carries the masked values. **The recovered
`AMODE1.SRC` text therefore predates the final Red assembly at this site.**
**Scope limit.** This is *not* a corpus-wide claim. The same tree is unambiguously Red-era elsewhere:
`defend.4 @ 0x008E` matches `defa7.src:1142–1149` including both coin-patch `NOP`s (L-107), a shape
absent from White/Blue/Green. The recovered corpus is a **late pre-final-Red working snapshot**.
**Source** `phr6.src:132–134`; `defa7.src:702`; `historicalsource-defender/AMODE1.SRC:147–163`
**Secondary** All four ROM sets (see section preamble)
**Confidence** MULTI_SOURCE_CONFIRMED
**Notes** Overturns `ROM_REVISION_MATRIX.md:371`'s "Behaviorally identical … MULTI_SOURCE_CONFIRMED"
and resolves the direction that `BUILD_REPRODUCTION.md:561–566` declined to resolve.

---

### L-136 · Factory difficulty defaults across all four revisions, from the dumps
**Mechanic** Operator settings / revision matrix
**Claim** The `DEFALT` CMOS block is decodable in every revision. **Red ships GA1 = `$05`,
GA2 = `$15`. White, Blue and Green all ship GA1 = `$00`, GA2 = `$10`.** Every other adjustable
default is identical across all four.
**Evidence** `DEFALT` is located by the byte pattern `02 12 70` + `"DRJ"` (`romc8.src:782–783`).
Decoding the 23 adjustable bytes that follow the eight high-score entries (`romc8.src:799–821`):

| | White (`rom10.bin @ 0x6C9`) | Blue (`defeng10.bin @ 0x6A3`) | Green (`defeng10.bin @ 0x6A3`) | Red (`defend.10 @ 0x6CF`) |
|---|---|---|---|---|
| `REPLAY` | `01 00` | `01 00` | `01 00` | `01 00` |
| `NSHIP` | `03` | `03` | `03` | `03` |
| `CSELCT` | `03` | `03` | `03` | `03` |
| **`GA1` initial difficulty** | **`00`** | **`00`** | **`00`** | **`05`** |
| **`GA2` difficulty ceiling** | **`10`** | **`10`** | **`10`** | **`15`** |
| `GA3` | `01` | `01` | `01` | `01` |
| `GA4` astronaut restore | `05` | `05` | `05` | `05` |
| `GA5`–`GA10` | all `00` | all `00` | all `00` | all `00` |

This independently corroborates the operator-manual split recorded in `ROM_REVISION_MATRIX.md` §4:
the early manual (16P-3000-103) prints 0 / 10, the July 1981 manual (16P-3001-103 R-T) prints 5 / 15,
and the dumps show the change happened at the Green→Red boundary, not earlier.
**Source** `romc8.src:782–821` (`DEFALT`), `romc8.src:811–812` (`GA1`, `GA2`)
**Secondary** All four ROM sets; both operator manuals
**Confidence** MULTI_SOURCE_CONFIRMED
**Notes** Raises the White/Blue/Green "Starting difficulty" and "Progressive difficulty" cells in
`ROM_REVISION_MATRIX.md` §2 from MANUAL_CONFIRMED to MULTI_SOURCE_CONFIRMED.

---

### L-137 · The factory high-score table is a hard revision fingerprint
**Mechanic** Attract mode / revision matrix
**Claim** The eight-entry factory high-score table differs across revisions and is the cheapest
available revision test.

| Rank | White | Blue / Green | Red |
|---|---|---|---|
| 1 | DRJ 20,000 | DRJ 21,270 | DRJ 21,270 |
| 2 | SCD 18,000 | SAM 18,315 | SAM 18,315 |
| 3 | LED 16,000 | LED 15,920 | LED 15,920 |
| 4 | PGD 14,000 | PGD **14,185** | PGD **14,285** |
| 5 | CRB 12,000 | CRB 12,520 | CRB 12,520 |
| 6 | MRS 10,000 | MRS 11,035 | MRS 11,035 |
| 7 | KJF 8,000 | SSR 8,265 | SSR 8,265 |
| 8 | TMH 6,000 | TMH 6,010 | TMH 6,010 |

White carries different initials (SCD, KJF) and round 2,000-point steps. Blue and Green are
identical to each other. Red differs from Blue/Green in exactly one BCD digit: PGD `01 42 85` vs
`01 41 85`. The recovered source (`romc8.src:788`, `FCB $01,$42,$85 ;THSTD3`, initials `"PGD"` on
`:789`) carries the **Red** value.
**Source** `romc8.src:782–797`
**Secondary** All four ROM sets
**Confidence** MULTI_SOURCE_CONFIRMED
**Notes** Also confirms Blue and Green share IC10 verbatim, consistent with L-138.

---

### L-138 · Blue precedes Green — settled from Williams' own document
**Mechanic** Revision ordering
**Claim** Release order is **White → Blue → Green → Red**.
**Evidence** `historicalsource-defender/INFO.SRC:15–18`: "ROM1 THRU ROM12 ARE THE WHITE SOFTWARE
(1ST RELEASE WITHOUT CHECKSUMS)" / "ROM1A THRU ROM12A ARE THE BLUE SOFTWARE (1ST RELEASE WITH
CHECKSUMS)" / "ROM1B, ROM2A, ROM3B, ROM4B, AND ROM6A THRU ROM12A ARE THE GREEN SOFTWARE (2ND
RELEASE …)". Green's own parts list cites Blue-suffixed parts (`ROM2A`, `ROM6A`–`ROM12A`); Blue's
list cites none of Green's — so Green is derived from Blue, not the reverse. Corroborated by the
dumps: Green `defeng02/06/07/08/09/10/11/12` are bit-identical to Blue's (CRC32 `d184ab6b`,
`3af34c05`, `545c3326`, `9a9eb3d2`, `f57caa62`, `941cf34e`, `5ca4e860`, `33db686f`).
**Source** `historicalsource-defender/INFO.SRC:15–18`
**Secondary** CRC32 sharing across the Blue and Green sets
**Confidence** MULTI_SOURCE_CONFIRMED
**Notes** Closes the `UNRESOLVED` at `MAME_HARDWARE_CROSSWALK.md:895–906`, which scoped itself to
MAME plus the mwenge README and never read `INFO.SRC`. MAME's driver comment "then green/blue"
(`williams.cpp:32`) is loose phrasing in a comment, not a claim about ordering; `INFO.SRC` is a
Williams primary document and wins.

---

### L-139 · The 6-bit / 5-bit sound command bus, stated once, correctly
**Mechanic** Sound interface
**Claim** The game transmits a **6-bit inverted** field on PB0–PB5 of `$CC02`. The sound board
decodes **5** bits. There are **31 real command codes plus idle — 32 decodable values, not 64.**
Commands are **active-low**: transmitted byte = `$3F − C`.
**Evidence** Transmit: `SNDOUT LDA #$3F / STA SOUND / COMB / ANDB #$3F ;DONT TURN ON LEDS /
STB SOUND` (`defa7.src:696–704`). Decode: `IRQ LDS #ENDRAM / LDAA SOUND+2 / CLI / COMA INVERT INPUT /
ANDA #$1F MASK GARB` (`VSNDRM1.SRC:909–913`) — PB5 is a decoder don't-care. Dispatch bounds
(`VSNDRM1.SRC:934–953`): `TSTA/BEQ IRQ3` (code 0 = idle), `DECA`, `CMPA #$C/BHI` → 13 `GWAVE` codes
`$01–$0D`; `CMPA #$1B/BHI` + `SUBA #$D` → 15 jump-table codes `$0E–$1C`; `SUBA #$1C` → 3 `VARI`
codes `$1D–$1F`. 13 + 15 + 3 = **31**. MAME agrees at the wire level:
`portb_w(param); cb1_w((param == 0xff) ? 0 : 1)` with `data | 0xc0` (`williams_m.cpp:162–175`).
The `$3F`-then-command sequence exists to produce the single low→high CB1 edge the sound board's
`CRB = $37` is armed for.
**Source** `defa7.src:696–704`; `historicalsource-williams-soundroms/VSNDRM1.SRC:909–913, 934–953`
**Secondary** `williams_m.cpp:162–175`
**Confidence** MULTI_SOURCE_CONFIRMED
**Notes** Amends X5, which adjudicated "6 bits vs 5 bits" as a layering question and did not notice
that `MAME_HARDWARE_CROSSWALK.md:780–783` had additionally published a wrong **code count** ("64
distinct sound command codes"). That sentence is corrected. `AUDIO_FORENSICS.md` A-2/A-3,
`SOURCE_MAP.md` §Sound and `SOUND_EVENT_MATRIX.md` were already correct.

---

### L-140 · The ROM dumps were always available
**Mechanic** Methodology
**Claim** Statements anywhere in the corpus that "no ROM dumps are present in the workspace" are
**false and superseded**. All four Williams sets are in `mwenge-defender` git history (see this
section's preamble), and `EVIDENCE_LEDGER` L-001, `SOURCE_MAP.md:51–62`,
`BUILD_REPRODUCTION.md:186–197` and `OPEN_QUESTIONS.md:34` all used them.
**Confidence** MULTI_SOURCE_CONFIRMED
**Notes** `ROM_REVISION_MATRIX.md` originally left the White/Blue/Green rules, scoring and
attract-mode cells `UNRESOLVED` on this false premise, and
`MAME_HARDWARE_CROSSWALK.md:889–892` carried a matching caveat. Both are corrected. Any future
`UNRESOLVED` that rests on "the evidence is not available" must name the search that was actually
run.

---

## Part H — Q-01 closure: the terrain altitude profile (2026-08-09)

`ALTBL` was generated for the first time. Method, both paths run independently and cross-checked:
`site-v2/defender/tools/gen_altbl.py` (`--check` reproduces every number below). Narrative in
`TERRAIN_PROFILE.md`.

---

### L-141 · `ALTBL` — the 1,024-entry terrain altitude table
**Mechanic** Terrain
**Claim** `BGALT` (`$C262`, `defend.6` bank 7) walks the 2,048 bits of `TDATA` (`$C350..$C44F`)
MSB-first from a seed of `$E0`, moving −1 per set bit and +1 per clear bit, storing the running
value every second bit into `$B300..$B6FF`. The resulting 1,024 bytes have
**sha1 `799b45320dfc169fcc2eb65a3cc152b9487b915c`**, range **rows 160–232**, mean 214.8, and are
**all even**.
**Confidence** MULTI_SOURCE_CONFIRMED
**Evidence** Path A: `TDATA` parsed from `blk71.src:510` and `BLK71.SRC:509` (identical), `BGALT`
re-implemented from `blk71.src:375–400`. Path B: `TDATA` read from `defend.6` offset `0x350` and the
ROM opcodes at `$C262` (`BGALT`) and `$C2D0` (`RFONR1`) **executed** on a 24-opcode 6809 interpreter
for 30,731 instructions. `TDATA` sha1 `8e79db98772f0f546bf322233b17ed97699ad36a` on all three
sources. The two tables are byte-identical, so the trust rule was never engaged.
**Notes** Table boundaries are self-proving from the bank's own vector block: `MTERR = $C450` is
exactly `TLEN` past `TDATA = $C350`, and `BGALT`'s terminator is `CMPX #$B700` = `$B300 + 4*TLEN`.
**Cross-revision:** `TDATA` is byte-identical in White, Blue, Green and Red (same sha1; at offset
`0x34C` in the first three, `0x350` in Red), as is `MTERR`. `BGALT` differs by exactly three bytes —
the operands of `LDX #TDATA` and the two `JSR RFONR1` — each shifted by 4. Seed, destination and
terminator unchanged. **`ALTBL` is identical in every shipped revision of Defender.**

---

### L-142 · World width is 2,048 pixels; 32 X-units per pixel; the wrap is the 16-bit range
**Mechanic** Coordinate system
**Claim** World X (`OX16`, `BGL`) is 16-bit at **32 units per screen pixel**, so the planet is
**2,048 pixels** — 6.74 screens of 304 — and wraps by 16-bit overflow with no explicit modulus.
**Confidence** MULTI_SOURCE_CONFIRMED
**Evidence** (a) `BGOUT` scrolls one pixel column per `$20` of `BGL`; (b) `OPON` converts
`(OX16−BGL)` to a screen byte column by ×4-then-high-byte, i.e. ÷64 = ÷2 pixels; (c) `ISCAN`
`SUBD #100*32 ;100 PIXEL LEFT BUFFER` states the scale in the source; (d) scanner `MT1`
`SUBD #$8000-(150*32)` uses `$8000` as *half the world*; (e) `GETALT` indexes `ALTBL` with
`OX16 >> 6`, mapping the whole 16-bit range onto exactly 0..1023 with no masking; (f) `TDATA` holds
exactly 2,048 bits at one bit per pixel column.
**Notes** Wrap modulus confirmed against table length: 1,024 entries × 2 px = 2,048 px = 65,536
units. `ALTBL[i]` is the surface row at world pixel `2i` — the left pixel of each 2-pixel cell.

---

### L-143 · The terrain walk closes exactly, and `ALTBL` is the same curve the renderer draws
**Mechanic** Terrain
**Claim** `TDATA` contains **exactly 1,024 set and 1,024 clear bits**, so the ±1 walk returns to
`$E0` after one lap and the terrain has **no seam at the world wrap**. The rendered terrain is the
same recurrence: `ADDL01`/`ADDR01` store `min(alt(n), alt(n+1))`, never more than **1 px** from the
`ALTBL` sample.
**Confidence** MULTI_SOURCE_CONFIRMED
**Evidence** Bit census over the ROM `TDATA`; the ROM-executed `BGALT` ends with `ROFF = $E0`.
Rendered-vs-table equality simulated over 400 columns from `ADDL01`'s bookkeeping
(`blk71.src:237–304`).
**Notes** Because the seed is even and each entry is two steps apart, **every `ALTBL` value is
even**. Odd rows exist on screen (odd world-x) but are invisible to gameplay logic.

---

### L-144 · `GETALT` is the only consumer, and the player is not one of them
**Mechanic** Terrain / collision
**Claim** `GETALT` (`$ED59`, `defb6.src:364`) is the **sole reader of `$B300`** in the main ROM.
Its callers are: humanoid walk (`ALT+4` left / `ALT+15` right, clamped `≤ $E8`), humanoid fall
(`ground when ALT ≤ OY16`; fatal above `OYV = $E0`), humanoid drop from the ship (`ALT < OY16`),
lander cruise (`OY16` held in `[ALT−50, ALT−30]`), and terrain-destruction bursts (`OY16 = ALT`,
shrapnel at `ALT−10`). **There is no player terrain-clearance test at all** — the ship's vertical
range is bounded only by `PLAUP CMPB #YMIN+1` (43) and `PLADN CMPB #238`, and it flies through the
mountains.
**Confidence** MULTI_SOURCE_CONFIRMED
**Evidence** Byte-scan of all four Red Label main ROMs for `8E B3 00` / `CE B3 00` / `10 8E B3 00`:
the single `defend.2` hit is inside `GETALT`. Constants byte-confirmed: `8B 04 81 E8` `$ECF8`,
`8B 0F 81 E8` `$ED26`, `80 32` (SUBA #50) `$F02E`, `81 EC` (CMPA #−20), `10 83 00 E0` `$F231`,
`80 0A` (SUBA #10) `$EE1E`.
**Notes** `$B300` is reused by other banks for unrelated tables (the player-explosion `TABLE` in
`blk71` itself), so the address alone is not a safe search key — hits in `defend.6`, `defend.9` and
`defend.12` are those other uses.

---

### L-145 · `MTERR` is a hand-drawn 5.7:1 reduction of the real profile, aligned at world x = 0
**Mechanic** Scanner
**Claim** The scanner mini-terrain is **64 unique 3-byte entries repeated twice** (ring-buffer
duplication), one entry per **32 world pixels**, covering the whole 2,048-px world in 64 byte-columns
= 128 scanner pixels. It **is** derived from the real terrain: correlation against per-block `ALTBL`
statistics is **0.931 (mean) / 0.914 (min) at zero circular shift**, best fit
`MTERR ≈ 0.176·mean − 2.1` (≈5.7:1 vertical compression), RMS residual **0.84 px**, max **2.5 px**.
**Confidence** MULTI_SOURCE_CONFIRMED
**Evidence** `MTERR` bytes from `blk71.src:530` and `defend.6` `0x450..0x5CF`; index derivation from
`amode1.src` `MT1` (`SUBD #$8000-(150*32)`, two `LSRA`, `LDB #3`/`MUL`, loop to
`#(SCANER>>8)+64`).
**Notes** Partially closes Q-07: too accurate to be freehand invention, too loose to be an exact
transform — traced by eye from the real profile. The residual is the draughtsman's hand.

---

### L-146 · The humanoid walk offsets are asymmetric by 11 pixels — arithmetic certain, appearance unmeasured
**Mechanic** Humanoids
**Claim** `ASTRO` targets `OY16 = ALT + 4` walking left and `ALT + 15` walking right, both clamped
to `≤ $E8` (232 — **exactly the lowest point on the planet**), while `AFALL0` treats `OY16 == ALT`
as ground contact and `ASTST` spawns humanoids at a flat `OY16 = $E0`. Since `OY16`'s high byte is
the sprite's **upper-left** row and all four astronaut pictures are `FCB 2,8` drawn **downward**,
this places a walking humanoid's 8-row sprite wholly beneath the 2-row terrain line.
**Confidence** SOURCE_CONFIRMED for the arithmetic; **UNRESOLVED** for the on-screen result
**Evidence** `defb6.src:314–318` / `337–341`, identical in `DEFB6.SRC`; ROM `8B 04 81 E8` `$ECF8`
and `8B 0F 81 E8` `$ED26`; `ASTP1..ASTP4` `= FCB 2,8` at ROM `$F901`–`$F927`; `ON28`
(`defa7.src:170`) draws 8 rows downward from `OBJY+8` via `PSHU`; `OPON` sets `OBJY = OY16` high
byte; framebuffer orientation MAME `williams_state::screen_update` (L-009).
**Notes** Tracked as **Q-01a**. Closes with a VIDEO_MEASURED reading of the pixel gap between the
terrain line and a walking humanoid, at a known world-x on the plain and on the massif, for both
walk directions. Do not launder the arithmetic into a claim about how the game looks.

---

## Part H — Added by the 2026-08-09 cross-revision **code** diff (Q-06 / Q-16 / Q-42 closure)

All four label revisions were rebuilt into CPU address-space images from their own file sets and
aligned instruction-stream-wise. Full method, reproduction commands and the unattributed residue
are in `REVISION_CODE_DIFF.md`. Tool: `site-v2/defender/tools/rom_diff.py`. Every one of the 48 ROM
files was re-verified (CRC32 **and** SHA-1) against `williams.cpp:1985-2075` before use; all 48
match.

Every claim in this Part is re-asserted mechanically by `python3 tools/rom_diff.py selftest`,
registered as the **`revision-diff`** check in `tools/check.sh`. Thirty-three assertions; all pass.
If a Part H entry is ever edited into disagreement with the ROMs, that check goes red.

---

### L-147 · Blue → Green is three bytes, and none of them is behavior
**Mechanic** Revision matrix / ROM self-test
**Claim** The entire Blue→Green delta is **three bytes in the fixed ROM and zero bytes in all seven
banked ROMs**: `$DB77 $81→$7E`, `$F88F $30→$00`, `$F894 $00→$30`. The latter two are the `ROMMAP`
descriptor pair (`defb6.src:1828-1839`, table base `$F88E`) moving the `$D800` descriptor `$30`
from ROM 1's second byte to ROM 4's first byte — Blue declares one 4K device at `$D000-$DFFF`,
Green declares two 2K devices. The first is the ROM self-test check byte for the `$D800` block,
whose seed changed from 1 to 4 with the socket, forcing a −3 adjustment: `$81 − $03 = $7E`.
**Evidence** `rom_diff.py summary` (3 bytes / 0 bytes); `rom_diff.py hexdiff blue green F880 48`;
`rom_diff.py checksums` — the `$D800` block passes at seed 1 in Blue and seed 4 in Green/Red, every
other block identical. Self-test algorithm `defb6.src:1630-1656` (`ADCB` over `$800` bytes seeded
with the ROM index, must leave `$80`).
**Confidence** MULTI_SOURCE_CONFIRMED
**Notes** Closes every "UNRESOLVED at the code level" cell for **Green** in
`ROM_REVISION_MATRIX.md` §2 as **no difference**. Also shows the dumped `defeng04.bin` is plain
`ROM4B`, not the `ROM4F` "GREEN SOFTWARE PRICING FIX" of `INFO.SRC:22` — that ROM is undumped.

---

### L-148 · White carries no ROM check bytes; Blue introduced them, seeded by ROM number
**Mechanic** ROM self-test
**Claim** Running the `defb6` self-test over every 2K block: **White fails 11 of 13 blocks at every
seed 0-31** (two apparent passes are single-seed coincidences). Blue, Green and Red pass all 13,
each at the seed equal to that socket's ROM number (`$D000`→1, `$E000`/`$E800`→2, `$F000`/`$F800`→3,
bank 7→6, bank 3→7/10, bank 2→8/11, bank 1→9/12).
**Evidence** `rom_diff.py checksums`; `defb6.src:1630-1656`
**Source** `INFO.SRC:15-16` — "1ST RELEASE WITHOUT CHECKSUMS" / "1ST RELEASE WITH CHECKSUMS"
**Confidence** MULTI_SOURCE_CONFIRMED
**Notes** Williams' own revision note is now mechanically corroborated rather than merely quoted.

---

### L-149 · 56 % of the Green→Red fixed-ROM byte delta is direct-page variable renumbering
**Mechanic** Diff hygiene
**Claim** Green→Red differs in 10,806 of 12,288 fixed-ROM bytes but is 83.2 % aligned-equal. Of the
1,051 single-byte replacements in the alignment, **342 are exactly +`$25` and 245 exactly +`$23`** —
two contiguous groups of direct-page variables relocated wholesale — and most of the remainder are
low bytes of moved absolute addresses. Several variables left the direct page entirely, turning
2-byte direct operations into 3-byte extended ones (the chain of +1-byte sites between `$EAF0` and
`$F3CD`). **Only 18 fixed-ROM sites are ≥ 12 bytes.**
**Evidence** `rom_diff.py shift green red`; single-byte delta histogram from the same alignment
**Confidence** MAME_CONFIRMED (byte reads from hash-verified images)
**Notes** Guards against reading raw byte-diff percentages as behavioral change. Any future
revision diff must align before it counts.

---

### L-150 · `WVTAB` moved out of the fixed ROM into bank 7 at the Green→Red boundary
**Mechanic** Wave parameters
**Claim** The 184-byte wave-parameter table lives in the **fixed ROM** at `$DE57` in Blue and Green
(`$DF35` in White) and in **bank 7 at `$C6BA`** in Red. Red appended two pointer words
(`FDB WVTAB` / `FDB WVTEND`) to the bank-7 entry header, growing it from 17 to 21 bytes and shifting
every `blk71` entry point by +4 (`JMP BGINIT` `$C011`→`$C015`, `FDB MTERR` `$C44C`→`$C450`,
`JMP PLEX` `$C5CC`→`$C5D0`). White/Blue/Green's bank-7 header has **no** `WVTAB` entry.
**Evidence** bank-7 header bytes at region offset `0x6000`; `rom_diff.py wavetab`; the
`$DE57 delete −184` entry in `rom_diff.py shift green red`
**Source** `phr6.src:30-31` (`WVTAB EQU $C011`, `WVTEND EQU $C013`), `blk71.src:89-90`
**Confidence** MULTI_SOURCE_CONFIRMED
**Notes** `phr6.src`'s `$C011`/`$C013` describe Red only. Any tooling that assumes those addresses
is Red-specific by construction.

---

### L-151 · Red's wave table is Green's rebased by exactly five inter-wall deltas
**Mechanic** Enemy speed / difficulty scaling — **load-bearing**
**Claim** `WVTAB` is **byte-identical across White, Blue and Green**. Red differs in 12 of 23 rows,
and **only ever in the W1..W4 base columns** — every `max`, `min` and intra-wall-delta cell is
unchanged, and exactly one inter-wall-delta cell changed. Applying Red's own `WDELT` clamp
semantics five times to Red's base cells reproduces **Green's cells in 90 of 92 cases**. Since
Red's factory `GA1` moved `$00`→`$05` and `GA2` `$10`→`$15` (L-136), and `GETWV` applies the
inter-wall delta `clamp(max(0, wave−4) + GA1, ≤ GA2)` times, **Red's delta count is Green's + 5 at
every wave including at the ceiling (10+5 = 15)**. Red's table was therefore pre-decremented by
five steps so that a factory Red machine presents the same enemies as a factory Green machine.
The two residual cells are one 16-bit value (`SZYV`, wave 2) split across independently-clamped
bytes: Red `$00FE` vs Green `$0100`, 2 parts in 65,536.
**Evidence** `rom_diff.py wavetab` (table + rebase test); `defa7.src:1857-1905` (`GETWV`),
`defa7.src:1907-1927` (`WDELT`), `blk71.src:673-722` (`WVTAB`)
**Confidence** MULTI_SOURCE_CONFIRMED
**Notes** The scale rebase is exactly what the July 1981 manual documents when Function 18's legend
changes from "0=LIB; 1=MOD; 2=CONS" to "0=LIB; 5=MOD; 10=CONS". **Consequence for Phase 2: a Red
wave table must be driven by a Red `GA1`/`GA2`. Mixing a Red table with Green defaults, or the
reverse, yields a five-step difficulty error in a game Williams never shipped.**

---

### L-152 · MAME's "red has much improved enemy AI and is harder to play" is refuted
**Mechanic** Revision matrix
**Claim** No enemy update routine, target-selection routine or state machine differs structurally
between Green and Red. The only enemy-behavior difference is the `WVTAB` constant table, and its
change is a **renormalisation that cancels the difficulty-default change** (L-151), plus one
genuine easing (L-153). `williams.cpp:34`'s claim is not supported by the code and should be
recorded as **refuted**, not merely uncited.
**Evidence** `REVISION_CODE_DIFF.md` §3.1-§3.2; `rom_diff.py wavetab`; `rom_diff.py shift`
**Confidence** MULTI_SOURCE_CONFIRMED for the absence of structural AI change across the 18
fixed-ROM sites ≥ 12 bytes and all four banks' sites ≥ 16 bytes; see the coverage statement in
`REVISION_CODE_DIFF.md` §6 for the unattributed residue (bank 1 attract, banks 2/3 diagnostics).
**Notes** Supersedes the "INFERRED at best" hedge in `ROM_REVISION_MATRIX.md` §2 "Timing / speed
claims". Closes Q-16 and the code half of Q-42.

---

### L-153 · Red removed the inter-wave escalation of lander descent speed
**Mechanic** Lander vertical velocity
**Claim** `LANDER_YV_LSB`'s **inter-wall delta was zeroed** (`$04`→`$00`) rather than compensated by
a base shift; its four base cells are unchanged. At factory settings the two revisions agree for
waves 1-4 and diverge thereafter:

| wave | Green 16-bit lander Y velocity | Red |
|---|---|---|
| 1-4 | `$0070`, `$00B0`, `$0100`, `$0100` | identical |
| 5 | `$0104` | `$0100` |
| 14+ (`GA2` ceiling) | `$0128` | `$0100` |

**Green's landers descend up to ~15.6 % faster than Red's from wave 5 onward.** This is the only
gameplay-affecting enemy difference between Green and Red at factory settings, and it makes Red
**easier**. The intra-wall delta (`$10`) is unchanged, so within-wave escalation is identical.
**Evidence** `rom_diff.py wavetab` row `LANDER_YV_LSB`; `defa7.src:1907-1927`
**Confidence** MULTI_SOURCE_CONFIRMED for the constants and the arithmetic; **UNRESOLVED** for the
perceived on-screen difference, which has not been measured.

---

### L-154 · Scoring is identical across all four Williams revisions
**Mechanic** Scoring
**Claim** All ten point-award sites — five `JSR SCORE` sites (`$0025` shell/bomb, `$0115` swarmer,
`$0150` humanoid returned, plus two register-supplied) and five inline constants after
`JSR KILPOS`/`JSR KILOS` (`$0120` Baiter 200, `$0115` Mutant 150, `$0115` Lander 150, `$0210` Pod
1000, `$0125` Bomber 250) — carry **identical values in White, Blue, Green and Red**. The
end-of-wave humanoid bonus (`CMPB #5` cap, `ASLB`×4 → 100 × min(wave,5)) is byte-identical. The
`SCORE` routine body differs only in one direct-page operand. The factory replay level `$01,$00`
(10,000) is identical (L-136).
**Evidence** `rom_diff.py`-driven signature search for `34 76 1A 01 09` (`SCORE`) and
`34 46 EE 64 37 06 BD` (`KILO01`) in all four images, then enumeration of every caller
**Source** `defa7.src:474-540`, `defb6.src:1162-1180`, `phr6.src:580-589`
**Confidence** MULTI_SOURCE_CONFIRMED
**Notes** Closes the "Scoring differences" row of `ROM_REVISION_MATRIX.md` §2 for all four columns.
Any Defender scoring reference from any era is safe to use.

---

### L-155 · Cocktail support is ~280 bytes of Red-only code, not a PROM change
**Mechanic** Cocktail / screen flip
**Claim** Red carries a **second, inverted interrupt handler** (`IRQB`, ~143 bytes inserted at
`$DFC3`, identified by its `LDA VERTCT / CMPA #$58` gate against the upright handler's `CMPA #$80`)
and reaches the interrupt through a three-byte `JMP` in RAM. Red's `$FFF8` IRQ vector is
**`$A08F` (RAM — `IRQHK`)**; White's is `$DFED`, Blue's and Green's `$DF0F`, all directly in ROM.
`P1SW`/`P2SW` (~120 bytes around `$D8CD`) rewrite the RAM vector per player with the
`$383C`/`$3934` PIA3-control/watchdog pairs. **None of `LDD #$383C`, `LDD #$3934`,
`LDA PIA3 / BPL`, `LDB #$39` or the `CMPA #$58` gate exists anywhere in White, Blue or Green.**
Two init consequences: `PIATAB`'s PIA3 control byte is `$3E` in Red and `$34` in the other three
(`C0 FF 00 00 14 05 34 3E` vs `... 34 34`), and Red's reset adds `CLR PIA0 / CLR PIA1` ("OFF LEDS").
**Evidence** `rom_diff.py find` on each signature; `$FFF8` vector bytes; `rom_diff.py dis`
**Source** `defa7.src:1176-1202` (`P1SW`/`P2SW`), `defa7.src:2007-2070` (`IRQB`),
`defa7.src:967-968` (`PIATAB`), `defa7.src:993-994`, `defb6.src:2251` (`FDB RESET,IRQHK`),
`phr6.src:15` (`WDATA EQU $38 ;NORMAL SCREEN;($39=FLIPPED)`), `phr6.src:148`
**Confidence** MULTI_SOURCE_CONFIRMED
**Notes** Confirms `INFO.SRC:19-21` ("COCKTAIL SOFTWARE") at the code level and relates the LED
lines of L-135 to a Red-only reset action.

---

### L-156 · Three Red-only coin-path changes: coin sound, credit persistence, CMOS validity
**Mechanic** Coin handling
**Claim** (a) Red's `COIN` routine executes `LDD #CNSND / JSR SNDLD` before dispatch; the
sound-descriptor table gained a matching new first entry `FF 01 18 19 00` (`CNSND`, sound `$19`).
**No coin descriptor exists anywhere in White, Blue or Green — inserting a coin is silent on those
three.** All 28 other descriptors are byte-identical across all four. (b) Red's `INIT` reads
`CREDST` (CMOS `$C47D`), BCD-validates it (`CMPA #$20 / BHI`, `ANDA #$0F / CMPA #9 / BLS`, else
`CLRB`) and seeds the RAM credit counter; Green's `INIT` performs no CMOS read, and Green's
power-up CMOS handler (bank 3 `$C108`-`$C117`) reads `CREDST` and writes **zero** back. **Credits
survive a power cycle on Red and not on Green.** (c) Red's bank-3 entry-vector table gained two
thunks (`$C036`, `$C039`) used by a rewritten CMOS validity path.
**Evidence** `rom_diff.py dis red D470 60` vs `dis green D460 66`; `dis red D74C 100` vs
`dis green D74C 68`; `dis green C0F0 48 --region bank --bank 3` vs the Red equivalent;
`rom_diff.py find ff01181900`
**Source** `defa7.src:648-654`, `defa7.src:665` (`CNSND`), `defa7.src:996-1005`,
`defa7.src:1168` (`LDX #CREDST ;BACKUP CREDITS`), `romc8.src:598-606` (`ACRED`)
**Confidence** MULTI_SOURCE_CONFIRMED
**Notes** Distinct from the `ROM4C`/`ROM4D`→`ROM4E` "multiple coin pricing error" of
`INFO.SRC:19-21`, which is Red-internal and invisible in a Green↔Red diff (see L-107 / Q-20).

---

### L-157 · Four blocks changed ROM socket or bank at the Green→Red boundary, none behaviorally
**Mechanic** ROM layout
**Claim**

| block | White/Blue/Green | Red |
|---|---|---|
| `WVTAB` (184 B) | fixed ROM `$DE57` | bank 7 `$C6BA` (L-150) |
| scanner / mini-map (`CSCAN`,`MT1`,`MTX`,`SCNR*`, ~195 B) | fixed ROM `$E9EB-$EAB2` | bank 1, entered via a **new `JMP $CCAD` at bank 1 `$C003`** |
| `MTERR` (384 B) | bank 7 `$C44C` only | **two copies**: bank 7 `$C450` and bank 1 `$CD69` |
| `defb6` `$CC90` graphics (877 B) | bank 1 `$CC90` (IC12) | bank 2 `$CC90` (IC11) |

The scanner's constants survive the move unchanged (`SUBD #$6D40`, `LDX #$4C09`, `LDD #$9090`,
`LDX #$5309`, `LDD #$0909`, `ADDD #$4B07`, `LDD #$9099`, `STA $FF01,X`, `CMPA #$70` present on both
sides), so scanner geometry, bezel and blip colors are revision-invariant. The `MTERR`
duplication is mirrored in the recovered source, which defines it twice with identical bytes.
Red also prefixed bank 1 with a two-entry `JMP` table (`JMP $C006` / `JMP $CCAD`).
**Evidence** signature search for each block across all four images; `rom_diff.py dis green E9D0 240`
**Source** `amode1.src:1195-1275` and `:1280` (scanner + `MTERR`), `blk71.src:530` (`MTERR`),
`defb6.src:2065` (`ORG $CC90`)
**Confidence** MULTI_SOURCE_CONFIRMED
**Notes** The `defb6` `$CC90` row substantially closes **Q-17**: 877 of the 944 `unknown.bin` bytes
in Red's `defend.11` (file offsets `0x0490-0x07FC`) are literal `FCB` data present verbatim in
`DEFB6.SRC:2067-2220`. Residue: `0x0450-0x048F` and `0x07FD-0x07FF`, 67 bytes.

---

### L-158 · The message dictionary lost the word "100" at the Green→Red boundary
**Mechanic** Attract / text
**Claim** The bank-2 message dictionary is identical word-for-word across all four revisions
**except** that White, Blue and Green contain `"100/"` between `"MUTANT/"` and `"150/"` and Red does
not. Words are referenced by address (`FDB`), not by index, so nothing is renumbered; but some
White/Blue/Green attract or scoring screen printed "100" and Red's does not. The recovered
`MESS0.SRC` has no `"100"` entry.
**Evidence** `rom_diff.py find` on `MUTANT/`; `rom_diff.py strings` set difference per bank
**Source** `MESS0.SRC:384-388`
**Confidence** MULTI_SOURCE_CONFIRMED for the dictionary difference; **UNRESOLVED** for which
screen used it and how Red's version reads.

---

### L-159 · The recovered source is pre-final-Red at exactly one datable site
**Mechanic** Source provenance — answers Q-41
**Claim** A source site can be *dated* only where Red actually differs from White/Blue/Green.
Twenty such sites were enumerated from the code diff and each was checked against the recovered
`historicalsource-defender/*.SRC` text. **Nineteen carry Red's value. One carries the
White/Blue/Green value: the hall-of-fame sound-select block in `AMODE1.SRC` (L-135).**
Independently, an automated pass reassembled every pure-numeric `FCB`/`FDB` run in the recovered
tree and searched all four images: of 41 runs ≥ 4 bytes, **5 matched Red only, 35 matched Red and
at least one earlier revision (uninformative), and 0 matched an earlier revision only.**
Observed pre-Red rate **1 in 20 (5 %)**; against an estimated 30-45 content-bearing Green↔Red
difference sites program-wide, the expected tree-wide total is **1-2 sites, with a defensible upper
bound of 4**.
**Evidence** `REVISION_CODE_DIFF.md` §5 (full 20-site table and the automated pass)
**Confidence** MULTI_SOURCE_CONFIRMED for the per-site verdicts; **INFERRED** for the extrapolated
bound
**Notes** The recovered text is a **late** pre-final-Red snapshot: it already contains the wave-table
rebase, the whole cocktail subsystem, the coin sound, the credit restore, the CMOS default change
and the message-dictionary edit. One further discrepancy is **inert and not counted**:
`blk71.src:64` declares `IRQHK EQU $A024` while shipped Red's `IRQHK` is `$A08F`; the symbol is
declared but never referenced inside `blk71`, so it assembles to nothing. This closes the question
Q-41 was asked in order to answer, by a toolchain-free route; it does not perform Q-41's stated
assemble-and-diff method, and it cannot date sites where all four revisions agree.

---

## Part I — Added by the C1 defect-closure pass (2026-08-09, iteration 2)

Entries created because an adversarial re-review found a claim that was published as `UNRESOLVED`
or as an unowned label while the evidence to settle it was already in the corpus or one command away.

---

### L-160 · The Hall-of-Fame letter color is `$85`, and it is magenta, not "light blue"
**Mechanic** Palette / attract mode
**Claim** `HALL1B` loads the attract-mode Hall-of-Fame letter color with `LDB #$85 / STB PCRAM+1`.
`$85` shipped in **all four Williams revisions**. Decoded through MAME's resistor ladders it is
**(174, 0, 160)** — a magenta/violet. The source comment "LIGHT BLUE LETTERS" is **stale** and must
not be implemented.
**Revision** all-Williams
**Evidence** `defend.9[0x53] = C6 85 D7 27`. `defend.9` is bank 1, mapped at `$C000`, so `0x53` is
CPU `$C053`; `D7 27` is `STB $27` with `DP = $A0`, i.e. `STB $A027 = PCRAM+1`. The same
`C6 85` appears at `rom9.bin[0x3D]` (White) and `defeng09.bin[0x40]` (Blue and Green).
Decoding: `$85` = `BBGGGRRR` → R bits 2+0 → 38+136 = 174, G = 0, B code `10` → 160 (L-020).
**Source** `amode1.src:148–149` (`HALL1B LDB #$85 ;LIGHT BLUE LETTERS / STB PCRAM+1`)
**Secondary** `BUILD_REPRODUCTION.md:503` already published the disassembly; `williams_v.cpp:340–352`
for the ladder weights
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** `python3 -c "print(open('roms/defender-redlabel/defend.9','rb').read()[0x53:0x57].hex())"`
→ `c685d727`.
**Notes** `MAME_HARDWARE_CROSSWALK.md` §4 carried this as `UNRESOLVED` with the resolution
"disassemble the corresponding bytes of the dumped `defend.9`/`defend.12`" — while the corpus already
contained that disassembly in two other files. It is the second instance of the same root cause as
N-03 (the sound-ROM checkbyte). This is a **rendered color** a builder will hard-code, so the stale
comment is the dangerous artifact, not the byte.

---

### L-161 · The six un-diffed modules contain no source-vs-source divergence beyond the cataloged set
**Mechanic** Provenance / methodology
**Claim** A normalized (label, opcode, first-operand) diff of `defb6`, `blk71`, `amode1`, `mess0`,
`romc0` and `romc8` between the mwenge modernised tree and `historicalsource-defender` yields
**ten behavior-bearing differences, every one of them already enumerated in this corpus**: the
`PLD21` build hack (`defb6.src:2170`, X2/L-012), the `blk71.src:191` `PatchROM` site (X11/Q-40),
four `$FFFF` checkbyte placeholders (`amode1:375`, `mess0:68`, `mess0:654`, `romc0:78`), and the
four hall-of-fame sound literals (L-135). Everything else is a label rename, an assembler-operator
canonicalisation, an indexed-mode syntax change, or quote style. **Constants drawn from those six
modules therefore carry the same low risk as `defa7`'s.**
**Revision** n/a — this compares two transcriptions of one Williams source snapshot
**Evidence** `tools/src_vs_src.py`. Normalized differing lines: `defb6` 6, `blk71` 3, `amode1` 38,
`mess0` 11, `romc0` 4, `romc8` 12. The harness is validated by the control run on `defa7.src`,
which re-derives its four independently-known semantic edits (lines 131, 834/840, 3049, 3079) and
nothing else.
**Source** `mwenge-defender/src/{defb6,blk71,amode1,mess0,romc0,romc8}.src` vs
`historicalsource-defender/{DEFB6,BLK71,AMODE1,MESS0,ROMC0,ROMC8}.SRC`
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** n/a
**Test** `python3 tools/src_vs_src.py` — assert the per-module counts above and that the `defa7`
control still shows exactly its four semantic edits.
**Notes** This closes **Q-06** (source vs source). It does **not** close, and is not evidence for,
**Q-41** (source vs ROM), which closed separately via `REVISION_CODE_DIFF.md` §5. The two were
conflated by the previous pass, which routed Q-06's Tier-0 work to Q-41's Tier-2 slot behind a
toolchain neither needs. Keeping them distinct is the point.

---

### L-162 · `ROMMAP` ships populated, and Blue's differs — it encodes the `$D000` packaging
**Mechanic** Diagnostics / revision matrix
**Claim** The shipping `ROMMAP` is `defb6.src:1828-1839`, **not** `romf8.src`'s all-zero copy
(X8, L-011). The 24 bytes appear verbatim in the Red, Blue and Green images and are **absent from
White** — consistent with White having no ROM self-test at all (L-148). **Blue's copy differs from
Red's and Green's in exactly the two bytes that describe the `$D000` region**, which is the
packaging change `INFO.SRC` names.

| Set | File | Offset | ROM 1 | ROM 4 |
|---|---|---|---|---|
| Red | `defend.3` | `0x870` | `20 00` | `30 00` |
| Green | `defeng03.bin` | `0x890` | `20 00` | `30 00` |
| Blue | `wb03.bin` | `0x890` | **`20 30`** | **`00 00`** |
| White | — | — | *(no `ROMMAP`)* | — |

Blue's single 4K `$D000` device is described by one entry covering both 2K blocks (`20 30`) with
ROM 4 marked absent (`00 00`); Red and Green, which split `$D000` into two 2K devices, describe them
separately. The remaining twenty-two bytes are identical in all three.
**Revision** per-revision, as tabulated
**Evidence** Byte search for the 24-byte pattern across all 48 images. Red/Green:
`20 00 40 50 60 70 30 00 00 00 07 00 03 00 02 00 01 00 13 00 12 00 11 00` — byte-for-byte the
`FCB` block at `defb6.src:1828-1839`. Blue: same with bytes 1 and 6–7 changed as above.
**Source** `defb6.src:1828–1839` (shipping copy); `romf8.src:626` (all-zero, not shipped)
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** `grep` the 24-byte Red pattern in `defend.3`; assert offset `0x870` and assert it is absent
from every White image.
**Notes** Closes the `UNRESOLVED` at `SOURCE_MAP_SUPPORT.md` §12 item 1, whose stated resolution
("disassemble the 24 bytes at the `ROMMAP` label inside the dumped `defend.*` images") was one byte
search. It also independently corroborates `INFO.SRC:18` on the Blue→Green packaging change and
L-148 on White's missing self-test.

---

### L-163 · The `$12` / `BON2` register bug is in **every** Williams revision
**Mechanic** Audio / entities
**Claim** The astronaut-lift sound `$12` was **never audible on any shipped Defender**. `LNDFX0`
zeroes `D` (`CLRD`), then loads the sound number into **A** while `SNDOUT` transmits **B** — so the
board receives `(~0) & $3F = $3F`, the idle pattern, and no `CB1` edge is produced. The instruction
is `LDA #$12` (`86 12`), not `LDB #$12` (`C6 12`), in White, Blue, Green **and** Red.
**Revision** all-Williams
**Evidence** The site is byte-identical across all four sets:
`4F 5F ED 88 10 ED A8 10 A6 2C A1 0C 23 0F 6A 2C 86 12 BD …`
(`CLRA / CLRB / STD 16,X / STD 16,Y / LDA 12,Y / CMPA 12,X / BLS +15 / DEC 12,Y / LDA #$12 /
JSR SNDOUT`). Nothing between `CLRB` and the `JSR` writes `B`. Offsets: Red `defend.3[0x126]`,
White `rom3.bin[0x1F6]`, Blue `wb03.bin[0x13D]`, Green `defeng03.bin[0x13D]` (each pointing at the
`86 12`).
**Source** `defb6.src:813–821` (`LNDFX0`); `defa7.src:696–704` (`SNDOUT`, `*B=SND TO MAKE`)
**Secondary** X6, L-139 (active-low, `$3F` = idle)
**Confidence** MULTI_SOURCE_CONFIRMED
**Impl** TBD
**Test** Search each set's `$F000` image for `86 12 BD`; assert exactly one hit and that the two
bytes before the `JSR` operand are `86 12`, never `C6 12`.
**Notes** Closes **Q-23** and `SOUND_EVENT_MATRIX.md` **U-1**, and removes the residual risk noted
against **X6**. A reconstruction must **not** "fix" this: `BON2`, `BONV`, `BONSND` and the `B2FLG`
state machine are unreachable, and the only sound at the suck-in is the `$0E` `LSKSND` ladder.
Making `$12` audible would add a sound no player has ever heard.
