diff --git a/docs/re/entity-position-anchor-refuted.md b/docs/re/entity-position-anchor-refuted.md new file mode 100644 index 0000000..16781cd --- /dev/null +++ b/docs/re/entity-position-anchor-refuted.md @@ -0,0 +1,86 @@ +# ❌ Two of my own claims, refuted — and a live-memory trap behind both + +Measured 2026-08-26, Canary on the retail disc, save slot 01, Stage 01, **in +flight with a live HUD** (`TIME 00:28.42`, speed 350, target on radar, dialogue +mid-render — the game was unambiguously running throughout). + +## ❌ Refuted: `position = instance − 0x12c` + +The corpus anchors the entity layout on **position**: +[`autopilot-memory-driven.md`](autopilot-memory-driven.md) gives the definition +pointer at `position + 0x130`, orientation at `position − 0x70`, hull at +`position + 0x154`. `gworld.py`'s `name_of()` reads the definition pointer at +`instance + 4`. If those name the same field, `instance + 4 = position + 0x130`, +so `position = instance − 0x12c`. + +They do not. Read at `instance − 0x12c` for all **42** named instances: + +``` +UN_f001_TCAF_DeltaSaber_T_Player ( 0.0, 0.9, 0.1) moved 0.00 +UN_e106_ADAN_Destroyer ( -48.9, 0.0, 0.0) moved 0.00 +UN_S01_Asteroid_cmesh_01a (2.07e11, 0.0, 0.0) moved 0.00 +... +moved in 0.8 s : 0/42 +``` + +Almost every read is `(0, 0, 0)`, three are garbage, and **nothing moves**. The +vtable-bearing object and the position block are two different structures that +merely name the same units. The prediction was clean and it was wrong. + +## ❌ Withdrawn: "`entities2.py`'s VA window is aimed at the wrong region" + +[Last iteration](world-unit-attempt-2026-08-26.md) I recorded `ENT_VA_LO/HI = +0xBD000000–0xBE000000` as a defect, on the grounds that the live instances sit at +`0xBC384CE0–0xBC9BAC20` and the window "covers the definitions instead". + +That reasoning was wrong. It assumed the thing the window *should* contain is the +vtable-bearing instance — but `entities2.py` does not look for those. It hunts +**moving position triples** and checks `+0x130` for a definition pointer, exactly +the documented anchor. And in the run where I called it broken it had *worked* +moments earlier: **6914 movers, `+0x130` voted 92×**, then 6634 and 138× on a +re-run. I built a defect report on the one sample in between that returned zero — +the same single-sample error this corpus keeps logging. + +The window may still be imperfect (it is hardcoded, and the heap moves). But it +is not "aimed at the definitions", and nothing here shows it needs changing. + +## 🟡→❌ The `+0x29d0` position candidate is not trustworthy either + +It was read through the same region that is shown below not to be updating, so +what it captured is load-time data, not a live position. Withdrawn pending a +re-measurement through a region that demonstrably changes. + +## ✅ The trap: most of guest memory is not changing while the game runs + +Sampling **64 spots of 64 KB** spread across the mapping's **505 extents +(357 MB)**, twice, 2.5 s apart, with the mission visibly running: + +``` +CHANGING: 5 / 64 + 0x007006f000 0x00704db000 0x0070557b000 0x00705df000 0x0080240000 +``` + +Everything else — including the megabyte containing all 42 live instances +(`0x11c37d780`), and that region's primary-VA counterpart at `0x00bc300000` — is +**byte-identical over seconds**. So: + +* The mapping is live. Five regions prove it, so "the shm view is stale" as a + blanket claim is refuted. +* The entity-object region genuinely does not change on a seconds timescale. +* ❔ **Not determined**: whether that is because those records are static + metadata and the moving state lives elsewhere, or because the guest physical + page is aliased at several file offsets and writes land in a different one. + Both fit the evidence. `gmem.primary_va(0x11c384ce0) = 0xbc384ce0` shows the + alias relationship exists; it does not show which copy the guest writes. + +**Consequence for anyone reading guest memory here:** verify the specific region +you are sampling actually changes before drawing conclusions from it. A scan that +finds correct, name-resolving records can still be reading a snapshot. + +## Still open + +* Where the live position blocks are. The five changing offsets above are the + place to start, but `moving()` takes **VAs** and converts with + `gmem.va_to_off`, while those five are **file offsets** — they are not the same + number and must be converted before they can be scanned. +* Whether `entities2.py`'s window needs any change at all.