re(route-b): the field map comes out of the code, and with it the Ratio family

The filler builds each key as addi r4, r30, -N with r30 = 0x82088f94, so every
store's field NAME is a string in the image. Pairing keys with the following stfs
gives the layout outright: Size_X/Y/Z at +48/52/56, HQRatio +88, ShieldRatio +92,
ThrusterRatio +96, resistances +116..132, Radar/FCS/FiringRange +672/676/680,
Attack/Defence points +692/696/700. verify_fieldmap.rs checks it against the live
dump: 62 fields agree with the disc, 0 disagree.

That yields the runtime value of each field for units whose record omits it --
HQRatio 0.2/1, ThrusterRatio 0.2/1, ShieldRatio 1, ResistanceToPlayer 1,
ResistanceToShell 0.1, ResistanceToExplosion 0.5, AttackVesselPoint 0.1,
AttackCraftPoint 0.1/0.5, DefencePoint 0.1/0.25, FiringRange 0, Size_Y = Size_X.
Recorded with the caveat that several show two values across units, so these are
per-unit runtime values rather than one global default.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 23:17:05 +00:00
parent 2ed475868b
commit 0434afa6a6
2 changed files with 121 additions and 0 deletions

View File

@@ -336,3 +336,54 @@ Two readings corrected on the way:
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).
## ✅ The field map, read out of the code — and the Ratio family with it
The filler builds each key as `addi r4, r30, -N` with `r30 = 0x82088f94`, so the
**field name for every store is a string in the image**. Pairing each key with the
`stfs` that follows gives the object layout directly, with no guessing:
| offset | field | offset | field |
|---|---|---|---|
| `+48` | `Size_X` | `+116` | `ResistanceToOptics` |
| `+52` | `Size_Y` | `+120` | `ResistanceToShell` |
| `+56` | `Size_Z` | `+124` | `ResistanceToExplosion` |
| `+64/68/72` | `Color_R/G/B` | `+128` | `ResistanceToPlayer` |
| `+80` | `Size_Radius` | `+132` | `ResistanceParalyze` |
| **`+88`** | **`HQRatio`** | `+672` | `RadarRange` |
| **`+92`** | **`ShieldRatio`** | `+676` | `FCSRange` |
| **`+96`** | **`ThrusterRatio`** | `+680` | `FiringRange` |
| `+692` | `AttackVesselPoint` | `+696` | `AttackCraftPoint` |
| `+700` | `DefencePoint` | | |
`examples/verify_fieldmap.rs` checks it against the live dump: **62 fields agree
with the disc record, 0 disagree** across five capital ships.
That turns the same dump into the answer for the family the brief asked about —
the runtime value of each field **for the units whose record omits it**:
| field | runtime value(s) seen | units |
|---|---|---|
| `Size_Y` | 200, 300, 400, 600, 700 (each `= Size_X`) | 5 |
| `HQRatio` | **0.2**, **1** | 4 |
| `ThrusterRatio` | **0.2**, **1** | 4 |
| `ShieldRatio` | **1** | 1 |
| `ResistanceToPlayer` | **1** | 5 |
| `ResistanceToShell` | **0.1** | 3 |
| `ResistanceToExplosion` | **0.5** | 3 |
| `AttackVesselPoint` | **0.1** | 2 |
| `AttackCraftPoint` | **0.1**, **0.5** | 5 |
| `DefencePoint` | **0.1**, **0.25** | 4 |
| `FiringRange` | **0** | 5 |
⚠️ Read that table as *"what the field holds at runtime for a unit that does not
set it"*, **not** as "the default constant". Several fields show **two distinct
values** across units (`HQRatio` 0.2 or 1, `AttackCraftPoint` 0.1 or 0.5), so the
value is derived per unit rather than being one global default — consistent with
`Size_Y`, which takes each unit's own `Size_X`. Only `FiringRange` (0 everywhere)
matches the loader's own miss value.
*Method note:* the first run of this check reported 32 "mismatches". They were an
off-by-one column in the dump reader — `f[3]` is the u32, `f[4]` the float — and
decoding one by hand (`1128792000` = `0x43480000` = 200.0) showed the map had been
right all along.