Skip to content

docs: interlace needs a rule extract cannot express, and Mesen2 is no longer flaky - #326

Merged
doublegate merged 1 commit into
mainfrom
docs/interlace-needs-per-host-rule
Aug 2, 2026
Merged

docs: interlace needs a rule extract cannot express, and Mesen2 is no longer flaky#326
doublegate merged 1 commit into
mainfrom
docs/interlace-needs-per-host-rule

Conversation

@doublegate

@doublegate doublegate commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Probed and withdrawn. Two findings, and the second is the blocker.

The recorded Mesen2 nondeterminism is gone

The standing note says "Mesen2 alternates between both field parities run-to-run on one build" — which is why the earlier interlace scene was withdrawn. It no longer reproduces. Three consecutive Mesen2 runs and two snes9x runs of an interlace scene produced identical results.

That was almost certainly the emu.setInput defect fixed earlier this cycle. The advice to "run any field-dependent capture twice per host" stays sound as general practice, but it can be retired as the reason interlace is blocked.

The actual blocker: the hosts emit different widths

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, every host applying the same reduction — is insufficient here.

Options, and the one to resist

  1. A generic Downsample that reduces whatever the host emits to 256×224 by deriving both steps from the observed geometry. General, subsumes HiResEven — and gives up the declared-not-inferred property, which is the whole reason extract catches a core rendering 256 wide when it should render 512.
  2. A per-host reduction table: "snes9x: as-is; Mesen2: halve both". Keeps the declaration honest, at the cost of hosts no longer being interchangeable.
  3. Declare expected geometry per rule and per host, rejecting anything else. Strictest, most to maintain.

Option 1 is the tempting one and should be resisted on its own merits. If interlace is worth covering, option 2 is the honest shape.

Docs only; the probe scene is not in the tree. 55 scenes still match on both hosts.

🤖 Generated with Claude Code

Documents that repeated Mesen2 interlace runs are deterministic after the emu.setInput fix. The remaining defect occurs on interlace scenes because snes9x outputs 256x224 and Mesen2 outputs 512x478; one HiResEven extraction rule cannot validate both geometries.

The claim is false if repeated Mesen2 runs become nondeterministic again, or if both hosts produce compatible geometry. ADR 0013 records the alternatives and favors per-host reduction rules over generic downsampling.

No dossier assertions were added or altered. The coverage denominator remains 55 scenes matching on both hosts.

…no longer flaky

Probed and withdrawn. Two findings.

GOOD NEWS: the recorded Mesen2 nondeterminism is gone. Three consecutive
Mesen2 runs and two snes9x runs of an interlace scene produced identical
results, so "Mesen2 alternates between both field parities run-to-run on
one build" no longer reproduces -- almost certainly the emu.setInput
defect fixed earlier. "Run any field-dependent capture twice per host"
can be retired as the REASON interlace is blocked.

THE BLOCKER: the two hosts emit different WIDTHS for the same interlace
scene. snes9x renders a single field at 256x224; Mesen2 doubles both
dimensions to 512x478. Hi-res worked because both emitted 512 wide and
differed only in height, which HiResEven absorbs by deriving the row step
from the observed height. Here no single (xstep, ystep) pair describes
both reductions, so `extract` as designed -- one declared rule per scene,
every host applying the same reduction -- is insufficient.

ADR 0013 records the three options and argues against the tempting one: a
generic "downsample whatever you get to 256x224" rule would subsume
HiResEven but give up the declared-not-inferred property, and would then
silently accept a 256-wide frame for a scene that must be 512. If
interlace is worth covering, a per-host reduction table is the honest
shape.

Docs only; the probe scene is not in the tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 2, 2026 07:37
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The changelog and ADR document that interlace output is deterministic, while snes9x and Mesen2 produce incompatible framebuffer geometries. The ADR rejects generic downsampling and identifies per-host reduction rules as the future design.

Changes

Interlace geometry documentation

Layer / File(s) Summary
Record deterministic results and extraction options
CHANGELOG.md, docs/adr/0013-accuracysnes-framebuffer-oracle.md
The documentation retracts the prior Mesen2 nondeterminism claim, records 256x224 and 512x478 host geometries, and describes extraction-rule alternatives for HiResEven.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Possibly related PRs

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 9 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title uses the valid docs type and describes the documentation change, but the subject is not written in imperative mood. Rewrite the subject in imperative mood, for example: "docs: document interlace geometry limits and deterministic Mesen2 output"
✅ Passed checks (9 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Changelog Entry ✅ Passed The full PR diff against origin/main contains an 18-line entry under CHANGELOG.md's Unreleased section.
Docs-As-Spec ✅ Passed Against origin/main, the PR changes only CHANGELOG.md and an ADR; it changes no crates/** or Cargo files, so it alters no crate behavior.
Accuracysnes Bookkeeping ✅ Passed The merge-base diff changes only CHANGELOG.md and docs/adr/0013-accuracysnes-framebuffer-oracle.md; no test or scene under tests/roms/AccuracySNES/gen/src/ changed.
No Panic On Untrusted Input ✅ Passed The commit changes only CHANGELOG.md and an ADR. It adds no executable code or new .unwrap(), .expect(), or panic!() calls.
Safety Comment On New Unsafe ✅ Passed The patch changes only CHANGELOG.md and an ADR; the exact diff adds no unsafe block or unsafe fn, so no new SAFETY comment is required.

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

Antigravity review (Gemini via Ultra)

This documentation PR updates ADR 0013 and the project changelog to record that Mesen2 frame captures are no longer nondeterministic and to document why current extract rules cannot handle interlace framebuffer dimensions across references.

Blocking issues

None found.

Suggestions

  • CHANGELOG.md:14-31: Logging withdrawn test-harness probes and research notes under ### Fixed misrepresents internal research as a bug fix in the emulator. Move these findings exclusively to docs/adr/0013-accuracysnes-framebuffer-oracle.md or a developer-facing document, and reserve CHANGELOG.md for actual software and toolchain changes.
  • docs/adr/0013-accuracysnes-framebuffer-oracle.md:301-304: Option 2 is recommended as the solution, but the section omits the proposed schema or syntax for per-host reduction rules in scene definitions. Detail how the host-specific rules will be represented before closing the ADR section.

Nitpicks

  • CHANGELOG.md:14: Avoid capitalization for emphasis ("GONE") in documentation and logs.

Automated first-pass review by agy on a self-hosted runner -- not a human review.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Ready to approve

The changes are documentation-only and the added ADR/changelog content is internally consistent and well-formed.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

Updates project documentation around AccuracySNES framebuffer “scenes” extraction rules, clarifying why interlace scenes are still blocked and noting that the previously recorded Mesen2 nondeterminism no longer reproduces.

Changes:

  • Extend ADR 0013 with a new “Interlace” section describing the host-geometry mismatch and why the current extract rule model can’t express it.
  • Add a matching Unreleased changelog entry summarizing the new finding and pointing readers to ADR 0013.
File summaries
File Description
docs/adr/0013-accuracysnes-framebuffer-oracle.md Documents the interlace blocker (host width/height mismatch) and evaluates rule-design options.
CHANGELOG.md Records the updated status of interlace scenes and links the rationale back to ADR 0013.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 21-25: Update the HiResEven description in the changelog to
clarify that it is host-specific row handling: each host applies the declared
rule using its own validated row mapping, rather than implying the Rust consumer
derives the row step from observed height. Preserve the existing explanation of
differing host widths and reductions.

In `@docs/adr/0013-accuracysnes-framebuffer-oracle.md`:
- Around line 296-303: Expand the ADR’s extraction rules into a complete
per-host contract covering Mesen2 and snes9x: define row cropping, FIRST_ROW,
column mapping, sample dimensions, accepted input geometry, and rejection
behavior. Correct Mesen2’s 512x478 reduction to specify how 239 rows become the
required 224, document RustySNES’s current 224/239-row limitation, and reconcile
the snes9x 256x224 entry with the 256x448 geometry in libretro_crossval.c.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b2f3cf6e-910f-434e-b968-1372dbe9ce1b

📥 Commits

Reviewing files that changed from the base of the PR and between 195092c and 3ee973a.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • docs/adr/0013-accuracysnes-framebuffer-oracle.md
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: build demo + docs
🧰 Additional context used
📓 Path-based instructions (8)
docs/**/*.md

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Before changing a subsystem, consult docs/architecture.md, docs/STATUS.md, CONTRIBUTING.md, the relevant subsystem documentation, and applicable ADRs.

New subsystems must add documentation under docs/.

Files:

  • docs/adr/0013-accuracysnes-framebuffer-oracle.md
**/*.{rs,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Chip-behavior changes must update both the chip implementation and the corresponding docs/<subsystem>.md documentation.

A chip change must update both the chip implementation and its corresponding docs/<chip>.md documentation in the same change.

Files:

  • docs/adr/0013-accuracysnes-framebuffer-oracle.md
  • CHANGELOG.md
**/*

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*: Do not commit or vendor the generated snesdev_wiki/ mirror; it is gitignored and intended only as a local reference.
Keep commits focused and use Conventional Commits: <type>(<scope>): <subject>, with an imperative subject of at most 72 characters.
Do not use emojis in code, comments, or commit messages.
Before opening a PR, ensure formatting, Clippy, workspace tests, the core embedded build, rustdoc with warnings denied, documentation coverage, and changelog requirements pass.
Ticket completion must be reflected in the relevant to-dos/ sprint file.

**/*: Preserve the one-directional crate graph: chip crates must not depend on one another; rustysnes-core ties them together.
Never commit commercial ROMs; only commit derived screenshots and hashes.
Keep docs/STATUS.md as the authoritative per-subsystem status and update project documentation in the same PR as code changes.
Do not treat RustyNES v2.0 or engine-lineage anchors as project releases.

Files:

  • docs/adr/0013-accuracysnes-framebuffer-oracle.md
  • CHANGELOG.md
docs/adr/**/*

📄 CodeRabbit inference engine (docs/adr/0004-determinism-contract.md)

docs/adr/**/*: The emulator must produce bit-identical framebuffer and audio for the same seed, ROM, and input sequence.
Power-on CPU, PPU, and SMP phase alignment must come from a seeded PRNG, and reset must preserve that alignment.
Track the SPC700 asynchronous domain using the integer relative-time accumulator defined by ADR 0001; do not use floating-point timing for this purpose.
The core must not use system time, thread scheduling, or OS RNG as sources of nondeterminism.
Use a fixed nominal 1.024 MHz SPC domain in the deterministic path; resonator drift must be excluded. Any hardware-accurate drift toggle belongs in the frontend, is off by default, and must remain outside the bit-identical path.
RTC chips such as S-RTC and SPC7110's RTC-4513 must use fixed or seeded time and must never read the host wall clock.
Rate control and run-ahead must be implemented in the frontend, never in core synthesis; netplay rollback must be frontend-orchestrated against the deterministic core.
Core code must not introduce hidden nondeterminism, including host time, thread scheduling, OS RNG, or HashMap iteration order.

Files:

  • docs/adr/0013-accuracysnes-framebuffer-oracle.md
docs/**/*

📄 CodeRabbit inference engine (docs/testing-strategy.md)

Chip crates should exceed 90% unit-test coverage, and each chip should be fuzzable in isolation.

Files:

  • docs/adr/0013-accuracysnes-framebuffer-oracle.md
docs/**

⚙️ CodeRabbit configuration file

docs/**: Docs are the spec, not a history log. Flag claims that contradict the code, counts that
contradict the generated docs/accuracysnes-coverage.md, and any statement of coverage that
is broader than what the corresponding test actually asserts.

Files:

  • docs/adr/0013-accuracysnes-framebuffer-oracle.md
**/*.md

⚙️ CodeRabbit configuration file

**/*.md: Docs are the spec, not a changelog. Flag prose that has drifted from the code it describes
rather than style nits. The markdownlint gate is pinned to v0.39.0 via pre-commit —
do not report rules that version does not have (MD060 in particular).

Files:

  • docs/adr/0013-accuracysnes-framebuffer-oracle.md
  • CHANGELOG.md
CHANGELOG.md

📄 CodeRabbit inference engine (CONTRIBUTING.md)

User-visible changes must be recorded under the [Unreleased] section.

For the full pull request diff against its base branch, modify CHANGELOG.md when user-visible behavior changes, including emulator output, frontend features, CLI flags, public APIs, or AccuracySNES cartridge contents. Do not require it for purely internal changes, tests, comments, or CI configuration.

Files:

  • CHANGELOG.md

Comment thread CHANGELOG.md
Comment on lines +21 to +25
The actual blocker is that the two hosts emit different **widths** for the same interlace scene:
snes9x `256x224`, Mesen2 `512x478`. Hi-res worked because both emitted 512 wide and differed only
in height, which `HiResEven` absorbs by deriving the row step from the observed height. Here no
single `(xstep, ystep)` pair describes both reductions, so `extract` as designed — one declared
rule per scene, every host applying the same reduction — is insufficient.

Copy link
Copy Markdown

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 HiResEven as host-specific row handling.

The entry says HiResEven derives 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 declared HiResEven rule with its own validated row mapping.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 21 - 25, Update the HiResEven description in the
changelog to clarify that it is host-specific row handling: each host applies
the declared rule using its own validated row mapping, rather than implying the
Rust consumer derives the row step from observed height. Preserve the existing
explanation of differing host widths and reductions.

Comment on lines +296 to +303
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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 -1000

Repository: 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 -120

Repository: 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
PY

Repository: 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
PY

Repository: 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
PY

Repository: doublegate/RustySNES

Length of output: 6024


Define a complete per-host extraction contract.

For Mesen2, 512x478 reduced by two produces 256x239, not 256x224. Specify the row crop, FIRST_ROW, column mapping, sample dimensions, expected geometry, and rejection behavior for each host. RustySNES currently exposes only 224 or 239 rows because interlace field doubling is not implemented. Also reconcile the ADR’s snes9x 256x224 entry with the 256x448 geometry documented in scripts/accuracysnes/libretro_crossval.c. Without this contract, hosts can hash different pixel sets under the same rule.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/adr/0013-accuracysnes-framebuffer-oracle.md` around lines 296 - 303,
Expand the ADR’s extraction rules into a complete per-host contract covering
Mesen2 and snes9x: define row cropping, FIRST_ROW, column mapping, sample
dimensions, accepted input geometry, and rejection behavior. Correct Mesen2’s
512x478 reduction to specify how 239 rows become the required 224, document
RustySNES’s current 224/239-row limitation, and reconcile the snes9x 256x224
entry with the 256x448 geometry in libretro_crossval.c.

Source: Path instructions

@doublegate
doublegate merged commit 24fc120 into main Aug 2, 2026
14 checks passed
@doublegate
doublegate deleted the docs/interlace-needs-per-host-rule branch August 2, 2026 07:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants