Correction to the previous commit's own result. The raw byte sweep reported
rot_n001 "26 times in 7 archives" - a SUBSTRING count. The exact field value
occurs zero times, in every IDXD record and every non-IDXD payload. What the
disc carries is rot_n001_break x14, LOD_Frame_rot_n001_break x7,
GameModel_rot_n001_break x5. rot_n001 exact is not on this disc. A prefix
match is not an exact match, and a byte sweep is the tool that breaks it.
rou_e202 does exist, in the other vocabulary: a Model field of a Generic
record, six times, one per GP_MAIN_GAME_<lang> pack, with no GameResourceID -
which is why the GameResourceID census called it absent.
Set arithmetic over every IDXD record: roster 40, Generic.Model 46,
GameResourceID 480; roster n Model 34, roster n GID 38, roster n both 33.
38 + 34 - 33 = 39, so 39 of 40 are one or the other and rot_n001 is the sole
exception. The six that are GID but not Model: mob_n040..043 are the four
AsteroidGroup_00N.AsteroidModelName values (asteroid models, not units) and
rou_e004 is a Level_0.Model.
What selects the 40 is still open, but two answers are dead: not the
Generic.Model set (34 of 46), not the GameResourceID set (38 of 480). The 12
Generic.Model values outside the roster include rou_f002 and rou_f004 - the
player's own craft for S7-S16/S24-27,29 and for S28 - while rou_f001 is kept,
so "the playable models" is dead too.
All fifteen artefacts byte-identical.
Each of the 60 builds a 40-element array of 24-byte string objects: addis
0x820B + addi fetches the literal, addi r30,r11,12944 fixes the array
(0x828E3290 for the first), bl sub_8217FA08(dest, literal, -1) assigns, the
rest of the object is zeroed, addi r3,r30,24 steps on.
Why they have no callers: each appears exactly twice in the image - once in
the .pdata unwind table, where EVERY function appears as (address, prolog/
length word) and reading it as a registry is a trap - and once as a slot of a
null-terminated 182-entry function-pointer array at 0x82870018-0x828702EC,
170 of whose targets have zero direct xrefs. That is the static-initialiser
list. Zero callers here means "static ctor", not "dead code".
The teardown side confirms it: 39-43 functions sit between consecutive clones
(median 40), the dominant size in 0x8281xxxx-0x8284xxxx is 11 instructions
(2280 of them, about 60 x 40), and three consecutive ones call sub_823F3D68
on 0x828E3290, +24, +48 - one destructor thunk per element, same stride, same
global, all zero-xref.
rot_n001 and rou_e202 ARE on the disc: a raw byte sweep finds rot_n001 26
times in 7 archives (DefTables included) and rou_e202 6 in 6, exactly one per
GP_MAIN_GAME_<lang> pack. Controls: rou_e201 151/8, rou_e901 917/8, mob_n500
46/8. Narrows the earlier claim to "not GameResourceID values".
Not settled: what reads the arrays. An exact-address search returns 2347
functions outside the 60 - too broad to be a consumer list; the globals sit in
a dense data region and that route has no power.
All fifteen artefacts byte-identical.
The remaining named false-positive mode - "107 rows solve to a 64K-boundary
base, a bare addis with no addi of its own, so any scatter of displacements
votes for it" - is refuted by its own measurement.
New positive test in the tool: simulate lis/addis rD,r0,HI + addi rD,rA,N +
or rD,rA,rA forward through each row's function and ask whether the solved
base lands in the solved register.
64K-boundary bases ("low confidence") : 107 / 107 confirmed
non-zero low half ("trustworthy") : 8 / 154 confirmed
A round base is the case where the compiler needed no second instruction, so
`addis r11, r0, 0x820B` stands in the code in full. A miss on the other class
is silence (base built in the caller or loaded from memory), not refutation.
Control: every row the corpus independently validated against the disc has a
64K-boundary base - debriefing, career, save, leaderboard, the 205-name PG*
HUD roster, material slots, the S16 boss collision/frames/motions and its
loader. 13 rows over 10 functions. The dense-short-string false positives the
corpus did name (r31 = 0x8202xxxx) all sit in the "trustworthy" class.
The 0x820B0000 cluster is DUPLICATION, not error: 60 of its 82 rows are one
function emitted 60 times, exactly 491 instructions each, two instructions
differing (both global data pointers), identical 41-address string sequences.
40 resource names written into a per-copy global via sub_8217FA08 at 24-byte
strides. 38 of the 40 are disc GameResourceID values (480 distinct); rot_n001
and rou_e202 are not, and no disc GameResourceID uses the rot_ prefix.
Artefact diff 13/4, confined to the replaced section; the 261-row table and
the 64K histogram untouched; byte-identical on a second run. Fourteen other
artefacts byte-identical.
sub_825F2CF0 / sub_825F2F88 (r0, 30 names each, 97% disc names) are not
name-block readers at all: they are the compiler's VMX save/restore helper
pair. 145 instructions each, 72 differ, every difference stvx<->lvx over
v14..v127.
Root cause: in `addi rD, rA, N` the rA slot reads as literal zero when it
names r0, so `addi r11, r0, -N` is `li r11, -N` - vector spill offsets, not
displacements. The solver excluded r1 only; the bogus base landed inside the
stage-settings name block, dense enough to "resolve" 30 real field names.
name_block_bases.py now skips r0 as well. Rows 277 -> 261, functions 190 ->
176, non-64K bases 170 -> 154, data-table rows 53 -> 50; 154+107=261 and
277-16=261. Artefact diff -100/+3, every removed row line carries r0, control
sub_82341A20 r30 = 0x82088F94 217/226 untouched.
Nothing lost: all 30 names are a strict subset of sub_8230D1F8 r29 (129
names), the stage-settings loader already in the corpus. String-xref join
agrees - FinalPassBG, FogMinDistance, ScreenColorR, ExposureKey_BeginValue
each exist once with exactly one xref, from sub_8230D1F8.
Fourteen other artefacts byte-identical; name-block-bases.txt byte-identical
on a second run.
name_block_bases.py extended with a per-row data-table test; artefact +57/-0,
byte-identical across two runs (now ~2 min 12 s -- it adds a disc-wide pak scan).
The test is objective, not by eye: a row is a data-table schema if its names are
IDXD record/field names on the disc (13450 such names disc-wide). 53 of 277 rows
are >=50 % disc names with >=8 names; the other 224 are engine/XDK vocabulary,
compiled key lists, or noise.
The two axes are independent: against base confidence, solved bases split 34
table / 136 not, round bases 16 / 91. "Round base" and "not a table" are
different questions.
The 53 contain every loader already known -- that is the control. Five rows in
the 53 are unowned, each noun grepped and appearing in no docs/re/ file:
sub_823BDAA8 r11 (33) = the S16 boss's muzzle/attach frames (GN_MainGun_*_Muz*);
sub_823BDAA8 r10 (25) = motion names (Motion_stand, Motion_attackA_start), the
EnumMotions family DefTables declares; sub_82315AE8 r11 (20) = the Guardian
record's own fields, i.e. the S16 boss loader; sub_8219E560 r11 (18) = the
leaderboard screen keys; sub_825F2CF0 + sub_825F2F88 r0 (30 each, same base) =
post-processing (FinalPassBG, FogMin/MaxDistance).
Four rows that look new are not, and their disc-overlap says so -- 53-70 % rather
than ~100 %, because they mix arsenal fields the corpus owns (ConditionToDevelop,
WeaponDesc, SilhouetteModel) with literal screen coordinates as strings.
Not settled: none of the five was opened -- this iteration produced the shortlist,
not the findings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
CORRECTION FIRST: a row in name-block-bases.txt is a (function, REGISTER) pair,
not a function. Every one-line label I carried for these blocks quoted one of
two rows, and the other row of the same function is an unrelated schema:
sub_822E3EC8 r11 = 29 shader constants + techniques, r10 = 15 material map slots;
sub_822AFA50 r11 = XDK shader-compiler tokens, r10 = 13 menu camera tags;
sub_823AE908 r11 = the 43-name S16 boss collision table, r31 = 33 shader tokens
(the dense-block false positive). Quote the register.
The engine's material vocabulary (into xbg7-mesh.md, which owns the
_col/_spc/_gls/_lum suffixes): nine base map slots -- ColorMap, LuminosityMap,
SpecularityMap, GlossinessMap, ReflectionMap, TransparencyMap, NormalMap,
FresnelMap, EdgeTransparencyMap -- and seven *MapArray twins; 9 - 7 = 2, and the
two without a twin are FresnelMap and EdgeTransparencyMap. Four of the nine are
exactly the four documented suffixes; the other five have no suffix in the
shipped meshes, so the engine supports more channels than the assets use. Plus
17 g_* shader constants, three cube samplers, and DepthOnly / MotionVector /
Transparent techniques -- the ...Last matrices with the motion-vector technique
say the renderer keeps the previous frame's per-object transform.
The S16 boss's collision parts (into collisionset.md): sub_823AE908 r11 is an
interleaved (mesh, logical-part) table -- rou_e901_body_02_c ->
Boss16CollisionBody02, five wing pairs, both tails, barrier, rou_e910_core_cmesh
-> Boss16CollisionCore, and mob_n900{,_b01,_b02,_gear}_cmesh -> Room00/01/02 +
Gear00/01/02. 23 logical parts + 20 meshes = 43, differing by exactly three:
Body01 opens with no mesh and the one gear mesh carries three gear names. Cross-
links the S16 Guardian/Core object found during the PlayerParams work.
Not settled: whether those meshes are CollisionSet entries, why Body01 has no
mesh, and no shader slot was traced to a shipped shader.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
CORRECTION FIRST. Last iteration I recorded sub_8233C368 as unblocking a
NEEDS-HUMAN item -- "the corpus carries the AI tail of Maneuver as
NEEDS-HUMAN/runtime; it is statically reachable after all." That was wrong.
stage-mission-tables.md already documents AIParams_S02.tbl as exact original
values obtained by static RE, directly portable, listing all 20 field names and
both shapes. I grepped FiringLength and saw the file but did not read the
section. Finding the owning doc is not reading it. The only genuinely new part
was the loader's name.
What is new: the census generalises Stage 02 to the disc. 23 AIParams objects,
identical in all six GP_MAIN_GAME_* paks, sharing ONE declared-name set of 34
profiles; 782 profile records = 23 x 34; 0 declared names without a record in
their own object. So "34 AI profiles" is not a Stage-02 fact -- every stage
carries the same 34 and only the values move. The roster is declared by an
Enumerate_AIs record whose field names are the profile names, the same
declaration-table mechanism that closed DefTables.
Type predicts the field count with exactly two exceptions: Fleet -> 6 fields is
253/253 zero partials; Squad -> 20 fields is 483/529. The 46-record residual in
full: AI_Test and AI_CraftSquadron_Test, both Type = Squad with only the six base
fields, in all 23 objects. No profile's shape varies between objects.
New regenerator aiparams_census.py, 45-line artefact, byte-identical across two
runs; the other fourteen verify unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
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
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
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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
Resolving every string sub_822F9498 references, in code order, gives 90, and they
are exactly the 890e1be4 object's schema in its record order: Misc (5 effect
names), SpecialAttack (+17), TacticalManeuver (+7), SpecialWeapon (+8), Booster
(+50). It never names Generic, Maneuver, Explosion, Shield or StructureCount, and
the string Maneuver has 0 xrefs. One call site, from sub_821A6CF0, which is
itself called once and references the literal PlayerParams.
CORRECTION to unit-datasheet-static.md, which calls sub_822F9498 the
unit-definition loader. It loads one object: the player parameter table. Its
AA_/AV_ interleave still stands as a struct layout, but the struct is
PlayerParams's Booster record, not each unit's Maneuver. What loads the 114 unit
Maneuver records is now an open question -- it uses none of these strings.
Ranking all 114 units by how many of Booster's 50 values they reproduce: the top
five are exactly the five _Player units, all at 39/50; the sixth drops to 13/50;
none matches 50/50. The five players agree with each other on all 50 -- the three
player ships share one flight model. Booster stands alone on 10 fields; the
eleventh, AA_Yaw_Max, is 65.0 vs 65, formatting not a value (last iteration's
"11 differ" over-counted).
Still open, and sharper: nothing selects Booster. PlayerParams is loaded once,
unconditionally, no branch -- yet the measured flight tracks the unit Maneuver
(1.25/1.20/1.28) and misses Booster badly. Both cannot govern the same craft; the
next test is a runtime watch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
The consumer is sub_822F9498, the unit-definition loader the corpus already
documents. The field-name block 0x8209F300-600 (BulletTimeVolumePC, FireBirdPower,
ChargeMaximum, Turn180RequiredTime, ShieldDoubler_Time, ...) is 36/36 referenced,
every reference inside that one function.
REFUTED, my own label: "Booster is a new schema". Its 50 field names are a strict
subset of the unit Maneuver record -- 50/50 shared, 0 Booster-only.
CruisingVelocity occurs in exactly 115 records disc-wide: 114 Maneuver + this one.
The first check compared against Generic and scored 0/50, which read as brand new:
the wrong record of a multi-record object is not a control.
REFUTED: "Booster is the player craft's flight envelope". flight-speed-law.md
measured ~125/~420/~1530; unit Maneuver 100/350/1200 gives consistent ratios
1.25/1.20/1.28, Booster 100/612/2100 gives 1.25/0.69/0.73. Booster is a SECOND,
faster profile: 39 of 50 values identical to the player unit, and of the 11 that
move the three velocities scale by exactly x1.75 and two accelerations by x1.5.
BLOCKED: the AnalogRevice/Tweak reader. That schema is its own contiguous block at
0x820A119C-0x820A1378 headed by ControlTweakName -- 0 of 28 strings has a code
xref and no instruction anywhere names a 0x820A1xxx operand, against a control of
36/36 on the neighbouring block. Runtime watch needed.
Caught on the way: mov_stick_play/eye_stick_play exist twice; the referenced copy
belongs to sub_822AE628, which reads GP_HANGAR_ARSENAL's ControlTweak records (6 of
them, and none anywhere else) -- a clean, wrong answer if taken for ours.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
All six language copies carry the identical 337 unnamed IDXD hashes. Partitioned
by record-name shape (53 shapes), 333 map onto families the corpus already
documents: 131 weapon datasheets, 114 unit datasheets, 64 unit Faces tables, 10
unit message sets, 8 chatter rule tables, 5 enumerations/formations/placement.
114 is exactly the corpus's unit count (43 Craft + 71 Vessel) -- these are the
tables the corpus has always worked with, reached by SHAPE because they have no
names. Naming them adds nothing.
The Enumerate object in each GP_MAIN_GAME_* is EMPTY, zero fields, which is why
route 2 named 1283 entries in DefTables and 0 here.
The 4 unclassified objects are new -> docs/re/structures/player-tuning-tables.md:
the analog stick response curves (8 axes, 11 samples + a named Count = 11, tested
8/8; yaw/roll/throttle are the identity ramp, the shaping is all on pitch and the
camera axes; adv_yaw is non-monotone and unexplained), the player craft's flight
envelope (Booster, 50 fields -- the player side of the AA_/AV_ pair documented
for NPCs -- plus TacticalManeuver, SpecialAttack + three gauge bands,
SpecialWeapon, Misc), the Stage 16 boss (identified by Shell_S16Boss_* ids;
Guardian HP 65000, Core 42000), and one unidentified Generic naming eff_n0071.
New regenerator main_game_unnamed.py, 112-line artefact, byte-identical across
two runs; the other twelve artefacts verify unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE