Toby's replay vs the autopilot (2026-10-14)
Toby’s replay vs the autopilot
Section titled “Toby’s replay vs the autopilot”Plan step 1 of Autopilot vs human skill gap: rebuild a human
run headlessly, then compare how the human and SkilledAutoPlayer move over the first 3 minutes,
which is where the bot dies.
Tool: tools/replay_compare/replay_compare.gd (see “Reproduce” at the end). All numbers below come
from that tool’s output. Anything that is interpretation is marked (inferred).
Fidelity: both human replays reproduce exactly
Section titled “Fidelity: both human replays reproduce exactly”The replay files from BUILD 325 record inputs only. Level-up picks were never recorded (fixed on
this branch, see below), so the tool rebuilt each run’s picks from its telemetry row
(timeline_json.levelups, plus visited_dimensions for the boss-death dimension choice). Each
offer the sim rolled was checked against the cards telemetry says were offered.
| Run | Sim (headless, Godot 4.7.1, Mac) | Truth (telemetry, Apple TV) |
|---|---|---|
| Toby, seed 1837537114 (run 881) | 882.88 s, level 36, 1362 kills, death to swarmer, area ice | 882.88 s, level 36, 1362 kills, death to swarmer, end_area ice |
| Eve, seed 3876285654 (run 880) | 306.90 s, level 9, 374 kills, death to boss | 306.9 s, level 9, 374 kills, boss fight |
- All 52,973 (Toby) and 18,414 (Eve) recorded ticks played. Every level-up was offered at the same run time and level as in telemetry, with the same 3 cards in the same order: 35 of 35 for Toby, 8 of 8 for Eve. No mismatches.
- So the input stream, the fresh-save setup (
MetaRunSetup.apply, cobalt, sentinel,{}config), the between-tick order of level-up resolution, and the device’s float maths all match, across the ATV’s engine build and the Mac’s stock 4.7.1, for the whole 14.7 min. - Eve’s replay could be rebuilt too: her telemetry row (run 880) was in D1, found by seed.
The gap was real, and is fixed for new recordings
Section titled “The gap was real, and is fixed for new recordings”ReplayRecorder stored only per-tick InputState. In main the level-up panel freezes the sim, rolls
3 cards from upgrade_rng (_open_levelup), and applies the tapped card in _on_upgrade_chosen.
None of that went into the file. Playback (main._replay_play) runs as an autopilot run, so its
auto-pick took SkilledAutoPlayer.pick_upgrade_for’s card, and the run diverged at the first level-up
that the human had picked differently. Boss-death dimension choices had the same gap.
Branch replay-picks now records picks [{tick, pilot, id}] and dimension_picks [{tick, id}].
Main’s playback applies the recorded card at the same tick and falls back to the autopilot for older
files. ReplayDriver (input/replay_driver.gd) is a pure headless player that follows main’s
between-tick order.
Still not recorded, so a replay that used any of these will not reproduce: dev-console commands
(force_levelup, grant_weapon, invuln, dimension jump…), the DevFlags A/B spawn flags
(tackler/suppressor/web/hard tuning), and a dev time-scale other than 1.
Human vs bot, first 3 minutes
Section titled “Human vs bot, first 3 minutes”Feature definitions (per second, pilot 1; tools/replay_compare/feature_log.gd):
- speed px/s, excluding warp ticks.
- dash: warps per second.
- near: mean distance to the nearest enemy.
- n300: mean number of enemies within 300 px.
- open: largest empty angular sector among enemies within 300 px (360 = clear).
- turn: degrees per second the stick direction turns.
- curl: |signed turn| / total turn. 1 means one steady arc, 0 means zig-zag.
- away: share of push ticks moving away from the centroid of enemies within 300 px.
- tangent: share of push ticks moving within 60 deg of side-on to that centroid.
- push: mean stick deflection.
- hp lost: HP lost per second.
- shots150: enemy shots within 150 px.
“Bot” is the mean over 11 seeds (Toby’s seed plus 1000-1009), counting only the seconds a run was
still alive. attract is main’s on-screen autopilot (humanize on, throttled planner).
planner is the raw planner that soak and the learner fly. The bot rolls and picks its own cards.
| Window | Who | speed | dash/s | near | n300 | open° | turn°/s | curl | away | tangent | push | HP lost/s | shots150 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0-60 s | Toby | 259 | 0.12 | 282 | 2.60 | 309 | 102 | 0.56 | 0.62 | 0.40 | 1.00 | 0.17 | 0.80 |
| Eve | 190 | 0.07 | 562 | 2.03 | 332 | 49 | 0.77 | 0.69 | 0.37 | 0.76 | 1.81 | 0.45 | |
| attract | 223 | 0.33 | 289 | 4.23 | 250 | 422 | 0.39 | 0.65 | 0.29 | 0.85 | 1.69 | 3.30 | |
| planner | 217 | 0.38 | 292 | 4.12 | 250 | 1026 | 0.33 | 0.77 | 0.23 | 0.83 | 1.39 | 3.22 | |
| 60-120 s | Toby | 285 | 0.15 | 343 | 0.88 | 342 | 97 | 0.53 | 0.45 | 0.46 | 1.00 | 1.77 | 0.97 |
| Eve | 255 | 0.08 | 732 | 0.53 | 347 | 42 | 0.63 | 0.59 | 0.27 | 0.97 | 1.30 | 0.74 | |
| attract | 235 | 0.41 | 264 | 2.52 | 314 | 519 | 0.31 | 0.60 | 0.32 | 0.89 | 2.24 | 4.81 | |
| planner | 231 | 0.42 | 258 | 2.33 | 300 | 1377 | 0.20 | 0.65 | 0.27 | 0.89 | 2.37 | 6.05 | |
| 120-180 s | Toby | 295 | 0.15 | 379 | 0.52 | 352 | 97 | 0.57 | 0.43 | 0.44 | 1.00 | 2.37 | 1.00 |
| Eve | 239 | 0.15 | 558 | 0.82 | 359 | 59 | 0.70 | 0.54 | 0.23 | 0.91 | 0.45 | 0.61 | |
| attract (45 s alive) | 255 | 0.42 | 317 | 1.26 | 345 | 471 | 0.34 | 0.52 | 0.30 | 0.88 | 2.35 | 5.02 | |
| planner (110 s alive) | 262 | 0.38 | 276 | 1.92 | 314 | 1831 | 0.18 | 0.47 | 0.31 | 0.98 | 1.67 | 6.99 |
Whole runs: Toby averaged 389 px/s, 0.18 warps/s and 0.98 enemies within 300 px over 883 s. Eve averaged 226 px/s, 0.11 warps/s and 0.89 enemies within 300 px over 307 s.
Time to death, 11 seeds per variant:
- attract: mean 93.3 s, median 94.1 s, range 53.8-164.4 s.
- planner: mean 105.9 s, median 78.1 s, range 49.5-279.6 s.
On Toby’s own seed, attract died at 94.1 s (charger) and planner at 150.2 s (charger); Toby lived 882.9 s. The causes of the 22 bot deaths were charger 11, swarmer or swarmer shot 7, shooter shot 3, burn 1.
Centre and edge distance did not differ meaningfully. Over 0-180 s Toby averaged 1141 px from the centre and 965 px from a wall; attract averaged 1173 and 952. Arena centre or edge preference is not the gap.
Top behavioural differences
Section titled “Top behavioural differences”- Fewer enemies close by, and far fewer shots near the ship. Within 300 px, Toby had 1.33 enemies and Eve 1.13 over 0-180 s; the bots had 3.4-3.6. Enemy shots within 150 px: Toby 0.93, Eve 0.60, bots 3.9-4.5, which is 4-7x more. In 0-60 s Toby lost 0.17 HP/s against 1.69 for attract, 10x less, at about the same nearest-enemy distance (282 vs 289 px). (inferred) He sits at a similar standoff from the nearest enemy but never lets a group or its shots build up around him.
- Warps 2.5-3x less often. Toby made 0.12-0.15 warps/s and Eve 0.07-0.15. The bots made
0.33-0.42. (inferred) The humans hold the warp in reserve; the bot spends it, partly on offensive dashes into
the engage window (
DASH_WINDOW_*,offensive_warp_gap_s). - Smooth, steady arcs instead of constant re-aiming. Toby turned 97-102°/s and Eve 42-59°/s. Attract turned 420-520°/s and planner 1000-1800°/s. Curl (how consistently the turn goes one way) was 0.53-0.57 for Toby and 0.63-0.77 for Eve, against 0.18-0.39 for the bots. (inferred) Both humans fly long curving paths: at full stick and ~100°/s, Toby completes a loop roughly every 3.6 s. The bot changes heading many times a second.
- Full throttle all the time (Toby). Toby’s stick push was 1.00 with 0% idle ticks for the first
3 min. That made him 16% faster than attract in 0-60 s (259 vs 223 px/s), before any move-speed
pick; his first move-speed card was at 71.5 s. Attract’s calm push is 0.82-1.0 (
HZ_CALM_PUSH), and it idles 6-8% of ticks. Eve was not at full throttle in 0-60 s (push 0.76, 24% still), so this one is Toby’s style, not a universal human trait. - More circling, less straight fleeing. Toby moved side-on to the enemy centroid on 40-46% of push ticks; attract did so on 29-32% and planner on 23-27%. Toby moved directly away 43-62% of the time; the planner did so 65-77%. Eve sits between the two (tangent 0.23-0.37, away 0.54-0.69). (inferred) Toby kites in loops around the pack. Openness backs this up: his widest clear sector was 309-352° against the bots’ 250-345°, so an exit was nearly always open.
Which differences map to existing consts vs need new behaviour
Section titled “Which differences map to existing consts vs need new behaviour”| Difference | Existing knob | Verdict |
|---|---|---|
| Warp rate (#2) | DASH_WINDOW_MIN/MAX_MULT, offensive_warp_gap_s (HZ_OFFENSIVE_WARP_GAP_S), REACTION_S / LATE_REACT_S (when walking is deemed too slow) |
Tunable. The offensive gap is already a genome field; defensive warps fire from REACTION_S, a skill handicap. |
| Full-throttle push (#4) | HZ_CALM_PUSH, HZ_PAUSE_CHANCE, HZ_WANDER_PUSH (humanize, presentation) |
Tunable, but these are presentation consts. Plan step 2 (the humanize A/B) should measure whether calm push costs survival. Planner (no humanize) pushes 0.83-0.98 and is not faster, so throttle alone does not explain the gap. (inferred) |
| Turn rate / curl (#3) | MAX_TURN_PER_TICK, HZ_TURN_CALM/TENSE/URGENT, HZ_KITE_FLIP_S |
Partly tunable. Turn caps limit the stick, but the planner re-decides direction every tick from local threats (curl 0.18-0.33 with no humanize). A held arc needs a path-level intent, which the planner does not have. |
| Circling vs fleeing (#5) | HZ_KITE_WEIGHT (0.35 tangential share, humanize only), ORBIT_WEIGHT (only while a charger/spider telegraphs) |
Partly tunable. A heavier kite weight is a cheap test, but kiting exists only inside humanize and only as a blend on top of flee. |
| Crowd and shot exposure (#1) | ENGAGE_SEEK_WEIGHT, ENGAGE_RANGE_MULT / ENGAGE_IDLE_MULT, THREAT_RADIUS (300), LOOKAHEAD_S (0.5) |
Needs new behaviour. The humans keep the local enemy count and nearby shots low over minutes, not just the next 0.5 s. The bot has no cost for “enemies within 300 px” or “shots I’m flying into” beyond its lane lookahead, and its engage force pulls it back into range. |
Ceiling caveat (step 1’s answer): the plan’s tuning steps (4-7) can move warp frequency, throttle,
turn caps and kite share, and should be tried first because they are cheap. The largest measured
gap (#1, with #3 and #5 as the likely means) is whole-run positioning: steady arcs around the pack
that keep the local crowd and shot density low and an exit open. SkilledAutoPlayer has no code for
that; it steers locally ~0.5 s ahead. (inferred) That needs new behaviour, for example a planner
term that scores candidate headings by crowd/shot density over 1-2 s and holds a loop direction. It
cannot be reached by tuning the existing consts.
Caveats
Section titled “Caveats”- n = 2 humans, 11 bot seeds per variant. The bot rows pool the seconds alive across seeds, so the 120-180 s bot rows cover only the survivors (45 s for attract, 110 s for planner) and are survivor-biased.
- The bot rolls its own cards, so after its first differing pick its build and offers diverge from
the human’s, even on the same seed. On Toby’s seed it took
critat level 2 where Toby tooksplit. - Build differences are not controlled. Toby took 2 move-speed and 2 thrusters cards by 255 s (levelup log), which feeds his speed after 71 s.
- The ATV build was Dev BUILD 325. Telemetry can’t separate Toby from Chris on the shared ATV, but these two replays are named and seeded.
Reproduce
Section titled “Reproduce”godot --headless --path . --importperl -e 'alarm 1500; exec @ARGV' godot --headless --path . -s res://tools/replay_compare/replay_compare.gd -- \ --replay=replays/hand-pulled/toby-2026-10-14-1837537114.json \ --telemetry=replays/hand-pulled/toby-2026-10-14-1837537114.run881.json \ --bot-seeds=10 --out=/tmp/toby.jsonTakes ~30 s for the human run plus ~2-3 min for 22 bot runs. --telemetry is the D1 runs row
as JSON (timeline_json holds the picks). It is not needed for a replay recorded after this
branch, which carries its own picks. Use --bots=attract for the attract autopilot only, and
--stop-on-mismatch to stop at the first divergence.