Chasing the 17 unlocated eff_l### turned up their shape first: 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_<letter><digits>_<e|f> 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 and both off-diagonal cells are empty. The control reads the
factions straight off the IDs: 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 is
authored twice, once per faction, which is exactly why eff_l### arrives in pairs.
What the 34 unlocated ARE is now also clear, even though where they live is not.
They are one job, not a scatter: Generic binds 32 of the 34, Explosion 19,
Shell 9, Level_0 and Weapon 2 each. The binder fields rank LowerHPFxModel 252,
HitFxModel 144, then JetFxModel_00N and AfterBurnerFxModel_00N. They sit in the
six GP_MAIN_GAME_* paks at 130 bindings each plus 32 in DefTables.pak. Since
LowerHPFxModel is the damaged-ship effect, the residual is largely the
per-faction battle-damage and hit visuals. None of the 34 is a record name and
only one is a field name, so they are asset references.
Stated plainly: they remain unlocated AS ASSETS. Knowing the family and its
naming law does not say where the geometry lives - the .xpr route is exhausted
for them and the parsed pak payloads hold references, not meshes.
Also fixes a defect in the artefact shipped last commit. effect-homes.txt came
back with two equal-count lines swapped: Counter.most_common() breaks ties by
insertion order, so the package listing was not deterministic. Now sorted by
(-count, name) and verified to regenerate byte-identical twice running. This is
the corpus's own rule - any map built by iterating a set or Counter needs
sorted() - and the new tool had violated it.
The other sixteen artefacts are byte-identical; effect-homes.txt changes only in
the tie-break ordering of the five 1-count rows, with every line pairing.
405 lines
20 KiB
Markdown
405 lines
20 KiB
Markdown
# ✅ 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 |
|
||
| 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_<n>` or
|
||
`GameModel_<n>` 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_<letter><digits>_<e|f>` 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.
|