## OFFLINE — j150. WHERE DOES THE LAVA PICKER LOSE ITS TILES? ## ## The owner's report: "the bot chooses only between a poor number of tiles while ## there are a lot of them available but not considered". This ruler drives the ## REAL `TFILModule.computeMove` (with `TR_TFIL_DIAG=1`) over recorded ## DrussGT fixtures and reads the per-pick loss histogram the mover counts: ## ## reachable hull -> CoolestLevels(=2) distinct-lava filter ## -> pathMaxHeat <= PathDangerThreshold -> draw set -> CHOSEN ## ## No battle, no Java, no server, no behaviour change: every knob it moves is ## default-off or default-identical. ## ## Run: ## nim c -r --path:common_libs --nimcache:/tmp/nc_j150 \ ## common_libs/tests/measure_tfil_picker_loss.nim [fixture.jsonl ...] import std/[os, strformat, math, algorithm, json, sets, sequtils] import std/strutils except fromHex # `fromHex` would clash with color.fromHex import gun_harness/offline_range # Private-field access: include (do NOT import) the shipped mover. include movements/the_floor_is_lava proc loadRoundStarts(fixturePath: string): HashSet[int] = result = initHashSet[int]() let side = currentSourcePath().parentDir.parentDir.parentDir / "tools" / "fixtures" / "drussgt_meta" / (extractFilename(fixturePath) & ".rounds.json") if not fileExists(side): return let root = parseFile(side) if not root.hasKey("rounds"): return for r in root["rounds"]: if r.hasKey("startTick"): result.incl r["startTick"].getInt() proc replay(path: string, threshold: float): TfilLossStats = putEnv("TR_TFIL_DIAG", "1") putEnv("TR_TFIL_DANGER_THRESHOLD", $threshold) loadTfilCommitEnv() TfilLoss = TfilLossStats() let fx = loadFixture(path) let starts = loadRoundStarts(path) var m = initTFIL() for si in 0.. how many rejected tiles carried it, most common ## first. Lava is a sum of quantised terms, so this shows the real steps. var counts: seq[(float, int)] for v in x: var i = 0 while i < counts.len and counts[i][0] != v: inc i if i < counts.len: inc counts[i][1] else: counts.add (v, 1) result = counts result.sort(proc (a, b: (float, int)): int = cmp(b[1], a[1])) proc report(path: string) = let s = replay(path, 10.0) echo &"\n\u2550\u2550\u2550 {path}" if s.picks == 0: echo " no picks"; return let n = s.picks.float echo &" picks = {s.picks} (replayed ticks, one recorded battle, seed fixed)" echo &" stage mean tiles" echo &" 1 reachable-hull tiles {s.sReach.float/n:>12.2f}" echo &" 2 .. after CoolestLevels=2 filter {s.sCool.float/n:>12.2f}" echo &" 3 .. after path heat filter (pre-prom) {s.sSafe.float/n:>12.2f}" echo &" 4 .. draw set (what it chooses among) {s.sCand.float/n:>12.2f}" echo &" LOST at the 2-levels filter {(1.0 - s.sCool.float/s.sReach.float)*100:>11.1f}%" echo &" LOST at the heat filter {(1.0 - s.sSafe.float/max(1.0,s.sCool.float))*100:>11.1f}%" echo &" LOST at blocked-tile / no-rev {(1.0 - s.sCand.float/max(1.0,s.sSafe.float))*100:>11.1f}%" echo &" picks with an EMPTY safe set (<2 at stage 3) = {s.emptySafe} ({pctStr(s.emptySafe.float/n)})" echo " safe-set size distribution (stage 3):" for i in 0.. 0: echo &" {SafeBuckets[i].alignLeft(6)} {s.safeHist[i]:>7} ({pctStr(s.safeHist[i].float/n)})" # heat of the tiles the filter dropped echo &" heat of the {s.rejectedHeat.len} REJECTED tiles (mean {f2(mean(s.rejectedHeat))}), most common first:" for (v, c) in heatTally(s.rejectedHeat)[0 ..< min(8, s.rejectedHeat.len)]: echo &" heat {f2(v):>7} {c:>7} ({pctStr(c.float/s.rejectedHeat.len.float)})" echo &" heat of ADMITTED tiles mean {f2(mean(s.admittedHeat))} (n={s.admittedHeat.len}); " & &"of CHOSEN mean {f2(mean(s.chosenHeat))} (n={s.chosenHeat.len})" # \u2550\u2550 threshold sweep echo " threshold mean draw set % empty safe newly admitted (n, mean heat) mean heat chosen" var baseAdmitted = replay(path, 10.0).admittedHeat.len for thr in [10.0, 14.0, 18.0]: let a = replay(path, thr) let newly = a.admittedHeat.filterIt(it > 10.0) let newTxt = $newly.len & ", " & f2(mean(newly)) echo &" {thr:>9.0f} {a.sCand.float / a.picks.float:>13.2f} " & &"{pctStr(a.emptySafe.float / a.picks.float):>12} {newTxt:>30} {f2(mean(a.chosenHeat)):>16}" echo &" (admitted at threshold 10: {baseAdmitted} tiles)" # ── driver ─────────────────────────────────────────────────────────────────── let args = commandLineParams() let fixtures = if args.len > 0: args else: @[currentSourcePath().parentDir.parentDir.parentDir / "tools" / "fixtures" / "tr_drussgt_vs_modularbot.jsonl"] for f in fixtures: if fileExists(f): report(f) else: echo "skip (missing): ", f