re: Stage 02 is lost at ~11 minutes, and the pilot's survival rule is what guarantees it
First session whose deliverable was the mission's ENDING rather than a measurement (mission_run.sh, 500 s, hull of every entity at 2 Hz). The ACROPOLIS is untouched to t=170 s then falls at ~53 HP/s with no let-up, reaching zero at t=640-720 s — so "no mission completed" is not an artifact of the 240 s time-boxes, and not of the 600 s cap on a blocking tool call. A longer session would only watch the loss arrive. Attributing the damage by co-presence, exactly two classes are ever near the asset: e007 turrets (8483) and e010 bombers (8334). pilot.py treats turrets as keep-out zones at 2500 units and never as targets — the rule that made it survive — so roughly half the escort damage comes from the one class it is designed to avoid. Survival and the objective are in direct conflict and the pilot resolves it entirely for survival: WARSHIPS 0000, WARPLANES 0009, REMAINING OB rising 004 -> 008, our hull untouched at 1500/1500 with 120 missiles spent. That is unspent risk budget, not a good run. Also corrects launch_mission.sh: a harness-tracked BACKGROUND task does not keep the display alive (lost 11 s in, at the turn boundary) — the one-blocking-foreground-call rule stands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
BIN
docs/re/captures/mission01-t485.png
Normal file
BIN
docs/re/captures/mission01-t485.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
89
docs/re/mission-outcome-stage02.md
Normal file
89
docs/re/mission-outcome-stage02.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Why Stage 02 is never won — the escort sinks at ~11 minutes (2026-08-10)
|
||||
|
||||
**Status: ✅ measured, one 500 s run.** The standing open item since 2026-07-29 was
|
||||
"no mission completed". This is the first session whose deliverable was the
|
||||
*ending* rather than a measurement, and it settles why: **the mission is lost
|
||||
before it can be won, and the pilot's survival policy is what guarantees it.**
|
||||
|
||||
Run: `tools/re-capture/mission_run.sh 500 mission01` — boot → Stage 02 in flight →
|
||||
`pilot.py` (escort-weighted targeting, target commitment, guided missiles) for
|
||||
500 s, with every entity's hull sampled at 2 Hz and a screenshot every 30 s.
|
||||
Artifacts at `/sylph-home/re/mission01/` (9 MB `mission.jsonl`, not committed).
|
||||
|
||||
## The escort's decay is linear, and it ends the mission
|
||||
|
||||
| t (s) | ACROPOLIS hull | % |
|
||||
|---|---|---|
|
||||
| 0–160 | 25000 | 100 % |
|
||||
| 180 | 24510 | 98.0 |
|
||||
| 280 | 20279 | 81.1 |
|
||||
| 380 | 13799 | 55.2 |
|
||||
| 480 | 8541 | 34.2 |
|
||||
| 485 (end) | 8182 | 32.7 |
|
||||
|
||||
Untouched until **t ≈ 170 s**, then **≈53 HP/s** with no let-up — so the asset
|
||||
reaches zero at **t ≈ 640 s**, and the whole-run average rate puts it at 722 s.
|
||||
Either way the escort is dead at **10–12 minutes**, and "the ACROPOLIS is sunk"
|
||||
is a defeat condition ([mission-escort-state](mission-escort-state.md)).
|
||||
|
||||
This also retires a suspicion: the 240 s time-box of earlier runs was *not*
|
||||
hiding a win, and the ~500 s ceiling of a single blocking tool call is **not**
|
||||
the binding constraint. A longer session would simply watch the loss arrive.
|
||||
|
||||
## What is actually killing it — and the conflict that follows
|
||||
|
||||
Attributing damage by co-presence (which hostiles are within 3000 units of the
|
||||
asset in the sample where its hull drops, damage split evenly among the classes
|
||||
present — suggestive, not per-shot proof), only **two** classes are ever near it:
|
||||
|
||||
| class | samples present | attributed damage |
|
||||
|---|---|---|
|
||||
| `UN_e007_ADAN_Turret` | 200 | 8483 |
|
||||
| `UN_e010_ADAN_Attacker_S` | 194 | 8334 |
|
||||
|
||||
Roughly half the damage comes from **turrets** — and `pilot.py` treats turrets as
|
||||
**keep-out zones at 2500 units, never as targets**. That rule is not arbitrary: a
|
||||
turret is what shot down every pilot before 2026-07-30, and it is why the craft
|
||||
now survives. But it means **the policy that keeps the pilot alive also
|
||||
guarantees the escort dies.** Survival and the objective are in direct conflict,
|
||||
and the pilot currently resolves it entirely in favour of survival.
|
||||
|
||||
The HUD at t≈485 s says the same thing from the game's side:
|
||||
|
||||
- `YOU KILLED WARSHIPS` **0000** — not one warship in 500 s, across every run ever;
|
||||
- `YOU KILLED WARPLANES` **0009** — fighters only;
|
||||
- `REMAINING OB` **004 → 008** — objectives are being *added* by waves faster than
|
||||
any are cleared, so the pilot is not touching the objective set at all;
|
||||
- SHIELD and ARMOR bars full, hull **1500/1500**, 120 missiles spent.
|
||||
|
||||

|
||||
|
||||
## The conclusion that matters
|
||||
|
||||
The pilot optimises the wrong thing. It maximises survival and fighter kills;
|
||||
the mission scores **objectives** and **the escort**, and the fighter population
|
||||
(134 → 92) is close to irrelevant to both. An untouched 1500/1500 hull at the
|
||||
moment the escort passes 33 % is not a good run — it is **unspent risk budget**.
|
||||
|
||||
Concretely, for the next attempt, in priority order:
|
||||
|
||||
1. **Turrets near the asset must become targets**, not keep-out zones — accepting
|
||||
hull damage is the only way to cut ~50 % of the incoming escort damage. The
|
||||
keep-out rule should be scoped to turrets that are *not* threatening the
|
||||
asset, rather than applied globally.
|
||||
2. **Engage warships.** `WARSHIPS 0000` forever means the objective class has
|
||||
never been attacked; `REMAINING OB` rising is the scoreboard saying so.
|
||||
3. Re-check whether the escort damage rate actually falls once turrets die —
|
||||
that is the experiment that tells us whether (1) is sufficient or whether the
|
||||
bombers need dedicated intercept too.
|
||||
|
||||
## Method note, learned the hard way
|
||||
|
||||
A harness-tracked **background** task does *not* protect the display: the same
|
||||
run launched in the background lost Xvfb 11 s in, at the turn boundary
|
||||
(`skip_intro` exit 3, "DISPLAY LOST"). The comment in `launch_mission.sh` saying
|
||||
the script may be run as a tracked background task is **wrong**; the
|
||||
one-blocking-foreground-call rule still stands, which caps a single attempt at
|
||||
the tool's 600 s timeout. And do not pipe a long run through `tail` — the first
|
||||
attempt printed nothing because `timeout` killed the pipeline before it flushed;
|
||||
the on-disk artifacts are what survived.
|
||||
@@ -19,7 +19,9 @@ alive(){ ps -o pid=,stat= -C xenia_canary 2>/dev/null | awk '$2 !~ /^Z/ {print $
|
||||
# that actually bought was the opposite: a process nothing owns is a process
|
||||
# nothing keeps alive, and both were being reaped a couple of minutes in — the
|
||||
# long-standing "Xvfb and the emulator die on their own every few minutes" note.
|
||||
# Run this whole script as ONE tracked background task and leave Xvfb, openbox
|
||||
# MEASURED WRONG 2026-08-10: a harness-tracked BACKGROUND task does not protect
|
||||
# them either — the display was lost 11 s in, at the turn boundary. Run this
|
||||
# whole script as ONE BLOCKING FOREGROUND call and leave Xvfb, openbox
|
||||
# and xenia as its children: they then live exactly as long as the session does.
|
||||
# `nohup` still shields them from a stray HUP; the exit-status wrapper means a
|
||||
# death is reported with the server's own account instead of being inferred.
|
||||
|
||||
60
tools/re-capture/mission_run.sh
Executable file
60
tools/re-capture/mission_run.sh
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
# Attempt to COMPLETE a mission and record how it ends.
|
||||
#
|
||||
# Every previous flight session was time-boxed to 240 s to measure something
|
||||
# (escort hull, lethality, ship placement) and none ever reached a mission
|
||||
# outcome — "no mission completed" has been the standing open item. Unit
|
||||
# definitions are instantiated per stage, so story progress is the only thing
|
||||
# that grows unit coverage past 21/110, and that needs a WIN, not a survival.
|
||||
#
|
||||
# So this run is deliberately long and its only deliverable is the ENDING:
|
||||
# screenshots throughout, every entity's hull sampled, and the pilot log kept
|
||||
# whole (never tail-piped — a frozen log tail is what mission-end looks like
|
||||
# from outside, and tailing throws away the transition).
|
||||
#
|
||||
# Runs as ONE tracked background task with Xvfb/openbox/xenia as plain nohup
|
||||
# children — see docs/re/session-lifetime notes; do NOT setsid anything.
|
||||
#
|
||||
# Usage: mission_run.sh [flight_seconds] [tag]
|
||||
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)"
|
||||
SECS="${1:-900}"
|
||||
TAG="${2:-mission}"
|
||||
SHOTS=/sylph-home/re/shots
|
||||
OUT="/sylph-home/re/$TAG"
|
||||
mkdir -p "$SHOTS" "$OUT"
|
||||
|
||||
"$SD/launch_mission.sh" fly || { echo "BOOT FAILED"; exit 1; }
|
||||
python3 "$SD/entities2.py" self 0x130 "$OUT/cfg.json" || { echo "BIND FAILED"; exit 1; }
|
||||
|
||||
echo "=== initial entity table ==="
|
||||
python3 "$SD/mission_state.py" scan "$OUT/cfg.json"
|
||||
|
||||
# A screenshot every 30 s for the WHOLE run: the outcome card (MISSION COMPLETE
|
||||
# / GAME OVER) is on screen only briefly, so sampling must not stop early.
|
||||
( n=$(( SECS / 30 + 4 ))
|
||||
for i in $(seq 1 "$n"); do
|
||||
printf '%s SHOT %03d\n' "$(date +%s)" "$i" >> "$OUT/shots.log"
|
||||
screenshot "$SHOTS/$TAG-$(printf %03d "$i").png" >/dev/null 2>&1
|
||||
sleep 30
|
||||
done ) &
|
||||
SHOTTER=$!
|
||||
|
||||
date +%s > "$OUT/t0"
|
||||
python3 "$SD/mission_state.py" watch "$OUT/cfg.json" "$SECS" 2 "$OUT/mission.jsonl" \
|
||||
> "$OUT/mission.log" 2>&1 &
|
||||
WATCHER=$!
|
||||
|
||||
python3 "$SD/pilot.py" "$OUT/cfg.json" "$SECS" > "$OUT/pilot.log" 2>&1
|
||||
PILOT_RC=$?
|
||||
wait $WATCHER 2>/dev/null
|
||||
kill $SHOTTER 2>/dev/null
|
||||
screenshot "$SHOTS/$TAG-end.png" >/dev/null 2>&1
|
||||
cp -f "$SHOTS/$TAG-end.png" "$OUT/end.png" 2>/dev/null
|
||||
|
||||
echo "PILOT_RC=$PILOT_RC"
|
||||
echo "--- last 5 pilot lines ---"; tail -5 "$OUT/pilot.log"
|
||||
echo "--- shots: $(ls "$SHOTS/$TAG-"*.png 2>/dev/null | wc -l) ---"
|
||||
echo "MISSION RUN DONE ($TAG, ${SECS}s)"
|
||||
Reference in New Issue
Block a user