re: the unit filler is sub_82341A20, and its 1.0 constant is not a default

Call graph completes: sub_82341048 allocates 880 bytes, calls the zero-filling
constructor, then sub_82341A20 (3969 instructions, 157 stfs, 892 calls) fills the
fields. A repeated lfs/stfs pattern writes the constant at 0x820854cc -- read from
the .pe as 1.0 -- into ten offsets, which looks like a blanket default.

Checking those offsets in the live objects first shows it is not: they hold
per-unit radian angles (10/15/16/30/40/45/60 degrees), so the 1.0 belongs to the
degree-to-radian conversion, not to a default. The ordinary default is 0 from the
constructor; non-zero defaults like Size_Y <- Size_X are specific derivations.
Incidental and useful: angle fields are radians at runtime, degrees on disc.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 22:18:56 +00:00
parent 71adafe7f7
commit be42e998bc

View File

@@ -256,3 +256,40 @@ confirmed: the first is a *conditional* assignment (it compares `+0x30` against
register and picks between two values before storing to `+0x34`), not a plain register and picks between two values before storing to `+0x34`), not a plain
copy, and offsets 48/52 are generic enough that both could belong to unrelated copy, and offsets 48/52 are generic enough that both could belong to unrelated
structures. Recorded as leads, not findings. structures. Recorded as leads, not findings.
## The filler, and a constant that turned out not to be a default
The call graph completes: `sub_82341048` allocates **880 bytes**
(`addi r3, r0, 880``bl 0x8230C160`), calls the zero-filling constructor
`sub_8233FAF8`, and then hands the object to **`sub_82341A20`** — 15 876 bytes,
**3 969 instructions, 157 `stfs`, 892 calls**. That is the per-field filler for a
unit definition, and it is where any default has to be applied.
Inside it, one pattern repeats: `lfs f, -15176(r30)` followed by `stfs f, +N(r29)`
for N = 336, 340, 344, 348, 352, 356, 360, 428, 512, 516 (and 124…160 of a
sub-object). `r30` resolves to `0x82090000 28780 = 0x82088f94` (from
`addis r11, r0, 0x8209` / `addi r30, r11, -28780`), so the constant sits at
**`0x820854cc`**, which the executable image gives as **`0x3f800000` = 1.0**.
**That looks exactly like "write the default 1.0 into ten fields" — and it is
wrong.** Checking those offsets in the live objects first:
| offset | `f001` fighter | `e007` turret | most others |
|---|---|---|---|
| `+336` | 0.261799 (15°) | 0 | 0 |
| `+344` | 0.174533 (10°) | 0 | 0 |
| `+356` | 0.698132 (40°) | 0 | 0 |
| `+428` | 0 | 0.785398 (45°) | 0 |
| `+512` | 0 | 0.523599 (30°) | 0 |
Those are **radians** — 10°, 15°, 16°, 30°, 40°, 45°, 60° — i.e. per-unit angle
limits converted from the degrees the disc stores (`MaximumBank_Normal 60`
1.0472). So the `1.0` is part of a formula or a conditional path in that
conversion, **not** a blanket default, and the fields themselves are 0 for units
whose records omit them.
Which leaves the picture consistent with everything measured so far: **the ordinary
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.**