# ✅ The two enumerations are two different structures — and only one is complete Measured 2026-08-26, live flight HUD, movers precondition checked (18 002). ## The stage changed between runs — earlier cross-run comparisons were invalid `LOAD GAME → slot 01` did **not** land on the same mission it did earlier today. This run's definition table has **14** entries and no asteroids at all: ``` UN_bf001_TCAF_SchlosBase UN_f101_TCAF_Acropolis UN_f201_TCAF_Tanker UN_e105_ADAN_Cruiser UN_e106_ADAN_Destroyer UN_e108_ADAN_ASFrigate UN_e201_ADAN_ISCMissile UN_f003_TCAF_ArrowHead UN_f105/f106 TCAF ... ``` That is the **Stage 02 escort mission** (the `ACROPOLIS` of [`autopilot-memory-driven.md`](autopilot-memory-driven.md)), not the S01 training area with `UN_S01_Asteroid_cmesh_*` that earlier runs loaded. Slot 01 is the **auto-save**, so the mission it restores moves as the save is written. ❌ **This invalidates the earlier "101 vs 42" comparison outright** — those two numbers came from *different stages*, not merely different moments. And it means no runtime roster is reproducible across runs unless the stage is verified first. ## Same moment, same stage, both methods | type | `INST_VTABLE` scan | moving + `+0x130` | |---|---|---| | `UN_e007_ADAN_Turret` | 21 | 14 | | `UN_e010_ADAN_Attacker_S` | 9 | 8 | | `UN_f001_TCAF_DeltaSaber_T` | 7 | 7 | | `UN_f001_TCAF_DeltaSaber_T_Player` | 1 | 1 | | `UN_e106_ADAN_Destroyer` | 19 | **0** | | `UN_f106_TCAF_Destroyer` | 14 | **0** | | `UN_f105_TCAF_Cruiser` | 11 | **0** | | `UN_e105_ADAN_Cruiser` | 7 | **0** | | `UN_e108_ADAN_ASFrigate` | 9 | **0** | | `UN_e201_ADAN_ISCMissile` | 9 | **0** | | `UN_f003_TCAF_ArrowHead` | 6 | **0** | | `UN_f101_TCAF_Acropolis` / `bf001_SchlosBase` / `f201_Tanker` | 1 each | **0** | | **total** | **116** | **30** | > # ❌ CORRECTED 2026-08-26 — "the absence is structural" is WRONG > > Everything below about capital ships being absent from the `+0x130` method is > an artifact of **how I sourced the entities**, not a property of the game. > > I enumerated by taking `moving()` triples and testing `+0x130`. `moving()` > requires a position to **change between two samples**. A capital ship holding > station changes by *exactly* zero — which is why dropping the floor to `lo=0` > did not help either, and why parked `ArrowHead` wingmen were missing too. The > whole-map scan failed for the same reason: the filter, not the range. > > `navigator.py` already does it correctly, and says so in its header — it > searches for the **definition pointer** and takes `position = hit - 0x130`, > *"which finds every entity whether it is moving or not"*. Running the existing > `mission_state.py scan` on Stage 02 finds **149 entities**, capital ships > included: > > ``` > UN_f101_TCAF_Acropolis ( +5000, +0, +0) hull= 25000.0 HP= 25000.0 frac=1.000 > UN_e105_ADAN_Cruiser ( -29500, +500, +3219) hull= 30000.0 HP= 30000.0 frac=1.000 > UN_e106_ADAN_Destroyer ( -25500, -1000, +7041) hull= 9150.0 HP= 10000.0 frac=0.915 > UN_f106_TCAF_Destroyer ( +1504, -498, -1937) hull= 9579.2 HP= 10000.0 frac=0.958 > UN_f001_..._Player ( -6676, +882, +1004) hull= 1500.0 HP= 1500.0 frac=1.000 > ``` > > `hull(+0x154) == definition HP` for **129 of 149**, the rest already damaged. > > ❌ So this specific claim is withdrawn outright: *"an autopilot that must > protect the Acropolis cannot locate it through the `+0x130` method at all."* > It can. The Acropolis is right there at `(+5000, 0, 0)` with its hull readable > and falling. `INDEX.md` had already recorded this — ACROPOLIS 25000 → 23038 > over 240 s — which makes this the eighth time this session that the corpus > held the answer before I measured. > > What survives from below: the two enumerations really are **different > structures** in different regions (0/116 vtable instances lie in the `+0x130` > region), and the vtable scan is still the one that sees stations and scenery. > What does **not** survive is any claim that the `+0x130` model is limited to > four types. ## ✅ The absence is structural, not a filter artifact The obvious explanations were tested and **both fail**: * **Speed band.** `moving()` filters displacement to `lo=1.0 … hi=4000.0` per 0.5 s, which would exclude a slow capital ship. Dropping the floor to **0** and the ceiling to 1e12 raised the count 30 → 59 and recovered the `_Player` — but still **only 4 types**. Capital ships never appear. * **Address window.** `moving()` defaults to `0xBD000000–0xBE000000`. Scanning the **whole map** with no speed floor gives 181 824 movers and **still 4 types**. So the ten missing types do not have a definition pointer at `position + 0x130` anywhere in memory that a moving-triple scan can reach. **The `+0x130` block is not the universal entity record — it belongs to a subset.** 🟡 The subset is *nearly* "small craft": every type it does cover is a fighter, attacker or turret. But `UN_f003_TCAF_ArrowHead` is a TCAF fighter and reads **0**, so "small craft" is not the rule. ❔ What actually distinguishes them is not established — plausibly an active-flight-model flag, with parked wingmen and capital ships handled by a different update path. ## ✅ They live in different regions, which is why they are different structures ``` vtable instances : 0xbc372cc0 .. 0xbc9bc720 (116) ENT_VA window : 0xbd000000 .. 0xbe000000 instances inside that window : 0/116 ``` **Zero** of the 116 vtable objects lie in the window where the `+0x130` position blocks are found. This is independent confirmation of [the earlier refutation](entity-position-anchor-refuted.md): the vtable object and the position block are separate allocations in separate regions, not two views of one record. ## Which list to use * **Completeness** — the `INST_VTABLE` scan. It sees all 14 types including stations, capital ships and the objective-critical `Acropolis`. * **Motion state** — the `+0x130` blocks. They carry position, speed, orientation (`pos−0x70`, stride 16) and hull (`pos+0x154`), but only for ~4 types. * An autopilot that must protect the `Acropolis` **cannot** locate it through the `+0x130` method at all. That is a concrete limit on the existing approach. ## Also confirmed The exact **2× duplication** reappeared: def-pointer hits come in pairs `0x1000` apart with byte-identical positions (`0x011db3bd80` / `0x011db3cd80`, both `(-1654.7, -3992.7, 458.2)`, both moving 424.482). ❌ It is **not** page aliasing in `gmem.MAP` terms — aliased VAs share one *file offset*, and these differ by `0x1000` in the offset itself, so they are genuinely two copies. ❌ **Correction to [the previous note](entity-count-discrepancy-resolved.md):** it said "at least one of the counts was un-deduplicated". That is wrong — `entities2.py` prints its count *after* `ents = list(uniq.values())`, so the 101 was already deduplicated. The gap was the stage change, not duplication.