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:
@@ -657,6 +657,197 @@ when declared(loadTfilCommitEnv):
|
||||
" mean path heat=", meanPathHeat(s).formatFloat(ffDecimal, 2),
|
||||
" reached=", pct(s.reached, s.picks)
|
||||
|
||||
# ── 6. j146: the bullet's OWN heat is tunable (default = today's const) ─────
|
||||
|
||||
const ShapeBase = "TR_MOVEMENT=tfil TR_TFIL_COMMIT_ARRIVAL=1 TR_TFIL_NOREV_SPEED=4"
|
||||
const ShapeShipped = 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"
|
||||
const ShapeMiddle = 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"
|
||||
|
||||
proc setShape(corridor, wallHot, wallRad, bCore, bAura: float) =
|
||||
putEnv("TR_TFIL_CORRIDOR_HEAT", $corridor)
|
||||
putEnv("TR_TFIL_WALL_HOTNESS", $wallHot)
|
||||
putEnv("TR_TFIL_WALL_RADIANCE", $wallRad)
|
||||
putEnv("TR_TFIL_BULLET_CORE", $bCore)
|
||||
putEnv("TR_TFIL_BULLET_AURA", $bAura)
|
||||
loadTfilHeatShapeEnv()
|
||||
|
||||
proc clearShape() =
|
||||
for n in ["TR_TFIL_CORRIDOR_HEAT", "TR_TFIL_WALL_HOTNESS",
|
||||
"TR_TFIL_WALL_RADIANCE", "TR_TFIL_BULLET_CORE", "TR_TFIL_BULLET_AURA"]:
|
||||
putEnv(n, "")
|
||||
loadTfilHeatShapeEnv()
|
||||
|
||||
proc replayWithShape(envspec: string): seq[TickRec] =
|
||||
## `replay` with the heat-shape knobs written out in full. Used to prove
|
||||
## DEFAULT-OFF-EFFECT without the golden: the shipped shape spelled out as
|
||||
## env must give the SAME move commands as the build with them unset.
|
||||
for tok in envspec.splitWhitespace():
|
||||
putEnv(tok.split('=', 1)[0], tok.split('=', 1)[1])
|
||||
loadTfilHeatShapeEnv()
|
||||
result = replay(loadStates(), loadRoundStarts())
|
||||
for tok in envspec.splitWhitespace():
|
||||
putEnv(tok.split('=', 1)[0], "")
|
||||
loadTfilHeatShapeEnv()
|
||||
|
||||
proc replayShape(tag, envspec: string): seq[PickRec] =
|
||||
## replayJ145's replay, but the FIELD SHAPE comes from `envspec` instead of
|
||||
## the turn knobs, and the turn bias is left OFF — so the only thing that
|
||||
## moves between two calls is the shape.
|
||||
for tok in envspec.splitWhitespace():
|
||||
putEnv(tok.split('=', 1)[0], tok.split('=', 1)[1])
|
||||
loadTfilHeatShapeEnv() # the shape this arm actually wants
|
||||
putEnv("TR_TFIL_COMMIT_LOG", "")
|
||||
putEnv("TR_TFIL_TURN_BIAS", "")
|
||||
putEnv("TR_TFIL_TURN_REF_DEG", "")
|
||||
loadTfilCommitEnv()
|
||||
TfilCommitArrival = true
|
||||
TfilCommitMargin = 0.0
|
||||
TfilNoRevSpeed = 4.0
|
||||
randomize(Seed)
|
||||
var m = initTFIL()
|
||||
let states = loadStates()
|
||||
let starts = loadRoundStarts()
|
||||
var prev = (x: 0.0, y: 0.0)
|
||||
var hadPicks = false
|
||||
for i in 0..<states.len:
|
||||
let ws = states[i]
|
||||
if i == 0 or i in starts:
|
||||
m.resetRound()
|
||||
hadPicks = false
|
||||
let before = m.picks
|
||||
discard m.computeMove(ws)
|
||||
if m.picks != before:
|
||||
result.add PickRec(turn: m.lastPickMinTurn,
|
||||
minTurn: m.lastPickMinTurn,
|
||||
pathHeat: probePathHeat(m, ws.selfX, ws.selfY,
|
||||
m.commitTarget.x, m.commitTarget.y),
|
||||
promoted: m.lastPickPromoted,
|
||||
reached: hadPicks and
|
||||
sqrt((ws.selfX-prev.x)^2 + (ws.selfY-prev.y)^2) < 18.0)
|
||||
prev = m.commitTarget
|
||||
hadPicks = true
|
||||
for tok in envspec.splitWhitespace():
|
||||
putEnv(tok.split('=', 1)[0], "")
|
||||
clearShape()
|
||||
loadTfilCommitEnv()
|
||||
TfilCommitArrival = false
|
||||
TfilNoRevSpeed = 0.0
|
||||
discard tag
|
||||
|
||||
## Heat the field paints on a tile a single tracked bullet sits on, with the
|
||||
## corridor and the wall field switched OFF so the number is the BULLET's own
|
||||
## contribution and nothing else. `aheadPx` walks forward along the bullet's
|
||||
## travel, where the CORRIDOR (drawn only from the bullet position forward)
|
||||
## actually paints. One enemy-free world, one bullet.
|
||||
proc bulletTileHeat(bCore, bAura, corridor, aheadPx: float): float =
|
||||
setShape(corridor, 0.0, 0.0, bCore, bAura)
|
||||
let ws = WorldState(enemyX: 0.0, enemyY: 0.0, enemyHeading: 0.0,
|
||||
enemySpeed: 0.0, enemyEnergy: 100.0,
|
||||
selfX: 400.0, selfY: 120.0, selfHeading: 0.0,
|
||||
selfSpeed: 0.0, selfEnergy: 100.0,
|
||||
arenaWidth: ArenaW, arenaHeight: ArenaH, tick: 1,
|
||||
enemies: @[])
|
||||
var m = initTFIL()
|
||||
randomize(Seed)
|
||||
discard m.computeMove(ws) # initGrid
|
||||
# heading up the arena, the bot BELOW it, so `advanceBullets` keeps it alive
|
||||
m.bullets = @[TrackedBullet(originX: 410.0, originY: 320.0, x: 410.0, y: 320.0,
|
||||
velX: 0.0, velY: -20.0, power: 1.0,
|
||||
alive: true, age: 0)]
|
||||
discard m.computeMove(ws)
|
||||
if m.bullets.len == 0: return 0.0
|
||||
let (c, r) = m.tileAt(m.bullets[0].x, m.bullets[0].y - aheadPx)
|
||||
result = m.lavaAt(c, r)
|
||||
|
||||
proc testJ146() =
|
||||
# 6a. DEFAULT-OFF-EFFECT: the defaults are today's `const`s, so the shipped
|
||||
# shape spelled out in full as env and the knobs left UNSET must be the
|
||||
# same build, tick for tick. (The golden above proves it against the
|
||||
# pre-change build; this proves it against today's defaults too.)
|
||||
clearShape()
|
||||
check "j146: TR_TFIL_BULLET_CORE/AURA default to today's consts (10.0/5.0)",
|
||||
BulletCore == 10.0 and BulletAura == 5.0
|
||||
let explicit = replayWithShape("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")
|
||||
let unset = replay(loadStates(), loadRoundStarts())
|
||||
var firstDiff = -1
|
||||
if explicit.len != unset.len:
|
||||
firstDiff = min(explicit.len, unset.len)
|
||||
else:
|
||||
for i in 0..<unset.len:
|
||||
if recLine(explicit[i]) != recLine(unset[i]):
|
||||
firstDiff = i
|
||||
break
|
||||
check "j146: the SHIPPED SHAPE written out in full as env (20/30/10/10/5) is " &
|
||||
"BYTE-FOR-BYTE the unset build over " & $unset.len & " ticks — the new " &
|
||||
"knobs are default-off-effect",
|
||||
firstDiff < 0
|
||||
|
||||
# 6b. knob parsing, including the fallbacks
|
||||
setShape(20.0, 30.0, 10.0, 20.0, 10.0)
|
||||
check "j146: TR_TFIL_BULLET_CORE=20 / TR_TFIL_BULLET_AURA=10 are read",
|
||||
BulletCore == 20.0 and BulletAura == 10.0
|
||||
putEnv("TR_TFIL_BULLET_CORE", "not-a-number")
|
||||
putEnv("TR_TFIL_BULLET_AURA", "")
|
||||
loadTfilHeatShapeEnv()
|
||||
check "j146: a malformed / empty value falls back to the DEFAULT (10.0/5.0)",
|
||||
BulletCore == 10.0 and BulletAura == 5.0
|
||||
putEnv("TR_TFIL_BULLET_CORE", "-5")
|
||||
loadTfilHeatShapeEnv()
|
||||
check "j146: a negative value clamps to 0 (heat can never go backwards)",
|
||||
BulletCore == 0.0
|
||||
putEnv("TR_TFIL_BULLET_CORE", "")
|
||||
loadTfilHeatShapeEnv()
|
||||
|
||||
# 6c. THE MECHANISM CLAIM: with the shipped core 10 a bullet is never
|
||||
# dangerous ON ITS OWN (10 == PathDangerThreshold, and `safe` is
|
||||
# `<=`), while a corridor at 10 is not over threshold either. Raise the
|
||||
# core to 20 and the bullet's core IS over the threshold by itself.
|
||||
let coreShipped = bulletTileHeat(10.0, 5.0, 0.0, 0.0)
|
||||
let coreRaised = bulletTileHeat(20.0, 10.0, 0.0, 0.0)
|
||||
let corr10Ahead = bulletTileHeat(0.0, 0.0, 10.0, 72.0)
|
||||
check "j146: with the SHIPPED bullet core (10) a bullet's own core tile is " &
|
||||
"NOT over PathDangerThreshold (" & $coreShipped & " <= " &
|
||||
$DangerThreshold & ") — the corridor carries the whole field",
|
||||
coreShipped <= DangerThreshold
|
||||
check "j146: TR_TFIL_BULLET_CORE=20 puts the bullet's core ALONE over the " &
|
||||
"threshold (" & $coreRaised & " > " & $DangerThreshold & "), while a " &
|
||||
"corridor at 10 paints " & $corr10Ahead & " on its own and never " &
|
||||
"reaches it", coreRaised > DangerThreshold and
|
||||
corr10Ahead <= DangerThreshold
|
||||
check "j146: the knobs really change the field — the same bullet paints " &
|
||||
$coreShipped & " with the shipped core and " & $coreRaised &
|
||||
" with the retune", coreRaised > coreShipped
|
||||
|
||||
# 6d. and the shape as a whole restores a REAL safe set (the j145 cause)
|
||||
let sh = turnStats(replayShape("shipped", ShapeShipped))
|
||||
let mid = turnStats(replayShape("middle", ShapeMiddle))
|
||||
check "j146: the middle shape really restores a safe set — the picks that " &
|
||||
"have to break the hard filter fall " & pct(sh.broken, sh.picks) &
|
||||
" -> " & pct(mid.broken, mid.picks),
|
||||
mid.broken.float < sh.broken.float * 0.75
|
||||
check "j146: and it does NOT buy that with more lava — the mean heat of the " &
|
||||
"path we were told to walk FALLS " &
|
||||
meanPathHeat(sh).formatFloat(ffDecimal, 2) & " -> " &
|
||||
meanPathHeat(mid).formatFloat(ffDecimal, 2),
|
||||
meanPathHeat(mid) < meanPathHeat(sh)
|
||||
clearShape()
|
||||
check "j146: clearing the knobs restores the shipped field exactly",
|
||||
BulletCore == 10.0 and BulletAura == 5.0 and
|
||||
CorridorHeat == 20.0 and WallHotness == 30.0 and WallRadiance == 10.0
|
||||
|
||||
echo "\n j146 diagnostics (offline fixture replay, arrive+norev base):"
|
||||
for (nm, s) in [("shipped 20/30/10/10/5", sh), ("middle 10/15/5/20/10", mid)]:
|
||||
echo " ", nm.alignLeft(22), " picks=", s.picks,
|
||||
" filter broken=", pct(s.broken, s.picks),
|
||||
" mean path heat=", meanPathHeat(s).formatFloat(ffDecimal, 2),
|
||||
" >90deg=", pct(s.bigTurn, s.picks)
|
||||
|
||||
# ── driver ───────────────────────────────────────────────────────────────────
|
||||
|
||||
testDefaultParity()
|
||||
@@ -665,6 +856,7 @@ when declared(loadTfilCommitEnv):
|
||||
testArms()
|
||||
testJ144()
|
||||
testJ145()
|
||||
testJ146()
|
||||
|
||||
if failures > 0:
|
||||
echo "\n", failures, " check(s) FAILED"
|
||||
|
||||
Reference in New Issue
Block a user