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