Files
Sylpheed/docs/re/structures/unit-substructure-records.md
Sylpheed RE agent b9c6253db4 re: CoverArea is a 6-bit MOUNT mask; the three other turret leftovers closed
Six bits, never more.  Over all 835 turrets: 41 distinct values, max
0x3f, bits 6/7 set on none; per-bit 709/117/274/274/260/237.  0x00 (34)
= the empty Weapon_NULL hardpoints with YawLimit 0; 0x01 alone (439) =
craft hardpoints with YawLimit 2.5/1.0/0; 2-6 bits (362) = warship
mounts with YawLimit 45-180.

It tracks the MOUNT, not the weapon.  UN_e107_ADAN_AAFrigate has eight
identical AAFrigate_AAGun turrets: GN_GunXS_01..04 are 0x1d and 05..08
are 0x2d -- same gun, same YawLimit 120, different mask.  The Battleship
spreads five masks over GN_TGunL_01..05 / GN_TGunM_02..03, all firing
the same CAF_Ship_ASGun.

Which sector each bit denotes is NOT determined; six bits and the name
invite +-X/+-Y/+-Z or six hull faces, but nothing static fixes the
convention.  Not adopted.  Refuted on the way: bits 2 and 3 are not a
mutually-exclusive pair -- 188 turrets set both.

The 26 weapons no turret mounts are a coherent set: 11 _P player
variants, the nose/twin mounts, two _Child sub-munitions, the three
S16Boss_*, three Weapon_Test_*, and ADAN_Attacker_S_GunTurret.

Versatile_NNN ships nowhere -- 0 populated records across all 41
archives, only the ??? template row.

The two units one turret short: both extras are missile mounts with no
Frame.  Elan_EX4 = NoseGun + Missile, both mask 0x01, both frameless;
AAFrigate_EX4 = eight framed guns plus one Ship_AAMissile at 0x0c with
no Frame.  n=2, stated as the observed pattern, not a rule.

New artefact and regenerator; the other nine regenerate byte-identical.
2026-08-27 15:35:09 +00:00

193 lines
9.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 destructible-subsystem model — a unit's sub-records
The corpus has named the sub-records of a unit `.tbl` since
[unit-struct-runtime](unit-struct-runtime.md) ("`Generic`, `Maneuver`,
`Shield`, `Explosion`, `Mass`, `Effect`, `SE`, `Turret_00N` … all flattened
into that single object") but has never **opened** them. This does.
One unit = one IDXD pak entry. 114 of them
([the `Generic` partition](unit-datasheet-static.md)). Artefact
`../data/unit-substructures.txt`, regenerator
`tools/re-capture/unit_substructures.py`.
| family | records | max/unit | non-indexed fields |
|---|---:|---:|---:|
| `Turret_NNN` | **835** | 63 | 38 |
| `Generic` / `Maneuver` / `Effect` / `Shield` / `Mass` / `SE` / `Explosion` / `StructureCount` | 114 each | 1 | — |
| `NS_Body` | 68 | 1 | 12 |
| `ShieldGenerator_NNN` | 46 | 4 | 21 |
| `Thruster_NNN` | 38 | 4 | 21 |
| `Hatch_NNN` | 26 | 4 | 23 |
| `Bridge_NNN` | 25 | 2 | 20 |
Seven singletons named with a literal `???` (`Turret_???`, `Versatile_???`, …)
are the schema's own template rows.
## The shared destructible-part base
`Turret`, `ShieldGenerator`, `Thruster`, `Hatch` and `Bridge` are the **same
record shape** with per-kind extras. Every one of the 970 part records carries:
```
ID Name ParentStructureID Frame NomalModel CollisionModel Radius
HP IsDestructible IsShielded IsInvolved IsRadarVisible SpreadDamage
DamagedMotionName DestroyMotionName DestroyMotionTime
Effect_Explosion Effect_Flare Effect_Paralyze
```
(`NomalModel` is the game's own spelling.) `Frame` is a **mesh node name**
`GN_mnt` on the player craft — so a part is bolted to a named bone of the
`Generic.Model` mesh the corpus already decodes.
Per-kind, on top of that base:
| kind | extra fields |
|---|---|
| `Turret` | `WeaponID`, `AngularVelocity`, `YawLimit`, `PitchLimit_Elevation`, `PitchLimit_Depression`, `CoverArea`, `IsAuto`, `HasBarrel`, `CommonCannonModel`/`Frame`, `CannonModel_NNN`/`CannonFrame_NNN` (up to 80), `SequencingCount`, `SequencingInterval`, and on a few: `CurveAngle`, `FCSAngle`, `FollowView`, `ShellModel`, `CoverModel` |
| `ShieldGenerator` / `Thruster` / `Bridge` | `PowerRatio` — losing the part degrades the parent |
| `Thruster` | `NozzleCount` |
| `Hatch` | `SquadronID`, `LoadedCount`, `MaxAvailableCount`, `TakeoffInterval`**a carrier's launch bay** |
🔑 That is a complete component model: named bone, own mesh and collision mesh,
own HP and destruction motion + effects, and a `PowerRatio` saying what the
parent loses when it dies.
## The one-record blocks
| record | fields |
|---|---|
| `Shield` | `MaxValue`, `ChargeSpeed`, `ChargeDelay`, `ChargeDelay_Break` |
| `Mass` | `DryMass`, `GrossMass` |
| `SE` | `ExplosionSE`, `Thruster`, `SideThruster`, `JumpIn`, `JumpOut`, `ShipEnvironmentSE`, `LowerHPSE`, `LowerHPThresholdRatio` |
| `Explosion` | `Delay`, `DelayAdjustment`, `ExplosionFxModel`, `ExplosionMotionName`, `LowerHPFxModel`, `DestroyMotionName`, `DestroyMotionTime` |
| `StructureCount` | `TurretCount`, `BridgeCount`, `HatchCount`, `ShieldGeneratorCount`, `ThrusterCount`, `VersatileCount` |
| `NS_Body` | `Model`, `NodeCount`, `Interval`, `StartingVelocity`, `Deceleration`, `Volume`, `Color_R/G/B`, `AttenuationVolume`, `AttenuationAlpha`, `DeleteFadeSpeed` — an engine-trail ribbon, on 68 of 114 |
Every field the runtime doc reached for `Shield` (`MaxValue +0x238`,
`ChargeSpeed +0x244`) and `Mass` (`DryMass +0x274`, `GrossMass +0x278`) is
here, at the same names.
## 🧪 Control 1 — `StructureCount` counts the part records
`declared == #<Kind>_NNN records`, over 114 units × 6 kinds = 684:
| | |
|---|---:|
| equal | **612** |
| declared 0, one blank placeholder record | 55 |
| differs | **11** |
| kind absent from `StructureCount` | 6 |
The 55 are a real sub-rule, not slack: a kind with a count of **0 still emits
exactly one record**, and all 55 have empty `Name`, `NomalModel` *and* `Frame`.
So the file always carries at least one slot per kind.
Every one of the 11 exceptions is a **`Turret`**, and every one has
`declared < records` — never the other way round. Nine are the player's
DeltaSaber family:
```
declared=4 records=63 x7 f001_T, f001_T_EX5, f001_T_EX5_el, f001_T_Player,
f002_W, f002_W_Player, f004_A_Player
declared=4 records=5 x2 f001_T_Player_Ttrl1 / _Ttrl2
declared=1 records=2 x1 UN_e001_ADAN_Elan_EX4
declared=8 records=9 x1 UN_e107_ADAN_AAFrigate_EX4
```
**ADOPTED the next day** — on the player craft the `Turret_NNN` list is a
*hardpoint catalogue* and `TurretCount 4` is how many mount at once. The
Arsenal table proves it from an independent file: **59/59 of its items name a
`Turret_NNN` slot in `PlayerWeapon`**, see
[arsenal-item-weapon-chain](arsenal-item-weapon-chain.md). The two `+1`
warship/craft cases are still unexplained.
⚠️ The "10 distinct `WeaponID`s" measured here is the **AI** variant
`UN_f001_TCAF_DeltaSaber_T`. The `_Player` variants give the same 59 slots
**59 distinct** weapon records — wingmen fly a collapsed 10-class set.
## 🧪 Control 2 — turret weapons resolve into the weapon datasheet
**835 / 835 `Turret_NNN.WeaponID` values are an `ID` in the 131-record
`Weapon` table. Zero unresolved.** 105 distinct values are used; **26 of the
131 weapons are never mounted on a turret** — the player-only arsenal.
That is a hard cross-table link: [the weapon
datasheet](weapon-datasheet-static.md) is what arms every gun on every ship.
## ❌ Refuted in the same pass — "the 59 hardpoints are the 59-name arsenal roster"
The DeltaSaber's 63 turret slots hold **59 non-`Weapon_NULL`** entries, and the
`WEAPONS` roster in `GP_HANGAR_ARSENAL.pak` has exactly **59** names. Tempting,
and wrong: the two sets **overlap in 0 values**, and the 59 slots carry only
**10 distinct** `WeaponID`s. The roster is display names
(`Machiene_Cannon_MG1` — the game's spelling); the hardpoints reference
`Weapon.ID` (`Weapon_TCAF_DeltaSaber_Gun`). Two namespaces, one coincidence of
count.
## ✅ `CoverArea` is a 6-bit mount mask — and the other three leftovers
Artefact `../data/turret-coverarea.txt`, regenerator
`tools/re-capture/turret_coverarea.py`.
**Six bits, never more.** Across all **835** turrets: 41 distinct values, max
`0x3f`, **bits 6 and 7 set on none**. Per-bit counts 709 / 117 / 274 / 274 / 260
/ 237; popcounts 0…6.
| mask | n | what |
|---|---:|---|
| `0x00` | 34 | the empty `Weapon_NULL` hardpoints — `YawLimit 0` on all 34 |
| `0x01` (one bit) | 439 | craft hardpoints — `YawLimit` 2.5 / 1.0 / 0 |
| 26 bits | 362 | warship mounts — `YawLimit` 45…180, real pitch limits |
🔑 **It tracks the MOUNT, not the weapon.** `UN_e107_ADAN_AAFrigate` carries
eight identical `..._AAFrigate_AAGun` turrets: `GN_GunXS_01…04` are **`0x1d`**
and `GN_GunXS_05…08` are **`0x2d`** — same gun, same `YawLimit 120`, different
mask. `UN_f104_TCAF_Battleship` spreads five masks (`0x1d`, `0x39`, `0x35`,
`0x27`, `0x2b`) across `GN_TGunL_01…05` / `GN_TGunM_02…03`, all firing the same
`CAF_Ship_ASGun`.
🟡 **Which physical sector each bit denotes is NOT determined.** Six bits and a
field called *CoverArea* invite ±X/±Y/±Z or six hull faces, and the 0104 /
0508 split above looks like a side — but nothing static fixes the axis
convention. **A reading, not adopted.** ❌ Also refuted on the way: bits 2 and 3
are not a mutually-exclusive pair — 188 turrets set both.
### The 26 weapons no turret mounts
`Weapon.ID` 131, mounted 105, never **26** — and they are a coherent set, not
strays: **11 `_P` player variants** (`…_Beam_P`, `_Gun_P`, `_Bomb_P`, …),
the **nose and twin mounts** (`_NoseGun`, `_NoseGun_P`, `_NoseGun_None`,
`_NoseGun_Ttrl`, `_TwinGun`, `_TwinGun_P`), **two `_Child` sub-munitions**
(`wep_13`, `wep_29`), the **three `S16Boss_*`** weapons, three `Weapon_Test_*`,
and `ADAN_Attacker_S_GunTurret`.
### `Versatile_NNN` ships nowhere
**0 populated records across all 41 archives** — only the `???` template row.
The schema declares a sixth destructible-part kind with zero instances.
### The two units one turret short — both extras are MISSILE mounts with no frame
* `UN_e001_ADAN_Elan_EX4`: `TurretCount 1`, two records — `Turret_000`
`…Turret_NoseGun` and `Turret_001` `…Turret_Missile`, both mask `0x01`, both
with an empty `Frame`.
* `UN_e107_ADAN_AAFrigate_EX4`: `TurretCount 8`, nine records — eight
`GN_GunXS_0N_ContH` gun turrets, plus one `…Ship_AAMissile` at mask `0x0c`
**with no `Frame` at all**.
🔑 In both, the uncounted extra is a **missile launcher with no mesh node** — so
`TurretCount` counts frame-mounted guns, and a frameless launcher rides in the
same record family without being counted. Consistent across both, n=2, so
**stated as the observed pattern, not a rule**.
## 🟡 Not settled
* What links a roster entry to a `Weapon.ID` — the 59-of-131 question is still
open, and this pass shows the answer is **not** the hardpoint list.
* `Versatile_NNN` has 0 populated records in this pak; only the `???` template
row exists, so its schema is known and its instances are not.
* ~~`CoverArea` reads as a hex word — a bit mask, bits unknown.~~ **Opened
above**: a 6-bit mount mask. Which sector each bit means is still open.
* The 6 `no field` comparisons are one unit whose `StructureCount` omits every
count.