Skip to content

feat(cart): Phase 5 cart I/O — PI domain regs, save backends, SI joybus, retail HLE boot - #117

Merged
doublegate merged 6 commits into
mainfrom
feat/phase-5-pi-boot
Jul 24, 2026
Merged

feat(cart): Phase 5 cart I/O — PI domain regs, save backends, SI joybus, retail HLE boot#117
doublegate merged 6 commits into
mainfrom
feat/phase-5-pi-boot

Conversation

@doublegate

@doublegate doublegate commented Jul 24, 2026

Copy link
Copy Markdown
Owner

What

Phase 5 (cart boot + saves) — the cartridge I/O subsystems: PI completion, the four save backends, and the SI/PIF/joybus transport. (The retail HLE boot + the commercial-ROM capstone are follow-ups.)

PI completion

  • The BSD domain-timing registers (PI_BSD_DOM1/2 LAT/PWD/PGS/RLS, 0x0460_00140x0460_0030) store and read back with field-width masking (LAT/PWD 8-bit, PGS 4-bit, RLS 2-bit), DOM1/DOM2 independent.

The four save backends (rustyn64-cart::save::SaveDevice)

  • SRAM (flat 32 KiB, PI DOM2), FlashRAM as its real erase/program/status command machine (CIR at 0x0801_0000; ReadArray/Status/SiliconID/LoadPage modes; program is a bit-clearing AND, so a page must be erased first — a flat store would mis-save), EEPROM 4k/16k (joybus 8-byte blocks), and the Controller Pak (joybus 32-byte blocks + the data CRC8).
  • SRAM/FlashRAM route through Cart::pi_read/pi_write (+ a word-level path for the FlashRAM CIR); the Bus's direct-I/O PI write now persists to the save instead of only latching.

The SI joybus (rustyn64-cart::pif + the Bus SI block at 0x0480_0000)

  • The 64-byte PIF RAM, the SI registers (SI_DRAM_ADDR, SI_PIF_AD_RD64B/WR64B), the 64-byte PIF↔RDRAM DMA, and the SI interrupt. The joybus frame executor runs 0x00/0xFF info, 0x01 controller state (from the Bus's four packed port words), 0x02/0x03 Controller-Pak access, and 0x04/0x05 EEPROM.
  • A controller read runs end to end — frame staged in RDRAM → WR64B DMA to PIF → RD64B executes the handshakes → replies DMA'd back — verified by a Bus integration test.

Tests / gates

  • rustyn64-cart: PI domain registers, each save backend's round-trip (incl. the FlashRAM erase→load→program sequence + program-only-clears-bits), joybus command decode, EEPROM/Controller-Pak joybus round-trips.
  • rustyn64-core: a direct-I/O SRAM round-trip through the CPU path, and the end-to-end SI joybus controller read.
  • cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace, RUSTDOCFLAGS="-D warnings" cargo doc, the no_std cross-build, markdownlint — all green.

Next (follow-ups)

The retail HLE boot (+ CIC seed), PI open-bus / non-instant DMA, and the local commercial-ROM boot capstone land in follow-up PRs toward the v0.6.0 release.

🤖 Generated with Claude Code

Retail HLE boot (added)

  • rom::hle_boot seeds the state IPL3 expects (COP0 Status/Config, the s3–s7 GPRs from the per-CIC seed injected into PIF RAM, PI DOM1 timing from the header) and copies the cart's real IPL3 into RSP DMEM, jumping to 0xA400_0040 — IPL3 then copies the game to RDRAM and jumps to the header entry. Algorithm from simple64's bootrom_hle.c (GPLv3 — studied, not copied). Unit-tested for the seeded state; running IPL3 to a game is the local commercial capstone.

doublegate and others added 3 commits July 24, 2026 03:11
Phase 5 (cart boot + saves), Sprint 1 — the first of the PI-completion work.
Implements PI_BSD_DOM1/DOM2 LAT/PWD/PGS/RLS (0x0460_0014..0x0460_0030): each
stores the written value masked to its field width (LAT/PWD 8-bit, PGS 4-bit,
RLS 2-bit) and reads it back, for DOM1 and DOM2 independently. IPL2 programs
DOM1 from the ROM header (LAT 64 / PWD 18 / PGS 7 / RLS 3 for official ROMs), so
a game that reads them back must see what it wrote.

Deriving DMA duration from these (the non-instant completion) is the follow-up
step that reads the stored fields; open-bus on unmapped PI reads and the retail
HLE boot are the rest of Sprint 1.

Unit-tested (store/read-back per field + width masking + DOM1/DOM2 independence).
Gates: fmt, clippy (-D warnings), cargo test -p rustyn64-cart — green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r Pak)

Phase 5 (cart boot + saves), Sprint 1 — the save subsystem.

- New `rustyn64-cart::save::SaveDevice`: SRAM (flat 32 KiB, PI DOM2), FlashRAM as
  its real erase/program/status state machine (CIR at 0x0801_0000; ReadArray /
  Status / SiliconID / LoadPage modes; program is a bit-clearing AND, so a page
  must be erased to all-ones first — a flat store would mis-save), EEPROM 4k/16k
  (joybus 8-byte blocks), and the Controller Pak (joybus 32-byte blocks).
- Wired into `Cart`: SRAM/FlashRAM route through pi_read/pi_write + a word-level
  pi_write_word for the FlashRAM CIR; the joybus backends expose block accessors
  for the SI/PIF module (Sprint 2). `Cart::save()` returns the persistable bytes.
- The Bus's direct-I/O PI write now persists to the save (via pi_write_word), not
  just latches for read-back timing — so a `SW` to SRAM/FlashRAM actually stores.

Tests: SRAM/EEPROM/Controller-Pak round-trips, the FlashRAM erase→load→program
sequence, and program-only-clears-bits (rustyn64-cart); a Bus-level SRAM
direct-I/O round-trip through the CPU path (rustyn64-core). docs/cart.md + CHANGELOG.

Also this sprint: the PI BSD domain-timing registers (prior commit).

Gates: fmt, clippy (-D warnings), workspace test, no_std, markdownlint — all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 5 (cart boot + saves), Sprint 2 — the SI/PIF/joybus transport.

- New `rustyn64-cart::pif`: the 64-byte PIF RAM + the joybus frame executor. It
  parses the per-channel handshakes (TX RX cmd.. resp..) and runs 0x00/0xFF info,
  0x01 controller state, 0x02/0x03 Controller-Pak accessory access (with the
  data CRC8, seed 0x00 poly 0x85), and 0x04/0x05 EEPROM blocks. Channels 0-3 are
  controller ports (fed by the Bus's packed port words), channel 4 the cart bus
  (EEPROM). The `Pif` lives in the `Cart` alongside the save device.
- Bus SI block at 0x0480_0000: SI_DRAM_ADDR, SI_PIF_AD_RD64B/WR64B, SI_STATUS;
  the 64-byte PIF↔RDRAM DMA (WR64B loads the frame, RD64B executes it + copies
  the replies out), and MI_INTR.si. The PIF-RAM window (0x1FC0_07C0) is CPU-
  addressable for direct access.

Tests: joybus command decode + EEPROM/Controller-Pak round-trips (rustyn64-cart);
a Bus end-to-end controller read through SI DMA → PIF execute → RDRAM, with the
SI interrupt (rustyn64-core). docs/cart.md + CHANGELOG.

Gates: fmt, clippy (-D warnings), workspace test, no_std, markdownlint — all green.

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

coderabbitai Bot commented Jul 24, 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: 21 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: fb9f788b-08ae-4a2d-971b-2222bc523d22

📥 Commits

Reviewing files that changed from the base of the PR and between aab7f37 and bbb32cd.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • crates/rustyn64-cart/src/lib.rs
  • crates/rustyn64-cart/src/pi.rs
  • crates/rustyn64-cart/src/pif.rs
  • crates/rustyn64-cart/src/save.rs
  • crates/rustyn64-test-harness/src/rom.rs
  • crates/rustyn64-test-harness/tests/commercial_boot.rs
  • docs/accuracy-ledger.md
  • docs/adr/0009-hle-vs-real-pif-boot.md
📝 Walkthrough

Walkthrough

The cartridge subsystem adds four save backends, FlashRAM command handling, PIF joybus execution, SI DMA and interrupt routing, PI BSD timing registers, retail HLE boot support, integration tests, and updated documentation.

Changes

Cartridge and peripheral emulation

Layer / File(s) Summary
PI BSD timing registers
crates/rustyn64-cart/src/pi.rs
PI stores and masks independent DOM1 and DOM2 timing-register values, with readback tests.
Save device backends
crates/rustyn64-cart/src/save.rs
SaveDevice implements SRAM, FlashRAM, EEPROM, and Controller Pak access, including FlashRAM command state and block operations.
PIF joybus execution
crates/rustyn64-cart/src/pif.rs
PIF RAM command frames execute controller, Controller Pak, and EEPROM operations with CRC and error handling.
Cartridge composition and PI routing
crates/rustyn64-cart/src/lib.rs
Cart composes PIF and save devices, exposes PIF access, and routes PI reads and writes to the appropriate backend.
SI DMA and integration
crates/rustyn64-core/src/bus.rs
Bus SI/PIF windows, DMA, interrupt handling, PI word writes, and end-to-end controller and save tests are added.
Retail HLE boot
crates/rustyn64-test-harness/src/rom.rs
Retail ROM boot state is seeded from CIC and ROM data, with PI timing and IPL3 handoff validation.
Documentation updates
CHANGELOG.md, docs/cart.md
The implemented cart, save, PI, SI, and PIF functionality is documented.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CpuBus
  participant Bus
  participant Cart
  participant Pif
  participant SaveDevice
  CpuBus->>Bus: write SI DMA command
  Bus->>Cart: transfer PIF RAM
  Cart->>Pif: execute joybus frame
  Pif->>SaveDevice: access EEPROM or Controller Pak
  SaveDevice-->>Pif: return save data
  Pif-->>Cart: write reply bytes
  Cart-->>Bus: transfer reply to RDRAM
  Bus-->>CpuBus: assert MI_INTR.si
Loading
🚥 Pre-merge checks | ✅ 7 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Oracle Number Is Stated ⚠️ Warning FAIL — the PR docs mention cart/PIF work, but nowhere state an n64-systemtest delta or 'not measured'; docs/STATUS.md only carries the pre-existing 93-suite-wide figure. Add a measured oracle note for the emulation change: either the exact n64-systemtest failing-assertion delta, or explicit 'not measured', in the relevant ledger/status entry.
Measured, Never Tuned ⚠️ Warning cic_seed() uses cen64-derived values with no manual/wiki citation or accuracy-ledger entry; C-26 only covers the HLE handoff boundary. Add an accuracy-ledger entry for the CIC seed words (or cite a manual/wiki page) and note how they were measured; C-26 is not enough.
Title check ⚠️ Warning The title is relevant, but it exceeds the 72-character limit and is not written in imperative mood. Shorten it to 72 characters or fewer and rewrite the subject in imperative mood, e.g. 'feat(cart): add PI regs, save backends, SI joybus'.
✅ Passed checks (7 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Docs-As-Spec Sync ✅ Passed PASS — docs/cart.md was updated to spec the new PI, save, and SI/PIF behaviours; no uncovered chip-crate behaviour change remained.
Changelog Entry For User-Visible Changes ✅ Passed CHANGELOG.md has an [Unreleased] Phase 5 cart section covering the PI, save backends, and SI/PIF user-visible changes.
Unsafe Stays Out Of The Chip Crates ✅ Passed No Rust unsafe blocks/functions exist; chip/core crates still have #![forbid(unsafe_code)], and this PR adds none outside frontend.
Description check ✅ Passed The description matches the cartridge I/O and HLE boot changes in the diff.

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

Phase 5 (cart boot + saves), Sprint 1 — `rom::hle_boot`. Boots a retail ROM by
seeding the machine state IPL3 leaves and running the cart's *real* IPL3 (rather
than reimplementing the boot copy/jump):

- Inject the per-CIC seed (6101/6102/6103/6105/6106, from cen64) into PIF RAM
  0x24, whence the boot reads the s3-s7 GPRs.
- COP0 Status = 0x3400_0000, Config = 0x7006_E463; s3-s7 = rom_type 0 / tv NTSC /
  reset cold / seed byte / 0; PI DOM1 timing from the ROM header's first word.
- Copy the real IPL3 (ROM 0x40..0x1000) into RSP DMEM and set PC = 0xA400_0040;
  IPL3 then copies the game to RDRAM and jumps to the header entry.

An ELF-payload ROM (n64-systemtest) is still handed to seed_ipl3_handoff. The
algorithm follows simple64's bootrom_hle.c (GPLv3 — studied, not copied). A
committable unit test asserts the boot seeds the DMEM copy / s6 seed / PIF-RAM
seed / PI DOM1 / PC; running IPL3 to a game is the local commercial capstone.

Gates: fmt, clippy (-D warnings), workspace test — all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@doublegate doublegate changed the title feat(cart): Phase 5 cart I/O — PI domain regs, the four save backends, the SI joybus feat(cart): Phase 5 cart I/O — PI domain regs, save backends, SI joybus, retail HLE boot Jul 24, 2026

@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: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/cart.md (1)

19-38: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Interfaces pseudocode is stale against this PR's own Cart additions.

Cart's synopsis here still shows only load/header/save/tick, but this PR adds pif_ram, pif_load, pif_read, pif_write, pif_execute, save_device_mut, and pi_write_word to the real Cart in crates/rustyn64-cart/src/lib.rs. As per path instructions, "Docs are the SPEC here, not a changelog... A behaviour change with an untouched spec is a finding" — update the interface listing in the same commit that changes the state/behaviour sections below it.

🤖 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/cart.md` around lines 19 - 38, Update the Cart interface synopsis in the
docs to include the newly exposed pif_ram, pif_load, pif_read, pif_write,
pif_execute, save_device_mut, and pi_write_word methods, matching their actual
signatures and public behavior. Keep the existing load, header, save, and tick
entries intact so the interface listing fully reflects Cart’s current API.

Source: Path instructions

🤖 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 13-30: The cart subsystem changelog entry must state the measured
effect on n64-systemtest’s failing-assertion count. Update the “Added — cart
(Phase 5, Sprint 1)” section with the observed before/after delta, or explicitly
state that n64-systemtest was not measured.

In `@crates/rustyn64-cart/src/lib.rs`:
- Around line 26-27: Add rustdoc comments for the public module declarations pif
and save in the crate root, describing each module’s purpose. Keep the existing
module declarations and their contents unchanged.

In `@crates/rustyn64-cart/src/pi.rs`:
- Around line 207-210: Update the comment in the Pi register-handling branch
near addr to remove the claim that timing registers affect DMA duration via
transfer_cycles. Document only the implemented masked write/readback behavior,
and state that transfer-duration modelling remains a follow-up consistent with
the existing note near the initialization logic.
- Around line 158-160: Add provenance for both hardware assertions: at
crates/rustyn64-cart/src/pi.rs lines 158-160, cite authoritative evidence for
the 0x7F length-register readback or explicitly label the zero return as an
unverified simplification; at lines 363-367, cite the source or add an
accuracy-ledger entry for the 64/18/7/3 timing tuple with its evidence status.
If either claim is only a fixture or simplification, remove the hardware
attribution.
- Around line 52-55: Add the missing public PI_BSD_DOM2_PWD and PI_BSD_DOM2_PGS
constants at the decoder-supported addresses between PI_BSD_DOM2_LAT and
PI_BSD_DOM2_RLS, then update the DOM2 independence test to assert both registers
independently.

In `@crates/rustyn64-cart/src/pif.rs`:
- Around line 219-232: Update the channel-4 EEPROM handlers for commands 0x04
and 0x05 in the PIF command dispatch to verify that save is SaveDevice::Eeprom
before reading or writing. For non-EEPROM devices, call the existing
mark_no_device path and avoid returning data or success status; preserve the
current read/write behavior for present EEPROM devices.

In `@crates/rustyn64-cart/src/save.rs`:
- Around line 94-99: Validate the page-derived sector base in the 0x4B command
handling before storing it in erase_sector, ensuring only bases within the flash
array are accepted, or add an equivalent bounds guard in the 0x78 sector-erase
execution path before slicing. Preserve valid sector erases and prevent
out-of-range guest page indices from reaching self.array[base..end] in Execute.
- Around line 138-141: Update the FlashMode::Status branch in the status read
handling to return self.status for every odd byte offset, while returning 0 for
even offsets. Preserve the existing repeated 00/status pattern and extend flash
status coverage to verify offsets such as 1, 5, and 7 in addition to 3.
- Around line 199-206: Update SaveDevice::pi_read to return Some only for
addresses within the save device window: bound-check SRAM access and gate
FlashRAM access so out-of-window reads return None. Also update Cart::pi_read to
consult self.save only when the address is within the existing
SAVE_PI_BASE..ROM_PI_BASE range, matching Cart::pi_write; apply the
corresponding changes in crates/rustyn64-cart/src/save.rs:199-206 and
crates/rustyn64-cart/src/lib.rs:340-349.

---

Outside diff comments:
In `@docs/cart.md`:
- Around line 19-38: Update the Cart interface synopsis in the docs to include
the newly exposed pif_ram, pif_load, pif_read, pif_write, pif_execute,
save_device_mut, and pi_write_word methods, matching their actual signatures and
public behavior. Keep the existing load, header, save, and tick entries intact
so the interface listing fully reflects Cart’s current API.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 85f2295f-0a46-4d8c-9a94-6677d7733a69

📥 Commits

Reviewing files that changed from the base of the PR and between d6a94c4 and 4145b33.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • crates/rustyn64-cart/src/lib.rs
  • crates/rustyn64-cart/src/pi.rs
  • crates/rustyn64-cart/src/pif.rs
  • crates/rustyn64-cart/src/save.rs
  • crates/rustyn64-core/src/bus.rs
  • docs/cart.md

Comment thread CHANGELOG.md
Comment thread crates/rustyn64-cart/src/lib.rs
Comment thread crates/rustyn64-cart/src/pi.rs
Comment thread crates/rustyn64-cart/src/pi.rs Outdated
Comment thread crates/rustyn64-cart/src/pi.rs Outdated
Comment thread crates/rustyn64-cart/src/pif.rs Outdated
Comment thread crates/rustyn64-cart/src/save.rs
Comment thread crates/rustyn64-cart/src/save.rs
Comment thread crates/rustyn64-cart/src/save.rs

@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 `@crates/rustyn64-test-harness/src/rom.rs`:
- Around line 207-228: Document the CIC seed values and HLE retail-boot
assumptions used by cic_seed and the surrounding HLE boot flow in
docs/accuracy-ledger.md. Include measurement details, falsification criteria,
and the measured impact on the n64-systemtest assertion, replacing reliance on
the cen64 and local simple64 references as unsupported provenance.
- Around line 461-464: Extend the DOM1 header assertions in the ROM test to
validate the encoded RLS value of 3 using the PI_BSD_DOM1_RLS register symbol,
alongside the existing LAT, PWD, and PGS checks. Ensure the test fails if the
DOM1 RLS write is removed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5ba2e22a-5c5f-40b7-811f-a9cf02bddc8f

📥 Commits

Reviewing files that changed from the base of the PR and between 4145b33 and aab7f37.

📒 Files selected for processing (1)
  • crates/rustyn64-test-harness/src/rom.rs

Comment thread crates/rustyn64-test-harness/src/rom.rs
Comment thread crates/rustyn64-test-harness/src/rom.rs Outdated
doublegate and others added 2 commits July 24, 2026 05:06
Address all 11 CodeRabbit comments on PR #117 and add the Phase 5
commercial-boot capstone. Two were guest-reachable defects:

- CRITICAL (save.rs): FlashRAM sector-erase page index was unbounded
  (`page & 0xFFFF`), so a page >= 1024 made `base > array.len()` and the
  0x78 Execute panicked on `array[base..end]`. Clamp the sector to the
  last valid one, matching the 0xA5 Page-Program guard.
- CRITICAL (save.rs/lib.rs): `SaveDevice::pi_read` returned `Some` for
  every address on an Sram/Flash cart, so `Cart::pi_read` never fell
  through to ROM — a game could not read its own cartridge. Gate the
  save window to `SAVE_PI_BASE..ROM_PI_BASE` before consulting it.

Also adopted:
- FlashRAM status now sits at every odd offset (1,3,5,7...), per the
  n64brew Flash burst pattern, not only offset 3.
- Joybus EEPROM read/write (0x04/0x05) now gate on device presence, so a
  non-EEPROM cart replies RX_NO_DEVICE instead of 8 silent zero bytes.
- Expose PI_BSD_DOM2_PWD/PGS and cover all DOM2 fields in the
  independence test; assert DOM1 RLS in the HLE-boot test.
- rustdoc on `pub mod pif/save`; cite the PI 0x7F readback + official-ROM
  timing tuple (N64brew); drop the false DMA-duration comment.

Docs/ledger:
- C-32 records the HLE boot state + per-CIC seeds as cited constants
  (N64brew CIC-NUS/PIF-NUS, cen64), not tuned; simple64 studied only.
- R-18 characterises the commercial-boot capstone honestly: a retail ROM
  boots and executes real code (~10^8 retired instructions) but does not
  yet reach video (VI/RI/F3DEX OS-boot runtime, out of Phase 5 scope).
- CHANGELOG states the measured n64-systemtest effect: 93 -> 90 (-3).

The capstone test (`commercial_boot.rs`, local-only, #[ignore]d) asserts
the achievable claim — boots and retires >= 10^6 instructions without
panicking — and reports the lit-pixel count rather than asserting video.

Gates: fmt, clippy -D warnings, cart/core/harness tests, rustdoc
-D warnings, markdownlint, no_std build all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The retail boot strategy (HLE default, real-PIF staged behind an
off-by-default flag) is a cross-cutting decision that ledger entry C-32
already referenced. Record it as a proper ADR in Nygard format and point
C-32 and the CHANGELOG at ADR 0009, closing the dangling reference. The
HLE path ships now; the real-PIF path is documented as the Sprint-2
deliverable (needs the copyrighted PIF ROM, never CI-gated).

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

Copy link
Copy Markdown

Antigravity review (Gemini via Ultra)

This PR implements Phase 5 cartridge I/O, including PI BSD domain-timing register storage, four save backends (SRAM, FlashRAM, EEPROM, Controller Pak), the SI joybus subsystem with PIF RAM frame execution, and a retail HLE boot sequence.

Blocking issues

  • Incorrect PIF RAM offset targeting in mark_no_device (crates/rustyn64-cart/src/pif.rs:236-241):
    mark_no_device calculates resp - 1 to find the channel's RX length byte, but resp is cmd + tx_len (i + 2 + tx_len). Therefore resp - 1 evaluates to i + 1 + tx_len, which points to the last byte of the TX payload (or the command byte when tx_len == 1) rather than the RX length byte at i + 1. When a channel times out or receives an unsupported command on a request with tx_len > 0, mark_no_device corrupts the TX payload in PIF RAM and fails to set the RX_NO_DEVICE flag (0x80) on the RX length byte (ram[i + 1]), causing guest code to misdetect device presence. mark_no_device should target ram[i + 1] directly (or accept the channel base index i).

Suggestions

  • PI DMA writes to FlashRAM CIR will be silently dropped (crates/rustyn64-cart/src/lib.rs:358-363):
    Cart::pi_write explicitly filters out writes where addr & !3 == save::FLASH_CIR. While pi_write_word handles 32-bit word stores during direct CPU I/O, PI DMA transfers execute byte-by-byte through pi_write. If software issues a FlashRAM command via PI DMA to 0x0801_0000, the byte writes will hit pi_write, be ignored, and flash_cir will never be invoked.
  • Inaccurate comment regarding PIF execution timing (crates/rustyn64-core/src/bus.rs:494):
    The comment on SI_PIF_AD_WR64B (0x10) states that pif_load parses/executes the command block. However, pif_load only updates internal RAM (self.ram = *bytes); pif_execute is actually invoked inside the 0x04 (SI_PIF_AD_RD64B) branch. Update the comment to avoid confusion.

Nitpicks

  • Magic calculation for FlashRAM sector count (crates/rustyn64-cart/src/save.rs:99):
    FLASH_SIZE / FLASH_SECTOR - 1 evaluates to 7 for clamping sector bounds. Replacing the inline math with a named constant like NUM_SECTORS - 1 would make the geometry clearer.

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

@doublegate
doublegate merged commit 2f03f79 into main Jul 24, 2026
11 checks passed
@doublegate
doublegate deleted the feat/phase-5-pi-boot branch July 24, 2026 09:13
doublegate added a commit that referenced this pull request Jul 24, 2026
Version bump 0.5.0 -> 0.6.0 + module-80 doc reconciliation for the v0.6.0 phase-close. Phase 5 (cart boot + saves) complete; #117 + #118 already landed. Full CI matrix green.
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