re: motion-independent liveness probe; n looks like craft-per-member
entities2.moving() finds entities by displacement between two samples, so anything stationary is invisible -- the entire explanation for the +/-10 swing that made the previous run's count useless. liveness_probe.py enumerates by definition pointer over the entity heap instead, moving or not, and reads hull as f32 at position+0x154. The series is monotone rather than oscillating: 298 -> 280 over 164 s, with the decline matching the 18 disappearance events exactly. The hunting pilot does kill: one hull crossing caught directly, an e010_ADAN_Attacker_S at t=57 s. The previous run's worry that SYLPH_HUNT shoots but never destroys anything is settled. Recorded as a non-result so the next run does not misread it: zero births in 164 s does NOT favour either wave model. The roster finding already established that every participant is allocated at mission load, so neither a clock nor an event model would produce an allocation. An arrival must be a state change on an existing entity. The mystery member field n now has a candidate meaning: the number of craft a roster member spawns. Static sum(n) for turrets is 216 against 214 sites found, with the count already falling before the first sample, where Count alone predicts 21 -- off by an order of magnitude. Not promoted, and the reason is a confound in my own measurement rather than the data: the probe counts definition-pointer SITES, not entities. The player is one member and yields two sites, and DeltaSaber_T yields exactly double its sum(n), so some entity types hold several pointers to their definition. Until sites are collapsed into distinct entities the turret match could be a coincidence between a x1 multiplicity and a x1 ratio. The capital-ship rows undershoot for a separate and expected reason: phases 2 and 3 have not started.
This commit is contained in:
@@ -320,6 +320,16 @@ search cannot find a *schedule*.
|
||||
record per member (n=1, NOT promoted; the refutation needs another stage's
|
||||
save, which we do not have — only slot 01 / Stage 02 exists).
|
||||
❔ Still unmeasured: whether the timetable's `t` is frames or seconds.
|
||||
* ✅🟡 **Motion-independent liveness probe works; `n` is probably craft-per-member**
|
||||
(2026-08-24) — [`mission-liveness-probe.md`](mission-liveness-probe.md).
|
||||
Enumerating by definition pointer instead of by motion removes the ±10 noise
|
||||
(monotone 298→280). The hunting pilot **does** kill (hull crossing at t=57s).
|
||||
🔴 "No births in 164 s" does NOT separate the wave models — everything is
|
||||
pre-allocated, so an arrival must be a STATE CHANGE, not an allocation.
|
||||
🟡 Turret `sum(n)`=216 vs 214 sites found (vs `Count`=21) suggests the mystery
|
||||
member field `n` is the craft count per member — NOT promoted: the probe counts
|
||||
definition-pointer *sites*, and the player (1 member → 2 sites) proves some
|
||||
entities hold several. **Next: collapse sites into entities, then re-test.**
|
||||
* 🟡 **COMPETING MODEL (user, 2026-08-24): waves are event-gated, not
|
||||
clock-driven** — released by kills/objectives rather than at a fixed time, with
|
||||
the route's start time being a post-trigger delay. Fits the evidence better:
|
||||
|
||||
86
docs/re/mission-liveness-probe.md
Normal file
86
docs/re/mission-liveness-probe.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# A motion-independent live roster — and what `n` probably is
|
||||
|
||||
Status: ✅ the enumeration method; ✅ the hunting pilot gets kills; 🔴 "no
|
||||
births" cannot distinguish the wave models; 🟡 the member field `n` looks like a
|
||||
craft count, with a confound that must be removed before believing it.
|
||||
|
||||
## ✅ Enumerate by definition pointer, not by motion
|
||||
|
||||
`entities2.moving()` finds entities *by displacement between two samples*, so
|
||||
anything stationary in the window is invisible. That is the whole explanation
|
||||
for the ±10 swing that made the previous run's entity count useless
|
||||
([mission-wave-arrivals.md](mission-wave-arrivals.md)).
|
||||
|
||||
`tools/re-capture/liveness_probe.py` scans the entity heap
|
||||
(`0xBD000000–0xBE000000`) for aligned words equal to a known unit-definition VA
|
||||
instead. Moving or not, an entity is counted. Hull is `f32` at
|
||||
`position + 0x154` (`HULL_OFF`, already confirmed in `pilot.py`), so a death is
|
||||
a hull crossing to ≤ 0.
|
||||
|
||||
The difference is immediate — the series is monotone instead of oscillating:
|
||||
|
||||
```
|
||||
t= 15s 294 alive born 0 died 0 gone 4
|
||||
t= 36s 290 born 0 died 0 gone 4
|
||||
t= 57s 289 born 0 died 1 gone 0 <- e010_ADAN_Attacker_S
|
||||
t= 78s 286 born 0 died 0 gone 4
|
||||
t=100s 286 born 0 died 0 gone 0
|
||||
t=143s 282 born 0 died 0 gone 4
|
||||
t=164s 280 born 0 died 0 gone 2
|
||||
```
|
||||
|
||||
Total decline 298 → 280 = 18, matching the 18 "gone" events exactly.
|
||||
|
||||
## ✅ The hunting pilot kills
|
||||
|
||||
One hull crossing was caught directly — an `e010_ADAN_Attacker_S` at t = 57 s —
|
||||
plus 18 disappearances. The previous run's worry that `SYLPH_HUNT` shoots but
|
||||
never destroys anything is settled: it does.
|
||||
|
||||
## 🔴 "No births" does **not** separate the two wave models
|
||||
|
||||
Zero entities appeared in 164 s. That looks like evidence against clock-driven
|
||||
arrivals at t = 90/120 s — **and it is not**, because the roster finding already
|
||||
established that all of a mission's entities are allocated at load. If every
|
||||
participant exists from the first frame, then *neither* model produces a "birth",
|
||||
and the observation is consistent with both. Recorded so the next run does not
|
||||
mistake it for a result: **an arrival must be a state change on an existing
|
||||
entity, not an allocation.**
|
||||
|
||||
## 🟡 `n` looks like the number of craft per roster member
|
||||
|
||||
The member tuple's third field `n` has been ❔ since the roster was decoded.
|
||||
Comparing the static `sum(n)` per unit type against what is resident:
|
||||
|
||||
| unit | members | `sum(n)` | sites found |
|
||||
|---|---|---|---|
|
||||
| `UN_e007_ADAN_Turret` | 21 | **216** | **214** |
|
||||
| `UN_e105_ADAN_Cruiser` | 7 | 7 | 6 |
|
||||
| `UN_e106_ADAN_Destroyer` | 19 | 19 | 14 |
|
||||
| `UN_f105_TCAF_Cruiser` | 11 | 11 | 4 |
|
||||
| `UN_f106_TCAF_Destroyer` | 14 | 16 | 10 |
|
||||
| `UN_e010_ADAN_Attacker_S` | 9 | 45 | 32 |
|
||||
| `UN_f001_TCAF_DeltaSaber_T` | 7 | 7 | **14** |
|
||||
| `UN_f001_..._Player` | 1 | 1 | **2** |
|
||||
|
||||
The turret row is striking: 216 predicted against 214 found, with the count
|
||||
already falling before the first sample. `Count` alone predicts 21, which is
|
||||
off by an order of magnitude. So `n` scaling a member into a flight of craft
|
||||
fits the dominant unit type well.
|
||||
|
||||
**It is not promoted, because the measurement has an unquantified confound.**
|
||||
The player is one member and yields **two** sites, and `DeltaSaber_T` yields
|
||||
exactly double its `sum(n)` — so at least some entity types hold more than one
|
||||
pointer to their definition. What the probe counts is *definition-pointer sites*,
|
||||
not entities, and until those are collapsed into distinct entities the turret
|
||||
match could be a coincidence between a ×1 multiplicity and a ×1 ratio. The
|
||||
capital-ship rows undershoot instead, which is separately explained by phases 2
|
||||
and 3 not having started.
|
||||
|
||||
## Next
|
||||
|
||||
Collapse sites into entities before re-testing `n` — cluster sites by their
|
||||
implied entity base and take distinct bases. Then the table above becomes a real
|
||||
test rather than a suggestive one. The same de-duplication is a precondition for
|
||||
using this probe to watch an arrival, since an arrival is now known to be a
|
||||
state change rather than an allocation.
|
||||
82
tools/re-capture/liveness_probe.py
Executable file
82
tools/re-capture/liveness_probe.py
Executable file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""A STABLE live-entity roster with hull, so kills and arrivals are observable.
|
||||
|
||||
Why not reuse the pilot's scan: entities2.moving() finds entities *by motion*
|
||||
between two samples, so anything stationary in the window is invisible. That is
|
||||
why the pilot's count swings +/-10 between ticks and why no wave conclusion
|
||||
could be drawn from it (mission-wave-arrivals.md).
|
||||
|
||||
This enumerates by definition pointer instead: every aligned word in the entity
|
||||
heap that equals a known unit-definition VA marks an entity, whether it is
|
||||
moving or not. Hull is f32 at position + 0x154 (HULL_OFF, already confirmed in
|
||||
pilot.py), so a death is a hull crossing to <= 0 and an arrival is a position
|
||||
that was not there before.
|
||||
"""
|
||||
import os, sys, time, struct, collections
|
||||
sys.path.insert(0, __file__.rsplit('/', 1)[0])
|
||||
import gmem, gworld, entities2
|
||||
|
||||
ENT_LO, ENT_HI = entities2.ENT_VA_LO, entities2.ENT_VA_HI
|
||||
HULL_OFF = 0x154
|
||||
|
||||
def enumerate_entities(fd, defs, delta):
|
||||
"""All entities in the heap region, moving or not."""
|
||||
lo, hi = gmem.va_to_off(ENT_LO), gmem.va_to_off(ENT_HI)
|
||||
out = []
|
||||
pos = lo
|
||||
while pos < hi:
|
||||
n = min(1 << 24, hi - pos)
|
||||
blob = os.pread(fd, n, pos)
|
||||
for k in range(0, len(blob) - 3, 4):
|
||||
nm = defs.get(blob[k:k+4])
|
||||
if nm:
|
||||
out.append((pos + k - delta, nm))
|
||||
pos += n
|
||||
return out
|
||||
|
||||
def hull(fd, off):
|
||||
try:
|
||||
return struct.unpack('>f', os.pread(fd, 4, off + HULL_OFF))[0]
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def main():
|
||||
secs = int(sys.argv[1]) if len(sys.argv) > 1 else 180
|
||||
every = int(sys.argv[2]) if len(sys.argv) > 2 else 10
|
||||
delta = int(sys.argv[3], 0) if len(sys.argv) > 3 else 0x130
|
||||
w = gworld.World(); fd = w.fd
|
||||
defs = entities2.definitions(w)
|
||||
print('unit definitions: %d' % len(defs))
|
||||
if not defs:
|
||||
print('NO DEFINITIONS -- not in a mission'); return 2
|
||||
|
||||
ents = enumerate_entities(fd, defs, delta)
|
||||
print('entities enumerated (motion-independent): %d' % len(ents))
|
||||
print('composition:', collections.Counter(n for _, n in ents).most_common(8))
|
||||
|
||||
log = open('/tmp/liveness.tsv', 'w'); log.write('t\talive\tborn\tdied\n')
|
||||
prev = {off: hull(fd, off) for off, _ in ents}
|
||||
names = dict(ents)
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < secs:
|
||||
time.sleep(every)
|
||||
el = round(time.time() - t0)
|
||||
cur_ents = enumerate_entities(fd, defs, delta)
|
||||
cur = {off: hull(fd, off) for off, _ in cur_ents}
|
||||
for off, n in cur_ents: names.setdefault(off, n)
|
||||
born = [o for o in cur if o not in prev]
|
||||
gone = [o for o in prev if o not in cur]
|
||||
died = [o for o in cur
|
||||
if prev.get(o) is not None and cur[o] is not None
|
||||
and prev[o] > 0 >= cur[o]]
|
||||
alive = sum(1 for o, h in cur.items() if h is not None and h > 0)
|
||||
print(' t=%4ds entities=%3d alive=%3d born=%2d died=%2d gone=%2d %s'
|
||||
% (el, len(cur), alive, len(born), len(died), len(gone),
|
||||
[names[o].replace('UN_', '') for o in (born + died)[:4]]), flush=True)
|
||||
log.write('%d\t%d\t%d\t%d\n' % (el, alive, len(born), len(died))); log.flush()
|
||||
prev = cur
|
||||
log.close()
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
16
tools/re-capture/liveness_session.sh
Executable file
16
tools/re-capture/liveness_session.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u
|
||||
export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98
|
||||
export PYTHONPATH=/sylph-home/.local/lib/python3.12/site-packages
|
||||
SD="$(cd "$(dirname "$0")" && pwd)"
|
||||
SECS="${1:-180}"; EVERY="${2:-10}"; HUNT="${3:-1}"
|
||||
CFG=/tmp/nav-live.json
|
||||
"$SD/launch_mission.sh" fly || { echo "BOOT FAILED"; exit 1; }
|
||||
if python3 "$SD/entities2.py" self 0x130 "$CFG" >/dev/null 2>&1; then
|
||||
SYLPH_HUNT="$HUNT" SYLPH_KILL_TURRETS=1 nohup python3 "$SD/pilot.py" "$CFG" "$SECS" \
|
||||
</dev/null >/tmp/live-pilot.log 2>&1 &
|
||||
PILOT=$!; echo "--- pilot (SYLPH_HUNT=$HUNT)"
|
||||
else PILOT=""; echo "--- BIND FAILED, no pilot"; fi
|
||||
python3 "$SD/liveness_probe.py" "$SECS" "$EVERY"; rc=$?
|
||||
[ -n "$PILOT" ] && kill "$PILOT" 2>/dev/null
|
||||
echo "LIVENESS DONE rc=$rc"
|
||||
Reference in New Issue
Block a user