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:
29
crates/sylpheed-formats/examples/pool_window.rs
Normal file
29
crates/sylpheed-formats/examples/pool_window.rs
Normal 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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user