re: the base-solver -- 277 name-block loaders indexed, and the analog block is SOLVED

New reusable tool, tools/re-capture/name_block_bases.py -> docs/re/data/name-block-bases.txt
(2880 lines, ~65 s, byte-identical across two runs).

A loader that reads a table by field name keeps one base pointer and emits
"addi rX, rBASE, -N" per name, so no static xref sees the strings.  Solve the base
from the DISPLACEMENT SET alone: every (string address, displacement) pair implies
a candidate base, and the true base collects a vote from every name it explains,
so it wins outright.  My first cut took candidates from ONE displacement and
scored the unit loader at 52/226 against the right answer's 217/226 -- vote over
the whole set, not a probe.

Control passes with no prior knowledge: the tool recovers sub_82341A20 -> r30 =
0x82088F94 at 217/226, and independently recovers sub_8230D1F8 (129/132),
sub_822F9498 (90/91) and sub_822AE628 (81/108).  277 name-block-reading functions
image-wide, with the schema each names.

The analog block is SOLVED: sub_821A6CF0, r29 = 0x820A1630, 22/24.  In code order
it names ControlTweakName, YawMagForNormal, the 12 Tweak fields, the 8
AnalogRevice_* curves and GP_MAIN_GAME -- the whole schema in the object's own
order plus its pak.  r29 is built at 0x821A6D34 as addi r29, r11, 5680 =
0x820A0000 + 5680, matching the solved base exactly.  It is the same function that
reads PlayerParams.

Two of my own verdicts withdrawn: "referenced by nothing" and the softened "not
found by these routes".  The measurements behind them were right; the conclusions
were wrong.  The base was solvable from the data the whole time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
This commit is contained in:
Claude (auto)
2026-08-27 21:22:50 +00:00
parent 38e0a63946
commit ae37e15d7d
5 changed files with 3067 additions and 21 deletions

View File

@@ -135,35 +135,50 @@ unit `Maneuver` 100 / 350 / 1 200 at a flat ~1.25 and misses `Booster`'s
runtime one:** watch which of the two constant sets reaches the live flight
struct, and re-measure with the afterburner held.
## 🟡 The analog-curve block is referenced by nothing *that I can see* — verdict softened
## ✅✅ SOLVED — `sub_821A6CF0` reads the analog block, found by solving the base
The `AnalogRevice_*` / `Tweak` schema is a second contiguous block at
**`0x820A119C0x820A1378`**, in the object's own order:
Two iterations called this block unreachable. It is not: it is read through a
**base register**, and the base can be *solved from the displacements alone*.
🔑 **The method** (`tools/re-capture/name_block_bases.py`
[`../data/name-block-bases.txt`](../data/name-block-bases.txt)): for each group
of `addi rX, rBASE, -N` sharing a source register, every (string address,
displacement) pair implies one candidate base; the true base collects a vote from
**every name it explains**, so it wins outright. ⚠️ Taking candidates from one
displacement instead misses it — that first cut scored the unit loader at 52/226
against the right answer's 217/226.
✅ **Control, with no prior knowledge: the tool recovers `r30 = 0x82088F94` for
the unit loader `sub_82341A20`, resolving 217 of 226 displacements.** It also
independently recovers `sub_8230D1F8` (129/132), `sub_822F9498` (90/91) and
`sub_822AE628` (81/108) — and finds **277** such functions image-wide.
**The answer: `sub_821A6CF0`, `r29 = 0x820A1630`, 22 of 24 displacements.** In
code order it names
```
ControlTweakName
ControlTweakName YawMagForNormal
mov_stick_play mov_trigger_play eye_stick_play receipt_after_b
receipt_reverse_s receipt_tgt_near receipt_tgt_atk receipt_side_s
minimum_side_s receipt_match_spd order_cancel_time YawMagForNormal
AnalogRevice_adv_roll … AnalogRevice_throttle (8 curve names)
minimum_side_s receipt_match_spd order_cancel_time
AnalogRevice_adv_roll … AnalogRevice_throttle (8 curve names)
GP_MAIN_GAME
```
**0 of its 28 strings has a code xref**, no instruction names a `0x820A1xxx`
operand, and a base-plus-displacement sweep (2 283 functions that form the
`0x820A` high half) reaches it **0** times.
— the whole `Tweak` + `AnalogRevice_*` schema, in the object's own order, plus
the **pak** it comes from. `r29` is built at `0x821A6D34` as `addi r29, r11, 5680`
= `0x820A0000 + 5680` = `0x820A1630`, confirming the solved base exactly.
🟡 **But "no xref" does not mean "no reader", and the corpus proves it.** The
*unit*-definition name block at `0x82085B38` — which includes `Maneuver` — also
has **0 xrefs**, and it is read by `sub_82341A20`, which builds every key as
`addi rX, r30, -N` from a base **passed in a register** (`Maneuver` sits at
`0x82088F94 13404`). No static xref can see that, and my base-tracking sweep
cannot either: run against that known-read block as a **control**, it recovers
only **5** references and misses `sub_82341A20` itself.
🔑 **And it is the same function that reads `PlayerParams`**`sub_821A6CF0`
references that literal and calls `sub_822F9498`. So one function loads the
player's parameter object *and* the control-tweak/analog table. 🟡 It reads the
curves as `AnalogRevice_*` record names; how the 11 samples are then *applied*
is still unread.
⇒ The measurement stands; last iteration's 🔴 "the reader is unreachable" was too
strong. The honest verdict is **🟡 not found by xref or by naive base tracking,
both of which have a demonstrated blind spot** for register-passed bases. A
runtime watch is still the cheap way in.
> Withdrawn: the earlier 🔴 "referenced by nothing" and the softened 🟡
> "not found by these routes". Both measurements were right — 0/28 xrefs, 0 hits
> from naive base tracking — and both conclusions were wrong. **The base was
> solvable from the data the whole time.**
⚠️ **The twin-string-block trap, in the flesh.** `mov_stick_play` and
`eye_stick_play` each exist **twice**; only the *second* copy (`0x820AA630`,