re: placement X/Y are SIGNED, the tail word is time, and the resting pose is max-dwell

Dumping the remaining Arsenal components turned up placements reading as
4294966780 and 4294966856 -- negative as i32 (-516, -440). X and Y are signed;
off-screen animation starts are negative, and read as unsigned a reimplementation
would draw them four billion pixels off.

The raw region then showed the rest of it. The trailing word is a TIME, and a
group is an in -> hold -> out animation: prselect_win1 runs
t=4:-516 -> 6:-71 -> 7:81 -> 8:127 -> 23:134 -> 24:134 -> 25:127 -> 27:81 ->
31:-71 -> 1:-516. So NEITHER the first nor the last keyframe is where the element
sits -- both are off-screen. The resting position is the max-dwell keyframe,
(127,155)..(134,155) here, which is where the weapon-list panel actually appears.

This corrects my own note from earlier today, which reported "final=" positions.
Static elements were unaffected (the pause menu's buttons have identical
keyframes) but animated ones were misread. screen_layout.rs now reports the
max-dwell pose plus the full timeline, and both captures are regenerated.

Also seen while dumping: kind 0x2, and a .sbo child (scrollbar) parented to the
list window.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-11 22:14:36 +00:00
parent de357ffdc9
commit 35e2d1067f
4 changed files with 101 additions and 44 deletions

View File

@@ -54,7 +54,8 @@ fn main() {
// Placement region: groups of (u32 element index, u32 keyframe count) then // Placement region: groups of (u32 element index, u32 keyframe count) then
// `count` 40-byte keyframes; the X/Y sit 12 bytes into the scale/tint block. // `count` 40-byte keyframes; the X/Y sit 12 bytes into the scale/tint block.
let mut pos = 0x20 + count * 60; let mut pos = 0x20 + count * 60;
let mut placements: Vec<Vec<(u32, u32)>> = vec![Vec::new(); count]; // X/Y are SIGNED: off-screen animation starts are negative (e.g. -516).
let mut placements: Vec<Vec<(i32, i32, u32)>> = vec![Vec::new(); count];
for _ in 0..count { for _ in 0..count {
if pos + 8 > bytes.len() { break } if pos + 8 > bytes.len() { break }
let idx = be32(&bytes, pos) as usize; let idx = be32(&bytes, pos) as usize;
@@ -65,7 +66,7 @@ fn main() {
for k in 0..frames { for k in 0..frames {
let blk = first + k * 40; let blk = first + k * 40;
if blk + 20 > bytes.len() { break } if blk + 20 > bytes.len() { break }
group.push((be32(&bytes, blk + 12), be32(&bytes, blk + 16))); group.push((be32(&bytes, blk + 12) as i32, be32(&bytes, blk + 16) as i32, be32(&bytes, blk + 20)));
} }
placements[idx] = group; placements[idx] = group;
pos = first + frames * 40 - 20; pos = first + frames * 40 - 20;
@@ -76,10 +77,25 @@ fn main() {
for i in 0..count { for i in 0..count {
let (parent, kind, px, py) = meta[i]; let (parent, kind, px, py) = meta[i];
let p = &placements[i]; let p = &placements[i];
let shown = match (p.first(), p.last()) { // A group is an in → hold → out animation, so neither the first nor the
(Some(f), Some(l)) if p.len() > 1 && f != l => format!("{:?}{:?}", f, l), // last keyframe is where the element sits: report the MAX-DWELL one
(Some(f), _) => format!("{:?}", f), // (longest gap to the next keyframe's time).
_ => "".into(), let shown = if p.is_empty() {
"".into()
} else if p.len() == 1 {
format!("({},{})", p[0].0, p[0].1)
} else {
let mut best = (0usize, 0u32);
for k in 0..p.len() - 1 {
let d = p[k + 1].2.saturating_sub(p[k].2);
if d > best.1 { best = (k, d) }
}
let r = p[best.0];
format!(
"rest ({},{}) t={}..{} [{}]",
r.0, r.1, r.2, p[best.0 + 1].2,
p.iter().map(|(x, y, t)| format!("{t}:{x},{y}")).collect::<Vec<_>>().join(" ")
)
}; };
println!( println!(
"{:<3} {:<30} {:>7} {:>8} {:>12} {:>4} {}", "{:<3} {:<30} {:>7} {:>8} {:>12} {:>4} {}",

View File

@@ -1,25 +1,50 @@
=== eng\prmain_scr.prt ===
23 elements 23 elements
# element parent kind pivot kf placement # element parent kind pivot kf placement
0 preff01.t32 - 0x0 (164,156) 5 (256, 214) 0 preff01.t32 - 0x0 (164,156) 5 rest (256,214) t=6..22 [6:256,214 22:256,214 31:256,214 42:256,214 1:256,214]
1 preff02.t32 - 0x0 (155,175) 7 (305, 240) 1 preff02.t32 - 0x0 (155,175) 7 rest (156,121) t=8..37 [4:305,240 6:193,150 7:165,128 8:156,121 37:155,120 44:155,120 2:305,240]
2 preff03.t32 - 0x0 (155,175) 7 (225, 150) 2 preff03.t32 - 0x0 (155,175) 7 rest (374,269) t=8..37 [4:225,150 6:337,240 7:365,262 8:374,269 37:375,270 44:375,270 3:225,150]
3 preff04.t32 - 0x0 (247,237) 6 (173, 133) 3 preff04.t32 - 0x0 (247,237) 6 rest (173,133) t=8..37 [4:173,133 6:173,133 8:173,133 37:173,133 44:173,133 4:173,133]
4 prwinbase.t32 - 0x0 (136,130) 5 (284, 240) 4 prwinbase.t32 - 0x0 (136,130) 5 rest (284,240) t=14..35 [6:284,240 14:284,240 35:284,240 43:284,240 5:284,240]
5 prbtn1.rat - 0x3002 (28,14) 5 (242, 166) 5 prbtn1.rat - 0x3002 (28,14) 5 rest (242,166) t=16..33 [10:242,166 16:242,166 33:242,166 37:242,166 6:242,166]
6 prbtn2.rat - 0x3002 (43,14) 5 (242, 220) 6 prbtn2.rat - 0x3002 (43,14) 5 rest (242,220) t=17..33 [11:242,220 17:242,220 33:242,220 37:242,220 7:242,220]
7 prbtn3.rat - 0x3002 (58,14) 5 (242, 276) 7 prbtn3.rat - 0x3002 (58,14) 5 rest (242,276) t=18..33 [12:242,276 18:242,276 33:242,276 37:242,276 8:242,276]
8 prbtn4.rat - 0x3002 (89,14) 5 (242, 331) 8 prbtn4.rat - 0x3002 (89,14) 5 rest (242,331) t=19..33 [13:242,331 19:242,331 33:242,331 37:242,331 9:242,331]
9 prbtn5.rat - 0x3002 (89,14) 5 (242, 385) 9 prbtn5.rat - 0x3002 (89,14) 5 rest (242,385) t=20..33 [14:242,385 20:242,385 33:242,385 37:242,385 10:242,385]
10 prbtn6.rat - 0x3002 (104,14) 5 (242, 441) 10 prbtn6.rat - 0x3002 (104,14) 5 rest (242,441) t=21..33 [15:242,441 21:242,441 33:242,441 37:242,441 11:242,441]
11 prbtn7.rat - 0x3002 (58,14) 5 (242, 496) 11 prbtn7.rat - 0x3002 (58,14) 5 rest (242,496) t=22..33 [16:242,496 22:242,496 33:242,496 37:242,496 12:242,496]
12 prbtn8.rat - 0x3002 (30,14) 5 (242, 551) 12 prbtn8.rat - 0x3002 (30,14) 5 rest (242,551) t=23..33 [17:242,551 23:242,551 33:242,551 37:242,551 13:242,551]
13 prexp1.t32 - 0x0 (277,30) 8 (726, 143) → (1286, 143) 13 prexp1.t32 - 0x0 (277,30) 8 rest (726,143) t=31..37 [21:726,143 25:1286,143 27:866,143 28:756,143 29:731,143 31:726,143 37:726,143 14:1286,143]
14 prexp1a.t32 13 0x1 (277,30) 3 (1126, 143) 14 prexp1a.t32 13 0x1 (277,30) 3 rest (1126,143) t=31..37 [31:1126,143 37:1126,143 15:1126,143]
15 prexp3.t32 - 0x0 (205,5) 5 (726, 381) 15 prexp3.t32 - 0x0 (205,5) 5 rest (726,381) t=10..29 [10:726,381 29:726,381 31:726,381 39:726,381 16:726,381]
16 prmsg.t32 - 0x0 (330,19) 5 (151, 645) 16 prmsg.t32 - 0x0 (330,19) 5 rest (151,645) t=21..29 [21:151,645 29:151,645 31:151,645 37:151,645 17:151,645]
17 prexp3.t32 - 0x4 (205,5) 5 (726, 415) 17 prexp3.t32 - 0x4 (205,5) 5 rest (726,415) t=10..29 [10:726,415 29:726,415 31:726,415 39:726,415 18:726,415]
18 prexp3.t32 - 0x4 (205,5) 5 (726, 449) 18 prexp3.t32 - 0x4 (205,5) 5 rest (726,449) t=10..29 [10:726,449 29:726,449 31:726,449 39:726,449 19:726,449]
19 prexp3.t32 - 0x4 (205,5) 5 (726, 483) 19 prexp3.t32 - 0x4 (205,5) 5 rest (726,483) t=10..29 [10:726,483 29:726,483 31:726,483 39:726,483 20:726,483]
20 prexp3.t32 - 0x4 (205,5) 5 (726, 517) 20 prexp3.t32 - 0x4 (205,5) 5 rest (726,517) t=10..29 [10:726,517 29:726,517 31:726,517 39:726,517 21:726,517]
21 prexp3.t32 - 0x4 (205,5) 5 (726, 551) 21 prexp3.t32 - 0x4 (205,5) 5 rest (726,551) t=10..29 [10:726,551 29:726,551 31:726,551 39:726,551 22:726,551]
22 prexp3.t32 - 0x4 (205,5) 5 (726, 585) 22 prexp3.t32 - 0x4 (205,5) 5 rest (726,585) t=39..1869640736 [10:726,585 29:726,585 31:726,585 39:726,585 1869640736:726,585]
=== eng\prselect_scr.prt ===
8 elements
# element parent kind pivot kf placement
0 prselect_win1.t32 - 0x0 (256,200) 10 rest (127,155) t=8..23 [4:-516,155 6:-71,155 7:81,155 8:127,155 23:134,155 24:134,155 25:127,155 27:81,155 31:-71,155 1:-516,155]
1 prselect_win2.t32 0 0x1 (256,200) 1 (135,560)
2 prselect_win1eff.t32 0 0x1 (256,200) 5 rest (71,92) t=2..14 [2:71,92 14:71,92 21:71,92 27:71,92 3:71,92]
3 prselect_win2eff.t32 0 0x1 (256,200) 5 rest (71,499) t=2..14 [2:71,499 14:71,499 21:71,499 27:71,499 4:71,499]
4 prselect_sbar.sbo 0 0x1 (256,200) 1 (614,172)
5 prselect_type_line.t32 - 0x0 (260,24) 5 rest (123,142) t=14..21 [8:123,142 14:123,142 21:123,142 25:123,142 6:123,142]
6 prselect_msg.t32 - 0x0 (468,19) 5 rest (151,645) t=11..19 [11:151,645 19:151,645 21:151,645 27:151,645 7:151,645]
7 prselect_msgbtn1.t32 - 0x2 (128,19) 5 rest (809,645) t=27..1869640736 [11:809,645 19:809,645 21:809,645 27:809,645 1869640736:809,645]
=== eng\prselect_win3.prt ===
2 elements
# element parent kind pivot kf placement
0 prselect_win3.t32 - 0x0 (245,300) 4 rest (704,39) t=10..16 [8:704,39 10:704,39 16:704,39 1:704,39]
1 prselect_win3eff.t32 - 0x0 (309,352) 4 rest (640,0) t=16..1869640736 [8:640,0 10:640,0 16:640,0 1869640736:640,0]
=== eng\prtitle.prt ===
5 elements
# element parent kind pivot kf placement
0 preff11.t32 - 0x0 (221,6) 6 rest (-160,82) t=16..35 [12:-440,82 14:-440,82 16:-160,82 35:0,82 41:0,82 1:-440,82]
1 preff12.t32 - 0x0 (75,3) 5 rest (0,72) t=20..34 [14:0,72 20:0,72 34:0,72 38:0,72 2:0,72]
2 preff13.t32 - 0x0 (53,3) 5 rest (407,78) t=22..33 [16:407,78 22:407,78 33:407,78 37:407,78 3:407,78]
3 preff14.t32 - 0x0 (52,3) 5 rest (427,68) t=24..32 [18:427,68 24:427,68 32:427,68 36:427,68 4:427,68]
4 prtitle.t32 - 0x0 (94,35) 7 rest (162,26) t=37..1869640736 [20:142,26 23:142,26 25:157,26 26:161,26 31:162,26 37:162,26 1869640736:142,26]

View File

@@ -1,13 +1,13 @@
11 elements 11 elements
# element parent kind pivot kf placement # element parent kind pivot kf placement
0 pgp_ttrl_eff11.t32 - 0x0 (34,33) 7 (603, 121) → (493, 121) 0 pgp_ttrl_eff11.t32 - 0x0 (34,33) 7 rest (470,121) t=9..25 [3:603,121 6:603,121 8:503,121 9:470,121 25:463,121 30:463,121 1:493,121]
1 pgp_ttrl_eff12.t32 - 0x0 (34,33) 7 (609, 121) → (719, 121) 1 pgp_ttrl_eff12.t32 - 0x0 (34,33) 7 rest (742,121) t=9..25 [3:609,121 6:609,121 8:709,121 9:742,121 25:749,121 30:749,121 2:719,121]
2 pgp_ttrl_eff10.t32 - 0x0 (204,60) 5 (436, 94) 2 pgp_ttrl_eff10.t32 - 0x0 (204,60) 5 rest (436,94) t=14..23 [8:436,94 14:436,94 23:436,94 30:436,94 3:436,94]
3 pgp_ttrl_eff22.t32 - 0x0 (133,130) 5 (480, 225) → (490, 225) 3 pgp_ttrl_eff22.t32 - 0x0 (133,130) 5 rest (480,225) t=11..25 [5:480,225 11:480,225 25:460,225 30:460,225 4:490,225]
4 pgp_ttrl_eff23.t32 - 0x0 (138,130) 5 (524, 271) → (514, 271) 4 pgp_ttrl_eff23.t32 - 0x0 (138,130) 5 rest (524,271) t=11..25 [5:524,271 11:524,271 25:544,271 30:544,271 5:514,271]
5 pgp_ttrl_eff21.t32 - 0x0 (214,180) 5 (426, 198) 5 pgp_ttrl_eff21.t32 - 0x0 (214,180) 5 rest (426,198) t=9..21 [9:426,198 21:426,198 23:426,198 30:426,198 6:426,198]
6 pgp_ttrl_title.rat - 0x0 (101,36) 6 (540, 119) 6 pgp_ttrl_title.rat - 0x0 (101,36) 6 rest (540,119) t=13..23 [5:540,119 13:540,119 23:540,119 28:540,119 30:540,119 7:540,119]
7 pgp_ttrl_btn10.rat - 0x3002 (43,21) 5 (546, 288) 7 pgp_ttrl_btn10.rat - 0x3002 (43,21) 5 rest (546,288) t=15..23 [9:546,288 15:546,288 23:546,288 26:546,288 8:546,288]
8 pgp_ttrl_btn11.rat - 0x3002 (86,22) 5 (546, 358) 8 pgp_ttrl_btn11.rat - 0x3002 (86,22) 5 rest (546,358) t=11..17 [11:546,358 17:546,358 23:546,358 26:546,358 9:546,358]
9 pgp_ttrl_btn12.rat - 0x3002 (110,21) 5 (546, 428) 9 pgp_ttrl_btn12.rat - 0x3002 (110,21) 5 rest (546,428) t=13..19 [13:546,428 19:546,428 23:546,428 26:546,428 10:546,428]
10 pgp_ttrl_msg.t32 - 0x0 (190,19) 5 (451, 545) 10 pgp_ttrl_msg.t32 - 0x0 (190,19) 5 rest (451,545) t=28..1869640736 [16:451,545 21:451,545 23:451,545 28:451,545 1869640736:451,545]

View File

@@ -202,17 +202,33 @@ recording them because a static-only reading would have shipped them:
``` ```
group header: u32 element index (0,1,2… — the correspondence is stated, not inferred) group header: u32 element index (0,1,2… — the correspondence is stated, not inferred)
u32 keyframe count u32 keyframe count
then `count` keyframes, 40 bytes apart, each holding the familiar block: then `count` keyframes, 40 bytes apart, each holding:
u32 scale X = 100 u32 scale Y = 100 u32 scale X = 100 u32 scale Y = 100
u32 tint = 0xffffffff u32 tint = 0xffffffff
u32 X u32 Y (+ a trailing time field) i32 X i32 Y ← SIGNED
u32 time u32 flags/terminator
``` ```
**Three things here are easy to get wrong, and were:**
- **X and Y are signed.** Off-screen animation starts are negative — the Arsenal's
weapon-list window begins at **X = 516**. Read as unsigned it is 4 294 966 780,
which a naïve implementation would happily draw four billion pixels to the right.
- **The trailing word is a time**, and a group is an **in → hold → out**
animation. `prselect_win1` runs `t=4:516 → 6:71 → 7:81 → 8:127 → 23:134 →
24:134 → 25:127 → 27:81 → 31:71 → 1:516`.
- **So neither the first nor the last keyframe is where the element sits.** Both
are off-screen for an animated element. The resting position is the
**max-dwell** keyframe — the one with the longest gap to the next time — which
for that window is **(127,155)…(134,155)**, matching where the list panel
actually appears. `screen_layout.rs` reports that, not `final`.
For the tutorial pause bundle this yields 11 groups for 11 elements, and every For the tutorial pause bundle this yields 11 groups for 11 elements, and every
group's header index and count match the blocks actually present. The values are group's header index and count match the blocks actually present. The values are
self-evidently right: the three menu buttons sit at X=546, **70 px apart** self-evidently right: the three menu buttons sit at X=546, **70 px apart**
(288 / 358 / 428), the title at (540,119), the message at (451,545), and the (288 / 358 / 428) — static, every keyframe identical, which is why their reading
`eff*` sprites carry multi-position fly-ins. was unaffected by the first/last mistake above — the title at (540,119), the
message at (451,545), and the `eff*` sprites carry multi-position fly-ins.
**Cross-checked against the records themselves:** `pgp_ttrl_btn10.rat` places its **Cross-checked against the records themselves:** `pgp_ttrl_btn10.rat` places its
sprite at **(546,288)** — identical to its inline group. So the inline region is sprite at **(546,288)** — identical to its inline group. So the inline region is