formats: two more measured paint orders, and the first independent confirmation

The three orders the derived rule was built from all live in GP_TITLE.pak, so
they cannot confirm it - the rule was fitted to them. These two are from
GP_SAVE_LOAD.pak, read off the running game now that the Canary threading fix
makes the main menu dependable.

The 9-element slot-list header composites EXACTLY as the sort predicts, on all 6
instances of it, and nothing about this screen was fed into the rule:

    measured  7 8 0 1 2 3 4 5 6
    derived   7 8 0 1 2 3 4 5 6

including TWO tied groups (0xb102 x2 and 0xb210 x5) that both come out in
declaration order, and the unkeyed pfeff00.prm fade quad last.

The 13-element save/load frame differs in exactly the two open questions and no
new ones: two unkeyed pfbase.tbm backgrounds paint FIRST where the sort puts the
keyless last - the splash's palogo_eff0.prm behaviour in a different file type,
so implied_layer_key now covers it - and the 0xb100 group of four paints
10,11,8,12 where declaration order is 8,10,11,12.

That second point is a SECOND screen with a mis-ordered tie, which is what the
question needed, and it immediately kills a candidate: 10 and 11 are kind=0x2002
while 8 and 12 are 0x0000, so "descending kind then declaration index"
reproduces 10,11,8,12 exactly - and then fails both title groups, where every
element of 0x8083 is kind 0 and where 0x80a0 would predict 2,3,4,5,0,1,7 against
a measured 0,2,4,7,1,3,5. Seven candidates refuted now.

16 disc tests green.
This commit is contained in:
Sylpheed RE agent
2026-08-19 11:46:09 +00:00
parent 714c74565f
commit ebc4e08b89
5 changed files with 145 additions and 4 deletions

View File

@@ -900,3 +900,67 @@ fn order_disagreements_that_change_pixels_are_pinned() {
"the set of order disagreements that actually change pixels has moved"
);
}
/// Two more paint orders read off the running game, from `GP_SAVE_LOAD` — and
/// the first **independent confirmation** of the derived rule.
///
/// The three orders the rule was built from all live in `GP_TITLE.pak`. These do
/// not, and one of them the sort gets exactly right without having been told
/// anything about it:
///
/// * the **slot list header** (9 elements) composites `[7,8,0,1,2,3,4,5,6]`,
/// which is what sorting by layer key gives — including **two tied groups**
/// (`0xb102` ×2 and `0xb210` ×5) that both come out in declaration order, and
/// the unkeyed `pfeff00.prm` fade quad last. Exact on all five bundle
/// instances of it.
/// * the **save/load frame** (13 elements) does not, in exactly two ways, and
/// both are already-known open questions rather than new ones: two unkeyed
/// `pfbase.tbm` background elements paint **first** where the sort puts the
/// keyless last, and the `0xb100` group of four paints `10,11,8,12` where
/// declaration order is `8,10,11,12`.
#[test]
fn the_save_load_screens_match_what_the_running_game_paints() {
skip_without_disc!(root);
let header: [usize; 9] = [7, 8, 0, 1, 2, 3, 4, 5, 6];
let frame: [usize; 13] = [0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 8, 12, 9];
let arc = PakArchive::open(root.join("dat").join("GP_SAVE_LOAD.pak")).expect("pak");
let (mut headers, mut frames) = (0usize, 0usize);
for e in arc.entries() {
let Ok(bundle) = arc.read(e) else { continue };
let Some(b) = ui_layout::parse_build(&bundle) else {
continue;
};
let names: Vec<&str> = b.elements.iter().map(|e| e.name.as_str()).collect();
let derived = ui_layout::derived_paint_order(&b, &bundle);
if b.elements.len() == 9 && names[0] == "pftitlebase.t32" && names[6] == "pfeff00.prm" {
headers += 1;
assert_eq!(
derived,
header.to_vec(),
"the slot-list header no longer composites in the order the game paints"
);
}
if b.elements.len() == 13 && names[0] == "pfbase.tbm" {
frames += 1;
// The two known gaps, stated as the measured difference rather than
// asserted away: the sort must at least agree on everything else.
let key = |i: usize| {
let el = &b.elements[i];
ui_layout::sprite_layer_key(&b, &bundle, el)
.or_else(|| ui_layout::implied_layer_key(&el.name))
.unwrap_or(u32::MAX)
};
let dk: Vec<u32> = derived.iter().map(|&i| key(i)).collect();
let mk: Vec<u32> = frame.iter().map(|&i| key(i)).collect();
assert_eq!(
dk, mk,
"the save/load frame's layer-key sequence differs from the game's \
— the only accepted differences are WITHIN a key group\n \
derived {derived:?}\n measured {frame:?}"
);
}
}
assert!(headers >= 3, "found {headers} slot-list headers, expected several");
assert!(frames >= 1, "found {frames} save/load frames");
eprintln!("GP_SAVE_LOAD: {headers} headers exact, {frames} frames agree by layer group");
}