chore(release): cut v2.3.9 "Crucible" - #426
Conversation
The gates release. Not a feature cut: the v2.3.x line added five tools in four releases, and the recurring finding across all of them was never that the emulation was wrong -- it was that a check reported a pass it had not earned. This release went looking for the rest of them and found that the docs-only CI skip had never worked, that the accuracy battery could not run on the PR that broke it, and that a provisioning bound had been calibrated against a claim rather than a measurement. The CHANGELOG's `[Unreleased]` is split rather than renamed. Its Divergence Lens entry is v2.3.8's marquee and is cut on `release/v2.3.8` from that work's own boundary commit, so on `main` it becomes a `[2.3.8]` section recording what that tag contains. Everything from #408 onward is `[2.3.9]`. Version bumped in the two places that carry it -- the workspace manifest and `rustynes_libretro.info`'s `display_version`, which `libretro_info_audit.rs` pins against the manifest. All three of its assertions pass. Note the manifest goes 2.3.7 -> 2.3.9 on `main`: 2.3.8 exists only on its release branch, which is the consequence of cutting it from its boundary commit so its artifacts contain exactly Parallax. `rustynes-apu` and `rustynes-core` both change in this release, so the accuracy battery is VERIFIED rather than asserted, and was re-run after the second round of deletions rather than only the first: AccuracyCoin (RAM): pass rate = 100.00% over 141 assigned tests nestest: 1 passed That is the authoritative decoder quoted case-sensitively; the framebuffer decoder reports 120 and is known-buggy. Both with a confirmed non-zero test count, since a filter matching nothing exits 0. Also green on this tree: fmt, workspace clippy under `-D warnings`, the `no_std` thumbv7em cross-compile, and every workspace suite.
…wrong about more than the version
Every "current release" claim outside CHANGELOG.md had drifted, each by a
different amount -- which is the predictable outcome of writing one fact down in
eight places with no gate asserting they agree:
README.md badge + Current Release .... v2.3.7
docs/STATUS.md header ................ v2.3.7
AGENTS.md (both anchors) ............. v2.3.7
VERSION-PLAN.md header ............... v2.3.6 (table stopped at v2.3.5,
still marked "(current)")
to-dos/ROADMAP.md .................... v2.3.3 in Status, v2.2.5 in the
release-line paragraph
SUPPORT.md ........................... v2.3.0
ROADMAP.md (root) .................... v2.0.4
All now read v2.3.9 "Crucible", with v2.3.8 "Parallax" and v2.3.7 "Overtone"
demoted into the historical trail rather than dropped. VERSION-PLAN.md's release
table gains rows for v2.3.6 through v2.3.9 and the "(current)" marker moves off
v2.3.5. AGENTS.md's "never claim any version later than vX" guard moves to
v2.3.9, so the rule and the tree agree again.
Two of these carried a claim worth considerably more than the version number.
SECURITY.md still offered support for 1.0.x and marked < 1.0 unsupported. That
is a policy table describing a project two major versions and eleven months
behind the tree, sitting on the one document a reporter reads before deciding
whether a finding is worth sending -- the failure mode is not embarrassment, it
is a real report that never gets written because the policy implies the tree is
unmaintained. It is rewritten for the rolling patch cadence RustyNES actually
ships on (fixes land in the next patch release; there are no long-lived release
branches and nothing is backported), and it now names the two boundaries that
change what a report MEANS rather than merely how old it is:
* v2.0.0 "Timebase" (ADR 0003 / ADR 0028) -- a pre-v2.0.0 .rns or .rnm is
refused with a clear error rather than reinterpreted, so a pre-v2.0.0
parsing report is not reproducible against a current build BY DESIGN. That
is a different conversation from "unsupported version".
* v2.2.9 (ADR 0036) -- the GPL-3.0-or-later relicence is a licensing
correction, not a SemVer break; no public API or on-disk format moved.
The root ARCHITECTURE.md presented the retired dot-lockstep scheduler as the
current design: tick_one_dot(), the `ppu.tick_count % 3 == cpu_phase_offset`
phase test, and the explicit claim that the bus need not re-sync the PPU or APU
inside an access "because they were already advanced in lockstep above". None of
that has been true since v2.0.0 shipped on 2026-07-03. docs/architecture.md and
docs/scheduler.md each carry a v2.0.0 "Timebase" banner correcting exactly this,
and docs/architecture.md additionally labels its stale subsections "(historical
-- see the banner above)"; the root companion had neither, so the one
architecture document a newcomer opens first was the one describing a scheduler
that no longer exists.
ARCHITECTURE.md now carries the same banner, spelling out the shipped model
rather than merely disclaiming the old one: start_cycle/end_cycle advance
master_clock by the region's phi1/phi2 split (NTSC asymmetric 5/7 on a read, 7/5
on a write; PAL 16; Dendy 15) and pull the PPU to master_clock - PPU_OFFSET at
BOTH halves via run_ppu_to, one dot per iteration, so the CPU's bus access lands
between the two catch-ups; every instruction cycle is a real bus access; DMA is
the unified per-cycle-interleaved engine; and LockstepBus::cycle is the single
canonical counter that Cpu::cycles and Apu::cpu_cycle are ASSIGNED from rather
than independently incremented. Its two stale subsections are labelled, and the
per-access fanout paragraph now contrasts the two orderings instead of asserting
the retired one.
It also states the part that did NOT change, because deleting the old text would
lose it: lockstep was selected so a mid-instruction PPU event -- sprite-zero hit
at a precise dot, a mid-scanline scroll write, an MMC3 IRQ at dot 260 -- is
visible to the rest of the instruction without a per-quirk patch. That
consequence survives the mechanism moving, which is why the load-bearing decision
below the banner still stands as written. The one-clock model makes the ordering
explicit where the dot loop left it implicit, and that is also why a single
counter is structural rather than tidy: two counters advanced by two rules is
precisely the bug the split exists to make unrepresentable.
This release is about checks that report a pass they have not earned. A spec
describing the previous implementation is the same failure with a longer fuse,
and this project has already paid for it once -- Pixel Provenance shipped
non-functional for four releases behind a comment asserting it worked.
Separately, the user guide caught up with the menu it documents.
docs/user-guide/analysis-tools.md described three tools where the UI offers five:
Divergence Lens (v2.3.8) and Audio Provenance (v2.3.7) were both reachable and
both undocumented, and docs/user-guide/menus.md listed neither. Both now have
sections, in the register the rest of the page uses -- what the tool answers,
what it refuses to claim, and where the full spec lives. The Divergence Lens
entry leads with the reason it exists (a per-frame hash is the right shape for
DETECTING a difference and the wrong shape for EXPLAINING one, because it has
nothing to hand Pixel Provenance) and gives Inconclusive its own paragraph.
Audio Provenance records the two registers that are NOT attributed -- $4014 and
$4016, both handled by the bus without routing through Apu::write_register --
because they were documented as attributed before v2.3.7 and were not.
v2.3.9's RAM Atlas -> RAM Watch export is documented with the reasoning intact:
the lens travels with the address because liveness is relative to what was
observed, so an unqualified "LIVE" in a watch list would outlive the panel that
qualified it and become a claim nobody can check. The absent cheat, Lua and
RetroAchievements exports are recorded as a decision rather than a gap -- a cheat
is a WRITE, so it needs the locked-session predicate the read-only watch export
correctly does without.
CHANGELOG.md gains a Documentation section under [2.3.9] and
.github/release-notes/v2.3.9.md the corresponding section, so the release body
carries this rather than leaving it to the commit log.
Documentation only: no source file changes, so AccuracyCoin 141/141 and the
nestest 0-diff result already verified for this release stand unchanged. Gates
run: `pre-commit run --files <changed>` (markdownlint, trailing whitespace,
end-of-file, mixed line ending) -- all passed; cargo hooks correctly skipped with
no source files staged.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: ✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
|
…n re-dating it Second pass of the v2.3.9 documentation sweep, covering the planning tree and the issue templates -- the surfaces a reader trusts to tell them what is finished. PLAN STATUS LINES. `to-dos/plans/v2.3.7-overtone-plan.md` still read `**Status:** IN PROGRESS`, for a release tagged on 2026-08-19 and superseded twice since. The v2.3.8 and v2.3.9 plans carried no status line at all, so nothing in either file distinguished "planned" from "shipped" without cross-referencing the CHANGELOG. All four now state it in the same place and the same form: v2.3.7 "Overtone" SHIPPED 2026-08-19 (tag v2.3.7) v2.3.8 "Parallax" SHIPPED 2026-08-20 (tag v2.3.8) v2.3.9 "Crucible" SHIPPED 2026-08-20 (tag v2.3.9) v2.4.0 "Concordance" PLANNED -- opened 2026-08-19, on top of v2.3.9 The v2.3.8 line additionally records that its tag was cut from the boundary commit `5ba66392` rather than from `main`, because that is the fact that explains why its release artifacts contain the Divergence Lens and nothing from the v2.3.9 branch -- and it is not recoverable from the tag alone, where `gh release view` reports a cosmetic `targetCommitish=main`. ISSUE TEMPLATE. `.github/ISSUE_TEMPLATE/bug_report.md` prompted for a version with the example `v0.1.0` -- a tag from 2025-12-19, twenty-three releases and two major versions ago. A stale example in a form field is not cosmetic: it is the value a reporter copies when they are unsure what to put, and a bug filed against a v0.1.0-shaped version is one that has to be re-triaged before it can be reproduced. Now `v2.3.9`, with the git-hash alternative kept for source builds. DEFERRED BACKLOG. `to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md` was last reconciled entry-by-entry at v2.3.4 (2026-08-15). Five releases have shipped since, so some entries below it are certainly closed without being struck. The tempting edit was to re-date the header and move on. That is precisely the failure this release exists to catch: a catalogue that LOOKS audited but is not is worse than one that admits its cut-off, because the first is trusted and the second is checked. So the file gets a dated audit-currency note instead -- naming the real cut-off, naming the five unswept releases, and telling the reader to check CHANGELOG.md for anything dated after 2026-08-15 before treating an entry as open. The entry-level sweep is left as real remaining work rather than being claimed. Documentation only; no source changes. Gates: `pre-commit run --files <changed>` (markdownlint, trailing whitespace, end-of-file, mixed line ending) all passed, cargo hooks correctly skipped with no source files staged. The AccuracyCoin 141/141 and nestest 0-diff results already verified for this release are unaffected.
…ed on `docs/frontend.md` documented the ROM-transition hook as "results are ROM-bound and cleared through one hook", which described the v2.3.6/v2.3.7 state: three analysis panels whose stale output was cosmetic, plus the Latency Oracle's queued Apply depth, which was not. That framing is what let the actual hazard sit outside the hook for three releases. Both memory panels' FREEZES feed the app's raw-cheat overlay, applied after every frame, and neither panel was registered with the hook. A byte frozen while playing one cartridge therefore went on being written into the next -- every frame, at an address that means something else there. Not a stale label: an active write into the wrong game, silent, and indistinguishable from the game misbehaving. The spec now carries the rule the sweep settled on, so a new panel can be measured against it rather than against a list of the panels that happened to exist: derived output is DISCARDED, user-authored input is KEPT, and only input that actively WRITES is neutralised. RAM Search baselines, reconstructed call stacks, access counters and debug telemetry clear -- they describe a cartridge that is gone. Watch lists and breakpoints survive, because the user typed those and would have to retype them. Breakpoints stay ARMED, and the asymmetry is the point: a breakpoint halts, which is visible and recoverable, where a freeze writes, which is silent and continuous. A rule that treated both as "user input, therefore keep" would have kept the bug. Two negatives are recorded because they cost real time to establish and the next reader would otherwise re-derive them: the header editor LOOKS ROM-bound and is not (it operates on a path, not on the loaded cartridge), and the event panel, trace status and HD-pixel coordinates are per-frame state or preferences -- rebuilt or intentionally persistent, not carried across a transition. Also updates `docs/user-guide/README.md`, whose analysis-tools row still listed three tools where the menu now offers five (Audio Provenance since v2.3.7, Divergence Lens since v2.3.8) -- the same drift corrected in `analysis-tools.md` and `menus.md` in the previous commit, one level up in the index. Documentation only; no source changes. Gates: `pre-commit run --files <changed>` passed (markdownlint, trailing whitespace, end-of-file, mixed line ending).
There was a problem hiding this comment.
Pull request overview
This PR cuts the v2.3.9 “Crucible” release by bumping the workspace version and updating documentation/metadata anchors across the repo so “current release” references are consistent and the user guide reflects the latest analysis tools.
Changes:
- Bump workspace/crate versions to 2.3.9 (including libretro
display_version) and refresh release references in top-level docs. - Update user guide documentation to include Audio Provenance and Divergence Lens, plus document the RAM Atlas → RAM Watch export.
- Clarify “historical vs shipped” scheduler documentation and refresh high-level architecture/overview docs to match the v2.0.0+ timebase model.
Reviewed changes
Copilot reviewed 25 out of 26 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| SUPPORT.md | Updates “current release” reference to v2.3.9. |
| SECURITY.md | Rewrites supported-versions policy table for the current release line. |
| ROADMAP.md | Updates project status/current release references in the historical snapshot roadmap. |
| README.md | Updates badges + “Current Release” narrative for v2.3.9. |
| OVERVIEW.md | Updates document metadata and summarizes progress since v1.0.0 through v2.3.9. |
| docs/user-guide/menus.md | Documents menu entries for Audio Provenance + Divergence Lens. |
| docs/user-guide/analysis-tools.md | Adds Audio Provenance + Divergence Lens sections and RAM Watch export docs. |
| docs/scheduler.md | Labels historical dot-lockstep sections as historical (v2.0.0+ note reference). |
| crates/rustynes-libretro/rustynes_libretro.info | Bumps libretro display_version to v2.3.9. |
| Cargo.toml | Bumps workspace package version to 2.3.9. |
| Cargo.lock | Updates crate versions to 2.3.9 across the workspace. |
| ARCHITECTURE.md | Updates document metadata and clarifies historical vs current scheduler model. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The Antigravity reviewer flagged the rewritten support table for using GitHub emoji shortcodes against the project's standing "no emojis in code, commits, comments, or docs" rule. The finding is correct and worth acting on rather than waving through on precedent. Two of the three shortcodes were inherited -- `:white_check_mark:` and `:x:` have been in this table since it was written -- but the previous commit rewrote the table wholesale and added a third, `:warning:`, for the partial tier. Carrying a rule violation forward into a table I had just rewritten, in the release whose subject is checks that report a pass they have not earned, is not a defensible place to invoke precedent. The column now reads Yes / Partial / No. That is also strictly more legible than the shortcodes were: `:warning:` had no self-evident meaning in a support column and depended entirely on the Notes cell beside it to be read at all, whereas "Partial" states the tier and lets the note explain the mechanism (fixes ship forward into the current line rather than being backported). Column widths are normalised in the same edit so the source table lines up. This was the only such site in the repository outside the vendored and archived trees -- the one other apparent hit, `::warning::` in the v2.3.9 plan, is a GitHub Actions workflow-command annotation and not an emoji at all, so it stays. Documentation only. `pre-commit run markdownlint --files SECURITY.md` passes.
|
Addressing the Antigravity review.
Worth recording why this wasn't waved through on precedent: two of the three shortcodes were inherited ( The text version is also strictly more legible than the shortcodes were. I swept the repository for the same pattern: Nitpick — "trivial, only version bumps and documentation updates": accurate as a description of the diff, and I'd flag that it undersells two of the changes rather than disputing it. |
…he actions, not the panel Copilot flagged a contradiction in the RAM Atlas section, and it is a real one in text added by this branch. The section led with **Not available during netplay, TAS recording or playback, or RetroAchievements hardcore.** and the new export subsection two paragraphs later said the export "is available in a locked session". Read in sequence those cannot both be true. The sentence after the bold lead did say "Both actions", so the restriction was correctly scoped one clause later -- but a reader scanning bold leads, which is how a reference page is actually read, takes "Not available" as applying to the panel. That is the reading the export paragraph then contradicts. Corrected at the source of the ambiguity rather than by softening the export sentence: the lead now names the two actions it governs and says explicitly that it is the actions and not the panel, and it states the positive consequence -- the panel stays open, and everything already classified stays readable and exportable. That is the fact a user needs when the panel is greyed out mid-session and they are wondering whether the classification they just spent minutes building is still reachable. It is. The export sentence is tightened to match: it is a pure read of a result the panel already holds, so it advances nothing and writes nothing, which is the property that makes it safe in a locked session -- rather than merely contrasting it with Verify. The distinction matters because it is exactly the reason the cheat, Lua and RA exports remain unbuilt: a cheat is a WRITE and needs the locked-session predicate this one correctly does without. Worth noting where this finding was: there is NO review thread and nothing in the review body -- it exists only as an inline comment reachable through `gh api repos/.../pulls/426/comments`. A resolve-every-thread sweep would have merged straight past it, which is the third hiding place AGENTS.md documents and the reason the ceremony fetches all three. Documentation only. `pre-commit run markdownlint` passes.
…erforms The comment above `[workspace.package] version` told the next person cutting a release to perform the CHANGELOG's "`[Unreleased]` -> `[X.Y.Z]` rename". That is module 70's wording, and it is not what this repository does -- caught by the Antigravity reviewer noticing that this PR inserted `## [2.3.9]` BELOW an intact `## [Unreleased]` rather than renaming it. The reviewer read it as the CHANGELOG deviating from the instruction. It is the other way round: the CHANGELOG is right and the instruction was wrong. Every release in this file's history has kept `## [Unreleased]` as a permanent header and inserted the new version section directly beneath it -- which is the only workable arrangement, because renaming the header would delete the section the next development window writes into, and `[Unreleased]` would then have to be re-added by hand at the start of every cycle or silently go missing. Following the instruction literally would therefore have produced a wrong CHANGELOG, and it was the FIRST thing a release-cutter reads, sitting directly above the field they came to edit. In the release whose subject is checks and documents that assert something their subject does not do, leaving that in place was not defensible. The comment now says "promotes its accumulated entries into a new `## [X.Y.Z]` section inserted directly below it", records why renaming is wrong, and adds the part that was missing entirely: the header format is load-bearing rather than decorative. `release-auto.yml` parses that exact line twice -- once to extract the `## [X.Y.Z]` section as the release-body fallback when no `.github/release-notes/vX.Y.Z.md` override exists, and once to derive the release TITLE's codename by stripping the version and date prefix. A malformed header degrades to the bare title `RustyNES vX.Y.Z` with no codename, or to empty notes and a failed job. Nothing else in the tree said so at the point of use. Also refuted, and recorded so it is not re-raised: the same review suggested updating the CHANGELOG's reference-link definitions at the bottom of the file (`[Unreleased]: ...`, `[2.3.9]: ...`), noting they were "not visible in the diff". They are not visible because they do not exist -- `grep -cE '^\[[^]]+\]: https?://' CHANGELOG.md` returns 0, and the file ends with the `[0.1.0]` entry. Keep-a- Changelog uses that convention; this file never adopted it, so there is nothing to update. No change made. Comment only -- no manifest field, dependency, or version changed; the workspace version is still 2.3.9 and `cargo metadata --no-deps` parses clean.
|
Addressing the second Antigravity pass. Nitpick — the You read this as the CHANGELOG deviating from the instruction. It is the other way round. Every release in this file's history has kept The comment now describes the promotion rather than a rename, and adds what was missing entirely: the header format is load-bearing, not decorative. Suggestion — CHANGELOG reference links: no change, the premise does not hold. You noted the Keep-a-Changelog uses that convention; this file never adopted it, and ends with the |
Antigravity review (Gemini via Ultra)Cuts the v2.3.9 "Crucible" release by bumping versions and updating documentation (trivial change). Blocking issuesNone found. SuggestionsNone. NitpicksNone. Automated first-pass review by |
Cuts v2.3.9 "Crucible" — the gates release.
A crucible tests something to destruction rather than inspecting it, and that is what this release does to the project's own checks: what they cover, what they only appear to cover, and where a regression could still reach
mainunchallenged. The v2.3.x line added five tools in four releases, and the recurring finding across all of them was never that the emulation was wrong. It was that a check reported a pass it had not earned.What this PR contains
2.3.7→2.3.9[Unreleased]split into[2.3.9] - 2026-08-20 - "Crucible"and[2.3.8] - 2026-08-20 - "Parallax".github/release-notes/v2.3.9.md(maintainer override)docs/STATUS.md, bothAGENTS.mdanchors,VERSION-PLAN.md,to-dos/ROADMAP.md,SUPPORT.md,SECURITY.md, rootROADMAP.md,OVERVIEW.md,ARCHITECTURE.mdrustynes_libretro.infodisplay_version→v2.3.9Why the version goes 2.3.7 → 2.3.9
v2.3.8 "Parallax" is already tagged and released, cut from its own boundary commit (#407's merge) rather than from
main, so its artifacts contain exactly the Divergence Lens and nothing from this branch. Its CHANGELOG section is backfilled here for the record; the tag itself is not created by this PR. The annotatedv2.3.8tag resolves to5ba66392— verified by resolving the tag object rather than trustinggh release view, which reportstargetCommitish=maincosmetically once a tag exists.The libretro bump is in the same commit deliberately
libretro_info_audit.rspinsdisplay_versionagainst[workspace.package]and fails the suite the moment the two disagree. That gate is what makes the upstream sync a copy rather than a re-derivation. Perdocs/libretro/UPSTREAM_SYNC.md, a version bump alone is not an upstream-sync trigger — the next sync is v2.4.0 — so the upstreamdist/infocopy continues to readv2.3.5deliberately, not by oversight. A licence change would override that; a version does not.The documentation sweep, and two documents that were wrong about more than the version
Every "current release" claim outside the CHANGELOG had drifted, each by a different amount — the predictable outcome of writing one fact down in eight places with no gate asserting they agree. README and
docs/STATUS.mdsaid v2.3.7;VERSION-PLAN.mdsaid v2.3.6 with its table stopping at v2.3.5 still marked(current);to-dos/ROADMAP.mdsaid v2.3.3 in one place and v2.2.5 in another;SUPPORT.mdsaid v2.3.0; the rootROADMAP.mdsaid v2.0.4.Two carried a claim worth more than the version number:
SECURITY.mdstill offered support for1.0.xand marked< 1.0unsupported — a policy table describing a project two major versions and eleven months behind the tree, on the one document a reporter reads before deciding whether a finding is worth sending. The failure mode is not embarrassment; it is a real report that never gets written. Rewritten for the rolling patch cadence RustyNES actually ships on, and it now names the two boundaries that change what a report means rather than merely how old it is: v2.0.0 "Timebase" (a pre-v2.0.0.rns/.rnmis refused with a clear error rather than reinterpreted, so such a parsing report is not reproducible against a current build by design, ADR 0028) and v2.2.9 (the GPL-3.0-or-later relicence is a licensing correction, not a SemVer break, ADR 0036).The root
ARCHITECTURE.mdpresented the retired dot-lockstep scheduler as the current design —tick_one_dot, the% 3phase test, and the explicit claim that the bus need not re-sync the PPU "because they were already advanced in lockstep above".docs/architecture.mdanddocs/scheduler.mdeach carry a v2.0.0 banner correcting exactly this and label their stale subsections; the root companion had neither, so the one architecture document a newcomer opens first described a scheduler that has not existed since 2026-07-03. It now carries the same banner and the same labels — and states the part that did not change: lockstep was chosen so a mid-instruction PPU event is visible to the rest of the instruction without a per-quirk patch, and that consequence survives the mechanism moving.Given what this release is about, a spec describing the previous implementation is the same failure with a longer fuse — and this project has already paid for that once, when Pixel Provenance shipped non-functional for four releases behind a comment asserting it worked.
Three further surfaces were swept in the same pass, in separate commits:
IN PROGRESSfor a release tagged the day before and superseded twice since; the v2.3.8 and v2.3.9 plans carried no status line at all. All four now state it in the same place and form, and the v2.3.8 line records that its tag was cut from boundary commit5ba66392— the fact that explains its artifact contents and is not recoverable from the tag alone.to-dos/DEFERRED-AND-CARRYOVER-FEATURES.mdwas last reconciled entry-by-entry at v2.3.4; five releases have shipped since. The tempting edit was to bump the header. That is precisely the failure this release exists to catch — a catalogue that looks audited is trusted, one that admits its cut-off gets checked — so it now names the real cut-off and the five unswept releases, and the entry-level sweep is left as real remaining work rather than claimed.docs/frontend.mdrecords the ROM-transition rule. It documented the hook as covering "results", which is the framing that let the actual hazard sit outside it: both memory panels' freezes feed the raw-cheat overlay and neither was registered, so a byte frozen in one cartridge kept being written into the next. The spec now carries the rule a new panel is measured against — derived output discarded, user-authored input kept, only input that actively writes neutralised — including why breakpoints stay armed (a breakpoint halts, visibly; a freeze writes, silently) and the two negatives that cost time to establish.The user guide also caught up with the menu it documents:
analysis-tools.mddescribed three tools where the UI offers five (Divergence Lens v2.3.8 and Audio Provenance v2.3.7 were reachable and undocumented), plus v2.3.9's RAM Atlas → RAM Watch export — including why the lens travels with the address, and why the cheat/Lua/RA exports are deliberately still absent.Accuracy
rustynes-apuandrustynes-coreboth change in this release, so the contract is verified, not asserted — and re-run again after the second round of deletions rather than only after the first:The RAM decoder is the authoritative one; the framebuffer decoder reports 120 and is known-buggy.
Also green on this tree:
cargo check --workspace,cargo fmt --all --check, workspace clippy-D warnings, all workspace suites, theno_stdthumbv7em-none-eabihfbuild, andlibretro_info_audit(3 passed).After merge
release-auto.ymltags and publishes off final-greenmain, using this branch's.github/release-notes/v2.3.9.mdas the body and invokingrelease.ymlto attach the Linux / macOS-aarch64 / Windows binaries. Do not tag by hand — a manual tag races the automation.