This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/re/structures/unit-datasheet-static.md
Claude (auto) 1a1a8d98d9 re: the AA_/AV_ selection is BLOCKED for static RE, with the mechanism
Route 3 (call-graph bound): sub_821A6CF0 and sub_821AB650 each read 196/200/204
off THREE different base registers -- three unrelated objects, not the block.

Route 4 (data-flow bound): the definition object lives in the global at
0x828F358C; 18 functions touch it, 9 also touch block offsets.  The two best
are refuted -- sub_8230D1F8 is the rank/score loader storing
CraftScore_Adjustment, FFPenalty_Zessel_Maximum and RankScore_S/A/B at exactly
256/320/324/328/332, and sub_82398CC0 uses r19 as a float-constant pool.

So the offset region is shared by two unrelated objects and a constant pool:
offset-based discrimination is contaminated by construction, which is why it
has now failed three times.  Catching the selection needs a runtime watch.

Side finding, unowned by the corpus: sub_8230D1F8 is the rank/score loader.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 18:07:42 +00:00

198 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ✅ 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-speed-law.md),
[flight-controls-runtime](../flight-controls-runtime.md),
[unit-struct-runtime](unit-struct-runtime.md).
> ## ❌ CORRECTION (2026-08-27) — `Generic` is a per-FILE header, not a table
>
> This page first read "`Generic` (394 per pak) is the unit datasheet". Wrong:
> **only 114 of the 394 are.** `Generic` is 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 six
> `GP_MAIN_GAME_*.pak` identically:
>
> | `Generic` shape | per pak | what the file is |
> |---|---:|---|
> | has `HP` | **114** | **a unit datasheet** (`Maneuver`, `Effect`, `Shield`, `Mass`, `SE`, `Explosion`, `Turret_00N` siblings) |
> | `{Count}` only | 204 | a **dialogue file** — `Count` is the number of `Message_NNN` siblings |
> | `{ID, Name, SideID, Unique}` | 64 | a **character** (`Faces` sibling) — 36 `TCAF` + 28 `ADAN` |
> | `{EnumAsteroidGroup}` | 10 | an asteroid-group file |
> | 0 and 1 field | 2 | one empty, one naming `eff_n0071` |
>
> 204 + 114 + 64 + 10 + 2 = 394, and **178 distinct `Generic.ID` = 114 unit +
> 64 character** — the only two shapes that carry an `ID`. Positive control in
> the same loop: for the 204 dialogue headers, `Count` equals the number of
> `Message_NNN` siblings **204 / 204**, no mismatches.
>
> 🔑 The 114 is not a coincidence: `Maneuver` = 114, `Effect` = 114, the entry
> sets are **identical**, every unit `Generic` has a `Maneuver` sibling, and
> `Generic.Type` splits **43 `Craft` + 71 `Vessel`** — reproducing the count
> [unit-struct-runtime](unit-struct-runtime.md) reached from the other side.
> Artefact `../data/generic-record-partition.txt`, tool
> `tools/re-capture/generic_partition.py`.
| record | per pak | field names | distinct `ID`s |
|---|---|---|---|
| `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)
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_8230D1F8` is 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
for `AA_Roll_Min` and the four `AxisMode` fields.
* `sub_82398CC0` uses `r19` as a **float-constant pool** (`lfs f0, 320(r19)`
alongside `272(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_ID` turned out **not** to be ship stats: they sit on records
named for wingmen and ship classes (`CARL`, `ELLEN`, `SANDRA`, `ACROPOLIS`,
`Carrier_TCAF1`, `NP_Facility`, …), 120144 instances each, alongside
`HPGauge` / `HPGaugeSub` / `RadarCursorType` on the `ObjectiveMarker_*` and
`TutorialMarker_*` records — a **HUD gauge binding table**, not a datasheet.
* ~~178 distinct `Generic` IDs against 394 records per pak — the surplus is
unexplained here.~~ **Settled 2026-08-27 — see the correction at the top.**
* The `Generic` field-name count of 110 is a POOL across all four shapes; no
single record carries more than 67.