re-capture: read the ADVANCED CONTROLS tutorial; its moves regress the pilot, so they ship off
tutorial_capture.sh plays a tutorial and photographs what it teaches. ADVANCED
CONTROLS states three mechanics the key-config screen only named:
B + LS Side Roll / 180 Degree Turn / Level Off
B + A together face the target (snap turn)
LT + RT together 'sets your fighter's speed to that of the target ... works
well when you are trying to get behind an enemy. Once
behind an enemy, this also helps you attack them.'
Wired both usable ones in and measured, one run each, everything else equal:
commitment only ............ 101 missiles, 364 fire frames, 9 kills
+ match(4500) + snap-face .... 9 missiles, 28 fire frames, 0 kills
+ match(1200) + snap-face ... 57 missiles, 225 fire frames, 2 kills
So both are a net regression as applied, and both now default to OFF. Matching a
target's speed while still 5 km behind means never closing (the pilot sat at
272 u/s all run) — the tutorial scopes it to being already in the saddle. The
B+A snap turn reorients mid-pursuit and destroys the dwell commitment buys.
The code and thresholds stay so a future session can re-enable and A/B them over
SEVERAL runs; one run per config is inside this stage's spawn variance.
This commit is contained in:
BIN
docs/re/captures/tutorial-advanced-controls-captions.png
Normal file
BIN
docs/re/captures/tutorial-advanced-controls-captions.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 626 KiB |
@@ -129,6 +129,37 @@ class Pilot:
|
||||
COMMIT_MAX = 14.0 # s before we are allowed to reconsider anyway
|
||||
COMMIT_DROP = 6000.0 # ...or it gets this far away
|
||||
COMMIT_BEHIND = 2.5 # ...or stays >90 deg off the nose this long
|
||||
# --- moves the ADVANCED CONTROLS tutorial teaches (tutorial_capture.sh) ---
|
||||
# "Target an enemy and pull LT and RT [together]. This sets your fighter's
|
||||
# speed to that of the target. This works well when you are trying to get
|
||||
# behind an enemy. Once behind an enemy, this also helps you attack them."
|
||||
# That is the overshoot problem solved by the game itself: matching speed
|
||||
# holds us in the target's rear hemisphere instead of flying through it,
|
||||
# which is the only way a time-on-target lock ever completes.
|
||||
# Scope matters, and a measured regression proved it: applying the match at
|
||||
# 4500 with a 70 deg cone dropped kills 9 -> 0. Matching a target's speed
|
||||
# while still 5 km behind it means never closing — the pilot sat at 272 u/s
|
||||
# and fired 28 frames all run. The tutorial's own wording scopes it: "when
|
||||
# you are trying to get BEHIND an enemy... ONCE BEHIND an enemy, this also
|
||||
# helps you attack them". So it is station-keeping in the saddle, not an
|
||||
# approach throttle. Only match when we are already there.
|
||||
# MEASURED: both tutorial moves are a NET REGRESSION as applied here, so
|
||||
# both ship DISABLED. One run each, same everything else:
|
||||
# commitment only ............ 101 missiles, 364 fire frames, 9 kills
|
||||
# + match(4500) + snap-face .... 9 missiles, 28 fire frames, 0 kills
|
||||
# + match(1200) + snap-face ... 57 missiles, 225 fire frames, 2 kills
|
||||
# The moves are real and the tutorial is right about them; the loop just
|
||||
# cannot use them yet. Snap-face (B+A) reorients the craft mid-pursuit and
|
||||
# destroys the very dwell that commitment buys, and speed-match needs to be
|
||||
# entered from the saddle rather than commanded at range. Set MATCH_RANGE
|
||||
# and lower FACE_MIN to re-enable, and A/B them over SEVERAL runs — one run
|
||||
# per config is inside this stage's spawn variance.
|
||||
MATCH_RANGE = 0.0 # 1200.0 to re-enable
|
||||
MATCH_CONE = math.radians(25)
|
||||
# "Press B and A together to face [the target]" — a snap turn, far quicker
|
||||
# than winding the PD controller around for a contact behind us.
|
||||
FACE_MIN = math.radians(999) # 50 deg to re-enable the B+A snap turn
|
||||
FACE_PERIOD = 4.0
|
||||
|
||||
def __init__(self, W, pad, dry=False, log=sys.stdout):
|
||||
self.W = W
|
||||
@@ -158,6 +189,10 @@ class Pilot:
|
||||
self.commit_off = None # entity we are committed to
|
||||
self.commit_t = -1e9
|
||||
self.behind_since = None
|
||||
self.matching = False
|
||||
self.face_t = -1e9
|
||||
self.face_down = None
|
||||
self.faces = 0
|
||||
|
||||
def f32(self, off):
|
||||
b = os.pread(self.W.fd, 4, off)
|
||||
@@ -204,13 +239,31 @@ class Pilot:
|
||||
return hull, shield
|
||||
|
||||
def set_throttle(self, want):
|
||||
"""RT / LT are a persistent setting, so only send the change."""
|
||||
"""RT / LT are a persistent setting, so only send the change.
|
||||
|
||||
`want` is +1 accelerate, -1 brake, 0 coast, or the string "match" for
|
||||
the tutorial's both-triggers speed-match onto the current target.
|
||||
"""
|
||||
if want == self.throttle or self.dry:
|
||||
return
|
||||
self.pad.trig("RT", 1.0 if want > 0 else 0.0)
|
||||
self.pad.trig("LT", 1.0 if want < 0 else 0.0)
|
||||
if want == "match":
|
||||
self.pad.trig("RT", 1.0)
|
||||
self.pad.trig("LT", 1.0)
|
||||
else:
|
||||
self.pad.trig("RT", 1.0 if want > 0 else 0.0)
|
||||
self.pad.trig("LT", 1.0 if want < 0 else 0.0)
|
||||
self.throttle = want
|
||||
|
||||
def face_target(self, t):
|
||||
"""B + A: snap the nose onto the selected target."""
|
||||
if self.dry or t - self.face_t < self.FACE_PERIOD:
|
||||
return
|
||||
self.pad.press("B")
|
||||
self.pad.press("A")
|
||||
self.face_down = t
|
||||
self.face_t = t
|
||||
self.faces += 1
|
||||
|
||||
# -------------------------------------------------------------- targets
|
||||
def hostiles(self, ents, me_off):
|
||||
out = []
|
||||
@@ -439,10 +492,10 @@ class Pilot:
|
||||
# than a stall in the middle of a battle; collision avoidance already
|
||||
# keeps a fighter-sized margin.
|
||||
want = norm(tgt[3]) if tgt else fwd
|
||||
if tgt and tgt[4] > 2500.0:
|
||||
if tgt and tgt[4] < self.MATCH_RANGE and ang(tgt[3], fwd) < self.MATCH_CONE:
|
||||
self.set_throttle("match") # sit in its rear hemisphere
|
||||
elif tgt and tgt[4] > 2500.0:
|
||||
self.set_throttle(+1)
|
||||
elif tgt and tgt[4] < 500.0 and speed > 900.0:
|
||||
self.set_throttle(-1)
|
||||
else:
|
||||
self.set_throttle(0)
|
||||
fire = True
|
||||
@@ -503,6 +556,13 @@ class Pilot:
|
||||
if fire != self.firing:
|
||||
(self.pad.press if fire else self.pad.release)("RB")
|
||||
self.firing = fire
|
||||
if self.face_down is not None and t - self.face_down > 0.2:
|
||||
self.pad.release("B")
|
||||
self.pad.release("A")
|
||||
self.face_down = None
|
||||
elif (tgt and self.mode in ("ENGAGE", "DEFEND")
|
||||
and abs(aim[0]) > self.FACE_MIN and pn < 1.2):
|
||||
self.face_target(t)
|
||||
if self.missile_down and t - self.missile_t > 0.15:
|
||||
self.pad.release("Y")
|
||||
self.missile_down = False
|
||||
@@ -514,9 +574,10 @@ class Pilot:
|
||||
self.missiles += 1
|
||||
|
||||
msg = (f"{self.mode:<7} hull={hull:6.0f} shd={shield:6.0f} spd={speed:6.0f} "
|
||||
f"thr={self.throttle:+d} yaw={math.degrees(yaw):+6.1f} "
|
||||
f"thr={str(self.throttle):>5} yaw={math.degrees(yaw):+6.1f} "
|
||||
f"pit={math.degrees(pitch):+6.1f} aim={math.degrees(aim[0]):+6.1f}"
|
||||
f"/{math.degrees(aim[1]):+6.1f} fire={int(fire)} msl={self.missiles}")
|
||||
f"/{math.degrees(aim[1]):+6.1f} fire={int(fire)} msl={self.missiles}"
|
||||
f" fc={self.faces}")
|
||||
if ast is not None:
|
||||
msg += f" ast={a_frac*100:5.1f}%"
|
||||
if a_dmg > self.ASSET_ALERT:
|
||||
|
||||
64
tools/re-capture/tutorial_capture.sh
Executable file
64
tools/re-capture/tutorial_capture.sh
Executable file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
# Play one TUTORIAL and photograph what it teaches.
|
||||
#
|
||||
# The OPTIONS key-config screen names the in-flight actions but not the buttons
|
||||
# they sit on, and probing the pad found the weapons only (an action that does
|
||||
# not fire a gun is invisible in the ammo counters). The tutorials state the
|
||||
# mapping outright — `ADVANCED CONTROLS` is index 5 and is where Change Target
|
||||
# and Padlock Mode live — so read it from the game instead of guessing.
|
||||
#
|
||||
# Boot + nav is launch_mission.sh's route as far as the main menu, then TUTORIAL
|
||||
# instead of LOAD GAME. Everything stays a CHILD of this script (no setsid): a
|
||||
# detached process is not a survivable one here, see the session-lifetime note.
|
||||
set -u
|
||||
N="${1:-5}" # 0 BASIC, 1 HUD, 2 RADAR, 3 SUPPLY, 4 RADIO, 5 ADVANCED
|
||||
SECS="${2:-150}"
|
||||
TAG="${3:-tut}"
|
||||
export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98
|
||||
SD="$(cd "$(dirname "$0")" && pwd)"
|
||||
SHOTS=/sylph-home/re/shots
|
||||
|
||||
alive(){ ps -o pid=,stat= -C xenia_canary 2>/dev/null | awk '$2 !~ /^Z/ {print $1}'; }
|
||||
step(){ vgamepad dpad "$1"; sleep 0.25; vgamepad dpad center; sleep 0.7; }
|
||||
shot(){ screenshot "$SHOTS/$TAG-$1.png" >/dev/null 2>&1; }
|
||||
|
||||
pkill -x xenia_canary 2>/dev/null; sleep 2
|
||||
[ -n "$(alive)" ] && { kill -9 $(alive) 2>/dev/null; sleep 2; }
|
||||
rm -f /dev/shm/xenia_memory_* /dev/shm/xenia_code_cache_* 2>/dev/null
|
||||
|
||||
if ! xdpyinfo -display "$DISPLAY" >/dev/null 2>&1; then
|
||||
rm -f "/tmp/.X${DISPLAY#:}-lock" 2>/dev/null || true
|
||||
nohup Xvfb "$DISPLAY" -screen 0 1280x720x24 -ac -nolisten tcp \
|
||||
+extension GLX +extension RANDR </dev/null >/tmp/xvfb98.log 2>&1 &
|
||||
for _ in $(seq 1 50); do xdpyinfo -display "$DISPLAY" >/dev/null 2>&1 && break; sleep 0.2; done
|
||||
nohup env DISPLAY="$DISPLAY" HOME=/sylph-home openbox </dev/null >/tmp/openbox98.log 2>&1 &
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
cd /sylph-home/re
|
||||
nohup run-canary --audio --apu=sdl --log_mask=13 \
|
||||
--logged_profile_slot_0_xuid=E0300000EFBEA3D4 </dev/null >/dev/null 2>&1 &
|
||||
sleep 5
|
||||
"$SD/skip_intro.sh" 600 || { echo "BOOT FAILED (skip_intro exit $?)"; exit 1; }
|
||||
sleep 14 # main menu is not input-ready before this
|
||||
|
||||
step down; step down # NEW GAME -> LOAD GAME -> TUTORIAL
|
||||
vgamepad tap A 250; sleep 8
|
||||
shot "list"
|
||||
i=0; while [ "$i" -lt "$N" ]; do step down; i=$((i+1)); done
|
||||
shot "pick"
|
||||
vgamepad tap A 250; sleep 6
|
||||
shot "sub" # some tutorials offer Level 1 / Level 2
|
||||
vgamepad tap A 250
|
||||
|
||||
# Then just watch. The lesson drives itself and prints its instructions; tap A
|
||||
# periodically to advance any prompt, and photograph often enough to catch the
|
||||
# caption before it is replaced.
|
||||
end=$(( SECONDS + SECS )); n=0
|
||||
while [ $SECONDS -lt $end ]; do
|
||||
n=$((n+1)); shot "$(printf '%02d' $n)"
|
||||
[ -n "$(alive)" ] || { echo "EMULATOR GONE at ${SECONDS}s"; exit 4; }
|
||||
sleep 5
|
||||
[ $((n % 3)) -eq 0 ] && vgamepad tap A 250
|
||||
done
|
||||
echo "TUTORIAL CAPTURE DONE ($n frames)"
|
||||
Reference in New Issue
Block a user