movement j119 Batch 3+4: pre-register the reversal/dwell and heat-field arms

This commit is contained in:
2026-09-26 01:40:03 +02:00
parent 7311aaef5a
commit 1256357f07
3 changed files with 185 additions and 0 deletions
+92
View File
@@ -803,3 +803,95 @@ which is why every number is inlined above.
The runner writes `<outdir>/session.json` (commit sha, binary sha256, arms, The runner writes `<outdir>/session.json` (commit sha, binary sha256, arms,
panel) so any later job can re-analyze an old session offline, with no arena. panel) so any later job can re-analyze an old session offline, with no arena.
---
## Batch 3 — the reversal/dwell timing of the strafe picker
> **Pre-registration (written and committed BEFORE the battles).** Commit
> `7311aae` (Task A, the heat field made env-overridable) is the frozen binary.
> Session `/tmp/ab/j119_b3`. Arms file `tools/ab/arms_movement_b3.txt`, panel
> `tools/ab/panel_movement.txt`, 6 arms × 15 opponents × 3 runs × 3 rounds = 270
> battles, conc 6, `--reference strafe`.
**Why this batch.** Batches 1–2 established that the strafe ENGINE wins by
survival (+0.33…+0.58 wins/run over the shipped `tfil`, incoming hit rate
−5…−7 pp) and that the RANGE knob is not the lever. The untouched axis is the
picker itself. The strafe design flips the SIGN of `setForward` (a free
reversal) and holds a sign for `rand(DWELL_MIN..DWELL_MAX)` ticks, so the dwell
IS the reversal period — the whole premise of the mover is "when to flip".
**Reference in this batch is `strafe` (current defaults), not `tfil`.** Every
delta below is (arm − strafe); `tfil` is carried only as the shipped control.
| # | arm | env | what it isolates |
|---|---|---|---|
| 1 | `strafe` | `TR_MOVEMENT=strafe` | reference: dwell 6-20, spread 1, reach 144 |
| 2 | `tfil` | *(none — shipped)* | shipped control / cross-batch calibration |
| 3 | `fast_flip` | `TR_MOVEMENT=strafe TR_STRAFE_DWELL_MIN=2 TR_STRAFE_DWELL_MAX=8` | reversal every ~5 ticks |
| 4 | `slow_flip` | `TR_MOVEMENT=strafe TR_STRAFE_DWELL_MIN=12 TR_STRAFE_DWELL_MAX=40` | reversal every ~26 ticks |
| 5 | `wide_spread` | `TR_MOVEMENT=strafe TR_STRAFE_SPREAD=2 TR_STRAFE_REACH=216` | wider hedge (±2 tiles, 216 px) |
| 6 | `narrow` | `TR_MOVEMENT=strafe TR_STRAFE_SPREAD=0 TR_STRAFE_REACH=108` | no hedge, short 108 px reach |
**Pre-registered prediction (before the battles):** reversal timing is a real
mechanism lever; the picker hedge geometry is not. Specifically: (a) `fast_flip`
will LOWER incoming hit rate vs `strafe` (each heading is exposed for less time)
and (b) `slow_flip` will RAISE it (a pattern gun gets a longer straight run);
(c) NEITHER extreme is expected to beat `strafe` on round wins by rule 2
(a sign-test win with no detectable damage loss), because the win effect is
bounded by survival that is already high; (d) `wide_spread` and `narrow` should
not separate from `strafe` (the Batch-2 lesson that picker-shape knobs sit below
the MDE). If an arm DOES beat `strafe`, the most likely is `fast_flip`, via
survival. I record this as a falsifiable claim; a wrong prediction is recorded
as wrong.
*Results appended below after the session.*
---
## Batch 4 — the heat field strength (how strongly strafe treats danger)
> **Pre-registration (written and committed BEFORE the battles).** Same frozen
> binary (`7311aae`), session `/tmp/ab/j119_b4`, arms file
> `tools/ab/arms_movement_b4.txt`, 6 arms × 15 opponents × 3 runs × 3 rounds =
> 270 battles, conc 6, `--reference strafe`.
**Why this batch.** The strafe win is a survival effect, and strafe runs a
deliberate RETUNE of the shipped heat field: bullet core/aura 20/10 (the core is
ABOVE the 10-px path threshold, so the bullet itself is the danger), corridor 10
(== threshold), wall 15/5 (outer ring only), pillar off — vs the shipped field's
corridor 20 and wall 30/10. The question is whether the retune (or the strength
of any one source) is what buys the survival. One arm per knob family.
| # | arm | env | what it isolates |
|---|---|---|---|
| 1 | `strafe` | `TR_MOVEMENT=strafe` | reference: bullet 20/10, corridor 10, wall 15/5 |
| 2 | `tfil` | *(none — shipped)* | shipped control |
| 3 | `bullet_strong` | `TR_MOVEMENT=strafe TR_STRAFE_BULLET_CORE=30 TR_STRAFE_BULLET_AURA=15` | the bullet retune |
| 4 | `field_strong` | `TR_MOVEMENT=strafe TR_STRAFE_CORRIDOR_HEAT=20 TR_STRAFE_WALL_HOTNESS=30 TR_STRAFE_WALL_RADIANCE=10` | the shipped corridor/wall shape |
| 5 | `field_off` | `TR_MOVEMENT=strafe TR_STRAFE_CORRIDOR_HEAT=0 TR_STRAFE_WALL_HOTNESS=0` | no corridors, no wall heat |
| 6 | `wall_tight` | `TR_MOVEMENT=strafe TR_STRAFE_WALL_MARGIN=54 TR_STRAFE_WALL_BIAS=0.7 TR_STRAFE_KAPPA=0.005 TR_STRAFE_WING_MAX=45` | the curved-wing geometry family |
Note on `field_off`: wall hotness is set to 0, NOT the radiance — a radiance of 0
paints a FLAT `WallHotness` field over the whole arena (the falloff multiplies
the tile index), which is the opposite of "no walls".
**Pre-registered prediction (before the battles):** the strafe retune is
load-bearing at the corridor/wall end. Specifically: (a) `field_strong` (the
shipped saturated corridor/wall shape) will RAISE incoming hit rate and LOSE
round wins vs `strafe`; (b) `field_off` will be a wash or slightly worse —
corridors and walls are real threats the picker should see; (c) `bullet_strong`
will be a wash or slightly worse (a 30 core is above the 25 danger-replan
threshold, so it over-replans); (d) `wall_tight` will not separate. NET: no arm is
expected to BEAT `strafe` on round wins, and the current retune should rank at
or near the top. A wrong prediction is recorded as wrong.
**Task A (this job's separate deliverable).** The shipped `tfil` mover's heat
shape (`CorridorHeat`/`WallHotness`/`WallRadiance`) was a Nim `const` and could
not be swept by env; commit `7311aae` makes them env-overridable vars
(`TR_TFIL_CORRIDOR_HEAT`/`TR_TFIL_WALL_HOTNESS`/`TR_TFIL_WALL_RADIANCE`, shipped
defaults 20/30/10) and the default path is proven byte-identical by
`common_libs/tests/test_tfil_commit_env.nim` (30 checks). STRAFE's own heat knobs
were already env-overridable, which is what this batch sweeps.
*Results appended below after the session.*
+47
View File
@@ -0,0 +1,47 @@
# ─────────────────────────────────────────────────────────────────────────────
# arms_movement_b3.txt — BATCH 3 of the movement campaign: the REVERSAL/DWELL
# TIMING of the strafe picker, plus the picker's hedge geometry.
#
# Prior data this batch is built on (docs/movement_campaign.md §3–§5, MEASURED
# on the frozen panel across two independent sessions):
# * `TR_MOVEMENT=strafe` BEATS the shipped `tfil` on round wins (+0.33…+0.58
# wins/run, all four 95% CIs excluding 0, 9/9…11/12 decisive opponents).
# * It wins by SURVIVAL: incoming hit rate 12.1–12.5% vs tfil's 17.6–18.2%,
# ~50 less damage taken/run, with NO detectable damage cost.
# * The RANGE knob is NOT the lever (Batch 2: none/250/600 made no separable
# difference). The untouched axis is the picker ITSELF.
#
# The strafe engine flips the SIGN of `setForward` (a free reversal) and keeps
# each sign for `rand(DWELL_MIN..DWELL_MAX)` ticks. That dwell IS the reversal
# period, so it is the most likely remaining lever: the whole design premise is
# "movement is about WHEN to flip direction".
#
# REFERENCE is `strafe` (current defaults), NOT `tfil` — every delta is
# (arm − strafe). `tfil` is kept as the shipped control for cross-batch
# calibration.
#
# Format: name | ENV=value ENV=value | label
# ─────────────────────────────────────────────────────────────────────────────
# 1. the reference: current strafe defaults (dwell 6-20, spread 1, reach 144)
strafe | TR_MOVEMENT=strafe | reference — current strafe defaults
# 2. the shipped control (no env): lets this session be calibrated against
# Batches 1-2 and shows the strafe-over-tfil effect again.
tfil | | shipped control (movement engine tfil, every knob at default)
# 3. FAST FLIP: reversal every ~5 ticks (dwell 2-8) instead of ~13.
# Predicts LOWER incoming hit rate (each direction is exposed for less time)
# but a less stable perpendicular line.
fast_flip | TR_MOVEMENT=strafe TR_STRAFE_DWELL_MIN=2 TR_STRAFE_DWELL_MAX=8 | reversal every ~5 ticks
# 4. SLOW FLIP: reversal every ~26 ticks (dwell 12-40). The opposite extreme:
# predicts HIGHER incoming hit rate (a pattern gun gets a longer straight run
# to learn) and worse survival.
slow_flip | TR_MOVEMENT=strafe TR_STRAFE_DWELL_MIN=12 TR_STRAFE_DWELL_MAX=40 | reversal every ~26 ticks
# 5. WIDE SPREAD: perpendicular jitter ±2 tiles and a 216px reach (vs ±1 / 144).
wide_spread | TR_MOVEMENT=strafe TR_STRAFE_SPREAD=2 TR_STRAFE_REACH=216 | wider hedge (±2 tiles, 216px reach)
# 6. NARROW: no perpendicular jitter and a 108px reach (vs ±1 / 144).
narrow | TR_MOVEMENT=strafe TR_STRAFE_SPREAD=0 TR_STRAFE_REACH=108 | no hedge, short 108px reach
+46
View File
@@ -0,0 +1,46 @@
# ─────────────────────────────────────────────────────────────────────────────
# arms_movement_b4.txt — BATCH 4 of the movement campaign: the HEAT FIELD
# STRENGTH (how strongly the strafe picker treats danger).
#
# Prior data (docs/movement_campaign.md §3–§5, MEASURED): the strafe win is a
# SURVIVAL effect (incoming hit rate 12.1–12.5% vs 17.6–18.2%, ~50 less damage
# taken/run, no detectable damage cost). The open question is whether that
# survival comes from HOW the field ranks danger — strafe runs a RETUNE of the
# shipped field: bullet core/aura 20/10 (core ABOVE the 10px path threshold),
# corridor 10 (== threshold), wall 15/5 (outer ring only), pillar off. The
# shipped field was corridor 20 (twice threshold), wall 30/10.
#
# REFERENCE is `strafe` (current defaults) — every delta is (arm − strafe).
# `tfil` is kept as the shipped control.
#
# Every knob below is ALREADY env-overridable in strafe.nim (Task A made the
# OTHER mover's — the shipped tfil field's — consts env-overridable; strafe's
# were already knobs). One arm per knob family.
#
# Format: name | ENV=value ENV=value | label
# ─────────────────────────────────────────────────────────────────────────────
# 1. the reference: current strafe retune (bullet 20/10, corridor 10, wall 15/5)
strafe | TR_MOVEMENT=strafe | reference — current strafe heat retune
# 2. the shipped control (no env).
tfil | | shipped control (movement engine tfil, every knob at default)
# 3. BULLET STRONG: the bullet itself much more dangerous (core 30 > the 25
# danger-replan threshold; aura 15). Isolates the bullet retune.
bullet_strong | TR_MOVEMENT=strafe TR_STRAFE_BULLET_CORE=30 TR_STRAFE_BULLET_AURA=15 | stronger bullet core/aura (30/15)
# 4. FIELD STRONG: the SHIPPED corridor/wall shape (corridor 20 = 2x the path
# threshold, wall 30/10 = a 3-tile wall apron). Isolates the corridor+wall
# retune that strafe deliberately weakened.
field_strong | TR_MOVEMENT=strafe TR_STRAFE_CORRIDOR_HEAT=20 TR_STRAFE_WALL_HOTNESS=30 TR_STRAFE_WALL_RADIANCE=10 | shipped TFIL corridor/wall shape (20, 30/10)
# 5. FIELD OFF: no corridor and no wall heat at all (wall hotness 0 — do NOT
# set radiance 0, that paints a FLAT field). Does the wall logic help or
# merely occupy decisions?
field_off | TR_MOVEMENT=strafe TR_STRAFE_CORRIDOR_HEAT=0 TR_STRAFE_WALL_HOTNESS=0 | no corridors, no wall heat
# 6. WALL TIGHT: the curved-wing geometry family — bend sooner (margin 108->54),
# prefer the interior harder (bias 0.35->0.7), curve twice as hard
# (kappa 0.0025->0.005) and allow a larger wing tilt (30->45 deg).
wall_tight | TR_MOVEMENT=strafe TR_STRAFE_WALL_MARGIN=54 TR_STRAFE_WALL_BIAS=0.7 TR_STRAFE_KAPPA=0.005 TR_STRAFE_WING_MAX=45 | tighter/faster wall wings + escape