From 37e488cbace678ff41f438aca7c3d0b5a13286a2 Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Sun, 2 Aug 2026 00:10:59 -0400 Subject: [PATCH 1/2] docs(adr-0013): hi-res needs an extraction rule, not a wider region The C5.15/C10.04/C9 hi-res rows were parked behind "widen the capture region past 256x224". That framing is wrong, and the measurement showing why is worth recording before anyone builds to it. A minimal Mode 5 frame was rendered as a throwaway scene and each host asked what it emits: snes9x (libretro) 256x224 ordinary -> 512x224 width doubled Mesen2 (Lua) 256x239 ordinary -> 512x478 width AND height The two references do not agree on the SHAPE of a hi-res frame, never mind its pixels: Mesen2 line-doubles and snes9x does not. A contract that simply said "512 wide now" would compare a 224-row picture against a 478-row one. With the earlier finding that even/subscreen columns agree within 0.4-3% across all three references while odd/mainscreen columns diverge 33-35% pairwise, the answer is: take even columns, take even rows from Mesen2, and get a 256x224 sample back. The REGION SIZE DOES NOT CHANGE. What a hi-res scene needs is a declared per-scene EXTRACTION, and `Scene` gaining a field that names it. This measurement was only possible because the hosts now reject an out-of-contract geometry loudly instead of silently hashing it (#320) -- the rejection is what printed the table. Co-Authored-By: Claude Opus 5 (1M context) --- .../0013-accuracysnes-framebuffer-oracle.md | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/adr/0013-accuracysnes-framebuffer-oracle.md b/docs/adr/0013-accuracysnes-framebuffer-oracle.md index 2e935a44..f5227664 100644 --- a/docs/adr/0013-accuracysnes-framebuffer-oracle.md +++ b/docs/adr/0013-accuracysnes-framebuffer-oracle.md @@ -124,3 +124,41 @@ state, holds it for a fixed number of frames, and publishes the current scene ID block. The host steps frames, watches the scene marker, and hashes the framebuffer on the last frame of each hold. Wholly deterministic, and on real hardware the same loop is simply a slideshow the viewer can watch. + +## Supplement, 2026-08-02 — hi-res needs an extraction rule, not a wider region + +The `C5.15`, `C10.04` and `C9` hi-res rows were parked behind "widen the capture region past +256x224". **That framing is wrong**, and the measurement that shows why is worth recording before +anyone builds to it. + +A minimal Mode 5 frame was rendered as a throwaway scene and each host asked what it emits: + +| host | ordinary frame | Mode 5 hi-res frame | +|---|---|---| +| snes9x (libretro) | 256x224 | **512x224** — width doubled | +| Mesen2 (Lua) | 256x239 | **512x478** — width **and height** doubled | + +The two references do not agree on the *shape* of a hi-res frame, never mind its pixels: Mesen2 +line-doubles and snes9x does not. A capture contract that simply says "512 wide now" would be +comparing a 224-row picture against a 478-row one. + +Combined with the earlier empirical finding — on a real Mode 5 scene the even/subscreen columns +agree within 0.4-3% across all three references while the odd/mainscreen columns diverge **33-35% +pairwise** — the shape of the answer is: + +- take the **even columns** of the 512-wide picture (the subscreen half, the half the references + agree on); +- from Mesen2, additionally take the **even rows**, since its 478 is a line-double of 239; +- which yields a 256x224 sample again. + +So the region size does not change at all. What a hi-res scene needs is a **declared per-scene +extraction** — a rule for turning whatever the host emits into the canonical 256x224 sample — and +`Scene` gaining a field that names it. The mainscreen halves of `C5.06`/`C5.07` and +`C9.01`/`.02`/`.07`/`.08` stay golden-blocked regardless, because rule 4 forbids blessing a golden +the references disagree about, and on those columns they do. + +The prerequisite for any of this was that a host must *reject* an out-of-contract geometry rather +than silently hash it. Both hosts' checks were lower bounds until 2026-08-02 — they caught a frame +that was too small and passed one that was too large, hashing a diagonal slice (Mesen2's Lua, from a +256 stride over a 512-wide buffer) or the leftmost 256 columns (the libretro host, which uses the +real pitch). That is fixed; the loud rejection above is what produced this table. From 90cbcae174db5bdccc839f3f69196f232b64e8a1 Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Sun, 2 Aug 2026 00:17:13 -0400 Subject: [PATCH 2/2] docs(adr-0013): name the extraction schema and cross-reference #320 Bot review, both suggestions accepted; no blocking issues. The extraction rule is now specified rather than gestured at: a closed `extract` enum on `Scene` (`Direct` | `HiResEven`), emitted as a COLUMN in build/scenes.tsv so the rule travels with the scene instead of three hosts each hard-coding their own -- the same reasoning that makes FIRST_ROW a declared per-host constant. A host meeting an `extract` value it does not implement must REJECT the scene, never fall back to `Direct`, which would silently hash the left half of a hi-res picture and is the exact failure this supplement exists to prevent. The "that is fixed" claim now names PR #320 and the two call sites, so the doc cannot drift from the code it depends on. Co-Authored-By: Claude Opus 5 (1M context) --- .../0013-accuracysnes-framebuffer-oracle.md | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/adr/0013-accuracysnes-framebuffer-oracle.md b/docs/adr/0013-accuracysnes-framebuffer-oracle.md index f5227664..4c018920 100644 --- a/docs/adr/0013-accuracysnes-framebuffer-oracle.md +++ b/docs/adr/0013-accuracysnes-framebuffer-oracle.md @@ -152,8 +152,22 @@ pairwise** — the shape of the answer is: - which yields a 256x224 sample again. So the region size does not change at all. What a hi-res scene needs is a **declared per-scene -extraction** — a rule for turning whatever the host emits into the canonical 256x224 sample — and -`Scene` gaining a field that names it. The mainscreen halves of `C5.06`/`C5.07` and +extraction** — a rule for turning whatever the host emits into the canonical 256x224 sample. + +Concretely, so three hosts cannot each invent their own: `Scene` gains an `extract` field of a small +closed enum, and the value is emitted as a **column in `build/scenes.tsv`** rather than compiled into +any host. Every host already parses that file to learn the scene list; making the rule travel with +the scene is what stops the C, Lua and Rust implementations drifting apart, which is the same reason +`FIRST_ROW` is a declared per-host constant rather than a guess. + +| `extract` | meaning | +|---|---| +| `Direct` | today's behaviour — the frame is 256 wide, take `SCENE_H` rows from `FIRST_ROW`. Every existing scene, and the default, so no golden moves. | +| `HiResEven` | the frame is 512 wide: take even columns, and even rows as well on a host whose height also doubled. Yields the same canonical 256x224 sample. | + +A host that meets an `extract` value it does not implement must **reject the scene**, exactly as it +now rejects an out-of-contract geometry — never fall back to `Direct`, which would silently hash the +left half of a hi-res picture and is the failure this whole supplement exists to prevent. The mainscreen halves of `C5.06`/`C5.07` and `C9.01`/`.02`/`.07`/`.08` stay golden-blocked regardless, because rule 4 forbids blessing a golden the references disagree about, and on those columns they do. @@ -161,4 +175,6 @@ The prerequisite for any of this was that a host must *reject* an out-of-contrac than silently hash it. Both hosts' checks were lower bounds until 2026-08-02 — they caught a frame that was too small and passed one that was too large, hashing a diagonal slice (Mesen2's Lua, from a 256 stride over a 512-wide buffer) or the leftmost 256 columns (the libretro host, which uses the -real pitch). That is fixed; the loud rejection above is what produced this table. +real pitch). That is fixed in **PR #320** (`libretro_crossval.c`'s exact `w`/`h` test and +`mesen_scenes.lua`'s exact `SCENE_BUF_LEN`), which landed before this supplement; the loud rejection +it added is literally what produced the table above.