re(flight): axis probe pins the rows and checks liveness; fly_stage waits, not sleeps
Both fixes the previous run's caveats asked for, plus one the run itself forced. axis_probe.py now: - PINS which non-forward row is up and which is right, by comparing world-Y across the rows in level flight, and says CONFIDENT or WEAK. entities2 measures row 2 = forward against velocity, but the other two were labelled by the D3D convention, and yaw/pitch SWAP if that is wrong -- so the previous run's last two columns were named on an assumption. - checks the craft is ALIVE between inputs, and ABORTS with a message instead of reporting the clean zeros a destroyed craft produces. The first run ended on GAME OVER and only said so afterwards. - measures the UNKNOWN inputs (rx, ry, LB, RB) first while the craft is healthy, keeping the established lx/ly as controls at the end. fly_stage.sh now WAITS for the stage load instead of sleeping a fixed guess. The fixed sleeps worked until they didn't: one load ran long, the script pressed START into a black screen, and every later step went to nothing while the screenshots recorded a plausible-looking sequence. It now polls for a non-black frame and aborts with a pointer to the log if the load hangs (PhysicalHeap::Release failures) rather than continuing blind. The probe itself did not run this iteration -- the stage load hung -- so there is no new axis data, and none is claimed.
This commit is contained in:
@@ -52,13 +52,16 @@ PAD_FILE = os.environ.get("XENIA_PAD_FILE", "/tmp/xenia_pad.txt")
|
||||
|
||||
# Candidates. Sticks at full deflection, shoulders as buttons. Triggers are the
|
||||
# throttle (already settled) and are left out.
|
||||
# Order matters: the craft can be shot down mid-run, so the UNKNOWN inputs go
|
||||
# first while it is healthy and the already-established ones (lx = roll) go last
|
||||
# as controls. A row measured after death looks like a clean zero.
|
||||
CANDIDATES = [
|
||||
("lx+", {"lx": 32767}),
|
||||
("ly+", {"ly": 32767}),
|
||||
("rx+", {"rx": 32767}),
|
||||
("ry+", {"ry": 32767}),
|
||||
("LB", {"press": "LB"}),
|
||||
("RB", {"press": "RB"}),
|
||||
("lx+", {"lx": 32767}),
|
||||
("ly+", {"ly": 32767}),
|
||||
]
|
||||
|
||||
|
||||
@@ -84,6 +87,46 @@ def dot(a, b):
|
||||
return sum(a[i] * b[i] for i in range(3))
|
||||
|
||||
|
||||
def alive(w, off, cfg, secs=1.0):
|
||||
"""Is the craft still flying? A destroyed craft stops moving, and every
|
||||
subsequent input then measures a clean, meaningless zero — the failure mode
|
||||
that invalidated this probe's first run (it ended on GAME OVER and only said
|
||||
so afterwards)."""
|
||||
p0 = struct.unpack(">3f", os.pread(w.fd, 12, off))
|
||||
time.sleep(secs)
|
||||
p1 = struct.unpack(">3f", os.pread(w.fd, 12, off))
|
||||
return math.dist(p0, p1) > 1.0
|
||||
|
||||
|
||||
def pin_rows(w, off, cfg):
|
||||
"""Which non-forward row is UP and which is RIGHT?
|
||||
|
||||
`entities2` measures row 2 = forward against the velocity vector, but the other
|
||||
two are assigned by the D3D convention rather than evidence — and yaw and pitch
|
||||
SWAP if that is wrong, so naming them without this test is a guess.
|
||||
|
||||
Discriminator: in level flight the craft's up-vector points along world +Y and
|
||||
its right-vector lies near the horizontal plane. Sample at neutral and compare
|
||||
|world-Y| across the rows."""
|
||||
f_i = cfg["fwd_row"]
|
||||
acc = [0.0, 0.0, 0.0]
|
||||
n = 0
|
||||
for _ in range(20):
|
||||
time.sleep(0.05)
|
||||
rs = rows_at(w.fd, off, cfg)
|
||||
for r in range(3):
|
||||
acc[r] += rs[r][1] # world Y component
|
||||
n += 1
|
||||
ys = [a / n for a in acc]
|
||||
cand = [r for r in (0, 1, 2) if r != f_i]
|
||||
up_i = max(cand, key=lambda r: abs(ys[r]))
|
||||
right_i = [r for r in cand if r != up_i][0]
|
||||
print(f"# row world-Y means: {[round(y,3) for y in ys]} (forward = row {f_i})")
|
||||
print(f"# -> up = row {up_i}, right = row {right_i}"
|
||||
f" {'CONFIDENT' if abs(ys[up_i]) - abs(ys[right_i]) > 0.3 else 'WEAK — craft may not be level'}")
|
||||
return up_i, right_i
|
||||
|
||||
|
||||
def main():
|
||||
out_csv = sys.argv[1]
|
||||
dwell = float(sys.argv[2]) if len(sys.argv) > 2 else 5.0
|
||||
@@ -93,7 +136,9 @@ def main():
|
||||
sys.exit("player entity not found")
|
||||
print(f"# locked on {nm}")
|
||||
f_i = cfg["fwd_row"]
|
||||
u_i, w_i = [r for r in (0, 1, 2) if r != f_i]
|
||||
if not alive(w, off, cfg):
|
||||
sys.exit("craft is not moving at the start — not in flight, or already dead")
|
||||
u_i, w_i = pin_rows(w, off, cfg)
|
||||
|
||||
rows = []
|
||||
print(f"# {'input':<6} {'roll':>9} {'yaw':>9} {'pitch':>9} (deg over the dwell)")
|
||||
@@ -115,6 +160,10 @@ def main():
|
||||
prev = cur
|
||||
pad_state()
|
||||
wall = time.time() - t0
|
||||
if not alive(w, off, cfg):
|
||||
print(f"# CRAFT STOPPED MOVING after {label} — everything from here is "
|
||||
f"meaningless, aborting rather than reporting zeros")
|
||||
break
|
||||
rate = [v / wall for v in sw]
|
||||
rows.append((label, *[round(v, 2) for v in rate]))
|
||||
print(f"# {label:<6} {rate[0]:9.1f} {rate[1]:9.1f} {rate[2]:9.1f} deg/wall-s")
|
||||
|
||||
@@ -76,6 +76,21 @@ sleep 1; shot 03-selected
|
||||
pad tap A 0.20; sleep 3; shot 04-after-A
|
||||
pad tap A 0.20; sleep 3; shot 05-after-A2
|
||||
|
||||
# Wait for a screen instead of sleeping a guess. The fixed sleeps below used to be
|
||||
# enough and then were not: one run's stage load ran long, the script pressed START
|
||||
# into a black loading screen, and every later step went to nothing while the shots
|
||||
# recorded a plausible-looking sequence. Same lesson as the menu oracles.
|
||||
wait_not_black() { # $1 = timeout s
|
||||
for _ in $(seq 1 "$1"); do
|
||||
screenshot /tmp/nav-probe.png >/dev/null 2>&1 || { sleep 1; continue; }
|
||||
read -r r g b < <(px "640,360")
|
||||
read -r r2 g2 b2 < <(px "200,200")
|
||||
if [ -n "${r:-}" ] && [ $((r + g + b + r2 + g2 + b2)) -gt 60 ]; then return 0; fi
|
||||
sleep 1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Selecting a stage lands on the mission BRIEFING (A: Continue), which leads to the
|
||||
# READY ROOM (TAKE OFF / BRIEFINGS / HANGAR / ...) with BRIEFINGS pre-highlighted.
|
||||
# A snapshot taken at the briefing yields ZERO unit-definition objects — they are
|
||||
@@ -83,6 +98,14 @@ pad tap A 0.20; sleep 3; shot 05-after-A2
|
||||
# START (Skip) clears the briefing. `A` does NOT: it pages through the brief and
|
||||
# ten taps still left the run sitting on a briefing screen, twice. One START press
|
||||
# lands on the READY ROOM.
|
||||
say "waiting for the stage to finish loading"
|
||||
if ! wait_not_black 180; then
|
||||
say "STILL BLACK after 180s — the load hung (this happens; check the log for"
|
||||
say " 'PhysicalHeap::Release failed'), aborting instead of pressing into nothing"
|
||||
shot 06-stuck-black
|
||||
exit 2
|
||||
fi
|
||||
sleep 3
|
||||
say "skipping the briefing (START, not A)"
|
||||
pad tap START 0.30; sleep 6
|
||||
shot 06-readyroom
|
||||
|
||||
Reference in New Issue
Block a user