j146 ledger: the field shape restores a real safe set offline (filter broken 63.5%->30.4%) and is a live null on damage/run and round wins; 375 battles, 5 arms

This commit is contained in:
2026-09-26 22:35:17 +02:00
parent 298ea6d586
commit de5d02ba3f
3 changed files with 385 additions and 0 deletions
@@ -258,6 +258,7 @@ type TurnStats = object
broken: int
ownHeatSum: float
ownHot: int
safeSum: int ## j146: sum of the SAFE candidate-set size per pick
turnRateSum: float
hardTurn: int
holdSum: int
@@ -288,6 +289,7 @@ proc replayTurn(arm, envspec: string): TurnStats =
putEnv("TR_TFIL_COMMIT_LOG", "")
loadTfilCommitEnv()
loadTfilHeatEnv()
loadTfilHeatShapeEnv() # j146: the heat SHAPE knobs (corridor/wall/bullet)
randomize(Seed)
var m = initTFIL()
let states = loadStates()
@@ -328,6 +330,7 @@ proc replayTurn(arm, envspec: string): TurnStats =
result.pathHeatSum += ph
if ph > 10.0: inc result.hot
if m.lastPickPromoted: inc result.broken
result.safeSum += m.lastPickSafe
if hadPicks and
sqrt((ws.selfX-prev.x)^2 + (ws.selfY-prev.y)^2) < ArriveR2: inc result.reached
result.distSum += sqrt((m.commitTarget.x - ws.selfX)^2 +
@@ -387,3 +390,50 @@ for s in trows:
echo &"| {s.arm} | {mean(s.turnRateSum, s.ticks).formatFloat(ffDecimal,2)} | {pct(s.hardTurn, s.ticks)} | " &
&"{mean(s.holdSum.float, s.picks).formatFloat(ffDecimal,1)} | {pct(s.reached, s.picks)} | " &
&"{mean(s.distSum, s.picks).formatFloat(ffDecimal,0)} px |"
# ══════════════════════════════════════════════════════════════════════════════
# j146 — THE FIELD SHAPE, the mechanism ruler. The upstream cause j145 surfaced:
# 59% of picks break the hard filter, because corridor 20 is TWICE
# PathDangerThreshold 10 and tfil's own bullet heat (core 10) is exactly the
# threshold, so a bullet is never dangerous by itself.
#
# Arms are the FIVE shapes pre-registered in docs/movement_campaign.md
# (Batch 7), all on the j144 base so the shape is isolated on the current best
# tfil. The metrics: `filter broken` (the share of picks that had to promote a
# hot tile because FEWER THAN TWO tiles were safe) and `mean safe candidates`
# (the size of the set the picker drew from) are the MECHANISM; mean |turn| and
# mean path heat are the cost.
# ══════════════════════════════════════════════════════════════════════════════
const ShapeBase = "TR_MOVEMENT=tfil TR_TFIL_COMMIT_ARRIVAL=1 TR_TFIL_NOREV_SPEED=4"
let shapeArms = [
("shipped 20/30/10/10/5", ShapeBase &
" TR_TFIL_CORRIDOR_HEAT=20 TR_TFIL_WALL_HOTNESS=30 TR_TFIL_WALL_RADIANCE=10" &
" TR_TFIL_BULLET_CORE=10 TR_TFIL_BULLET_AURA=5"),
("middle 10/15/5/20/10", ShapeBase &
" TR_TFIL_CORRIDOR_HEAT=10 TR_TFIL_WALL_HOTNESS=15 TR_TFIL_WALL_RADIANCE=5" &
" TR_TFIL_BULLET_CORE=20 TR_TFIL_BULLET_AURA=10"),
("corr10 10/30/10/10/5", ShapeBase &
" TR_TFIL_CORRIDOR_HEAT=10 TR_TFIL_WALL_HOTNESS=30 TR_TFIL_WALL_RADIANCE=10" &
" TR_TFIL_BULLET_CORE=10 TR_TFIL_BULLET_AURA=5"),
("bullets 20/30/10/20/10", ShapeBase &
" TR_TFIL_CORRIDOR_HEAT=20 TR_TFIL_WALL_HOTNESS=30 TR_TFIL_WALL_RADIANCE=10" &
" TR_TFIL_BULLET_CORE=20 TR_TFIL_BULLET_AURA=10"),
("nofield 0/0/0/20/10", ShapeBase &
" TR_TFIL_CORRIDOR_HEAT=0 TR_TFIL_WALL_HOTNESS=0 TR_TFIL_WALL_RADIANCE=0" &
" TR_TFIL_BULLET_CORE=20 TR_TFIL_BULLET_AURA=10"),
]
echo "\n\n══ j146 FIELD SHAPE — mechanism ruler ══\n"
echo "columns: CORRIDOR / WALL_HOTNESS / WALL_RADIANCE / BULLET_CORE / BULLET_AURA\n"
var srows: seq[TurnStats]
for (name, envspec) in shapeArms:
srows.add replayTurn(name, envspec)
echo &"| arm | picks | filter broken | mean safe candidates | mean path heat | path heat >10 | mean \\|turn\\| | >90 deg |"
echo "|---|---:|---:|---:|---:|---:|---:|---:|"
for s in srows:
echo &"| {s.arm} | {s.picks} | {pct(s.broken, s.picks)} | " &
&"{mean(s.safeSum.float, s.picks).formatFloat(ffDecimal,2)} | " &
&"{mean(s.pathHeatSum, s.picks).formatFloat(ffDecimal,2)} | {pct(s.hot, s.picks)} | " &
&"{mean(s.turnSum, s.picks).formatFloat(ffDecimal,1)} | {pct(s.bigTurn, s.picks)} |"