Close Phase 3's level table: 27 keys, and 21 is not one of them
Picked the first open Phase 3 item. "Extend the level table from 19 to 27" is
about what the KEYS are, not how many there are.
ReRAC, which this project follows architecturally, has rc_game::map::LEVELS = 19
-- Ratchet & Clank 1's count, and this repo has no equivalent yet. Reading every
container header's own id off the disc gives:
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 through
LEVEL26.WAD, but LEVEL21.WAD reports id 30, so the id space is 0..20, 22..26, 30 --
one number was cut from the disc and reused by nothing. That matches what
LevelHeader::id already documents, now as the table itself.
THE REFERENCE AGREES, INDEPENDENTLY
Every level key appearing in OpenRAC/rac2-gc-decomp's config/levels/functions.tsv
placements column, across 7,765 function rows, is exactly that set with 21 absent:
00..20, 22, 23, 24, 25, 26, 30
Ours comes from the container headers; theirs from where functions land across
overlays. Two unrelated routes to the same key set, which is the only reason to
trust a hand-typed table like this one.
Why it matters concretely: 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 -- which is the kind of bug that shows up as one wrong
background rather than a crash.
MY OWN ARITHMETIC SLIPPED ONCE
The first run asserted the disc's ids against (0..=20).chain(22..=27).chain([30]),
which is 28 entries -- 22..=27 should be 22..=26. The test caught it against the
disc, which is the right way round. Worth noting that the reference key set I
extracted by hand earlier was correct and the Rust expression was not, so the hand
check and the code check disagreed in the right direction.
Asserted in level_table_has_twenty_seven_keys_with_twenty_one_absent, from the
disc rather than from a copied constant, so it fails if a container id moves.
269 tests, clippy clean.
This commit is contained in:
@@ -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<i32> = 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<i32> = (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");
|
||||
}
|
||||
|
||||
+10
-1
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user