From 496f8f6adac3c9c634eb71efe59202c22031ff45 Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Sun, 2 Aug 2026 04:02:00 -0400 Subject: [PATCH] docs(ppu): identify the inidisp gap's exact model difference; correct C3.04 -> C3.12 The last per-dot framebuffer gap was documented only as "an internal_cgram_address draw-ordering detail". The concrete difference: MesenCE updates InternalCgramAddress inside GetRgbColor, called ONLY when `color > 0` (SnesPpu.cpp:1114) and PER LAYER during tilemap render -- so a transparent pixel leaves the previous opaque column's value standing, and several layers may update it within one column. RustySNES assigns it once per column, UNCONDITIONALLY, from the COMPOSITED pixel at the draw cursor. Structurally different models, not an off-by-one. Gate-on-opaque is NOT the fix, and this was measured rather than assumed: wrapping the assignment in `if ap.opaque` moves the ROM's hash from 0xaeb678a4165b28c5 to 0xa55bd66a1e6dd125 -- still not the MesenCE-agreeing golden -- because gating the COMPOSITE is not the same as gating each LAYER FETCH. CORRECTION to a recorded claim: the AccuracySNES row that breaks under gate-on-opaque is C3.12 ("CGRAM taken in render"), NOT C3.04 ("H counter advances"), which passes. C3.12 is the row that asserts the redirect target directly, so its failure means the cart row and MesenCE's model disagree about a backdrop column -- that needs adjudicating before either is changed. The real fix tracks the redirect target in the FETCH stage, per layer, updating only for a non-zero colour index in MesenCE's layer order. That is a Phase 4c-shaped change, which is why it is still open. Docs only; render.rs restored byte-clean from a pre-edit snapshot. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 19 +++++++++++++++++++ docs/ppu.md | 21 +++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68420729..9852b28b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- **`inidisp_forgot_to_force_blank`: the model difference is now identified exactly, and a recorded + claim about it is corrected.** The last per-dot framebuffer gap was documented only as "an + `internal_cgram_address` draw-ordering detail". The concrete difference: MesenCE updates + `InternalCgramAddress` inside `GetRgbColor`, called **only when `color > 0`** and **per layer + during tilemap render** — so a transparent pixel leaves the previous opaque column's value standing, + and several layers may update it within one column. RustySNES assigns it **once per column, + unconditionally, from the composited pixel** at the draw cursor. Structurally different models, not + an off-by-one. + + **Gate-on-opaque is not the fix**, measured rather than assumed: wrapping the assignment in + `if ap.opaque` moves the ROM's hash from `0xaeb678a4165b28c5` to `0xa55bd66a1e6dd125` — still not + the MesenCE-agreeing golden — because gating the *composite* is not gating each *layer fetch*. + + **Correction:** the AccuracySNES row that breaks under gate-on-opaque is **`C3.12`** ("CGRAM taken + in render"), not `C3.04` ("H counter advances") as previously recorded — `C3.04` passes. `C3.12` + is the row that asserts the redirect target directly, so its failure means the cart row and + MesenCE's model disagree about a backdrop column, and that needs adjudicating before either + changes. The real fix tracks the target in the fetch stage, per layer, on non-zero colour indices. + - **Interlace scenes: the recorded Mesen2 nondeterminism is GONE, but interlace is blocked for a different reason.** Probed and withdrawn. Three consecutive Mesen2 runs and two snes9x runs of an interlace scene produced identical results, so the standing note that "Mesen2 alternates between diff --git a/docs/ppu.md b/docs/ppu.md index 4abc023a..dfceb006 100644 --- a/docs/ppu.md +++ b/docs/ppu.md @@ -165,6 +165,27 @@ Per `ref-docs/2026-06-24-ppu.md` §6: documented per-dot gap in `undisbeliever_golden.rs`: its artifact is the CGRAM-redirect target on backdrop columns between opaque regions (an `internal_cgram_address` draw-ordering detail, tied to the draw-cursor/fetch-ahead alignment of Phase 4c), not a missing access-during-render redirect. + + **The exact model difference, identified 2026-08-02.** MesenCE updates `InternalCgramAddress` + inside `GetRgbColor`, which it calls **only when `color > 0`** (`SnesPpu.cpp:1114`) — so a + *transparent* pixel leaves the address holding the previous opaque column's value — and it does so + **per layer during tilemap render**, in render order, so several layers may update it within one + column. RustySNES assigns it **once per column, unconditionally, from the composited pixel** at + the draw cursor (`render.rs`, `pd_draw_columns`). Those are structurally different models, not an + off-by-one. + + **Gate-on-opaque is NOT the fix, and this was measured rather than assumed.** Wrapping the + assignment in `if ap.opaque` moves the ROM's hash from `0xaeb678a4165b28c5` to + `0xa55bd66a1e6dd125` — still not the MesenCE-agreeing golden — because gating the *composite* is + not the same as gating each *layer fetch*. It also breaks an AccuracySNES row. **Correction to a + previously recorded claim: the row it breaks is `C3.12` ("CGRAM taken in render"), not `C3.04` + ("H counter advances"), which passes.** `C3.12` failing is the expected shape — it is the row that + asserts the redirect target directly — and it means the cart row and MesenCE's model disagree + about a backdrop column, which has to be adjudicated before either is changed. + + So the real fix is to track the redirect target in the **fetch** stage, per layer, updating only + for a non-zero colour index in MesenCE's layer order. That is a Phase 4c-shaped change, which is + why this is still open. The per-dot line state is transient (re-fetched at each line start), so it is not save-stated; `Ppu::load_state` invalidates it (forcing a re-fetch) and a mid-line save re-fetches on load. - **In-render OAM write redirect** (dossier C7.16). During