re(flight): the afterburner is not on A/B/X/LB/LS/RS — bounded negative, with a cheap HUD oracle
The definition describes the burner (AB_ConsumeShield_Begin 50, AB_ConsumeShield 10, AB_AV_* turn caps well below normal) but names no input, and carries no AB velocity field. Three probes, all negative for A, B, X, LB (plus LS/RS on the first): - ab_probe.py: hold RT for a max-speed baseline, then each candidate — speed stayed inside the baseline's own noise band every time. - ab_state_probe.py: sample a window of the player object during each hold and report any float that falls — nothing fell. - HUD oracle needing no offsets: count green pixels of the SHIELD bar on a freshly spawned craft. AB_ConsumeShield_Begin 50 should take a visible bite; the bar read 156/156/156/157/157 across baseline and all four buttons. So the burner needs a chord, an input this pad cannot reach, or belongs to another craft/the AI. Recorded so the obvious buttons are not re-probed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NptfmpjdpNCKEez6d2xvA9
This commit is contained in:
@@ -100,3 +100,34 @@ pitching — but the yaw fields may equally belong to the AI or to an input this
|
||||
has not driven.
|
||||
|
||||
Raw samples: [`captures/turn-law-pitch-phases.csv`](captures/turn-law-pitch-phases.csv).
|
||||
|
||||
---
|
||||
|
||||
# The afterburner is not on the obvious buttons — a bounded negative
|
||||
|
||||
The definition describes the burner without naming its input: `AB_ConsumeShield_Begin`
|
||||
**50**, `AB_ConsumeShield` **10**, and a set of `AB_AV_*` turn caps far below the
|
||||
normal ones (roll 40 vs 125–200 °/s, pitch-up 30 vs 70–150). Notably there is **no
|
||||
`AB_*Velocity`**, so the burner may not raise the speed target at all.
|
||||
|
||||
Two independent probes, both negative for `A`, `B`, `X`, `LB` (and `LS`/`RS` on the
|
||||
first):
|
||||
|
||||
* [`tools/re-capture/ab_probe.py`](../../tools/re-capture/ab_probe.py) — hold `RT`
|
||||
for a max-speed baseline, then hold each candidate: speed stayed inside the
|
||||
baseline's own noise band (1 200–1 580 units/s) for every one.
|
||||
* [`tools/re-capture/ab_state_probe.py`](../../tools/re-capture/ab_state_probe.py) —
|
||||
sample a window of the player object while each candidate is held and report any
|
||||
float that falls during the hold: **nothing fell**.
|
||||
* And the cheapest oracle of the three, needing no offsets at all: **count the green
|
||||
pixels of the HUD's SHIELD bar** before and after each hold, on a freshly spawned
|
||||
craft with a full shield. `AB_ConsumeShield_Begin 50` should take a visible bite;
|
||||
the bar read **156, 156, 156, 157, 157** across baseline and all four buttons.
|
||||
|
||||
So the burner is **not a simple hold on `A`/`B`/`X`/`LB`/`LS`/`RS`**. What remains:
|
||||
a chord (something plus `RT`), an input this pad cannot reach, a craft variant that
|
||||
has it, or fields that belong to the AI rather than the player. Worth knowing before
|
||||
anyone re-probes the obvious buttons.
|
||||
|
||||
(`RB` is the nose gun, `Y` the missile mount and the d-pad the tactical map — see
|
||||
[flight controls](flight-controls-runtime.md) — so those were not held here.)
|
||||
|
||||
65
tools/re-capture/ab_probe.py
Normal file
65
tools/re-capture/ab_probe.py
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Which input is the AFTERBURNER, and what does it do?
|
||||
|
||||
The definition carries `AB_ConsumeShield_Begin 50`, `AB_ConsumeShield 10` and a set
|
||||
of `AB_AV_*` turn caps far below the normal ones (roll 40 vs 125-200 °/s, pitch-up
|
||||
30 vs 70-150) — so the burner should cost shield and cut agility. It carries no
|
||||
`AB_*Velocity`, so whether it raises the speed target at all is an open question.
|
||||
|
||||
Method: hold `RT` (max-speed baseline), then hold each candidate button in turn and
|
||||
measure speed over 1-second windows. A button that pushes speed past the RT plateau
|
||||
is the burner. Buttons known to do something else are skipped: `RB` is the nose gun,
|
||||
`Y` the missile mount, the d-pad the tactical map (see flight-controls-runtime.md).
|
||||
|
||||
Usage: ab_probe.py <out.csv> [hold_s]
|
||||
"""
|
||||
import math, os, subprocess, sys, time
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import speed_law
|
||||
|
||||
def pad(*a):
|
||||
subprocess.run(["vgamepad", *a], capture_output=True)
|
||||
|
||||
def windowed(seq, lo, hi):
|
||||
a = [s for s in seq if s[0] >= lo][0]
|
||||
b = [s for s in seq if s[0] <= hi][-1]
|
||||
return math.dist(a[1:], b[1:]) / (b[0] - a[0])
|
||||
|
||||
def main():
|
||||
out_csv = sys.argv[1]
|
||||
hold = float(sys.argv[2]) if len(sys.argv) > 2 else 5.0
|
||||
w, off, nm = speed_law.find_player()
|
||||
if not w:
|
||||
sys.exit("player entity not found after retries")
|
||||
print(f"# locked on {nm}")
|
||||
rows = []
|
||||
pad("trig", "RT", "1.0") # max-speed baseline for every phase
|
||||
time.sleep(2.0)
|
||||
for label in ("baseline", "A", "B", "X", "LB", "baseline2"):
|
||||
if label.startswith("baseline"):
|
||||
pass
|
||||
else:
|
||||
pad("press", label)
|
||||
seq, t0 = [], time.time()
|
||||
while time.time() - t0 < hold:
|
||||
p = speed_law.pos_at(w.fd, off)
|
||||
seq.append((round(time.time() - t0, 3), *p))
|
||||
time.sleep(0.05)
|
||||
if not label.startswith("baseline"):
|
||||
pad("release", label)
|
||||
for s in seq:
|
||||
rows.append((label, *s))
|
||||
try:
|
||||
v = [windowed(seq, t, t + 1.0) for t in range(int(hold) - 1)]
|
||||
print(f"# {label:<10} speed per 1 s window: " + " ".join(f"{x:6.0f}" for x in v))
|
||||
except Exception as e:
|
||||
print(f"# {label:<10} failed: {e}")
|
||||
pad("trig", "RT", "0.0"); pad("reset")
|
||||
with open(out_csv, "w") as f:
|
||||
f.write("phase,t,x,y,z\n")
|
||||
for r in rows:
|
||||
f.write(",".join(str(x) for x in r) + "\n")
|
||||
print(f"# wrote {out_csv}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
53
tools/re-capture/ab_state_probe.py
Normal file
53
tools/re-capture/ab_state_probe.py
Normal file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Find the afterburner by what it COSTS, not by what it speeds up.
|
||||
|
||||
`ab_probe.py` held A/B/X/LB at full throttle and none of them moved the speed —
|
||||
so either the burner is a different input or it does not raise the speed target
|
||||
(the definition has no `AB_*Velocity`, only `AB_AV_*` turn caps and
|
||||
`AB_ConsumeShield{,_Begin}` 10 / 50). This looks for the cost instead: sample a
|
||||
window of the player object while each candidate is held, and report any float
|
||||
that FALLS during the hold and not before it — `AB_ConsumeShield_Begin 50` should
|
||||
be a visible step.
|
||||
|
||||
Usage: ab_state_probe.py [hold_s]
|
||||
"""
|
||||
import os, struct, subprocess, sys, time
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import speed_law
|
||||
|
||||
WIN_LO, WIN_HI = 0x100, 0x220 # object window, relative to the position triple
|
||||
|
||||
def pad(*a):
|
||||
subprocess.run(["vgamepad", *a], capture_output=True)
|
||||
|
||||
def snap(fd, off):
|
||||
b = os.pread(fd, WIN_HI - WIN_LO, off + WIN_LO)
|
||||
return [struct.unpack_from(">f", b, i)[0] for i in range(0, len(b) - 3, 4)]
|
||||
|
||||
def main():
|
||||
hold = float(sys.argv[1]) if len(sys.argv) > 1 else 4.0
|
||||
w, off, nm = speed_law.find_player()
|
||||
if not w:
|
||||
sys.exit("player entity not found after retries")
|
||||
print(f"# locked on {nm}; window {WIN_LO:#x}..{WIN_HI:#x}")
|
||||
pad("trig", "RT", "1.0")
|
||||
time.sleep(1.5)
|
||||
for btn in ("LS", "RS", "A", "B", "X", "LB"):
|
||||
before = snap(w.fd, off)
|
||||
pad("press", btn)
|
||||
time.sleep(hold)
|
||||
during = snap(w.fd, off)
|
||||
pad("release", btn)
|
||||
time.sleep(1.0)
|
||||
after = snap(w.fd, off)
|
||||
drops = []
|
||||
for i, (b, d, a) in enumerate(zip(before, during, after)):
|
||||
# a float that fell while held and did not fall further after release
|
||||
if b - d > 1.0 and (a - d) > -1.0 and abs(b) < 1e6:
|
||||
drops.append((WIN_LO + i * 4, round(b, 1), round(d, 1), round(a, 1)))
|
||||
tag = ", ".join(f"{o:#05x}: {b}->{d}->{a}" for o, b, d, a in drops[:4]) or "nothing fell"
|
||||
print(f"# {btn:<3} {tag}")
|
||||
pad("trig", "RT", "0.0"); pad("reset")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user