re: add a hunting pilot; withdraw the "10 of 116 records are dynamic" result
The user asked for an actively hunting pilot, since a player who kills nothing cannot trigger an event-gated wave and both previous runs used the survival pilot. pilot.py gains SYLPH_HUNT=1. The substantive change is which contacts ENGAGE may shoot: it previously skipped every "hard" target -- "turrets and hulls are not the objective" -- and stood off 2500 units from turrets, on the assumption that an e007 Turret is an AA mount on a capital ship. It is a craft, one of the main enemy types of the first six missions, and at 100 HP the cheapest kill on the field. Under SYLPH_HUNT it is a target and the keep-out drops to 600. The run confirms the pilot engages: steady ENGAGE, fire=1, committed to an e010_ADAN_Attacker_S at ~2.2 km, hull and escorted asset untouched over 160 s. Withdrawn: "only 10 of 116 records ever changed a byte in 170 s". This run measured 41-56 records changing in every 10 s tick. The old figure does not reproduce. I cannot say why, because I changed two variables at once -- the record bound (fixed 0x200 to bounded-by-next-record) and the pilot (survival to hunting). Either explains it. That is a design error, and the honest outcome is a retraction without a replacement explanation rather than a story that fits. The conclusion it had supported is unaffected: the roster identity now rests on the exact 10-of-10 unit-composition match measured independently. Still open, and explicitly not concluded: the pilot's own entity scan shows ADAN drifting 147 -> 129 -> 142, and the late rise has the shape of an arrival, but the sample-to-sample swing is +/-10, the same size as the effect. AGENT.md warns that polling faster than the guest updates manufactures a curve out of noise, so no wave conclusion is drawn. The run probably did not kill anything either (fc=0, asset at 100%), so it does not test the event-gated model. A stable per-record liveness field and a working kill counter are both needed first; REMAINING OB at 0xbdb59668 still does not read as a counter.
This commit is contained in:
@@ -332,8 +332,11 @@ search cannot find a *schedule*.
|
|||||||
moves in one and not the other.
|
moves in one and not the other.
|
||||||
* 🔴 **Diffing inside the 116 records did not find the arrival flag**
|
* 🔴 **Diffing inside the 116 records did not find the arrival flag**
|
||||||
(2026-08-24, same doc). 10 of 116 records are dynamic, 106 never change a byte
|
(2026-08-24, same doc). 10 of 116 records are dynamic, 106 never change a byte
|
||||||
in 170 s — more support for the pre-allocated roster — and STRONGER than
|
in 170 s — 🔴 **the "10 of 116 dynamic" figure is WITHDRAWN** — a hunting
|
||||||
first written: the "turrets don't move" hedge was withdrawn after the user
|
run measured 41–56 records changing per tick; I had changed two variables at
|
||||||
|
once (record bound and pilot) so the discrepancy is unattributed. The roster
|
||||||
|
identity is unaffected: it now rests on the 10/10 unit-composition match.
|
||||||
|
Earlier note, kept for history: the "turrets don't move" hedge was withdrawn after the user
|
||||||
pointed out that early-mission "Turret" is a craft type, which the data
|
pointed out that early-mission "Turret" is a craft type, which the data
|
||||||
confirms (`UN_e007_ADAN_Turret` flies under `AI_ADAN_CraftSquadron_*`, never
|
confirms (`UN_e007_ADAN_Turret` flies under `AI_ADAN_CraftSquadron_*`, never
|
||||||
`AI_Structure`). Lesson: check a unit's `AIID`, not its English name. No field transitions in groups of 3 at the
|
`AI_Structure`). Lesson: check a unit's `AIID`, not its English name. No field transitions in groups of 3 at the
|
||||||
|
|||||||
@@ -313,3 +313,76 @@ records it recurring in about 5 runs of 7), and this was one of the misses, so
|
|||||||
the run cannot say whether the pilot killed anything. Re-hunting the counter
|
the run cannot say whether the pilot killed anything. Re-hunting the counter
|
||||||
with `ob_hunt.py` is a precondition for the kill-versus-no-kill test, not an
|
with `ob_hunt.py` is a precondition for the kill-versus-no-kill test, not an
|
||||||
optional extra.
|
optional extra.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Hunting run (2026-08-24) — and a withdrawal
|
||||||
|
|
||||||
|
Status: ✅ a hunting pilot exists and engages; 🔴 the "10 of 116 records are
|
||||||
|
dynamic" result is **withdrawn**; ❔ the wave question is still open, and the
|
||||||
|
entity scan is too noisy to settle it.
|
||||||
|
|
||||||
|
The user asked for an actively hunting pilot, with attention to the objectives,
|
||||||
|
because the two previous runs used the *survival* pilot and a player who kills
|
||||||
|
nothing cannot trigger an event-gated wave.
|
||||||
|
|
||||||
|
## ✅ `SYLPH_HUNT=1`
|
||||||
|
|
||||||
|
`pilot.py` gained a hunt mode. The change that matters is which contacts ENGAGE
|
||||||
|
is allowed to shoot: it previously skipped every "hard" target with the comment
|
||||||
|
*"turrets and hulls are not the objective"*, and kept 2500 units away from
|
||||||
|
turrets. That was written on the assumption that an `e007` "Turret" is an
|
||||||
|
anti-aircraft mount on a capital ship. It is not — it is a craft, one of the main
|
||||||
|
enemy types of the first six missions
|
||||||
|
(`AI_ADAN_CraftSquadron_*`, `Type = Squad`), and at 100 HP the cheapest kill on
|
||||||
|
the field. Under `SYLPH_HUNT=1` turrets are targets and the keep-out drops to
|
||||||
|
600.
|
||||||
|
|
||||||
|
The run confirms it flies and shoots: steady `ENGAGE`, `fire=1`, committed to
|
||||||
|
`e010_ADAN_Attacker_S` at ~2.2 km, hull 1500/1500 and escorted asset at 100 %
|
||||||
|
throughout 160 s.
|
||||||
|
|
||||||
|
## 🔴 Withdrawn: "only 10 of 116 records are dynamic"
|
||||||
|
|
||||||
|
The previous section reported that 10 of 116 records ever changed a byte in
|
||||||
|
170 s, and used it as supporting evidence for the pre-allocated roster. **This
|
||||||
|
run measured 41–56 records changing in every single 10 s tick.** The earlier
|
||||||
|
figure does not reproduce and is withdrawn.
|
||||||
|
|
||||||
|
I cannot say why, because I changed two things at once — the record bound (fixed
|
||||||
|
`0x200` → bounded by the next record's address, capped at `0x400`) *and* the
|
||||||
|
pilot (survival → hunting). Either could explain it: the old window mis-framed
|
||||||
|
every record whose true size is not `0x200`, and a hunting pilot flies into
|
||||||
|
traffic that an evading one avoids. **That is a design error on my part**: a run
|
||||||
|
that changes two variables cannot attribute its own result, and the honest
|
||||||
|
outcome is that the old number is retracted without a replacement explanation.
|
||||||
|
|
||||||
|
The conclusion the retracted number was supporting is unaffected — the
|
||||||
|
roster identity now rests on the exact 10-of-10 unit-composition match, which is
|
||||||
|
far stronger evidence and was measured independently.
|
||||||
|
|
||||||
|
## ❔ The entity scan is too noisy to answer the wave question
|
||||||
|
|
||||||
|
`pilot.py`'s own scanner reports a live entity count each tick. Over the run:
|
||||||
|
|
||||||
|
```
|
||||||
|
t=0s 168 (147 ADAN) t=56s 147 (131) t=101s 145 (129)
|
||||||
|
t=20s 171 (147) t=76s 155 (131) t=137s 151 (129)
|
||||||
|
t=30s 148 (132) t=86s 147 (131) t=157s 166 (142)
|
||||||
|
```
|
||||||
|
|
||||||
|
ADAN drifts 147 → 129 and back to 142. The late rise is the shape an arrival
|
||||||
|
would have, but the sample-to-sample swing is ±10 or more, which is the same
|
||||||
|
size as the effect. This is exactly the trap `docker/agent/AGENT.md` warns about
|
||||||
|
— polling faster than the guest updates manufactures a curve out of noise — so
|
||||||
|
**no wave conclusion is drawn from it.**
|
||||||
|
|
||||||
|
Two things are needed before this run type can settle the question:
|
||||||
|
|
||||||
|
1. A *stable* liveness signal — a per-record field that means alive/dead, read
|
||||||
|
from the 116 labelled records, rather than a re-scan whose population changes
|
||||||
|
between samples.
|
||||||
|
2. A kill count. `fc=0` in the pilot's telemetry and the asset at 100 % suggest
|
||||||
|
the hunt did not actually destroy anything in 160 s, in which case this run
|
||||||
|
does not test the event-gated model either. `REMAINING OB` at `0xbdb59668`
|
||||||
|
still did not read as a counter, so that check remains unavailable.
|
||||||
|
|||||||
@@ -113,13 +113,18 @@ ASSET_NAME = os.environ.get("SYLPH_ASSET", "Acropolis")
|
|||||||
KILL_TURRETS = os.environ.get("SYLPH_KILL_TURRETS") == "1"
|
KILL_TURRETS = os.environ.get("SYLPH_KILL_TURRETS") == "1"
|
||||||
|
|
||||||
|
|
||||||
|
HUNT = os.environ.get("SYLPH_HUNT") == "1"
|
||||||
|
|
||||||
|
|
||||||
class Pilot:
|
class Pilot:
|
||||||
KP, KD = 2.2, 0.45
|
KP, KD = 2.2, 0.45
|
||||||
FIRE_CONE = math.radians(9) # fallback only; the real gate is angular size
|
FIRE_CONE = math.radians(9) # fallback only; the real gate is angular size
|
||||||
FIRE_RANGE = SHELL_MAX_RANGE # the shells simply do not arrive past this
|
FIRE_RANGE = SHELL_MAX_RANGE # the shells simply do not arrive past this
|
||||||
CONE_MIN = math.radians(2.0)
|
CONE_MIN = math.radians(2.0)
|
||||||
CONE_MAX = math.radians(25.0) # close-in the target subtends a lot; let it
|
CONE_MAX = math.radians(25.0) # close-in the target subtends a lot; let it
|
||||||
TURRET_KEEPOUT = 2500.0 # ...and stay this far from things that shoot back
|
# Keep-out from things that shoot back. Under SYLPH_HUNT the turrets ARE the
|
||||||
|
# targets, so standing off 2.5 km from them just guarantees no kills.
|
||||||
|
TURRET_KEEPOUT = 600.0 if HUNT else 2500.0
|
||||||
EVADE_QUIET = 5.0 # seconds without damage before re-engaging
|
EVADE_QUIET = 5.0 # seconds without damage before re-engaging
|
||||||
RETIRE_FRAC = 0.30 # hull fraction that sends us home
|
RETIRE_FRAC = 0.30 # hull fraction that sends us home
|
||||||
HZ = 8.0
|
HZ = 8.0
|
||||||
@@ -355,11 +360,20 @@ class Pilot:
|
|||||||
return tgt
|
return tgt
|
||||||
|
|
||||||
def pick(self, me_p, me_v, fwd, hos):
|
def pick(self, me_p, me_v, fwd, hos):
|
||||||
"""Nearest *fighter*, weighted by how far off the nose it is."""
|
"""Nearest *fighter*, weighted by how far off the nose it is.
|
||||||
|
|
||||||
|
SYLPH_HUNT=1 also lets ENGAGE take turrets. The exclusion below was
|
||||||
|
written believing an e007 "Turret" is an anti-aircraft mount bolted to a
|
||||||
|
capital ship. It is not -- it is a craft, one of the main enemy types of
|
||||||
|
the first six missions, flown by AI_ADAN_CraftSquadron_* (Type=Squad,
|
||||||
|
full manoeuvre-weight block) and never by AI_Structure. At 100 HP it is
|
||||||
|
also the cheapest kill on the field, which is what a run needs when the
|
||||||
|
question is whether kills release the next wave.
|
||||||
|
"""
|
||||||
best, bestscore = None, 1e18
|
best, bestscore = None, 1e18
|
||||||
for off, nm, p, v, r, hard in hos:
|
for off, nm, p, v, r, hard in hos:
|
||||||
if hard:
|
if hard and not HUNT:
|
||||||
continue # turrets and hulls are not the objective
|
continue # capital-ship hulls are not the objective
|
||||||
rel = p - me_p
|
rel = p - me_p
|
||||||
d = float(np.linalg.norm(rel))
|
d = float(np.linalg.norm(rel))
|
||||||
if d < 1e-3:
|
if d < 1e-3:
|
||||||
|
|||||||
54
tools/re-capture/wave4_probe.py
Executable file
54
tools/re-capture/wave4_probe.py
Executable file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Hunting run: does killing things make more records go active?
|
||||||
|
|
||||||
|
Uses the labelling and the MEASURED stride from wave3_probe.py -- each record is
|
||||||
|
bounded by the next record's address, not by a constant. Reports, per tick, how
|
||||||
|
many records changed and which unit types they are, so a rise in activity can be
|
||||||
|
tied to the hunt rather than to the clock.
|
||||||
|
"""
|
||||||
|
import os, sys, time, struct, collections, importlib.util
|
||||||
|
SD = __file__.rsplit('/', 1)[0]
|
||||||
|
spec = importlib.util.spec_from_file_location('gmem', SD + '/gmem.py')
|
||||||
|
gmem = importlib.util.module_from_spec(spec); spec.loader.exec_module(gmem)
|
||||||
|
w3 = importlib.util.spec_from_file_location('w3', SD + '/wave3_probe.py')
|
||||||
|
wave3 = importlib.util.module_from_spec(w3); w3.loader.exec_module(wave3)
|
||||||
|
|
||||||
|
MAXREC = 0x400
|
||||||
|
|
||||||
|
def main():
|
||||||
|
secs = int(sys.argv[1]) if len(sys.argv) > 1 else 200
|
||||||
|
every = int(sys.argv[2]) if len(sys.argv) > 2 else 10
|
||||||
|
path = gmem.mem_path()
|
||||||
|
fd = os.open(path, os.O_RDONLY); size = os.fstat(fd).st_size
|
||||||
|
f = os.fdopen(os.dup(fd), 'rb')
|
||||||
|
offs = wave3.find_records(f, fd, size)
|
||||||
|
if not offs: print('NO ENTITY RECORDS'); return 2
|
||||||
|
lens = [min(MAXREC, offs[i+1] - offs[i]) for i in range(len(offs)-1)] + [0x100]
|
||||||
|
ids = [wave3.resolve_id(f, size, o)[0] or '?' for o in offs]
|
||||||
|
print('records: %d, labelled %d' % (len(offs), sum(1 for i in ids if i != '?')))
|
||||||
|
print('composition:', collections.Counter(ids).most_common(6))
|
||||||
|
prev = [wave3.rd(f, o, l) for o, l in zip(offs, lens)]
|
||||||
|
log = open('/tmp/wave4-activity.tsv', 'w'); log.write('t\tactive\tunits\n')
|
||||||
|
t0 = time.time(); series = []
|
||||||
|
while time.time() - t0 < secs:
|
||||||
|
time.sleep(every)
|
||||||
|
el = round(time.time() - t0)
|
||||||
|
act = []
|
||||||
|
for k, (o, l) in enumerate(zip(offs, lens)):
|
||||||
|
cur = wave3.rd(f, o, l)
|
||||||
|
if cur != prev[k]: act.append(k)
|
||||||
|
prev[k] = cur
|
||||||
|
types = collections.Counter(ids[k] for k in act)
|
||||||
|
series.append((el, len(act)))
|
||||||
|
line = '%d\t%d\t%s\n' % (el, len(act), dict(types))
|
||||||
|
log.write(line); log.flush()
|
||||||
|
print(' t=%4ds active=%3d %s' % (el, len(act),
|
||||||
|
[(u.replace('UN_',''), c) for u, c in types.most_common(5)]), flush=True)
|
||||||
|
log.close()
|
||||||
|
print('\nactive-record series:', series)
|
||||||
|
a = [n for _, n in series]
|
||||||
|
if a: print('active: min=%d max=%d first=%d last=%d' % (min(a), max(a), a[0], a[-1]))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
||||||
17
tools/re-capture/wave4_session.sh
Executable file
17
tools/re-capture/wave4_session.sh
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/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:-200}"; EVERY="${2:-10}"
|
||||||
|
CFG=/tmp/nav-wave4.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=1 SYLPH_KILL_TURRETS=1 nohup python3 "$SD/pilot.py" "$CFG" "$SECS" \
|
||||||
|
</dev/null >/tmp/wave4-pilot.log 2>&1 &
|
||||||
|
PILOT=$!; echo "--- HUNTING pilot (SYLPH_HUNT=1)"
|
||||||
|
else PILOT=""; echo "--- BIND FAILED, no pilot"; fi
|
||||||
|
python3 "$SD/wave4_probe.py" "$SECS" "$EVERY"; rc=$?
|
||||||
|
[ -n "$PILOT" ] && kill "$PILOT" 2>/dev/null
|
||||||
|
echo "--- pilot tail ---"; tail -12 /tmp/wave4-pilot.log 2>/dev/null
|
||||||
|
echo "WAVE4 DONE rc=$rc"
|
||||||
Reference in New Issue
Block a user