Skip to content

feat(gfx): v2.2.8 "Aperture II" — gamma-correct + sharper scanlines - #345

Open
doublegate wants to merge 1 commit into
feat/v2.2.7-timbre-ii-audiofrom
feat/v2.2.8-aperture-ii-video
Open

feat(gfx): v2.2.8 "Aperture II" — gamma-correct + sharper scanlines#345
doublegate wants to merge 1 commit into
feat/v2.2.7-timbre-ii-audiofrom
feat/v2.2.8-aperture-ii-video

Conversation

@doublegate

Copy link
Copy Markdown
Owner

v2.2.8 "Aperture II" — gamma-correct + sharper scanlines

Third of the v2.2.6 → v2.3.0 NESdev-remediation line. Addresses the forum
feedback on gamma-aware resampling and bilinear-soft scanlines.

Stacked on #344 (v2.2.7). Base is feat/v2.2.7-timbre-ii-audio; will retarget
to main once #344 merges.

⚠️ VISUAL VERIFICATION PENDING — please confirm before merge. These are
shader/appearance changes. naga validates that the WGSL compiles and the native +
wasm builds are clean, but it cannot confirm the look. Please check on a real
display (sharper scanlines when the scanline filter is on) and in a browser
(WebGL2 gamma). The shipped native default is byte-identical (new behavior is
gated behind aux = 0), so nothing changes unless scanlines are enabled or you're
on WebGL2.

Presentation-only — core untouched

Nothing here touches emulation, so the pre-shader framebuffer + save-states + every
golden vector are byte-identical (AccuracyCoin 141/141, nestest 0-diff). A prior
investigation established the native path is already gamma-correct (the NES texture
is Rgba8UnormSrgb, so the sampler decodes to linear before filtering + the scanline
math), and the base BLEP is an 81.6 dB-SFDR decimator — so the real gaps were the
WebGL2 non-sRGB path and scanline sharpness, which this targets. The advanced
CRT stacks (royale/guest/megatron) were already gamma-correct and are untouched.

Changed

  • Gamma-correct scanlines + mask (aux.y). The darkening now runs in linear
    light
    . aux.y = 0 (native sRGB) leaves it linear — byte-identical output;
    aux.y = 1 (plain UNORM, e.g. WebGL2) sRGB-decodes on read + re-encodes on output,
    fixing a browser-only gamma error.
  • Sharper scanlines (aux.x, default 0.5). Blends the soft parabola → a narrow
    Gaussian beam for crisp vertical boundaries. aux.x = 0 = the exact pre-v2.2.8
    profile; visible only when scanlines are enabled.
  • Base CRT_WGSL uniform 12 → 16 floats (rect+crop+params+aux); wired on both the
    desktop (crt.rs) and Android (gfx.rs) hosts.

Checks

naga validation green · desktop builds · native + wasm32 clippy -D warnings clean ·
cargo fmt + markdownlint clean · rustynes-frontend tests 464/464 · no golden
snapshots move (CRT output isn't golden-tested). Docs: docs/crt-composite.md +
STATUS/README/AGENTS/CHANGELOG.

🤖 Generated with Claude Code

v2.2.8 "Aperture II" addresses the NESdev-forum feedback on gamma-aware resampling
and bilinear-soft scanlines. It is presentation-only: nothing here touches the
emulation core, so the pre-shader framebuffer, save-states, and every golden vector
are byte-identical (AccuracyCoin 141/141, nestest 0-diff), and the shipped native
default is byte-identical to v2.2.7 (the new behavior is gated behind aux = 0).

The base BLEP audio and the advanced CRT stacks (royale/guest/megatron, already
gamma-correct via their own gamma_in/gamma_out) are untouched. A prior investigation
established that the native path is already gamma-correct (the NES framebuffer texture
is Rgba8UnormSrgb, so the sampler decodes to linear before filtering + the scanline
math, and the surface re-encodes on write) and the base BLEP is an 81.6 dB-SFDR
band-limited decimator -- so the real gaps were the WebGL2 non-sRGB path and scanline
sharpness, which this release targets.

The base CRT/scanline pass (CRT_WGSL) uniform grows from 12 to 16 floats
(rect + crop + params + aux); both hosts already had a 16-float-capable buffer
(Android shares it with the NTSC pass; the desktop CrtFilter is extended here).

Gamma-correct scanlines + aperture mask (aux.y)
- The scanline/mask darkening now happens in linear light. aux.y = 0 (native: sRGB
  texture + surface) leaves the value linear -- byte-identical shipped output.
  aux.y = 1 (a plain UNORM path, e.g. WebGL2, which neither decodes on sample nor
  encodes on write) makes the shader sRGB-decode on read and re-encode before output,
  so a scanline valley is 50% of the LINEAR luminance, not the gamma-encoded value.
  This is a real browser-only gamma fix.

Sharper scanlines (aux.x, default 0.5)
- The scanline profile blends from the original soft parabola (0) to a narrow
  Gaussian beam (1) for crisp vertical row boundaries instead of the linear-sampler
  blur -- the sharper scanlines the feedback asked for. aux.x = 0 reproduces the
  pre-v2.2.8 profile exactly; the effect is only visible when scanlines are enabled.

Hosts
- Desktop (crates/rustynes-frontend/src/crt.rs): CrtFilter's uniform extended to 16
  floats; sharpness (0.5) + linearize (from surface_format.is_srgb()) fields wired in
  new() and the per-frame render().
- Android (crates/rustynes-android/src/gfx.rs): the CRT/scanline filters (1, 2) now
  set aux = (0.5, linearize, 0, 0); linearize derives from the surface format (sRGB
  there, so 0 -- Android output unchanged). NTSC/Bisqwit aux paths untouched.

Verification
- naga validation (crt::tests::shader_parses_and_validates + the CRT stack) green;
  desktop frontend builds; native + wasm32 clippy -D warnings clean; cargo fmt +
  markdownlint clean; rustynes-frontend tests 464/464. The CRT filter output is not
  golden-tested (presentation-only), so no snapshots move.
- VISUAL VERIFICATION PENDING: naga proves the WGSL compiles, not that it looks
  right. The sharper-scanline default and the WebGL2 gamma round-trip must be
  confirmed on a real display + a browser before merge.

Docs: docs/crt-composite.md gains a "gamma + sharpness" section; STATUS/README/AGENTS/
CHANGELOG updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 4, 2026 16:46
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 79278f67-c6b4-49cf-998c-9f24f0b48c7a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Antigravity review (Gemini via Ultra)

This PR updates the CRT shader (CRT_WGSL) to perform scanline and aperture mask darkening in linear light with sRGB gamma conversion for non-sRGB surface targets, introduces a Gaussian scanline sharpness profile, and bumps the crate workspace version to v2.2.8.

Blocking issues

  • crates/rustynes-frontend/src/crt.rs: self.scanline was omitted from the uniform array construction in CrtFilter::update_uniforms. The array contains 15 f32 floats instead of the expected 16 (rect [4] + crop [4] + params [4] + aux [4]). This shifts self.mask into params.x (scanline) and corrupts the remaining uniform struct layout in WGSL, while truncating queue.write_buffer payload from 64 to 60 bytes.

Suggestions

  • crates/rustynes-gfx-shaders/src/lib.rs: pow(rgb, vec3<f32>(2.2)) and pow(rgb, vec3<f32>(1.0 / 2.2)) run on raw texture sample results. If texture filtering yields subtle negative floating-point components, pow invocation results in undefined behavior in WGSL. Guard inputs using max(rgb, vec3<f32>(0.0)) prior to exponentiation.
  • crates/rustynes-frontend/src/crt.rs and crates/rustynes-android/src/gfx.rs: Scanline beam sharpness is hardcoded to 0.5 across frontend implementations. Consider exposing sharpness through user-facing filter settings rather than fixing it to a constant.

Nitpicks

  • crates/rustynes-android/src/gfx.rs: The inline comment details aux.x and aux.y parameters but omits rationale for applying 0.5 sharpness to filters 1 and 2 unconditionally.

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.

Pull request overview

Updates RustyNES to the v2.2.8 “Aperture II” presentation-fidelity release, focusing on gamma-correct scanline/mask darkening and a sharper scanline profile in the shared CRT WGSL shader while keeping emulation/core outputs byte-identical by default.

Changes:

  • Extend the base CRT_WGSL uniform block (12 → 16 floats) and implement gamma linearization (aux.y) plus scanline sharpness blending (aux.x).
  • Wire the new CRT uniform layout and defaults through the desktop frontend (crt.rs) and Android renderer (gfx.rs).
  • Bump release/version metadata across docs and workspace manifests (README/STATUS/CHANGELOG/AGENTS, Cargo.toml/Cargo.lock).

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
README.md Bumps visible version strings and updates “Current Release” narrative to v2.2.8.
docs/STATUS.md Updates the top-of-file “current release” banner to v2.2.8 with presentation details.
docs/crt-composite.md Documents the new base CRT pass aux semantics (gamma + sharpness).
crates/rustynes-gfx-shaders/src/lib.rs Implements the CRT shader’s new aux behavior and expands the uniform layout to 16 floats.
crates/rustynes-frontend/src/crt.rs Updates uniform packing and defaults (sharpness + linearize flag) for the desktop CRT pass.
crates/rustynes-android/src/gfx.rs Updates uniform semantics/packing for Android and sets CRT aux values for filters 1/2.
CHANGELOG.md Adds the v2.2.8 release entry describing the presentation changes.
Cargo.toml Bumps workspace package version to 2.2.8.
Cargo.lock Propagates the 2.2.8 version bump across workspace crates.
AGENTS.md Updates the “Current release” line to v2.2.8.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +29 to +33
/// `params` (x = scanline, y = mask, z = source rows, w unused), and `aux`
/// (v2.2.8 "Aperture II": x = scanline sharpness 0..1, y = linearize flag,
/// z,w unused). Setting `params` to (0,0,0,0) and `crop` to (1,0,1,0) yields a
/// plain letterboxed blit; `aux = (0,0,0,0)` preserves the pre-v2.2.8 look
/// exactly (soft parabola, no explicit gamma round-trip).
Comment thread CHANGELOG.md
Comment on lines +22 to +25
every golden vector are byte-identical (AccuracyCoin 141/141, nestest 0-diff), and
the **shipped native default is byte-identical** to v2.2.7 (the new behavior is
gated behind `aux = 0`). The base BLEP audio and the advanced CRT stacks
(royale/guest/megatron, already gamma-correct) are untouched.
Comment thread AGENTS.md
RustyNES is a cycle-accurate Nintendo Entertainment System emulator written in pure Rust. The accuracy bar is Mesen2 / higan / ares: tight lockstep scheduling at PPU-dot resolution on a master-clock-precise timebase, sub-instruction PPU events visible to subsequent CPU code, and a lookup-table non-linear audio mixer with band-limited synthesis. The frontend is pure Rust (`winit` + `wgpu` + `cpal` + `egui`).

**Current release: v2.2.7 "Timbre II"** (2026-08-04) — an **expansion-audio fidelity** release (2nd of the v2.2.6 → v2.3.0 NESdev-remediation line), driven by a measure-first cross-reference of VRC6 and Sunsoft 5B against 11 reference emulators + the NESdev wiki (Mesen2-only comparison hides where Mesen2 is the outlier). **VRC6 recalibrated to ~1.0× a 2A03 pulse** (`VRC6_MIX_SCALE` 979 → 650; the NESdev/field consensus — rustico/tetanes/BizHawk encode 1.0× exactly; Mesen2's louder ~1.506× mixer weighting was the outlier a reviewer flagged; `db_vrc6a/b` oracle 1.506 → 1.0), and the **Sunsoft 5B envelope moved to the exact 5-bit 1.5 dB/step DAC** (`SUNSOFT5B_LOG_VOL32`, matching nestopia/rustico, replacing the 4-bit 3 dB approximation). **Expansion-only — base 2A03 byte-identical**, so **AccuracyCoin holds 141/141 (100.00%)** and nestest is 0-diff; the base BLEP is a verified 81.6 dB-SFDR band-limited decimator. Built on **v2.2.6 "Almanac"** (2026-08-04) — a **de-monetization + provenance** release opening the **v2.2.6 → v2.3.0** NESdev-remediation line. **RustyNES is permanently open-source and income-free (ADR 0035)**: all planned monetization is removed (the `rustynes-monetization` crate, `docs/monetization/`, and the Android/iOS billing / ad / freemium / paywall layers deleted) and the native apps are kept as **free FOSS apps** (no ads, no tracking, no paid unlock; the free Google-Play services + `foss`/`play` split retained). It also discloses (ADR 0030) that the PPU hybrid-address *timing* was calibrated to TriCNES (reproducing the Rad Racer mis-render), flagged for a documentation-derived rework in v2.3.0. **Zero emulation-core behavior changes**, so **AccuracyCoin holds 141/141 (100.00%)** and nestest is 0-diff by construction. On top of **v2.2.5 "Colophon"** (2026-08-03) — a **provenance, licensing, and documentation-integrity** release with **zero emulation-core behavior changes** (so **AccuracyCoin holds 141/141 (100.00%)** and nestest is 0-diff by construction). It reworded in-source comments that had mischaracterized publicly-documented hardware-behavior implementations as "ports of" copyleft emulators (Mesen2 GPLv3, puNES GPLv2) into the accurate oracle framing; rewrote `NOTICE` to disclose the behavioral-oracle use of GPL emulators (Mesen2/MesenCE, higan, GeraNES, ares, FCEUX, Nestopia, puNES — no code incorporated) and to attribute the genuinely-incorporated permissive components (emu2413, TriCNES, rcheevos — all MIT), the bundled fonts and test ROMs, and the CRT-shader/NTSC-filter visual influences as independent reimplementations; disclosed **GeraNES (GPL-3.0-only)** as an oracle; added `docs/originality-and-provenance.md`; and added an AI-assistance disclosure to the README (removing a misleading comparison graphic and fixing a mislabeled screenshot caption). On top of **v2.2.4 "Cartridge"** (2026-07-24) — a **libretro / RetroArch distribution** cut whose purpose is that the RustyNES core **builds and installs cleanly through the Libretro buildbot** (<https://git.libretro.com/libretro/RustyNES>) for in-RetroArch use. **Zero emulation-core changes** — the deterministic `#![no_std]` chip stack, save-state / TAS / netplay formats, and every golden vector are byte-identical to v2.2.3, so **AccuracyCoin holds 141/141 (100.00%)** and nestest is 0-diff by construction. `crates/rustynes-libretro` wraps `rustynes-core`, so it inherits every v2.2.3 change automatically (the fast-dot-path default; the `PPU_SNAPSHOT_VERSION` 8 / APU v4 save-state schema, transparent because `get_serialize_size` / `on_serialize` size and emit the *current* snapshot via `Nes::snapshot_core_into` rather than a fixed layout; the `Mapper::mix_audio` i32 widening; the Zapper model; the `mNNN_` mapper rename), and both buildbot cross-ABIs the CI early-warning gate models — `x86_64-pc-windows-gnu` and `aarch64-linux-android` — `cargo check --release -p rustynes-libretro` clean. The concrete change is a **`rustynes_libretro.info` metadata correction**: **`disk_control` `false` → `true`** (the real fix — the FDS multi-side Disk Control interface has been wired since the buildbot recipe landed but was advertised as absent, hiding multi-disk FDS swapping from RetroArch's Quick Menu), `display_version` `v1.0.0` → `v2.2.4`, and the description mapper count `168` → `172`. Libretro **core options** (region / overscan / palette / accuracy toggles) remain unexposed — `core_options = "false"` is accurate, a documented future enhancement rather than a v2.2.4 gap. The Antigravity PR reviewer standardization onto the shared template rides along. On top of **v2.2.3 "Datum"** (2026-07-23) — a **performance and accuracy-closure patch**, the product of a measure-first appraisal that profiled the emulator and acted on what the profile showed rather than on intuition. **Performance:** the specialized PPU fast dot path is promoted to the **default** and exposed to users for the first time — `Nes::set_fast_dotloop` had **no caller outside the core**, so a **−11.3%** frame-time win (fresh clean-host Criterion, reproducing v2.1.8's +12.3% by a different method; differential-tested bit-identical every frame since v2.1.8) shipped switched off and unreachable; release builds now ship **PGO-optimized** Linux binaries when the existing >3%-and-byte-identical gate passes; and CI gained a same-runner **relative** frame-time regression gate, closing a hole where a 2.5x slowdown passed the deliberately-loose absolute ceiling. **Two optimizations were measured and REJECTED** and are documented with their numbers per `docs/performance.md`'s convention — P3 (`emit_pixel` bounds-check elision) made the shipped default *slower* (+4.32% / +3.35% on the `_fast` workloads, p ≤ 0.02), and P4 (`cpu_clock`) found both textbook optimizations already implemented with the one remaining lever capped at **≤1.9%**. **Accuracy:** the **last two Holy Mapperel residuals are closed**, so all 17 ROMs report `detail=0000` (was 15/17) — MMC1's two software WRAM write-protect layers (`$E000` bit 4 + SNROM's CHR-register layer, gated on `chr_is_ram`) and FME-7's open bus on the RAM-selected-but-disabled window, both routed through the trait's existing `cpu_read_unmapped` contract. MMC1 is the change Holy Mapperel's README calls a game-compatibility hazard (FCEUX / PowerPak omit it), so it was validated before landing: **60/60** commercial ROMs including seven battery-backed MMC1 saves, plus **138/138** extended. The **Sunsoft 5B absolute level** is calibrated against Mesen2, which required widening `Mapper::mix_audio` to `i32` (the correct full-scale 5B tone `1882 * 18.471 = 34,761` does not fit `i16`). A **save-state schema gap** is fixed — `PPU_SNAPSHOT_VERSION` **8** carrying the sprite-eval FSM + OAM data-bus state, plus an APU **v4** tail — which is what made AccuracyCoin report **141/141 through run-ahead** as well as without it; a new standing field-vs-schema audit found it and the two APU gaps mechanically. A **Zapper beam-relative light model** lands opt-in / default-off (no pass-fail light-gun ROM exists to adjudicate it). **AccuracyCoin holds 141/141 (100.00%)**, nestest 0-diff. Also: the eleven `sprintN.rs` mapper modules (27,631 lines, ~110 boards) are renamed for the boards they emulate with `mNNN_` mapper-number prefixes, proven content-preserving by a byte-for-byte item comparison (930 items, 0 altered) and an identical 172-ID dispatch table.
**Current release: v2.2.8 "Aperture II"** (2026-08-04, a presentation-fidelity release — gamma-correct scanlines in linear light + a WebGL2 gamma fix + a sharper Gaussian scanline profile in the base `CRT_WGSL`; presentation-only, so the pre-shader framebuffer + AccuracyCoin 141/141 are byte-identical and the shipped native default is unchanged [gated behind `aux = 0`]; the shader/appearance changes await on-display + browser visual verification), on top of **v2.2.7 "Timbre II"** (2026-08-04) — an **expansion-audio fidelity** release (of the v2.2.6 → v2.3.0 NESdev-remediation line), driven by a measure-first cross-reference of VRC6 and Sunsoft 5B against 11 reference emulators + the NESdev wiki (Mesen2-only comparison hides where Mesen2 is the outlier). **VRC6 recalibrated to ~1.0× a 2A03 pulse** (`VRC6_MIX_SCALE` 979 → 650; the NESdev/field consensus — rustico/tetanes/BizHawk encode 1.0× exactly; Mesen2's louder ~1.506× mixer weighting was the outlier a reviewer flagged; `db_vrc6a/b` oracle 1.506 → 1.0), and the **Sunsoft 5B envelope moved to the exact 5-bit 1.5 dB/step DAC** (`SUNSOFT5B_LOG_VOL32`, matching nestopia/rustico, replacing the 4-bit 3 dB approximation). **Expansion-only — base 2A03 byte-identical**, so **AccuracyCoin holds 141/141 (100.00%)** and nestest is 0-diff; the base BLEP is a verified 81.6 dB-SFDR band-limited decimator. Built on **v2.2.6 "Almanac"** (2026-08-04) — a **de-monetization + provenance** release opening the **v2.2.6 → v2.3.0** NESdev-remediation line. **RustyNES is permanently open-source and income-free (ADR 0035)**: all planned monetization is removed (the `rustynes-monetization` crate, `docs/monetization/`, and the Android/iOS billing / ad / freemium / paywall layers deleted) and the native apps are kept as **free FOSS apps** (no ads, no tracking, no paid unlock; the free Google-Play services + `foss`/`play` split retained). It also discloses (ADR 0030) that the PPU hybrid-address *timing* was calibrated to TriCNES (reproducing the Rad Racer mis-render), flagged for a documentation-derived rework in v2.3.0. **Zero emulation-core behavior changes**, so **AccuracyCoin holds 141/141 (100.00%)** and nestest is 0-diff by construction. On top of **v2.2.5 "Colophon"** (2026-08-03) — a **provenance, licensing, and documentation-integrity** release with **zero emulation-core behavior changes** (so **AccuracyCoin holds 141/141 (100.00%)** and nestest is 0-diff by construction). It reworded in-source comments that had mischaracterized publicly-documented hardware-behavior implementations as "ports of" copyleft emulators (Mesen2 GPLv3, puNES GPLv2) into the accurate oracle framing; rewrote `NOTICE` to disclose the behavioral-oracle use of GPL emulators (Mesen2/MesenCE, higan, GeraNES, ares, FCEUX, Nestopia, puNES — no code incorporated) and to attribute the genuinely-incorporated permissive components (emu2413, TriCNES, rcheevos — all MIT), the bundled fonts and test ROMs, and the CRT-shader/NTSC-filter visual influences as independent reimplementations; disclosed **GeraNES (GPL-3.0-only)** as an oracle; added `docs/originality-and-provenance.md`; and added an AI-assistance disclosure to the README (removing a misleading comparison graphic and fixing a mislabeled screenshot caption). On top of **v2.2.4 "Cartridge"** (2026-07-24) — a **libretro / RetroArch distribution** cut whose purpose is that the RustyNES core **builds and installs cleanly through the Libretro buildbot** (<https://git.libretro.com/libretro/RustyNES>) for in-RetroArch use. **Zero emulation-core changes** — the deterministic `#![no_std]` chip stack, save-state / TAS / netplay formats, and every golden vector are byte-identical to v2.2.3, so **AccuracyCoin holds 141/141 (100.00%)** and nestest is 0-diff by construction. `crates/rustynes-libretro` wraps `rustynes-core`, so it inherits every v2.2.3 change automatically (the fast-dot-path default; the `PPU_SNAPSHOT_VERSION` 8 / APU v4 save-state schema, transparent because `get_serialize_size` / `on_serialize` size and emit the *current* snapshot via `Nes::snapshot_core_into` rather than a fixed layout; the `Mapper::mix_audio` i32 widening; the Zapper model; the `mNNN_` mapper rename), and both buildbot cross-ABIs the CI early-warning gate models — `x86_64-pc-windows-gnu` and `aarch64-linux-android` — `cargo check --release -p rustynes-libretro` clean. The concrete change is a **`rustynes_libretro.info` metadata correction**: **`disk_control` `false` → `true`** (the real fix — the FDS multi-side Disk Control interface has been wired since the buildbot recipe landed but was advertised as absent, hiding multi-disk FDS swapping from RetroArch's Quick Menu), `display_version` `v1.0.0` → `v2.2.4`, and the description mapper count `168` → `172`. Libretro **core options** (region / overscan / palette / accuracy toggles) remain unexposed — `core_options = "false"` is accurate, a documented future enhancement rather than a v2.2.4 gap. The Antigravity PR reviewer standardization onto the shared template rides along. On top of **v2.2.3 "Datum"** (2026-07-23) — a **performance and accuracy-closure patch**, the product of a measure-first appraisal that profiled the emulator and acted on what the profile showed rather than on intuition. **Performance:** the specialized PPU fast dot path is promoted to the **default** and exposed to users for the first time — `Nes::set_fast_dotloop` had **no caller outside the core**, so a **−11.3%** frame-time win (fresh clean-host Criterion, reproducing v2.1.8's +12.3% by a different method; differential-tested bit-identical every frame since v2.1.8) shipped switched off and unreachable; release builds now ship **PGO-optimized** Linux binaries when the existing >3%-and-byte-identical gate passes; and CI gained a same-runner **relative** frame-time regression gate, closing a hole where a 2.5x slowdown passed the deliberately-loose absolute ceiling. **Two optimizations were measured and REJECTED** and are documented with their numbers per `docs/performance.md`'s convention — P3 (`emit_pixel` bounds-check elision) made the shipped default *slower* (+4.32% / +3.35% on the `_fast` workloads, p ≤ 0.02), and P4 (`cpu_clock`) found both textbook optimizations already implemented with the one remaining lever capped at **≤1.9%**. **Accuracy:** the **last two Holy Mapperel residuals are closed**, so all 17 ROMs report `detail=0000` (was 15/17) — MMC1's two software WRAM write-protect layers (`$E000` bit 4 + SNROM's CHR-register layer, gated on `chr_is_ram`) and FME-7's open bus on the RAM-selected-but-disabled window, both routed through the trait's existing `cpu_read_unmapped` contract. MMC1 is the change Holy Mapperel's README calls a game-compatibility hazard (FCEUX / PowerPak omit it), so it was validated before landing: **60/60** commercial ROMs including seven battery-backed MMC1 saves, plus **138/138** extended. The **Sunsoft 5B absolute level** is calibrated against Mesen2, which required widening `Mapper::mix_audio` to `i32` (the correct full-scale 5B tone `1882 * 18.471 = 34,761` does not fit `i16`). A **save-state schema gap** is fixed — `PPU_SNAPSHOT_VERSION` **8** carrying the sprite-eval FSM + OAM data-bus state, plus an APU **v4** tail — which is what made AccuracyCoin report **141/141 through run-ahead** as well as without it; a new standing field-vs-schema audit found it and the two APU gaps mechanically. A **Zapper beam-relative light model** lands opt-in / default-off (no pass-fail light-gun ROM exists to adjudicate it). **AccuracyCoin holds 141/141 (100.00%)**, nestest 0-diff. Also: the eleven `sprintN.rs` mapper modules (27,631 lines, ~110 boards) are renamed for the boards they emulate with `mNNN_` mapper-number prefixes, proven content-preserving by a byte-for-byte item comparison (930 items, 0 altered) and an identical 172-ID dispatch table.
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