# ✅ 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. ## ✅ `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 | | 2–6 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 01–04 / 05–08 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. ## ✅ Every effect a datasheet binds, against every effect the LOD tables declare The `Explosion` substructure's field list is already above; what was untested is whether its `ExplosionFxModel` — and the whole effect-binder family — actually *resolves*. Run as a declared × used partition, sweeping **34 binder field names** (`*FxModel*`, `*EffectName`, `Effect_*`, `ShellModel`, `CoverModel`, `SilhouetteModel`) against every name declared by a `LOD_Effect_` or `GameModel_` field anywhere on the disc: | | declared | not declared | |---|---|---| | **used** | **172** | **58** | | **not used** | **318** | — | 490 declared, 230 used. The top binders are `Effect_Paralyze` (2 874 values), `ShellModel` (996), `HitFxModel` (738), `JetFxModel_000` (408), `MuzzleFlashFxModel_Loop` (384). 🔑 **The 58-cell is almost one field.** 53 of the 58 are bound by **`SilhouetteModel`** alone, and all 53 are `rou_f###_wep*` names — the arsenal item silhouettes already documented in [[arsenal-item-weapon-chain]]. They are undeclared because they are **the wrong kind**: each resolves as its own standalone package, e.g. `rou_f001_wep_01.xpr`, and never as a LOD-table entry. Nothing is missing — the sweep was reading an asset-file name as if it were an effect name. That leaves **four genuinely undeclared effects** — `eff_e0044` (`ExplosionFxModel`), `eff_f0002` + `eff_f0002_barn` (`JetFxModel_00N` / `AfterBurnerFxModel_00N`) and `eff_h308` (`HitFxModel`) — plus one placeholder (below). None of the four resolves as a record name or a field name either. ⚠️ Controls, both from the previous iteration and both reproduced: `eff_n0071` is **declared and used 6×** (= one user, per the per-pak-copy rule); `eff_e0033` is **declared and used 0×**, i.e. it sits in the 318-cell. The 318 is expected rather than alarming — the `EnumLODSet`/`EnumGameModel` family is overwhelmingly *equipment*, so most of what it declares no unit datasheet binds. ### 🔑 A Shift-JIS dev placeholder: `文字列` One "effect name" in the 58-cell is not a name at all. Bound by `Effect_Explosion` and `Effect_Flare`, **72 occurrences = 12 users**, its bytes are `95 B6 8E 9A 97 F1` — Shift-JIS for **`文字列`**, literally *"character string"*. It is the placeholder a developer left in the field. ⚠️ **Reader defect this exposes:** `tools/re-capture/unitgroup.py` hands the value back as Latin-1 mojibake (`¶ñ`), so an IDXD string field can carry **Shift-JIS** and our decode does not know it. Worth fixing before any of these strings reach a port; harmless for name-matching, because every other value in the corpus is ASCII. ### ✅ `_hangar` *is* a package-naming convention — 59 of 166, zero partials Following the silhouettes into the ISO tree: **59 of the 166 `.xpr` packages end `_hangar.xpr`, and 59 of 59 have a bare twin** of the same stem. So a model shown in the hangar ships as a second package beside the in-flight one. This is the direct contrast to the previous iteration's refutation — `_all` / `_child` was **1** of 166 with one stem and is not a convention; `_hangar` is **59** of 166 with zero partials and is. Suffix conventions in this corpus are worth testing precisely because they are not all real. ### ❌ An `.xpr` byte search cannot answer "does this effect ship" Testing the four genuinely-undeclared effects against the 166 `.xpr` packages gave `eff_f0002` and `eff_f0002_barn` in `Base.xpr`, and nothing for `eff_e0044` or `eff_h308`. **But the control refutes the instrument, not the effects:** `eff_n0071` — measured *live*, bound by an `Explosion` record's `ExplosionFxModel` — **also returns nothing** from the same search. A known-live name the test cannot find means the test has no power here. So: * `eff_f0002` / `eff_f0002_barn` **do** have a mesh in `Base.xpr` (presence is still evidence when it is positive); * nothing follows about `eff_e0044` or `eff_h308`, and the earlier remark that `eff_e0058`, `eff_e0059` and `eff_e0060` "have no mesh" should be read the same way — **absent from a byte search over `.xpr` is not absent from the disc**. Whatever ships `eff_n0071` is not a plain name string inside those 166 files, so effect meshes must be reached some other way. Finding that route is the open question; until then, only the positive half of this test counts. ### 🔑 Effects live in two families, split by digit-width — `ptc_pack.xpr` holds one The open question from the last iteration was how an effect mesh is reached at all. The corpus already had the pointer: [[xbg7-mesh]] mentions **`ptc_pack.xpr`** and its `eff_*` entries. It is a 20 MB shared particle package in `hidden/resource3d/`, and it names **532 distinct `eff_*` resources** — 527 of them with a **three-digit** number, 3 with four digits, 2 unnumbered. Joining it against the effect names the datasheets actually bind: | bound name | in `ptc_pack` | not | |---|---|---| | **3-digit** (`eff_h308`, `eff_m…`) | **71** | 39 | | **4-digit** (`eff_e0033`, `eff_n0071`) | **0** | **27** | 🔑 **Zero of the 27 four-digit names resolve in `ptc_pack`, and the four-digit series is a closed three-letter set** — `e` (10), `f` (12), `n` (5). Meanwhile `eff_e0033` *was* found in `Base.xpr`. So the disc carries **two effect families**: a shared three-digit particle library in `ptc_pack.xpr`, and a four-digit series that lives in the per-model and base packages instead. ⚠️ **The instrument passes its own control this time.** The previous iteration's `.xpr` byte search was refuted because a known-live name returned nothing. Here the same kind of search **demonstrably reads names out of this file — 532 of them** — so a zero *within `ptc_pack`* is meaningful in a way the earlier disc-wide zero was not. That is the difference between a search that has been shown to work on the population being searched and one that has not. Among the 39 three-digit misses the letters cluster hard: `j` 22, `t` 14, `m` 2, `h` 1 — and `ptc_pack` contains just **one** `j` name against 149 `m` and 81 `s`. So `eff_j###` is a third grouping that is almost entirely elsewhere. 🟡 This does not yet close the question — 66 of the 137 bound effects still have no located home, `eff_n0071` among them. But the route is now a real one with a number on it, and the next step is the `eff_j###` family and the four-digit series rather than another disc-wide grep. ### ✅ The effect→package map — 103 of 137 located, and a correction Enumerating `eff_*` names **per `.xpr`** across all 166 packages and matching the bound names **exactly** gives a real home for **103 of the 137**, up from the 71 `ptc_pack` alone accounted for. Only **36** packages carry an effect name at all, and two dominate: | package | bound effects it holds | |---|---| | `Base.xpr` | **53** | | `ptc_pack.xpr` | **46** | | `Stage_S28.xpr` | 2 | | five `rou_f001_wep_NN.xpr` | 1 each | 🔑 **There are two shared effect libraries, not one.** `ptc_pack.xpr` is the particle library the previous iteration found; **`Base.xpr` is the larger home** for bound effects. `ptc_pack.xpr` is also the *only* `*_pack` library on the disc — no other shared bundle exists. By digit-width: **3-digit 80 resolved of 110, 4-digit 23 of 27.** So the previous iteration's reading survives and sharpens — the four-digit series really does live outside `ptc_pack` (that zero stands), and now we can say where: `Base.xpr`. ### ❌ Correction: `eff_f0002` does **not** ship — that was a substring hit The previous iteration reported `eff_f0002` and `eff_f0002_barn` as present in `Base.xpr`. **Both were substring artefacts.** What the file actually contains is **`eff_f0002_barnhaze`** — one longer resource name that `grep -l eff_f0002` and `grep -l eff_f0002_barn` both match inside. Neither bound name is there. This is the corpus's own paid-for lesson arriving from the other direction: a prefix hit is not an exact hit (cf. `rot_n001` vs `rot_n001_break`), and here it was the *bound* name that was a prefix of the *stored* one. The map above is exact-keyed, so it does not have this failure; the earlier positive is withdrawn. ### 🟡 The 34 that remain The unlocated residual is dominated by a family the previous iteration did not single out — **`eff_l###` with 17 of the 34** — then `eff_h###` 4, `eff_s###` 4, `eff_j###` 2, `eff_m###` 2, `eff_t###` 1, and four four-digit names (`eff_e0044`, `eff_f0002`, `eff_f0002_barn`, `eff_n0071`). ⚠️ Note the scope shift: the `j` 22 / `t` 14 clustering reported last time was the residual **against `ptc_pack` alone**; against *all* packages those families are largely accounted for and `l` is what is left. Both numbers are right for their own population — which is the reason to say which population a residual is measured against. Regenerate with `tools/re-capture/effect_homes.py` → [`docs/re/data/effect-homes.txt`](../data/effect-homes.txt), which lists all 34 by name. ### ✅ `_e` / `_f` on an effect name is the FACTION of the unit that binds it Chasing the 17 unlocated `eff_l###` turned up their shape: they come in **`_e`/`_f` pairs** — `eff_l101_e` + `eff_l101_f`, and the same for `l102`, `l104`, `l105`, `l106`, `l201`, plus `_e`-only `l010`, `l011`, `l107`, `l108` and `_f`-only `l002`. Partitioning every `eff__` binding by the **ID letter of the owning unit** (one `GP_MAIN_GAME_*` pak = one user): | | effect `_e` | effect `_f` | |---|---|---| | **`UN_e###`** | **33** | 0 | | **`UN_f###`** | 0 | **61** | **94 of 94 agree; both off-diagonal cells are empty.** And the unit IDs name the factions themselves — the control reads `UN_e### → ADAN` (42 objects), `UN_f### → TCAF` (26), `UN_n### → TTRL` (2, tutorial, binding neither). 🔑 So an effect ending `_e` belongs to an **ADAN** ship and one ending `_f` to a **TCAF** ship: the same visual effect is authored twice, once per faction. That is why the `eff_l###` family arrives in pairs. ### 🟡 What binds the 34 unlocated, and what they are The 34 are not scattered — they are one job. `Generic` binds 32 of the 34, `Explosion` 19, `Shell` 9, and two apiece for `Level_0` and `Weapon`. The binder fields rank **`LowerHPFxModel` 252**, `HitFxModel` 144, then `JetFxModel_00N` and `AfterBurnerFxModel_00N` at 48/30 each. They sit in the six `GP_MAIN_GAME_*` paks (130 bindings each — one user) plus 32 in `DefTables.pak`. `LowerHPFxModel` is the damaged-ship effect, so **the unlocated set is largely the per-faction battle-damage and hit visuals**. None of the 34 resolves as a record name and only one as a field name, so they are asset references, not records. ⚠️ **Still unlocated as assets.** Knowing what they are for does not say where their geometry lives; the `.xpr` route is exhausted for them and the parsed pak payloads hold references, not meshes. Worth stating plainly: this iteration identified the family and its naming law, not the missing package. ### ✅ The faction law holds across all six paks and four fields — 564 of 564 The previous section measured `_e`/`_f` on one pak and only through the effect binders. Widening to **every string field** of every unit object in **all six** `GP_MAIN_GAME_*` paks: | | value `_e` | value `_f` | |---|---|---| | **`UN_e###`** | **198** | 0 | | **`UN_f###`** | 0 | **366** | **564 of 564 agree; the mismatch residual is empty.** And the law is not confined to one field — it holds separately, at 100%, in each of four: | field | agree | |---|---| | `LowerHPFxModel` | 252 of 252 | | `ShieldHitEffectName` | 210 of 210 | | `ShieldRecoverEffectName` | 84 of 84 | | `ExplosionFxModel` | 18 of 18 | Two of those — `ShieldHitEffectName` and `ShieldRecoverEffectName` — were not in the earlier measurement at all, so the law reaches further than the `*FxModel` family that suggested it. ⚠️ **But state the scope exactly: this is a law about *effects*, not about assets in general.** The sweep covered every field, and *every* `_e`/`_f`-suffixed value a unit binds turns out to live in those four effect fields. No model, motion or SE value carries the suffix at all — so the faction pairing is **not** shown for those kinds; there was simply nothing to test. "General across fields" here means general across the fields that use it. ✅ **`UN_n###` (TTRL) binds no `_e`/`_f` value in any of the six paks** — 12 objects (= 2 users) with nothing on either side, confirming the earlier single-pak observation over the whole population. ⚠️ **A correction to the ID grammar, and a reconciliation.** The earlier section counted 42 + 26 + 2 = 70 unit objects using a regex that required `UN_###__`; the looser `UN_###_` finds **71**. The one extra is **`UN_e910_core_ADAN_GeneratorCore`**, which inserts a sub-kind token *before* the faction tag. So the ID grammar is `UN_###_[_]_`, and both counts were right for their own pattern. ### ❌ `ptc_pack` holds 727 names, not 532 — an `EF_IDX_` prefix the pattern truncated Censusing the package's own naming vocabulary turned up a third variant of the prefix trap, and this one had been corrupting a number the corpus carried. **268 of `ptc_pack`'s names do not start with `eff_`** — they start with **`EF_IDX_`**, e.g. `EF_IDX_eff_d001_f`. A regex anchored at `eff_` chops that prefix off and merges distinct names, which is where the earlier figure of 532 came from. Enumerating **maximal `[A-Za-z0-9_]` runs** gives **727**. ⚠️ The two earlier traps were a *stored* name being longer (`rot_n001_break`) and a *bound* name being a prefix (`eff_f0002` ⊂ `eff_f0002_barnhaze`). This is the third: **a prefix the pattern cannot see at all**, because the anchor sits in the middle of the real name. ### ✅ The map improves to 128 of 137 — and a claim of mine is withdrawn Looking each bound name up **bare and under `EF_IDX_`** resolves **25 of the 34** that were unlocated. The map is now: | package | bound effects held | |---|---| | **`ptc_pack.xpr`** | **71** | | `Base.xpr` | 53 | | `Stage_S28.xpr` | 2 | | five `rou_f001_wep_NN.xpr` | 1 each | ❌ **This withdraws the previous section's correction.** I had recorded that `Base.xpr` holds *more* bound effects than `ptc_pack` (53 vs 46) and struck out "`ptc_pack` is the effect library". With the prefixed keys counted, `ptc_pack` holds **71** — it *is* the larger library, and the 46 was an undercount from the same truncating pattern. Two shared libraries remains right; which is bigger does not. 🟡 **Nine names remain unlocated**, small enough to print in full: `eff_e0044`, `eff_f0002`, `eff_f0002_barn`, `eff_h308`, `eff_j002_e01`, `eff_j002_e02`, `eff_m010_wep_85`, `eff_m011_wep_85`, `eff_n0071`. ### ✅ The suffix vocabulary, and `_root` as the outermost token Splitting all 727 names on `_` gives 106 distinct tokens. The frequent ones are `IDX` 223 (the prefix above), `_f` 137, `_e` 119, `_root` 87, `_col` 54, `_mdl` 45, `_break` 43, `_ring` 38, `_ALL` 17, `_haze` 14, `_thunder` 10 — a mix of one structural marker, the faction pair, and descriptive part names. ⚠️ Method note: that census counts **all** tokens, not trailing ones — which is exactly how the `EF_IDX_` *prefix* surfaced in what I first labelled a suffix list. The mislabel found the bug. Testing the two structural candidates the way `_hangar` was tested — does the suffixed name have a bare parent? | token | names | bare parent present | |---|---|---| | `_ALL` | 17 | **17 of 17** | | `_root` | 87 | 64 of 87 | | `_break` | 30 | 15 of 30 | | `_e` | 74 | **0 of 74** | | `_f` | 61 | **0 of 61** | 🔑 **`_root` is strictly terminal — 87 of 87, and `_root` never appears mid-name.** The compound shapes place it outermost: `_e_root` 19, `_f_root` 18, `_break_root` 13, bare `_root` 30. So the order is `_[|]_root`, and `_root` reads as a hierarchy marker rather than a variant — though 64 of 87 having a bare parent means it is not simply "the parent of an existing node", and `_break` at 15 of 30 is likewise not a plain "destroyed twin of every effect". 🔑 **`_e`/`_f` never have a bare parent — 0 of 135.** That is independent, asset-side confirmation of the faction law: an effect is authored per faction, and there is no faction-neutral original for either side to derive from. ### ✅ The last 9 are genuinely unshipped — a controlled zero over seven prefixes `EF_IDX_` proved prefixes exist, so the residual deserved the same treatment across every package rather than one. Censusing prefixes over all 36 effect-carrying `.xpr` gives **seven** in use: | prefix | names it carries | |---|---| | `EF_IDX_` | 223 | | `mdl_` | 45 | | `EF_IDX_mdl_` | 45 | | `VolumeLine_` | 10 | | `GN_` | 10 | | `GN__` | 6 | | *(bare)* | the rest | Testing all nine unlocated names against all seven: **0 of 9 resolve.** ⚠️ **This zero has force where the earlier disc-wide zero did not**, and for the stated reason: the control shows each prefix genuinely carries names the same search reads — 223, 45, 45, 10, 10, 6. The instrument demonstrably works on the population it is being asked about. So the nine are **bound by a datasheet field and shipped in no package**: `eff_e0044`, `eff_f0002`, `eff_f0002_barn`, `eff_h308`, `eff_j002_e01`, `eff_j002_e02`, `eff_m010_wep_85`, `eff_m011_wep_85`, `eff_n0071`. ### 🔑 Two of the nine belong to `wep_85` — a declared weapon with no package `eff_m010_wep_85` and `eff_m011_wep_85` name a weapon, and the weapon is real in the data: **`Weapon_DSaber_P_wep_85_Beam` (24 uses)** with its `Shell_`, `WeaponCannonName_` and `WeaponShellName_` siblings, plus `GameModel_eff_m010_wep_85` / `_m011_` declaring the two effects. But **the weapon packages stop at 84**. `hidden/resource3d/` holds **59** `rou_f001_wep_NN.xpr` files numbering 00–84 with gaps, and **no `wep_85`**. So `wep_85` is a **declared-but-unshipped weapon**, and its two effects go missing with it — one cut asset explaining two of the nine. ⚠️ This does not contradict "every weapon is placed — 131 = 105+22+0+4" ([[weapon-struct-runtime]]): that partition is declared × *mounted in a loadout*, which is a different question from whether a package ships. 🟡 **Seven remain with no account**: `eff_e0044`, `eff_f0002`, `eff_f0002_barn`, `eff_h308`, `eff_j002_e01`, `eff_j002_e02`, `eff_n0071`. The `.xpr` route is now exhausted for them under every prefix the disc uses.