diff --git a/crates/gc-extract/tests/disc.rs b/crates/gc-extract/tests/disc.rs index e1d74d4..4cb2faa 100644 --- a/crates/gc-extract/tests/disc.rs +++ b/crates/gc-extract/tests/disc.rs @@ -6197,3 +6197,77 @@ fn dispatch_tables_contain_far_fewer_behaviours_than_records() { ); } } + +/// R&C2's level table: 27 entries, keyed by **level id**, not by file order. +/// +/// ReRAC, which this project follows architecturally, has +/// `rc_game::map::LEVELS: usize = 19` -- Ratchet & Clank 1 has nineteen levels. +/// This game has twenty-seven, so "extend the level table from 19 to 27" is about +/// *what the keys are*, not only how many there are. +/// +/// Reading every container header's own id off the disc gives the key set: +/// +/// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 22 23 24 25 26 30 +/// +/// **27 keys, and 21 is not one of them.** The container files run `LEVEL0.WAD` to +/// `LEVEL26.WAD`, but `LEVEL21.WAD` reports id **30**, so the id space is +/// 0..20, 22..26, 30 -- one number was cut and reused by nothing. +/// +/// The reference agrees independently. Every level key appearing in +/// `rac2-gc-decomp`'s `config/levels/functions.tsv` placements column, across 7,765 +/// function rows, is exactly that set, with 21 absent. Ours comes from the +/// container headers and theirs from where functions land across overlays. +/// +/// A table keyed by container index would silently mis-address one level in +/// twenty-seven, and every id-keyed lookup after the gap would be off by one. +#[test] +fn level_table_has_twenty_seven_keys_with_twenty_one_absent() { + let path = require_disc!(); + let iso = gc_formats::iso9660::IsoImage::open(&path).expect("ISO should open"); + + let mut levels: Vec<(usize, &gc_formats::iso9660::DirEntry)> = iso + .files + .iter() + .filter(|e| e.name.starts_with("G/LEVEL")) + .map(|e| { + let n: usize = e + .name + .trim_start_matches("G/LEVEL") + .trim_end_matches(".WAD") + .parse() + .expect("level container names end in a number"); + (n, e) + }) + .collect(); + levels.sort_by_key(|(n, _)| *n); + assert_eq!( + levels.len(), + 27, + "the disc should carry 27 level containers" + ); + + let mut ids: Vec = levels + .iter() + .map(|(_, e)| { + let data = gc_formats::iso9660::read_at(&path, e.lba, 0x60).expect("read header"); + gc_formats::level_wad::LevelHeader::parse(&data) + .expect("parse") + .id + }) + .collect(); + ids.sort_unstable(); + + let expected: Vec = (0..=20).chain(22..=26).chain([30]).collect(); + eprintln!( + "level table: {} ids, 21 absent, highest {}", + ids.len(), + ids.last().unwrap() + ); + assert_eq!(ids.len(), 27, "level id count moved"); + assert!( + !ids.contains(&21), + "id 21 must not exist; LEVEL21.WAD reports 30" + ); + assert_eq!(ids, expected, "the level-id set moved"); + assert_eq!(*ids.last().unwrap(), 30, "the highest level id is 30"); +} diff --git a/docs/roadmap.md b/docs/roadmap.md index 0304e80..4b132fc 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -678,7 +678,16 @@ reference function's copy on another overlay. and `copies` - [x] `$gp` for this game (`0x001AEFF0`, against R&C1's `0x166c00`) -- confirmed by `OpenRAC/rac2-gc-decomp` `docs/LEVELS.md` for all 27 levels -- [ ] Extend the level table from 19 to 27 +- [x] **The level table has 27 keys, and 21 is not one of them.** ReRAC, which this + project follows, has `rc_game::map::LEVELS = 19` -- Ratchet & Clank 1's + count. Reading every container header's own id off the disc gives + `0..=20, 22..=26, 30`: the files run `LEVEL0.WAD`..`LEVEL26.WAD` but + `LEVEL21.WAD` reports **30**, so one number was cut and reused by nothing. + `OpenRAC/rac2-gc-decomp`'s `config/levels/functions.tsv` independently + yields exactly the same 27 keys with 21 absent across 7,765 function rows -- + ours from container headers, theirs from where functions land across + overlays. A table keyed by container index would mis-address one level in + twenty-seven and shift every id-keyed lookup after the gap. Asserted. **Gate:** every level-01 function's relocated copy is found on all 27 overlays; cross-checked against an independent cluster hash.