Files
Sylpheed/docs/re/structures/unit-datasheet-static.md
Claude (auto) 4ffbd39a01 re: AA_/AV_ are one interleaved block; the selection is still unfound
sub_822F9498 (the unit-definition loader, called only from sub_821A6CF0) is the
only function referencing the AA_/AV_ name strings.  Mapping each name to the
stfs that follows gives an exact interleave: AV_ at X, AA_ at X+8, for all five
axes -- PitchPlus 196/204, PitchMinus 212/220, Yaw 228/236, Roll 244/252,
AxisMode 320/328.  A selector is an offset of 0 or 8, not two lookups.

The 20 strings exist twice in the image; the first block is referenced by
nothing.  Data control: the ten suffixes match exactly between families.

The selection stays a reading: functions loading two or more of 196/204/320/328
number 39 image-wide, and only two are call-graph-reachable from the loader.

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

174 lines
8.2 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 still not found
Looking for the consumer by offset does not work here: functions loading two or
more of `+196`/`+204`/`+320`/`+328` number **39** across the image, and those
offsets are far too common to identify anything — the same trap the corpus has
paid for before. Only two of the 39 are call-graph-reachable from the loader's
owner (`sub_821A6CF0` itself, and `sub_821AB650`); that is the next handle, and
it was not followed this iteration. **So "anti-air vs anti-vessel" remains a
reading.**
## `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.