feat(gfx): v2.2.8 "Aperture II" — gamma-correct + sharper scanlines - #345
Conversation
|
Warning Review limit reached
Next review available in: 42 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughVersion 2.2.8 adds gamma-aware CRT scanline processing and configurable sharpness. Frontend, Android, and iOS hosts upload the new shader parameters. Release metadata and documentation identify v2.2.8 “Aperture II.” ChangesCRT presentation update
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant CrtFilter
participant AndroidGfx
participant IOSMetal
participant CRT_WGSL
CrtFilter->>CRT_WGSL: upload sharpness and linearization values
AndroidGfx->>CRT_WGSL: upload CRT auxiliary values
IOSMetal->>CRT_WGSL: upload CRT auxiliary values
CRT_WGSL->>CRT_WGSL: decode, process, and encode CRT colors
🚥 Pre-merge checks | ✅ 8 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (8 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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_WGSLuniform 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.
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>
…ilot #345) Address the three Copilot review threads on #345 ("Aperture II"): - **iOS Metal renderer parity (the substantive one).** `write_uniforms` in `crates/rustynes-ios/src/gfx_metal.rs` hard-wrote `aux = 0` for every filter, so the scanline (1) and CRT (2) filters on iOS never received the v2.2.8 `aux.x` scanline-sharpness or `aux.y` gamma-linearize knobs that the shared `CRT_WGSL` now consumes — iOS silently kept the old soft-parabola, gamma- encoded scanlines while desktop (`crt.rs`) and Android (`gfx.rs`) got the corrected path. It now computes `crt_linearize` from the surface's sRGB-ness (0 on the native sRGB Metal surface, 1 on a non-sRGB surface) and passes `aux = [0.5, crt_linearize, 0, 0]` for filters 1/2, byte-for-byte matching the Android host. On the native sRGB surface `crt_linearize == 0`, so the Metal CRT/scanline output stays byte-identical to pre-v2.2.8 — this closes a cross-platform-consistency gap, not a behavior change on the shipped default. - **CHANGELOG + AGENTS wording (backwards phrasing).** "the new behavior is gated behind `aux = 0`" read the flag inside-out: `aux = 0` is the *compatibility* path (the exact pre-v2.2.8 scanline profile), and the new linear-light + sharper-scanline path is what activates for a *non-zero* `aux` (the WebGL2 non-sRGB path, or when the scanline knob is raised). Both the CHANGELOG entry and the AGENTS.md current-release note now say so. Verification: `rustfmt --edition 2024 --check` clean; `cargo check -p rustynes-ios` clean on Linux (the uniform-writing path is not Metal-gated, so it type-checks off-device); `pre-commit run markdownlint` passes on the two docs. Presentation-only — the emulation core, AccuracyCoin 141/141, and nestest 0-diff are untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6240145 to
3badffd
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@AGENTS.md`:
- Line 30: Update the v2.2.4 history entry in AGENTS.md to remove its stale
“current release” wording, replacing it with “the v2.2.4 release” or equivalent.
Preserve the v2.2.8 entry as the sole current-release record and leave the
historical release details unchanged.
In `@CHANGELOG.md`:
- Around line 47-49: Update the v2.2.8 release entry to document the iOS Metal
host wiring alongside the existing desktop and Android hosts, referencing the
shared 16-float CRT uniform; if iOS is intentionally excluded, explicitly state
the reason instead.
In `@crates/rustynes-gfx-shaders/src/lib.rs`:
- Around line 102-105: Replace the power-gamma conversion in the shader’s
linearize path at crates/rustynes-gfx-shaders/src/lib.rs lines 102-105 with the
sRGB EOTF using the 0.04045 breakpoint, and update the inverse conversion at
lines 149-152 to the sRGB OETF using the 0.0031308 breakpoint. Align the
corresponding transfer-function documentation at docs/crt-composite.md lines
55-61 with these piecewise formulas.
🪄 Autofix
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: 75093a99-2f9a-4c6b-895b-82bd0281106e
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock,!Cargo.lock
📒 Files selected for processing (10)
AGENTS.mdCHANGELOG.mdCargo.tomlREADME.mdcrates/rustynes-android/src/gfx.rscrates/rustynes-frontend/src/crt.rscrates/rustynes-gfx-shaders/src/lib.rscrates/rustynes-ios/src/gfx_metal.rsdocs/STATUS.mddocs/crt-composite.md
…bbit #345) Address CodeRabbit's re-review of the iOS-aux commit — three findings: - **Major (functional correctness): use the exact sRGB piecewise transfer, not pow(2.2).** The `aux.y = 1` WebGL2 round-trip is meant to reproduce what the native path gets for free from a hardware sRGB texture/surface. But that hardware applies the IEC 61966-2-1 piecewise curve (a linear segment below `0.04045` / `0.0031308`, a 2.4-exponent power segment above), whereas the shader linearized with `pow(rgb, 2.2)` / re-encoded with `pow(rgb, 1/2.2)` — a plain power gamma that diverges from true sRGB most in the shadows. That made the WebGL2 result subtly *disagree* with the native sRGB path it exists to match. `CRT_WGSL` now carries `srgb_to_linear` / `linear_to_srgb` helpers implementing the exact transfer (component-wise `select` on the breakpoint), and the two `pow` sites call them. Presentation-only and native-path-inert: the native surface sets `aux.y = 0`, so this branch never runs there and the shipped native default stays byte-identical; only the WebGL2 appearance changes, now correctly. naga validation (`crt::tests::shader_parses_and_validates`) green. - **iOS host in the CHANGELOG.** The v2.2.8 "Changed" host-wiring sentence now lists the iOS Metal host alongside desktop and Android (the parity the prior commit added), and the gamma bullet documents the exact-sRGB transfer. - **Stale "current release" label.** The v2.2.4 entry in the AGENTS.md lineage paragraph still called v2.2.4 "the current release"; reworded to point at the actual current-release paragraph, so AGENTS.md carries one current-release record. `docs/crt-composite.md` aligned to the exact-transfer description. rustfmt + markdownlint clean; the emulation core, AccuracyCoin 141/141, and nestest 0-diff are untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Antigravity review (Gemini via Ultra)This PR bumps the crate workspace to v2.2.8 "Aperture II" and updates the CRT presentation pipeline to perform scanline and aperture-mask darkening in linear light with a selectable Gaussian beam sharpness profile. Blocking issuesNone found. Suggestions
Nitpicks
Automated first-pass review by |
…ilot #346) Copilot flagged a real UX regression in the v2.2.9 detachable-window conversion: routing every tool panel through the shared `detachable_window` helper dropped each panel's bespoke `egui::Window` builder options — `default_pos`, `default_size`, `default_width` / `min_width`, and `resizable(false)` on ROM Info / Input Display / ROM Database / Performance. Losing the `default_pos` values in particular collapsed the debugger's designed workspace layout into egui's default overlap cascade on first open, and four fixed-size panels silently became resizable. `detachable_window` now takes a `WindowCfg { default_pos, default_size, default_width, min_width, resizable }` (all `Option`, `Copy + Default`) and applies each set field to the docked `egui::Window`; all 19 call sites (18 panels; `cheat_panel` has a native + a wasm variant) pass back their exact prior values, so first-open placement/size and the four non-resizable panels are restored. The config applies to the docked form only — a detached panel is a real OS window the window manager sizes and places (egui persists the docked window's own position/size by id after first open, so `WindowCfg` only seeds the first appearance). Native + wasm32 `clippy -D warnings` clean on both feature sets. Also (proactive, matching the CodeRabbit finding already fixed on #345): the v2.2.4 entry in the AGENTS.md lineage paragraph still called v2.2.4 "the current release" — reworded to point at the actual current-release paragraph so AGENTS.md carries a single current-release record. Frontend-only; the emulation core, AccuracyCoin 141/141, and nestest 0-diff are untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ilot #346) Copilot flagged a real UX regression in the v2.2.9 detachable-window conversion: routing every tool panel through the shared `detachable_window` helper dropped each panel's bespoke `egui::Window` builder options — `default_pos`, `default_size`, `default_width` / `min_width`, and `resizable(false)` on ROM Info / Input Display / ROM Database / Performance. Losing the `default_pos` values in particular collapsed the debugger's designed workspace layout into egui's default overlap cascade on first open, and four fixed-size panels silently became resizable. `detachable_window` now takes a `WindowCfg { default_pos, default_size, default_width, min_width, resizable }` (all `Option`, `Copy + Default`) and applies each set field to the docked `egui::Window`; all 19 call sites (18 panels; `cheat_panel` has a native + a wasm variant) pass back their exact prior values, so first-open placement/size and the four non-resizable panels are restored. The config applies to the docked form only — a detached panel is a real OS window the window manager sizes and places (egui persists the docked window's own position/size by id after first open, so `WindowCfg` only seeds the first appearance). Native + wasm32 `clippy -D warnings` clean on both feature sets. Also (proactive, matching the CodeRabbit finding already fixed on #345): the v2.2.4 entry in the AGENTS.md lineage paragraph still called v2.2.4 "the current release" — reworded to point at the actual current-release paragraph so AGENTS.md carries a single current-release record. Frontend-only; the emulation core, AccuracyCoin 141/141, and nestest 0-diff are untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… tool windows (#346) * fix(tas): wire TAStudio piano-roll edits to the emulator + robust .bk2 import (v2.2.9) Two of the v2.2.9 'Studio II' items, both objectively verified: TAStudio inputs now drive the emulator. handle_tas_requests (the piano-roll panel path) only mutated the editor's input_log on a SetInput and never re-derived the running Nes, so a cell edit looked disconnected from emulation (the NESdev-forum 'TAStudio inputs do not seem to be connected up' report). It now tracks an input_dirty flag across the batch and does a single deterministic re-seek to the cursor afterward, exactly like the scripting path (apply_tas_commands). InsertFrame / DeleteFrame / StampMacro also mark dirty; Seek / CreateBranch / LoadBranch reseat the Nes themselves. .bk2 import honors the LogKey column order. The parser ignored the LogKey: line and mapped pad columns by fixed U D L R S s B A position, so a BizHawk movie authored with a different column order or extra columns mapped every button to the wrong bit ('.bk2 did not play back'). parse_log_key now reads the per-port column order from the LogKey (# groups, | columns), maps each column by its button name (ignoring the 'Pn ' prefix), and falls back to the standard order when a group is truncated/exotic (preserving the existing tests). parse_pad maps by that column list and tolerates a group LONGER than the modeled columns (extra buttons like a mic are ignored). A new test proves a non-standard order + an extra column. .bk2 import feedback is on-screen. handle_movie_import surfaced every outcome via eprintln! to a terminal nobody sees (so a failed import looked like nothing happened). It now sets the on-screen status line for each path (no ROM, parse error, wrong-ROM seek failure, success) via StatusMessage. Consolidated the file's nine per-function StatusMessage imports into one module-level use. Verification: bk2 tests 7/7 (incl. the new order test), rustynes-frontend 464/464, core no_std cross-compile clean, clippy -D warnings + fmt clean on both crates. Remaining v2.2.9 item: floating tool windows. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(ui): detachable/floating tool windows (v2.2.9 "Studio II") Addresses the NESdev-forum report that tool windows are trapped inside the main OS window on Windows 10: every debugger/tool panel used `egui::Window::new(...)` inside the single central viewport, so it could never leave the host window. Adds a shared `detachable_window` helper in `debugger/mod.rs` that gives each panel a "⧉ Detach" button. Detached, the panel renders in a real OS window via `ctx.show_viewport_immediate` (the same egui multi-viewport mechanism `basic_bot_panel` already used) with a "⧉ Reattach" button; the OS window's close button reattaches too. A `DebuggerOverlay::detached_panels: HashSet<&'static str>` (keyed by each panel's stable id) tracks which panels are floating across frames. **Native-only by construction.** egui multi-viewport needs winit multi-window, absent on wasm, so the detached branch and the Detach button are `#[cfg(not(target_arch = "wasm32"))]`; on wasm the panel always renders docked in an `egui::Window`, unchanged. The helper carries a wasm-scoped `allow(clippy::needless_pass_by_ref_mut)` plus a `let _ = (&detached, id)` discard so both the rustc `unused_variables` and clippy `needless_pass_by_ref_mut` lints stay green there without desyncing the native signature (verified: `cargo clippy -p rustynes-frontend --target wasm32-unknown-unknown --lib --bins` clean for both the default and `wasm-canvas` feature sets). 17 panels are routed through the helper (PPU, OAM, APU, Memory, Event Viewer, NSF, Mapper, Watch, Trace, Cheats [native + wasm cfg variants], ROM Database, Performance, Documentation, Input Display, Audio Mixer, Replay/TAS, Memory Compare, ROM Info), each dropping its bespoke `.resizable()/.default_pos()/ .default_size()/.min_width()` builder options for the shared affordance. Panels whose `show()` returns a value (`cpu_panel`) or that already own multi-window / config-heavy bodies (settings, netplay, cheevos, input-rebind, tastudio, basic_bot) are intentionally left for a follow-up. Frontend-only — the deterministic core, save-states, and every golden vector are untouched (AccuracyCoin 141/141, nestest 0-diff). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(release): bump to v2.2.9 "Studio II" + docs Version bump 2.2.8 → 2.2.9 (workspace `version`, Cargo.lock) and the docs-as-spec sync for the "Studio II" release — the fourth step of the v2.2.6 → v2.3.0 NESdev-remediation line, capping the TAStudio-wiring, `.bk2` playback, and detachable-tool-window work committed earlier on this branch. - **CHANGELOG.md** — new `[2.2.9]` section (Fixed: TAStudio piano-roll edits now drive the emulator, `.bk2` playback honors the `LogKey` column order; Added: detachable/floating tool windows across 17 panels, native-only). - **docs/STATUS.md** (single source of truth) — current-release lead reset to v2.2.9, demoting v2.2.8 to "Built on". - **README.md** — Current Release lead updated to v2.2.9. - **AGENTS.md** — both the top current-release block and the operating-note paragraph lead with v2.2.9; the "never claim a version later than …" guard and the v2.2.6 → v2.3.0 line-summary bump to mark v2.2.8/v2.2.9 shipped. - **docs/frontend.md** — detachable multi-viewport tool windows moved out of the Deferred list into shipped (v2.2.9), with the `detachable_window` / `show_viewport_immediate` mechanism noted. Frontend-only across the whole release, so the deterministic core, save-states, and every golden vector are byte-identical: **AccuracyCoin 141/141**, nestest 0-diff. The detached-window behavior itself awaits an on-device (ideally Windows-10) visual check; the mechanism compiles + clippy-passes on native and both wasm feature sets. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ui): preserve per-panel window geometry in detachable_window (Copilot #346) Copilot flagged a real UX regression in the v2.2.9 detachable-window conversion: routing every tool panel through the shared `detachable_window` helper dropped each panel's bespoke `egui::Window` builder options — `default_pos`, `default_size`, `default_width` / `min_width`, and `resizable(false)` on ROM Info / Input Display / ROM Database / Performance. Losing the `default_pos` values in particular collapsed the debugger's designed workspace layout into egui's default overlap cascade on first open, and four fixed-size panels silently became resizable. `detachable_window` now takes a `WindowCfg { default_pos, default_size, default_width, min_width, resizable }` (all `Option`, `Copy + Default`) and applies each set field to the docked `egui::Window`; all 19 call sites (18 panels; `cheat_panel` has a native + a wasm variant) pass back their exact prior values, so first-open placement/size and the four non-resizable panels are restored. The config applies to the docked form only — a detached panel is a real OS window the window manager sizes and places (egui persists the docked window's own position/size by id after first open, so `WindowCfg` only seeds the first appearance). Native + wasm32 `clippy -D warnings` clean on both feature sets. Also (proactive, matching the CodeRabbit finding already fixed on #345): the v2.2.4 entry in the AGENTS.md lineage paragraph still called v2.2.4 "the current release" — reworded to point at the actual current-release paragraph so AGENTS.md carries a single current-release record. Frontend-only; the emulation core, AccuracyCoin 141/141, and nestest 0-diff are untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * license: relicense to GPL-3.0-or-later — RustyNES is a derivative work of GPL emulators RustyNES incorporates and is derived from code from GPL-licensed NES emulators. It is therefore a derivative work distributable only under the GPL, and this commit relicenses it from `MIT OR Apache-2.0` to **GPL-3.0-or-later**, credits the derived-from sources per subsystem, and withdraws the incorrect "no GPL source incorporated" position taken in v2.2.5 "Colophon". Context. A NESdev community review found that the codebase contains bugs, constants, variable names, code ordering, and comments referencing specific upstream files, functions, and line numbers that go well beyond using an emulator as a testing oracle. That is correct. The project's own in-source comments, before a v2.2.5 edit reworded them, said as much: "Faithful port of Mesen2's `ProcessSpriteEvaluation` (`NesPpu.cpp:1015-1141`)", "Ported bit-for-bit from puNES `JV001.c`", "numeric tables ported verbatim from Bisqwit's C", and ~12 "Ported from Mesen2 `<file>.h`" mapper comments. v2.2.5 reframed that code as "oracle cross-checks" and kept a permissive license the combined work was not entitled to use. Laundering GPL code through AI tooling does not change its license, and responsibility for what landed in the tree rests with the project. Derived-from sources and their licenses (full file-by-file table in docs/originality-and-provenance.md Section 1): - Mesen2 (GPL-3.0-or-later): CPU unstable-store opcodes; the PPU sprite-evaluation FSM + OAM-data-bus model; ~15 mapper boards (Bandai EEPROM, JY Company, Waixing, Sachen, Txc, NTDEC, Kaiser, MMC3 variants, FK23C, CoolBoy); the Bisqwit NTSC filter tables; the UNIF tables; the debug-symbol importer; the PGO harness. - puNES (GPL-2.0-or-later): JV001 / mapper 147 (bit-for-bit); the FDS per-CRC drive-timing table. - FCEUX (GPL-2.0-or-later): UNIF handling; some mapper banking. - Nestopia UE (GPL-2.0-or-later): FME-7 / 5B audio detail. Every upstream grants "or (at your option) any later version", so the GPL-2.0-or-later material upgrades to v3 and GPL-3.0-or-later is the correct, consistent expression for the combined work. GeraNES (GPL-3.0-only) was used as an oracle only, with no code derived, so it does not force `-only`. Changes: - LICENSE is now the GPLv3 text; LICENSE-MIT and LICENSE-APACHE are removed; the workspace + rustynes-cheevos `license` fields become GPL-3.0-or-later; deny.toml allows GPL-3.0-or-later for the project's own crates (cargo-deny `check licenses` = ok); release.yml packages LICENSE instead of the two removed files. - docs/originality-and-provenance.md is rewritten to lead with the derivation table and the derivative-work declaration; NOTICE attributes each GPL upstream and the code derived from it; README, AGENTS, CONTRIBUTING, SUPPORT, ROADMAP, the in-app About/CLI/doc-panel strings, the Android about_body (EN + ES), and the libretro `.info` license field all state GPL-3.0-or-later. - New ADR 0036 records the decision, the SPDX rationale, and the GPLv3/App-Store distribution caveat. The scattered "port of" comments are deliberately NOT restored (they were imprecise; the audited derivation table supersedes them), but the derivation is now stated plainly and completely. - Incorporated permissive components (emu2413/MIT, TriCNES/MIT, rcheevos/MIT, blip_buf/LGPL-2.1-or-later, fonts) are GPL-compatible and keep their notices. Zero emulation-core behavior change: AccuracyCoin holds 141/141 and nestest is 0-diff by construction. This is a licensing and documentation correction. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: bump README version badge to v2.2.9 Carry-over fix: the version badge still read v2.2.8 after the v2.2.9 doc bump. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(provenance): add GPLv3 SPDX + per-file provenance headers to derived source Follows the v2.2.9 relicense: now that the accurate license and attribution are established, mark the derived source itself. Each of the 23 files that contains code derived from a GPL emulator gains a top-of-file header: // SPDX-License-Identifier: GPL-3.0-or-later // // Provenance: <what is derived, from which upstream file/function>. See // docs/originality-and-provenance.md (Section 1) and NOTICE ... so the license and the specific upstream are discoverable at the point of use — e.g. `rustynes-ppu/src/ppu.rs` names Mesen2 `NesPpu.cpp` (`ProcessSpriteEvaluation` / `ReadSpriteRam`) plus the TriCNES (MIT) octal-latch model; `rustynes-mappers/src/fds.rs` names puNES `fds.c`; `rustynes-frontend/src/ntsc_bisqwit.rs` records the verbatim-ported Bisqwit tables via Mesen2. The ~15 Mesen2-derived mapper boards, the CPU unstable-store opcodes, the emu2413/blip_buf audio, the CRT-shader reimplementations, the debug- symbol importer, and the PGO harness are all likewise marked. This is the accurate replacement for the old scattered, imprecise per-line "port of" comments (not restored verbatim); the SPDX + provenance headers plus the audited §1 derivation table are the discoverable record. CHANGELOG, ADR 0036, and docs/originality-and-provenance.md §8 are updated to describe this approach. Comments only — `cargo fmt --all --check` clean, `cargo check --workspace` compiles, zero behavior change (AccuracyCoin 141/141, nestest 0-diff). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: address CodeRabbit #346 review + add the provenance-failure post-mortem Two things: the maintainer-directed forensic analysis of the GPL-provenance failure, and the CodeRabbit review pass on the v2.2.9 PR. **docs/provenance-failure-postmortem.md (new).** A complete, evidence-cited reconstruction of how RustyNES came to incorporate lifted GPL emulator code despite a black-box-only instruction: the timeline across RustyNES_v2 (the private "engine stack" where the porting happened, 2026-05, Opus 4.7) and the 2026-06-13 transplant into this repo; the two distinct failures (the port itself, then the v2.2.5 scrubbing of the honest "port of" comments); the root causes (GPL source on disk + accuracy-bar goal + no firewall; the guardrail post-dating the violation; honest-at-build-time then laundered; multi-session framing propagation; trusted AI self-attestation); and an honest accounting of what is NOT recoverable (the RustyNES_v2 porting-era session logs are gone). The single hardest fact: the original "Faithful port of Mesen2's ..." comments still exist verbatim in RustyNES_v2 today — only this public repo scrubbed them. Linked from originality-and-provenance.md §8. **CodeRabbit #346 review (9 threads):** - **`.bk2` LogKey empty-field bug (Major, data integrity).** `parse_log_key` filtered out empty positional fields, shifting later columns/groups (an empty console group promoted P2's map into P1; an empty interior column misaligned buttons so `U.A` replayed as `Up` alone). Now strips only the syntax delimiters and keeps interior empties; +regression test for both cases. - **TAS branch/load ordering (correctness).** `CreateBranch` / `LoadBranch` cleared `input_dirty` without flushing pending edits, so a branch snapshot captured stale state; they now `ed.seek` to flush first. - **`WindowCfg` -> `ViewportBuilder` (Major).** The detached branch maps default size / position / resizability onto the viewport, not just the docked window. - **Multi-viewport honesty (Major).** RustyNES's frontend is a single-viewport `egui_winit` integration, so `show_viewport_immediate` renders the "detached" panel EMBEDDED in the main window rather than a separate OS window — it does not yet fully resolve the Windows-10 trapped-window report. Documented honestly in code, CHANGELOG, AGENTS.md, and docs/frontend.md; true OS-window detach is tracked follow-up. Corrects an overclaim. - Doc/metadata: panel count 17 -> 18; ADR 0036 "by construction" -> verified release-check evidence + STATUS link; README badge/BibTeX -> v2.2.9; SUPPORT current-release v2.0.4 -> v2.2.9; libretro display_version -> v2.2.9; Android about_body 139/139 -> 141/141 and 168 -> 172 mappers (EN + ES). Core-affecting fixes (bk2, TAS) are core/frontend only; cargo check + the bk2 tests pass. AccuracyCoin 141/141 unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(postmortem): the black-box instruction was given but not enforced/followed Reconcile the root-cause framing with the maintainer's correction: the black-box / oracle-only instruction WAS given — the failure was that it was not mechanically enforced (no barrier at the tool boundary; no persisted written rule in the loaded guidance until 2026-06-13) and the porting model did not follow it. §4.1 and §4.2 reframed from "no guardrail / the guardrail post-dated the violation" to "instruction given, neither persisted early nor enforced"; §4.5 sharpened (an instruction the agent can silently disregard and then falsely certify is not a control). The evidentiary caveat is unchanged: the porting-era logs are gone, so the exact wording/timing of the spoken instruction cannot be quoted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(postmortem): formatting + self-consistency pass on the maintainer's edits Non-substantive cleanup of the maintainer's review edits: stripped trailing whitespace (§1, §2, and the closing NOTE), evened out the wrap widths the inline edits left uneven, standardized hyphen-as-dash to em-dash in the NOTE, fixed one phrase that didn't parse ("finally did baseline" -> "finally did become the baseline"), and reconciled §5 with §4.2's "written instruction" framing (dropped the now-inconsistent "or verbally"; the honest "cannot be quoted, logs gone" point is unchanged). No substantive claims or the maintainer's wording/voice were altered. markdownlint clean, no trailing whitespace. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(provenance): correct the laundered per-function comments at ported sites Restore accurate, license-specific attribution at each genuine ported site, replacing the v2.2.5-laundered false-independence claims that were STILL in the shipped per-function comments (the SPDX top-of-file headers alone did not undo them). At each ported function the comment now names the upstream file/function and its license, instead of asserting "an independent implementation … no third-party emulator code is incorporated" / "cross-checked … as oracles". Corrected (18 files) — examples: - `cpu.rs` SH* stores → Mesen2 `NesCpu.h` (`SyaSxaAxa`), GPL-3.0-or-later - `ppu.rs` OAM-data-bus / sprite-eval → Mesen2 `NesPpu.cpp` (`ProcessSpriteEvaluation` / `ReadSpriteRam`), GPL-3.0-or-later - `ntsc_bisqwit.rs` → tables ported verbatim via Mesen2 `BisqwitNtscFilter`, GPL-3.0-or-later - `blip.rs` → BLEP technique derived from Shay Green's `blip_buf`, LGPL-2.1-or-later (our kernel is a finer 32-phase refinement) - mapper boards (m016 Bandai EEPROM, m035/lib JY, m176/m268 FK23C/CoolBoy, m513/mmc3_clones/sachen_discrete Sachen, multicart/ntdec NTDEC/Txc, kaiser Waixing) → their specific Mesen2 `.h` sources, GPL-3.0-or-later; CoolBoy also FCEUX (GPL-2.0-or-later) - `unif.rs` → Mesen2 `UnifLoader.cpp` (GPLv3) + FCEUX `unif.cpp` (GPLv2) - `sachen_discrete.rs` JV001 → puNES `JV001.c` / `mapper_147.c` (GPL-2.0-or-later) - `fds.rs` per-CRC drive table → puNES `src/core/fds.c` (GPL-2.0-or-later) - `source_map.rs` → mirrors Mesen2 `DbgImporter` (GPL-3.0-or-later) Deliberately LEFT unchanged (no over-attribution): `opll.rs` (already honestly "a pure-Rust port of emu2413", MIT), `pgo_trainer.rs` (honest `PGOHelper` pattern), `palette_gen.rs` / `crt_stack.rs` (documented method / genuine look-reimplementation, no false claim), and `m069_sunsoft_fme7.rs` (its Mesen2 mentions are genuine `_volumeLut` oracle cross-checks). Genuine oracle-comparison mentions ("matches Mesen2", "Mesen2-independent oracle") were NOT converted. Comments only — `cargo fmt`, `cargo check`, and `cargo clippy -D warnings` (cpu/ppu/apu/mappers/frontend) all clean. Zero behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: add the themed PDF of the provenance-failure post-mortem (ref-docs/) A styled PDF rendering of docs/provenance-failure-postmortem.md for the reference corpus, at ref-docs/RustyNES_Provenance-Failure-Postmortem.pdf. Themed as a forensic incident record — oxblood-crimson accents (severity), a charcoal serif body (official-record readability), dark-slate evidence-table headers, and monospace for commit hashes / file paths — with a title block, table of contents, and page footers. 8 pages, US Letter. Built with pandoc 3.6.1 -> WeasyPrint 68.1 from the committed markdown; content is identical to the source document. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: add ingestible provenance/license guardrails for AI-assisted emulator dev A forward-looking, general-purpose ruleset distilled from the provenance-failure post-mortem: docs/ai-emulator-provenance-guardrails.md. Written to be dropped into a project's agent instructions / permanent memory BEFORE development starts so the same trap cannot recur — in this project or any other emulator project using prior art (reference emulators, test ROMs). Contents: why emulators are a special trap for AI agents (accuracy is convergent + references are mostly copyleft); a classification of every external input (documentation / test ROMs / observable oracles / incorporated components) and what each permits; the reference firewall (oracles are run and observed, never opened and read — enforced by a denied read-path + a CI check, not by prose); attribution on four consistent surfaces (site comment + SPDX + central table + NOTICE) with a no-over-attribution rule; license arithmetic (the or-later grant, combined-work copyleft, the license gate); mechanical enforcement; a pre-development checklist; a paste-ready guardrail block for CLAUDE.md/AGENTS.md; a remediation runbook (do NOT scrub); and a red-flags table of the thoughts that precede the failure. Intended for sharing as NESdev-community best-guidance. Cross-linked from the post-mortem's §7 (Lessons and prevention) as the actionable counterpart. markdownlint clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: restyle the post-mortem PDF — calm cool palette, sans-serif, 4pp Redesign per maintainer feedback ("less harsh on the eyes"): a cool blue/teal palette (navy title, blue headings + rules, teal accents) on soft cool-slate text, with RED reserved only for genuine takeaways; humanist sans-serif throughout (Fira Sans, with FiraCode for code) at a comfortable weight/leading; a two-column layout for an ideal ~55-60 character measure (per readability research); and the maintainer's closing NOTE set apart in a full-width light-blue "Maintainer's Statement" box. Compacted from 8 pages to 4 (US Letter). Built pandoc 3.6.1 -> a bs4 DOM assembler (full-width title + evidence table between 2-column prose groups) -> WeasyPrint 68.1. Content unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(guardrails): make the provenance guardrails a standalone, console-agnostic doc Generalize docs/ai-emulator-provenance-guardrails.md so it stands on its own as a community resource — no references to any specific project or its source-tree documents, and applicable to emulation of ANY console, not just the NES: - Reworded the opening to describe the failure pattern generically (no specific project, no cross-link to a project post-mortem) and to state it applies to NES / SNES / Genesis / Game Boy / N64 / PlayStation / arcade / etc. - Broadened the reference-emulator examples across consoles (Mesen2/FCEUX, bsnes/Mesen-S, Genesis Plus GX/BlastEm, SameBoy/mGBA, ares/higan/MAME, …) in the bucket table and the paste-ready block. - Genericized the example provenance comment, the central-table / provenance-doc filenames (`PROVENANCE.md` or equivalent), the firewall-check grep, and the "matches reference X" over-attribution example. - Removed the "case study" / "this project" phrasings and the two links to project source-tree docs (post-mortem, derivation table); the remediation and closing now speak generally. Content and rules are unchanged; only the framing is now project-neutral and multi-console. markdownlint clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(pdf): convert provenance post-mortem to full-width single-column layout Reflow `ref-docs/RustyNES_Provenance-Failure-Postmortem.pdf` from the prior dense two-column treatment to a single wide-column, full-page layout. The two-column measure forced an awkward mid-title column break and cramped the first page; the evidence timeline table (already a full-width block) sat inconsistently between the two flowed columns. Typographic changes (theme CSS, WeasyPrint pipeline unchanged otherwise): - `.cols` collapses from `columns: 2` (with a column rule) to a plain single-column block; body text switches from justified to left-aligned (ragged right) for the wider measure. - Base type 8.75pt -> 10.2pt, line-height 1.4 -> 1.5; page margins widened to 1.9/2.1/1.6/2.1cm to hold the single-column measure near a comfortable ~80-char line rather than a full 19cm bleed. - Headings scaled to the new base (h2 11.5 -> 13.5pt, h3 9.6 -> 11pt), the evidence table 6.9 -> 8.4pt, code blocks 7.6 -> 8.6pt, and the maintainer's NOTE box 8.6 -> 10pt. Cool blue/teal structure, red-only-for-takeaways emphasis, and the blue-shaded "MAINTAINER'S STATEMENT" NOTE box are all preserved; the document grows from 4 to 5 pages, which the maintainer accepted ("regardless of the final page count"). Source markdown `docs/provenance-failure-postmortem.md` is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(provenance): unquote maintainer's statement; italicize it in the PDF Two presentation tweaks to the closing "Maintainer's Statement": - Source `docs/provenance-failure-postmortem.md`: remove the surrounding quotation marks from the DoubleGate NOTE so the statement reads as a first-person remark rather than a quoted block. Wording unchanged. - Regenerated `ref-docs/RustyNES_Provenance-Failure-Postmortem.pdf`: the PDF assembly now wraps everything after "NOTE (from DoubleGate): " in an <em>, so the statement body renders italic while the "NOTE (from DoubleGate):" label stays upright. This is a PDF-only styling step (the assembler splits the note paragraph at the "): " marker and re-parents the trailing nodes — including the `~/.claude/` code span — under an emphasis element); the Markdown source carries no emphasis markup so its own rendering is unaffected. Layout, theme, and page count (5) are otherwise unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(provenance): render the section cross-reference as bold "7. Lessons and prevention" In the maintainer's NOTE, change the cross-reference from "#7 'Lessons and prevention'" to a bold "7. Lessons and prevention" in both surfaces: - Source `docs/provenance-failure-postmortem.md`: wrap the phrase in `**...**` and drop the `#` prefix and single quotes. - Regenerated `ref-docs/RustyNES_Provenance-Failure-Postmortem.pdf`: the phrase is now a <strong> inside the italic NOTE body, so it reads as a bold section label (the assembler re-parents it under the note's <em> along with the rest of the statement body). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(pdf): render "Fiskbit" bold and upright in the maintainer's NOTE PDF-only styling: within the italic NOTE body, "Fiskbit" is now wrapped in a <strong class="upright"> so it reads bold and non-italicized while the rest of the statement stays italic. The assembler splits the note text run and re-parents the name under an upright strong; the theme adds `.note-box strong.upright { font-style: normal; }` to cancel the inherited italic (leaving the bold "7. Lessons and prevention" label, also a strong, italic as before). Markdown source unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(ref): add themed PDF of the AI-emulator provenance guardrails Render `docs/ai-emulator-provenance-guardrails.md` to a styled PDF at `ref-docs/AI-Emulator-Provenance-Guardrails.pdf`, using the same cool "Calm" family theme as RustyNES_Provenance-Failure-Postmortem.pdf so the two provenance documents read as a set. Pipeline (pandoc gfm -> html5, a small BeautifulSoup title-block/emphasis pass, WeasyPrint with a dedicated theme CSS): - Full-width single-column, humanist sans (Fira Sans), blue/teal structure; red reserved for the few hardest takeaways ("capability + availability + accuracy objective", "C used as if it were A", "source physically unavailable to the agent", "Never launder"). - Title block reflecting the doc's own framing (community best-guidance; ready-to-ingest ruleset), running header/footer retitled for this doc. - The document's own structures styled to match: the "in one sentence" blockquote becomes a teal TL;DR callout; GitHub task-list checklists render as blue-outlined checkboxes (the real <input> hidden, the box drawn as an absolutely-positioned gutter marker — reliable in WeasyPrint); the paste-ready block keeps the monospace code panel; tables get the navy-header/zebra treatment with hyphenated long words. 8 pages. The Markdown source is unchanged; this is a presentation artifact derived from it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(pdf): fix the paste-ready guardrail block sizing in the guardrails PDF The §8 code panel rendered at 8.6pt, so its ~95-char lines wrapped raggedly inside the box and one bullet's leading "-" was orphaned onto its own line. Drop the monospace size to 6.9pt (the block's lines now fit the panel width) and add a hanging indent so any residual continuation line stays readable under its bullet. The document tightens from 8 to 7 pages; nothing else changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(agents): ingest the provenance/license firewall as the top development rule Add a `## MOST IMPORTANT RULE — Provenance & license firewall` section at the top of the AGENTS.md project block (reached via the CLAUDE.md / GEMINI.md symlinks), positioned before "## What this is" so it is the first substantive guidance every session loads. It is declared to outrank everything else in the file. Motivation: RustyNES is a corrected provenance failure — GPL emulator source (Mesen2, puNES, FCEUX, GeraNES) was reproduced despite a black-box instruction, the honest "ported from X" comments were later scrubbed, and the project was relicensed MIT/Apache -> GPL-3.0-or-later as the derivative work it actually is. The failure was caught by an outside NESdev reviewer, not by tooling, which is the empirical basis for the "do not self-certify" clause. The full preventive ruleset now lives in docs/ai-emulator-provenance-guardrails.md (with a forensic post-mortem in docs/provenance-failure-postmortem.md); this section is the always-loaded distillation that binds an agent before it touches any file. The section encodes the six non-negotiables — the REFERENCE FIREWALL (reference emulators are black-box oracles whose output may be observed but whose source is never read or reproduced; the local ref-proj/ clone is removed from disk and stays gitignored so the source is out of reach by design), IMPLEMENT FROM DOCS, IF YOU DERIVE SAY SO AND STOP (attribute at the site + originality doc §1 + NOTICE + SPDX; keep the license GPL-3.0-or-later-compatible), NEVER LAUNDER, NO OVER-ATTRIBUTION, and DO NOT SELF-CERTIFY — and points at the mechanical enforcement that backs the prose (the gitignore / dockerignore / markdownlintignore / CodeRabbit exclusions, deny.toml, and the per-file SPDX + provenance headers), on the principle that a rule the tooling enforces beats a rule an agent is merely asked to follow. Also updates the existing `.markdownlintignore` note to record that ref-proj/ is now removed from disk but retained in the ignore lists as a firewall guard rather than as a build convenience. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(provenance): normalize in-source reference citations to upstream paths Delete the removed-clone `ref-proj/` prefix from every in-source provenance citation so each names the upstream project + file directly (attribution surface #1 of the guardrails: "the upstream project, the specific file/function, and its license"), rather than a path into a local working copy that no longer exists. `ref-proj/GeraNES/src/GeraNES/Mappers/Mapper0NN.h` becomes `GeraNES/src/GeraNES/Mappers/Mapper0NN.h`; `ref-proj/Mesen2/Core/...`, `ref-proj/TriCNES/Emulator.cs`, `ref-proj/tetanes`, and `ref-proj/fceux/...` likewise. The one non-path use — m024_vrc6.rs's "a cross-check against the whole `ref-proj/` field" — is reworded to "the whole field of reference emulators". This is a pure path/wording normalization: it does not change any derivation claim. The sites that genuinely document a port keep their verb and license verbatim — ppu.rs still reads "Ported from TriCNES (`TriCNES/Emulator.cs`, MIT, commit 9199870)", and m093_sunsoft3r.rs still says its `writePrg` matches "the designated reference `GeraNES/src/GeraNES/Mappers/Mapper093.h`, whose `writePrg` opens with `data &= readPrg(addr);`". Nothing is softened, laundered, or over-attributed; only the dangling local-clone prefix is removed. Scope: 30 files across rustynes-core (movie_interop), rustynes-frontend (crt + two debugger panels), rustynes-mappers (28 board modules), and rustynes-ppu. The changes are comments and doc-comments only — no code tokens move — so the compiled `#![no_std]` chip stack is byte-identical, the deterministic contract is untouched, and AccuracyCoin holds 141/141 and nestest stays 0-diff by construction. Verified: `cargo check --workspace` clean, `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps` clean, `cargo fmt --all --check` clean, and `git grep "ref-proj/" -- crates/**/*.rs` now returns nothing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: account for the ref-proj/ removal + reference firewall across docs and ignores The local `ref-proj/` reference-emulator clone (Mesen2, puNES, FCEUX, GeraNES, TriCNES, tetanes, ...) has been removed from disk. Update the surrounding documentation and ignore configuration so nothing points a developer — or an agent — back at reference-emulator source, and so the firewall is stated where the setup that used to depend on ref-proj/ lived. - .gitignore: keep the `/ref-proj/` entry but re-annotate it as a *firewall guard* — the directory is removed and must never re-enter the working tree, because its copyleft source is what made RustyNES a derivative work. The entry now cross-links the guardrails doc and the AGENTS.md top rule. (The parallel ignores in .dockerignore / .markdownlintignore / .pre-commit-config.yaml / .coderabbit.yaml are retained unchanged for the same belt-and-suspenders reason.) - Oracle / trace tooling (docs/tooling/oracle-tooling-setup.md, docs/ppu-trace-tooling.md): add a REFERENCE FIREWALL banner and rewrite the ref-proj/ paths. These guides build and instrument a reference emulator to capture its *output* for cross-diffing — legitimate black-box-oracle use — so they now state that any such build must live out-of-tree, outside the agent's allowed paths, and be used for output only; the committed golden vectors (crates/rustynes-test-harness/golden/) remain the preferred, self-contained path that needs no reference source at all. - Provenance / spec docs: originality-and-provenance.md records that the §1 derivation table was cross-checked against the sources at the time (the since-removed ref-proj/ clone) and stands on its named upstream citations; STATUS.md, to-dos/ROADMAP.md, adr/0030, adr/0006, apu-2a03.md, hd-pack-zelda-troubleshooting.md, and SALVAGE_MANIFEST.md have their ref-proj/ citations normalized to upstream (or, where they said "vendored ref-proj/X", corrected to "out-of-tree" / "in-repo", since the clone is no longer vendored). - Discoverability: add a "Provenance & Licensing" section to docs/DOCUMENTATION_INDEX.md and a matching group to the mkdocs nav so the guardrails, post-mortem, originality record, and ADR 0036 are linked from the documentation entry points rather than only from AGENTS.md. Frozen / historical trees (docs/archive/, to-dos/archive/, to-dos/plans/**, ref-docs/, .github/release-notes/, CHANGELOG-FULL.md) deliberately keep their ref-proj/ mentions as immutable record. No behavior changes; markdownlint clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: document the reference firewall + guardrails in README and CHANGELOG Surface the provenance guardrails on the two developer-facing entry points. - README.md: add a "Reference firewall (so it does not recur)" paragraph to the License section, immediately after the existing GPLv3-derivation and AI-assistance disclosures. It names the forensic post-mortem and the console-agnostic guardrails ruleset (with the themed PDFs in ref-docs/), states that it is the project's top development rule ingested into AGENTS.md, and summarizes the firewall: reference emulators are black-box oracles whose output may be observed but whose source is never read; the ref-proj/ clone is removed and gitignored so that source is out of reach; hardware behavior is implemented from documentation and test ROMs; genuine derivation is attributed and license-checked, never laundered. Notes the guardrails are shared as community best-guidance for other AI-assisted emulator projects. - CHANGELOG.md [Unreleased]: add a "Provenance guardrails + reference firewall" block recording the new guardrails doc + post-mortem + PDFs, the ingestion into AGENTS.md and the memory bank, the ref-proj/ removal and the retained firewall ignores, the comments-only normalization of in-source citations to upstream paths (deterministic core byte-identical), the out-of-tree oracle posture in the tooling docs, and the new documentation-index / mkdocs-nav entries. Documentation only. markdownlint clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(provenance): align the §1 derivation table with the in-source citations A CodeRabbit review of #346 flagged a per-site ↔ central-table inconsistency in the provenance record. A full audit of every `derived from Mesen2's \`X\`` comment against its `docs/originality-and-provenance.md` §1 row confirmed six files whose in-source citations name an upstream Mesen2 header that the central table row omitted. Each such header is a shared chip/transform that a sibling file already lists in its own row, so the table under-reported which files derive from it: - `kaiser.rs` (mapper-253 IRQ board, line 614) derives from `Waixing/Mapper253.h`. - `m035_jy_asic.rs` (`invert_prg_bits`, line 315) derives from `InvertPrgBits`. - `m176_bmc_fk23c.rs` (CoolBoy banking, line 554) derives from `Mmc3Variants/MMC3_Coolboy.h`. - `m513_sachen_9602.rs` (TxcChip accumulator, line 349) derives from `Txc/TxcChip.h`. - `mmc3_clones.rs` (Sachen 8259A/B/C, mappers 138/139/141, line 784) derives from `Sachen/Sachen8259.h`. - `ntdec.rs` (BMC-11160, line 1262) derives from `Txc/Bmc11160.h`. Add each missing upstream header to the corresponding table row so the central derivation record is fully consistent with the per-site attributions, per the provenance guardrails' "provenance-comment ↔ table consistency" rule. This is an alignment, not new attribution: each derivation is already asserted verbatim in the source comment; the table now records what the code already documents (a correction toward completeness, not over-attribution). No license changes — all six sources are Mesen2 (GPL-3.0-or-later), already the project's license. Documentation only; markdownlint clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: resolve CodeRabbit provenance-firewall review on the tooling/ignore docs Address four follow-on CodeRabbit findings on #346: - .gitignore: the ref-proj/ guard comment claimed the referenced emulators' source is uniformly "copyleft". That over-generalized — Mesen2 / puNES / FCEUX / GeraNES are GPL, but TriCNES is MIT. Reword to state the licenses are project-specific (copyleft for the four GPL oracles; MIT for TriCNES) and note that the MIT TriCNES source is instead deliberately vendored, with attribution, under crates/rustynes-test-harness/golden/tricnes/. The /ref-proj/ ignore rule itself is unchanged. - docs/tooling/oracle-tooling-setup.md: resolve a genuine contradiction. The two firewall notes said "any Mesen2 / TriCNES build must live out of tree", but the same page (§2a) vendors the complete MIT TriCNES source in-repo at golden/tricnes/tricnes-full-src/ and calls the in-tree harness the preferred path. Scope the out-of-tree / never-reproduce rule to the copyleft references (Mesen2, puNES, FCEUX, GeraNES) and state TriCNES explicitly as the MIT exception whose in-repo vendoring is license-compatible and not a firewall violation. The committed golden vectors remain the preferred, no-live-emulator path. - docs/originality-and-provenance.md and docs/DOCUMENTATION_INDEX.md: describe the removed reference-emulator clone without reproducing its literal repository path in these provenance/index prose additions (the path stays authoritatively named where it is load-bearing — the /ref-proj/ ignore rule and the AGENTS.md rule). Documentation only; markdownlint clean. No behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(core): bound .bk2 LogKey parsing against #-group allocation amplification `bk2_interop::parse_log_key` collected every `#`-separated group of the BizHawk `LogKey` header into a `Vec<&str>` and then read only `groups[1]` (P1) and `groups[2]` (P2). A hostile `.bk2` whose `LogKey` is padded with a large run of `#` delimiters therefore allocated one `&str` slot (~16 bytes on 64-bit) per empty group — an unbounded, ~16x-of-input allocation on an untrusted import path, the same DoS class the v2.2.0 `Movie::deserialize` fuzzing already closed elsewhere. Read the three groups we actually consume (console, P1, P2) directly from the `split('#')` iterator via `next()` instead of collecting. `split` still yields empty groups, so `next()` preserves the empty console slot (`##P1…`) and keeps P1/P2 from shifting left into it — the behavior is byte-identical for every valid movie, only the unbounded intermediate allocation is removed. The parse now touches at most three groups regardless of how many `#` the input contains. Adds `log_key_bounded_against_pathological_group_padding`, which imports a movie whose `LogKey` carries 100k trailing `#` delimiters and asserts P1/P2 still map correctly (the trailing groups are ignored), as the standing regression guard. This is the `.bk2` *import* path only; the deterministic chip stack and every golden vector are untouched (AccuracyCoin 141/141 unaffected). Reported by CodeRabbit as an outside-diff-range finding on #346. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(changelog): correct windowing scope, fold guardrails into the v2.2.9 notes Three corrections to the [2.2.9] release notes (which release-auto.yml publishes verbatim, so accuracy and completeness there matter): - **Honesty fix (CodeRabbit outside-diff-range finding).** The intro note claimed detached tool windows use "egui multi-viewport (real OS windows)", which contradicted both the detailed "Fixed" entry and AGENTS.md: the frontend is a single-viewport `egui_winit` integration, so `show_viewport_immediate` renders a detached panel *embedded in the main window*, not as a separate OS window, and the Windows-10 "trapped window" report is therefore not yet fully resolved. Reword the note to state the embedded scope honestly and point at the v2.3.0 multi-viewport follow-up, matching the rest of the section. - **Fold [Unreleased] into [2.2.9].** The provenance-guardrails + reference-firewall work (guardrails doc + post-mortem + PDFs, ingestion into AGENTS.md and memory, the ref-proj/ removal and citation normalization, the §1 derivation-table audit, and the MIT-TriCNES vendoring exception) all ship in v2.2.9, so it belongs in the v2.2.9 notes rather than a separate [Unreleased] section the release body would omit. Recorded as a new "Added — Provenance & license firewall" subsection. - **Log the .bk2 import hardening** (the `LogKey` allocation-amplification bound) in the same subsection. Documentation only; markdownlint + fmt clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.
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 scanlinemath), 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
aux.y). The darkening now runs in linearlight.
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.
aux.x, default 0.5). Blends the soft parabola → a narrowGaussian beam for crisp vertical boundaries.
aux.x = 0= the exact pre-v2.2.8profile; visible only when scanlines are enabled.
CRT_WGSLuniform 12 → 16 floats (rect+crop+params+aux); wired on both thedesktop (
crt.rs) and Android (gfx.rs) hosts.Checks
naga validation green · desktop builds · native + wasm32
clippy -D warningsclean ·cargo fmt+ markdownlint clean ·rustynes-frontendtests 464/464 · no goldensnapshots move (CRT output isn't golden-tested). Docs:
docs/crt-composite.md+STATUS/README/AGENTS/CHANGELOG.
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation