Files
SirRoboGarage/docs/tfil_hold_when_trapped_ab.md
T
SirStone 94ffc63160 j153: the open-loop ruler for "hold when trapped" + the A/B proposal (NOT run)
Measures, on the recorded fixtures and with NO counterfactual replay: how long
until a safe tile appears at a forced pick, whether the enemy had just fired,
whether our own tile is already hot, and all three split by distance. Registers
nothing and changes no default: the knob TR_TFIL_HOLD_WHEN_TRAPPED and its
implementation live in the concurrently edited working tree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-27 10:16:53 +02:00

91 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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. |