pilot: target commitment takes kills from 2 to 9; and the target pointer does not exist

The loop re-scored every contact every tick, so the nose chased whichever
fighter scored best that instant and aim error wandered 10-40 deg through a
pass. A missile lock is time-on-target, so constant switching is the one thing
guaranteed to prevent a kill. Commit to a contact until it dies, passes 6000,
sits >90 deg off the nose for 2.5 s, or 14 s elapse.

Same guns, same ballistics, same escort weighting, same missile cadence:
kills 0000 (five gun-only runs) -> 0002 (missiles) -> 0009 (commitment), with
101 missiles vs 98, and the largest hostile-population fall of any run
(134->97). Own hull untouched. The ACROPOLIS still ended at 76.6%, so this is
lethality, not the mission outcome.

Also records a negative result so it is not re-attempted: the selected target is
NOT a raw entity pointer. Three searches came up empty — a +-0x1400 window of
the player object, a full-RAM sweep of every entity-pointer word tapped through
each button (only thread-stack slots churn, which is frame noise), and a delta
tally over all 150 entities of the kind that found the definition pointer at
+0x130. The selection must be a handle, an index, or in a subsystem outside the
entity object.
This commit is contained in:
claude-re
2026-07-30 19:19:23 +00:00
parent 95ac545b2b
commit 9f41fe08e9
4 changed files with 251 additions and 1 deletions

View File

@@ -0,0 +1,173 @@
#!/usr/bin/env python3
"""Find the SELECTED TARGET inside the player object, and the input that changes it.
The OPTIONS key-config screen lists `Change Target` and `Padlock Mode Toggle` as
real bindable actions (docs/re/flight-controls-runtime.md), but a button sweep
that watched the *ammo counters* could not see either — neither action fires a
weapon. Screenshots of the reticle were no better: the view keeps moving, so
"did the selection change" is not legible frame to frame.
Guest memory is legible. If the craft holds a selected target, it holds a
**pointer to that target's object**, and every live entity's address is already
known from the entity scan. So:
1. enumerate live entities and their addresses;
2. read a window of the player object and keep every word that points at one
of them (allowing a small fixed delta, since a pointer to an object's base
is not a pointer to its transform);
3. tap each candidate input and see which of those words switches to a
*different* entity.
The word that follows the button is the selection, and the button that moves it
is `Change Target`. Both answers come out of the same run, and neither depends
on reading pixels.
Usage: target_probe.py <config.json> [seconds_per_button]
"""
import json
import os
import struct
import sys
import time
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import gmem # noqa: E402
import navigator # noqa: E402
from flight_probe import Pad # noqa: E402
BACK, FWD = 0x400, 0x1000
MAX_DELTA = 0x400 # how far below its transform an object's base may sit
BUTTONS = ["LB", "X", "B", "A", "LS", "RS", "BACK", "START", "Y", "RB"]
def main():
cfg = json.load(open(sys.argv[1]))
dwell = float(sys.argv[2]) if len(sys.argv) > 2 else 1.2
W = navigator.World(cfg)
ents = W.scan()
me = [(off, va) for off, va in ents if "Player" in W.defs[va]]
if not me:
sys.exit("player entity not found — not in flight?")
me_off = me[0][0]
print(f"# player object at {gmem.primary_va(me_off):#010x}, "
f"{len(ents)} live entities")
# every address that could be a pointer to some entity
ptr_map = {}
for off, va in ents:
pos_va = gmem.primary_va(off)
if pos_va is None:
continue
for d in range(0, MAX_DELTA, 4):
ptr_map.setdefault(pos_va - d, (W.defs[va], d, off))
def window():
b = os.pread(W.fd, BACK + FWD, me_off - BACK)
return np.frombuffer(b, dtype=">u4").copy()
# ---- global mode: the selection need not live in the player object at all.
# Scan every mapped extent for words that hold an entity pointer, then see
# which of THOSE follow a button. A word that is an entity pointer before
# AND after, pointing at a different entity, is a selection by construction.
keys = np.array(sorted(ptr_map), dtype=np.uint32)
def global_ptrs():
out = {}
for a0, b0 in gmem.extents(W.fd, W.size):
n = (b0 - a0) // 4 * 4
if n < 64:
continue
arr = np.frombuffer(os.pread(W.fd, n, a0), dtype=">u4").astype(np.uint32)
idx = np.flatnonzero(np.isin(arr, keys))
for k in idx:
out[a0 + int(k) * 4] = int(arr[k])
return out
# ---- delta mode: WHERE does an entity keep its target?
# The AI ships clearly hold pointers to other entities, so the field is a
# fixed offset from the transform — exactly the situation entities2.py
# solved for the definition pointer. Tally, over many entities, the delta at
# which a word points at *another* entity; the offset that repeats is the
# field, and reading it on the PLAYER gives our selected target.
if "--delta" in sys.argv:
from collections import Counter
votes, examples = Counter(), {}
for off, va in ents:
blob = os.pread(W.fd, 0x1000, max(0, off - 0x800))
arr = np.frombuffer(blob[:len(blob) // 4 * 4], dtype=">u4")
for i, w in enumerate(arr):
hit = ptr_map.get(int(w))
if hit and hit[2] != off:
d = i * 4 - 0x800
votes[d] += 1
examples.setdefault(d, (W.defs[va], hit[0]))
print(f"# target-pointer delta candidates over {len(ents)} entities:")
for d, n in votes.most_common(10):
src, dst = examples[d]
print(f" pos{d:+#07x} seen {n:4d} e.g. {src[:26]:<26} -> {dst}")
best = votes.most_common(1)
if best:
d = best[0][0]
pw = struct.unpack(">I", os.pread(W.fd, 4, me_off + d))[0]
hit = ptr_map.get(pw)
print(f"\n# PLAYER at that delta: {pw:#010x} -> "
f"{hit[0] if hit else 'not an entity pointer'}")
return
a = window()
cands = []
for i, w in enumerate(a):
hit = ptr_map.get(int(w))
if hit and hit[2] != me_off: # a self-pointer is not a target
cands.append((i, hit[0], hit[1]))
print(f"# {len(cands)} word(s) in the player object point at a live entity")
for i, nm, d in cands[:30]:
print(f" pos{i * 4 - BACK:+#07x} -> {nm} (entity_va - {d:#x})")
if not cands:
print("# none — widen BACK/FWD or MAX_DELTA, or nothing is selected")
# ---- which input moves the selection?
pad = Pad()
if "--global" in sys.argv:
print("\n# GLOBAL scan: every word in RAM that holds an entity pointer")
g0 = global_ptrs()
print(f"# {len(g0)} entity-pointer words in RAM")
for btn in BUTTONS:
pad.f.write(f"tap {btn} 200\n")
time.sleep(dwell)
g1 = global_ptrs()
moved = [(o, g0[o], g1[o]) for o in g0
if o in g1 and g1[o] != g0[o]]
named = []
for o, v0, v1 in moved[:4]:
n0 = ptr_map.get(v0, ("?",))[0]
n1 = ptr_map.get(v1, ("?",))[0]
named.append(f"{gmem.primary_va(o):#010x} {n0[:16]}->{n1[:16]}")
print(f" [{btn:<5}] {len(moved):4d} switched " + " ".join(named),
flush=True)
g0 = g1
pad.reset()
return
print("\n# tapping each input; a word that switches to a DIFFERENT entity is"
" the selection")
for btn in BUTTONS:
before = window()
pad.f.write(f"tap {btn} 200\n")
time.sleep(dwell)
after = window()
moved = []
for i, nm, d in cands:
if before[i] == after[i]:
continue
hit = ptr_map.get(int(after[i]))
moved.append((i, nm, hit[0] if hit else f"{int(after[i]):#010x}"))
tag = " ".join(f"pos{i * 4 - BACK:+#x} {o[:18]}->{n[:18]}"
for i, o, n in moved[:3])
print(f" [{btn:<5}] {len(moved):2d} changed {tag}", flush=True)
pad.reset()
if __name__ == "__main__":
main()