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
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "gc-engine"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
publish.workspace = true
description = "Bevy application: Graphics Synthesizer, VU0/VU1 and the renderer"
[dependencies]
anyhow.workspace = true
thiserror.workspace = true
gc-formats.workspace = true
gc-game.workspace = true
bevy.workspace = true
+35
View File
@@ -0,0 +1,35 @@
//! The application: renderer, hardware models, and the frame loop.
//!
//! Reimplements the pieces of the PlayStation 2 that the game talks to, so the
//! game logic in `gc-game` can be written as ordinary Rust that draws and
//! sounds like the original.
//!
//! # Hardware being modelled
//!
//! * **Graphics Synthesizer (GS)** - fixed-function rasteriser. No shaders on
//! the real hardware: the GS does texture mapping, alpha and blend rules,
//! and z/stencil. Here that becomes wgpu plus explicit handling of the GS's
//! own rules (its `round(log2(z/n) + K)` mip selection, its blend and alpha
//! test behaviour, its alpha-0x80-is-1.0 convention).
//! * **VU0 / VU1** - vector units running microcode uploaded from the disc.
//! Skinning, lighting and transform setup live here. The microcode is
//! **binary** in the game's `.vutext` section, so it is reverse engineered
//! rather than parsed: this is the single hardest item in the project.
//! * **DMA and GIF/VIF packets** - the game streams geometry to the GS through
//! tag-split packet lists. Those packet formats are the input to rendering.
//! * **SPU2 / IOP** - audio and the disc interface, in `gc-video` and
//! `gc-extract` respectively.
//!
//! # Divergence from the first game
//!
//! R&C2's levels are spherical worlds, so camera, culling, fog and the sky are
//! all effectively rewritten compared with Ratchet & Clank (2002). Geometry
//! subsystems carry over in shape - `tfrag`, `tie`, `shrub`, `moby` are the
//! same four - but their behaviour on a sphere is new work.
//!
//! # Reference frame
//!
//! The port is validated against PCSX2 by replaying recorded input and
//! comparing frames. Without that loop, "looks close" and "is correct" drift
//! apart, and this game's difficulty and movement make that drift very
//! noticeable.
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "gc-extract"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
publish.workspace = true
description = "Disc pipeline: identify, unpack and verify game data from a Going Commando disc image"
[dependencies]
anyhow.workspace = true
thiserror.workspace = true
gc-formats.workspace = true
sha1.workspace = true
+29
View File
@@ -0,0 +1,29 @@
//! Disc pipeline for *Ratchet & Clank: Going Commando* (PS2).
//!
//! Turns a legally obtained disc image into verified, unpacked asset files that
//! the engine can read directly, and does nothing else.
//!
//! # Stages
//!
//! 1. **identify** - read `SYSTEM.CNF`, confirm serial/version/video mode and
//! hash the boot ELF. Reports clearly when a disc is not the supported
//! build. A Python reference implementation of this stage lives in
//! `tools/identify-disc.py`.
//! 2. **unpack** - read `RC2.HDR`, walk the table of contents, decompress WAD
//! streams and write per-level and global asset directories.
//! 3. **verify** - check every extracted file against a recorded size and
//! SHA-1 table, so a corrupted or mismatched dump cannot quietly produce a
//! subtly wrong game.
//!
//! # Output layout
//!
//! Extracted data is written outside the repository (see `.gitignore`). The
//! pre-commit hook in `.githooks/` refuses disc images, asset directories and
//! oversized files, so nothing here can end up in git history by accident.
//!
//! # Verification is not optional
//!
//! Every parser downstream is only as trustworthy as the bytes it was handed.
//! A hash table committed to the repo (sizes and hashes only, never content)
//! is what lets a test say "this parse matches the retail disc" rather than
//! "this parse matches whatever happened to be on my disk".
+16
View File
@@ -0,0 +1,16 @@
[package]
name = "gc-formats"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
publish.workspace = true
description = "Parsers for Ratchet & Clank: Going Commando (PS2) asset formats"
[dependencies]
anyhow.workspace = true
thiserror.workspace = true
bytemuck.workspace = true
sha1.workspace = true
[dev-dependencies]
+38
View File
@@ -0,0 +1,38 @@
//! Asset format parsers for *Ratchet & Clank: Going Commando* (PS2).
//!
//! Target: `SCUS_972.68` v1.01, USA Black Label, NTSC 60 Hz. See
//! `docs/disc-identification.md`.
//!
//! This crate reads data structures out of the game and produces plain Rust
//! values. It performs no rendering, no simulation and no I/O beyond what the
//! caller hands it, which keeps every parser testable against a byte slice.
//!
//! # What belongs here
//!
//! * `iso9660` - the disc filesystem. R&C2 lists its assets as real files,
//! unlike R&C1, so enumeration works.
//! * `hdr` - `RC2.HDR`, the table of contents (408,444 bytes at the
//! disc root). The engine addresses assets by sector, and
//! this is where those sector numbers come from.
//! * `wad` - Insomniac's LZ77 container (`WAD` magic, 16-byte header).
//! Carries level core data, audio and video streams.
//! * `tfrag` - terrain fragments: LOD chains, strips, morph targets.
//! * `tie` - instanced environment geometry ("ties"), with fat vertices.
//! * `shrub` - instanced foliage and decorative meshes.
//! * `moby` - dynamic actors, including Ratchet, enemies and crates.
//! * `moby_anim` - skeletal animation: sequences, frames, blending.
//! * `collision` - the collision world: cells, packed vertices, faces.
//! * `occlusion` - the PVS grid used for visibility.
//! * `sky`, `volumes`, `textures` - sky shells, fog volumes, CSM1 textures.
//!
//! # Rules
//!
//! **Bound every read.** Malformed data must produce a typed error, never a
//! panic and never a read past the end of the buffer.
//!
//! **No game data in this repository.** Parsers are written against a copy of
//! your own legally obtained disc, and the extracted output stays outside git.
//!
//! **Verify against the real thing.** Every parser gets a test that loads
//! known-good bytes from your disc and checks the parsed result. See
//! `docs/formats/` for the specs being implemented.
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "gc-game"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
publish.workspace = true
description = "Game logic: hero controller, weapons, moby classes, progression and saves"
[dependencies]
anyhow.workspace = true
thiserror.workspace = true
gc-formats.workspace = true
gc-video.workspace = true
+38
View File
@@ -0,0 +1,38 @@
//! Game logic for *Ratchet & Clank: Going Commando* (PS2).
//!
//! The rules, systems and behaviour of the game, reimplemented from the
//! original code rather than wrapped around it. No emulation, no interpreter:
//! this is a Rust implementation that is meant to behave the way the game does.
//!
//! # What belongs here
//!
//! * `hero` - Ratchet's controller. Movement on a **spherical** planet,
//! which is what distinguishes this game: reaching for the
//! camera and the collision code changes gravity handling
//! across the whole world, and is the main reason the first
//! game's movement code does not transfer.
//! * `chargeboots`- hold and release timing, chained jumps and the dash.
//! * `weapons` - the wrench and the weapon set, plus the weapon modification
//! system (per-weapon bitmasks: lock-on, shock, acid, ...).
//! * `gadgets` - the Pyrocitor, Bomb Glove, Glove of Doom and friends.
//! * `moby_update`- the per-class update dispatch, driven by each level's
//! `lvl.vtbl` class table. Classes that are identical across
//! levels are shared; the rest are resolved through the
//! overlay relocator in `gc-formats`.
//! * `collision` - queries against the collision world.
//! * `progression`- bolts, the Gadgetron vendor, RPG upgrades and unlocks.
//! * `save_game` - the IFF chunk save format, and importing a Ratchet & Clank
//! (2002) save to unlock the legacy weapons for free.
//! * `memcard` - the 25-state memory-card finite state machine and its UI.
//!
//! # The float model
//!
//! The Emotion Engine's FPU is not IEEE-754 in every respect: it has no
//! denormals, and rounding differs in places. Gameplay code here reproduces
//! that model rather than using Rust's native `f32`, because the difference is
//! visible in the movement and the camera. See `docs/plan/float-model.md`.
//!
//! # Timing
//!
//! The game is 60 Hz (this disc is NTSC). Constants are expressed per tick at
//! that rate. PAL discs run 50 Hz and would need different timing throughout.
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "gc-video"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
publish.workspace = true
description = "IPU/MPEG-2 video stream decoding for Ratchet & Clank: Going Commando"
[dependencies]
anyhow.workspace = true
thiserror.workspace = true
+19
View File
@@ -0,0 +1,19 @@
//! Video decoding for *Ratchet & Clank: Going Commando* (PS2).
//!
//! R&C2 uses the Emotion Engine's **IPU** to stream MPEG-2 video, most visibly
//! on the title screen where Ratchet watches television. This crate decodes
//! those streams to raw frames for the host GPU to upload.
//!
//! # Note on scale
//!
//! The MPEG WADs on this disc total roughly 2.4 GB (about 1.0 GB, 1.3 GB and
//! 115 MB). Video is a data problem before it is a decoding problem, so the
//! pipeline needs to stream rather than hold a whole movie in memory.
//!
//! # Pipeline
//!
//! `bits.rs` -> `vlc.rs` -> `idct.rs` -> `mpeg2.rs`
//!
//! MPEG-2 decoding is a solved, well-specified problem, and a general-purpose
//! decoder will produce correct output. The PS2-specific part is only the
//! container: how the streams are stored and addressed on the disc.