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:
@@ -698,7 +698,11 @@ pub fn sprite_layer_key(build: &UiBuild, bundle: &[u8], el: &Element) -> Option<
|
||||
/// — and why `ComposeOptions::include_primitives` is still off by default.
|
||||
pub fn implied_layer_key(name: &str) -> Option<u32> {
|
||||
Some(match name {
|
||||
"palogo_eff0.prm" => 0x0000,
|
||||
// Full-screen backdrops, measured painting FIRST on their screens.
|
||||
// `pfbase.tbm` is not a `.prm` at all — a `.tbm` with no sprite and no
|
||||
// key — but it behaves identically: the save/load screen paints it and
|
||||
// its `kind = 0x3004` instance before everything else.
|
||||
"palogo_eff0.prm" | "pfbase.tbm" => 0x0000,
|
||||
"pteff04.t32" | "pteff05.t32" => 0x8020,
|
||||
"pteff02.prm" => 0x8030,
|
||||
"pteff00.prm" => 0xffff_fffe,
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -139,9 +139,16 @@ See [`structures/ui-composable-bundles.md`](structures/ui-composable-bundles.md)
|
||||
and it is pinned by a test. The third pair (`ptlogo2` vs `ptlogo_tm`) overlaps
|
||||
by bounding box but shares no opaque pixel; a box test called it a defect and
|
||||
the alpha says otherwise.
|
||||
**Next:** it is one element on one screen, so the cheap move is a fourth
|
||||
measured screen with a tied group — not more static guessing at fields that
|
||||
have all now been checked. 🔴 **Attempted 2026-08-19 and blocked:** advancing
|
||||
✅ **Fourth and fifth screens measured (2026-08-19)**, from `GP_SAVE_LOAD`,
|
||||
reachable now that the Canary threading fix makes the menu dependable. The
|
||||
9-element slot-list header is **EXACT** under the derived rule — two tied
|
||||
groups both in declaration order, unkeyed `.prm` last — and it is the first
|
||||
screen outside `GP_TITLE.pak`, so it *confirms* the rule rather than being
|
||||
fitted to it. The 13-element save/load frame differs in exactly the two known
|
||||
ways: unkeyed `pfbase.tbm` backgrounds paint **first** (now covered by
|
||||
`implied_layer_key`), and the `0xb100` group of four paints `10,11,8,12`.
|
||||
🔴 **Refuted: the tie-break is not `kind`.** "Descending kind" reproduces
|
||||
`10,11,8,12` exactly but fails both title groups. Seven candidates refuted now. 🔴 **Attempted 2026-08-19 and blocked:** advancing
|
||||
past the title is intermittent — **1 success in 3 attempts**, same binary,
|
||||
same profile, same procedure. ✅ **And now diagnosed one layer deeper:** the
|
||||
title *does* act on Ⓐ — the press spawns a slot-`(1F)` loader thread (exactly
|
||||
|
||||
BIN
docs/re/captures/load-game-oracle.png
Normal file
BIN
docs/re/captures/load-game-oracle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 673 KiB |
@@ -257,3 +257,69 @@ it is not. That is why the test reads pixels.
|
||||
|
||||
So the residual error in the derived order is confined to **one element's blend
|
||||
on one screen**, and it is pinned: a change that makes it worse fails the test.
|
||||
|
||||
|
||||
## Two more measured orders — and the first independent confirmation (2026-08-19)
|
||||
|
||||
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 after the Canary threading fix made
|
||||
the main menu reachable ([capture](../captures/load-game-oracle.png)).
|
||||
|
||||
### ✅ The slot-list header — exact, without being told anything
|
||||
|
||||
`GP_SAVE_LOAD` ratc-indices 1/4/5/21/27, 9 elements:
|
||||
|
||||
```
|
||||
measured 7 8 0 1 2 3 4 5 6
|
||||
derived 7 8 0 1 2 3 4 5 6 EXACT, on all 6 instances
|
||||
```
|
||||
|
||||
| element | key |
|
||||
|---|---|
|
||||
| 7 `pffocus1.rat`, 8 `pfslot1f_scr.rat` | `0xb102` **tied ×2** |
|
||||
| 0 `pftitlebase` | `0xb200` |
|
||||
| 1–5 `pfeff11`…`pftitle1` | `0xb210` **tied ×5** |
|
||||
| 6 `pfeff00.prm` | none → last |
|
||||
|
||||
Both tied groups come out in **declaration order**, which is what the stable sort
|
||||
gives, and the unkeyed fade quad lands last. Nothing about this screen was fed
|
||||
into the rule. It is the first evidence that the layer key is a real ordering
|
||||
mechanism rather than a description of three screens in one pak.
|
||||
|
||||
### 🟡 The save/load frame — differs in exactly the two known ways
|
||||
|
||||
`GP_SAVE_LOAD` ratc-index 18/24, 13 elements (`is_build` rejects it, so the
|
||||
compositor never sees it — but it is just as good as *evidence*):
|
||||
|
||||
```
|
||||
measured 0 1 2 3 4 5 6 7 10 11 8 12 9
|
||||
derived 2 3 4 5 6 7 8 10 11 12 9 0 1
|
||||
```
|
||||
|
||||
1. **Unkeyed elements paint first, not last.** Elements 0 and 1 are
|
||||
`pfbase.tbm` — a `.tbm`, not a `.prm`, with no sprite and no key — and the
|
||||
game paints them before everything else. Exactly the splash's
|
||||
`palogo_eff0.prm` behaviour in a different file type, so `implied_layer_key`
|
||||
now covers it and the layer-key sequences agree.
|
||||
2. **A tied group in a non-declaration order.** `0xb100` holds 8 `pfwin`,
|
||||
10 `pfbtn0`, 11 `pfbtn1`, 12 `pfmsg`, and the game paints
|
||||
**10, 11, 8, 12**.
|
||||
|
||||
That second point is the tie-break question again — and it is now on a *second*
|
||||
screen, which is what it needed.
|
||||
|
||||
### 🔴 Refuted: the tie-break is not `kind`
|
||||
|
||||
The new group is suggestive: 10 and 11 are `kind = 0x2002` and 8 and 12 are
|
||||
`0x0000`, so "descending `kind`, then declaration index" reproduces
|
||||
`10, 11, 8, 12` exactly. It does not survive the title:
|
||||
|
||||
* `0x8083` ×5 — every element is `kind = 0x0000`, so the rule predicts
|
||||
declaration order `14,15,16,17,18`; the game paints `14,15,18,16,17`.
|
||||
* `0x80a0` ×7 — kinds are `0x0` and `0x4`, so the rule predicts
|
||||
`2,3,4,5` then `0,1,7`; the game paints `0,2,4,7,1,3,5`.
|
||||
|
||||
So `kind` is not it either, and the count of refuted candidates is now seven:
|
||||
declaration order, RATC child order, first keyframe time, resting time, resting
|
||||
X/Y, the `T8aD` header words, and `kind`.
|
||||
|
||||
Reference in New Issue
Block a user