Skip to content

chore(release): cut v2.3.9 "Crucible" - #426

Merged
doublegate merged 7 commits into
mainfrom
release/v2.3.9
Aug 20, 2026
Merged

chore(release): cut v2.3.9 "Crucible"#426
doublegate merged 7 commits into
mainfrom
release/v2.3.9

Conversation

@doublegate

@doublegate doublegate commented Aug 20, 2026

Copy link
Copy Markdown
Owner

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 main unchallenged. 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

Workspace version 2.3.72.3.9
CHANGELOG [Unreleased] split into [2.3.9] - 2026-08-20 - "Crucible" and [2.3.8] - 2026-08-20 - "Parallax"
Release notes .github/release-notes/v2.3.9.md (maintainer override)
Anchors updated README badge + "Current Release", docs/STATUS.md, both AGENTS.md anchors, VERSION-PLAN.md, to-dos/ROADMAP.md, SUPPORT.md, SECURITY.md, root ROADMAP.md, OVERVIEW.md, ARCHITECTURE.md
libretro rustynes_libretro.info display_versionv2.3.9
User guide Divergence Lens + Audio Provenance sections; RAM Atlas → RAM Watch export

Why 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 annotated v2.3.8 tag resolves to 5ba66392 — verified by resolving the tag object rather than trusting gh release view, which reports targetCommitish=main cosmetically once a tag exists.

The libretro bump is in the same commit deliberately

libretro_info_audit.rs pins display_version against [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. Per docs/libretro/UPSTREAM_SYNC.md, a version bump alone is not an upstream-sync trigger — the next sync is v2.4.0 — so the upstream dist/info copy continues to read v2.3.5 deliberately, 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.md said v2.3.7; VERSION-PLAN.md said v2.3.6 with its table stopping at v2.3.5 still marked (current); to-dos/ROADMAP.md said v2.3.3 in one place and v2.2.5 in another; SUPPORT.md said v2.3.0; the root ROADMAP.md said v2.0.4.

Two carried a claim worth more than the version number:

  • SECURITY.md still offered support for 1.0.x and marked < 1.0 unsupported — 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/.rnm is 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.md presented the retired dot-lockstep scheduler as the current designtick_one_dot, the % 3 phase test, and the explicit claim that the bus need not re-sync the PPU "because they were already advanced in lockstep above". docs/architecture.md and docs/scheduler.md each 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:

  • Plan status lines. The v2.3.7 plan still read IN PROGRESS for 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 commit 5ba66392 — the fact that explains its artifact contents and is not recoverable from the tag alone.
  • The deferred backlog was dated, not re-dated. to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md was 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.md records 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.md described 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-apu and rustynes-core both 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:

AccuracyCoin (RAM): pass rate = 100.00% over 141 assigned tests
nestest: test result: ok. 1 passed

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, the no_std thumbv7em-none-eabihf build, and libretro_info_audit (3 passed).

After merge

release-auto.yml tags and publishes off final-green main, using this branch's .github/release-notes/v2.3.9.md as the body and invoking release.yml to attach the Linux / macOS-aarch64 / Windows binaries. Do not tag by hand — a manual tag races the automation.

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.
Copilot AI lite review requested due to automatic review settings August 20, 2026 03:52
@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Review Change Stack

Important

  • 🔍 Trigger review

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

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 74d5c9b6-863f-4d0f-bbdf-4ec9a873c689

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/v2.3.9

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.

@doublegate

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Head commit changed.

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.

…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).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

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.

Comment thread docs/user-guide/analysis-tools.md Outdated
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.
@doublegate

Copy link
Copy Markdown
Owner Author

Addressing the Antigravity review.

SECURITY.md emoji shortcodes — valid, fixed in e21ee1fa. The column now reads Yes / Partial / No.

Worth recording why this wasn't waved through on precedent: two of the three shortcodes were inherited (:white_check_mark: and :x: have been in that table since it was written), but the previous commit rewrote the table wholesale and added the third, :warning:, for the partial tier. Carrying a standing rule violation forward into a table I had just rewritten — in the release whose entire subject is checks that report a pass they have not earned — is not a defensible place to invoke "it was already like that".

The text version 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; "Partial" states the tier and lets the note carry the mechanism (fixes ship forward into the current line rather than being backported).

I swept the repository for the same pattern: SECURITY.md was the only site outside the vendored and archived trees. The one other apparent hit — ::warning:: in to-dos/plans/v2.3.9-crucible-plan.md — is a GitHub Actions workflow-command annotation, not an emoji, so it stays.

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. SECURITY.md had been offering support for 1.0.x and marking < 1.0 unsupported — a policy table two major versions behind the tree, on the document a reporter reads before deciding whether a finding is worth sending. And the root ARCHITECTURE.md was presenting the retired dot-lockstep scheduler as the current design, complete with tick_one_dot and the claim that the bus need not re-sync the PPU. Both are documentation-only in the sense that no code moved; neither is trivial in effect.

…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.
@doublegate

Copy link
Copy Markdown
Owner Author

Addressing the second Antigravity pass.

Nitpick — the Cargo.toml comment vs. the CHANGELOG layout: valid, and the comment was the wrong half. Fixed in 4f09825e.

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 ## [Unreleased] as a permanent header and inserted the new version section directly beneath it — which is the only workable arrangement, since renaming the header would delete the section the next development window writes into, and [Unreleased] would have to be re-added by hand every cycle or silently go missing. So following the comment literally would have produced a wrong CHANGELOG, and it sits directly above the field a release-cutter came to edit.

The comment now describes the promotion rather than a rename, and adds what was missing entirely: the header format is load-bearing, not 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 a bare 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.

Suggestion — CHANGELOG reference links: no change, the premise does not hold. You noted the [Unreleased]: ... / [2.3.9]: ... definitions were "not visible in the diff". They are not visible because they do not exist:

$ grep -cE '^\[[^]]+\]: https?://' CHANGELOG.md
0
$ tail -3 CHANGELOG.md
- The first release: a cycle-accurate 6502 CPU (all 256 opcodes, a 100% nestest
  golden-log match) and a dot-level 2C02 PPU (97.8% pass rate); 144 tests.

Keep-a-Changelog uses that convention; this file never adopted it, and ends with the [0.1.0] entry. There is nothing to update. Recording it here so it is not re-raised on the next release cut.

@github-actions

Copy link
Copy Markdown

Antigravity review (Gemini via Ultra)

Cuts the v2.3.9 "Crucible" release by bumping versions and updating documentation (trivial change).

Blocking issues

None found.

Suggestions

None.

Nitpicks

None.

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

@doublegate
doublegate merged commit fdfb2c0 into main Aug 20, 2026
31 checks passed
@doublegate
doublegate deleted the release/v2.3.9 branch August 20, 2026 04:48
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