#!/usr/bin/env python3 """Locate the player's flight object via its speed scalar, then its position. The HUD prints the craft's speed, so it is a known quantity we can *change on demand* — the classic two-state value scan, which is far more reliable than hunting for a structure by shape: 1. coast -> RAM holds the cruise speed somewhere; collect every float ≈ it; 2. boost -> the real one rises; everything coincidental is filtered out; 3. coast -> it must come back down. Whatever survives all three is the player's speed. Its object then contains the position, which is confirmed independently: a position triple near that scalar must move at exactly the speed the scalar reports. Usage: findspeed.py [boost_wait] """ 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 FIFO = "/tmp/sylph-vgamepad.fifo" def pad(line): with open(FIFO, "w") as f: f.write(line + "\n") def extents_arrays(fd, size): for a, b in gmem.extents(fd, size): n = (b - a) // 4 * 4 if n >= 64: yield a, np.frombuffer(os.pread(fd, n, a), dtype=">f4") def scan_equal(fd, size, val, tol): hits = [] for a, arr in extents_arrays(fd, size): with np.errstate(invalid="ignore"): ok = np.isfinite(arr) & (np.abs(arr.astype(np.float64) - val) <= tol) for i in np.flatnonzero(ok): hits.append(a + int(i) * 4) return hits def read_f(fd, off): b = os.pread(fd, 4, off) return struct.unpack(">f", b)[0] if len(b) == 4 else float("nan") def main(): cruise = float(sys.argv[1]) wait = float(sys.argv[2]) if len(sys.argv) > 2 else 4.0 fd = os.open(gmem.mem_path(), os.O_RDONLY) size = os.path.getsize(gmem.mem_path()) pad("reset") time.sleep(2.0) c0 = scan_equal(fd, size, cruise, 2.0) print(f"# pass 1 (coast {cruise}): {len(c0)} candidates", flush=True) pad("trig RT 1.0") time.sleep(wait) c1 = [o for o in c0 if read_f(fd, o) > cruise + 40] print(f"# pass 2 (boost): {len(c1)} rose above {cruise+40:.0f}", flush=True) vals = {o: read_f(fd, o) for o in c1} pad("reset") time.sleep(wait + 2.0) c2 = [o for o in c1 if abs(read_f(fd, o) - cruise) <= 6.0] print(f"# pass 3 (coast again): {len(c2)} returned to ≈{cruise}", flush=True) for o in c2[:20]: print(f" va {gmem.primary_va(o):#010x} boost value was {vals[o]:.1f}") if not c2: print("# no survivors — is the craft actually accelerating?") return # position triple in the same object: must move at exactly that speed print("\n# looking for a position triple near each survivor", flush=True) RAD = 0x400 for o in c2[:8]: lo = max(0, o - RAD) n = RAD * 2 t0 = time.time() a0 = np.frombuffer(os.pread(fd, n, lo), dtype=">f4").astype(np.float64) time.sleep(0.4) t1 = time.time() a1 = np.frombuffer(os.pread(fd, n, lo), dtype=">f4").astype(np.float64) sp_now = read_f(fd, o) dt = t1 - t0 best = [] for i in range(len(a0) - 2): d = a1[i:i + 3] - a0[i:i + 3] if not np.all(np.isfinite(d)): continue v = float(np.linalg.norm(d)) / dt if abs(v - sp_now) <= max(8.0, 0.06 * sp_now): best.append((lo + i * 4, v, tuple(a0[i:i + 3]))) print(f" survivor va {gmem.primary_va(o):#010x} (speed {sp_now:.1f}): " f"{len(best)} matching triples") for off, v, p in best[:4]: print(f" pos va {gmem.primary_va(off):#010x} delta-off " f"{off - o:+#07x} |v|={v:7.1f} ({p[0]:+9.1f},{p[1]:+9.1f},{p[2]:+9.1f})") if __name__ == "__main__": main()