Compare commits
1 Commits
auto/re-un
...
auto/re-mi
| Author | SHA1 | Date | |
|---|---|---|---|
| f4d59c5783 |
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