Skip to content

fix(dsp1): run continuous-mode NEC DSP to its host-wait spin, not first RQM (v1.22.0) - #253

Merged
doublegate merged 1 commit into
mainfrom
fix/dsp1-continuous-raster
Jul 29, 2026
Merged

fix(dsp1): run continuous-mode NEC DSP to its host-wait spin, not first RQM (v1.22.0)#253
doublegate merged 1 commit into
mainfrom
fix/dsp1-continuous-raster

Conversation

@doublegate

@doublegate doublegate commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the long-standing DSP-1 continuous-mode Mode-7 flat-floor bug (Pilotwings flight, Super Mario Kart track) and cuts the v1.22.0 "Horizon" release. DSP-1 games rendered a flat, perspective-less Mode-7 ground — the last major visible accuracy gap in the coprocessor set. The fix is one function in the shared NEC-DSP host interface.

Root cause

Upd77c25::run_until_rqm advances the NEC DSP engine to its parked state after every host DR access, and stopped at the first RQM=set. But reading a host input word (src == 8) raises RQM as a side effect while the firmware still has pending setup — notably clearing DRC to 16-bit for the next transfer. Stopping there deferred the DRC clear past the next host write, so Pilotwings' 7-word cmd 0x02 projection block was transferred 8-bit-misaligned and the DSP computed a degenerate, constant-across-scanlines matrix → a flat floor.

The engine now runs to the firmware's genuine host-wait spin (a JRQM/JNRQM self-loop), matching ares' continuous clock-stepping to the wait point:

while budget > 0 {
    let pc_before = self.pc;
    self.exec();
    budget -= 1;
    if self.sr.rqm && self.pc == pc_before {
        break; // firmware is spinning on RQM, waiting for the host
    }
}

How it was pinned

  • A MesenCE Lua tracer captured a real flight's DR command stream. RustySNES received byte-identical DSP-1 input but produced constant output (8 distinct words/frame) vs the reference's per-scanline ramp (264 distinct).
  • Replaying the real command stream through a fresh core reproduced the flat result deterministically — ruling out bus/game timing.
  • ares' readDR/writeDR, ALU/multiplier/flag logic, execLD, and the SR-write mask were verified line-for-line identical to RustySNES, isolating the single structural difference: the host-wait stepping.

Verification & no-regression

  • In-game Pilotwings flight: 150/150 flat frames → a per-scanline ramp on every frame.
  • run_until_rqm is shared by all six NEC coprocessors (DSP-1/2/3/4, ST010/011). Before/after framebuffer content-hashes are byte-identical for Pilotwings title/attract, Dungeon Master (DSP-2), and Top Gear 3000 (DSP-4) — the fix only touches the flight/raster path.
  • New dsp1_raster_ramp regression test replays the real params and asserts the ramp; confirmed it fails on the pre-fix core ([fe7f 01ff fe00 fe7f]) and passes after.
  • Gates: cargo test --workspace 527 passed / 0 failed, dsp1_oncart green, 156 cart-crate unit tests green, fmt + clippy (default & per-feature) + no_std thumbv7em + -D warnings doc all green.

Release (v1.22.0 "Horizon")

Version bumped across the workspace; CHANGELOG.md, docs/STATUS.md, to-dos/VERSION-PLAN.md, and docs/cart.md §the shared NEC core updated; root .gitignore now ignores the local .codegraph/ index. v1.22.0 also tags the additive [Unreleased] work already on main (WAI wake cycle, DRAM refresh).

Scope notes

  • The full pin-exact rewrite (stepping the DSP on the master-clock scheduler for cycle-accurate response latency) is a scoped future effort — this fix is byte-exact at the RQM bus boundary, which is all DSP-1 games observe.
  • The investigation's throwaway diagnostic scaffolding (capture harnesses, debug APIs, the Lua tracer) stays on the fix/dsp1-mode7 branch and is intentionally not part of this release — this branch was cut clean from main.

🤖 Generated with Claude Code

Summary

Fixes DSP-1 continuous-mode Mode-7 rasterization so the host interface advances to the genuine firmware host-wait spin instead of stopping at the first RQM signal. This restores per-scanline projection updates in Pilotwings and Super Mario Kart; the claim would be false if raster output remained flat or other NEC-coprocessor framebuffers changed unexpectedly.

Adds the dsp1_raster_ramp regression test, asserting that consecutive raster results are not identical. Accuracy/status assertions for v1.22.0 “Horizon” were added or updated; the reported coverage denominator did not move.

Also publishes the v1.22.0 release metadata and documentation updates.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@doublegate, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 26 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f07b601e-9794-4c39-ae1a-c9da28037c5a

📥 Commits

Reviewing files that changed from the base of the PR and between e9682d3 and 00bc097.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock and included by Cargo.lock
📒 Files selected for processing (25)
  • .gitignore
  • 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/upd77c25.rs
  • crates/rustysnes-cheevos/Cargo.toml
  • crates/rustysnes-core/Cargo.toml
  • 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

Walkthrough

The DSP-1 host interface now steps through the complete host-wait spin after DR accesses, preventing continuous-mode parameter misalignment and restoring changing Mode-7 raster results. A regression test, documentation, release notes, version metadata, and local tooling ignore rule were added or updated.

Changes

DSP-1 continuous-mode raster correction

Layer / File(s) Summary
Host-wait stepping correction
crates/rustysnes-cart/src/coproc/upd77c25.rs, docs/cart.md
run_until_rqm now waits for RQM with an unchanged program counter, matching the documented next host-wait spin and preserving pending firmware setup.
Continuous raster regression coverage
crates/rustysnes-test-harness/tests/dsp1_raster_ramp.rs, CHANGELOG.md, docs/STATUS.md, to-dos/VERSION-PLAN.md
The new test checks that sampled DSP-1 continuous-raster results differ; release documentation records the Mode-7 correction and reported no-regression results.

v1.22.0 release metadata

Layer / File(s) Summary
Version and repository metadata
Cargo.toml, crates/*/Cargo.toml, README.md, .gitignore
Workspace and crate versions, the README badge, and the local .codegraph/ ignore rule were updated for v1.22.0.

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

Sequence Diagram(s)

sequenceDiagram
  participant RegressionTest
  participant Upd77c25
  participant DSPFirmware
  RegressionTest->>Upd77c25: Send 0x02 parameters and 0x0a raster command
  Upd77c25->>DSPFirmware: Step until RQM is set and PC stops changing
  DSPFirmware-->>Upd77c25: Return parked host-wait state
  RegressionTest->>Upd77c25: Read raster-cycle results
  Upd77c25-->>RegressionTest: Return changing four-word raster values
Loading
🚥 Pre-merge checks | ✅ 9 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (9 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main fix and follows the required Conventional Commits format with a valid fix(scope): subject structure.
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 CHANGELOG.md entry for the DSP-1 Mode-7 flat-floor fix triggered by continuous-mode Pilotwings/SMK host DR transfers.
Docs-As-Spec ✅ Passed crates/rustysnes-cart/src/coproc/upd77c25.rs changes DSP-1 host-wait behavior, and docs/cart.md was updated to specify the new spin semantics; other rustysnes-* edits are version bumps/tests.
Accuracysnes Bookkeeping ✅ Passed No files under tests/roms/AccuracySNES/gen/src/ changed versus the merge-base, so the dossier/artifact/count bookkeeping requirements were not triggered.
No Panic On Untrusted Input ✅ Passed No new unwrap/expect/panic on untrusted input outside tests; the only unwrap is in #[cfg(test)] on a locally constructed save-state with an immediate validity check.
Safety Comment On New Unsafe ✅ Passed The commit adds no new unsafe blocks or unsafe fn; the diff contains no unsafe tokens in changed Rust files, so no new SAFETY comment was required.

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: 2

🤖 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 12-14: Move the DSP-1 user-visible fix entry from the 1.22.0
section into the existing [Unreleased] section in CHANGELOG.md, preserving the
entry text and leaving the 1.22.0 release notes unchanged.

In `@docs/STATUS.md`:
- Around line 28-34: Restrict the no-regression claims tied to dsp1_raster_ramp
to the DSP-1/Pilotwings path, removing or narrowing assertions covering DSP-2/4
and ST010/011 unless separate framebuffer-hash evidence is cited. Apply this
guidance in docs/STATUS.md lines 28-34, CHANGELOG.md lines 28-34, and
to-dos/VERSION-PLAN.md lines 1429-1435.
🪄 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: 55ae9401-d25e-4169-a390-578360065d07

📥 Commits

Reviewing files that changed from the base of the PR and between c8a58ab and e9682d3.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock and included by Cargo.lock
📒 Files selected for processing (25)
  • .gitignore
  • 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/upd77c25.rs
  • crates/rustysnes-cheevos/Cargo.toml
  • crates/rustysnes-core/Cargo.toml
  • 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: lint
  • GitHub Check: test-light
  • GitHub Check: build
  • GitHub Check: build demo + docs
🧰 Additional context used
📓 Path-based instructions (15)
**/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-gfx-shaders/Cargo.toml
  • crates/rustysnes-savestate/Cargo.toml
  • crates/rustysnes-cart/Cargo.toml
  • crates/rustysnes-netplay/Cargo.toml
  • crates/rustysnes-ios/Cargo.toml
  • crates/rustysnes-monetization/Cargo.toml
  • crates/rustysnes-cpu/Cargo.toml
  • crates/rustysnes-script/Cargo.toml
  • crates/rustysnes-ppu/Cargo.toml
  • crates/rustysnes-test-harness/Cargo.toml
  • Cargo.toml
  • crates/rustysnes-mobile/Cargo.toml
  • crates/rustysnes-cheevos/Cargo.toml
  • crates/rustysnes-frontend/Cargo.toml
  • crates/rustysnes-android/Cargo.toml
  • crates/rustysnes-apu/Cargo.toml
  • crates/rustysnes-core/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-gfx-shaders/Cargo.toml
  • crates/rustysnes-savestate/Cargo.toml
  • crates/rustysnes-cart/Cargo.toml
  • crates/rustysnes-netplay/Cargo.toml
  • crates/rustysnes-ios/Cargo.toml
  • crates/rustysnes-monetization/Cargo.toml
  • crates/rustysnes-cpu/Cargo.toml
  • crates/rustysnes-script/Cargo.toml
  • crates/rustysnes-ppu/Cargo.toml
  • crates/rustysnes-test-harness/Cargo.toml
  • Cargo.toml
  • crates/rustysnes-mobile/Cargo.toml
  • crates/rustysnes-cheevos/Cargo.toml
  • README.md
  • crates/rustysnes-frontend/Cargo.toml
  • crates/rustysnes-android/Cargo.toml
  • crates/rustysnes-apu/Cargo.toml
  • crates/rustysnes-core/Cargo.toml
  • CHANGELOG.md
  • crates/rustysnes-test-harness/tests/dsp1_raster_ramp.rs
  • docs/cart.md
  • docs/STATUS.md
  • crates/rustysnes-cart/src/coproc/upd77c25.rs
  • to-dos/VERSION-PLAN.md
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-gfx-shaders/Cargo.toml
  • crates/rustysnes-savestate/Cargo.toml
  • crates/rustysnes-cart/Cargo.toml
  • crates/rustysnes-netplay/Cargo.toml
  • crates/rustysnes-ios/Cargo.toml
  • crates/rustysnes-monetization/Cargo.toml
  • crates/rustysnes-cpu/Cargo.toml
  • crates/rustysnes-script/Cargo.toml
  • crates/rustysnes-ppu/Cargo.toml
  • crates/rustysnes-test-harness/Cargo.toml
  • crates/rustysnes-mobile/Cargo.toml
  • crates/rustysnes-cheevos/Cargo.toml
  • crates/rustysnes-frontend/Cargo.toml
  • crates/rustysnes-android/Cargo.toml
  • crates/rustysnes-apu/Cargo.toml
  • crates/rustysnes-core/Cargo.toml
  • crates/rustysnes-test-harness/tests/dsp1_raster_ramp.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-gfx-shaders/Cargo.toml
  • crates/rustysnes-savestate/Cargo.toml
  • crates/rustysnes-cart/Cargo.toml
  • crates/rustysnes-netplay/Cargo.toml
  • crates/rustysnes-ios/Cargo.toml
  • crates/rustysnes-monetization/Cargo.toml
  • crates/rustysnes-cpu/Cargo.toml
  • crates/rustysnes-script/Cargo.toml
  • crates/rustysnes-ppu/Cargo.toml
  • crates/rustysnes-test-harness/Cargo.toml
  • Cargo.toml
  • crates/rustysnes-mobile/Cargo.toml
  • crates/rustysnes-cheevos/Cargo.toml
  • crates/rustysnes-frontend/Cargo.toml
  • crates/rustysnes-android/Cargo.toml
  • crates/rustysnes-apu/Cargo.toml
  • crates/rustysnes-core/Cargo.toml
  • crates/rustysnes-test-harness/tests/dsp1_raster_ramp.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-gfx-shaders/Cargo.toml
  • crates/rustysnes-savestate/Cargo.toml
  • crates/rustysnes-cart/Cargo.toml
  • crates/rustysnes-netplay/Cargo.toml
  • crates/rustysnes-ios/Cargo.toml
  • crates/rustysnes-monetization/Cargo.toml
  • crates/rustysnes-cpu/Cargo.toml
  • crates/rustysnes-script/Cargo.toml
  • crates/rustysnes-ppu/Cargo.toml
  • crates/rustysnes-test-harness/Cargo.toml
  • crates/rustysnes-mobile/Cargo.toml
  • crates/rustysnes-cheevos/Cargo.toml
  • crates/rustysnes-frontend/Cargo.toml
  • crates/rustysnes-android/Cargo.toml
  • crates/rustysnes-apu/Cargo.toml
  • crates/rustysnes-core/Cargo.toml
  • crates/rustysnes-test-harness/tests/dsp1_raster_ramp.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-gfx-shaders/Cargo.toml
  • crates/rustysnes-savestate/Cargo.toml
  • crates/rustysnes-cart/Cargo.toml
  • crates/rustysnes-netplay/Cargo.toml
  • crates/rustysnes-ios/Cargo.toml
  • crates/rustysnes-monetization/Cargo.toml
  • crates/rustysnes-cpu/Cargo.toml
  • crates/rustysnes-script/Cargo.toml
  • crates/rustysnes-ppu/Cargo.toml
  • crates/rustysnes-test-harness/Cargo.toml
  • crates/rustysnes-mobile/Cargo.toml
  • crates/rustysnes-cheevos/Cargo.toml
  • crates/rustysnes-frontend/Cargo.toml
  • crates/rustysnes-android/Cargo.toml
  • crates/rustysnes-apu/Cargo.toml
  • crates/rustysnes-core/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
  • CHANGELOG.md
  • crates/rustysnes-test-harness/tests/dsp1_raster_ramp.rs
  • docs/cart.md
  • docs/STATUS.md
  • crates/rustysnes-cart/src/coproc/upd77c25.rs
  • to-dos/VERSION-PLAN.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:

  • README.md
  • CHANGELOG.md
  • docs/cart.md
  • docs/STATUS.md
  • to-dos/VERSION-PLAN.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
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-test-harness/tests/dsp1_raster_ramp.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-test-harness/tests/dsp1_raster_ramp.rs
  • crates/rustysnes-cart/src/coproc/upd77c25.rs
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/cart.md
  • docs/STATUS.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/cart.md
  • docs/STATUS.md
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-cart/src/coproc/upd77c25.rs
🔇 Additional comments (22)
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-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!

.gitignore (1)

142-145: 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!

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

4-4: LGTM!

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

4-4: LGTM!

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

400-423: LGTM!

docs/cart.md (1)

105-113: LGTM!

crates/rustysnes-test-harness/tests/dsp1_raster_ramp.rs (1)

1-73: LGTM!

Comment thread CHANGELOG.md Outdated
Comment thread docs/STATUS.md
Comment on lines +28 to +34
**`v1.22.0 "Horizon"` fixes the long-standing DSP-1 continuous-mode Mode-7 flat floor** (Pilotwings
flight, SMK track): the shared NEC-DSP host-sync (`run_until_rqm`) stopped one host-write too early,
so reading a host input word raised RQM before the firmware cleared DRC to 16-bit — mis-framing the
DSP-1 parameter block into a degenerate, constant-across-scanlines projection. Running the engine to
the firmware's true host-wait spin (matching ares) restores the per-scanline perspective ramp,
verified against a MesenCE reference with **zero change** to any non-flight NEC-coprocessor rendering
(DSP-1/2/4 title/attract framebuffers byte-identical before and after).

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Files ==\n'
git ls-files 'docs/STATUS.md' 'CHANGELOG.md' 'to-dos/VERSION-PLAN.md' 'docs/cart.md' | cat

printf '\n== Search for dsp1_raster_ramp ==\n'
rg -n "dsp1_raster_ramp|run_until_rqm|byte-identical|zero change|zero-change|all six NEC|DSP-1/2/3/4|ST010|ST011|Revision::Upd7725" \
  docs/STATUS.md CHANGELOG.md to-dos/VERSION-PLAN.md docs/cart.md .

printf '\n== Test definition locations ==\n'
rg -n "dsp1_raster_ramp" . --glob '!target' --glob '!node_modules'

printf '\n== Outline of likely test file(s) ==\n'
for f in $(rg -l "dsp1_raster_ramp" . --glob '!target' --glob '!node_modules'); do
  echo "--- $f ---"
  ast-grep outline "$f" --view expanded || true
done

Repository: doublegate/RustySNES

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Test file contents (bounded) ==\n'
for f in $(rg -l "dsp1_raster_ramp" . --glob '!target' --glob '!node_modules'); do
  echo "--- $f ---"
  wc -l "$f"
  # print relevant window around the test
  line=$(rg -n "dsp1_raster_ramp" "$f" | head -n1 | cut -d: -f1)
  start=$(( line > 40 ? line - 40 : 1 ))
  end=$(( line + 120 ))
  sed -n "${start},${end}p" "$f" | cat -n
done

Repository: doublegate/RustySNES

Length of output: 18599


Narrow the no-regression claim to the DSP-1 path. dsp1_raster_ramp only supports the Pilotwings/DSP-1 fix; it does not back the byte-identical/zero-change claims for DSP-2/4 or ST010/011 in docs/STATUS.md#L28-L34, CHANGELOG.md#L28-L34, and to-dos/VERSION-PLAN.md#L1429-L1435. Trim those statements or point to separate framebuffer-hash coverage.

📍 Affects 3 files
  • docs/STATUS.md#L28-L34 (this comment)
  • CHANGELOG.md#L28-L34
  • to-dos/VERSION-PLAN.md#L1429-L1435
🤖 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 `@docs/STATUS.md` around lines 28 - 34, Restrict the no-regression claims tied
to dsp1_raster_ramp to the DSP-1/Pilotwings path, removing or narrowing
assertions covering DSP-2/4 and ST010/011 unless separate framebuffer-hash
evidence is cited. Apply this guidance in docs/STATUS.md lines 28-34,
CHANGELOG.md lines 28-34, and to-dos/VERSION-PLAN.md lines 1429-1435.

Sources: Coding guidelines, Path instructions

@doublegate
doublegate force-pushed the fix/dsp1-continuous-raster branch from e9682d3 to 2b78a1f Compare July 29, 2026 12:48
@doublegate

Copy link
Copy Markdown
Owner Author

Review adjudication (pushed in 2b78a1f)

Thanks @coderabbitai and the Antigravity reviewer. Resolutions:

Antigravity — applied:

  • upd77c25.rs single-instruction-spin assumption — documented it explicitly: the self.pc == pc_before check detects the one-word JRQM/JNRQM self-loop every NEC firmware in this fixed chip set (DSP-1/2/3/4, ST010/011) uses for its host-wait; a hypothetical multi-instruction spin falls through to the RUN_CAP cap (still correct, one wasted catch-up), noted in the doc comment.
  • dsp1_raster_ramp.rs BTreeSet → windows(2) — swapped for the alloc-free cycles.windows(2).any(|w| w[0] != w[1]) (exactly "not all equal", no heap).
  • Subject >72 chars — shortened to fix(dsp1): step continuous-mode NEC DSP to its host-wait spin (v1.22.0) (71).

CodeRabbit — CHANGELOG [Unreleased]: respectfully declining the move. This is a release-closeout PR, not a normal feature PR — it renames [Unreleased]## [1.22.0] "Horizon" - <date>, which is precisely the signal release-auto.yml keys on (an empty [Unreleased] immediately followed by a closed-out [X.Y.Z] "Name" - date section) to auto-tag and publish on merge. This matches how every prior release was cut (e.g. [1.21.0] "Touchstone"). Moving the entry back under [Unreleased] would leave nothing to release. I did add the missing "Horizon" codename to the heading to match the format the automation and the prior sections use.

Gates re-verified after the changes: dsp1_raster_ramp green, cargo test --workspace 527/527, fmt + clippy (default & per-feature) + no_std + doc all green.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

workflow=$(fd -t f -i '^release-auto\.ya?ml$' . | head -n 1 || true)
if [ -n "$workflow" ]; then
  echo "=== $workflow ==="
  cat -n "$workflow"
else
  echo "release-auto workflow not found"
fi

echo "=== CHANGELOG release headings ==="
rg -n -C2 '^\s*##\s+\[(Unreleased|1\.22\.0|1\.21\.0)\]' CHANGELOG.md || true

echo "=== commit 2b78a1f changed files ==="
git show --stat --oneline --no-renames 2b78a1f

Length of output: 3032


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== release automation workflow candidates ==="
fd -H -t f -i 'release.*auto|auto.*release|release' .github 2>/dev/null || true

echo "=== references to changelog release parsing ==="
rg -n -H -i -C4 'CHANGELOG|Unreleased|release-auto|auto-tag|publish' .github 2>/dev/null || true

Length of output: 13909


@doublegate The CHANGELOG finding is withdrawn. .github/workflows/release-auto.yml explicitly requires an empty ## [Unreleased] section immediately followed by ## [X.Y.Z] "Name" - date; CHANGELOG.md Lines 10–12 satisfies that contract for v1.22.0 “Horizon”. Moving the release notes under [Unreleased] would prevent automatic tagging and publishing.


✏️ Learnings added
Learnt from: doublegate
URL: https://github.com/doublegate/RustySNES/pull/253

Timestamp: 2026-07-29T12:49:17.189Z
Learning: For RustySNES release-closeout pull requests, `.github/workflows/release-auto.yml` uses an empty `## [Unreleased]` section immediately followed by `## [X.Y.Z] "Name" - date` in `CHANGELOG.md` as the required automatic release trigger. Do not request that the closed release notes be moved back under `[Unreleased]`; non-empty content there suppresses release creation.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

DSP-1 games rendered a flat, perspective-less Mode-7 ground (Pilotwings flight,
Super Mario Kart track) — the last major visible accuracy gap in the coprocessor
set. Root-caused to the shared NEC-DSP host interface stopping the engine one
host-write too early.

Root cause
----------
`Upd77c25::run_until_rqm` advances the engine to its parked state after each host
DR access, and stopped at the FIRST `RQM=set`. But reading a host input word
(`src == 8`) raises RQM as a side effect while the firmware still has pending
setup — notably clearing DRC to 16-bit for the next transfer. Stopping there
deferred the DRC clear past the next host write, so Pilotwings' 7-word `cmd 0x02`
projection block transferred 8-bit-misaligned and the DSP computed a degenerate,
constant-across-scanlines matrix → a flat floor. The engine now runs to the
firmware's genuine host-wait spin (a single-instruction `JRQM`/`JNRQM` self-loop),
matching ares' continuous clock-stepping to the wait point.

How it was pinned
-----------------
- A MesenCE Lua tracer captured a real flight's DR command stream; RustySNES
  received byte-identical DSP-1 INPUT but produced constant OUTPUT (8 distinct
  words/frame) vs the reference's per-scanline ramp (264 distinct).
- Replaying the real command stream through a fresh core reproduced the flat
  result deterministically, ruling out bus/game timing.
- ares' readDR/writeDR, ALU/multiplier/flag logic, execLD, and the SR-write mask
  were verified line-for-line identical to RustySNES, isolating the single
  structural difference: the host-wait stepping.

Verification and no-regression
------------------------------
- In-game Pilotwings flight went from 150/150 flat frames to a per-scanline ramp
  on every frame.
- `run_until_rqm` is shared by all six NEC coprocessors (DSP-1/2/3/4, ST010/011);
  before/after framebuffer content-hashes are byte-identical for Pilotwings
  title/attract, Dungeon Master (DSP-2), and Top Gear 3000 (DSP-4).
- New `dsp1_raster_ramp` regression test replays the real params and asserts the
  ramp — confirmed it FAILS on the pre-fix core (`[fe7f 01ff fe00 fe7f]`) and
  PASSES after.
- `dsp1_oncart` + 156 cart-crate unit tests green; `cargo test --workspace`
  527/527; all gates green (fmt, clippy default + per-feature, no_std, doc).

Release
-------
Cuts v1.22.0 "Horizon" (release-closeout: empty [Unreleased] + closed-out
[1.22.0] section triggers release-auto.yml on merge). Version bumped across the
workspace; CHANGELOG, STATUS, VERSION-PLAN, docs/cart.md §the shared NEC core
updated; root .gitignore ignores the local `.codegraph/` index. The full
pin-exact rewrite (scheduler-stepped DSP for cycle-accurate latency) is a scoped
future effort — this fix is byte-exact at the RQM bus boundary, all DSP-1 games
observe. Throwaway diagnostics stay on `fix/dsp1-mode7`, not in this release.

Addresses review: documented the single-instruction-spin assumption
(upd77c25.rs), swapped the test's BTreeSet for an alloc-free windows(2) check,
added the "Horizon" codename to the CHANGELOG heading, and shortened the subject.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@doublegate
doublegate force-pushed the fix/dsp1-continuous-raster branch from 2b78a1f to 00bc097 Compare July 29, 2026 12:53
@github-actions

Copy link
Copy Markdown

Antigravity review (Gemini via Ultra)

This PR updates the NEC µPD77C25 host-synchronization logic (run_until_rqm) to advance execution until the firmware reaches its host-wait spin loop rather than stopping at the first RQM status bit assertion, fixing DSP-1 continuous-mode Mode-7 perspective rendering, and bumps workspace crate versions to v1.22.0.

Blocking issues

  • crates/rustysnes-libretro/Cargo.toml version mismatch: Cargo.lock updates rustysnes-libretro to 1.22.0, but crates/rustysnes-libretro/Cargo.toml was omitted from the PR diff and remains at 1.21.0. This breaks cargo check --locked and CI builds due to manifest/lockfile desynchronization.

Suggestions

  • crates/rustysnes-cart/src/coproc/upd77c25.rs:422: Budget exhaustion at RUN_CAP fails silently. Consider adding a log/warning when budget == 0 so non-standard firmware or multi-instruction spin loops falling through the cap are observable during debugging.

Nitpicks

  • crates/rustysnes-test-harness/tests/dsp1_raster_ramp.rs:57: _param_out is assigned but unused; drain the 4 parameter words directly with for _ in 0..4 { read_word(&mut dsp); } to keep the harness clean.

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

@doublegate
doublegate merged commit 44a3316 into main Jul 29, 2026
16 checks passed
@doublegate
doublegate deleted the fix/dsp1-continuous-raster branch July 29, 2026 13:28
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.

1 participant