fe77056459
The_floor_is_lava_ring carried raw commitTicks with no arrival guard and no
no-reversal guard. Port the behaviour of tfil's j144 fix on RING-SPECIFIC env
names (TR_TFIL_RING_COMMIT_ARRIVAL, TR_TFIL_RING_NOREV_SPEED) so the two forks
never share a namespace. Both default OFF: with them unset the ring mover is
byte-for-byte the pre-change mover over the whole 20026-tick fixture replay
(golden generated from git show HEAD:..., checked by tfil_ring_replay.nim).
Structural differences from tfil, all noted in the code:
* ring has no TfilTileReplanMode - the tile-crossing cancel is unconditional
self-tile, so the arrival guard is just 'not TfilRingCommitArrival'.
* ring has no replanReason enum, so the arrival/danger/expiry outcomes are a
local bool; the default-off path keeps ring's original dec/no-dec exactly.
* ring's MinCommitTicks is 0 (tfil's is 5), so the arrival branch is evaluated
from the first committed tick. Left as is: changing it would change the
default path.
* ring's ScoredTile carries no turnDeg, so the no-reversal offsets are
computed by ringTileOffTravel at the pick site.
TR_TFIL_COMMIT_MARGIN (tfil's hysteresis) is deliberately NOT ported: it is a
third knob, outside the two named, and inert at its 0.0 default.
No other tfil mechanism touched: no turn-cost tiebreak, TR_TFIL_ARRIVE_TICKS,
TR_FIRE_LAG, heat-field override, corridor bound, hold, or geometry weighting.
No default changed anywhere.
110 lines
4.7 KiB
Nim
110 lines
4.7 KiB
Nim
## Fixture replay for the TFIL-RING mover (`movements/the_floor_is_lava_ring.nim`).
|
|
##
|
|
## It `include`s the mover (not `import`s it) so the replay can read the
|
|
## private `commitTarget` / `commitTicks` — the same reason
|
|
## `test_tfil_commit_env.nim` includes `the_floor_is_lava.nim`. Living in its
|
|
## OWN module keeps those privates in this module's scope, so a file that
|
|
## includes BOTH movers still compiles (there is no name clash between them:
|
|
## this one only sees ring's).
|
|
##
|
|
## `--path:common_libs` relative to the repo root.
|
|
##
|
|
## GOLDEN GENERATION (j165 default parity). Compile this file with the golden
|
|
## flag against the PRE-CHANGE ring, e.g. from a `git show HEAD:...` tree:
|
|
##
|
|
## TFIL_RING_GOLDEN_OUT=<path> \
|
|
## nim c -r --path:. -d:tfilRingGenGolden common_libs/tests/tfil_ring_replay.nim
|
|
##
|
|
## Nothing here references a j165 symbol, so the SAME file generates the golden
|
|
## on the pre-change mover and checks it on the post-change one. Regenerating
|
|
## the golden from the new code would defeat the check — only do that after a
|
|
## DELIBERATE change to the ring defaults.
|
|
|
|
import std/[os, json, random, math]
|
|
import std/strutils except fromHex
|
|
import gun_harness/gun_interface
|
|
include movements/the_floor_is_lava_ring
|
|
|
|
const
|
|
Seed = 20250923 ## same seed as the tfil replay: comparable arms
|
|
ArenaW = 800.0
|
|
ArenaH = 600.0
|
|
fixtureRel = "tr_drussgt_vs_modularbot.jsonl"
|
|
|
|
const RingGoldenPath* = currentSourcePath().parentDir / "fixtures" /
|
|
"tfil_ring_commit_default.golden"
|
|
|
|
type RingTickRec* = object
|
|
spd, trn: float ## the emitted MoveCommand
|
|
tx, ty: float ## where we are steering to
|
|
ct: int ## ticks left on the commitment
|
|
picked*: bool ## this tick made a NEW pick (not in the golden)
|
|
|
|
# Thin accessors for the mover's PRIVATE per-round state. The guard needs them
|
|
# to measure what the commitment did; the mover's own API stays unchanged.
|
|
proc ringPicks*(m: TFILRingModule): int = m.picks
|
|
proc ringTarget*(m: TFILRingModule): tuple[x, y: float] = m.commitTarget
|
|
|
|
proc ringRecLine*(r: RingTickRec): string =
|
|
$r.spd & " " & $r.trn & " " & $r.tx & " " & $r.ty & " " & $r.ct
|
|
|
|
proc loadRingStates*(): seq[WorldState] =
|
|
let path = currentSourcePath().parentDir.parentDir.parentDir / "tools" /
|
|
"fixtures" / fixtureRel
|
|
for rawLine in lines(path):
|
|
let line = rawLine.strip()
|
|
if line.len == 0: continue
|
|
let n = parseJson(line)
|
|
if n.hasKey("meta") or n.hasKey("end"): continue
|
|
let ex = n["ex"].getFloat()
|
|
let ey = n["ey"].getFloat()
|
|
result.add WorldState(
|
|
enemyX: ex, enemyY: ey,
|
|
enemyHeading: n["eh"].getFloat(), enemySpeed: n["es"].getFloat(),
|
|
enemyEnergy: n["ee"].getFloat(),
|
|
selfX: n["sx"].getFloat(), selfY: n["sy"].getFloat(),
|
|
selfHeading: n["sh"].getFloat(), selfSpeed: n["ss"].getFloat(),
|
|
selfEnergy: n["se"].getFloat(),
|
|
arenaWidth: ArenaW, arenaHeight: ArenaH,
|
|
tick: n["tick"].getInt(),
|
|
enemies: @[EnemyInfo(id: 1, x: ex, y: ey,
|
|
heading: n["eh"].getFloat(), speed: n["es"].getFloat(),
|
|
energy: n["ee"].getFloat())])
|
|
|
|
proc loadRingStarts*(): seq[int] =
|
|
let side = currentSourcePath().parentDir.parentDir.parentDir / "tools" /
|
|
"fixtures" / "drussgt_meta" / (fixtureRel & ".rounds.json")
|
|
if not fileExists(side): return
|
|
for r in parseFile(side)["rounds"]:
|
|
result.add r["startTick"].getInt()
|
|
|
|
proc replayRing*(): seq[RingTickRec] =
|
|
## Drive the REAL ring `computeMove` over the recorded WorldState stream with a
|
|
## fixed seed, touching no env knob. With every j165 knob unset this is the
|
|
## pre-change code path exactly.
|
|
randomize(Seed)
|
|
var m = initTFILRing()
|
|
let states = loadRingStates()
|
|
let starts = loadRingStarts()
|
|
for i in 0..<states.len:
|
|
if i == 0 or i in starts: m.resetRound()
|
|
let before = m.picks
|
|
let cmd = m.computeMove(states[i])
|
|
result.add RingTickRec(spd: cmd.speed, trn: cmd.turnRate,
|
|
tx: m.commitTarget.x, ty: m.commitTarget.y,
|
|
ct: m.commitTicks, picked: m.picks != before)
|
|
|
|
when isMainModule and defined(tfilRingGenGolden):
|
|
block:
|
|
let recs = replayRing()
|
|
let outPath = getEnv("TFIL_RING_GOLDEN_OUT", RingGoldenPath)
|
|
var g = "# TFIL-RING default-path parity golden (j165).\n"
|
|
g.add "# Generated from the PRE-CHANGE ring mover (`git show HEAD:...`) with\n"
|
|
g.add "# every j165 knob UNSET, over the whole\n"
|
|
g.add "# tools/fixtures/tr_drussgt_vs_modularbot.jsonl replay.\n"
|
|
g.add "# Format: speed turnRate targetX targetY commitTicks\n"
|
|
for r in recs: g.add ringRecLine(r) & "\n"
|
|
createDir(outPath.parentDir)
|
|
writeFile(outPath, g)
|
|
echo "wrote ", outPath, " (", recs.len, " ticks)"
|