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
11 KiB
✅ The STATIC unit datasheet and AI flight model — Generic, Maneuver, Effect
Found the same way as the weapon tables: IDXD field names are literal strings in the pool, so a field-shape search over all 190 782 records finds the carriers without guessing a filename. This is the static source for what the corpus had only measured at runtime — flight-speed-law, flight-controls-runtime, unit-struct-runtime.
❌ CORRECTION (2026-08-27) —
Genericis a per-FILE header, not a tableThis page first read "
Generic(394 per pak) is the unit datasheet". Wrong: only 114 of the 394 are.Genericis the record name every IDXD file gives its own header record — exactly one per pak entry — and its schema is set by what kind of file it is. Partitioned by field set, in each of the sixGP_MAIN_GAME_*.pakidentically:
Genericshapeper pak what the file is has HP114 a unit datasheet ( Maneuver,Effect,Shield,Mass,SE,Explosion,Turret_00Nsiblings){Count}only204 a dialogue file — Countis the number ofMessage_NNNsiblings{ID, Name, SideID, Unique}64 a character ( Facessibling) — 36TCAF+ 28ADAN{EnumAsteroidGroup}10 an asteroid-group file 0 and 1 field 2 one empty, one naming eff_n0071204 + 114 + 64 + 10 + 2 = 394, and 178 distinct
Generic.ID= 114 unit + 64 character — the only two shapes that carry anID. Positive control in the same loop: for the 204 dialogue headers,Countequals the number ofMessage_NNNsiblings 204 / 204, no mismatches.🔑 The 114 is not a coincidence:
Maneuver= 114,Effect= 114, the entry sets are identical, every unitGenerichas aManeuversibling, andGeneric.Typesplits 43Craft+ 71Vessel— reproducing the count unit-struct-runtime reached from the other side. Artefact../data/generic-record-partition.txt, tooltools/re-capture/generic_partition.py.
| record | per pak | field names | distinct IDs |
|---|---|---|---|
Generic (unit shape) |
114 | 67 max on one record | 114 |
Generic (all shapes) |
394 | 110 pooled | 178 |
Maneuver |
114 | 101 | — |
Effect |
114 | 9 | — |
Repeated identically in every per-language GP_MAIN_GAME_*.pak. A unit's
Generic, Maneuver and Effect share a pak entry — one entry is one unit's
.tbl, which is why the runtime flattens all of them into a single object.
⚠️ Generic is the game's own record name, not a placeholder — it is also one of
the 65 section names in data/config-keys.txt.
Generic — the unit datasheet
Sample UN_e006_ADAN_Vindicator_MargrasF:
ID / Name / Type Craft / UncertainName Craft
HP 6400.0 ShieldRatio 1.0 DefencePoint 0.0004
AttackCraftPoint 1.0 AttackVesselPoint 0.001
ResistanceToShell 0.1 ResistanceToExplosion 0.5 ResistanceToOptics 0.8
ResistanceToPlayer 1.0 ResistanceParalyze 0.99
Size_X/Y/Z 12.0 / 5.0 / 30.0 Size_Radius 15.0
RadarRange 10000.0 FCSRange 8500.0
MountedFCS No MountedShieldGenerator Yes
ScorePoint 4000 DamageScore 640 MassScore 30
HQRatio 1.0 ThrusterRatio 1.0 IsDestructible Yes
Model rou_e006 CollisionModel Color_R/G/B 1.0 / 0.5 / 0.1
NozzleCount 3 + per-nozzle Jet / AfterBurner / ReverseThruster / Contrail
Fx model & frame names, NozzleSpec_00N
🔑 Model rou_e006 ties the datasheet straight to the mesh names the corpus
already decodes, and Size_* / RadarRange / FCSRange are in the corpus's
1 metre world unit — a 10 km radar, an 8.5 km fire-control range, a 12 × 5 ×
30 m craft.
Maneuver — the AI flight model
The single richest find for the port. Sample, same unit:
MaximumVelocity 1200 MinimumVelocity 100.0 CruisingVelocity 700
Acceleration 600 Deceleration 400
SideThrustAcceleration 1000.0 SideThrustVelocity_Max 1000.0
Turn_AngularVelocity 180.0 MaximumBank_Normal 60.0
YawDragFactor / RollDragFactor / PitchDragFactor 3.0
DragFactorThreshold 0.5
ArterBurner_Vc 2.5 ArterBurner_Acc 2.0 (the game's own spelling)
ReverseThrust_Vc -0.5 ReverseThrust_Acc 2.0
UsingChaffRatio 0.5 HomingResistAdjustment 0.8
and a set of named manoeuvres, each a Yes/No switch plus its own timing, ratio and length bounds:
TurnAttack + _CutoffRatio _DoubleRatio _DoubleTimeMin/Max
TurnAway + _Time_Minimum/Maximum
BarrelRoll + _CountMinimum/Maximum _Radius _Time
SideRoll + _Time _Length
Slalom + _CutoffRatio _TurnCount_Min/Max
Through + _Angle/Length Minimum/Maximum _Time1/2 Min/Max _CutoffRatio
HoldPosition + _MinimumTime _MaximumTime _CancelTime _CutoffRatio
_BackRatio _SideRatio _LengthMin/Max
SolidCutoff + _LengthMin/Max _Ratio
BoostAway + _Time_Minimum/Maximum
CutoffTimeMin/Max
AA_* and AV_* Pitch/Yaw/Roll Min/Max pairs
🟡 The AA_ / AV_ prefixes pair with AttackCraftPoint / AttackVesselPoint,
so anti-air vs anti-vessel rate limits is the obvious reading — still not
adopted. See below for what the executable does say.
✅ The two families are one interleaved block (2026-08-27)
🔴 Corrected 2026-08-27 —
sub_822F9498is not the unit-definition loader. Every one of the 90 strings it references is a field or record name of thePlayerParamsobject (Misc/SpecialAttack/TacticalManeuver/SpecialWeapon/Booster); it never namesGeneric,ManeuverorStructureCount, andManeuverhas 0 xrefs. It is called once, fromsub_821A6CF0, which references the literalPlayerParams. The interleave below is real — but it is the layout ofPlayerParams'sBoosterrecord, not of each unit'sManeuver. Seeplayer-tuning-tables.md. What loads the 114 unitManeuverrecords is now an open question.
Exactly one function in the image references the AA_/AV_ name strings:
sub_822F9498, the unit-definition loader, itself called only from
sub_821A6CF0. Mapping each addi rN, r29, -offset (the name) to the stfs
that follows gives the definition-object layout, and it is a clean interleave —
AV_ at X, AA_ at X+8, for all five axes:
| axis | AV_Max / AV_Min |
AA_Max / AA_Min |
|---|---|---|
PitchPlus |
+196 / +200 |
+204 / +208 |
PitchMinus |
+212 / +216 |
+220 / +224 |
Yaw |
+228 / +232 |
+236 / +240 |
Roll |
+244 / +248 |
+252 / +256 |
AxisMode |
+320 / +324 |
+328 / +332 |
The constant +8 displacement is the structural point: whatever selects
between the families does it as an offset of 0 or 8, not as two separate
lookups. The loader reads 78 named float fields in total.
Data side: the ten suffixes match exactly between the two families (checked
as a list equality, not a count), with 690 records each for the eight rate fields
and 36 each for AxisMode. Where the values differ, AA_ is consistently the
larger — AA_AxisMode_Min 100 vs AV_ 35, AA_Yaw_Max 50 vs AV_ 25,
AA_PitchPlus_Min 100 vs AV_ 200 being the exception that keeps this a
description, not a rule.
⚠️ The 20 strings exist twice in the image, at 0x82085B98… and
0x8209F674…. The loader uses the second block; the first 20 are referenced by
nothing.
🔴 The selection is BLOCKED for static RE — four routes, all with controls
| route | result |
|---|---|
| 1. string xref on the field names | ✅ only sub_822F9498 references them — this gave the layout above, and nothing else in the image touches an AA_/AV_ string |
2. unbounded offset intersection (+196/+204/+320/+328) |
🔴 39 functions image-wide; pure coincidence |
3. call-graph bound — the loader's owner sub_821A6CF0, and sub_821AB650 |
🔴 both read 196/200/204 off three different base registers in three consecutive instructions: three unrelated objects contributing one float each, not the block |
4. data-flow bound — the definition object lives in the global at [0x828F358C] (lwz r3, 13708(r26) feeds the loader); 18 functions touch it, 9 also touch block offsets |
🔴 the two best are both refuted, below |
Why route 4's best candidates fail, and it is instructive:
sub_8230D1F8is a different loader entirely — a scoring table. Resolving its field names the same way gives+256 CraftScore_Adjustment,+320 FFPenalty_Zessel_Maximum,+324 RankScore_S,+328 RankScore_A,+332 RankScore_B. It stores at the very offsets the definition object uses forAA_Roll_Minand the fourAxisModefields.sub_82398CC0usesr19as a float-constant pool (lfs f0, 320(r19)alongside272(r19)and-656(r19)), not as a definition pointer.
So the offset region is shared by at least two unrelated objects and by a constant pool. That is why every offset-based discriminator has failed here: the contamination is structural, not bad luck.
🔴 Marking this blocked for static RE. The selection would have to be caught
at runtime — a watch on the definition object's +196…+332 during flight —
which is emulator work, not a disc or image read. "Anti-air vs anti-vessel"
stays a reading, as unit-datasheet-static.md has always had it; what is new
is the layout, and that the selector must be a +0/+8 offset.
Side finding, unowned by the corpus: sub_8230D1F8 is the rank/score
table loader — RankScore_S/_A/_B, FFPenalty_Zessel_Maximum,
CraftScore_Adjustment. Nothing in docs/re/ mentions those names. Not pursued
here.
Effect
ShieldHitEffectName, ShieldRecoverEffectName, Effect_Paralyze, plus
JumpIn / JumpOut / FadeIn / FadeOut slots.
🟡 Not settled
- No cross-check against the runtime captures — this locates and reads the tables; it does not reconcile them field by field.
HP_CLASS/HP_IDturned out not to be ship stats: they sit on records named for wingmen and ship classes (CARL,ELLEN,SANDRA,ACROPOLIS,Carrier_TCAF1,NP_Facility, …), 120–144 instances each, alongsideHPGauge/HPGaugeSub/RadarCursorTypeon theObjectiveMarker_*andTutorialMarker_*records — a HUD gauge binding table, not a datasheet.178 distinctSettled 2026-08-27 — see the correction at the top.GenericIDs against 394 records per pak — the surplus is unexplained here.- The
Genericfield-name count of 110 is a POOL across all four shapes; no single record carries more than 67.