From 38e0a639469b41c756a6a330b07ac62da69f55f9 Mon Sep 17 00:00:00 2001 From: "Claude (auto)" Date: Thu, 27 Aug 2026 21:02:58 +0000 Subject: [PATCH] re: the unit loader was already in the corpus (sub_82341A20), and 0-xref does not mean no reader Item "what loads the 114 unit Maneuver records" -- the corpus already answers it, and I should have found that before framing it as new. My own error, corrected: I claimed live-unit-definitions.md did not exist. It does, at docs/re/, not under structures/. Looking in one directory is not looking. sub_82341A20 is the unit-definition loader, documented in unit-struct-runtime.md with a checked-in 177-line layout at crates/sylpheed-formats/data/unit_definition_layout.txt. Verified independently: 3969 instructions, 227 of its 236 "addi rX, r30, -N" displacements resolve to strings -- the unit schema in order, Generic, UncertainName, IsDestructible, NamePlate, Size_X/Y/Z ... Maneuver sits at exactly 0x82088F94 - 13404. Record keys are tag_hash, confirmed on the data side: the Maneuver record's key is 0x43FAA517 = tag_hash("Maneuver"), not name_hash (0x63A248D7), x114 records. That explains the 0-xref phenomenon and softens my own verdict from last iteration. Maneuver's string has 0 xrefs AND a known reader: the loader takes its address as a register-passed base plus a displacement, which no static xref can see. So the AnalogRevice/Tweak verdict goes from RED to AMBER -- a base-plus-displacement sweep over the 2283 functions forming the 0x820A high half reaches it 0 times, but run against the unit block as a control the same sweep recovers only 5 references and misses sub_82341A20 itself. The measurement stands; "the reader is unreachable" does not. Also fixed: player-tuning-tables.md credited live-unit-definitions.md with documenting sub_822F9498. It documents sub_82341A20, a different function. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE --- docs/re/BACKLOG.md | 42 ++++++++++++++++++++++ docs/re/INDEX.md | 2 +- docs/re/structures/player-tuning-tables.md | 22 +++++++++--- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index cee275d..4d07343 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -4997,3 +4997,45 @@ which tracks the unit `Maneuver` 100/350/1 200 at a flat ~1.25 and misses 2 100. **Both tables cannot govern the same craft.** Cheapest next test is a runtime one (NEEDS-HUMAN): watch which constant set reaches the live flight struct, and re-measure with the afterburner held. + +--- + +## ✅⚠️ 2026-08-27 — the unit loader was already in the corpus (`sub_82341A20`), and 0-xref ≠ no reader + +Item (a), "what loads the 114 unit `Maneuver` records". **The corpus already +answers it and I should have found that before framing it as new.** + +⚠️ **My own error, corrected:** I claimed `live-unit-definitions.md` did not +exist. It does — at `docs/re/live-unit-definitions.md`, not under `structures/`. +Looking in one directory is not looking. + +✅ **`sub_82341A20` is the unit-definition loader**, documented in +`unit-struct-runtime.md` ("Cross-validated against the loader itself, +2026-08-13") with a checked-in 177-line layout at +`crates/sylpheed-formats/data/unit_definition_layout.txt` (159 fields, 25/25 +agreement with the solver). Verified independently here: 3 969 instructions, and +**227 of its 236 `addi rX, r30, -N` displacements resolve to strings** — the unit +schema in order, `Generic`, `UncertainName`, `IsDestructible`, `NamePlate`, +`Size_X/Y/Z`, … `Maneuver` sits at exactly `0x82088F94 − 13404`. + +✅ **Record keys are `tag_hash`, confirmed on the data side:** the `Maneuver` +record's key is `0x43FAA517` = `tag_hash("Maneuver")`, not `name_hash` +(`0x63A248D7`). ×114 records. + +🔑 **And that explains the 0-xref phenomenon — which softens my own verdict from +last iteration.** `Maneuver`'s string has **0 xrefs** *and* a known reader: the +loader takes its address as a **register-passed base plus a displacement**, which +no static xref can see. So a 0-xref block is not evidence of no reader. + +🟡 **Verdict on the `AnalogRevice_*`/`Tweak` block softened 🔴 → 🟡.** A +base-plus-displacement sweep over the 2 283 functions that form the `0x820A` high +half reaches it **0** times — but run against the unit block as a **control**, +the same sweep recovers only **5** references and **misses `sub_82341A20` +itself**. The measurement stands; "the reader is unreachable" does not. Recorded +in `player-tuning-tables.md`; runtime watch still the cheap way in. + +⚠️ Also fixed there: the doc credited `live-unit-definitions.md` with documenting +`sub_822F9498`. It documents `sub_82341A20` — a different function. + +🟡 Not settled: which table governs the measured player speeds (unchanged); a +base-tracking sweep strong enough to follow register-passed bases was not built. diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index fa8fee9..5dc6536 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -103,7 +103,7 @@ files, which is how the same ground got covered twice. | [`structures/stage-settings-table.md`](structures/stage-settings-table.md) | The per-stage settings object — Camera rigs, Player limits, Difficulty_* damage record | ✅ CONFIRMED, 24 settings objects vs 29 resource objects both using Phase_1/2/3; Camera is 13/14 fields constant; GravityFactor non-zero in 4 stages; IsBoss16Enable identifies one object; the Phase block is 94 fields of which 68 never vary between phases; all 24 objects named StageParameter_S | | [`structures/isl-mission-timer.md`](structures/isl-mission-timer.md) | The mission timer — two sequential countdowns, not a limit and a warning | ✅ CONFIRMED — read from `sub_822639B8`; the threshold reading is | | [`structures/archive-naming.md`](structures/archive-naming.md) | Which archives the disc can name — 100% for menu paks, **0.0%** for the six 2D paks and `GP_READY_ROOM` | ✅ CONFIRMED — 6027 names × 16 prefixes, bimodal; unnamed entries split by content — three UI paks have ZERO unnamed IDXD (artwork only), DefTables is the only real data gap (1295 LOD/motion tables); **route 2 = the `Enumerate` declaration tables** closes DefTables to 1413/1425 IDXD (99.2 %), 12 left; XPR2 manifests refuted as a naming source | -| [`structures/player-tuning-tables.md`](structures/player-tuning-tables.md) | **`PlayerParams`** — the player parameter table: `Booster` flight model, special attacks, 8 analog curves, the S16 boss | ✅✅ `sub_822F9498` is `PlayerParams`'s loader, NOT the unit loader (90/90 strings); all 5 player craft fly identically and `Booster` differs on 10 fields; 🟡 nothing selects it and the measured flight tracks the unit `Maneuver` instead | +| [`structures/player-tuning-tables.md`](structures/player-tuning-tables.md) | **`PlayerParams`** — the player parameter table: `Booster` flight model, special attacks, 8 analog curves, the S16 boss | ✅✅ `sub_822F9498` is `PlayerParams`'s loader, NOT the unit loader `sub_82341A20` (90/90 strings); all 5 player craft fly identically, `Booster` differs on 10 fields; 🟡 nothing selects it, and the analog block has no xref — but 0-xref does not mean no reader | | [`structures/hud-config.md`](structures/hud-config.md) | The in-game HUD configuration — 16 records, 419 asset paths, and a per-stage `ResourceTable` | ✅ CONFIRMED — 0 of 419 config paths resolve as pak entries | | [`structures/mission-script-manifest.md`](structures/mission-script-manifest.md) | `Stage\script.tbl`'s 11 non-`MISSION` fields, and the mission dialogue table they name | ✅ CONFIRMED — 1 of 7 values resolves (`message\MissionDialogMessage.tbl`, 200 records), controls in the same sweep | | [`structures/stage-numbering-and-player-craft.md`](structures/stage-numbering-and-player-craft.md) | The disc's stage numbering (S01–S16, S18–S29) and the player's craft per stage | ✅✅ CONFIRMED — two independent files close on 6 + 15 + 1 + 6 = 28 | diff --git a/docs/re/structures/player-tuning-tables.md b/docs/re/structures/player-tuning-tables.md index 60eaddc..91e6dee 100644 --- a/docs/re/structures/player-tuning-tables.md +++ b/docs/re/structures/player-tuning-tables.md @@ -135,7 +135,7 @@ 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. -## 🔴 BLOCKED: the analog-curve block is referenced by nothing +## 🟡 The analog-curve block is referenced by nothing *that I can see* — verdict softened The `AnalogRevice_*` / `Tweak` schema is a second contiguous block at **`0x820A119C–0x820A1378`**, in the object's own order: @@ -148,10 +148,22 @@ minimum_side_s receipt_match_spd order_cancel_time YawMagForNormal AnalogRevice_adv_roll … AnalogRevice_throttle (8 curve names) ``` -**0 of its 28 strings has a code xref, and no instruction anywhere in the -disassembly names a `0x820A1xxx` operand.** Control: the neighbouring block that -`sub_822F9498` reads scores **36/36**. So this is a real absence, not a query -mistake — but its cause is unexplained, and the reader is 🔴 unreachable this way. +**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. + +🟡 **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. + +⇒ 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. ⚠️ **The twin-string-block trap, in the flesh.** `mov_stick_play` and `eye_stick_play` each exist **twice**; only the *second* copy (`0x820AA630`,