# 0011 — GS dump ingestion (Ch340): parse, census, translate a supported subset

Status: **ACCEPTED — Ch340 CLOSED (2026-06-21)** as a parser + census / fail-closed victory. Brick 5
(authentic on-silicon render) is **explicitly waived** because the authentic dump contains zero
supported segments — that is the census doing its job, not a failure.

## Closeout (honest framing, per Codex)
- Authentic `cubes_demo` GS dump (MIT homebrew, content-clean) parsed **deterministically, 0 malformed**.
- Container format pinned from PCSX2 source and validated byte-exact; byte-exact synthetic parser test
  passes (`tools/test_gs.sh`).
- Primitive reconstruction (GS vertex-kick model) works: **648 triangles + 540 sprites**.
- Support census classified **every** primitive; histograms + reasons emitted to
  `captures/gs/reports/cubes.census.txt` (aggregate only).
- Translator **failed closed with no scene**: every authentic triangle is textured (`TME=1`, no real-
  texture path) and sprites are unsupported. Nothing was approximated.
- Core trust-boundary goal achieved: **authentic GS traffic enters the pipeline and unsupported
  content is reported, not faked.** The translator→`ps2_feeder`→staging path is proven on the
  supported synthetic fixture; authentic silicon render is deferred to the census-derived blocker.
- Mechanical top blocker → **Ch341: textured-triangle ingestion** (real texture state/upload/bind).
  Do NOT hunt another dump for a convenient flat segment; do NOT substitute synthetic silicon for
  authentic Brick 5.

Original design follows (the boundary it set still holds).

## Goal & trust boundary
Authentic GS traffic enters the proven host pipeline, is decoded **deterministically**, and a
**strictly-supported subset** reaches pixels with **no hidden approximation**. Ch340's honest victory
is that property — NOT a full game frame rendering. Real captures will expose unsupported textures,
transfers, blend/state, and primitive modes; those are **reported**, never approximated.

## Pipeline
```
.gs[.xz]  ──(container parser)──►  raw packets  ──(GIF/GS decoder)──►  normalized event stream
   │                                                                          │
   │ (local, gitignored)                                                      ▼
   └────────────────────────────────────────────────►  support census + histograms (reports/)
                                                                              │
                                              supported subset ──(translator)─┴─►  ps2_feeder scene file
                                                                                    (Ch339 encoder streams it)
```
The translator emits the Ch339 text scene grammar (`tri`/`trig`/`tritile`/`rect`/`go`); it does NOT
re-implement the staging format. `ps2_feeder -f scene.txt` renders it on the existing bitstream.

## Normalized event stream (schema v1, versioned)
A flat, ordered list; every event carries `byte_off`, `frame_idx`, `event_idx`. Event kinds:
- `FRAME_BOUNDARY {field}` — VSync packet (frame delimiter).
- `GIFTAG {path, nloop, eop, pre, prim, flg, nreg, regs}` — decoded GIF tag header.
- `GSREG {addr, name, value}` — a GS register write (from A+D, REGLIST, or PACKED expansion).
- `IMAGE {qwc, dst_fmt}` — an IMAGE-mode (HWREG/texture/FB upload) transfer; payload bytes summarized,
  NOT inlined into committable output.
- `READFIFO {qwc}` — local→host transfer.
- `MALFORMED {reason}` — structural decode failure at this offset.

`GSREG.name` covers the register set we already encode in `bake.py`/`ps2_feeder`: PRIM, RGBAQ, ST, UV,
XYZ2/XYZ3, XYZF2/XYZF3, TEX0_1/2, CLAMP, FOG, TEX1/2, FRAME_1/2, ZBUF_1/2, TEST_1/2, ALPHA_1/2,
SCISSOR, PRMODE/PRMODECONT, BITBLTBUF, TRXPOS, TRXREG, TRXDIR, etc. Unknown addrs → `GSREG` with
`name="UNKNOWN_0xNN"` (reported, not dropped).

## Support census (every event classified)
- **translated** — emitted into the ps2_feeder scene (the supported subset, below).
- **ignored (justified)** — safely skippable with a stated reason (e.g. FOG with FGE off; a redundant
  state write; a NOP). The justification is explicit per category.
- **unsupported** — a real effect we cannot faithfully reproduce yet (textured prim, sprite with a
  real texture, blend mode ≠ the proven source-over, PSM we don't render, Z format, dest-alpha test,
  scissor we don't honor, TRIANGLE_FAN/STRIP we haven't reduced, lines/points, IMAGE texture upload).
  Recorded with frame/event/byte offset + the exact reason. **Never approximated.**
- **malformed** — structural failure (bad GIF tag, truncated payload, length mismatch).

Reports (committable, no game content): per-dump JSON/text with frame count, a GS-register-write
histogram, a primitive-mode histogram, and the full unsupported/malformed list with offsets+reasons.

## Supported subset that reaches pixels (Ch340 v1)
Matches what the feeder + `ps2_feeder` already render faithfully on silicon:
- `PRIM` = TRIANGLE (prim type 3), with `IIP` flat or gouraud (per-vertex RGBAQ).
- Vertices via `XYZ2`/`XYZ3` (Z honored — Ch338 cross-batch Z is correct).
- Color via `RGBAQ` (MODULATE through the unity texel — matches the proven path).
- `FRAME_1`/`ZBUF_1`/`TEST_1`(GEQUAL)/`ALPHA_1`(the proven source-over) within the supported envelope.
A draw segment qualifies only if EVERY primitive in it is in this subset and the state matches the
proven envelope. Sprites→rect and triangle-strip→triangle reduction are candidate Ch341 work, decided
from the census, not pre-built.

## Acceptance (Codex)
1. Byte-exact parser tests on a small **synthetic** `.gs` fixture (`captures/gs/synthetic/`, authored
   once the real container format is confirmed).
2. One authentic dump parses **deterministically** (same events every run).
3. Frame / register / primitive histograms emitted.
4. Unsupported events carry frame/event/byte offset + reason.
5. ≥1 carefully chosen supported frame/segment translates to a ps2_feeder scene and **renders on
   silicon**.
6. Translation failures **stop before board access**.

## Bricks (gated on the dump)
1. Inspect the real dump bytes; confirm the container framing (header, compression, packet types).
   Build the container parser + a matching synthetic fixture; byte-exact parser test.
2. GIF/GS decoder → normalized events (GIF tag + A+D/REGLIST/PACKED register expansion). Unit-tested
   against hand-built GIF packets (the encode side already exists in `bake.py`).
3. Census + histograms over the normalized stream; emit reports.
4. Translator: supported-subset events → ps2_feeder scene file; everything else → census. Fail closed.
5. Pick a supported segment from the census; render via `ps2_feeder -f`; confirm on silicon.

Ch341 is then chosen from the census's highest-impact blocker, not guessed now.
