# ✅ 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 == #_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. ## 🟡 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 (`0x00000000` / `0x00000001`) — a bit mask, bits unknown. * The 6 `no field` comparisons are one unit whose `StructureCount` omits every count.