Four numbers in one session looked load-bearing and turned out to be
artifacts: a format identified as VIF-packed on the strength of recurring
byte patterns where a control put it on the random baseline, a body size
computed over wrongly bounded spans, a "6.2 MB of unidentified region" that
was texture data, and "3 unexplained blocks" that were a sampling artifact.
All four shared one cause -- measuring before bounding, naming bytes before
looking at them -- and none errored, which is why they survived long enough
to be believed. Two of them needed follow-up commits to undo.
The rule: any size, count or distribution comes with the byte range it
describes and how it was measured. Ranges come from core_layout rather than
from running to core_data.len(). A statistic that cannot separate your format
from uniform random cannot tell you anything. And counting distinct byte
values only tells you which blocks to look at, not what they are.
Worth having in the working agreements rather than in anyone's memory.
No behaviour change. This is the pass over the last stretch of work to
make sure the accumulated result hangs together rather than only each
commit in isolation.
Code. lit_color had grown a `let _ = (depth, lod_distances)` and a dead
branch from repeated edits. The directional pass genuinely does not
depend on distance, so the parameters are gone rather than discarded,
with a note that the point lights -- not implemented -- will need them.
lit_at folded into lit_color, which had become a wrapper around it.
Checked for leftover scaffolding: only two env vars remain, both real
(GC_ENGINE_SHOT and GC_ENGINE_DEBUG_BOUNDS); GC_ENGINE_UNLIT,
GC_ENGINE_DEBUG_COLOR and GC_ENGINE_ABL were never actually written,
because those edits silently failed to apply against rustfmt'd source.
AGENTS.md. The crate table did not say that gc-engine owns hardware
models. ps2.rs is the Emotion Engine's FPU and the lighting pass runs
on it, so the boundary is now stated: gc-formats decodes inputs,
gc-engine reproduces how the hardware computes with them. That is the
distinction rule 2 depends on, and it was implicit.
Docs. Three claims had been overtaken by what we learned and were
actively misleading:
* docs/formats/level-wad.md still said gameplay "does not decode with
the WAD decoder". It does; it is a WAD stream.
* docs/plan/level-assets.md carried a whole section, "a compression we
have not identified", with a ruled-out list including "bit order",
"header length" and "the WAD format at all". All three were ruled out
against the wrong input. Replaced, with the failure mode kept: a
uniform failure across 27 inputs was read as "the decoder is wrong"
when it meant "the data was handed over uncompressed", and the
positive control -- decompress and look -- was never run.
* The same file still said the tfrag "record framing is not recovered".
True, but it was a false lead that has since been superseded by the
VIF1 framing. Kept as a recorded wrong turn rather than deleted.
Also refreshed "Still open" to name what is actually outstanding:
point lights, the GS register set, the guard-band clip test, the
unread core_index class tables, and -- stated plainly -- that lighting
magnitudes are unverified because there is still nothing to compare
against.
Verified after the changes: 148 tests pass, clippy and fmt clean, all
5,213 extracted files match the hash table, and level 0 still loads at
26,758 triangles with the same LOD split and fully lit.
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.