# Ch356 audit log — N-TEXTURE SCHEDULER (data-driven epoch descriptors)

Codex rung after Ch355. Generalizes Ch355's hard-coded two-group flow to a DATA-DRIVEN scheduler over ordered authentic
draw epochs, each with a DIFFERENT TEX0/CLUT, accumulating into ONE LPDDR framebuffer via scene-level texture rebind +
runtime staged-list retriggering. Default N_EPOCHS=3 (dump order): E0=idx11671 (tbp=10240) E1=idx19562 (tbp=11264)
E2=idx89761 (tbp=9216). Runtime CLUT UPLOAD is OUT of scope (Codex): the scheduler operates over a PRELOADED CLUT table
(the bootlet uploads all N relocated CLUTs); each epoch SELECTS its palette via its list's scene-level TEX0 CBP.

## Preflight (gs_make_sh3_scheduler_fixture.py) — fail-CLOSED, PASS
- >=3 epochs; authentic ascending dump order; single frame f1.
- >=3 DISTINCT textures (tbp 10240/11264/9216); each PSMT8 512x512 perspective TME (fst=0) — supported format/state.
- each texture + CLUT INDEPENDENTLY RESIDENT at its draw-time epoch (per-epoch local-memory reconstruction).
- palettes pairwise-DISTINCT (real multi-texture, no aliasing).
- CLUTs relocated to distinct, non-overlapping, in-BRAM CBPs: E0->480 E1->484 E2->488 (each 1 KiB / 4 blocks, VRAM 512 blocks).
- per-triangle Q-canonicalization: all-neg -> negate S/T/Q (exact); mixed-sign/zero -> FAIL CLOSED. neg-Q per epoch: 68/68/0.
- each list fits staging (619 words < 2048); deterministic union framebuffer geometry.

## Emit — union geometry + N of everything + composed + per-epoch references
- union origin (170,9), content 321x381 -> stored FB **384x381 (FBW=6), stride 1536 B, size 0x8ee00, scanout 18288
  beats/frame**.
- N feeder lists (canonicalized): feeder_sh3_sched{k}.mem, each 68 tris / 619 words. Each list's TEX0 CBP = its relocated palette.
- N textures de-swizzled (LINEAR) -> LPDDR single region 0x200000: sh3_sched{k}_tex_lpddr.mem. Per-epoch CRC (the cache-fill CRCs):
  E0=0x26a88b77  E1=0x95a83cbc  E2=0xfbdeaa32 (E1/E2 == Ch355's texA/texB).
- N relocated CLUTs; bootlet (bios/payload_sh3_sched) preloads ALL N via N 256x1 BITBLTs; DISPLAY1 = 384x381.
- composed reference (paint-order DECAL) sh3_sched_refmap.mem: [31]cov [30]int [28]multi(>=2) [26:24]owner_epoch [17:9]tu [8:0]tv.
  42414 covered px, 6124 multi-epoch (>=2 distinct) px. Per-epoch refmaps sh3_sched{k}_refmap.mem carry each epoch's OWN (tu,tv).
- descriptor table sh3_sched_epochs.txt (META line + one row/epoch: k idx tbp cbp tex_file lpddr size crc list_file words records) — the host reads it.
- params sh3_sched_params.vh (FBW/FBPXW/FBH, TEX_*, N_EPOCHS, EPk_CRC/EPk_NTRIS/EPk_CBP).
- canonicalization self-test PASS: a positive-Q triangle and its all-negated twin pack BIT-IDENTICALLY.

## Integration sim (tb_top_psmct32_sh3_sched) — PASS. All Codex acceptance gates:
- preclear EXACTLY once (fb precleared in the initial block, never between epochs).
- 3 FRESH cache fills, each fill_done low->high (rearm), beats=8192 bytes=262144 rd_errs=0, epoch CRC exact (0x26a88b77/0x95a83cbc/0xfbdeaa32).
- 3 lists STREAMED through the feeder staging WRITE PORT (feeder_stg_we/waddr/wdata), NOT the $readmemh backdoor — closes
  the Ch355 sim gap where the write-port sequencing was untested. Each streams the full 2048 STG words (staging fully reset);
  word0 (ntris header) verified in the DUT staging = 68 per epoch.
- 3 FRESH ORDERED drains: E0 low->high (first render, falls 0->0 rises 0->1); E1 high->low->high (falls 0->1 rises 1->2);
  E2 high->low->high (falls 1->2 rises 2->3). records_emitted=68 each; writer ovf=0 bresp=0.
- scanout held OFF until the last fresh drain; scanout beats/frame = **18288 EXACT**; underflow=0 rd_errs=0; 171720 px checked.
- oracle (per-pixel, multi-texture correctness): each covered px must equal ONE covering epoch's texel (each epoch's own
  (tu,tv) from refmap_ep[k]) within <=1 texel; clut_bad = colour in NO epoch palette. Result: ALL=94.1% <=1texel, MULTI(>=2)=92.2%,
  clut_bad=0. The <=1texel % is RTL reciprocal-LUT fidelity (per-epoch isolated ONLY=k: 93.3/93.0/95.6% — matches Ch355), NOT
  accumulation. Gated at the documented 90% reciprocal floor.

## Accumulation correctness — 100% BIT-FOR-BIT (the decisive proof; make tb_top_psmct32_sh3_sched_compose)
- render each epoch ALONE (+ONLY=k +FBDUMP) over a precleared FB, then composite the isolation dumps in paint order
  (nonzero-wins DECAL) and compare to the joint ALL render.
- **compose_sched.py: 146304/146304 px, 0 mismatch — 100.0000% BIT-FOR-BIT.** The scheduler drawing the epochs together
  produces exactly the same FB as compositing the individually-rendered epochs: no cache bleed, no stale pixels, correct rebind.
- (An earlier 365-px discrepancy was a bug in MY compose heuristic — a reference-covered-but-RTL-unwritten epoch-2 pixel
  wrongly "owned" a value-0 slot, masking the lower epoch that actually drew. Pure nonzero-wins DECAL is the correct operator.)

## Host (ps2_sh3_sched.c) — data-driven, builds -Wall clean, dry-run OK
- reads sh3_sched_epochs.txt (META + rows); loads each epoch's texture+list; verifies local CRC per epoch.
- flow: wait ready -> FB base 0 -> preclear ONCE (146304 words) -> for each epoch: upload tex -> LPDDR 0x200000, FRESH fill
  + verify CRC/beats/rd_errs -> STREAM list (0x0D8=0 reset, 0x0DC/0x0E4 commit) -> arm writer (k==0) -> GO + fresh ordered
  drain (k==0 low->high; k>0 high->low->high anti-stale) -> records==tris -> enable scanout after the LAST drain.
- register map identical to ps2_sh3_multitex (frame_drained 0x02C[6], staging 0x0D8/0x0DC/0x0E4, GO 0x0E8).
- BOARD RUN: `sudo ./ps2_sh3_sched sh3_sched_epochs.txt`  (all fixtures in the cwd).

## Board prep — fit-ready, owner-gated
- de25 top: new `GS_SH3_LPDDR_FB_SCHED` branch -> SCANOUT_LB 1536/48/381 (384x381, 18288 beats/frame) + FEEDER_STG_WORDS=2048.
  Elaborates clean under GS_SH3_LPDDR_FB+_SCHED+GS_LPDDR4B_FB+GS_LPDDR_TEX+USE_QSYS_TOP (only the known Quartus-only EMIF_Qsys
  is unresolved in iverilog — same status as multitex; Quartus has the real IP).
- profile `sh3_lpddr_fb_sched` (select_de25_profile.sh): fixtures bios/payload_sh3_sched; macros GS_SH3_LPDDR_FB +
  GS_SH3_LPDDR_FB_SCHED + GS_LPDDR_TEX. QSF verified (macros + fixtures applied; MULTITEX cleared). NO FEEDER_STG_INIT_FILE
  (runtime-staged, feeder starts empty).

## Copyright — all SH3-derived data LOCAL/gitignored
- fixtures (*.mem/.vh), the epoch descriptor table (sh3_sched_epochs.txt), and rendered FB dumps (sim/build/sched_fb_*.hex,
  dump-derived per-pixel data) are gitignored. Added `*sh3*.txt` to .gitignore for the descriptor table. Only OWN code
  (tb_top_psmct32_sh3_sched.sv, gs_make_sh3_scheduler_fixture.py, compose_sched.py, ps2_sh3_sched.c) is trackable.

## Two claims
1. **Accumulation/scheduling PROVEN EXACT**: joint N-epoch render == composited isolation dumps 100% bit-for-bit; 3 fresh
   cache rearms with per-epoch CRCs; 3 write-port-streamed lists (word0 verified); 3 fresh ordered anti-stale drains; exact
   18288 scanout beats; clut_bad=0; 6124 multi-epoch overlap px exercised.
2. **Fidelity** = 94.1% ALL / 92.2% MULTI <=1texel, i.e. the RTL 11-bit reciprocal floor (per-epoch isolated 93.0-95.6%,
   consistent with Ch355). Signed reciprocal for mixed-sign triangles remains a later platform extension.

Next rungs (Codex earlier): 640x480; persistent cross-draw Z (only if a census shows the chosen draws need it — no speculative Z).
