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>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
# 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. |
|
||||
Reference in New Issue
Block a user