Skip to content

v1.23.0 "Cadence" — pin-exact master-clock-stepped NEC DSP - #254

Merged
doublegate merged 7 commits into
mainfrom
feat/dsp-pin-exact
Jul 29, 2026
Merged

v1.23.0 "Cadence" — pin-exact master-clock-stepped NEC DSP#254
doublegate merged 7 commits into
mainfrom
feat/dsp-pin-exact

Conversation

@doublegate

@doublegate doublegate commented Jul 29, 2026

Copy link
Copy Markdown
Owner

v1.23.0 "Cadence" — pin-exact master-clock-stepped NEC DSP

The follow-up v1.22.0 "Horizon" scoped out. v1.22.0 fixed the visible DSP-1 Mode-7 flat floor value-exact; this PR rewrites the shared NEC-DSP host interface so the DSP joins every other timed subsystem on the master clock instead of resolving its host handshake in zero emulated time. It is a model-consistency + response-latency change, deliberately not a rendering change.

What changed

The µPD77C25 / µPD96050 engine (DSP-1/2/4, ST010; DSP-3/ST011 decoded but unwired) was the last timed subsystem still drained synchronously inside Upd77c25::read_dr / write_dr (a lazy catch-up on host access model). It now free-runs on the master-clock scheduler like the SPC700, GSU/Super FX, and ST018 ARMv3:

  • Upd77c25::tick_master, driven once per master clock from each NEC-DSP board's Board::coprocessor_tick, advances the DSP on its own gcd-reduced fractional divisor (Revision::rates):
    • µPD7725 (DSP-1/2/4): 760_000 / 2_147_727 (7.6 MHz / master)
    • µPD96050 (ST010): 1_100_000 / 2_147_727 (11 MHz / master)
  • Same integer accumulator (dsp_accum: u64) the SPC700 uses — no floats, fully deterministic (docs/adr/0004), guarded by a compile-time reduction assertion.
  • read_dr / write_dr are now pure — they exchange the current DR/SR; the DSP has already produced the value on its own clock. run_until_rqm survives only for the one-time firmware-load prime and standalone tests.

Why it is safe (not a picture change)

All four wired NEC chips render byte-identical to the v1.22.0 baseline (before/after framebuffer content-hash):

Chip Title Result
DSP-1 Pilotwings (flight) hash 0x5dba62c02af9a44a, unchanged
DSP-2 Dungeon Master byte-identical
DSP-4 Top Gear 3000 byte-identical
ST010 F1 ROC II byte-identical

A continuously-clocked free-runner cannot "stop at the first RQM=set", so the hazard class behind the v1.22.0 Mode-7 flat floor is eliminated by construction, not by special-casing.

Save-state

The DSP sub-clock phase (dsp_accum) is serialized in the NDSP section → FORMAT_VERSION 9 → 10; older blobs loud-fail (docs/adr/0006, no silent migration). Battery .srm saves are unaffected.

Commits

  • b5cd929 Phase A — accumulator infra + DSP-1 + transient hybrid net
  • cc2371d Phase B+C — go pure (remove net) + DSP-2/4/ST010
  • e839742 docs — master-clock-stepped model in upd77c25.rs + docs/cart.md (+ GSU cross-ref fix)
  • 0e631d8 drop throwaway pin-exact diagnostic harnesses
  • bdc631b release-prep — bump 17 crates to 1.23.0, CHANGELOG/README/STATUS/VERSION-PLAN

Checks run locally

  • cargo test --workspace527 passed / 0 failed
  • cargo fmt --check; cargo clippy --workspace --all-targets -- -D warnings (default + per-feature; never --all-features)
  • cargo build -p rustysnes-core --target thumbv7em-none-eabihf --no-default-features (no_std)
  • RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps

Release

On squash-merge to main, release-auto.yml tags v1.23.0 (CHANGELOG [1.23.0] is closed out with an empty [Unreleased] above it) and builds the cross-platform binaries; the release will be enriched with standalone v1.23.0 "Cadence" notes.

🤖 Generated with Claude Code

Summary

Changes the NEC shared DSP (µPD77C25/µPD96050) emulation from a host-access “catch-up”/zero-emulation-time handshake model to a deterministic master-clock-stepped, pin-exact free-run model. Observable behavior changes: host read_dr/write_dr no longer synchronously advances the DSP during the access; instead they exchange the DSP’s current DR/SR bytes and update SR transfer state, while RQM/DRC/DRS handshake progression advances only when the scheduler drives Upd77c25::tick_master (with revision-specific clock-rate stepping). (DSP-3/ST011 is still decoded but remains unwired.)

The PR claims:

  • Timing/handshake correctness (pins + host latency): For the covered revisions (DSP-1/DSP-2/DSP-4 and ST010), the new tick_master-driven handshake scheduling is pin-exact and the resulting rendering is byte-identical to the v1.22.0 baseline.
  • Save-state correctness: Mid-computation timing is preserved by serializing the DSP master-clock fractional accumulator (dsp_accum) in the NDSP save-state section.
  • Save-state robustness/validation: Save-state loading rejects crafted states where dsp_accum is out of range—specifically dsp_accum >= den—with SaveStateError::Invalid. Save-state format handling is intentionally incompatible: format version is bumped 9 → 10 and older blobs are expected to fail explicitly (battery saves unaffected).

What would make the claims false:

  • Handshake/cycle mismatch: The “free-running tick_master + no synchronous DSP run during read_dr/write_dr” contract yields different observable RQM/DR handshake evolution than expected hardware (or than the v1.22.0-referenced behavior) for DSP-1/DSP-2/DSP-4/ST010.
  • Rendering divergence: Any framebuffer/rendering hashes or outputs differ from the v1.22.0 byte-identical expectation for the claimed revisions.
  • Save-state phase bugs: Loading does not restore the same effective DSP sub-clock phase, or the accumulator validation boundary is wrong (e.g., accepting dsp_accum >= den, or incorrectly rejecting valid boundary values).
  • Compatibility behavior regression: Format-version bump behavior deviates from the intended contract (older format-9 blobs fail implicitly or are silently migrated rather than failing explicitly).

Coverage/verification tied to the above claims:

  • Adds/updates save-state tests specifically targeting dsp_accum serialization and the invalid/valid boundary behavior (including the >= den rejection case).
  • Updates the DSP-1 continuous raster regression test to advance the DSP in a way consistent with the new tick_master/RQM sequencing.
  • Reports passing quality gates and workspace test runs (including workspace tests, formatting, clippy, a no-std build, and documentation generation), plus cart-based validation coverage.

doublegate and others added 5 commits July 29, 2026 09:44
Fractional accumulator (dsp_accum) on Upd77c25 + Revision::rates() (7725=760000/
2147727, 96050=1100000/2147727) + tick_master(); Dsp1Board drives it from
coprocessor_tick. read_dr/write_dr no longer run_until_rqm synchronously; a
transient instrumented hybrid net in read_dr catches up only when RQM is unset.
Serialize dsp_accum (FORMAT_VERSION 9->10). In-game validation: Pilotwings flight
fires the hybrid ZERO times, floor_variety=55 (non-degenerate). WIP checkpoint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase B: remove the transient hybrid net; read_dr/write_dr are pure (return
current DR/SR, DSP advances only via tick_master). Remove hybrid_fires field +
plumbing. dsp1_raster_ramp drives the DSP with run_until_rqm (no bus in the
standalone). Pure in-game Pilotwings flight hash = 0x5dba62c02af9a44a, IDENTICAL
to the v1.22.0 baseline; floor_variety=55.
Phase C: NecDspVariantBoard overrides coprocessor_tick (DSP-2/4 7.6MHz, ST010
11MHz auto via Revision). WIP checkpoint (variant before/after validation next).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bring the spec in line with the implemented free-running model (docs-as-spec,
same change as the code it describes):

- upd77c25.rs module doc "Host synchronization model": rewrite from the old
  catch-up-on-host-access framing to the master-clock-stepped free-runner
  (tick_master on Revision::rates via coprocessor_tick; run_until_rqm now only
  the firmware-load prime + standalone tests).
- docs/cart.md "the shared NEC core": same rewrite, plus the GSU cross-reference
  that pointed at DSP-1's run_until_rqm as "the same economy" — the GSU stays
  run-to-completion (nothing observable between Go-set and Go-clear), the DSP is
  now free-running because its RQM handshake is polled mid-run.
- CHANGELOG [Unreleased]: the pin-exact rewrite entry + FORMAT_VERSION 9->10.

No code change; rustdoc -D warnings + fmt clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
diag_dsp_pinexact.rs and diag_necdsp_variants.rs were session-only validation
scaffolds (both self-labeled "THROWAWAY ... NOT to be committed") that hardcode
corpus-specific framebuffer hashes and require gitignored commercial ROMs; they
were accidentally captured in the Phase B+C WIP commit. The committed regression
for this work is dsp1_raster_ramp.rs (permissive, self-skipping).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d NEC DSP

Release-prep for v1.23.0, the follow-up v1.22.0 scoped out: the shared NEC-DSP
host interface (µPD77C25 / µPD96050 — DSP-1/2/4, ST010) now free-runs on the
master-clock scheduler instead of resolving its host handshake in zero emulated
time. Model-consistency + response-latency change; NOT a rendering change (all
four wired NEC chips are byte-identical to the v1.22.0 baseline).

Version:
- Bump all 17 workspace package versions 1.22.0 -> 1.23.0 (root [workspace.package]
  + every crate). Internal path-dependency floor pins (version = "1.4.0") are left
  untouched — they are decoupled from the marching package version by design.
- Refresh Cargo.lock to match.

Docs (docs-as-spec, same change as the behaviour it describes — the code + module
docs already landed in the preceding pin-exact commits on this branch):
- CHANGELOG.md: close [Unreleased] as [1.23.0] "Cadence" - 2026-07-29 with a
  narrative intro + Changed (tick_master / Revision::rates / dsp_accum / pure
  read_dr/write_dr; FORMAT_VERSION 9->10) + Internal (host-sync spec + GSU
  cross-reference correction) sections; open a fresh empty [Unreleased].
- README.md: version badge -> v1.23.0.
- docs/STATUS.md: current release -> v1.23.0 "Cadence" (v1.22.0 "Horizon" demoted
  into the ladder); new pin-exact narrative + FORMAT_VERSION 9->10 note.
- to-dos/VERSION-PLAN.md: new "v1.23.0 Cadence" RELEASED entry (what/why-safe/
  save-state/verification) + back-reference from the v1.22.0 "not included" note.

.gitignore: reviewed, no change needed (.codegraph/, tests/captures/, salvaged/,
iOS-generated bindings, AI-local files already covered; working tree clean).

Gates green: cargo check/build (Cargo.lock refreshed), fmt, clippy (workspace
default + per-feature), cargo test --workspace 527/527, no_std thumbv7em-none-eabihf,
RUSTDOCFLAGS=-D warnings doc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 13721567-62f8-4a06-ad7a-bb0f152e7876

📥 Commits

Reviewing files that changed from the base of the PR and between b3773e0 and 4b3b0f5.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • docs/STATUS.md
  • docs/cart.md
  • to-dos/VERSION-PLAN.md
📜 Recent review details
⏰ Context from checks skipped due to timeout. (5)
  • GitHub Check: lint
  • GitHub Check: accuracysnes
  • GitHub Check: test-light
  • GitHub Check: build
  • GitHub Check: build demo + docs
🧰 Additional context used
📓 Path-based instructions (6)
docs/**/*.md

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Before changing a subsystem, consult docs/architecture.md, docs/STATUS.md, CONTRIBUTING.md, the relevant subsystem documentation, and applicable ADRs.

New subsystems must add documentation under docs/.

Files:

  • docs/STATUS.md
  • docs/cart.md
**/*.{rs,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Chip-behavior changes must update both the chip implementation and the corresponding docs/<subsystem>.md documentation.

A chip change must update both the chip implementation and its corresponding docs/<chip>.md documentation in the same change.

Files:

  • docs/STATUS.md
  • to-dos/VERSION-PLAN.md
  • CHANGELOG.md
  • docs/cart.md
**/*

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*: Do not commit or vendor the generated snesdev_wiki/ mirror; it is gitignored and intended only as a local reference.
Keep commits focused and use Conventional Commits: <type>(<scope>): <subject>, with an imperative subject of at most 72 characters.
Do not use emojis in code, comments, or commit messages.
Before opening a PR, ensure formatting, Clippy, workspace tests, the core embedded build, rustdoc with warnings denied, documentation coverage, and changelog requirements pass.
Ticket completion must be reflected in the relevant to-dos/ sprint file.

**/*: Preserve the one-directional crate graph: chip crates must not depend on one another; rustysnes-core ties them together.
Never commit commercial ROMs; only commit derived screenshots and hashes.
Keep docs/STATUS.md as the authoritative per-subsystem status and update project documentation in the same PR as code changes.
Do not treat RustyNES v2.0 or engine-lineage anchors as project releases.

Files:

  • docs/STATUS.md
  • to-dos/VERSION-PLAN.md
  • CHANGELOG.md
  • docs/cart.md
docs/**

⚙️ CodeRabbit configuration file

docs/**: Docs are the spec, not a history log. Flag claims that contradict the code, counts that
contradict the generated docs/accuracysnes-coverage.md, and any statement of coverage that
is broader than what the corresponding test actually asserts.

Files:

  • docs/STATUS.md
  • docs/cart.md
**/*.md

⚙️ CodeRabbit configuration file

**/*.md: Docs are the spec, not a changelog. Flag prose that has drifted from the code it describes
rather than style nits. The markdownlint gate is pinned to v0.39.0 via pre-commit —
do not report rules that version does not have (MD060 in particular).

Files:

  • docs/STATUS.md
  • to-dos/VERSION-PLAN.md
  • CHANGELOG.md
  • docs/cart.md
CHANGELOG.md

📄 CodeRabbit inference engine (CONTRIBUTING.md)

User-visible changes must be recorded under the [Unreleased] section.

For the full pull request diff against its base branch, modify CHANGELOG.md when user-visible behavior changes, including emulator output, frontend features, CLI flags, public APIs, or AccuracySNES cartridge contents. Do not require it for purely internal changes, tests, comments, or CI configuration.

Files:

  • CHANGELOG.md
🔇 Additional comments (4)
docs/cart.md (1)

150-152: LGTM!

CHANGELOG.md (1)

19-19: LGTM!

Also applies to: 36-36, 55-56

docs/STATUS.md (1)

37-38: LGTM!

to-dos/VERSION-PLAN.md (1)

1453-1453: LGTM!

Also applies to: 1475-1476


Walkthrough

The NEC DSP host interface now advances on master-clock ticks instead of host-access catch-up execution. Its fractional clock phase is serialized, save-state format version 10 is required, tests and specifications reflect the new timing model, and the workspace release is updated to 1.23.0.

Changes

NEC DSP Cadence timing

Layer / File(s) Summary
Master-clock DSP execution
crates/rustysnes-cart/src/coproc/upd77c25.rs
Upd77c25 adds revision-specific integer clock ratios and tick_master; read_dr and write_dr now exchange current DR/SR state without catch-up execution.
Scheduler wiring and save-state persistence
crates/rustysnes-cart/src/coproc/dsp1.rs, crates/rustysnes-cart/src/coproc/necdsp_variant.rs, crates/rustysnes-cart/src/coproc/upd77c25.rs, crates/rustysnes-core/src/scheduler.rs
Coprocessor ticks call tick_master, dsp_accum is saved and validated in NDSP, and FORMAT_VERSION increases from 9 to 10.
Timing regression and specifications
crates/rustysnes-test-harness/tests/dsp1_raster_ramp.rs, docs/cart.md
Raster transfer helpers advance between host operations, and documentation describes master-clock NEC DSP execution and the separate GSU model.
Version and release documentation
Cargo.toml, crates/rustysnes-*/Cargo.toml, README.md, CHANGELOG.md, docs/STATUS.md, to-dos/VERSION-PLAN.md
Workspace and crate versions are updated to 1.23.0, and release materials document the timing-model and save-state changes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MasterScheduler
  participant NecDspVariantBoard
  participant Upd77c25
  participant Host
  MasterScheduler->>NecDspVariantBoard: coprocessor_tick()
  NecDspVariantBoard->>Upd77c25: tick_master()
  Upd77c25->>Upd77c25: advance DSP and RQM state
  Host->>Upd77c25: read_dr() or write_dr()
  Upd77c25-->>Host: current DR/SR values
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 9 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title describes the change, but it does not use the required Conventional Commits format. Rewrite it as a Conventional Commit, for example: "docs: describe the pin-exact NEC DSP timing model".
✅ Passed checks (9 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Changelog Entry ✅ Passed The full PR diff includes a new CHANGELOG.md v1.23.0 "Cadence" entry covering the NEC DSP timing and save-state changes.
Docs-As-Spec ✅ Passed PASS: The NEC DSP timing rewrite under crates/rustysnes-cart is documented in docs/cart.md, so the chip behavior change and its spec update are aligned.
Accuracysnes Bookkeeping ✅ Passed No files under tests/roms/AccuracySNES/gen/src/ changed in the PR, so the AccuracySNES bookkeeping rules are not applicable.
No Panic On Untrusted Input ✅ Passed PASS: the only new expect is in a #[cfg(test)] regression, and production load_state rejects invalid NDSP bytes with SaveStateError::Invalid instead of panicking.
Safety Comment On New Unsafe ✅ Passed No new unsafe blocks or unsafe fn were added in the touched Rust files; diff and source searches returned no matches.

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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 10-12: Move the user-visible DSP timing and save-state
compatibility notes from the 1.23.0 section into the currently empty
[Unreleased] section in CHANGELOG.md, leaving the 1.23.0 release notes unchanged
and preserving the notes until the release-cut commit.

In `@crates/rustysnes-cart/src/coproc/upd77c25.rs`:
- Line 874: Validate the value read into self.dsp_accum in the save-state
deserialization path before assigning or accepting it: reject phases greater
than or equal to the divisor den with SaveStateError::Invalid, while preserving
valid values below den. Use the existing typed save-state error mechanism and
ensure malformed NDSP payloads cannot reach tick_master().

In `@crates/rustysnes-test-harness/tests/dsp1_raster_ramp.rs`:
- Around line 40-58: Extend the DSP-1 timing regression beyond the direct
Upd77c25 harness by adding a System/Board-level test that issues the same
Pilotwings host-command sequence through the bus. Drive execution via the
master-clock stepping path (Board::coprocessor_tick/tick_master) rather than
direct run_until_rqm calls, and retain the existing expected DSP results.

In `@docs/STATUS.md`:
- Around line 35-39: Correct the release documentation’s scheduling comparisons:
in docs/STATUS.md lines 35-39, remove GSU from the master-clock comparison; in
CHANGELOG.md lines 14-20, replace the “last synchronous coprocessor” wording and
remove GSU from the free-running list; in CHANGELOG.md lines 32-37 and
to-dos/VERSION-PLAN.md lines 1451-1459, compare the NEC DSP only with SPC700 and
ST018, preserving GSU’s run-to-completion distinction.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8435c7be-139f-4594-bf03-70534880fc64

📥 Commits

Reviewing files that changed from the base of the PR and between 44a3316 and bdc631b.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock and included by Cargo.lock
📒 Files selected for processing (27)
  • CHANGELOG.md
  • Cargo.toml
  • README.md
  • crates/rustysnes-android/Cargo.toml
  • crates/rustysnes-apu/Cargo.toml
  • crates/rustysnes-cart/Cargo.toml
  • crates/rustysnes-cart/src/coproc/dsp1.rs
  • crates/rustysnes-cart/src/coproc/necdsp_variant.rs
  • crates/rustysnes-cart/src/coproc/upd77c25.rs
  • crates/rustysnes-cheevos/Cargo.toml
  • crates/rustysnes-core/Cargo.toml
  • crates/rustysnes-core/src/scheduler.rs
  • crates/rustysnes-cpu/Cargo.toml
  • crates/rustysnes-frontend/Cargo.toml
  • crates/rustysnes-gfx-shaders/Cargo.toml
  • crates/rustysnes-ios/Cargo.toml
  • crates/rustysnes-mobile/Cargo.toml
  • crates/rustysnes-monetization/Cargo.toml
  • crates/rustysnes-netplay/Cargo.toml
  • crates/rustysnes-ppu/Cargo.toml
  • crates/rustysnes-savestate/Cargo.toml
  • crates/rustysnes-script/Cargo.toml
  • crates/rustysnes-test-harness/Cargo.toml
  • crates/rustysnes-test-harness/tests/dsp1_raster_ramp.rs
  • docs/STATUS.md
  • docs/cart.md
  • to-dos/VERSION-PLAN.md
📜 Review details
⏰ Context from checks skipped due to timeout. (5)
  • GitHub Check: accuracysnes
  • GitHub Check: test-light
  • GitHub Check: lint
  • GitHub Check: build demo + docs
  • GitHub Check: build
🧰 Additional context used
📓 Path-based instructions (16)
**/Cargo.toml

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Do not configure the scripting and script-wasm backends as simultaneously enabled features; they are mutually exclusive.

Files:

  • crates/rustysnes-cheevos/Cargo.toml
  • crates/rustysnes-gfx-shaders/Cargo.toml
  • crates/rustysnes-monetization/Cargo.toml
  • crates/rustysnes-savestate/Cargo.toml
  • crates/rustysnes-script/Cargo.toml
  • crates/rustysnes-apu/Cargo.toml
  • crates/rustysnes-netplay/Cargo.toml
  • crates/rustysnes-test-harness/Cargo.toml
  • Cargo.toml
  • crates/rustysnes-cart/Cargo.toml
  • crates/rustysnes-ios/Cargo.toml
  • crates/rustysnes-cpu/Cargo.toml
  • crates/rustysnes-frontend/Cargo.toml
  • crates/rustysnes-mobile/Cargo.toml
  • crates/rustysnes-android/Cargo.toml
  • crates/rustysnes-core/Cargo.toml
  • crates/rustysnes-ppu/Cargo.toml
**/*

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*: Do not commit or vendor the generated snesdev_wiki/ mirror; it is gitignored and intended only as a local reference.
Keep commits focused and use Conventional Commits: <type>(<scope>): <subject>, with an imperative subject of at most 72 characters.
Do not use emojis in code, comments, or commit messages.
Before opening a PR, ensure formatting, Clippy, workspace tests, the core embedded build, rustdoc with warnings denied, documentation coverage, and changelog requirements pass.
Ticket completion must be reflected in the relevant to-dos/ sprint file.

**/*: Preserve the one-directional crate graph: chip crates must not depend on one another; rustysnes-core ties them together.
Never commit commercial ROMs; only commit derived screenshots and hashes.
Keep docs/STATUS.md as the authoritative per-subsystem status and update project documentation in the same PR as code changes.
Do not treat RustyNES v2.0 or engine-lineage anchors as project releases.

Files:

  • crates/rustysnes-cheevos/Cargo.toml
  • crates/rustysnes-gfx-shaders/Cargo.toml
  • crates/rustysnes-monetization/Cargo.toml
  • crates/rustysnes-savestate/Cargo.toml
  • crates/rustysnes-script/Cargo.toml
  • crates/rustysnes-apu/Cargo.toml
  • crates/rustysnes-netplay/Cargo.toml
  • crates/rustysnes-test-harness/Cargo.toml
  • Cargo.toml
  • crates/rustysnes-cart/Cargo.toml
  • crates/rustysnes-ios/Cargo.toml
  • crates/rustysnes-cpu/Cargo.toml
  • crates/rustysnes-frontend/Cargo.toml
  • crates/rustysnes-mobile/Cargo.toml
  • crates/rustysnes-android/Cargo.toml
  • crates/rustysnes-core/Cargo.toml
  • crates/rustysnes-ppu/Cargo.toml
  • README.md
  • crates/rustysnes-core/src/scheduler.rs
  • CHANGELOG.md
  • crates/rustysnes-cart/src/coproc/necdsp_variant.rs
  • docs/STATUS.md
  • crates/rustysnes-test-harness/tests/dsp1_raster_ramp.rs
  • docs/cart.md
  • crates/rustysnes-cart/src/coproc/dsp1.rs
  • to-dos/VERSION-PLAN.md
  • crates/rustysnes-cart/src/coproc/upd77c25.rs
crates/rustysnes-*/**/*

📄 CodeRabbit inference engine (Custom checks)

For the full pull request diff against its base branch, any observable behavior change under crates/rustysnes-<chip>/ must be accompanied by an edit to the matching docs/<chip>.md; a crate change passes without documentation only when it does not alter observable behavior, with the non-behavioral change stated explicitly.

Files:

  • crates/rustysnes-cheevos/Cargo.toml
  • crates/rustysnes-gfx-shaders/Cargo.toml
  • crates/rustysnes-monetization/Cargo.toml
  • crates/rustysnes-savestate/Cargo.toml
  • crates/rustysnes-script/Cargo.toml
  • crates/rustysnes-apu/Cargo.toml
  • crates/rustysnes-netplay/Cargo.toml
  • crates/rustysnes-test-harness/Cargo.toml
  • crates/rustysnes-cart/Cargo.toml
  • crates/rustysnes-ios/Cargo.toml
  • crates/rustysnes-cpu/Cargo.toml
  • crates/rustysnes-frontend/Cargo.toml
  • crates/rustysnes-mobile/Cargo.toml
  • crates/rustysnes-android/Cargo.toml
  • crates/rustysnes-core/Cargo.toml
  • crates/rustysnes-ppu/Cargo.toml
  • crates/rustysnes-core/src/scheduler.rs
  • crates/rustysnes-cart/src/coproc/necdsp_variant.rs
  • crates/rustysnes-test-harness/tests/dsp1_raster_ramp.rs
  • crates/rustysnes-cart/src/coproc/dsp1.rs
  • crates/rustysnes-cart/src/coproc/upd77c25.rs
**/*.{rs,toml}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{rs,toml}: Additive features must be default-off so shipped/native, no_std, and wasm builds remain byte-identical.
Never use or configure --all-features; validate opt-in feature combinations individually as required by the project recipe.

Files:

  • crates/rustysnes-cheevos/Cargo.toml
  • crates/rustysnes-gfx-shaders/Cargo.toml
  • crates/rustysnes-monetization/Cargo.toml
  • crates/rustysnes-savestate/Cargo.toml
  • crates/rustysnes-script/Cargo.toml
  • crates/rustysnes-apu/Cargo.toml
  • crates/rustysnes-netplay/Cargo.toml
  • crates/rustysnes-test-harness/Cargo.toml
  • Cargo.toml
  • crates/rustysnes-cart/Cargo.toml
  • crates/rustysnes-ios/Cargo.toml
  • crates/rustysnes-cpu/Cargo.toml
  • crates/rustysnes-frontend/Cargo.toml
  • crates/rustysnes-mobile/Cargo.toml
  • crates/rustysnes-android/Cargo.toml
  • crates/rustysnes-core/Cargo.toml
  • crates/rustysnes-ppu/Cargo.toml
  • crates/rustysnes-core/src/scheduler.rs
  • crates/rustysnes-cart/src/coproc/necdsp_variant.rs
  • crates/rustysnes-test-harness/tests/dsp1_raster_ramp.rs
  • crates/rustysnes-cart/src/coproc/dsp1.rs
  • crates/rustysnes-cart/src/coproc/upd77c25.rs
crates/**

⚙️ CodeRabbit configuration file

crates/**: Emulator core. Hot paths are allocation-free; unsafe requires a // SAFETY: comment
naming the invariant. Any change to save-stated fields needs a FORMAT_VERSION bump and a
docs/adr/0006 bump-log entry. Behavior changes must update the matching docs/<chip>.md
in the same change.

Files:

  • crates/rustysnes-cheevos/Cargo.toml
  • crates/rustysnes-gfx-shaders/Cargo.toml
  • crates/rustysnes-monetization/Cargo.toml
  • crates/rustysnes-savestate/Cargo.toml
  • crates/rustysnes-script/Cargo.toml
  • crates/rustysnes-apu/Cargo.toml
  • crates/rustysnes-netplay/Cargo.toml
  • crates/rustysnes-test-harness/Cargo.toml
  • crates/rustysnes-cart/Cargo.toml
  • crates/rustysnes-ios/Cargo.toml
  • crates/rustysnes-cpu/Cargo.toml
  • crates/rustysnes-frontend/Cargo.toml
  • crates/rustysnes-mobile/Cargo.toml
  • crates/rustysnes-android/Cargo.toml
  • crates/rustysnes-core/Cargo.toml
  • crates/rustysnes-ppu/Cargo.toml
  • crates/rustysnes-core/src/scheduler.rs
  • crates/rustysnes-cart/src/coproc/necdsp_variant.rs
  • crates/rustysnes-test-harness/tests/dsp1_raster_ramp.rs
  • crates/rustysnes-cart/src/coproc/dsp1.rs
  • crates/rustysnes-cart/src/coproc/upd77c25.rs
crates/*/Cargo.toml

⚙️ CodeRabbit configuration file

crates/*/Cargo.toml: The frontend pins one compatible GUI tier (currently winit 0.30 / wgpu 29 / egui 0.35 and
its egui-wgpu/egui-winit siblings). Flag a bump to any one of those that is not
accompanied by matching bumps to the others. Optional features are default-off by
convention so shipped, no_std and wasm builds stay byte-identical; flag a new feature added
to a default = [...] list.

Files:

  • crates/rustysnes-cheevos/Cargo.toml
  • crates/rustysnes-gfx-shaders/Cargo.toml
  • crates/rustysnes-monetization/Cargo.toml
  • crates/rustysnes-savestate/Cargo.toml
  • crates/rustysnes-script/Cargo.toml
  • crates/rustysnes-apu/Cargo.toml
  • crates/rustysnes-netplay/Cargo.toml
  • crates/rustysnes-test-harness/Cargo.toml
  • crates/rustysnes-cart/Cargo.toml
  • crates/rustysnes-ios/Cargo.toml
  • crates/rustysnes-cpu/Cargo.toml
  • crates/rustysnes-frontend/Cargo.toml
  • crates/rustysnes-mobile/Cargo.toml
  • crates/rustysnes-android/Cargo.toml
  • crates/rustysnes-core/Cargo.toml
  • crates/rustysnes-ppu/Cargo.toml
Cargo.toml

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Cargo.toml: Keep crate dependencies one-directional; chip crates must not depend on each other.
Additive features must remain default-off so native, no_std, and wasm outputs remain byte-identical in default configurations.
Do not use cargo clippy --all-features; use cargo clippy --workspace --all-targets -- -D warnings, with explicit feature jobs when needed.

Files:

  • Cargo.toml
**/*.{rs,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Chip-behavior changes must update both the chip implementation and the corresponding docs/<subsystem>.md documentation.

A chip change must update both the chip implementation and its corresponding docs/<chip>.md documentation in the same change.

Files:

  • README.md
  • crates/rustysnes-core/src/scheduler.rs
  • CHANGELOG.md
  • crates/rustysnes-cart/src/coproc/necdsp_variant.rs
  • docs/STATUS.md
  • crates/rustysnes-test-harness/tests/dsp1_raster_ramp.rs
  • docs/cart.md
  • crates/rustysnes-cart/src/coproc/dsp1.rs
  • to-dos/VERSION-PLAN.md
  • crates/rustysnes-cart/src/coproc/upd77c25.rs
**/*.md

⚙️ CodeRabbit configuration file

**/*.md: Docs are the spec, not a changelog. Flag prose that has drifted from the code it describes
rather than style nits. The markdownlint gate is pinned to v0.39.0 via pre-commit —
do not report rules that version does not have (MD060 in particular).

Files:

  • README.md
  • CHANGELOG.md
  • docs/STATUS.md
  • docs/cart.md
  • to-dos/VERSION-PLAN.md
crates/**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

crates/**/*.rs: Preserve the master-clock lockstep timing model.
rustysnes-core::Bus owns mutable machine state, and the CPU borrows &mut Bus.
Preserve determinism: seed, ROM, and input must produce bit-identical output.
Treat test ROMs as the behavioral specification; when documentation disagrees with passing ROM behavior, update the documentation.
Keep unsafe confined to existing allowed areas, namely frontend and FFI code, and document every unsafe block with a // SAFETY: comment.

Files:

  • crates/rustysnes-core/src/scheduler.rs
  • crates/rustysnes-cart/src/coproc/necdsp_variant.rs
  • crates/rustysnes-test-harness/tests/dsp1_raster_ramp.rs
  • crates/rustysnes-cart/src/coproc/dsp1.rs
  • crates/rustysnes-cart/src/coproc/upd77c25.rs
crates/rustysnes-{cpu,ppu,apu,cart,core}/**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Keep core chip implementation changes localized to the owning chip crate and preserve the workspace crate boundaries.

Files:

  • crates/rustysnes-core/src/scheduler.rs
  • crates/rustysnes-cart/src/coproc/necdsp_variant.rs
  • crates/rustysnes-cart/src/coproc/dsp1.rs
  • crates/rustysnes-cart/src/coproc/upd77c25.rs
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Use Rust edition 2024 and the toolchain pinned in rust-toolchain.toml (Rust 1.96).
Run cargo fmt --all --check; Rust code must remain rustfmt-compliant.
Run Clippy with cargo clippy --workspace --all-targets -- -D warnings; warnings must not remain.
New public Rust items must have rustdoc because missing_docs is a workspace lint.
Do not run cargo clippy --all-features; scripting and script-wasm are mutually exclusive. Use explicit per-feature jobs instead.

**/*.rs: Do not introduce .unwrap(), .expect(), or panic!() on untrusted external input—such as ROM/save-state bytes, netplay messages, Lua or scripting input, or user-supplied paths—outside #[cfg(test)] code. Use typed errors at those boundaries; locally constructed values or values immediately protected by a checked invariant are allowed.
Every new unsafe { ... } block or unsafe fn must have an adjacent // SAFETY: comment naming the relied-on invariant and its guarantor. Unsafe code outside the frontend and FFI shims should additionally be questioned because unsafe_code is a workspace lint.

**/*.rs: Use Rust edition 2024 with the pinned 1.96 toolchain; satisfy workspace pedantic, nursery, missing_docs, and unsafe_code warnings because CI runs with -D warnings. Document every public item.
Keep unsafe code restricted to the frontend and FFI, and include a // SAFETY: justification for each use.
Keep hot paths allocation-free.
Treat rustysnes_core::Bus as the owner of mutable emulator state; the CPU borrows &mut Bus.
Use the master clock at 21477270 Hz as the timing master; advance the scheduler in lockstep and run other chips on their divisors.
Maintain determinism: seed, ROM, and input must produce bit-identical audio/video; frontend rate control must not alter emulation results.
When implementing hardware behavior, pin and run the failing test ROM first; treat test ROMs as the specification.

Files:

  • crates/rustysnes-core/src/scheduler.rs
  • crates/rustysnes-cart/src/coproc/necdsp_variant.rs
  • crates/rustysnes-test-harness/tests/dsp1_raster_ramp.rs
  • crates/rustysnes-cart/src/coproc/dsp1.rs
  • crates/rustysnes-cart/src/coproc/upd77c25.rs
crates/rustysnes-core/**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

The core crate must compile for thumbv7em-none-eabihf with --no-default-features; the chip stack must remain no_std.

Files:

  • crates/rustysnes-core/src/scheduler.rs
CHANGELOG.md

📄 CodeRabbit inference engine (CONTRIBUTING.md)

User-visible changes must be recorded under the [Unreleased] section.

For the full pull request diff against its base branch, modify CHANGELOG.md when user-visible behavior changes, including emulator output, frontend features, CLI flags, public APIs, or AccuracySNES cartridge contents. Do not require it for purely internal changes, tests, comments, or CI configuration.

Files:

  • CHANGELOG.md
docs/**/*.md

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Before changing a subsystem, consult docs/architecture.md, docs/STATUS.md, CONTRIBUTING.md, the relevant subsystem documentation, and applicable ADRs.

New subsystems must add documentation under docs/.

Files:

  • docs/STATUS.md
  • docs/cart.md
docs/**

⚙️ CodeRabbit configuration file

docs/**: Docs are the spec, not a history log. Flag claims that contradict the code, counts that
contradict the generated docs/accuracysnes-coverage.md, and any statement of coverage that
is broader than what the corresponding test actually asserts.

Files:

  • docs/STATUS.md
  • docs/cart.md
🔇 Additional comments (27)
crates/rustysnes-cart/src/coproc/upd77c25.rs (1)

15-33: LGTM!

Also applies to: 104-127, 236-239, 281-281, 308-308, 364-411, 471-488, 829-829, 1036-1050, 1087-1087

crates/rustysnes-cart/src/coproc/dsp1.rs (1)

168-172: LGTM!

crates/rustysnes-cart/src/coproc/necdsp_variant.rs (1)

242-247: LGTM!

crates/rustysnes-core/src/scheduler.rs (1)

72-79: LGTM!

docs/cart.md (1)

102-124: LGTM!

Also applies to: 150-153

crates/rustysnes-monetization/Cargo.toml (1)

4-4: LGTM!

crates/rustysnes-netplay/Cargo.toml (1)

4-4: LGTM!

crates/rustysnes-ppu/Cargo.toml (1)

3-3: LGTM!

crates/rustysnes-savestate/Cargo.toml (1)

3-3: LGTM!

crates/rustysnes-script/Cargo.toml (1)

4-4: LGTM!

crates/rustysnes-test-harness/Cargo.toml (1)

3-3: LGTM!

README.md (1)

17-17: LGTM!

CHANGELOG.md (1)

48-57: LGTM!

docs/STATUS.md (2)

6-6: LGTM!


40-43: LGTM!

to-dos/VERSION-PLAN.md (1)

1441-1441: LGTM!

Also applies to: 1443-1450, 1462-1476

Cargo.toml (1)

25-25: LGTM!

crates/rustysnes-android/Cargo.toml (1)

4-4: LGTM!

crates/rustysnes-apu/Cargo.toml (1)

3-3: LGTM!

crates/rustysnes-cart/Cargo.toml (1)

3-3: LGTM!

crates/rustysnes-cheevos/Cargo.toml (1)

4-4: LGTM!

crates/rustysnes-core/Cargo.toml (1)

3-3: LGTM!

crates/rustysnes-cpu/Cargo.toml (1)

3-3: LGTM!

crates/rustysnes-frontend/Cargo.toml (1)

4-4: LGTM!

crates/rustysnes-gfx-shaders/Cargo.toml (1)

4-4: LGTM!

crates/rustysnes-ios/Cargo.toml (1)

4-4: LGTM!

crates/rustysnes-mobile/Cargo.toml (1)

4-4: LGTM!

Comment thread CHANGELOG.md
Comment on lines 10 to +12
## [Unreleased]

## [1.23.0] "Cadence" - 2026-07-29

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Record these user-visible notes under [Unreleased].

The new DSP timing and save-state compatibility behavior is inserted directly under ## [1.23.0] while [Unreleased] remains empty. Move the notes under [Unreleased] until the release-cut commit.

As per coding guidelines, user-visible changes must be recorded under the [Unreleased] section.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 10 - 12, Move the user-visible DSP timing and
save-state compatibility notes from the 1.23.0 section into the currently empty
[Unreleased] section in CHANGELOG.md, leaving the 1.23.0 release notes unchanged
and preserving the notes until the release-cut commit.

Source: Coding guidelines

Comment thread crates/rustysnes-cart/src/coproc/upd77c25.rs Outdated
Comment on lines +40 to +58
// Host command byte (8-bit while parked) then 16-bit LE parameter words. In the pin-exact model
// read_dr/write_dr no longer advance the DSP (only the master-clock tick does), so this standalone
// harness — which has no bus — drives the chip explicitly with `run_until_rqm` after each host op,
// exactly the catch-up the Bus performs continuously in-game.
let cmd = |dsp: &mut Upd77c25, c: u8, params: &[u16]| {
dsp.write_dr(c);
dsp.run_until_rqm();
for &p in params {
dsp.write_dr((p & 0xff) as u8);
dsp.run_until_rqm();
dsp.write_dr((p >> 8) as u8);
dsp.run_until_rqm();
}
};
let read_word = |dsp: &mut Upd77c25| -> u16 {
let lo = dsp.read_dr();
dsp.run_until_rqm();
let hi = dsp.read_dr();
dsp.run_until_rqm();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -nP '\b(tick_master|coprocessor_tick|dsp_accum)\b' \
  crates/rustysnes-test-harness crates/rustysnes-cart crates/rustysnes-core

Repository: doublegate/RustySNES

Length of output: 6531


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== test file ==\n'
cat -n crates/rustysnes-test-harness/tests/dsp1_raster_ramp.rs

printf '\n== nearby DSP1 tests ==\n'
rg -n --context 2 'dsp1_raster_ramp|run_until_rqm|tick_master|coprocessor_tick|raster' crates/rustysnes-test-harness/tests crates/rustysnes-core crates/rustysnes-cart

printf '\n== scheduler / board docs around NDSP ==\n'
sed -n '60,95p' crates/rustysnes-core/src/scheduler.rs
sed -n '360,520p' crates/rustysnes-cart/src/coproc/upd77c25.rs
sed -n '1,120p' crates/rustysnes-cart/src/coproc/dsp1.rs

Repository: doublegate/RustySNES

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n --context 3 'run_until_rqm' crates

Repository: doublegate/RustySNES

Length of output: 10093


Add a bus-level DSP-1 timing regression. This test only drives Upd77c25 directly through run_until_rqm; it never exercises the master-clock path that calls Board::coprocessor_tick/tick_master, so a broken scheduler or clock ratio would still pass. Add the same Pilotwings host-command sequence to a System/Board test that steps the bus.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/rustysnes-test-harness/tests/dsp1_raster_ramp.rs` around lines 40 -
58, Extend the DSP-1 timing regression beyond the direct Upd77c25 harness by
adding a System/Board-level test that issues the same Pilotwings host-command
sequence through the bus. Drive execution via the master-clock stepping path
(Board::coprocessor_tick/tick_master) rather than direct run_until_rqm calls,
and retain the existing expected DSP results.

Comment thread docs/STATUS.md
Adjudicated bot-review findings on the pin-exact save-state path (PR #254):

- CodeRabbit (Major, accepted): `load_state` accepted any `u64` for the
  master-clock accumulator phase (`dsp_accum`). A crafted `NDSP` payload with a
  phase >= the divisor would make the next `tick_master` execute up to billions
  of `exec()` iterations before the accumulator drained — uncontrolled resource
  consumption on untrusted save-state input (module 60). A valid phase is always
  reduced below `den`, so reject `>= den` with the typed `SaveStateError::Invalid`
  at the boundary. Adds `out_of_range_accumulator_phase_is_rejected_not_spun_on`
  (rejects `den`/`den+1`/`u64::MAX`, accepts `den-1`).

- Antigravity (suggestion + nitpick, accepted): evaluate `Revision::rates()` (a
  `const fn`) directly inside the reduction's `const _` assertion instead of
  re-stating the reduced literals, so the proof tracks the source; this also drops
  the `clippy::eq_op` allow.

Verified: fmt, clippy -p rustysnes-cart -D warnings, 157 cart unit tests, and the
RUSTDOCFLAGS=-D warnings doc build all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@doublegate

Copy link
Copy Markdown
Owner Author

Bot-review adjudication (commit b3773e0)

Thanks both. Decisions:

Accepted — fixed in b3773e0

  • CodeRabbit (Major) — bounds-check the serialized accumulator phase. Correct and important: a crafted NDSP payload with dsp_accum >= den would make the next tick_master() spin for up to ~billions of exec() iterations. load_state now rejects >= den with the typed SaveStateError::Invalid (a valid phase is always reduced below the divisor), plus a regression test (out_of_range_accumulator_phase_is_rejected_not_spun_on) covering den/den+1/u64::MAX reject and den-1 accept.
  • Antigravity (suggestion + eq_op nitpick) — derive the const assertion from rates(). Since Revision::rates is a const fn, the reduction proof now evaluates it directly instead of duplicating the reduced literals, which also removes the clippy::eq_op allow.

Rejected, with reasons

  • CodeRabbit (Minor) — move notes back under [Unreleased]. This is the release-cut commit. release-auto.yml tags the release on merge only when [Unreleased] is empty and ## [1.23.0] is closed out (that section becomes the tag body). Moving the notes back would break the release automation — the [Unreleased] → [version] rename is the ceremony.
  • Antigravity (Blocking) — "missing docs/<chip>.md spec update". False positive: there is no dedicated NEC chip doc. docs/cart.md §"the shared NEC core" is the coprocessor spec (per the cart crate's own CLAUDE.md: "Spec: ../../docs/cart.md"), and it was updated in this PR (commit e839742), along with the upd77c25.rs module doc.
  • Antigravity (Blocking) — "breaking save-state format in a minor release". Rejected per established project policy: FORMAT_VERSION loud-fail bumps in minor releases are the documented docs/adr/0006 convention — this is the 9th such bump across the 1.x line. Save-state format is intentionally not under a SemVer stability guarantee before a major release; a v2.0.0 bump is not warranted for one field.
  • Antigravity (suggestion) — drive tick_master() instead of run_until_rqm() in dsp1_raster_ramp. Kept as-is: that test pins the Mode-7 value ramp, and the standalone harness has no bus to tick the chip; run_until_rqm deterministically reaching the parked state is exactly right for a value-regression pin. The production tick_master path is exercised by the full-bus workspace tests.

All local gates green after the fix: fmt, clippy -p rustysnes-cart -D warnings, 157 cart unit tests, RUSTDOCFLAGS=-D warnings doc.

Adjudicated CodeRabbit finding (PR #254): the release docs described the NEC DSP
as free-running "like the SPC700, GSU, and ST018", but the GSU's scheduling model
is described inconsistently within the codebase itself — gsu.rs's core doc frames
it as run-to-completion (`Gsu::run_until_stopped`, no per-master-clock hook) while
superfx.rs's board doc frames it as master-clock-stepped (`coprocessor_tick` ->
`Gsu::tick`, one master clock at a time). Resolving that pre-existing GSU-doc
contradiction is out of scope for a DSP release, so the release docs no longer
take a side: the NEC DSP is now compared only to the SPC700 and ST018 ARMv3, both
of which are unambiguously master-clock-stepped free-runners.

- CHANGELOG.md / docs/STATUS.md / to-dos/VERSION-PLAN.md: remove GSU/Super FX from
  the "free-run on the master clock" comparison; simplify the Internal note to say
  the stale DSP-1 `run_until_rqm` cross-reference was retired (not that the GSU was
  re-characterized).
- docs/cart.md: the GSU host-sync paragraph keeps its own (pre-existing)
  run-to-completion description and simply drops the stale `run_until_rqm` economy
  cross-reference, pointing at "the shared NEC core" for the DSP's new model.

Docs-only; no code change. (Follow-up noted separately: reconcile the gsu.rs vs
superfx.rs GSU scheduling-model docs.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@doublegate

Copy link
Copy Markdown
Owner Author

Follow-up (commit 4b3b0f5) — GSU scheduling comparison

CodeRabbit's re-review flagged that the release docs compared the NEC DSP to "SPC700, GSU, and ST018" as master-clock-stepped free-runners. Good catch — but the fix goes a step further than the literal suggestion, because the GSU's own model is documented inconsistently in the codebase:

  • gsu.rs core doc: run-to-completion the instant Go is set (Gsu::run_until_stopped), "without a per-master-clock core hook."
  • superfx.rs board doc: "advanced by exactly one master clock per Board::coprocessor_tick"Gsu::tick, which the Bus drives every master tick.

Both run_until_stopped and the coprocessor_ticktick path exist. Reconciling which is canonical is a pre-existing GSU-doc question, out of scope for a DSP release — so rather than assert either side, the release docs now compare the NEC DSP only to the SPC700 and ST018 ARMv3, which are unambiguously master-clock-stepped. docs/cart.md's GSU paragraph keeps its existing description and just drops the stale run_until_rqm cross-reference.

I've noted the gsu.rs vs superfx.rs GSU-model doc reconciliation as a separate follow-up.

The three earlier re-posted threads (CHANGELOG [Unreleased], upd77c25.rs:874, dsp1_raster_ramp.rs:58) are stale re-anchors of findings already fixed (the accumulator guard landed in b3773e0 — line 874 now holds self.dr = …) or rejected with reasons in the adjudication comment above.

@github-actions

Copy link
Copy Markdown

Antigravity review (Gemini via Ultra)

This PR refactors the NEC DSP coprocessor core (µPD77C25 / µPD96050) to free-run on a master-clock-stepped fractional divisor (tick_master), removes synchronous catch-up execution from host register accesses, and bumps the save-state FORMAT_VERSION from 9 to 10.

Blocking issues

  • NEC DSP clock is never stepped during live emulation (rustysnes-core missing integration): Upd77c25::read_dr and write_dr in upd77c25.rs no longer invoke run_until_rqm(), delegating execution entirely to Upd77c25::tick_master() via Board::coprocessor_tick(). However, rustysnes-core (bus.rs / scheduler.rs) is not modified in this PR and never invokes coprocessor_tick() during master-clock steps. As a result, the DSP engine is never advanced during live bus execution, freezing execution for all DSP-1, DSP-2, DSP-4, and ST010 games.
  • Breaking save-state format version bump outside a major release: FORMAT_VERSION in scheduler.rs is bumped from 9 to 10 in minor release v1.23.0, making older save states fail to load. The project style guide explicitly specifies that a breaking save-state format change outside a major release is a blocking violation.

Suggestions

  • Cache (num, den) divisor rates on the Upd77c25 struct at construction in upd77c25.rs rather than pattern-matching self.revision.rates() on every master-clock tick.

Nitpicks

  • In dsp1_raster_ramp.rs, annotate test helper closures to explicitly note that manual run_until_rqm calls are standalone harness shims compensating for the absence of Bus master ticks.

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

@doublegate
doublegate merged commit 9d092f7 into main Jul 29, 2026
16 checks passed
@doublegate
doublegate deleted the feat/dsp-pin-exact branch July 29, 2026 17:13
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