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
|
# Candidates. Sticks at full deflection, shoulders as buttons. Triggers are the
|
||||||
# throttle (already settled) and are left out.
|
# 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 = [
|
CANDIDATES = [
|
||||||
("lx+", {"lx": 32767}),
|
|
||||||
("ly+", {"ly": 32767}),
|
|
||||||
("rx+", {"rx": 32767}),
|
("rx+", {"rx": 32767}),
|
||||||
("ry+", {"ry": 32767}),
|
("ry+", {"ry": 32767}),
|
||||||
("LB", {"press": "LB"}),
|
("LB", {"press": "LB"}),
|
||||||
("RB", {"press": "RB"}),
|
("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))
|
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():
|
def main():
|
||||||
out_csv = sys.argv[1]
|
out_csv = sys.argv[1]
|
||||||
dwell = float(sys.argv[2]) if len(sys.argv) > 2 else 5.0
|
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")
|
sys.exit("player entity not found")
|
||||||
print(f"# locked on {nm}")
|
print(f"# locked on {nm}")
|
||||||
f_i = cfg["fwd_row"]
|
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 = []
|
rows = []
|
||||||
print(f"# {'input':<6} {'roll':>9} {'yaw':>9} {'pitch':>9} (deg over the dwell)")
|
print(f"# {'input':<6} {'roll':>9} {'yaw':>9} {'pitch':>9} (deg over the dwell)")
|
||||||
@@ -115,6 +160,10 @@ def main():
|
|||||||
prev = cur
|
prev = cur
|
||||||
pad_state()
|
pad_state()
|
||||||
wall = time.time() - t0
|
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]
|
rate = [v / wall for v in sw]
|
||||||
rows.append((label, *[round(v, 2) for v in rate]))
|
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")
|
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 04-after-A
|
||||||
pad tap A 0.20; sleep 3; shot 05-after-A2
|
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
|
# Selecting a stage lands on the mission BRIEFING (A: Continue), which leads to the
|
||||||
# READY ROOM (TAKE OFF / BRIEFINGS / HANGAR / ...) with BRIEFINGS pre-highlighted.
|
# READY ROOM (TAKE OFF / BRIEFINGS / HANGAR / ...) with BRIEFINGS pre-highlighted.
|
||||||
# A snapshot taken at the briefing yields ZERO unit-definition objects — they are
|
# 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
|
# 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
|
# ten taps still left the run sitting on a briefing screen, twice. One START press
|
||||||
# lands on the READY ROOM.
|
# 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)"
|
say "skipping the briefing (START, not A)"
|
||||||
pad tap START 0.30; sleep 6
|
pad tap START 0.30; sleep 6
|
||||||
shot 06-readyroom
|
shot 06-readyroom
|
||||||
|
|||||||
Reference in New Issue
Block a user