Files
retroDE_ps2/docs/ch355_audit_log.md
T
thejayman77 ba74bbd5aa Snapshot: fog implementation + fidelity tooling baseline (pre bilinear-clamp fix)
Per-vertex GS fog end-to-end (gs_stub emit incl. persp_emit5, gs_prim_list_feeder
XYZ2->XYZF2 on PRIM.FGE, gs_make_sh3_scheduler_fixture.py F/FGE packing), new fog
TBs, fidelity attribution tooling. Functional baseline before removing the dead
bilinear lerp8 clamps (Codex: 161-node comb loop -> -0.042ns setup fail).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 19:56:46 -04:00

8.3 KiB

Ch355 audit log — MULTI-TEXTURE composition (different-texture draws accumulate into one LPDDR framebuffer)

Codex rung after Ch354. Brick 1 (narrow): TWO authentic SH3 draws with DIFFERENT TEX0/CLUT composited into one FB via SCENE-LEVEL texture rebind + staged-list retriggering. Dump order (authentic): A=idx19562 (tbp=11264/cbp=14080) THEN B=idx89761 (tbp=9216/cbp=13952). NOT Brick 1: per-primitive TEX0, multi-resident cache, 640x480, cross-draw Z.

Preflight (gs_make_sh3_multitex_fixture.py) — fail-CLOSED, PASS

  • authentic dump order (A idx < B idx); same frame f1; DIFFERENT texture keys (11264 vs 9216).
  • on-screen overlap: A[203..340]x[10..282] ∩ B[245..490]x[57..390] = [245..340]x[57..282].
  • each texture+CLUT INDEPENDENTLY RESIDENT at its draw-time epoch (per-draw local-mem reconstruction); the two textures differ (real multi-texture, not aliasing); the two CLUTs differ.
  • CLUTs RELOCATED to distinct, non-overlapping, in-BRAM CBPs: A->CBP=480 (0x1E000), B->CBP=484 (0x1E400).

Emit — union geometry (Codex's numbers) + two of everything + independent A->B reference

  • union origin (202,9), content 289x381 -> stored FB 320x381 (FBW=5), stride 1280 B, size 0x77100, scanout 15240 beats/frame; HDMI shows all 320 cols (31 right cols precleared-black beyond the 289px content).
  • two feeder lists (dump order): A (TEX0 CBP_A) 68 tris/619 words; B (TEX0 CBP_B) 68 tris/619 words; max staging addr 618 < 12-bit 4096.
  • two textures de-swizzled -> LPDDR + per-texture CRC: A sum32=0x95a83cbc, B sum32=0xfbdeaa32 (=Ch354's tbp=9216).
  • two relocated CLUTs; bootlet uploads BOTH (A->CBP_A, B->CBP_B); each list's scene-level TEX0 (CLD=1) selects its CBP.
  • independent A->B composed reference (dump order, DECAL paint-order, B over A): 27455 covered, 9849 A&B overlap px.

Integration sim (tb_top_psmct32_sh3_multitex) — PASS on ALL Codex gates

  • TWO cache fills, each a FRESH fill_done low->high (rearm), correct beats(8192)/bytes(262144)/0 rd_errs, texture- specific CRC: A=0x95a83cbc THEN B=0xfbdeaa32.
  • NO stale-frame_drained race: scene A = fresh low->high; scene B = high->low->high (an observed FALL proves B's beats flowed and cleared A's stale high before the fresh rise). Enforced by a fd_before-aware run_scene task.
  • preclear EXACTLY once; scanout (video_src) disabled until the SECOND fresh drain.
  • records_emitted = 68 per list; writer ovf/BRESP = 0; scanout 15240 beats/frame EXACT; underflow=0.
  • final FB scored vs the independent A->B reference: OVERLAP(A&B) COMPOSITE = 96.7% <=1-texel (gated >=95%) — proves B (tex B + relocated CLUT B) correctly overwrote A. clut_bad=0 (every covered px in palette A or B).

Two SEPARATE claims (as Ch354)

  • CLAIM 1 multi-texture accumulation/composite: PROVEN — overlap composite 96.7% >=95%, clut_bad=0, cache rearmed with correct per-texture CRCs, anti-stale drains, exact scanout beats.
  • CLAIM 2 fidelity: ALL=69.3% (REPORTED, not gated) — dragged by the A-only region = draw 19562's OWN per-draw perspective-sampling fidelity (the parked limit; B/89761 renders fine, hence the 96.7% B-dominated overlap). Whether to independently verify 19562-alone is an open question for Codex.

Remaining before board fit (Codex: no fit until runtime staging also passes)

  • Host two-group RUNTIME-STAGING flow: preclear once; upload+fill+CRC tex A; STREAM list A (report words/records, 12-bit bound); GO A + observe fresh drain; REFILL+CRC tex B; STREAM list B; GO B + observe high->low->high; video_src after the 2nd drain. Then board fit (owner-gated).

Codex round 2 — NEGATIVE-Q root cause + exact canonicalization (RESOLVED)

The combined ALL=69.3% was masking a real defect, NOT the parked perspective limit. Diagnostic (ONLY=A/B/AB + FBDUMP + compose_mt.py):

  • A→B RTL composition == combined AB, 0/121920 px BIT-EXACT (accumulation/order correct).
  • CLUT relocation CORRECT: A rendered 100% in palette A, B 100% in palette B (palA ∩ palB = ∅).
  • ONLY=B (89761, positive Q) = 95.6%; ONLY=A (19562) = 27.4% with a CORRECT CLUT.
  • ROOT CAUSE: idx19562 has NEGATIVE q/s/t (q[-0.00039..-0.00024]); 89761 positive. Codex confirmed the RTL perspective reciprocal input/products are UNSIGNED -> negative Q is unsupported (not merely imprecise).

FIX (Codex) — exact host-side homogeneous-coordinate canonicalization in the fixture tool, BEFORE fixed-point pack: per triangle, all-Q-positive unchanged; all-Q-negative -> negate every vertex's S,T,Q (EXACT: (-s)/(-q)=s/q, and the negation of a linear attribute interpolates identically); mixed-sign/zero Q -> FAIL CLOSED. Overflow-checked.

  • SYNTHETIC GATE: a positive-Q triangle and its all-negated twin pack BIT-IDENTICALLY (self-test PASS).
  • 19562-alone re-rendered vs the UNCHANGED reference: 27.4% -> 93.0%, clut_bad=0 (oracle fixed to not count RTL-unwritten coverage-edge pixels as clut_bad).
  • A→B composition re-run: still 0/121920 BIT-EXACT.
  • Default AB acceptance: ALL 69.3% -> 95.1%, OVERLAP composite 96.4% (>=95%), clut_bad=0, 15240 beats, PASS.

RECORDED: the feeder CANONICALIZES homogeneous coordinates to positive Q. This is NOT native signed-Q hardware support; a true signed reciprocal path remains a later platform extension for mixed-sign triangles.

Ch355 Brick 1 status: integration PROVEN. Remaining before fit = host two-group runtime-staging flow.

Board prep (Ch355 multi-texture) — verified in sim, owner-gated for the fit

  • Board top: GS_SH3_LPDDR_FB_MULTITEX overrides SCANOUT_LB to STRIDE=1280/ROW_BEATS=40/NROWS=381 (320x381, FBW=5) and FEEDER_STG_WORDS->2048; everything else shared with GS_SH3_LPDDR_FB. Elaborates clean (exit 0) under GS_SH3_LPDDR_FB + GS_SH3_LPDDR_FB_MULTITEX + GS_LPDDR4B_FB + GS_LPDDR_TEX.
  • FB 320x381, stride 1280, size 0x77100, scanout 15240 beats/frame.
  • Bootlet (bios/payload_sh3_mt) uploads BOTH relocated CLUTs (CBP_A=480, CBP_B=484). RUNTIME staging -> NO FEEDER_STG_INIT_FILE (feeder starts empty; the host streams each per-scene list).
  • Host tools/ps2_sh3_multitex.c (the two-group runtime flow): wait feeder ready -> FB base 0 -> preclear ONCE (121920 words, 0..0x77100) -> fill+CRC tex A (fresh fill_done, crc 0x95a83cbc, 8192 beats/262144 bytes/0 rd_errs) -> STREAM list A (reset 0x0D8, per-word LO 0x0DC/HI 0x0E4; report words/records + 12-bit bound) -> arm writer, GO A, fresh drain (0x02C[6]) -> REFILL+CRC tex B (0xfbdeaa32) -> STREAM list B -> GO B, frame_drained HIGH->LOW->HIGH -> video_src after the 2nd fresh drain. Compiles -Wall clean; dry-run parses texA/texB CRCs + 68 tris/619 words each.
  • Build profile sh3_lpddr_fb_multitex (select_de25_profile.sh + QSF): SCENE GS_SH3_LPDDR_FB + GS_SH3_LPDDR_FB_MULTITEX
    • GS_LPDDR_TEX (no bitstream feeder init); FIXTURE bios/payload_sh3_mt. Ran profile-select + verified the QSF.
  • BOARD RUN: sudo ./ps2_sh3_multitex sh3_mtA_tex_lpddr.mem sh3_mtB_tex_lpddr.mem feeder_sh3_mtA.mem feeder_sh3_mtB.mem.

Codex round 3 — bridge-streaming sim test (closes the gap that hid the board bug)

BOARD RUN #1 rendered 0 beats: the host wrote 0x0D8=1 (SET staging addr) instead of 0, so word0/header landed at slot 1 -> feeder read ntris=0. Root cause is RTL: ps2_hps_bridge.sv:1171 feeder_addr_q <= wdata_lane (0x0D8 W SETS the address to the written value). Host fixed to write 0 (matches ps2_feeder.c); BOARD RUN #2 = full success. The gap: BOTH the multi-texture TB and tb_top_psmct32_feeder_bridge_demo drove feeder_stg via $readmemh / direct waddr — neither exercised the real 0x0D8/0x0DC/0x0E4 protocol.

Closed in tb_ps2_hps_bridge (joins the full regression): a model_stg captures the bridge's commit strobe (feeder_stg_we_tgl_o + waddr/wdata), and the test stages TWO complete lists EXCLUSIVELY via bridge register writes:

  • Scene A (10 words): 0x0D8=0, per-word 0x0DC(lo)/0x0E4(hi) -> every word checked at its slot, ESPECIALLY word0; final 0x0DC readback == nwords.
  • NEGATIVE test: 0x0D8=1 -> word0 mis-slots to slot 1 (reproduces the exact board failure; pins the addr-set semantics).
  • Scene B (6 words, different): re-stage after 0x0D8=0 (A->B, no $readmemh) -> every word + word0 + final addr.
  • records_emitted readback (0x0E4 R) == 68. tb_ps2_hps_bridge PASS. This exercises the exact host<->bridge address path that failed on silicon; the render/drain per scene is silicon-verified (BOARD RUN #2) + covered by tb_top_psmct32_sh3_multitex.

Ch355 Brick 1: BANKED — multi-texture composition on silicon + the runtime-staging gap closed.