diff --git a/docs/movement_campaign.md b/docs/movement_campaign.md index 41bfb1f..48f7bf4 100644 --- a/docs/movement_campaign.md +++ b/docs/movement_campaign.md @@ -803,3 +803,95 @@ which is why every number is inlined above. The runner writes `/session.json` (commit sha, binary sha256, arms, 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.* diff --git a/tools/ab/arms_movement_b3.txt b/tools/ab/arms_movement_b3.txt new file mode 100644 index 0000000..68aadc2 --- /dev/null +++ b/tools/ab/arms_movement_b3.txt @@ -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 diff --git a/tools/ab/arms_movement_b4.txt b/tools/ab/arms_movement_b4.txt new file mode 100644 index 0000000..9d81c1a --- /dev/null +++ b/tools/ab/arms_movement_b4.txt @@ -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