Initialise native Rust port of Ratchet & Clank: Going Commando

Sets up the foundation for reimplementing the PS2 game in Rust rather than
emulating it. Nothing is implemented yet; this is the scaffold, the verified
target and the working agreements that keep a long project coherent.

Target disc, verified against a real image:

  SCUS_972.68 v1.01, USA Black Label, NTSC 60 Hz
  boot ELF 2,618,684 bytes, SHA-1 bacda860...d098a77, entry 0x00131AE8

The entry point independently matches the value recorded by the rac2-gc-decomp
community reference, which cross-confirms the identification. This is the exact
build the upstream research pins; Greatest Hits v2.00 is a different binary and
is not the target. NTSC matters beyond label compatibility: PAL discs run 50 Hz,
which would make chargeboots and other speed-sensitive movement run at 5/6
speed. Verifying the disc before writing movement code avoids discovering that
in Phase 5.

Contents:

- Five-crate workspace (formats, extract, game, video, engine) on Bevy 0.19.1,
  matching the version ReRAC uses so its Graphics Synthesizer and VU work stays
  portable. `cargo check --workspace` is clean.

- tools/identify-disc.py, which reproduces the identification against any
  disc image and distinguishes a truncated dump from a non-PS2 image.

- .githooks/pre-commit, refusing game data from entering git history: disc
  image extensions, asset directories, container signatures at offset 0, and
  files over 50 MiB. Verified against seven cases including a disc image
  renamed to look like source. Now that this pushes to a remote, this matters
  more than it would have locally.

- AGENTS.md, the operating agreements: no game data ever, reproduce the PS2
  float model rather than approximating it, bound every read, verify against
  the real disc.

- PROVENANCE.md, recording that this follows ReRAC (ISC) and learns from
  rac2-gc-decomp (MIT). Wrench is used as a written specification only and no
  GPLv3 code is copied, since that would relicense everything derived from it.
  RAC2Decomp is explicitly not used: its provenance is disputed in the upstream
  community reference, and it targets the wrong region.

- docs/roadmap.md, phased so the riskiest unknowns are retired early. The two
  that matter: overlay relocation transfers from ReRAC and makes 27 level
  overlays tractable, while VU1 microcode is binary and is the hardest single
  item. Phase 5 is a deliberately scoped Veldin vertical slice as the go/no-go.
This commit is contained in:
2026-10-09 15:42:10 +02:00
commit a02f79a6d1
20 changed files with 6880 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
# Disc identification — VERIFIED 2026-10-09
Your disc was identified while initialising this repository. It is the **best
case target**: the exact build that `OpenRAC/rac2-gc-decomp` pins.
## Result
| Field | Value |
|---|---|
| Title | Ratchet & Clank: Going Commando |
| Serial | `SCUS_972.68` |
| Version | **1.01** (Black Label) |
| Video mode | **NTSC** (60 Hz) |
| Region | USA |
| ISO 9660 volume | `RATCHETANDCLANK2` |
| Image size | 3,828,350,976 bytes |
| Publisher / Preparer | SONY / INSOMNIAC |
### Boot executable (`SCUS_972.68`)
| Property | Value |
|---|---|
| Size | 2,618,684 bytes |
| SHA-1 | `bacda860cbd6723142e86db56f197e47e728970f` |
| CRC-32 | `262705496` |
| Format | ELF32, little-endian, MIPS (EM_MIPS = 8) |
| Entry point | `0x00131AE8` |
The entry point `0x00131AE8` matches the value independently recorded by the
`rac2-gc-decomp` community reference for this target, which cross-confirms the
identification.
## Why this matters
**`VER = 1.01` + `VMODE = NTSC` is exactly what we want.**
- **v1.01 is the pinned target.** `rac2-gc-decomp` states plainly that
*Greatest Hits v2.00 is a different target — do not point a build at it.*
You have the Black Label original.
- **NTSC means 60 Hz.** This is the detail that decides the movement constants.
PAL discs report `VMODE = PAL` and run the game at 50 Hz, which makes the
chargeboots and other speed-sensitive movement run at 5/6 of their intended
speed. Building against NTSC timing constants is correct for this disc.
There is no ambiguity to resolve, so no disc-conversion work is required.
## Not used for identification
Redump's catalogue records the **first track** hash for this disc as
`f2cfaf0eb12b3c0d8fd6db7aa49ce032e8a6020b` (CRC-32 `3828350976`). The values
computed above are the **boot executable's** hash, which is a different thing:
a single file rather than a track. Both are stable identifiers, but they must
not be compared against each other. We identify by serial + `VER` + `VMODE` +
boot-ELF SHA-1, which is unambiguous.
## Disc layout (observed)
R&C2 lists its assets in the ISO 9660 filesystem, unlike R&C1. Confirmed
entries at the disc root:
```
SYSTEM.CNF 60 bytes
RC2.HDR 408,444 <- the table of contents, as a named file
SCUS_972.68 2,618,684 <- the boot ELF
IOPRP255.IMG 264,449
Z6TAIL.DUP 0
G/... <- global WADs (LEVEL*, AUDIO*, SCENE*, MPEG*, HUD, …)
```
`RC2.HDR` being present as a real file is convenient: the extractor can read
the table of contents directly instead of hunting for it at a fixed sector as
with R&C1. The `G/` WADs are listed in the filesystem, so enumeration works
too.
### Observation on MPEG WADs
The three MPEG WADs are large — roughly 1.0 GB, 1.3 GB and 115 MB. That is a
substantial fraction of the 3.8 GB disc and will matter for the video phase
(R&C2 uses IPU/FMV streams, notably the title screen). Worth factoring into the
plan: video is a large *data* problem before it is a decoding problem.
## Reproducing this check
`tools/identify-disc.py` performs the same identification against any
R&C2 disc image and prints the table above. It reads only the ISO 9660
structures, `SYSTEM.CNF` and the boot ELF (a few MB of seeks), so it runs in
well under a second on a local image.
+194
View File
@@ -0,0 +1,194 @@
# Roadmap
Status as of **2026-10-09**.
A from-scratch native Rust reimplementation of Ratchet & Clank: Going Commando
(PS2). Each phase ends in a **gate** — something checkable, not a feeling that
it is going well. Phases are ordered so that the riskiest unknowns are retired
early, while they are still cheap to retire.
## Where we are
- [x] Target disc identified and verified (`SCUS_972.68` v1.01, NTSC 60 Hz)
- [x] Workspace builds (`cargo check --workspace`)
- [x] Repository hygiene in place (pre-commit guard, `.gitignore`, licence
record)
- [ ] Nothing else implemented yet
The disc being the exact build the reference projects pin is the single
biggest favourable fact so far: no disc conversion, no regional timing work,
and no re-derivation of the reference baseline.
---
## Phase 0 — Foundation ✅
**Done.**
- Disc identified: `SCUS_972.68` v1.01, USA Black Label, NTSC 60 Hz. Boot ELF
SHA-1 `bacda860cbd6723142e86db56f197e47e728970f`, entry `0x00131AE8` — the
entry point independently matches the value recorded by `rac2-gc-decomp`,
which cross-confirms the identification.
- `tools/identify-disc.py` reproduces the identification against any disc image
and distinguishes a truncated dump from a non-PS2 image.
- Five-crate workspace scaffolding, Bevy 0.19.1 pinned, builds clean.
- `.githooks/pre-commit` blocks game data. Verified against seven cases:
`.iso`, renamed disc image, WAD container, ADX container, `extracted/`,
a 60 MiB file, and legitimate source.
- `AGENTS.md` and `PROVENANCE.md` record conventions and licences.
### Open from this phase
- `alsa-lib-devel` is not installed, so Bevy's host audio backend is disabled in
`Cargo.toml`. Audio is the last phase; revisit then.
- Bevy feature set is deliberately minimal. Re-add features as needed rather
than switching `default-features` back on wholesale.
---
## Phase 1 — Disc pipeline
**Goal:** turn the disc image into verified, unpacked asset files.
- [ ] `iso9660` reader (R&C2 lists assets as real files, unlike R&C1)
- [ ] `RC2.HDR` table of contents parser (408,444 bytes at disc root)
- [ ] WAD LZ77 decompression
- [ ] Unpack global WADs (`G/*.WAD`) and per-level WADs
- [ ] Commit a size+SHA-1 table per target file (hashes only, never content)
- [ ] `verify` stage
**Gate:** every extracted file matches the committed hash table, and the
decompressed size of every WAD stream matches its header.
**Note:** the three MPEG WADs total roughly 2.4 GB of the 3.8 GB disc. Video is
a data problem before it is a decoding problem — plan to stream.
---
## Phase 2 — Format parsers
**Goal:** every asset type parses into plain Rust values, tested against real
disc data.
- [ ] `tfrag` — terrain: LOD chains, strips, morph targets
- [ ] `tie` — instanced environment geometry, fat vertices
- [ ] `shrub` — foliage and decorative meshes
- [ ] `moby` — dynamic actors; `moby_anim` — skeletal animation
- [ ] `collision` — cells, packed vertices, faces
- [ ] `occlusion` — the PVS grid
- [ ] `sky`, `volumes`, `textures` (CSM1 CLUT + PS2 alpha)
- [ ] `level_overlay` — the seven-section parser and relocation from Phase 3
**Gate:** every parser has a golden test against bytes from the real disc, and
reports mismatched header counts as failures rather than warnings.
**Discipline:** bound every read. Typed errors, no panics, no reads past the
buffer.
---
## Phase 3 — Overlay system
**Goal:** make 27 level overlays tractable.
Every overlay carries the same seven sections (`.lit`, `.bss`, `.data`,
`lvl.vtbl`, `lvl.camvtbl`, `lvl.sndvtbl`, `.text`) and the same engine code,
linked at different addresses. ReRAC solves this by masking relocations — `j`/
`jal` targets, `lui` immediates of main-RAM addresses, the `%lo` that completes
them, and `$gp`-relative operands — then matching the masked words to find a
reference function's copy on another overlay.
- [ ] Port the masking and code-identity logic
- [ ] `Relocation::func` and `Relocation::data`
- [ ] `$gp` for this game (`0x001AEFF0`, against R&C1's `0x166c00`)
- [ ] Extend the level table from 19 to 27
**Gate:** every level-01 function's relocated copy is found on all 27 overlays;
cross-checked against an independent cluster hash.
**Why this is high-value:** it turns "27 overlays of unknown code" into a table
lookup, and it is the main thing inherited from ReRAC.
---
## Phase 4 — Engine core
**Goal:** draw a frame.
- [ ] Graphics Synthesizer in wgpu, with the GS's own rules: its
`round(log2(z/n) + K)` mip selection, blend and alpha-test behaviour,
alpha `0x80` meaning 1.0, CLAMP wrap
- [ ] VU0/VU1 skinning and lighting on the PS2 float model
- [ ] LOD/morph/cull per `TfragProc` and `TieProc`
- [ ] Occlusion culling, fog, shadows
- [ ] GIF/VIF packet stream interpretation
**Gate:** CPU and GPU paths agree on every vertex of a reference level — the
same identity check ReRAC runs.
**Risk:** VU1 microcode is **binary** in the game's `.vutext` section, so it is
reverse engineered rather than parsed. This is the hardest single item in the
project and the main threat to the timeline. Note that ReRAC's own moby VU1
analysis trips their disassembler on two opcodes.
---
## Phase 5 — Veldin vertical slice
**Goal:** one planet, playable start to finish.
- [ ] Ratchet's controller on a **spherical** planet
- [ ] Chargeboots: hold/release timing, chained jumps, dash
- [ ] Wrench and the camera
- [ ] Collision and the HUD
**Gate:** Veldin is completable and visually matches PCSX2 frame-for-frame under
replayed input.
**This is the go/no-go for the project.** If Veldin works, the other 26 levels
are content. If it does not, the reason is discovered while it is still cheap.
Spherical gravity is the thing most likely to break, because it invalidates
R&C1's movement and camera assumptions wholesale.
---
## Phase 6 — Remaining overlays and the shell
- [ ] Per-level moby class dispatch via `lvl.vtbl`
- [ ] Remaining 26 levels
- [ ] Planets, travel, the Gadgetron vendor, RPG upgrades
- [ ] Weapon modification bitmasks (lock-on, shock, acid, …)
- [ ] Minigames and challenges
- [ ] The 25-state memory-card FSM and its UI
- [ ] IFF chunk saves, including import of a R&C1 save to unlock legacy weapons
---
## Phase 7 — Audio and video
- [ ] 989snd v2 audio decode and playback (not CRI ADX — a different codec)
- [ ] SPU2 mixing, reverb zones, level sounds
- [ ] IPU/MPEG-2 video for the title screen and cutscenes
---
## Phase 8 — Fidelity pass
Differential testing against PCSX2 across every level:
- [ ] Recorded-input frame comparison, per planet
- [ ] Geometry, fog, blend, alpha, texture sampling
- [ ] Audio sync and 60 Hz timing
- [ ] Behavioural comparison: traversal routes, boss timing, RNG
---
## Principles
- **A gate is pass/fail, not a percentage.** "Looks fine" is not progress.
- **Negative results are written down.** A ruled-out approach saves the next
attempt as much as a working one.
- **The float model is not negotiable.** Approximate is not the same as
faithful, and the difference is visible in how the game feels.
- **Fidelity over tidiness.** Where the original does something that looks like
a bug, reproduce it and say why in a comment. See `AGENTS.md`.