277 rows over 190 distinct functions (a function can read several blocks). name_block_bases.py extended with a confidence split. The tool's false-positive mode, measured and named: 107 of 277 rows solve to a base on a 64K boundary -- a bare "addis rX, r0, 0xHHHH" with no addi, so any scatter of displacements votes for it. 82 are 0x820B0000: about 60 near-identical functions in 0x8281xxxx-0x8284xxxx all "naming" the same rou_e0NN list. The 170 rows with a non-zero low half are the trustworthy set. A round base is not automatically wrong -- sub_822215D0 sits on 0x820A0000 and resolves 205/206 -- so read the ratio, not the base. The index re-derives every loader we already knew (unit 217, stage settings 129, PlayerParams 90, hangar 81, squadron orders, missile guidance, shell movement, substructures, six camera/fog readers) -- that is the control. The find: sub_8233C368 reads the AI behaviour table. r28, base 0x8208583C, 20 names -- Enumerate_AIs, FiringLength, GuardLength, AutoGuardLength, CounterLength, MusterLength. stage-mission-tables.md owns those field names on the data side, but Enumerate_AIs appears in no document and no reader was known; the corpus carries the AI tail of Maneuver as NEEDS-HUMAN/runtime. It is statically reachable after all. The same base also serves sub_82338EE0 (97 names, Weapon TargetType SpecialWeaponType ReticleType IsCharging ...) -- the weapon datasheet loader, also not previously named. Five unowned blocks surfaced and NOT opened: PGHUD_*/PGREMAIN_NUM HUD part names (205/206), STAGE_RESULT/stage_num_shoot_down_aircrafts/EX_OVERVIEW, g_mWorldViewProjection/NormalMap/GlossinessMap engine material slots, Boss16Collision* (cross-links the S16 Guardian object), and roh_n001_menu1_cam_pos menu camera tags. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
14 KiB
✅ The player-tuning tables — analog response curves, booster/flight limits, special attacks
- Regenerate:
python3 tools/re-capture/main_game_unnamed.py→../data/main-game-unnamed.txt - Where:
GP_MAIN_GAME_{D,E,F,I,J,S}.pak, as unnamed IDXD objects — identical hash sets in all six language copies. - Related: flight-speed-law, flight-controls-runtime, unit-datasheet-static, archive-naming.
Found while characterising the 337 unnamed IDXD objects in each main-game pak
(see archive-naming.md). 333 of the 337 belong to families the corpus already
documents. These are the four that do not.
✅ The analog stick response curves — object 955ca077
Eight records, one per input axis, each 11 positional values plus a named
Count = 11. ⚠️ Count naming an indexed group is not safe in general — here
it was tested and holds 8/8.
| record | shape | curve |
|---|---|---|
AnalogRevice_yaw |
identity | 0.0 0.1 … 1.0 |
AnalogRevice_roll |
identity | 0.0 0.1 … 1.0 |
AnalogRevice_throttle |
identity | 0.0 0.1 … 1.0 |
AnalogRevice_pitch |
strongly eased | 0.000 0.003 0.010 0.020 0.040 0.065 0.100 0.155 0.250 0.500 1.000 |
AnalogRevice_eye_pitch |
eased | 0.00 0.01 0.03 0.06 0.10 0.18 0.28 0.42 0.60 0.80 1.00 |
AnalogRevice_eye_yaw |
eased, flat dead lead-in | 0.00 0.00 0.00 0.00 0.03 0.10 0.18 0.29 0.45 0.68 1.00 |
AnalogRevice_adv_roll |
S-curve | 0.000 0.005 0.032 0.080 0.138 0.231 0.387 0.584 0.792 0.941 1.000 |
AnalogRevice_adv_yaw |
not monotone | 0.000 0.016 0.052 0.143 0.482 0.940 1.000 0.916 0.611 0.382 0.208 |
✅ Three of the eight are the identity ramp — yaw, roll and throttle are
unshaped, and the shaping the game does apply is all on pitch and the camera
("eye") axes. 🟡 adv_yaw rises to 1.0 at sample 6 then falls back to
0.208, so whatever it indexes, it is not a stick-deflection curve like the
other seven; it has the shape of an envelope over something else. Untested.
The same object carries a Tweak record with what read as deadzones and
input-timing constants — mov_stick_play = 5000, eye_stick_play = 6000
(raw stick units), order_cancel_time = 120, YawMagForNormal = 2.0, and six
receipt_* / minimum_side_s values. 🟡 The units are inferred from magnitude,
not read from the consumer.
🔴 The player craft's flight envelope — object 890e1be4, CORRECTED
My label from the first pass was wrong twice over. Kept, with the measurements that refuted it, because both errors are instructive.
Error 1 — Booster is not a new schema. Its 50 field names are a strict
subset of the unit Maneuver record: 50/50 shared, zero Booster-only. (The
first check compared against Generic and scored 0/50, which looked like a
brand-new structure — the wrong record of a multi-record object is not a
control.) CruisingVelocity appears in exactly 115 records disc-wide: 114
Maneuver + this one Booster.
Error 2 — it is not the profile the player actually flies. flight-speed-law measured the Delta Saber at ~125 / ~420 / ~1 530 (min / cruise / max). Against the two candidate tables:
| min | cruise | max | ratios measured ÷ table | |
|---|---|---|---|---|
unit Maneuver (5 units carry it) |
100 | 350 | 1 200 | 1.25 / 1.20 / 1.28 — consistent |
Booster |
100 | 612 | 2 100 | 1.25 / 0.69 / 0.73 — not |
✅ The flight matches the unit Maneuver record, at one consistent scale.
Booster is a second, faster profile — and the way it differs is the
finding: 39 of its 50 values are identical to the player unit's Maneuver,
and of the 11 that move, the three velocities are scaled by exactly ×1.75
(350 → 612, i.e. 612.5 rounded; 1 200 → 2 100; SideThrustVelocity_Max
500 → 875) and two accelerations by ×1.5 (Acceleration 600 → 900,
SideThrustAcceleration 1 000 → 1 500). The rest: Deceleration 500 → 600,
PowerCutDeceleration 10 → 15, PowerCutConsumeShield 20 → 75,
AB_ConsumeShield_Begin 50 → 20, ArterBurner_Vc 2.0 → 1.5.
🟡 What that profile is for — an upgrade, a difficulty tier, a boost mode — is not settled. Nothing selects it on the disc that I could find.
The object's other records stand as described: TacticalManeuver
(Turn180RequiredTime 0.5, RollL/RRequiredTime 1.0, RollL/RSlideLength 350,
RollToHorizonTime 2.0), SpecialAttack + SpecialAttackGauge_1/2/3
(ChargeMaximum 33 / 66 / 100), SpecialWeapon, Misc.
✅✅ The object is PlayerParams, and sub_822F9498 is ITS loader — not the unit loader
Resolving every string sub_822F9498 references, in code order, gives 90
of them, and they are exactly this object's schema, in this object's record
order:
CollisionEffectName … EffectPlacementFrameName (Misc, 5)
SpecialAttack + 17 fields
TacticalManeuver + 7
SpecialWeapon + 8
Booster + 50
It never names Generic, Maneuver, Explosion, Shield or StructureCount;
the string Maneuver exists once in the image and has 0 xrefs. The function
is called exactly once, from sub_821A6CF0 — which is itself called once and
whose four strings include the literal PlayerParams.
⇒ 🔴 Correction to unit-datasheet-static, which calls sub_822F9498 "the
unit-definition loader". It reads one object, the player parameter table. Its
AA_/AV_ interleave finding (AV_ at X, AA_ at X+8, five axes) still
stands as a struct layout — but the struct is PlayerParams's Booster
record, not each unit's Maneuver. Whatever loads the 114 unit Maneuver
records does not use these name strings at all, and has not been found.
🟡 name_hash/tag_hash of PlayerParams under 7 prefixes × 6 spellings
(84 combinations) hits the object's key 0x890E1BE4 0 times — the string
names the table to the code, not the pak entry.
⚠️ The loader names 8 SpecialWeapon fields; the disc values 7.
HPRegenerator_Quantity is named and never valued — the usual pattern.
✅ All five player craft fly identically; Booster differs on ten fields
Ranking all 114 units by how many of the 50 Booster values they reproduce:
the top five are exactly the five _Player units — f001_T, two
f001_T_Tt, f002_W, f004_A — all at 39/50, and the sixth-best unit drops
to 13/50. No unit matches 50/50.
✅ And the five players agree with each other on all 50 fields: the three
player ships share one flight model. Booster stands alone on 10 of them
(the eleventh, AA_Yaw_Max, is 65.0 vs 65 — a formatting difference, not a
value; my earlier "11 differ" over-counted).
🟡 So what selects Booster? Nothing does, and that is the problem
PlayerParams is loaded once, unconditionally, at startup: one call site,
one caller, no branch. So Booster is not a profile the game chooses — it is
the only flight table this path loads.
🟡 That is in tension with the measurements and I have not resolved it.
flight-speed-law clocked the player at ~125 / ~420 / ~1 530, which tracks the
unit Maneuver 100 / 350 / 1 200 at a flat ~1.25 and misses Booster's
100 / 612 / 2 100 badly — and the RT-held run reached ~1 530, not anything near
2 100. Both tables cannot govern the same craft. Cheapest next test is a
runtime one: watch which of the two constant sets reaches the live flight
struct, and re-measure with the afterburner held.
✅✅ SOLVED — sub_821A6CF0 reads the analog block, found by solving the base
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): 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 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
AnalogRevice_adv_roll … AnalogRevice_throttle (8 curve names)
GP_MAIN_GAME
— 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.
🔑 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.
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,
0x820AA654) is referenced, by sub_822AE628 — which also references
ControlTweak, Camera, Rendering, BackGround, WeaponChangeFrame and the
DOF/bloom field names. And ControlTweak records exist only in
GP_HANGAR_ARSENAL.pak (×6), while Tweak exists only in the six main-game
paks (×1 each). sub_822AE628 reads the hangar's control table. Taking it
for ours would have been a clean, wrong answer.
✅ The Stage 16 boss — object 0202269d
Records Guardian (19 fields) and Core (10). Guardian names its three
weapons by shell id — Shell_S16Boss_AAGun, Shell_S16Boss_Laser,
Shell_S16Boss_HBeam — which is what identifies the object: Stage 16, the
stage stage-settings-table.md singles out via its unique IsBoss16Enable
field. Guardian HP 65 000 with DamageLevel1/2 at 35 000 / 20 000; Core
HP 42 000 with levels at 25 000 / 10 000 and a lock-on release cycle
(LockOnReleaseInterval 15.0 ± LockOnReleaseRandomInterval 5.0).
🟡 The fourth: object a6c6c79b
One record, Generic, one field — the name eff_n0071 with an empty value.
An effect id and nothing else. Not identified.
✅ Mining the 277: what the base-solver's index actually contains (2026-08-27)
277 rows over 190 distinct functions (a function can read more than one block). Classified:
🔴 The tool's false-positive mode, now named. 107 rows solve to a base on a
64K boundary — a bare addis rX, r0, 0xHHHH with no addi of its own, so any
scatter of displacements votes for it. 82 of them are 0x820B0000, and they
are ~60 near-identical functions in 0x8281xxxx–0x8284xxxx all "naming" the same
rou_e007 rou_e010 rou_e015 … list. Read a round base with its resolution ratio,
never on its own. The 170 rows with a non-zero low half are the trustworthy set.
Already owned (the index re-derives them, which is the point): the unit
loader sub_82341A20 (217), stage settings sub_8230D1F8 (129), PlayerParams
sub_822F9498 (90), the hangar sub_822AE628 (81), squadron orders
(sub_82320B48 → ORDER_WINGMAN_*), missile guidance (sub_8236B608,
sub_8237BB78 → st1_up_aperture etc., weapon-datasheet-static), shell
movement (sub_82261F70 → Spiral_BeginTime, weapon-struct-runtime),
substructures (sub_823479B8 → ParentStructureID), and six camera/fog readers
(sub_825F2CF0, sub_825F2F88, sub_8247DFC0, sub_823B2620, sub_82222E70,
sub_822C7480).
🔑 The find: sub_8233C368 reads the AI behaviour table. r28, base
0x8208583C, 20 names — Enumerate_AIs, FiringLength, GuardLength,
AutoGuardLength, CounterLength, MusterLength, … stage-mission-tables
owns those field names on the data side; Enumerate_AIs appears in no
document, and no reader was known — the corpus carries the AI tail of
Maneuver as 🟡 NEEDS-HUMAN/runtime. It is statically reachable after all.
The same base 0x8208583C also serves sub_82338EE0 (97 names,
Weapon TargetType SpecialWeaponType ReticleType IsCharging …) — the weapon
datasheet loader, likewise not previously named.
Other unowned blocks the index surfaces (measured, not investigated):
sub_822215D0 (205/206, PGHUD_* / PGREMAIN_NUM%d / PGTIMER_NUM%d — HUD
part names; mission-script-manifest mentions PGHUD_SUBTARGET only),
sub_822814D8 + sub_8227A3A0 (STAGE_RESULT, stage_num_shoot_down_aircrafts,
EX_OVERVIEW — results/leaderboard fields), sub_822E3EC8
(g_mWorldViewProjection, NormalMap, GlossinessMap — engine shader/material
slots), sub_823C0260 + sub_823AE908 (Boss16CollisionBody01…,
rou_e901_body_02_c — the S16 boss collision parts, cross-linking the Guardian
object above), and sub_822AFA50 (roh_n001_menu1_cam_pos — menu camera tags).
🟡 None of those five were opened; the index says what each names, not what each means.