-
Notifications
You must be signed in to change notification settings - Fork 0
docs: interlace needs a rule extract cannot express, and Mesen2 is no longer flaky
#326
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -265,3 +265,43 @@ a state no picture can show; an unshowable scene hashes stably and every emulato | |
| The control scene is what caught it, and its doc comment said in advance what an equal hash would | ||
| mean. **Write the control first; it is the only thing that distinguishes "the references agree" from | ||
| "nothing happened".** | ||
|
|
||
| ### Interlace needs a rule the current design cannot express | ||
|
|
||
| Probed 2026-08-02, scene withdrawn. Two findings, and the second is the blocker. | ||
|
|
||
| **The good news: Mesen2 is no longer nondeterministic here.** The earlier note that "Mesen2 | ||
| alternates between both field parities run-to-run on one build" no longer reproduces — three | ||
| consecutive runs of an interlace scene produced the identical result, as did two of snes9x. That | ||
| nondeterminism was almost certainly the `emu.setInput` defect fixed earlier, and the standing advice | ||
| to "run any field-dependent capture twice per host" can be retired as the *reason* interlace is | ||
| blocked. It is blocked for a different reason. | ||
|
|
||
| **The blocker: the two hosts emit different WIDTHS for the same interlace scene.** | ||
|
|
||
| | scene kind | snes9x | Mesen2 | | ||
| |---|---|---| | ||
| | ordinary | `256x224` | `256x239` | | ||
| | Mode 5 hi-res | `512x224` | `512x478` | | ||
| | **interlace** | **`256x224`** | **`512x478`** | | ||
|
|
||
| Hi-res worked because both hosts emitted **512 wide** and differed only in height, which `HiResEven` | ||
| absorbs by deriving the row step from the observed height. Interlace does not: snes9x renders a | ||
| single field at 256 wide while Mesen2 doubles both dimensions. `HiResEven`'s `width == 512` assertion | ||
| cannot hold for both, and no single `(xstep, ystep)` pair describes both reductions. | ||
|
|
||
| So `extract` as designed — one declared rule per scene, each host applying the same reduction — is | ||
| insufficient for interlace. The options, none of them free: | ||
|
|
||
| 1. A `Downsample` rule that reduces *whatever* the host emits to 256x224 by deriving both steps from | ||
| the observed geometry. General, and it subsumes `HiResEven` — but it gives up the | ||
| **declared-not-inferred** property that is the whole reason `extract` catches a core rendering | ||
| 256 wide when it should render 512. | ||
| 2. A per-host reduction table, so a rule can say "snes9x: as-is; Mesen2: halve both". Keeps the | ||
| declaration honest at the cost of the hosts no longer being interchangeable. | ||
| 3. Declare the expected geometry per rule *and* per host, and reject anything else — the strictest, | ||
| and the most to maintain. | ||
|
Comment on lines
+296
to
+303
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
rg -n -C 8 \
'enum Extract|HiResEven|extract|visible_width|SCENE_W|SCENE_H|FIRST_ROW|SCENE_BUF_LEN' \
--glob '*.rs' --glob '*.lua' --glob '*.c' .Repository: doublegate/RustySNES Length of output: 50377 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- ADR 0013 target section ---'
sed -n '240,315p' docs/adr/0013-accuracysnes-framebuffer-oracle.md
printf '%s\n' '--- extraction-related files ---'
git ls-files | rg 'AccuracySNES|accuracysnes|0013|scene|libretro' | head -200
printf '%s\n' '--- host extraction definitions and call sites ---'
rg -n -C 5 \
'HiResEven|hires-even|FIRST_ROW|SCENE_H|SCENE_W|line.?double|512x478|512x224|256x239|256x224|extract' \
tests scripts crates docs/adr/0013-accuracysnes-framebuffer-oracle.md \
--glob '*.rs' --glob '*.c' --glob '*.lua' --glob '*.md' --glob '*.tsv' \
| head -1000Repository: doublegate/RustySNES Length of output: 50379 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- ADR 0013 complete extraction supplement ---'
sed -n '128,205p' docs/adr/0013-accuracysnes-framebuffer-oracle.md
sed -n '268,307p' docs/adr/0013-accuracysnes-framebuffer-oracle.md
printf '%s\n' '--- Rust extraction implementation ---'
sed -n '65,225p' crates/rustysnes-test-harness/tests/accuracysnes_scenes.rs
printf '%s\n' '--- C extraction implementation ---'
sed -n '120,265p' scripts/accuracysnes/libretro_crossval.c
printf '%s\n' '--- Lua extraction implementation ---'
sed -n '120,180p' scripts/accuracysnes/mesen_scenes.lua
printf '%s\n' '--- scene manifest extraction tags ---'
awk -F '\t' 'NF >= 4 { print NR ":" $0 }' tests/roms/AccuracySNES/build/scenes.tsv | head -120Repository: doublegate/RustySNES Length of output: 29247 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- interlace references and Rust PPU geometry ---'
rg -n -C 4 \
'interlace|INTERLACE|interlaced|pseudo.?hires|frame_hires|visible_height|SCREEN_HEIGHT|overscan' \
docs crates/rustysnes-ppu crates/rustysnes-test-harness scripts/accuracysnes \
--glob '*.md' --glob '*.rs' --glob '*.c' --glob '*.lua' \
| head -1200
printf '%s\n' '--- exact relevant Rust PPU constants and accessors ---'
rg -n -C 8 \
'pub const (SCREEN_WIDTH|MAX_SCREEN_WIDTH|SCREEN_HEIGHT)|fn visible_height|frame_hires|interlace' \
crates/rustysnes-ppu/src --glob '*.rs' | head -500
printf '%s\n' '--- extraction-shape verifier ---'
python3 - <<'PY'
from dataclasses import dataclass
SCENE_W, SCENE_H = 256, 224
cases = {
"snes9x interlace": (256, 224, 1, 1, 0),
"Mesen2 interlace": (512, 478, 2, 2, 7),
}
for name, (w, h, xstep, ystep, first_row) in cases.items():
sample_w = (SCENE_W - 1) if w == 512 else SCENE_W
source_rows = list(range(first_row * ystep, (first_row + SCENE_H) * ystep, ystep))
source_cols = [(1 + x) * xstep for x in range(sample_w)] if w == 512 else list(range(sample_w))
print(name)
print(f" source geometry={w}x{h}")
print(f" sampled geometry={sample_w}x{len(source_rows)}")
print(f" first/last source row={source_rows[0] if source_rows else None}/{source_rows[-1] if source_rows else None}")
print(f" first/last source col={source_cols[0] if source_cols else None}/{source_cols[-1] if source_cols else None}")
assert source_rows[-1] < h
assert source_cols[-1] < w
PYRepository: doublegate/RustySNES Length of output: 50379 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- Rust PPU geometry symbols ---'
rg -n -C 6 \
'pub const (SCREEN_WIDTH|MAX_SCREEN_WIDTH|SCREEN_HEIGHT)|pub const fn visible_height|fn visible_height|SETINI|is_interlace|interlace' \
crates/rustysnes-ppu/src/lib.rs crates/rustysnes-ppu/src/render.rs crates/rustysnes-ppu/src/registers.rs \
--glob '*.rs' | head -700
printf '%s\n' '--- targeted interlace documentation ---'
rg -n -C 8 \
'interlace|field.?doubling|448|478' \
docs/ppu.md docs/accuracysnes-coverability-audit-2026-07-23.md \
docs/adr/0013-accuracysnes-framebuffer-oracle.md docs/adr/0014-per-dot-ppu-compositor.md \
--glob '*.md' | head -700
printf '%s\n' '--- verifier for the documented Mesen2 reduction ---'
python3 - <<'PY'
SCENE_W, SCENE_H = 256, 224
source_w, source_h = 512, 478
first_row, xstep, ystep, first_col = 7, 2, 2, 1
sample_w = SCENE_W - first_col
rows = [(y + first_row) * ystep for y in range(SCENE_H)]
cols = [(x + first_col) * xstep for x in range(sample_w)]
print(f"halved geometry: {source_w // xstep}x{source_h // ystep}")
print(f"cropped sample: {sample_w}x{len(rows)}")
print(f"source rows: {rows[0]}..{rows[-1]}")
print(f"source cols: {cols[0]}..{cols[-1]}")
assert source_w // xstep == 256
assert source_h // ystep == 239
assert len(rows) == SCENE_H
assert rows[-1] < source_h
assert cols[-1] < source_w
PYRepository: doublegate/RustySNES Length of output: 28114 🏁 Script executed: #!/usr/bin/env bash
set -u
printf '%s\n' '--- ADR interlace section ---'
sed -n '276,305p' docs/adr/0013-accuracysnes-framebuffer-oracle.md
printf '%s\n' '--- Rust interlace output status ---'
sed -n '193,200p' docs/ppu.md
sed -n '315,321p' docs/ppu.md
sed -n '550,560p' docs/ppu.md
printf '%s\n' '--- conflicting host geometry statements ---'
sed -n '136,152p' docs/adr/0013-accuracysnes-framebuffer-oracle.md
sed -n '196,206p' scripts/accuracysnes/libretro_crossval.c
printf '%s\n' '--- deterministic geometry verifier ---'
python3 - <<'PY'
SCENE_W, SCENE_H = 256, 224
# The ADR's measured Mesen2 interlace frame and the proposed "halve both" reduction.
source_w, source_h = 512, 478
reduced_w, reduced_h = source_w // 2, source_h // 2
print(f"Mesen2 source={source_w}x{source_h}; halve-both result={reduced_w}x{reduced_h}")
assert (reduced_w, reduced_h) == (256, 239)
assert (reduced_w, reduced_h) != (SCENE_W, SCENE_H)
# Existing Mesen2 host constants crop seven leading output rows after line halving.
first_row, ystep = 7, 2
sample_rows = [(y + first_row) * ystep for y in range(SCENE_H)]
print(f"Mesen2 sampled rows={len(sample_rows)}, source range={sample_rows[0]}..{sample_rows[-1]}")
assert len(sample_rows) == SCENE_H
assert sample_rows[-1] < source_h
# RustySNES currently exposes only 224 or 239 visible rows, not interlace-doubled rows.
rust_heights = {224, 239}
print(f"RustySNES documented visible heights={sorted(rust_heights)}")
assert 478 not in rust_heights
PYRepository: doublegate/RustySNES Length of output: 6024 Define a complete per-host extraction contract. For Mesen2, 🤖 Prompt for AI AgentsSource: Path instructions |
||
|
|
||
| Option 1 is the tempting one and should be resisted on its own: it would make the hi-res case | ||
| silently accept a 256-wide frame for a scene that must be 512. If interlace is worth covering, option | ||
| 2 is the honest shape. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Describe
HiResEvenas host-specific row handling.The entry says
HiResEvenderives its row step from the observed height. The Rust consumer uses fixed scene rows and does not observe a height-based row step. Mesen2 handles doubled rows in its own host. State that each host applies the declaredHiResEvenrule with its own validated row mapping.🤖 Prompt for AI Agents