re: Size_Y is genuinely defaulted, and the loader leaves it at 0.0

pool_window.rs shows the Destroyer's raw tokens: "200.0" "Size_X" "Size_Y"
"2000.0" "Size_Z" -- Size_Y is a bare key, so the 13 runtime values recovered
earlier are real defaults, not a reader artefact.

The filler reads each size field through sub_822FC5A8, which loads f31 from
0x8209fd28 = 0.0 at entry and returns it on a pool miss, then stores to +52.
Nothing else in the 15876-byte filler writes +52, so the definition leaves a
defaulted Size_Y at 0.0 and the runtime 200 is written later.

Two corrections: the +48/+52/+56 comparison block builds a size-class bitmask
against 1000.0 (constant 0x8209fd20), not a has-value mask; and both earlier
Size_Y derivation candidates are refuted (one is a conditional pick, the other
multiplies into a different struct).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 22:47:43 +00:00
parent be42e998bc
commit 2ed475868b
2 changed files with 72 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
//! Raw token sequence around a key, for one record — the ground truth for
//! "is this field actually defaulted, or is our reader missing it?"
//! Run: pool_window <disc-root> <ID substring> <key>
use sylpheed_formats::idxd::IdxdObject;
use sylpheed_formats::pak::PakArchive;
fn main() {
let disc = std::env::args().nth(1).unwrap();
let want = std::env::args().nth(2).unwrap();
let key = std::env::args().nth(3).unwrap();
let pak = PakArchive::open(format!("{disc}/dat/GP_MAIN_GAME_E.pak")).unwrap();
for e in pak.entries() {
let Ok(b) = pak.read(e) else { continue };
let Ok(o) = IdxdObject::parse(&b) else { continue };
let Some(id) = o.get_raw("ID") else { continue };
if !id.contains(&want) { continue; }
let t = o.tokens();
println!("=== {id} get_f32({key}) = {:?}", o.get_f32(&key));
for (i, tok) in t.iter().enumerate() {
if tok == &key {
let lo = i.saturating_sub(6);
let hi = (i + 7).min(t.len());
for j in lo..hi {
println!(" [{j}]{} {:?}", if j == i { " <-- key" } else { " " }, t[j]);
}
println!();
}
}
}
}

View File

@@ -293,3 +293,46 @@ default is 0**, straight from the zero-filling constructor, and the interesting
non-zero defaults (`Size_Y``Size_X`) are specific derivations to be found
individually in this filler. Worth noting for the reimplementation regardless:
**angle fields are stored in radians at runtime and in degrees on disc.**
## Confirming the field really is defaulted, and where the loader leaves it
Before chasing the derivation any further it was worth checking the premise.
`examples/pool_window.rs` prints the raw token sequence around a key, and for the
Destroyer it reads:
```
[14] "200.0" [15] "Size_X" [16] "Size_Y" [17] "2000.0" [18] "Size_Z"
```
`Size_X` takes the value before it; **`Size_Y` is a bare key** — genuinely
defaulted, exactly as the value-before-key rule predicts. So the 13 values
recovered from the running game are real defaults, not a reader artefact.
The loader's own answer for a missing field is now pinned too. Each size field is
filled by the same three-call sequence — build the key string, call the float
accessor **`sub_822FC5A8`**, store the result:
```
0x82341d94 addi r4, r30, -14004 ; "Size_Y"
0x82341da0 bl 0x8217FA08 ; make key
0x82341dac bl 0x822FC5A8 ; read float
0x82341db0 stfs f1, 52(r29) ; -> Size_Y
```
and `sub_822FC5A8` loads `f31` from `0x8209fd28` = **0.0** at entry and returns it
when the pool lookup misses. **So the definition leaves a defaulted `Size_Y` at
0.0**, and nothing else in the 15 876-byte filler writes `+52`.
Two readings corrected on the way:
- The block at `0x82341e4c``0x82341ebc` that loads `+48`/`+52`/`+56` is **not** a
"was this field set" mask. It compares each against the constant at
`0x8209fd20` = **1000.0** and ORs flags into `+108` — a **size-class bitmask**
for large objects.
- Both earlier `Size_Y` candidates are refuted: `0x82217e18` conditionally picks
between two registers rather than copying, and `0x823081e8` *multiplies* `+48`
by a table constant into `+52` of a different structure.
So the runtime `Size_Y = Size_X` must be written **after** the definition is
loaded — by whatever instantiates a unit from it, or a post-load pass over the
table. `+108` itself is too generic to chase (1 129 loads image-wide).