pilot: RB fires, Y is the main mount, and guided missiles produce the first kills
fire_probe.sh holds each pad input in flight and photographs the HUD ammo counters. RB moves NOSE BM 06000 -> 05956 in 4 s (~11 rounds/s, HEAT rises); Y moves MAIN MPM 00300 -> 00299; nothing else moves either counter. So the control mapping is measured rather than assumed, and 'we never shoot' is dead: we shoot and miss. The disc data says to stop shooting: Shell_TCAF_DeltaSaber_Missile_P is Power 200, GuidanceType 5 (guided), MaximumRange 5000, versus the nose gun's Power 15 unguided — one missile is worth ~14 gun hits on a 500 HP fighter and it steers itself. Launching them (press Y, release a tick later, >=2 s apart) produced YOU KILLED: WARPLANES 0002 — the first non-zero kill counter of the series, against 0000 in all five gun-only runs, with hostiles down 134 -> 104. Still only 2 kills per 98 missiles (~2%). Likely cause: the game expects a lock before launch and an unlocked missile is wasted. Reading the lock state out of RAM is the next step.
This commit is contained in:
BIN
docs/re/captures/fire-probe-ammo-counters.png
Normal file
BIN
docs/re/captures/fire-probe-ammo-counters.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 MiB |
BIN
docs/re/captures/first-missile-kills.png
Normal file
BIN
docs/re/captures/first-missile-kills.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 215 KiB |
@@ -147,10 +147,35 @@ angular half-size **alone** (2.7° at 2584 units for a fighter) is far tighter t
|
|||||||
steering loop can hold the nose, and firing collapsed to 1 frame in 2639. Angular size
|
steering loop can hold the nose, and firing collapsed to 1 frame in 2639. Angular size
|
||||||
belongs in the gate as a **floor** that opens it up close, never as a cap.
|
belongs in the gate as a **floor** that opens it up close, never as a cap.
|
||||||
|
|
||||||
**Open (next):** find out why nothing dies. The cheap decisive test is whether the
|
### Why nothing died — settled by probe, then fixed
|
||||||
`RB` command discharges the gun at all — the HUD carries a live ammo count
|
|
||||||
(`MAIN MPM 00300`, which the weapon RE matched to `LoadingCount`), so holding fire and
|
`fire_probe.sh` holds each pad input in turn in flight and photographs the HUD ammo
|
||||||
watching that number settles "we never shoot" vs "we shoot and miss" in one run.
|
counters. Result:
|
||||||
|
|
||||||
|
| input | `NOSE BM` | `MAIN MPM` |
|
||||||
|
|---|---|---|
|
||||||
|
| idle | 06000 | 00300 |
|
||||||
|
| **RB** | **05956** (−44 in 4 s, HEAT rises) | 00300 |
|
||||||
|
| **Y** | 05951 | **00299** (−1) |
|
||||||
|
| LB / X / B / A / RT / LT | no change | no change |
|
||||||
|
|
||||||
|
So **`RB` is the nose gun (~11 rounds/s) and `Y` is the main mount** — measured, not
|
||||||
|
assumed — and the "we never shoot" hypothesis is dead: **we shoot and miss.**
|
||||||
|
|
||||||
|
Which is what the disc data says to stop doing. `Shell_TCAF_DeltaSaber_Missile_P` is
|
||||||
|
**Power 200, `GuidanceType` 5 (guided), `MaximumRange` 5000**, against the nose gun's
|
||||||
|
**Power 15, unguided**. One missile is worth ~14 gun hits on a 500 HP fighter *and it
|
||||||
|
steers itself* — the accuracy problem solved rather than tuned. (`ASMissile_P` is
|
||||||
|
Power **5000**, the anti-ship option.)
|
||||||
|
|
||||||
|
Adding missile launches to the pilot (press `Y`, release a tick later, ≥2 s apart)
|
||||||
|
produced **the first kills of the whole series: `YOU KILLED: WARPLANES 0002`**, versus
|
||||||
|
`0000` in all five gun-only runs, with hostiles down 134 → 104 (the largest fall yet).
|
||||||
|
|
||||||
|
**Still poor, and stated as such: 98 missiles for 2 kills (~2 %).** The likely cause is
|
||||||
|
that the game expects a *lock* — holding the target in the reticle before launch — and
|
||||||
|
an unlocked launch is wasted. Reading the lock state (or the lock timer) out of RAM is
|
||||||
|
the next step, and it is the same anchoring trick as everything else here.
|
||||||
|
|
||||||
## Reimplementation notes
|
## Reimplementation notes
|
||||||
|
|
||||||
|
|||||||
42
tools/re-capture/fire_probe.sh
Executable file
42
tools/re-capture/fire_probe.sh
Executable file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Does the pad actually DISCHARGE a weapon? Hold each candidate input in turn
|
||||||
|
# and photograph the HUD's ammo counters.
|
||||||
|
#
|
||||||
|
# Why this exists: the autopilot's HUD kill counters read WARSHIPS 0000 /
|
||||||
|
# WARPLANES 0000 at the end of every run, in every targeting configuration, so
|
||||||
|
# "fraction of frames with the guns commanded on" was never measuring anything.
|
||||||
|
# Before tuning aim any further, settle the prior question — whether the fire
|
||||||
|
# command reaches the gun at all. The HUD carries a live ammo count (`MAIN MPM
|
||||||
|
# 00300`, matched to `LoadingCount` by the weapon RE), so the counter falling
|
||||||
|
# during a hold is direct evidence of a discharge, and the counter sitting still
|
||||||
|
# through every button is direct evidence that we have never fired a shot.
|
||||||
|
#
|
||||||
|
# `RB fires` came from an earlier session; this re-tests it rather than assuming
|
||||||
|
# it, and sweeps the other buttons so a wrong mapping cannot hide.
|
||||||
|
set -u
|
||||||
|
export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98
|
||||||
|
export PYTHONPATH=/sylph-home/.local/lib/python3.12/site-packages
|
||||||
|
SD="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
SHOTS=/sylph-home/re/shots
|
||||||
|
HOLD="${1:-4}"
|
||||||
|
|
||||||
|
shot(){ screenshot "$SHOTS/fire-$1.png" >/dev/null 2>&1; echo " shot $1"; }
|
||||||
|
|
||||||
|
"$SD/launch_mission.sh" fly || { echo "BOOT FAILED"; exit 1; }
|
||||||
|
sleep 3
|
||||||
|
echo "=== probing (hold ${HOLD}s each) ==="
|
||||||
|
shot "00-idle"
|
||||||
|
|
||||||
|
# RB first: it is the incumbent claim. Then the rest, so a wrong mapping cannot
|
||||||
|
# hide behind it. A/B/X/Y may also switch weapon or open something — that is
|
||||||
|
# fine for a probe, and the final frame records wherever it ended up.
|
||||||
|
for b in RB LB Y X B A LS RS; do
|
||||||
|
vgamepad press "$b"; sleep "$HOLD"; shot "hold-$b"; vgamepad release "$b"; sleep 1.5
|
||||||
|
done
|
||||||
|
|
||||||
|
# triggers are analogue, not buttons
|
||||||
|
vgamepad trig RT 1.0; sleep "$HOLD"; shot "hold-RT"; vgamepad trig RT 0.0; sleep 1.5
|
||||||
|
vgamepad trig LT 1.0; sleep "$HOLD"; shot "hold-LT"; vgamepad trig LT 0.0; sleep 1.5
|
||||||
|
vgamepad reset
|
||||||
|
shot "99-final"
|
||||||
|
echo "PROBE DONE"
|
||||||
@@ -77,6 +77,22 @@ SHELL_VELOCITY = 8000.0
|
|||||||
SHELL_MAX_RANGE = 4000.0
|
SHELL_MAX_RANGE = 4000.0
|
||||||
SHELL_RADIUS = 20.0
|
SHELL_RADIUS = 20.0
|
||||||
|
|
||||||
|
# The MAIN weapon, fired with Y — measured, not assumed: a hold-each-input probe
|
||||||
|
# (fire_probe.sh) moved NOSE BM 06000 -> 05956 under RB and MAIN MPM 00300 ->
|
||||||
|
# 00299 under Y, so RB is the nose gun (~11 rounds/s) and Y is the main mount.
|
||||||
|
# That probe also settled the lethality question the other way round: we DO
|
||||||
|
# shoot, so the kill counters reading 0000 mean we shoot and MISS.
|
||||||
|
#
|
||||||
|
# Which is exactly what the disc data says to stop doing. Shell_TCAF_DeltaSaber_
|
||||||
|
# Missile_P is Power 200 with GuidanceType 5 (guided) and MaximumRange 5000,
|
||||||
|
# against the nose gun's Power 15 unguided — one missile is worth ~14 gun hits
|
||||||
|
# on a 500 HP fighter, and it steers itself, which is the accuracy problem
|
||||||
|
# solved rather than tuned. Range is held under the confirmed 5000 because which
|
||||||
|
# main weapon is actually loaded is not read from RAM yet.
|
||||||
|
MISSILE_RANGE = 4000.0
|
||||||
|
MISSILE_CONE = math.radians(20.0)
|
||||||
|
MISSILE_PERIOD = 2.0 # s between launches; 300 rounds is not unlimited
|
||||||
|
|
||||||
# Stage 02's protected asset. Named rather than derived: "the biggest friendly"
|
# Stage 02's protected asset. Named rather than derived: "the biggest friendly"
|
||||||
# picks the f105 cruiser (30000 HP > the Acropolis's 25000), and "the friendly
|
# picks the f105 cruiser (30000 HP > the Acropolis's 25000), and "the friendly
|
||||||
# with the most HP" picks it too, so neither rule finds the right ship. The
|
# with the most HP" picks it too, so neither rule finds the right ship. The
|
||||||
@@ -126,6 +142,9 @@ class Pilot:
|
|||||||
self.asset_hist = deque(maxlen=64)
|
self.asset_hist = deque(maxlen=64)
|
||||||
self.asset_hp0 = None
|
self.asset_hp0 = None
|
||||||
self.asset_last_hit = -1e9
|
self.asset_last_hit = -1e9
|
||||||
|
self.missile_down = False
|
||||||
|
self.missile_t = -1e9
|
||||||
|
self.missiles = 0
|
||||||
|
|
||||||
def f32(self, off):
|
def f32(self, off):
|
||||||
b = os.pread(self.W.fd, 4, off)
|
b = os.pread(self.W.fd, 4, off)
|
||||||
@@ -431,17 +450,33 @@ class Pilot:
|
|||||||
aim_ok = abs(aim[0]) < cone and abs(aim[1]) < cone
|
aim_ok = abs(aim[0]) < cone and abs(aim[1]) < cone
|
||||||
fire = bool(fire and tgt and aim_ok and tgt[4] < self.FIRE_RANGE and pn < 1.2)
|
fire = bool(fire and tgt and aim_ok and tgt[4] < self.FIRE_RANGE and pn < 1.2)
|
||||||
|
|
||||||
|
# The main mount is a discrete launch, not a continuous stream: press Y
|
||||||
|
# and let go a tick later, then wait out MISSILE_PERIOD. Holding it
|
||||||
|
# would empty 300 rounds in half a minute.
|
||||||
|
msl = bool(tgt and self.mode in ("ENGAGE", "DEFEND")
|
||||||
|
and tgt[4] < MISSILE_RANGE
|
||||||
|
and abs(aim[0]) < MISSILE_CONE and abs(aim[1]) < MISSILE_CONE
|
||||||
|
and pn < 1.2)
|
||||||
if not self.dry:
|
if not self.dry:
|
||||||
self.pad.axis("LX", sx)
|
self.pad.axis("LX", sx)
|
||||||
self.pad.axis("LY", sy)
|
self.pad.axis("LY", sy)
|
||||||
if fire != self.firing:
|
if fire != self.firing:
|
||||||
(self.pad.press if fire else self.pad.release)("RB")
|
(self.pad.press if fire else self.pad.release)("RB")
|
||||||
self.firing = fire
|
self.firing = fire
|
||||||
|
if self.missile_down and t - self.missile_t > 0.15:
|
||||||
|
self.pad.release("Y")
|
||||||
|
self.missile_down = False
|
||||||
|
elif (not self.missile_down and msl
|
||||||
|
and t - self.missile_t > MISSILE_PERIOD):
|
||||||
|
self.pad.press("Y")
|
||||||
|
self.missile_down = True
|
||||||
|
self.missile_t = t
|
||||||
|
self.missiles += 1
|
||||||
|
|
||||||
msg = (f"{self.mode:<7} hull={hull:6.0f} shd={shield:6.0f} spd={speed:6.0f} "
|
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={self.throttle:+d} yaw={math.degrees(yaw):+6.1f} "
|
||||||
f"pit={math.degrees(pitch):+6.1f} aim={math.degrees(aim[0]):+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)}")
|
f"/{math.degrees(aim[1]):+6.1f} fire={int(fire)} msl={self.missiles}")
|
||||||
if ast is not None:
|
if ast is not None:
|
||||||
msg += f" ast={a_frac*100:5.1f}%"
|
msg += f" ast={a_frac*100:5.1f}%"
|
||||||
if a_dmg > self.ASSET_ALERT:
|
if a_dmg > self.ASSET_ALERT:
|
||||||
|
|||||||
Reference in New Issue
Block a user