From be42e998bc040cce9a7e2ceb458fd0c768fd1ae0 Mon Sep 17 00:00:00 2001 From: "Claude (auto-RE)" Date: Wed, 12 Aug 2026 22:18:56 +0000 Subject: [PATCH] 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) --- docs/re/live-unit-definitions.md | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/re/live-unit-definitions.md b/docs/re/live-unit-definitions.md index 9f5df47..5b3e26d 100644 --- a/docs/re/live-unit-definitions.md +++ b/docs/re/live-unit-definitions.md @@ -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 copy, and offsets 48/52 are generic enough that both could belong to unrelated 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.**