Skip to content

v3.0.1 "Mortar": the open items, Rust 1.99 everywhere, the review sweep, the roadmap to v4.0.0 - #590

Merged
doublegate merged 44 commits into
mainfrom
release/v3.0.1
Oct 7, 2026
Merged

doublegate merged 44 commits into
mainfrom
release/v3.0.1

Conversation

@doublegate

Copy link
Copy Markdown
Owner

v3.0.1 "Mortar"

A maintenance release on v3.0.0. Release notes: .github/release-notes/v3.0.1.md; CHANGELOG [3.0.1]; plan to-dos/plans/v3.0.1-mortar-plan.md.

Breaking: EMULATION_EPOCH 1 -> 2 (the mapper 45 fix changes one game's output), so v3.0.0 movies and netplay peers are refused with both epochs named. Save states are unaffected.

What changes

  • T-GA23C-CHRRAM: mapper 45 CHR-RAM is unbanked; Famicom Yarou Vol.1 draws its menu.
  • Rust 1.99 everywhere, the libretro buildbot included. Test branch test/libretro-rust-1.99: pipeline 119614, 15/15 jobs, Apple included. libretro-cross now fails if .gitlab-ci.yml and rust-toolchain.toml disagree. Every dependency, action, Android, web and Docker image moves to its newest release; this supersedes chore(ci): bump taiki-e/install-action from 2.87.21 to 2.87.22 #584.
  • The bot-review sweep back to PR chore(ci): Bump actions/cache from 4 to 5 #1: 290 items, 473 verdicts, 80 fixes; every reply posted and every open thread resolved.
  • Provenance:
    • the shared Bisqwit NTSC pass is recorded as derived (T-NTSC-PROVENANCE);
    • the TriCNES source moves out of the repository (62 deleted files; guardrails section 3a records the maintainer's terms for consulting it);
    • the m069 "derived from" comment is restored.
  • The roadmap v3.1.0 -> v4.0.0, from 29 maintainer decisions. The hardware release now comes at the end of v3.9.x.
  • bump_release.py: the Cargo.toml anchor no longer matches internal X.0.0 requirements.

MiSTer (sibling, private)

  • The dot-0 A12 rule is fixed and a new odd-frame gate added.
  • Ladders: 200 passed / 0 failed / 1 expected failure on-die, 201 / 0 / 1 off-die.
  • Seed 2 at BUILD_DATE 261007. Both builds compile byte-identically twice: on-die 7e81a718..., off-die 88d1dfa5....
  • Release candidate, not hardware-verified.

Verification (on the release tree)

  • test-roms 3,234 / 0 / 11; workspace 2,886 / 0 / 7; cosim 54 / 0.
  • Commercial 60 / 0, 137 / 0, 6 / 0. AccuracyCoin 144/144, nestest 0-diff.
  • fmt; all 18 clippy combinations; rustdoc; no_std; the seven release audits; markdownlint.

Not done

The palette-offset A/B (8f449691 -> 33ea0572) is still waiting for a quiet host.

Review

CodeRabbit skips PRs over 100 files, so it reviews three stacked, review-only slices: review/v3.0.1-a (crates), -b (harness, CI, scripts) and -c (docs, plans). They will be closed unmerged.

The merge waits for the maintainer.

🤖 Generated with Claude Code

https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj

doublegate and others added 30 commits October 6, 2026 20:32
Famicom Yarou Vol.1 7-in-1 (mapper 45, 256 KiB PRG, CHR-RAM) booted to
noise in every release that staged it. The v3.0.0 trace showed why: the
menu writes all 8 KiB of pattern data through $2007 while every MMC3 CHR
register is 0, then draws with R0-R5 = 0, 2, 4, 5, 6, 7. Under MMC3 CHR
banking that upload lands in 1 KiB banks 0-1 only, and the outer CHR-OR
can move it further, so banks 2-7 are never written and the screen shows
whatever the RAM powered on with.

The document. The mapper 45 page says nothing about CHR-RAM, which is why
v3.0.0 left the ticket open ("fix only from a document or a hardware
measurement"). The answer is on the page of a GA23C variant: NES 2.0
mapper 372 is "INES Mapper 045 but with one bit of outer bank register #2
working as a CHR-ROM/RAM switch", and its bit table reads "Select CHR-ROM
(0)/CHR-RAM (1, unbanked)". That is the family's own CHR-RAM wiring, and
it is exactly what the trace needs. (Mapper 356, a second GA23C variant,
gives its CHR-RAM as a plain 8 KiB.) A NESdev forum post on MMC3 clones
names the two wirings boards use -- RAM A10-A12 on PPU A10-A12
("unbanked") or on the MMC3's CHR A10-A12 ("banked") -- and mapper 45
carries no submapper to choose between them, so the family document
decides.

The change: in Mmc3Board::chr_target, a Board::M45 with chr_is_ram returns
Chr::Rom(addr & 0x1FFF), the whole-image CHR-RAM indexed by the PPU
address. The existing write path already stores Chr::Rom offsets into
CHR-RAM when chr_is_ram, so reads and writes agree. CHR-ROM boards take
the unchanged arm below it.

Verification:
- m45_chr_ram_is_unbanked (new) replays the traced sequence: CHR
  registers 0, 8 KiB written, R0-R5 = 0,2,4,5,6,7, outer registers set to
  CHR-OR $10 / CHR-AND $7, and every byte read back. It FAILED before the
  fix ($0000 read 7: register 0 banked it elsewhere) and passes after;
  removing the new arm is that same failure, so the mutant is caught.
- rustynes-mappers: 954 passed, 0 failed.
- external_coverage on the five local mapper 45 dumps: only Vol.1 moved.
  Its new frame is the "7 IN 1" title scene (trees, birds, sun, a girl in
  green); the old committed screenshot was noise. Snapshot re-blessed and
  screenshot replaced; all five then pass. The other four carry CHR-ROM
  and cannot reach the new arm.

EMULATION_EPOCH 1 -> 2. ADR 0045's mechanical trigger is a moved
snapshot, and Vol.1's moved: a v3.0.0 movie of it would replay on
different pattern data, and a v3.0.0/v3.0.1 netplay pair would desync.
Both are now refused, naming both epochs. Everything that tests the epoch
is relative to the constant (movie.rs, bk2_interop.rs), so core and
netplay pass unchanged (251 + 101 + the rest, 0 failed). The constant's
doc now carries a table of which release moved it and why.

Docs in the same change: docs/mappers.md row 45, the ROADMAP ticket
(FIXED v3.0.1 with the source), CHANGELOG [Unreleased].

Not established: no hardware has been measured. The fix rests on the
372 page describing the family plus a trace that requires it; a mapper 45
board wired "banked" would need a submapper the format does not have.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
…every dependency at its newest

v3.0.1's dependency and toolchain refresh (the maintainer's request: every
version bump, PR #584 included, except the parts that must stay on 1.96).

THE TOOLCHAIN SPLIT. rust-toolchain.toml moves 1.96.0 -> 1.99.0 (newest
stable, 2026-10-01). 1.100 is the current beta and 1.101 nightly, so 1.99 is
the newest that is released. The pin had been held at 1.96 since 2026-09-28
for one reason: libretro's build image injects `-Car=...` into every Apple job,
and `-C ar` became a hard error in Rust 1.97. That reason still stands, so the
buildbot alone stays on 1.96.0:

- .gitlab-ci.yml `.core-defs` sets RUSTUP_TOOLCHAIN: "1.96.0". The variable
  outranks rust-toolchain.toml, and every job's `rustup target add` became
  `rustup toolchain install ${RUSTUP_TOOLCHAIN} --profile minimal --target
  ${RUST_TARGET}`. The shared libretro templates set neither RUSTUP_TOOLCHAIN
  nor rustflags (checked in rust-apple/linux-x64/windows-x64/android-jni/
  webos .yml).
- The seven crates that core builds (cpu, ppu, apu, mappers, core, gamedb,
  libretro) keep rust-version = "1.96"; every other crate is 1.99.
- CI's libretro-cross job reads the version out of .gitlab-ci.yml with a
  table-scoped, fail-closed awk (the same discipline as rust-setup's
  resolver) and builds on it, so .github/ still carries no toolchain literal
  and the 1.96 floor is checked on every PR rather than found on the buildbot.

That check has already paid for itself. clippy 1.99 rewrote
`for b in self.ram.iter_mut()` (a Box<[u8; 2048]>) to `for b in &mut self.ram`,
which only 1.97+ accepts (`&mut Box<[T; N]>` is not IntoIterator on 1.96);
clippy did not honour the crate's rust-version, and only the 1.96 build
caught it. Fixed as `&mut *self.ram`, with the reason at the site.

The RetroAchievements question (whether newer Rust eases the C bridge):
1.99 stabilises `extern "C"` variadic DEFINITIONS (VaList). None of the
rcheevos functions RustyNES calls is variadic, and the one C file,
static_asserts.c, exists only to report C sizeof values for an ABI test. So
nothing there gets simpler; recorded rather than invented.

CLIPPY 1.99. 28 findings in the first pass grew to about 70 once each crate
compiled (a failing crate hides everything downstream; `--keep-going` was
needed to see them). All are exact rewrites: `chunks_exact(N)` ->
`as_chunks::<N>()` (stable since 1.88, so fine on 1.96 too), `fill`,
`unwrap_or`, byte-string literals, a redundant #[must_use], an array
destructure, and 34 `assert!(x.is_empty())` -> `assert_eq!(x, [] as [T; 0])`
(clippy::assert_is_empty), applied from clippy's own suggestions and made
crate-absolute where it emitted module-relative paths. `cargo clippy --fix`
compiles one feature set at a time, so every CI combination was re-run after.

DEPENDENCIES. Cargo: `cargo update` in both workspaces (23 lock changes; the
cosim lock only libc). The only crates not at their newest are held upstream:
getrandom 0.2 and 0.3 by piccolo 0.3.3 (its newest release) via rand 0.8 and
ahash, and generic-array 0.14.7 by crypto-common 0.1.7's `=0.14.7`. The
MSRV-aware resolver held nothing back (with and without it, `cargo update`
chose identical versions).

- GitHub Actions: taiki-e/install-action 2.87.21 -> 2.87.26 (this supersedes
  Dependabot's PR #584, which proposed 2.87.22); dtolnay/rust-toolchain at
  its current v1 (7e38f4b4, adds install retries). Every other action was
  already on its newest major.
- macOS: macos-14 -> macos-15 in ci.yml and release.yml. The 14 image is in
  deprecation brownouts since 2026-10-05 and unsupported from 2026-11-02
  (actions/runner-images 13518), still aarch64.
- Android: cargo-ndk 3 -> 4 (our flags -t/-o/--platform are unchanged in
  4.x), NDK r29 -> r30 installed by sdkmanager in each job (the runner image
  ships r29; the version is one workflow env value), Gradle run on Temurin
  17 -> 25 (bytecode stays JVM 17, AGP 9.4's floor), org.json 20250517 ->
  20260814. AGP, Gradle, Kotlin, the Compose BOM and every AndroidX library
  were already newest.
- Web: wasm-opt pinned at version_133. It was not pinned at all, so trunk
  0.21.14 used its built-in version_123, and web.yml's header claimed both
  tools were pinned; corrected. MkDocs on Python 3.14.
- Docker: the signaling image on rust:1.99-trixie (it said 1.86, two years
  stale; the build was right only because rustup reads the copied
  rust-toolchain.toml anyway), runtime debian:trixie-slim, raproxy on
  python:3.14-slim.
- pre-commit: ruff 0.16.10.

Verified, on this tree:
- cargo fmt (workspace + cosim) clean; all 18 CI clippy combinations rc=0
  on 1.99, plus scripts/ios-host-typecheck.sh; rustdoc -D warnings; no_std
  thumbv7em build.
- On 1.96.0: `cargo check --release -p rustynes-libretro` and the no_std
  chip-stack build.
- cargo test --workspace + the cosim crate on 1.99: 2,926 passed, 0 failed.
- Android: the CI cross-build command (cargo-ndk 4.1.2, NDK r30) and
  `gradlew assembleFossDebug testFossDebugUnitTest` on JDK 27, newer than
  CI's 25 (Gradle 9.8 supports both).
- Web: `trunk build --release` downloaded and ran wasm-opt version_133.
- Docs: `mkdocs build --strict` on Python 3.14.7.
- Docker: both images build.
Not verified locally: the libretro buildbot itself (no pipeline runs before
main moves; the 1.96 build of its crates is the PR-time stand-in), the iOS
Xcode build, and the GitHub-hosted macos-15 and Windows legs (CI runs them).

Not done, with the reason: Xcode stays on the newest Xcode_26* the runner
image has (Xcode 27 shipped 2026-09-14 but is not on macos-26 yet);
targetSdk stays 36 (the Play mandate; raising it is a behaviour change);
markdownlint-cli is already newest; Quartus stays 17.0.2 (MiSTer's flow).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
…hat it found

to-dos/plans/v3.0.1-plan.md records the maintainer's five-part scope for the
first patch on v3.0.0 (the palette A/B, T-GA23C-CHRRAM, the odd-frame A12
stimulus, every dependency and toolchain bump, and every unanswered bot review
comment back to #1), a measurable gate for each, and the outcome so far. The
file has no codename yet; the maintainer chooses it at the cut.

to-dos/mister/TASKS.md: the odd-frame stimulus item is DONE (sibling fdb48ec).
Its two predictions are corrected openly rather than overwritten: the window
is pre-render dots 333-336, not 337-339 (rendering takes effect a few dots
after the write, so a write at 337-339 usually lands too late for the skip),
and the bitstream does change, because fixing the dot-0 rule is RTL. A new
open item records the one finding the bot sweep left UNSURE: whether a $2006
copy landing on a v_pipeline load dot matches the oracle (ordering confirmed,
divergence unmeasured, needs a dot-257 or increment-dot stimulus).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
The maintainer chose the codename (2026-10-06); the plan file takes it, as
every earlier plan does (to-dos/plans/vX.Y.Z-<codename>-plan.md).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
The release-branch commits were re-made to add the session attribution
trailers the first ones lacked (unpushed, re-signed, trees unchanged), so
the hashes the plan quoted no longer existed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
…d of holding it

Two defects in .github/dependabot.yml, both still present on v3.0.0.

Doubled scope. The cargo, gradle and cosim-cargo entries set
`prefix: "chore(deps)"` together with `include: "scope"`. Dependabot
appends the dependency-type scope (`deps` / `deps-dev`) to the prefix
itself (GitHub's dependabot options reference: "prefix followed by the
type of dependencies updated"), so every Dependabot PR was titled
`chore(deps)(deps): ...` (#565-#569 most recently). The three prefixes
are now `chore`; `include: "scope"` supplies `(deps)` once. The
github-actions entry (`chore(ci)`, no include) was correct and is
unchanged.

Stale hold. The root cargo entry still ignored egui, egui-wgpu and
egui-winit `>=0.36` and wgpu and naga `>=30`, with a comment saying the
tier was held at 0.35. The workspace moved to egui 0.36 / wgpu 30 on
2026-09-28 (vendored egui-winit; Cargo.toml lines 167-205, Cargo.lock
egui 0.36.2, wgpu 30.0.1), so the ignore block was silently suppressing
every future egui and wgpu release. The coupling it protected is real
(Cargo.toml: two wgpu majors break every device/queue handoff, and naga
is a direct frontend dependency that must match wgpu), so the five
entries become an `egui-wgpu-tier` group that bumps them in one PR.
It is listed first and has no `update-types`, because Dependabot puts a
dependency in the first group it matches; below the minor/patch
catch-all groups, a minor bump of one member would land alone. The
comment records the hold as dated history and points at the vendored
egui-winit, which a bump past 0.36.2 bypasses.

Verified: `pre-commit run check-yaml` passes; validated against the
SchemaStore dependabot-2.0 schema with jsonschema. The only schema
error is the pre-existing `reviewers` key (also on HEAD; not touched
here); with it removed both HEAD and this file validate clean.

Bot findings: RustyNES#390/A/5314720292/4, RustyNES#391/A/5314746008/3,
RustyNES#541/A/5758822410/2, RustyNES#543/A/5758833866/1 (doubled
scope); RustyNES#397/A/5323299625/1 (stale egui/wgpu hold).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
…put to stdout

Two scripts, both still defective on v3.0.0.

scripts/pr-review/list_unresolved_threads.py is the working list of the
bot-review closeout, and its last line, "0 unresolved thread(s)", is
what lets a merge go ahead. It read the thread list as
`(pr.get("reviewThreads") or {}).get("nodes") or []`, so a payload with
no `reviewThreads`, or a null `nodes`, printed exactly that all-clear
and exited 0 -- a fail-open gate. A partial node died on a bare
KeyError (`isResolved`, `comments`, `databaseId`) with a traceback that
hid which node and field were missing. The script now requires
`reviewThreads.nodes` to be a list and validates each node's boolean
`isResolved`, its `comments.nodes` list and the first comment's
`databaseId`, exiting through SystemExit with a message that names the
node. An empty list is still a genuine all-clear.

New scripts/pr-review/list_unresolved_threads_selftest.py runs the
REAL script as a subprocess with stdin payloads (the reply_and_resolve
selftest's precedent: test the script, not a copy of its rules). Each
refusal is asserted for its stated reason and with no traceback. Red
first: against the v3.0.0 script 5 of 7 checks FAIL (missing list, null
list, node without isResolved, without comments.nodes, comment without
databaseId); against this one, 7/7 pass. Listed in the directory README.

scripts/diag/ppu2002_read_value_histogram.py had its INPUT moved off a
predictable shared-/tmp path earlier, but still wrote its report to the
fixed `/tmp/RustyNES/an_out.txt` with a plain `open(..., 'w')`: no
private directory, no exclusive or no-follow open, so a pre-planted
symlink redirects the write. Running it on a 4-row fixture created
that file (644, 394 B). Nothing in the repository reads the file, so the
report now goes to stdout; after the change the same run prints the
report and no file is created.

Gates: ruff-check and markdownlint (pre-commit) pass on the changed
files; reply_and_resolve_selftest.py still passes.

Bot findings: RustyNES#325/B/4766773317/scripts/pr-review/list_unresolved_threads.py:49-69:174,
RustyNES#325/B/4766656411/scripts/diag/ppu2002_read_value_histogram.py:40-51:173.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
…rry it

tests/roms/assorted/README.md said every upstream path was in the
"blargg's NES test ROMs" section of tests/roms/LICENSES.md. That section
carries only the CPU, branch-timing, OAM, reset and APU ROMs; the two
palette ROMs and assorted/nestest.nes (kevtris) are rows of the "full
palette" ROMs section (LICENSES.md lines 193-199). The README now names
both sections and which files each carries. markdownlint passes.

Bot finding: RustyNES#325/B/4766656411/tests/roms/assorted/README.md:28-30:168.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
…IFIED

scripts/perf/perf_log_check.py printed "capture VALID -- window was on
screen throughout" whenever the post-warmup `present_discarded` delta
was 0. The frontend writes that column through
`presentation_clock.as_ref().map_or(0, ...)` (app.rs, the F16 block),
so a run with no presentation clock at all (non-Wayland, or the global
never bound) logs 0 on every row, and docs/performance.md already says
zero "is not proof of health" for exactly that reason. The checker
asserted something it never measured.

The checker now reads the header's `measured_refresh_hz` (already
loaded via `load_meta`). That field is set only from the presentation
clock's `poll()` answer (`self.dsync.measured_hz = Some(hz)`), so any
value other than `none` is evidence the counter was live. With `none`
and a zero count the line reads "validity UNVERIFIED (no presentation
clock)". The header is written when logging starts, so a clock that
answered later is also reported unverified -- an understatement, never
an overclaim. Report line only: the >1% discard-rate hard failure and
the exit codes are unchanged. docs/performance.md's capture-validity
section gains the fourth state.

Red first, on a real capture (perf-logs/perf-flowing_palette_nes-
20261001-041210.csv: measured_refresh_hz = none, refresh_source = none,
present_discarded column present): v3.0.0 printed "capture VALID --
window was on screen throughout"; now "validity UNVERIFIED (no
presentation clock)", exit 0 both times. The same capture with the
header changed to measured_refresh_hz = 119.991 still reads "capture
VALID". The script has no selftest mechanism, so these fixture runs are
the check. ruff-check and markdownlint pass.

Bot finding: RustyNES#363/A/5284716625/1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
…keeps quoted stops

Four defects in scripts/release-automation/bump_release.py, all present
on v3.0.0.

1. Swallowed lockfile failure. The `cargo metadata` refresh of the
   excluded crate's crates/rustynes-cosim/Cargo.lock was wrapped in
   `except Exception`, printed a WARNING and carried on, so `--apply`
   could exit 0 with that lockfile still on the old version. It is now
   `refresh_cosim_lock()`, which catches only CalledProcessError (with
   cargo's last stderr line) and FileNotFoundError (no cargo) and
   returns the message; `main` prints it as an ERROR and returns 1.
   Anything else is a script bug and still raises.

2. Locale-dependent encoding. Every read_text()/write_text() and the
   subprocess call used the locale's encoding. Under an ASCII locale
   (PYTHONCOERCECLOCALE=0 PYTHONUTF8=0 LC_ALL=C, preferred encoding
   ANSI_X3.4-1968) v3.0.0's chains_needing_a_summary() raised
   "UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2" on an
   em-dash document. Every call now passes encoding="utf-8".

3. terminate() promised to leave a lead ending in a closing quote or
   bracket after terminal punctuation alone, but tested only the last
   character: v3.0.0 returns 'the core says "enough.".',
   'the core rests (for now.).' and the same for curly quotes. The
   closers ("')] and U+2019/U+201D) are now looked through first; a
   closer with no stop inside still gets one.

4. The ANCHORS marker decode `m.encode().decode("unicode_escape")`
   re-reads UTF-8 bytes as Latin-1, so a non-ASCII marker comes back
   garbled ("Current release — v" -> "Current release \xe2\x80\x94 v")
   and silently matches no line. Checked: all 15 markers in today's
   table are ASCII with no escapes, so nothing is mis-bumped on v3.0.0;
   the defect is latent but real. `unescape_marker()` encodes as Latin-1
   with backslashreplace first, which round-trips non-ASCII and still
   decodes the Rust escapes.

Selftest: new cases for each -- four terminate() shapes, four
unescape_marker() cases, a UTF-8 chain document written as bytes, and a
cosim refresh against a temp root with no manifest. A harness running
the v3.0.0 functions on the same inputs under the ASCII locale fails all
five non-lockfile checks (the outputs quoted in 2-4); v3.0.0 has no lockfile function
to call, its failure path being the `except Exception` itself.
`--selftest` passes under the normal locale and under the ASCII locale.
ruff-check passes.

Bot findings: RustyNES#440/A/5382668515/1 (cargo metadata swallowed),
RustyNES#440/A/5382668515/3 (no explicit UTF-8),
RustyNES#443/A/5383852799/1 (terminate() ignores closing quote/bracket).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
`release_notes_are_not_hard_wrapped` (crates/rustynes-test-harness/
tests/release_notes_render_audit.rs) failed open in two places on
v3.0.0. `read_dir(...).flatten()` dropped any directory entry that
returned an error, and `read_to_string(f).unwrap_or_default()` turned a
read error -- including a non-UTF-8 file -- into an empty string, which
has no paragraphs and therefore no findings: a pass for a file the
audit never read. Each entry error and each read error now panics with
the path and the error.

Red first: with a probe `.github/release-notes/vzz-red-probe.md`
holding the bytes `v\xff\xfe broken`, v3.0.0's test passed (2 passed).
With the fix the same probe fails with "read .../vzz-red-probe.md:
stream did not contain valid UTF-8"; with the probe removed both tests
pass. cargo fmt --check and cargo clippy -p rustynes-test-harness
--all-targets -D warnings are clean.

Bot finding: RustyNES#440/A/5382668515/2.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
…ly if present

Two bot-review findings on scripts/agy-review.sh, which is installed
from the antigravity-pr-review template. The same change is committed
in the template (branch fix/v301-sweep, 9acd7f2). This repository's
copy is an older install than the template; this commit changes only
these two sites and does not re-sync the rest.

1. No test for the 406 fallback matcher. GitHub refuses an oversized
   diff with two different messages (over 20,000 lines, over 300
   files); both must reach the local-diff fallback, and the log must
   name the limit that fired. Nothing in the self-test exercised either
   message. The matcher and the label are now `diff_limit_hit()`
   inside a `SELFTEST-EXTRACT: diff-limit classifier` block, and the
   self-test feeds it both GitHub strings plus an unrelated HTTP 404.
   Behaviour is unchanged. Mutation check: narrowing the pattern to
   `(lines)`, or mislabelling the files case, each makes "406, files
   variant: falls back, named 300-file" FAIL.

2. API merge base used without checking it exists. If the SHA fetch
   failed and `--deepen=250` succeeded without reaching the merge base,
   `git merge-base ... || echo "$api_base"` used the absent object,
   logged "resolved via the compare API", and the failure surfaced as
   the generic "local git diff failed". The API value is now used only
   when `git cat-file -e "$api_base^{commit}"` succeeds; otherwise a
   specific line says the commit is not in the local clone, and the
   existing "could not compute the merge base" exit follows.

Red: the new self-test against the v3.0.0 agy-review.sh stops with
"SELFTEST-EXTRACT block 'diff-limit classifier' is missing or empty".
bash scripts/agy-review-selftest.sh: all checks passed (here and in
the template). shellcheck
reports the same findings as before the change, none on these lines.

Bot findings: RustyNES#374/A/5303548954/1, RustyNES#375/A/5303660637/2.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
…review

GitHub removed Dependabot's `reviewers` option in 2025 and routes review
assignment through CODEOWNERS instead (github.blog changelog 2025-04-29
announcing it, 2025-08-08 completing it). The key survived in two update
entries here (cargo at the root, github-actions); the current options
reference no longer lists it and the SchemaStore schema rejects it, which is
how the v3.0.1 sweep's tooling agent noticed it while validating the egui
grouping change.

Nothing about who reviews changes: `.github/CODEOWNERS` opens with
`* @doublegate`, so every Dependabot PR still requests the same reviewer.
`assignees` is a supported option and stays. One comment at the first site
records why the key is absent, so it is not "restored" later.

Verified: check-yaml passes; `grep reviewers:` matches only that comment.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
… 0036

Four bot findings on the provenance record (PR #346), re-verified on
v3.0.0 and fixed without removing or weakening any statement.

ADR 0036 Decision 3 and docs/originality-and-provenance.md say every
derived file's provenance header names its upstream file or function.
Three did not: kaiser.rs and ntdec.rs said only "derived from Mesen2",
and m069_sunsoft_fme7.rs named projects, not files. The citations now in
the headers come only from this repository's own record, never from the
upstream source:

- kaiser.rs: `Waixing/Mapper253.h`, already in the section 1 row and in
  the pre-v2.2.5 "Ported from Mesen2 Waixing/Mapper253.h" comment
  (git show 0265b3b^:crates/rustynes-mappers/src/kaiser.rs:609).
- ntdec.rs: `Txc/Bmc11160.h` (section 1), plus `Ntdec/NtdecTc112.h` and
  `Unlicensed/Mapper204.h`, which the file's own pre-v2.2.5 "Ported from
  Mesen2" comments named (0265b3b^ ntdec.rs:864-865, 1049-1050) and
  which section 1 had not recorded; the row now records them too.
- m069_sunsoft_fme7.rs: Mesen2 `NesSoundMixer::GetOutputVolume` over
  `Sunsoft5bAudio::_volumeLut`, the functions the file's pre-v2.2.5
  "Target, derived from Mesen2" comment named (0265b3b^ :115-118);
  section 1 now records the same. No Nestopia file is recorded anywhere,
  and the header says so rather than inventing one.

ADR 0036's Consequences said distributors "keep those terms for those
releases", asserting the old MIT/Apache grant was effective. Accepted
ADRs are not rewritten: a dated amendment adopts the provenance
document's neutral wording and leaves the bullet as the 2026-08-04
record.

The postmortem's executive summary stated the model's decision as fact;
section 5 says that reasoning is reconstructed from the result. The
sentence now carries that qualifier and points to section 5. Its
timeline called the transplanted code the "v2.8.0 engine stack" with no
lineage label, ambiguous now that RustyNES shipped its own v2.8.0; it is
labelled as the private RustyNES_v2 project's internal v2.8.0. The
quoted commit subject is unchanged.

Verified: cargo test -p rustynes-test-harness --test
provenance_record_audit passes (header and section 1 sets agree);
markdownlint passes on the changed files.

Bot findings RustyNES#346/T/PRRT_kwDOQrRows6WfQMt,
RustyNES#346/T/PRRT_kwDOQrRows6Wf6_-,
RustyNES#346/T/PRRT_kwDOQrRows6Wf7AB,
RustyNES#346/T/PRRT_kwDOQrRows6Wf7AG.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Since v2.6.7 `Cpu::handle_interrupts` freezes the NMI dispatch copy
`mc_prev_need_nmi` while `skip_irq_sample && skip_irq_sample_q`
(crates/rustynes-cpu/src/cpu.rs, the "taken-branch poll rule applies
to NMI too" block), following nesdev's CPU_interrupts, which says
*interrupts* are not polled before the third cycle of a taken branch.
Three records still described the pre-v2.6.7 behaviour:

- docs/cpu-6502.md's branch_delays_irq section and the rustdoc on
  `skip_irq_sample` both said "NMI sampling is unaffected -- the quirk
  is IRQ-only". Both now say the dispatch is deferred while the NMI edge
  latch (`mc_need_nmi`) keeps running, so an edge is never lost; the
  spec names `skip_irq_sample_q` and CPU snapshot version 4.
- The v4 note on `CPU_SNAPSHOT_VERSION` said a restore that dropped
  `skip_irq_sample_q` would resume with "the NMI edge detector re-armed
  a cycle early". The edge latch runs every cycle; what the field gates
  is the dispatch copy. It now says "the NMI dispatch gate".
- The published v2.6.7 release notes said "The emulation core is
  unchanged" and that AccuracyCoin and nestest held "by construction",
  while the same notes describe this CPU change and say the gates were
  re-run. Published notes are not rewritten: a dated
  "Correction (v3.0.1)" line follows the false sentence.

Comment and documentation changes only; no behaviour changes. The edits
to cpu.rs are outside its SH-group provenance region.

Verified: RUSTDOCFLAGS="-D warnings" cargo doc -p rustynes-cpu
--no-deps clean; cargo clippy -p rustynes-cpu --all-targets -D warnings
clean; RUSTUP_TOOLCHAIN=1.96.0 cargo check --release -p
rustynes-libretro clean; cargo test -p rustynes-test-harness --test
release_notes_render_audit --test snapshot_schema_audit pass;
markdownlint passes.

Bot findings RustyNES#477/T/PRRT_kwDOQrRows6dnsnR,
RustyNES#477/T/PRRT_kwDOQrRows6dnsnW,
RustyNES#477/T/PRRT_kwDOQrRows6dnsnd.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
…claims

Five bot findings on the MiSTer records, re-verified on v3.0.0.

- docs/mister.md "What this is, and what it is not" said in the present
  tense that the sibling holds "the harness at rung 0 and no RTL". It
  has carried RTL since v2.4.4 and holds a complete core. The sentence
  is now dated history (written 2026-08-20, first shipped in v2.4.2,
  per git log -S and git tag --contains fbf5364), and the licence
  paragraph's "all 57 files" is qualified as the v2.4.3 upstream-clone
  count, with the v2.6.6 re-verification against the vendored tree
  (40 HDL files, 4 GPL-3.0-or-later, 9 GPL-2.0-or-later, 0 GPL-2.0-only)
  cited from the sibling's licence audit.
- docs/mister.md and to-dos/mister/TASKS.md named
  `releases/RustyNES_MiSTer-vX.Y.Z.rbf` as the committed file. Since
  v2.6.15 the sibling commits `releases/RustyNES_YYYYMMDD.rbf`, the only
  form both MiSTer parsers accept, and attaches the version-named file
  to the GitHub releases. Both sites now name the two files separately,
  as the sibling's docs/bitstream-release.md does.
- docs/mister.md said all 148 gates carry "a mutation record apiece".
  The five blargg cpu_interrupts_v2 verdict gates v2.6.15 added have
  none (no rung document, and neither tb/mutate.sh nor
  tb/mutate_apu.sh names them; checked by grep in the sibling). The
  sentence now names that exception.
- to-dos/mister/contribution-checklist.md cited "the 142-gate suite";
  it now gives v3.0.0's ladder (199/0/1 on-die, 200/0/1 off-die, as
  README.md states).
- The v2.6.16 plan's acceptance line gave no off-die total; it now says
  147/147 on the die and 148/148 off-die (the SDRAM latency gate is N/A
  on the die).

Verified: cargo test -p rustynes-test-harness --test
contribution_checklist_audit --test mister_source_map_audit pass;
markdownlint passes on the changed files.

Bot findings RustyNES#434/A/5363668968/2,
RustyNES#477/T/PRRT_kwDOQrRows6dnsnY,
RustyNES#495/B/5119186180/README.md:686-686:266,
RustyNES#495/T/PRRT_kwDOQrRows6ffGcT,
RustyNES#495/T/PRRT_kwDOQrRows6ffGcX.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Five bot findings on the agent notes (PRs #529 and #534), re-verified
on v3.0.0.

docs/agents/tooling-traps.md recommended `git add -A && pre-commit run
<hook>` as the safe way to lint changed files. `git add -A` stages every
modified and untracked file, so the next commit takes unrelated work
with it. The bullet now recommends staging only the intended paths
(`git add -- <paths>`) or a NUL-delimited list
(`git diff -z --name-only -- '*.md' | xargs -0 pre-commit run <hook>
--files`), and records the old advice as superseded. It also names the
condition the trailing-space hazard needs: the list must reach argv
intact, which happens in zsh (no word splitting of an unquoted
parameter) or when the variable is quoted; bash or sh splitting an
unquoted `$FILES` drops the space.

The same file said the `gh api -F body=@file` form appears in the
PR-ceremony rule in review-bots.md; no file under docs/agents/ other
than tooling-traps.md contains `body=@`. The rule is now stated where
the pointer was: `-F body=@reply.md`, never `-F body=-`.

docs/agents/review-bots.md said CodeRabbit reports `Plan: Pro Plus` and
that its check "never resolves". Its comments on #583 and #589 report
`Plan: Advanced`, and its status context on #589 is SUCCESS once a
review is requested (gh pr view 589 --json statusCheckRollup). The
bullet now dates the plan and says the context stays pending only until
a review is requested.

Verified: markdownlint passes on both files; gh api
repos//issues/589/comments shows "Plan: Advanced".

Bot findings RustyNES#529/A/5744324402/1, RustyNES#529/A/5744324402/2,
RustyNES#529/A/5744324402/3, RustyNES#534/T/PRRT_kwDOQrRows6kG_Mv,
RustyNES#534/T/PRRT_kwDOQrRows6kG_M4.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Seventeen bot findings plus four drift items found during adjudication,
each re-verified on v3.0.0 against the code or git history.

Code-vs-doc:
- `Mapper::mix_audio` returns `i32` since v2.2.3 (mapper.rs); both
  docs/apu-2a03.md and docs/expansion-audio.md still printed `-> i16`.
- docs/frontend.md described v2.2.9's embedded `show_viewport_immediate`
  detach with real OS windows "tracked as follow-up"; v2.3.0's
  detached.rs gives each panel its own OS window (docs/STATUS.md says
  so). The paragraph now describes detached.rs and keeps v2.2.9 as
  history.
- ios/RustyNES/GameView.swift's comments named only Movies/TAStudio and
  Cheats as non-pausing; `updateMenuPaused()` pauses only for States,
  Settings and the Debugger, so Netplay, Achievements and Lua also keep
  the core running. Both comments now state the rule directly.
- docs/STATUS.md: the implied dummy read covers 22 official opcodes plus
  the six one-byte unofficial NOPs (28 opcode values, 23 match arms
  calling `Cpu::implied_dummy_read`), not "23 opcodes"; and one row used
  `->` where its neighbour uses the arrow.
- debugger/mod.rs rustdoc on `detachable_window` still showed the old
  `⧉` glyph and an `egui::CentralPanel`; the UI uses the Font Awesome
  EXPAND/COMPRESS glyphs and an `Area` + central-panel `Frame`.

Counts and figures:
- tests/roms/LICENSES.md: 33 AccuracyCoin sub-tests (git ls-files), not
  26, twice; 338 committed `.nes`, not 328; AccuracyCoin 144/144, not
  141/141.
- docs/performance.md: the F19 table's mmc3 saving now prints 7.5 us,
  the difference of its rounded columns (the unrounded means were not
  recorded), with a dated note; the memcpy estimate states its
  ~10-20 GB/s assumption (also in benches/snapshot_restore.rs); the
  5/5 sign-test p is written 1/32 = 0.03125 here and in CHANGELOG.md
  (the published v2.3.3 notes stay as recorded).
- docs/scheduler.md quoted ~3.9/2.5 ms and ~3.8/2.6 ms for the same
  thing; both now give the shipped fast-path figures from
  docs/performance.md "Current figures" (~3.95 / ~2.65 ms).

History:
- CHANGELOG [2.4.1] said the release-anchor audit and backlog sweep
  landed "between v2.4.0 and v2.4.1"; both landed in #427 (a1dd5fd,
  09:32) before v2.4.0 merged (b67c4f9, 13:20; merge-base confirms
  ancestry). CHANGELOG and VERSION-PLAN.md's v2.4.0 row now agree.
- VERSION-PLAN.md: the v2.3.0 row no longer calls it the head of the
  v2.x line; the v2.6.6 row marks its +0.363/+0.245 ns slack as
  withdrawn at v2.6.7 with the re-measured pair; the v2.6.16 row says
  three passes followed by a failure at +3 are the evidence.
- The v2.0.x mobile-finalization plan gains a banner marking its store
  line superseded by ADR 0035, and its iOS lines name the v1.9.x
  TestFlight train.
- ROADMAP.md / OVERVIEW.md "Last Updated" set to 2026-10-06, the date
  both were last edited (ad32ce3, 9c23715).
- SUPPORT.md called the root ROADMAP.md a pre-1.0 snapshot; it is the
  maintained project roadmap.

Verified: cargo fmt --all --check; cargo clippy -p rustynes-core -p
rustynes-frontend --all-targets -D warnings; RUSTDOCFLAGS="-D warnings"
cargo doc -p rustynes-frontend --no-deps; cargo test -p
rustynes-test-harness --test release_anchor_audit --test
release_state_prose_audit --test feature_flag_audit pass; markdownlint
passes on every changed Markdown file.

Bot findings RustyNES#194/B/4568745536/to-dos/plans/v2.0.x-mobile-finalization-plan.md:30:159,
RustyNES#201/B/4576269787/ios/RustyNES/GameView.swift:120:162,
RustyNES#342/T/PRRT_kwDOQrRows6WKQcA, RustyNES#344/T/PRRT_kwDOQrRows6WZY5g,
RustyNES#346/A/5186922238/1, RustyNES#347/T/PRRT_kwDOQrRows6Woict,
RustyNES#365/A/5286477079/2, RustyNES#367/A/5287157195/1,
RustyNES#372/A/5293088673/3, RustyNES#430/T/PRRT_kwDOQrRows6a8ZeA,
RustyNES#439/T/PRRT_kwDOQrRows6bbFw-, RustyNES#477/T/PRRT_kwDOQrRows6dnsnk,
RustyNES#495/T/PRRT_kwDOQrRows6ffGcV, RustyNES#507/A/5643265643/4,
RustyNES#507/T/PRRT_kwDOQrRows6hs6U8.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
…omment

Found by the v3.0.1 bot-sweep docs agent and confirmed by history: v2.2.5
(0265b3b), the relicensing release, reworded the Sunsoft 5B mix-level
comment in m069_sunsoft_fme7.rs from

    **Target, derived from Mesen2 (the project's accuracy bar) rather than
    from our own prior numbers.**

to "**Target, calibrated against Mesen2 ... as an oracle rather than against
our own prior numbers.**" -- in the same commit that added the file's
`// Provenance: ... derived from Mesen2` header. The derivation was therefore
never hidden (header, docs/originality-and-provenance.md section 1, NOTICE),
but the comment AT the derived value described a black-box comparison, which
is the wording this project reserves for genuinely independent code. Under
the never-launder rule that is the wrong direction, and the maintainer chose
to restore it (2026-10-06).

The comment now says "derived from Mesen2", points at the header and section
1, and records that v2.2.5 had reworded it and v3.0.1 restored it, so the
history is at the site rather than only in git.

Checked the rest of that release for the same pattern: v2.2.5 replaced ten
other "Ported from Mesen2 <file>" / "Ported from puNES <file>" site comments
with NESdev-wiki wording plus "no third-party emulator code is incorporated".
All ten have since been re-attributed ("the implementation is derived from
Mesen2's `<file>` (GPL-3.0-or-later). See NOTICE + ... section 1"), and every
cited upstream file is named in a header and in section 1. This comment was
the only one left.

Not changed, raised to the maintainer: rustynes-gfx-shaders/src/lib.rs:311
describes the Bisqwit-style NTSC pass as "an independent implementation ...
no third-party emulator code is incorporated". That is a self-certification
the provenance guardrails forbid, and the NESdev "NTSC video" page it cites
carries Bisqwit's published code, so whether the pass derives from it is an
open question for review, not one to settle by editing the sentence.

Comment-only; cargo fmt and clippy for rustynes-mappers clean.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
The Bisqwit-style NTSC pass documents itself as independent and says no
third-party emulator code is incorporated: a self-certification the
provenance guardrails forbid, about a pass named after the author whose code
the cited NESdev page publishes. Recorded as an open question for the
maintainer, with what would settle it, rather than decided by editing the
sentence.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Seven bot findings against crates/rustynes-cosim that were still valid on
v3.0.0.

checkpoint::from_bytes checked only that the length was a multiple of 16,
so a corrupt or non-monotonic .ckpt.bin given to checkpoint_diff reached
Divergence::window_len, whose `through_cycle - after` underflowed (a panic
in debug, a wrapped length in release). from_bytes now refuses a stream
whose through_cycle does not strictly increase (Hasher cannot produce one)
and window_len uses checked_sub. Red first: the two new tests failed with
"attempt to subtract with overflow" and an Ok parse of a backwards stream.
(Bot finding RustyNES#435/T/PRRT_kwDOQrRows6bAg8w)

nes_golden_export wrote <stem>.irq.csv before matching the checkpoint
result, so an overflowed trace left a truncated CSV with no overflow
marker on disk and then panicked. The CSV is now written in the Ok arm
only; obs.bin keeps its documented keep-on-overflow behaviour. Red first:
an_overflowed_trace_writes_no_irq_csv saw the CSV written and failed.
(Bot finding RustyNES#433/T/PRRT_kwDOQrRows6a_ssH)

Observable::from_cycle_record open-coded the bus-access numbering that
access_code already defines; it now calls access_code, and a new test maps
every BusAccess variant through both. (RustyNES#433/A/5363505721/3)

localisation_is_consistent had its two `false` arm comments swapped:
(Identical, Some) is the false negative that matters, (Diverged |
Inconclusive, None) the false positive. (RustyNES#435/T/PRRT_kwDOQrRows6bAg82)

Docs: take_irq_artifacts and take_checkpoints gain `# Panics` sections
for a zero interval (RustyNES#433/A/5363505721/2); the three rn_write_*
FFI writers replace "SAFETY: as above." with the invariant they rely on
(RustyNES#433/T/PRRT_kwDOQrRows6a_ssT) and each now says it consumes the
trace (RustyNES#436/A/5364286445/1). rn_write_irq_trace_csv's advice to
write the checkpoints first was wrong, since that writer consumes the
trace too; it now says so.

Verified: cargo fmt --check, cargo clippy --all-targets -D warnings and
cargo test, all with --manifest-path crates/rustynes-cosim/Cargo.toml.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Four low-severity bot findings, each still present on v3.0.0. None changes
emulation behaviour.

- rustynes-cpu: implied_dummy_read wrapped its single read1 call in a
  bare block left over from the removed cfg branches. The braces are gone.
  (Bot finding RustyNES#507/A/5643265643/1)
- rustynes-frontend config.rs: an empty line directly after
  `mod tests {`. Removed. (RustyNES#414/A/5349340796/3)
- fds_trace: the headered-.fds check compared only the first three bytes
  to "FDS" while the comment above it names the four-byte "FDS\x1a" magic.
  It now uses `disk.starts_with(b"FDS\x1a")` with the same 16-byte length
  guard, so code and comment agree. (RustyNES#39/B/4493453078/...fds_trace.rs:62:156)
- zapper_light_probe: `let bright = bright_px >= 2;` was never read and
  was discarded with `let _ = bright;`; the output prints bright_px
  directly. Both lines removed. (RustyNES#383/A/5309752445/8)

Verified: cargo fmt --all --check; cargo clippy -p rustynes-cpu
-p rustynes-test-harness --all-targets -D warnings; cargo test -p
rustynes-cpu; RUSTUP_TOOLCHAIN=1.96.0 cargo check --release -p
rustynes-libretro.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
parse_row read both the vendored table and the user overlay, and its
`filter(|&m| m != 0)` -- correct for the vendored table, whose unfilled
rows carry `0` and which has no separate empty marker -- also discarded a
mapper-0 override the user saved from the ROM Database panel. The
override applied in-session through upsert_user_entry, serialize_row
wrote it as `0`, and the next start read it back as "no override".

The overlay has an empty marker: serialize_row leaves the Mapper column
blank for None. So an overlay `0` is a deliberate NROM correction. The
overlay is now read by parse_overlay_row, which keeps it; the vendored
table keeps its rule through parse_row. Both share parse_row_from.

Red first: an_overlay_mapper_zero_override_survives_reload failed with
`mapper: None` where `Some(0)` was saved. It also pins that the vendored
reader still drops the `0` and that an empty overlay column stays None.

Bot finding RustyNES#373/A/5303905922/3.

Verified: cargo fmt --all --check; cargo test -p rustynes-gamedb (all
pass); cargo clippy -p rustynes-gamedb --all-targets -D warnings;
RUSTDOCFLAGS=-D warnings cargo doc -p rustynes-gamedb; RUSTUP_TOOLCHAIN=
1.96.0 cargo check --release -p rustynes-libretro.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
No unit test set the v6-tail fields (spr_halted[0..8],
prev_rendering_enabled, rendering_enabled_delayed, oam_corruption_pending,
oam_corruption_index, oam_corruption_disabled,
oam_corruption_disabled_instant) away from their power-on defaults before
a snapshot/restore. snapshot_round_trip does not touch them, and
snapshot_schema_audit proves only that the writer mentions each field, so
a reader that disagreed with the writer about order would still have
passed: every field reads back the value it already had.

The new test flips one field at a time and compares the whole tail after
restore, so a swapped pair of adjacent bools reads back as two wrong
fields. This is a coverage gap, not a behaviour defect, so the test is
green on the current code; it was checked by mutation instead: swapping
the reader lines for oam_corruption_disabled and
oam_corruption_disabled_instant failed this test and no other snapshot
test (26 passed, 1 failed). The mutation was reverted.

Bot finding RustyNES#250/T/PRRT_kwDOQrRows6PurIn.

Verified: cargo fmt --all --check; cargo clippy -p rustynes-ppu
--all-targets -D warnings; cargo test -p rustynes-ppu; RUSTUP_TOOLCHAIN=
1.96.0 cargo check --release -p rustynes-libretro.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
…overs

Four bot findings against rustynes-frontend, all still valid on v3.0.0.

Bisqwit index staging (medium). The two no-ROM present branches refilled
only present_staging (RGBA); present_index_staging kept the last game's
index frame, and close_rom cleared it to empty. Gfx skips the index
upload on a length mismatch and keeps the texture's previous contents,
so with the composite-rt (Bisqwit) filter on, closing the ROM left the
last game frame on screen instead of black. A new associated fn,
App::blank_present_staging, fills the RGBA half with zeros and, when an
index consumer is active, a full NES_W*NES_H index frame of colour $0F;
both no-ROM branches and close_rom now call it. Red first:
a_blank_present_stages_a_black_index_frame failed against a helper that
did only what the no-ROM branches did. (Bot findings RustyNES#33/B/
4492472013 at app.rs:4547:154 and app.rs:4620:155 -- one defect.)

Startup-path test (RustyNES#373/A/5303905922/2).
the_startup_path_applies_the_same_header_overrides_as_the_menu_path built
bytes that never matched a DB row, so it always took its early return
and never ran the comparison. The image now forges its last four hashed
bytes so the header-excluded CRC32 is Seicross's (0x0F05FF0A, mapper 185
submapper 4), the early return is gone, and it asserts the helper
changes the header and matches apply_header_overrides. Mutation check:
making apply_load_time_header_overrides a no-op fails it ("the startup
helper must rewrite the header"); reverted.

Run-ahead throttle (RustyNES#371/A/5290707031/1). The per-frame cost
model was written out separately in the engage cascade and the release
arm; both now call one private per_frame_cost, pinned by a unit test.

tick_iv (RustyNES#366/A/5287325218/1). tick_iv_ns is differenced on the
receiver, so a tick dropped on the depth-1 channel is never seen and the
next interval spans two sends, while its docs said "between successive
SENDS". The field docs now say "between delivered ticks" and that it
equals the send cadence only while tick_dropped is 0; docs/performance.md
gets a dated correction under the F15 table (drops were measured at 0 and
0-1 per capture, so the F15 conclusion stands).

Verified: cargo fmt --all --check; cargo clippy -p rustynes-frontend
--all-targets -D warnings with default, scripting, scripting,hd-pack,
retroachievements and full; cargo test -p rustynes-frontend --lib for the
three tests; pre-commit run markdownlint --files docs/performance.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Five bot findings against three audit tests in rustynes-test-harness,
each still valid on v3.0.0. Each audit could pass over prose or rows it
exists to check.

release_state_prose_audit: versions_near collected digits and dots
greedily, so a version ending a sentence ("v2.4.2.") read as "2.4.2.",
which triple() rejects, and the version was silently skipped -- the same
scanner feeds tag_claims. It now trims trailing dots before triple().
Red first: a_version_ending_a_sentence_is_still_found returned [] for
"Next up — v2.4.2.". The audit still passes on the current tree with the
fix, so no prose was hiding behind it. (RustyNES#438/A/5381656496/1)

release_anchor_audit: the theme check refused only `**`, `__` and a
backtick, so `~~strike~~` or a `[link](url)` would reach the plain-text
GitHub release title unrendered. The list moves into
title_markdown_marker() and gains "~~" and "]("; single `*`/`_` stay
allowed. Red first: title_markers_cover_strikethrough_and_links got None
for "~~". (RustyNES#459/A/5397645197/1)

feature_flag_audit, three findings:
- the table ended at the next blank line, so prose directly under it was
  read as table text (to EOF with no blank line). It now ends at the
  first line that does not start with `|`. (RustyNES#507/A/5643265643/2)
- a table line that did not parse as "| `flag` |" was skipped with
  `continue`, so a malformed row vanished while the row-count floor still
  passed. It now panics naming the line. (RustyNES#507/T/PRRT_kwDOQrRows6hs6Uy)
- both cross-checks pass over rows marked *(removed)*, so a manifest
  re-declaring cpu-implied-dummy-reads passed every test.
  no_removed_flag_is_still_declared now asserts that no removed row names
  a declared flag. (RustyNES#507/T/PRRT_kwDOQrRows6hs6U2)
The parsing moved into parse_feature_table(text) so it can be tested on
synthetic tables. Red first: the v3.0.0 parse loop, compiled standalone
with the two new tests, read a stray "| `gamma`" line after the table as
a row and did not panic on "| delta |". removed_but_declared is pinned
by a synthetic test; the real-tree check passes.

Verified: cargo fmt --all --check; cargo clippy -p rustynes-test-harness
--all-targets -D warnings; cargo test -p rustynes-test-harness for the
three test files (9, 16 and 7 passed).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
… rustdoc

`MAX_APU_TRACE_CAPACITY` (pub) linked `[`APU_REC_LEN`]`, a private const,
which `RUSTDOCFLAGS="-D warnings" cargo doc` rejects. It went unseen because
rustynes-cosim is excluded from the workspace and CI built its clippy and
tests but never its rustdoc; the v3.0.1 sweep's code agent found it by running
it by hand. The link is now a plain code span (the project rule for items a
public doc cannot link), and the lint job gains a cosim rustdoc step beside
the existing cosim clippy and test steps.

Verified: cosim `cargo doc --no-deps` with -D warnings passes; actionlint
passes on ci.yml.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
…released]

Two v3.0.1 results with no entry yet: the MiSTer core's dot-0 A12 rule
now gated on cycle-0 rendering (found by the new mapper4mmc3oddskip080
stimulus, which also gates the odd-frame exception through the new /IRQ-rise
comparison), and the back-to-#1 bot-review sweep (290 unanswered items, 473
verdicts, 80 still-valid findings fixed), listed by user-visible effect.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
…d on it

Three comments (.gitlab-ci.yml, docs/agents/libretro.md, ci.yml) said the
libretro buildbot cannot run a change before `main` moves, which made the
v3.0.1 toolchain split look untestable until after release. It is false.
libretro's mirror runs a pipeline for every pushed branch: the v3.0.0 cycle
has pipelines for release/v3.0.0 and both review slices, and the pushed
release/v3.0.1 got pipeline 119606 about 20 minutes later. All 15 jobs
passed on the RUSTUP_TOOLCHAIN=1.96.0 pin, including osx-x64, osx-arm64,
ios-arm64 and tvos-arm64, the four that `-C ar` threatens.

Recorded with two facts that make it usable: the job list is public through
the GitLab API while job logs need a login (401), and a review-slice branch
fails every job by construction because it holds only part of a release.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
`rustynes-gfx-shaders` documented `BISQWIT_WGSL` as "an independent
implementation of the NES composite signal model documented at the NESdev
wiki; no third-party emulator code is incorporated". That is a
self-certification, which docs/ai-emulator-provenance-guardrails.md forbids
as a finished claim. It was also false. The constant is
`include_str!("bisqwit.wgsl")`, and that file is generated verbatim from
`rustynes-frontend`'s `ntsc_bisqwit::shader_src()` (a drift test keeps the two
identical). `ntsc_bisqwit.rs` has long carried a `// Provenance:` header and a
section 1 row saying its coefficient tables were ported verbatim from
Bisqwit's C via Mesen2's `BisqwitNtscFilter` (GPL-3.0-or-later). So the
shared crate held a copy of recorded-derived code under a sentence denying
it. The maintainer's decision (2026-10-07) was to treat it as derived; the
existing record made that the only consistent answer.

Changes:
- lib.rs gains an SPDX line and a `// Provenance:` header naming
  `BISQWIT_WGSL` / `bisqwit.wgsl`. The doc comment now states the derivation
  and says it read "independent" until v3.0.1.
- Section 1 of docs/originality-and-provenance.md: the CRT row now names only
  crt_stack.rs (lib.rs merely re-exports those shaders), and lib.rs gets its
  own Bisqwit; Mesen2 row.
- NOTICE's video-filter section names the generated WGSL file.
- provenance_record_audit.rs: `ROW_WITHOUT_HEADER` is now empty. Its one
  entry was lib.rs, excused as "only re-exports the CRT shaders", and that
  excuse is exactly what hid the Bisqwit copy from the audit.
- to-dos/ROADMAP.md: T-NTSC-PROVENANCE closed, with the original record
  kept below the resolution.

Deliberately not done: no header in bisqwit.wgsl itself. The file is
generated byte for byte from shader_src(), and a header there would fail the
drift test. The lib.rs header covers the constant that includes it.

Verified: provenance_record_audit 2/2 pass. With the new lib.rs header
mutated away it fails every_section_1_row_names_a_file_that_carries_a_header
(caught), and passes again once restored. clippy -D warnings on
rustynes-test-harness and rustynes-gfx-shaders (all targets) is clean, fmt is
clean, and markdownlint passes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
doublegate and others added 2 commits October 7, 2026 00:38
v3.0.1 had split the toolchain. The workspace moved to 1.99.0, while the
libretro buildbot stayed on 1.96.0 (`RUSTUP_TOOLCHAIN` in .gitlab-ci.yml),
because its build image injected `-Car=<path>,Clink-arg=...` into every Apple
job and `-C ar` is a hard error from Rust 1.97. The seven crates that core
compiles kept `rust-version = "1.96"`, and libretro-cross built them on 1.96.

That premise had already expired. The v3.1 roadmap research found that
libretro-infrastructure/libretro-build-rust removed the flag on 2026-09-03
(841f3619, "Remove -C usage as no longer supported by rust"); the rebuilt
image's own pipeline passed on 2026-09-23. libretro's mirror builds every
pushed branch, so this was tested before merge rather than after.
test/libretro-rust-1.99 (771fdb8) changed only `RUSTUP_TOOLCHAIN` to
"1.99.0". Its pipeline 119614 passed all 15 jobs, including osx-x64,
osx-arm64, ios-arm64 and tvos-arm64, the four `-C ar` used to break.
Maintainer decision D5 (2026-10-07): test it in v3.0.1, and drop the pin if
everything is green.

Changes:
- .gitlab-ci.yml `.core-defs`: `RUSTUP_TOOLCHAIN: "1.99.0"`, the exact
  configuration 119614 tested. The variable stays because every job's
  `rustup toolchain install ${RUSTUP_TOOLCHAIN}` needs a name. The tvOS
  comment no longer calls `-C ar` a live hazard.
- The seven crates (cpu, ppu, apu, mappers, core, gamedb, libretro) now use
  `rust-version.workspace = true` (1.99); cargo metadata confirms it.
- ci.yml libretro-cross: instead of building on an older toolchain, it now
  FAILS if `.gitlab-ci.yml`'s RUSTUP_TOOLCHAIN differs from
  rust-toolchain.toml's `channel`. Both are parsed table-scoped and
  fail-closed. The step was extracted from the workflow YAML and run against
  copies: match -> rc 0, channel 1.100.0 -> rc 1 with the error, no
  [toolchain] channel -> rc 1. A pin move that forgets the buildbot is now
  caught on the PR, not on git.libretro.com after merge.
- rust-toolchain.toml keeps the two-line RUSTFLAGS fix in case a future image
  reintroduces `-C ar`. It adds the trap the research flagged: the image now
  separates its `-C link-arg` flags properly, so those link arguments reach
  the linker for the first time. They used to be swallowed as the `ar`
  value. 119614 links fine, but this is the first suspect if an Apple link
  ever changes behaviour.
- mmc3_boards.rs `mirror_bank`: three `size & size.wrapping_neg()` become
  `size.isolate_lowest_one()`. This is an exact rewrite (lowest set bit;
  `size` is never 0 because `count > 0`). Clippy's `manual_isolate_lowest_one`
  is MSRV-gated, so it fired only once the crate's rust-version reached 1.99.
  Trap worth knowing: a workspace `cargo clippy` run straight after the
  rust-version edit reported nothing, and only the `-p rustynes-libretro` run
  (a separate feature unification, so a fresh lint pass) flagged it. Clippy's
  cached results evidently do not key on rust-version; that is a hypothesis
  not tested further, and a fresh CI cache lints it either way.
- Docs: AGENTS.md toolchain paragraph, docs/agents/ci-and-release.md and
  libretro.md (a new bullet, "AND v3.0.1 UNDID THE SPLIT"), the canary
  header, CHANGELOG ("Rust 1.99, everywhere"), and the roadmap records:
  the line plan, v3.0.1 and v3.1.0 plans, libretro SPRINT_PLAN L1 (done),
  VERSION-PLAN and T-LIBRETRO-TOOLCHAIN (closes on the first green `main`
  pipeline after merge).

Verified on this tree:
- clippy -D warnings for the workspace, the scripting, scripting+hd-pack,
  retroachievements and full frontend sets, both wasm32 sets, and
  -p rustynes-libretro;
- rustdoc -D warnings, fmt, and the no_std thumbv7em build;
- mmc3_boards tests 33/0;
- release_anchor 14/0, release_state_prose 16/0, contribution_checklist 9/0;
- markdownlint, actionlint and check-toml.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Moves every release anchor from 3.0.0 "Cornerstone" to 3.0.1 "Mortar":
- the workspace and cosim versions, both Cargo.locks;
- the libretro .info display_version;
- Android versionName / versionCode 30001;
- iOS MARKETING_VERSION;
- the AGENTS, README, STATUS, VERSION-PLAN, ROADMAP, OVERVIEW, ARCHITECTURE,
  SECURITY and SUPPORT anchors.
CHANGELOG [3.0.1] is dated 2026-10-07, with an intro, a roadmap entry and a
Verification section. .github/release-notes/v3.0.1.md is the release body.

bump_release.py had a latent bug, found here and fixed in this cut. The
Cargo.toml anchor pattern `version = "{v}"` was unanchored, so it also matched
every internal path-dependency requirement. v3.0.0 moved those requirements
to "3.0.0", so the first cut whose OUTGOING release is an X.0.0 found 15 copies
of the string and refused. No earlier cut could see it, because the
requirements sat at "2.0.0" while the workspace moved through 2.x.y. Both
Cargo.toml patterns are now whole-line ("\nversion = ...\n"). A selftest case
builds a manifest with a matching requirement. Mutation: reverting to the
unanchored pattern makes that case FAIL (caught); the selftest passes
otherwise.

The script also refused, correctly, until to-dos/ROADMAP.md's release-line
chain got a written v3.0.1 entry (its "owed a summary" rule).

Records:
- VERSION-PLAN moves the forward v3.0.1 row into the release table, marked
  (current).
- README's MiSTer paragraph and STATUS's top-line counts now carry v3.0.1's
  numbers.
- The Cargo.toml comment no longer describes the one-day 1.96 split.
- The plan's outcome rows: item 5 is done; item 1, the palette A/B, is still
  pending a quiet host.

Verification on the final tree (3175827 + this cut; the cut changes no
code):
- test-roms 3,234 / 0 / 11; workspace 2,886 / 0 / 7; cosim 54 / 0;
- commercial 60 / 0, 137 / 0, 6 / 0;
- fmt, 18 clippy combinations, rustdoc, no_std;
- the seven release audits (release_anchor, release_state_prose,
  contribution_checklist, release_notes_render, provenance_record,
  libretro_info, cosim_manifest), 0 failed (per-audit counts not quoted: they
  came from one combined run, whose output order does not name the suite);
- bump_release --selftest;
- markdownlint.

MiSTer: ladders 200/0/1 on-die and 201/0/1 off-die. Seed 2 at 261007, with
byte-identical double compiles: on-die 7e81a718..., off-die 88d1dfa5....

NOT included: an uncommitted condensation of AGENTS.md (and an untracked
docs/history/ archive) written by another session at 01:55. This commit
stages AGENTS.md as HEAD plus only the anchor edits, produced by running the
bump in a clean worktree. Every other file was compared byte for byte against
that clean run. The other session's edits remain in the working tree for the
maintainer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository: doublegate/RustyNES/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: ba43412c-d97c-4aa9-b0ef-0b821053b393
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@context7

context7 Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Docs7 for doublegate/rustynes

Result Status Action
Deployment ➖ Not used —
Content review ➖ Did not run. This site has no agent runs available this month. Wait for the monthly reset or check your Docs7 plan. —

Commit 36c15ea

@doublegate

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@socket-security

socket-security Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedcargo/​tokio@​1.53.1 ⏵ 1.53.25810093100100
Updatedcargo/​libc@​0.2.189 ⏵ 0.2.19078 -910093 -7100100
Updatedcargo/​objc2@​0.6.4 ⏵ 0.6.57910093100100
Updatedcargo/​insta@​1.48.0 ⏵ 1.49.09310093100100
Updatedcargo/​cc@​1.5.1 ⏵ 1.6.09910093100100
Updatedcargo/​mlua@​0.12.1 ⏵ 0.12.295 -110093100100

View full report

@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review skipped: 264 files exceed the limit of 100.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@doublegate
doublegate marked this pull request as ready for review October 7, 2026 10:42
Copilot AI balanced review requested due to automatic review settings October 7, 2026 10:42
AGENTS.md is loaded into every agent session, so its length is paid on every
turn. Another agent session on this machine condensed it at 01:55 on
2026-10-07 and left the change uncommitted. The release cut (fa48dfc)
deliberately did not carry it: that commit staged AGENTS.md as HEAD plus the
version-anchor edits only. The maintainer has now reviewed the change and
directed it committed (2026-10-07).

What changed in AGENTS.md:
- per-region firewall paragraph: the dated incident narrative is condensed;
  the rule, the grep command and the escalation ladder remain;
- "Timebase (v2.0.0)" release bullet: removed from the current-release block;
- "(51 -> 172 families)": dropped from the release-history pointer (the count
  was stale; 191 now);
- engine-lineage, hot-path and commit-body paragraphs: condensed;
- "Exhaustive Documentation Sweeps" and "GitHub Wiki Initialization" bullets:
  removed.
Every removed or shortened passage is reproduced verbatim in the new
docs/history/AGENTS-archive.md, under its original line number (8 sections),
so nothing is lost and each removal can be reviewed against the original.

Not committed: docs/history/CLAUDE.local-archive.md, the same session's
archive of CLAUDE.local.md. It is matched by a global gitignore rule, because
CLAUDE.local.md is private session state.

Checks:
- markdownlint on both files: passes, after trimming one trailing blank line
  from the archive (MD012);
- release_anchor, release_state_prose, contribution_checklist and
  provenance_record audits: all pass on the condensed file.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Antigravity review (Gemini via Ultra)

A maintenance release that establishes Rust 1.99 across the workspace, patches MMC3 timing and Mapper 45, bumps the emulation epoch, and resolves pending review items.

Blocking issues

None found.

Suggestions

  • crates/rustynes-frontend/src/app.rs (~line 8880): In blank_present_staging, the if want_index block resizes the index texture and fills it with black, but it lacks an else branch. If want_index is false, index retains its previous 61,440-element size and is uselessly uploaded to the GPU on every frame. Add an else { index.clear(); } branch to prevent this.

Nitpicks

  • None.

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

Earlier review rounds (newest first)
Round reviewed at 2026-10-07 13:24 UTC

Antigravity review (Gemini via Ultra)

This maintenance release fixes CHR-RAM addressing for mapper 45, updates the project to Rust 1.99 along with all dependencies, resolves lingering code reviews, and lays out the roadmap to v4.0.0.

Blocking issues

None found.

Suggestions

  • crates/rustynes-core/src/bus.rs: Now that the MSRV is 1.99 and Box iteration compiles natively, you can replace the explicit &mut *self.ram reborrow with the more idiomatic self.ram.iter_mut() (or &mut self.ram) and remove the legacy 1.96/1.97 compatibility comment.

Nitpicks

  • In test assertions (e.g., crates/rustynes-frontend/src/debugger/cpu_panel.rs), replacing assert!(x.is_empty()) with assert_eq!(x, [] as [T; 0]) provides great failure output, but the explicit as [T; 0] cast is verbose. Type inference handles assert_eq!(x, []) natively for Vec<T>, which keeps the code cleaner.

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

Round reviewed at 2026-10-07 13:04 UTC

Antigravity review (Gemini via Ultra)

A maintenance patch (v3.0.1) that fixes Famicom Yarou graphics, MMC3 timing in the MiSTer core, updates the workspace to Rust 1.99, resolves pending review threads, and drops the vendored TriCNES reference.

Blocking issues

  • Breaking format change in a patch release: EMULATION_EPOCH is bumped from 1 to 2, causing the emulator to explicitly refuse v3.0.0 movies and netplay. Breaking compatibility with public on-disk/wire formats requires an appropriate version bump (MAJOR or MINOR), not a PATCH bump (v3.0.1).

Suggestions

  • crates/rustynes-core/src/movie.rs (line 372): The PR uses bytes.as_chunks::<8>(). Double-check that this won't break the stable build, as slice_as_chunks is currently an unstable/nightly-only feature.
  • crates/rustynes-core/src/bus.rs (line 1263): The comment notes for byte in &mut *self.ram was required as a workaround for Rust 1.96. Since the crate's MSRV floor is now 1.99, you can safely revert this to for byte in self.ram.iter_mut().

Nitpicks

  • crates/rustynes-cpu/src/cpu.rs (line 858): Removing the enclosing block around let _ = self.read1(bus, self.pc); is a clean simplification.
    I have completed the review based on the patch contents. Let me know if you need anything else!

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

Round reviewed at 2026-10-07 11:38 UTC

Antigravity review (Gemini via Ultra)

Bumps the emulation epoch for mapper 45 fixes, drops vendored TriCNES golden references, enforces Rust 1.99, and fixes script failure paths.

Blocking issues

None found.

Suggestions

  • crates/rustynes-frontend/src/app.rs (in blank_present_staging): The index buffer is only cleared if want_index is true. If false, it leaves potentially stale frame data allocated. Unconditionally call index.clear() outside the if block.

Nitpicks

  • crates/rustynes-core/src/cheats.rs (and other files): Asserting against empty arrays with assert_eq!(back.genie, [] as [crate::cheats::CheatEntry; 0]) is verbose. Use assert!(back.genie.is_empty()) instead.

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

Round reviewed at 2026-10-07 11:30 UTC

Antigravity review (Gemini via Ultra)

This PR bumps the Rust toolchain to 1.99 and updates dependencies, fixes an MMC3 timing exception, and increments the EMULATION_EPOCH to reject older movie and netplay formats.

Blocking issues

  • Breaking changes to an on-disk/wire format without an appropriate version bump: The release notes state that the EMULATION_EPOCH bump rejects movies and netplay peers from v3.0.0. A breaking change to public formats requires a major (or minor) version bump, but this is being shipped in a patch release (v3.0.1).
  • Silent failure path: In crates/rustynes-gamedb/src/lib.rs, fields[2].parse::<u16>().ok() silently swallows parsing errors for invalid mapper strings, converting them to None (unspecified) rather than explicitly rejecting the row or surfacing the error.
  • Data-loss / Hang: In crates/rustynes-mappers/src/mmc3_boards.rs, if count is 0, size.isolate_lowest_one() evaluates to 0, causing an infinite loop (while 0 + 0 <= 0 { size += 0; }).

Suggestions

  • Verify API stability: In crates/rustynes-core/src/movie.rs, bytes.as_chunks::<8>() relies on slice::as_chunks, which is historically a nightly-only API. Double-check that this has stabilized in Rust 1.99 to avoid build breakage.
  • Do not silence tool output: In .github/workflows/android.yml, piping (yes || true) into sdkmanager and directing stdout to /dev/null hides the installation logs. If sdkmanager fails before the test -x check, diagnosing the failure will be unnecessarily difficult.

Nitpicks

  • crates/rustynes-core/src/bus.rs: The comment indicates the crate builds on 1.96 for the libretro buildbot, but .gitlab-ci.yml and the release notes show the buildbot was successfully moved to 1.99.0. Update the comment to prevent confusion.

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

Round reviewed at 2026-10-07 11:20 UTC

Antigravity review (Gemini via Ultra)

Updates Rust to 1.99 across the workspace and libretro builds, resolves deferred review items, and corrects minor testing logic defects.

Blocking issues

  • .github/workflows/android.yml: The addition of set -euo pipefail breaks the sdkmanager installation step. yes | "$sdk/.../sdkmanager" ... > /dev/null will fail with exit status 141. When sdkmanager completes and closes stdin, yes receives SIGPIPE and exits with 141. Because pipefail is set, the pipeline inherits this failure and breaks the CI job.

Suggestions

  • crates/rustynes-core/src/bus.rs: The newly added comment states "this crate builds on 1.96 for the libretro buildbot". Since this PR simultaneously bumps the workspace rust-version and .gitlab-ci.yml to 1.99.0, this comment is obsolete upon merging. Update the comment to reflect 1.99.0.

Nitpicks

None found.

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

Round reviewed at 2026-10-07 11:02 UTC

Antigravity review (Gemini via Ultra)

This PR (v3.0.1) updates the MSRV and all environments to Rust 1.99, fixes CHR-RAM unbanking for Mapper 45, corrects an MMC3 A12 odd-frame interrupt timing issue in the MiSTer core (with co-simulation test coverage), groups egui/wgpu in Dependabot, and resolves a large backlog of historical review feedback.

Blocking issues

None found.

Suggestions

  • crates/rustynes-core/src/bus.rs (~line 1264): The comment explains that the &mut *self.ram iterator workaround is necessary because the crate builds on Rust 1.96 for the libretro buildbot. Since this PR updates the MSRV and the libretro buildbot to Rust 1.99 everywhere, you can likely remove this workaround and the comment.

Nitpicks

  • crates/rustynes-cheevos/src/lib.rs (and other test files): Replacing assert!(x.is_empty()) with assert_eq!(x, [] as [T; 0]) provides more detailed output on failure, but is_empty() is generally more idiomatic and readable.

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.

Copilot review overview

🟡 Changes recommended

Several changed files incorrectly retain the removed Rust 1.96 libretro exception, and the review-thread closeout helper can still miss paginated threads.

Review effort: Balanced
Findings: 1 Medium severity · 13 Low severity

Open (14)
What changed in this PR

Prepares the v3.0.1 “Mortar” maintenance release with an emulation fix, Rust 1.99 migration, provenance cleanup, dependency refreshes, and roadmap updates.

Changes:

  • Fixes mapper 45 CHR-RAM behavior and raises EMULATION_EPOCH.
  • Moves the workspace and release infrastructure to Rust 1.99.
  • Updates provenance records, review tooling, CI, documentation, and release metadata.
File Description
to-dos/​plans/​v2.6.16-interlock-plan.md Corrects co-simulation totals.
to-dos/​mister/​contribution-checklist.md Updates MiSTer evidence totals.
to-dos/​libretro/​IMPLEMENTATION_PLAN.md Marks the plan historical.
tests/​roms/​assorted/​README.md Clarifies ROM provenance.
scripts/​pr-review/​README.md Documents the new review self-test.
scripts/​perf/​perf_log_check.py Avoids unverified presentation claims.
scripts/​diag/​ppu2002_read_value_histogram.py Writes reports to stdout safely.
ios/​RustyNES/​GameView.swift Corrects sheet-pausing documentation.
ios/​README.md Updates the Rust prerequisite.
ios/​project.yml Bumps the iOS marketing version.
docs/​user-guide/​getting-started.md Updates the required Rust version.
docs/​testing-strategy.md Updates the CI toolchain reference.
docs/​scheduler.md Corrects measured performance figures.
docs/​expansion-audio.md Corrects the audio hook type.
docs/​dev/​STYLE_GUIDE.md Updates MSRV guidance.
docs/​dev/​BUILD.md Updates build prerequisites.
docs/​cpu-6502.md Documents taken-branch NMI deferral.
docs/​apu-2a03.md Corrects the expansion-audio API.
docs/​ai-emulator-provenance-guardrails.md Defines the TriCNES exception.
docs/​adr/​0036-relicense-gplv3-derivative-work.md Clarifies historical licensing terms.
docs/​adr/​0035-rustynes-is-permanently-non-commercial.md Schedules free store listings.
docs/​adr/​0032-vs-dualsystem-desktop-presentation.md Plans dual-system rewind/run-ahead.
deploy/​Dockerfile.raproxy Updates the Python image.
deploy/​Dockerfile Updates Rust and Debian images.
crates/​rustynes-test-harness/​tests/​vs_system_rgb.rs Adopts fixed-size slice chunks.
crates/​rustynes-test-harness/​tests/​vs_dualsystem.rs Adopts fixed-size slice chunks.
crates/​rustynes-test-harness/​tests/​snapshots/​external_coverage__mapper_045_GA23C_Famicom_Yarou_Vol_1_7_in_1_Unl.snap Updates the corrected framebuffer baseline.
crates/​rustynes-test-harness/​tests/​provenance_record_audit.rs Removes the shader-header exception.
crates/​rustynes-test-harness/​tests/​mister_source_map_audit.rs Updates empty-result assertions.
crates/​rustynes-test-harness/​tests/​holy_mapperel.rs Adopts fixed-size slice chunks.
crates/​rustynes-test-harness/​tests/​contribution_checklist_audit.rs Updates an empty-result assertion.
crates/​rustynes-test-harness/​src/​lib.rs Updates a version assertion.
crates/​rustynes-test-harness/​src/​coverage.rs Adopts fixed-size slice chunks.
crates/​rustynes-test-harness/​src/​bin/​zapper_light_probe.rs Removes dead state and modernizes chunking.
crates/​rustynes-test-harness/​src/​bin/​vs_dual_trace.rs Adopts fixed-size slice chunks.
crates/​rustynes-test-harness/​src/​bin/​smb3_dma_trace.rs Adopts fixed-size slice chunks.
crates/​rustynes-test-harness/​src/​bin/​fds_trace.rs Validates the complete FDS magic.
crates/​rustynes-test-harness/​src/​bin/​fds_swap_repro.rs Adopts fixed-size slice chunks.
crates/​rustynes-test-harness/​src/​bin/​fds_smoke.rs Adopts fixed-size slice chunks.
crates/​rustynes-test-harness/​golden/​tricnes/​tricnes-harness-src/​tricnes-harness.csproj Removes vendored TriCNES project source.
crates/​rustynes-test-harness/​golden/​tricnes/​tricnes-harness-src/​mappers/​Mapper_NROM.cs Removes vendored TriCNES source.
crates/​rustynes-test-harness/​golden/​tricnes/​tricnes-harness-src/​mappers/​Mapper_CNROM.cs Removes vendored TriCNES source.
crates/​rustynes-test-harness/​golden/​tricnes/​tricnes-full-src/​Properties/​Settings.Designer.cs Removes vendored TriCNES source.
crates/​rustynes-test-harness/​golden/​tricnes/​tricnes-full-src/​Properties/​AssemblyInfo.cs Removes vendored TriCNES source.
crates/​rustynes-test-harness/​golden/​tricnes/​tricnes-full-src/​Program.cs Removes vendored TriCNES source.
crates/​rustynes-test-harness/​golden/​tricnes/​tricnes-full-src/​packages.config Removes vendored TriCNES configuration.
crates/​rustynes-test-harness/​golden/​tricnes/​tricnes-full-src/​mappers/​Mapper_NROM.cs Removes vendored TriCNES source.
crates/​rustynes-test-harness/​golden/​tricnes/​tricnes-full-src/​mappers/​Mapper_CNROM.cs Removes vendored TriCNES source.
crates/​rustynes-test-harness/​golden/​tricnes/​tricnes-full-src/​LICENSE Removes the relocated source license copy.
crates/​rustynes-test-harness/​golden/​tricnes/​tricnes-full-src/​forms/​TriCNTViewer.cs Removes vendored TriCNES source.
crates/​rustynes-test-harness/​golden/​tricnes/​tricnes-full-src/​Forms/​TriC72PinConnector.Designer.cs Removes vendored TriCNES source.
crates/​rustynes-test-harness/​golden/​tricnes/​tricnes-full-src/​App.config Removes vendored TriCNES configuration.
crates/​rustynes-script/​src/​mlua_backend.rs Modernizes framebuffer chunking.
crates/​rustynes-script/​src/​lib.rs Updates empty-result assertions.
crates/​rustynes-script/​Cargo.toml Inherits the workspace MSRV.
crates/​rustynes-probe/​src/​atlas.rs Modernizes WRAM chunking.
crates/​rustynes-ppu/​src/​state_trace.rs Modernizes record parsing.
crates/​rustynes-ppu/​src/​lib.rs Updates a version assertion.
crates/​rustynes-ppu/​Cargo.toml Adds toolchain documentation.
crates/​rustynes-netplay/​src/​transport.rs Updates empty-poll assertions.
crates/​rustynes-netplay/​src/​signaling.rs Updates an empty-action assertion.
crates/​rustynes-netplay/​src/​relay.rs Modernizes hash-block parsing.
crates/​rustynes-mappers/​tests/​battery_sram_exposed.rs Makes header mutation const-capable.
crates/​rustynes-mappers/​src/​ntdec.rs Restores detailed provenance.
crates/​rustynes-mappers/​src/​lib.rs Updates a version assertion.
crates/​rustynes-mappers/​src/​kaiser.rs Restores detailed provenance.
crates/​rustynes-mappers/​src/​homebrew_boards.rs Updates an empty-save assertion.
crates/​rustynes-mappers/​src/​header.rs Simplifies test input construction.
crates/​rustynes-mappers/​src/​bmc_simple.rs Uses slice filling for bank tables.
crates/​rustynes-mappers/​Cargo.toml Adds toolchain documentation.
crates/​rustynes-libretro/​src/​lib.rs Modernizes pixel conversion.
crates/​rustynes-libretro/​src/​abi_tests.rs Modernizes file and pixel checks.
crates/​rustynes-libretro/​rustynes_libretro.info Bumps displayed core version.
crates/​rustynes-libretro/​Cargo.toml Adds toolchain documentation.
crates/​rustynes-ios/​src/​audio_ring.rs Uses f32::midpoint.
crates/​rustynes-gfx-shaders/​src/​lib.rs Records Bisqwit shader provenance.
crates/​rustynes-gfx-shaders/​src/​crt_stack.rs Updates non-empty assertions.
crates/​rustynes-gamedb/​Cargo.toml Adds toolchain documentation.
crates/​rustynes-frontend/​web/​Trunk.toml Pins Binaryen wasm-opt.
crates/​rustynes-frontend/​src/​wasm_io.rs Simplifies result checking.
crates/​rustynes-frontend/​src/​perf.rs Updates an empty-ring assertion.
crates/​rustynes-frontend/​src/​patch.rs Makes endian parsing const-capable.
crates/​rustynes-frontend/​src/​movie_srt.rs Updates empty-string assertions.
crates/​rustynes-frontend/​src/​input_macros.rs Updates an empty-bank assertion.
crates/​rustynes-frontend/​src/​icon.rs Modernizes RGB conversion.
crates/​rustynes-frontend/​src/​i18n.rs Updates non-empty assertions.
crates/​rustynes-frontend/​src/​help_tui.rs Makes topic lookup const-capable.
crates/​rustynes-frontend/​src/​genie_encode.rs Makes nibble encoding const-capable.
crates/​rustynes-frontend/​src/​genie_db.rs Updates empty-result assertions.
crates/​rustynes-frontend/​src/​emu_thread.rs Corrects tick-interval documentation.
crates/​rustynes-frontend/​src/​debugger/​tastudio_panel.rs Updates an empty-request assertion.
crates/​rustynes-frontend/​src/​debugger/​mod.rs Corrects detach UI documentation.
crates/​rustynes-frontend/​src/​debugger/​memory_panel.rs Updates an empty-cheat assertion.
crates/​rustynes-frontend/​src/​debugger/​memory_compare_panel.rs Updates an empty-cheat assertion.
crates/​rustynes-frontend/​src/​debugger/​latency_panel.rs Updates an empty-status assertion.
crates/​rustynes-frontend/​src/​debugger/​divergence_panel.rs Updates an empty-status assertion.
crates/​rustynes-frontend/​src/​debugger/​cpu_panel.rs Updates empty-poke assertions.
crates/​rustynes-frontend/​src/​debugger/​callstack.rs Removes redundant must_use.
crates/​rustynes-frontend/​src/​debugger/​badge_cache.rs Modernizes PNG conversion.
crates/​rustynes-frontend/​src/​debugger/​atlas_panel.rs Updates cleared-state assertions.
crates/​rustynes-frontend/​src/​debugger/​access_counter.rs Simplifies mutable iteration.
crates/​rustynes-frontend/​src/​config.rs Modernizes palette parsing.
crates/​rustynes-frontend/​src/​cli.rs Updates a non-empty assertion.
crates/​rustynes-frontend/​src/​cheats.rs Updates empty-cheat assertions.
crates/​rustynes-frontend/​src/​audio.rs Uses f32::midpoint.
crates/​rustynes-frontend/​src/​about_fx.rs Modernizes PNG conversion.
crates/​rustynes-cpu/​src/​snapshot.rs Clarifies NMI snapshot state.
crates/​rustynes-cpu/​src/​lib.rs Updates a version assertion.
crates/​rustynes-cpu/​Cargo.toml Adds toolchain documentation.
crates/​rustynes-cosim/​Cargo.toml Bumps version and Rust requirement.
crates/​rustynes-core/​src/​nes.rs Updates empty and label assertions.
crates/​rustynes-core/​src/​movie.rs Modernizes rolling-hash chunking.
crates/​rustynes-core/​src/​movie_interop.rs Simplifies controller letters.
crates/​rustynes-core/​src/​lib.rs Updates a version assertion.
crates/​rustynes-core/​src/​hardware_options.rs Raises and documents the emulation epoch.
crates/​rustynes-core/​src/​cpu_boot_trace.rs Modernizes record parsing.
crates/​rustynes-core/​src/​bus.rs Retains compatible boxed-array iteration.
crates/​rustynes-core/​Cargo.toml Adds toolchain documentation.
crates/​rustynes-core/​benches/​snapshot_restore.rs Clarifies benchmark math and chunking.
crates/​rustynes-cheevos/​src/​lib.rs Updates empty-list assertions.
crates/​rustynes-cheevos/​Cargo.toml Inherits the workspace MSRV.
crates/​rustynes-apu/​src/​lib.rs Updates a version assertion.
crates/​rustynes-apu/​src/​blip.rs Updates an empty-buffer assertion.
crates/​rustynes-apu/​Cargo.toml Adds toolchain documentation.
CONTRIBUTING.md Updates toolchain and MSRV guidance.
Cargo.toml Bumps the release and workspace MSRV.
ARCHITECTURE.md Updates release and MSRV metadata.
android/​app/​build.gradle.kts Bumps Android version and JSON library.
.pre-commit-config.yaml Updates Ruff.
.gitignore Removes obsolete TriCNES build exclusions.
.github/​workflows/​web.yml Updates Python and optimizer documentation.
.github/​workflows/​toolchain-canary.yml Updates toolchain history.
.github/​workflows/​security.yml Updates install-action.
.github/​workflows/​release.yml Moves releases to macOS 15.
.github/​workflows/​pgo.yml Updates the toolchain reference.
.github/​workflows/​ios.yml Updates the toolchain reference.
.github/​release-notes/​v2.6.7.md Corrects a historical release claim.
.github/​actions/​rust-setup/​action.yml Updates the pinned setup action.
.cargo/​config.toml Updates resolver/MSRV documentation.
Files not reviewed (10)
  • crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Forms/TriC72PinConnector.Designer.cs: Generated file
  • crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Properties/Resources.Designer.cs: Generated file
  • crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Properties/Settings.Designer.cs: Generated file
  • crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties.Designer.cs: Generated file
  • crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties3ct.Designer.cs: Generated file
  • crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCHexEditor.Designer.cs: Generated file
  • crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNESGUI.Designer.cs: Generated file
  • crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNTViewer.Designer.cs: Generated file
  • crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTASTimeline.Designer.cs: Generated file
  • crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTraceLogger.Designer.cs: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread scripts/pr-review/README.md Outdated
Comment thread .cargo/config.toml Outdated
Comment thread ARCHITECTURE.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread crates/rustynes-apu/Cargo.toml Outdated
Comment thread crates/rustynes-libretro/Cargo.toml Outdated
Comment thread crates/rustynes-mappers/Cargo.toml Outdated
Comment thread crates/rustynes-ppu/Cargo.toml Outdated
Comment thread docs/dev/BUILD.md Outdated
Comment thread docs/dev/STYLE_GUIDE.md Outdated
doublegate and others added 4 commits October 7, 2026 07:16
#590's first ready-mode run had two failures. Both came from this release's
own changes, which no earlier run had exercised.

1. Android, NDK cross-build + UniFFI bindings: "Resolve NDK" exited 1 after
   `yes: standard output: Broken pipe`. v3.0.1 replaced the runner's preinstalled
   NDK with an `sdkmanager --install "ndk;30.0.16248370"` step under
   `set -euo pipefail`, and fed it the licence answers with `yes | sdkmanager`.
   Once sdkmanager finishes and closes the pipe, `yes` exits with EPIPE (or is
   killed by SIGPIPE: 141). Under pipefail that status fails the pipeline even
   when the install succeeded. sdkmanager's own output goes to /dev/null, so
   the log showed nothing else. Reproduced locally:
   `set -euo pipefail; yes | head -1` exits 141.
   Fix in all three Resolve NDK steps: `(yes || true) | sdkmanager ...`. Only
   yes's status is absorbed. Checked:
   - a succeeding installer gives rc 0;
   - `(yes || true) | false` still gives rc 1;
   - the existing `test -x .../clang` check still fails the step if the NDK
     is not on disk.
   Not run before: the heavy Android jobs only run on a ready PR, and this is
   the first ready run since the NDK move (4aae8c1).

2. Pages, build demo + docs: `mkdocs build --strict` aborted on two warnings.
   They came from docs/history/AGENTS-archive.md (de90569), whose verbatim
   AGENTS.md paragraphs keep repo-root links (`docs/adr/0043-...`,
   `docs/adr/0041-...`); from inside docs/history/ those links point nowhere.
   The archive is engineering-only material, the same class as `agents/`,
   `archive/` and `audits/`, which the published handbook already excludes.
   `history/` joins `exclude_docs`, so the archive stays verbatim and the
   rewriting hook has nothing to do. A local `mkdocs build --strict` with the
   CI's pinned mkdocs-material range now builds with no warning.
   The run also logs one INFO-level performance.md anchor message; it is older
   than this change and not a strict-mode failure.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
…t scores

Records the v3.0.1 investigation of two Socket findings on #590 and main:
GHSA-qwgh-2vcv-g2f7 against block-buffer 0.10.4 (locked only as an optional
dependency of ratatui's unused termwiz backend; 0 compiled paths in every
build; no upstream fix exists), and the supply-chain scores of tokio, libc and
objc2 (fresh releases by their usual maintainers; libc's first Trusted
Publishing release; tokio wasm-only). Commands and numbers are in the bullets.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
3175827 moved the libretro buildbot to Rust 1.99 and gave every crate the
workspace floor. It left the split described as current in places the first
sweep missed (`git grep 1.96`, not run then). Copilot (#590, #591, #593),
CodeRabbit (#591) and agy (#590, #591, #593) each reported subsets.

Fixed:
- the seven libretro-path Cargo.toml files: the "builds this crate on Rust
  1.96.0" comment is deleted, since the inherited workspace value is the fact;
- .cargo/config.toml (the MSRV-aware resolver note);
- ARCHITECTURE.md's tree line;
- CONTRIBUTING.md's MSRV bullet;
- docs/build-and-tooling.md;
- docs/dev/BUILD.md, whose RUSTUP_TOOLCHAIN=1.96.0 command would now be
  rejected by cargo;
- docs/dev/STYLE_GUIDE.md;
- docs/benchmarks.md;
- the webOS comment in .gitlab-ci.yml, plus a header on its #91899
  post-mortem saying which two details have moved since (the pin value, and
  `rustup toolchain install ... --target` in place of `rustup target add`;
  Copilot on #592);
- the bus.rs reborrow comment. The `&mut *self.ram` reborrow is KEPT:
  iterating `&mut Box<[T; N]>` needs 1.97+, both forms compile on the 1.99
  floor, and changing working code buys nothing (agy suggested removing it).

Kept: every 1.96 reference that is dated history (CHANGELOG entries, the
2026-07-20 buildbot post-mortem, rust-toolchain.toml's account of the split,
the plans). A second `git grep -E "1\.96"` after this change shows only those.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
Bot findings from the v3.0.1 release PR and its CodeRabbit slices. Each code
fix below was pinned by a test that failed before it; the mutation results are
listed per item.

- list_unresolved_threads.py (Copilot #590, #592). This is the bot-closeout
  merge gate, and three inputs could still reach its "0 unresolved
  thread(s)" all-clear:
  - a TRUNCATED list: the query asks for `first:100` and nothing checked
    `pageInfo`. The payload must now carry `reviewThreads.pageInfo.hasNextPage`,
    the script refuses if it is missing, and refuses if it is true. The README
    query selects it.
  - an open thread with an EMPTY comment list, which was skipped (only a
    partial query can produce one);
  - an open thread with no `id`, which crashed with a KeyError instead of a
    named error.
  Selftest +4 cases. They were red before (the first two, and these two
  printed FAIL). Mutation: disabling the more-pages refusal fails "a list
  with more pages is refused" (caught).

- nes_golden_export.rs (Copilot #591). On an IRQ-trace overflow the CSV was
  already withheld, but an irq.csv and ckpt.bin from a PREVIOUS successful run
  with the same stem survived beside this run's obs.bin and read as its
  output. Both are now removed before the refusal (a missing file is fine; any
  other error is reported). The test pre-creates both stale files. It was red
  before ("an irq.csv ... was left on disk") and passes now: 8/8 bin tests.

- release_anchor_audit.rs (Copilot #592). The release-title check refused
  `**`, `__`, backticks, `~~` and `](`, but not a PAIRED single delimiter:
  `(an *important* fix)` and `(an _important_ fix)` render literally in the
  plain-text GitHub release title. A new `has_emphasis_pair` refuses `*` or
  `_` that opens emphasis (not after a word character, not before space) and
  is later closed (not after space, not before a word character). `3*4`,
  `snake_case`, `2 * 3 * 4` and `major_version` stay allowed. Red before
  (left: None, right: Some("*")); 16/16 pass now, the real CHANGELOG header
  included.

- Records:
  - NOTICE's TriCNES entry still said, in two places, that the source is in
    the tree (Copilot #593).
  - oracle-tooling-setup section 2a was titled "In-repo" and gave no
    destination for restoring the harness. It now gives a tested command that
    extracts straight to ~/reference-oracles (`git archive
    416fe7d^:<path> | tar -x -C <dir>`), never into the working tree.
  - ARCHITECTURE.md's Last Updated date (in the previous commit).
  - "Artefact" -> "Artifact" in the hardware plan; the docs use "artifact"
    302 times to 34.

Declined or refuted, with the evidence in the replies:
- agy's "ios/project.yml missing from MANIFESTS" (blocking): it is at
  bump_release.py:77.
- agy's misplaced cosim doc comment: it is already on rn_write_observables.
- agy's LICENSES count mismatch: 338 committed .nes and 33 sub-test ROMs,
  counted; the old 328 and 26 were stale on main.
- agy's as_chunks stability doubt: stable since 1.88, and CI builds it.
- The bump_release regex and gsub suggestions: the patterns are strict on
  purpose and fail closed.

Gates: fmt; clippy on rustynes-test-harness and rustynes-cosim; both pr-review
selftests; ruff; markdownlint.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
@doublegate

Copy link
Copy Markdown
Owner Author

Answering the Antigravity review (both rounds):

  • Blocking, android.yml pipefail: correct, and it was the real cause of the NDK job's failure on this PR's first ready run. Fixed in a5f7aea: all three Resolve NDK steps use (yes || true) | sdkmanager .... Reproduced locally (set -euo pipefail; yes | head -1 exits 141); (yes || true) | false still exits 1, and the clang check still fails the step if the NDK is missing.
  • bus.rs comment: fixed in eecf8ea; it no longer claims a 1.96 build. The reborrow itself is kept: both forms compile on the 1.99 floor, and the comment says where it came from.
  • assert_eq!(x, [] as [T; 0]) over is_empty(): declined. The point of the change is that a failure prints the unexpected elements; assert!(x.is_empty()) prints only false.

Review round 2 on the v3.0.1 slices (CodeRabbit on #592).

1. libretro-cross holds the new check that .gitlab-ci.yml's RUSTUP_TOOLCHAIN
   equals rust-toolchain.toml's channel. But its path filter (`libretro`)
   did not list .gitlab-ci.yml, so a PR changing only that file -- exactly
   the change the check guards -- skipped the job and passed. It now lists
   .gitlab-ci.yml.
   Two inputs the libretro core compiles were also missing from the filter:
   - crates/rustynes-gamedb, one of the seven libretro-path crates;
   - vendor/, which holds the patched rust-libretro-sys.
   Both are added; actionlint passes.

2. list_unresolved_threads.py checked only one end of the page range. A
   payload fetched with `after:` can be the LAST page: it has
   `hasNextPage: false` while earlier pages hold open threads, and it still
   printed the all-clear. The gate now requires `hasPreviousPage` and refuses
   if it is missing or true; the README query selects it. Two new selftest
   cases were red before and pass now (13 checks in all).

Declined on the same review: the request to delete the TriCNES trees and turn
the README back to I/O-only. The trees are deleted in the release PR (#590,
416fe7d), not in this slice, so that half is a cross-slice finding. The
consultation terms are the maintainer's decision D7 (2026-10-07), recorded in
the guardrails' section 3a.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
@doublegate

Copy link
Copy Markdown
Owner Author

Answering the Antigravity review (round 2):

  • "Breaking format change in a patch release" (blocking): declined, on a recorded maintainer decision. VERSION-PLAN.md was rewritten in this release (decision D2, 2026-10-07): a save-state, movie, netplay or epoch break may land in any release that says so, and MAJOR is reserved for a public Rust API break or a new kind of deliverable. That matches practice since v2.9.5 and v2.9.9. The release notes put the epoch rise first, under "Breaking change".
  • as_chunks on stable: slice::as_chunks has been stable since Rust 1.88; the workspace pins 1.99, and every CI leg builds it. No change.
  • Revert bus.rs to iter_mut(): declined. Both forms compile on 1.99; the comment now says why the reborrow is there, and rewriting working code buys nothing.

… fixed

All 14 inline findings held. The review body had no outside-diff, nitpick or
duplicate section.

Toolchain:
- docs/build-and-tooling.md's Channel bullet still told readers the libretro
  buildbot runs 1.96.0, the floor is checked on every PR, and a pin bump is a
  one-line edit. Round 1 (eecf8ea) fixed the MSRV bullet one line above and
  missed this one. It now says the buildbot uses the same toolchain and that
  both files move together (CI fails otherwise), and gives the 1.96 exception
  as history.
- docs/agents/libretro.md: the `-C ar` bullet is marked HISTORICAL, pointing to
  the "UNDID THE SPLIT" bullet for the current state.

Measurements:
- docs/scheduler.md and docs/performance.md called both frame costs "on the
  shipped fast dot path". Only nestest (~3.95 ms) is. flowing_palette
  (~2.65 ms) is rendering-disabled, so its `_fast` variant's guard bails and
  never enters that path; the bench source says so (full_frame.rs:125-126).
  It is now labelled as the control it is.

Records:
- CHANGELOG [3.0.1] Verification links docs/STATUS.md.
- OVERVIEW: v2.0.0 is "the first designated breaking release", not "the one"
  (v3.0.0 is MAJOR too).
- VERSION-PLAN's MAJOR rule: the new-deliverable trigger applies when that
  release is DESIGNATED MAJOR (ADR 0043 allows a minor; D1/D29 place it at the
  end of v3.9.x).
- The v3.0.1 plan separates its reply count from its resolve count:
  279 replies = 153 thread replies + 126 PR comments; 77 resolves.

External facts, checked before editing:
- ADR 0035 and the v3.9.0 plan, Android developer verification. It started
  2026-09-30 in four countries for apps distributed through the
  participating stores, and goes to certified devices worldwide in 2027.
  ADB and the "advanced flow" still install an unregistered app. The account
  paths: an individual gives photo ID and proof of address; an organisation
  gives organisation documents; limited distribution covers up to 20 devices
  without a government ID. Which path to take stays an open maintainer
  decision. (developer.android.com developer-verification guide; Help Net
  Security.)
- v3.9.0, iOS signing: iPhone and iPad uploads require the iOS/iPadOS 27 SDK
  from April 2027, as Apple announced on 2026-09-09; Xcode 27 is the tool that
  provides it. The plan previously called the date an inference.
- v3.4.0, hosting: "well inside the free 1,000 GB" had no forecast behind it.
  It is replaced by the capacity it implies: about 45,000 relayed
  player-hours a month at the inferred 22 MB per player-hour, with the
  allowance shared with Realtime SFU. The gate now measures the first
  month's traffic.
- v3.4.0, privacy: RetroAchievements' privacy-policy requirement and F-Droid's
  NonFreeNet anti-feature are now separate items. The anti-feature does not
  itself require a policy.

Also:
- v4.0.0, T-API-ENUMS now covers rustynes-netplay (NetMessage among its
  enums), which rustynes-core does not re-export.
- The v2.0.x plan: the blank line splitting two blockquotes (MD028) is now
  `>`.

Checks: markdownlint (pre-commit) on the linted files; the pinned 0.49.1 on
copies of the new plans, which reports only two MD049 errors older than this
change, in the ignored v2.0.x plan's lines 95-96; release_anchor 14/0,
release_state_prose 16/0, contribution_checklist 9/0, each run on its own.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
@doublegate

Copy link
Copy Markdown
Owner Author

Answering the Antigravity review (round 3): both items were answered in round 2 and stand -- the bus.rs reborrow is kept (it compiles on 1.99 either way, and the comment says why it is there), and [] as [T; 0] stays explicit, so each assertion names the element type it compares.

@doublegate

Copy link
Copy Markdown
Owner Author

Answering the Antigravity review (round 4): the blank_present_staging point was refuted in round 2, and it still holds. Nothing uploads the index buffer when want_index is false: both present paths pass want_index.then_some(self.present_index_staging.as_slice()) (app.rs:11044 and :11465), so Gfx receives None whatever the buffer holds. Leaving it untouched is the zero-cost behaviour that app.rs:12151-12154 pins.

@doublegate
doublegate merged commit 48173a3 into main Oct 7, 2026
41 checks passed
@doublegate
doublegate deleted the release/v3.0.1 branch October 7, 2026 14:26
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