# j153 proposal (NOT RUN) — `TR_TFIL_HOLD_WHEN_TRAPPED`: hold when no safe tile exists **Status: awaiting the owner's approval. No battle, no A/B arm, no tournament has been started.** The knob exists, is registered, defaults to today's behaviour byte-for-byte, and is guarded offline. ## The owner's claim > "if no tile is found to go, to not choose the less dangerous, but to stay > still! the next tick probably the situation already changed and we did not > commit to any dangerous place." Today, when the safe set (`pathMaxHeat <= PathDangerThreshold`, after the `CoolestLevels = 2` filter) is too small to draw from, the picker **promotes the 2 least-hot blocked tiles** and moves to one of them. The proposal replaces that with `speed 0.0` for that tick only. ## The knob | env | default | meaning | |---|---|---| | `TR_TFIL_HOLD_WHEN_TRAPPED` | `0` (off) | `1` = hold when the safe set is **empty**; today = promote the 2 least-hot blocked tiles | - **One tick, never latched.** The hold is taken at the pick site, and the pick site only runs when `commitTicks == 0`, so the next tick re-evaluates the field from scratch. There is deliberately **no max-hold knob** in j153: a counter can only add a way to get stuck. (j154, in flight, adds `TR_TFIL_HOLD_MAX_TICKS` = 0 = off as a *separate* default-off knob.) - **It never interrupts a live commitment.** The hold replaces a *replan*, not a commitment in progress: with `TR_TFIL_COMMIT_ARRIVAL=1` or `TR_TFIL_NOREV_SPEED=4` armed the two do not fight, because the hold branch is downstream of the commitment block and only runs at `commitTicks == 0`. - **The gun keeps firing.** `computeMove` never emits a fire command (the gun lives in the bot's `go()` loop), and the hold `return`s *after* the bullet tracking, so the fire tracker's state on a held tick is bit-identical to a non-held tick (guarded in `common_libs/tests/test_tfil_commit_env.nim`). ## What the offline harness can and cannot say Per `docs/offline_harness_trust.md` the replay harness is trustworthy only for per-gun single-tick prediction on a fixed enemy trajectory; it scored **0/6** on closed-loop questions. "Hold vs move" is a **counterfactual closed-loop** question, so this document contains **no** damage-taken comparison for holding. Only open-loop descriptors of the recorded field are reported (`common_libs/tests/measure_tfil_hold_window.nim`). ## Proposed A/B (needs approval) ``` TOURNAMENT_NIMCACHE=/tmp/nc_j153 \ tools/ab/tournament_run.sh \ --arms tools/ab/arms_hold_trapped.txt \ --panel tools/ab/panel_movement.txt \ --runs 14 --rounds 7 --conc 7 --wait-arena 45 \ --reference hold0 \ --outdir /tmp/ab/j153_hold python3 tools/ab/tournament_analyze.py /tmp/ab/j153_hold --reference hold0 ``` Arms file (frozen 15-opponent movement panel, 14 runs/arm, 7 rounds): ``` hold0 | | control = today's promote-the-2 fallback hold1 | TR_TFIL_HOLD_WHEN_TRAPPED=1 | hold one tick when the safe set is empty hold8 | TR_TFIL_HOLD_WHEN_TRAPPED=1 TR_TFIL_HOLD_MAX_TICKS=8 | bounded hold (only if j154 lands) ``` **Primary metrics: damage/run and round-win rate** (NOT hit rate — a movement arm's value flows through the closed loop). **Mechanism metrics:** incoming hit rate, % of picks held, mean distance-to-enemy at a hold, tick-share at speed 0. **MDE, stated up front:** ~0.28 wins/run at 14 runs/arm; a two-arm session is ~1 h. Resolving ~0.10 wins/run needs ~2.2 h / ~2,900 battles. A 1 h two-arm run can only reject effects at or above ~0.28 wins/run — anything smaller is a null by construction, and must be reported as such. **Prior, stated plainly:** four mechanism-positive / outcome-null results in a row on this campaign. **A null is the most likely outcome.** A null with a mechanism hit (holds fire, distance at hold is large) would mean: holding is achievable and does not by itself buy rounds; ship nothing. A null *without* a mechanism hit means the arm never bound and the A/B is void, not negative. ## Decision each duration supports | duration | supports | |---|---| | 1 h (2 arms × 14 runs) | reject/accept only ≥0.28 wins/run. Mechanism check only. | | 2.2 h (~2,900 battles, 3 arms) | resolve ~0.10 wins/run. Still not a small-effect test. | | any null | no change to the shipped default. `TR_TFIL_HOLD_WHEN_TRAPPED` stays 0. |