659 lines
33 KiB
Nim
659 lines
33 KiB
Nim
## Boot-time environment report for ModularBot.
|
|
##
|
|
## The user runs the bot behind the SERVER/GUI, so the bot inherits the
|
|
## SERVER's environment — not the environment of whatever terminal they
|
|
## exported a variable in. This report answers, once per process at boot and
|
|
## in stdout, two questions:
|
|
##
|
|
## A. What TR_*/GUN_* variables did THIS process actually receive? (plus the
|
|
## process identity — pid, parent pid, parent command line — so "I exported
|
|
## it in the wrong terminal" is obvious at a glance).
|
|
## B. For every documented knob, what value will the bot actually use and did
|
|
## it come from the env or from the shipped default?
|
|
##
|
|
## Every line starts with `[env]` so the whole interface is:
|
|
##
|
|
## grep '^\[env\]' /tmp/modularbot_stdout.log
|
|
##
|
|
## It runs once (idempotent guard) and can be suppressed with TR_ENV_REPORT=0.
|
|
## It deliberately does NOT dump the whole environment: only TR_*/GUN_* are
|
|
## printed, because unrelated variables may hold secrets or machine paths.
|
|
|
|
import std/[os, strutils, algorithm, times, sets]
|
|
import env_dotenv
|
|
import gun_harness/virtual_bullets
|
|
import gun_harness/selector
|
|
import movements/ram_decision
|
|
import movements/the_floor_is_lava
|
|
import movements/the_floor_is_lava_ring
|
|
import movements/strafe
|
|
import movements/wave_surfer
|
|
import movements/learned_surfer
|
|
import guns/tm_horizon
|
|
import guns/bitbrain_gun
|
|
import guns/pattern_matcher
|
|
import vbullet_draw
|
|
|
|
const EnvReportEnableEnvVar* = "TR_ENV_REPORT"
|
|
|
|
# ── resolved values that live in ModularBot.nim (not importable here) ─────────
|
|
|
|
type
|
|
EnvReportContext* = object
|
|
## Everything the report needs that ModularBot owns. Values are the
|
|
## RESOLVED process-start values, not re-reads, so the report cannot
|
|
## diverge from what the bot will actually use.
|
|
movementName*: string
|
|
vBulletAdmitOnly*: bool
|
|
vBulletDebug*: bool
|
|
vBulletDebugGun*: string
|
|
vBulletDebugMax*: int
|
|
recordWorldState*: bool
|
|
radarForceSpin*: bool
|
|
radarScanLog*: bool
|
|
radarScanLogPath*: string
|
|
trackerProbe*: bool
|
|
trackerProbePath*: string
|
|
powerLog*: bool
|
|
resultLog*: bool
|
|
gunStatsPath*: string
|
|
shotLogPath*: string
|
|
disabledGuns*: HashSet[int]
|
|
tmHorizon*: TmHorizonGun
|
|
bitbrain*: BitBrainGun
|
|
patternMatcher*: PatternMatcherGun
|
|
|
|
# ── helpers ──────────────────────────────────────────────────────────────────
|
|
|
|
proc onOff(b: bool): string {.inline.} = (if b: "on" else: "off")
|
|
|
|
proc metricName(m: BulletMetric): string =
|
|
case m
|
|
of bmPoint: "point"
|
|
of bmPath: "path"
|
|
|
|
proc selectorModeName(m: SelectorMode): string =
|
|
case m
|
|
of smAbsolute: "absolute"
|
|
of smRelative: "relative"
|
|
|
|
proc rankName(r: RankStat): string =
|
|
## The word `GUN_SELECTOR_RANK` accepts on the command line, not the enum.
|
|
case r
|
|
of rsMean: "mean"
|
|
of rsWilson: "wilson"
|
|
of rsUCB: "ucb"
|
|
of rsThompson: "thompson"
|
|
of rsShrunk: "shrunk"
|
|
|
|
proc tieBreakName(t: TieBreakMode): string =
|
|
case t
|
|
of tbOff: "off"
|
|
of tbPoint: "point"
|
|
of tbPointCommit: "pointCommit"
|
|
|
|
proc sourceOf(name: string): string =
|
|
## Parsed-value knobs go through `getEnv(name, "")`; an empty value means the
|
|
## shipped default, so a non-empty value is the only thing that counts as
|
|
## "from the env". A value that came from the resolved .env file is labelled
|
|
## `.env` even though the file applied it with putEnv (so `getEnv` finds it).
|
|
if isFromEnvFile(name): ".env"
|
|
elif getEnv(name, "").len > 0: "env"
|
|
else: "default"
|
|
|
|
proc sourceOfPresence(name: string): string =
|
|
## Flags read with `existsEnv` are "from the env" whenever they are present,
|
|
## even if their value is "0" (presence is what the code tests). A value that
|
|
## came from the .env file is labelled `.env`.
|
|
if isFromEnvFile(name): ".env"
|
|
elif existsEnv(name): "env"
|
|
else: "default"
|
|
|
|
proc disabledGunsString(guns: HashSet[int]): string =
|
|
if guns.len == 0: return "none"
|
|
var ids: seq[int]
|
|
for id in guns: ids.add id
|
|
ids.sort()
|
|
for i, id in ids:
|
|
if i > 0: result.add ","
|
|
result.add $id
|
|
|
|
proc emit(name, value, source: string) =
|
|
echo "[env] ", name, " = ", value, " (source: ", source, ")"
|
|
|
|
proc readProcFile(path: string): string =
|
|
## Best-effort /proc read; never raises, returns "" when unavailable.
|
|
try: readFile(path)
|
|
except CatchableError: ""
|
|
|
|
proc ppidFromStatus(): int =
|
|
for line in readProcFile("/proc/self/status").splitLines():
|
|
if line.startsWith("PPid:"):
|
|
let parts = line.splitWhitespace()
|
|
if parts.len >= 2:
|
|
try: return parseInt(parts[1])
|
|
except ValueError: return -1
|
|
-1
|
|
|
|
proc cmdlineOf(pid: int): string =
|
|
## NUL-separated argv from /proc/<pid>/cmdline, flattened for display.
|
|
if pid < 0: return "(unavailable)"
|
|
let raw = readProcFile("/proc/" & $pid & "/cmdline")
|
|
if raw.len == 0: return "(unavailable)"
|
|
result = raw.replace('\0', ' ').strip()
|
|
if result.len == 0: result = "(available but empty)"
|
|
|
|
proc selfCmdline(): string =
|
|
let raw = readProcFile("/proc/self/cmdline")
|
|
if raw.len == 0:
|
|
return getAppFilename()
|
|
result = raw.replace('\0', ' ').strip()
|
|
if result.len == 0: result = getAppFilename()
|
|
|
|
# ── section A: the raw process environment + process identity ────────────────
|
|
|
|
proc printRawEnvironment() =
|
|
echo "[env] --- A. raw process environment (what THIS process really has) ---"
|
|
|
|
var rawVars: seq[(string, string)]
|
|
var total = 0
|
|
for key, val in envPairs():
|
|
inc total
|
|
if key.startsWith("TR_") or key.startsWith("GUN_"):
|
|
rawVars.add (key, val)
|
|
rawVars.sort(proc(a, b: (string, string)): int = cmp(a[0], b[0]))
|
|
|
|
for (key, val) in rawVars:
|
|
echo "[env] ", key, "=", val
|
|
|
|
echo "[env] raw: ", rawVars.len, " TR_*/GUN_* of ", total,
|
|
" total environment variables"
|
|
|
|
if rawVars.len == 0:
|
|
echo "[env] WARNING: this process has NO TR_*/GUN_* variables - the env you " &
|
|
"exported did NOT reach the bot. See docs/env_reference.md."
|
|
|
|
# Process identity. The parent is the process that SPAWNED the bot: while
|
|
# running under the GUI it is the SERVER/GUI, never the user's interactive
|
|
# shell — which is exactly why exporting in an unrelated terminal does nothing.
|
|
let pid = getCurrentProcessId()
|
|
let ppid = ppidFromStatus()
|
|
echo "[env] pid=", pid, " ppid=", ppid
|
|
echo "[env] cwd=", getCurrentDir()
|
|
echo "[env] self_cmd=", selfCmdline()
|
|
echo "[env] parent_cmd=", cmdlineOf(ppid)
|
|
|
|
# ── section B: effective values ──────────────────────────────────────────────
|
|
|
|
proc printEffectiveValues(ctx: EnvReportContext) =
|
|
echo "[env] --- B. effective values (resolved at boot) ---"
|
|
|
|
# ── movement / process-level ModularBot knobs ─────────────────────────────
|
|
# Only `tfil_ring` and `strafe` select a non-default engine; every other value
|
|
# runs `tfil`, so report the RESOLVED engine rather than the raw string.
|
|
let effectiveMovement =
|
|
if ctx.movementName == "tfil_ring": "tfil_ring"
|
|
elif ctx.movementName == "strafe": "strafe"
|
|
elif ctx.movementName == "surf": "surf"
|
|
elif ctx.movementName == "learned": "learned"
|
|
else: "tfil"
|
|
emit("TR_MOVEMENT", effectiveMovement, sourceOf("TR_MOVEMENT"))
|
|
emit("TR_MOVEMENT_LOG", onOff(MovementLog), sourceOfPresence("TR_MOVEMENT_LOG"))
|
|
emit("TR_VBULLET_ADMIT_ONLY", onOff(ctx.vBulletAdmitOnly),
|
|
sourceOf("TR_VBULLET_ADMIT_ONLY"))
|
|
emit("TR_VBULLET_DEBUG", onOff(ctx.vBulletDebug),
|
|
sourceOfPresence(VBulletDebugEnv))
|
|
emit("TR_VBULLET_DEBUG_GUN",
|
|
(if ctx.vBulletDebugGun.len == 0: "(unset -> selected gun)"
|
|
else: ctx.vBulletDebugGun),
|
|
sourceOf(VBulletDebugGunEnv))
|
|
emit("TR_VBULLET_DEBUG_MAX", $ctx.vBulletDebugMax,
|
|
sourceOf(VBulletDebugMaxEnv))
|
|
emit("TR_POWER_LOG", onOff(ctx.powerLog), sourceOfPresence("TR_POWER_LOG"))
|
|
emit("TR_RESULT_LOG", onOff(ctx.resultLog), sourceOf("TR_RESULT_LOG"))
|
|
emit("TR_RECORD_WORLDSTATE", onOff(ctx.recordWorldState),
|
|
sourceOfPresence("TR_RECORD_WORLDSTATE"))
|
|
emit("TR_RADAR_FORCE_SPIN", onOff(ctx.radarForceSpin),
|
|
sourceOfPresence("TR_RADAR_FORCE_SPIN"))
|
|
emit("TR_RADAR_SCANLOG", onOff(ctx.radarScanLog),
|
|
sourceOfPresence("TR_RADAR_SCANLOG"))
|
|
emit("TR_RADAR_SCAN_LOG_PATH", ctx.radarScanLogPath,
|
|
sourceOf("TR_RADAR_SCAN_LOG_PATH"))
|
|
emit("TR_TRACKER_PROBE", onOff(ctx.trackerProbe),
|
|
sourceOfPresence("TR_TRACKER_PROBE"))
|
|
emit("TR_TRACKER_PROBE_PATH", ctx.trackerProbePath,
|
|
sourceOf("TR_TRACKER_PROBE_PATH"))
|
|
emit("GUN_STATS_PATH", ctx.gunStatsPath, sourceOf("GUN_STATS_PATH"))
|
|
emit("GUN_SHOTLOG_PATH", ctx.shotLogPath, sourceOf("GUN_SHOTLOG_PATH"))
|
|
let seed = getEnv("GUN_SELECTOR_SEED", "")
|
|
if seed.len > 0:
|
|
emit("GUN_SELECTOR_SEED", seed, "env")
|
|
else:
|
|
emit("GUN_SELECTOR_SEED", "(unset -> time+pid)", "default")
|
|
|
|
# ── rack ──────────────────────────────────────────────────────────────────
|
|
emit("GUN_RACK_DISABLE", disabledGunsString(ctx.disabledGuns),
|
|
sourceOf("GUN_RACK_DISABLE"))
|
|
for i in 0..<len(RackGunNames):
|
|
let name = "TR_RACK_" & RackGunNames[i]
|
|
emit(name, rackMembershipName(ActiveRackMembership[i]), sourceOf(name))
|
|
echo "[env] rack active 1v1 = ", rackActive(ActiveRackMembership, rm1v1)
|
|
echo "[env] rack active melee = ", rackActive(ActiveRackMembership, rmMelee)
|
|
|
|
# ── the selector (virtual-bullet fitness) ─────────────────────────────────
|
|
emit("GUN_VBULLET_METRIC", metricName(ActiveMetric), sourceOf("GUN_VBULLET_METRIC"))
|
|
emit("GUN_SELECTOR_MODE", selectorModeName(ActiveSelectorMode), sourceOf("GUN_SELECTOR_MODE"))
|
|
emit("GUN_SELECTOR_WINDOW", $ActiveWindow, sourceOf("GUN_SELECTOR_WINDOW"))
|
|
emit("GUN_SELECTOR_MINOBS", $ActiveMinObs, sourceOf("GUN_SELECTOR_MINOBS"))
|
|
emit("GUN_SELECTOR_TIE", $ActiveRelTie, sourceOf("GUN_SELECTOR_TIE"))
|
|
emit("GUN_SELECTOR_FLOOR", $ActiveFloorFrac, sourceOf("GUN_SELECTOR_FLOOR"))
|
|
emit("GUN_SELECTOR_POOL", onOff(ActivePooled), sourceOf("GUN_SELECTOR_POOL"))
|
|
emit("GUN_SELECTOR_RANK", rankName(ActiveRank), sourceOf("GUN_SELECTOR_RANK"))
|
|
emit("GUN_SELECTOR_SHRINK", $ActiveShrink, sourceOf("GUN_SELECTOR_SHRINK"))
|
|
emit("GUN_SELECTOR_DWELL", $ActiveDwellTicks, sourceOf("GUN_SELECTOR_DWELL"))
|
|
emit("GUN_SELECTOR_MARGIN", $ActiveSwitchMargin, sourceOf("GUN_SELECTOR_MARGIN"))
|
|
emit("GUN_SELECTOR_TIEBREAK", tieBreakName(ActiveTieBreak), sourceOf("GUN_SELECTOR_TIEBREAK"))
|
|
emit("GUN_SELECTOR_POINT_TIE", $ActivePointTie, sourceOf("GUN_SELECTOR_POINT_TIE"))
|
|
|
|
# ── the power policy ──────────────────────────────────────────────────────
|
|
emit("TR_POWER_POLICY", onOff(PowerPolicyEnabled), sourceOf("TR_POWER_POLICY"))
|
|
emit("TR_POWER_FAR_DIST", $PowerFarDist, sourceOf("TR_POWER_FAR_DIST"))
|
|
emit("TR_POWER_FAR_CAP", $PowerFarCap, sourceOf("TR_POWER_FAR_CAP"))
|
|
emit("TR_POWER_MID_CAP", $PowerMidCap, sourceOf("TR_POWER_MID_CAP"))
|
|
emit("TR_POWER_REF", $PowerRefFixed, sourceOf("TR_POWER_REF"))
|
|
emit("TR_POWER_ENERGY_HI", $PowerEnergyHi, sourceOf("TR_POWER_ENERGY_HI"))
|
|
emit("TR_POWER_ENERGY_LO", $PowerEnergyLo, sourceOf("TR_POWER_ENERGY_LO"))
|
|
emit("TR_POWER_ENERGY_MIN", $PowerEnergyMin, sourceOf("TR_POWER_ENERGY_MIN"))
|
|
emit("TR_POWER_ENERGY_MAX", $PowerEnergyMax, sourceOf("TR_POWER_ENERGY_MAX"))
|
|
emit("TR_POWER_FINISH_KILL", onOff(PowerFinishKill),
|
|
sourceOf("TR_POWER_FINISH_KILL"))
|
|
|
|
# ── movement (TFIL heat shape + ring) ─────────────────────────────────────
|
|
# NOTE (j119): `TR_TFIL_CORRIDOR_HEAT` / `TR_TFIL_WALL_HOTNESS` drive BOTH
|
|
# movers now — the ring's own copies (reported here) AND the shipped base
|
|
# field (the_floor_is_lava, shipped defaults 20/30/10). The two modules export
|
|
# the same symbol names, so the reads below are module-qualified.
|
|
emit("TR_TFIL_RANGE_LO", $RangeLo, sourceOf("TR_TFIL_RANGE_LO"))
|
|
emit("TR_TFIL_RANGE_HI", $RangeHi, sourceOf("TR_TFIL_RANGE_HI"))
|
|
emit("TR_TFIL_RANGE_TEMP", $RangeTemp, sourceOf("TR_TFIL_RANGE_TEMP"))
|
|
emit("TR_TFIL_RANGE_K", $RangeK, sourceOf("TR_TFIL_RANGE_K"))
|
|
emit("TR_TFIL_CORRIDOR_HEAT", $the_floor_is_lava_ring.CorridorHeat,
|
|
sourceOf("TR_TFIL_CORRIDOR_HEAT"))
|
|
emit("TR_TFIL_WALL_HOTNESS", $the_floor_is_lava_ring.WallHotness,
|
|
sourceOf("TR_TFIL_WALL_HOTNESS"))
|
|
emit("TR_TFIL_WALL_RADIANCE", $the_floor_is_lava.WallRadiance,
|
|
sourceOf("TR_TFIL_WALL_RADIANCE"))
|
|
emit("TR_TFIL_TILE_REPLAN", tileReplanName(TfilTileReplanMode),
|
|
sourceOf("TR_TFIL_TILE_REPLAN"))
|
|
emit("TR_TFIL_COMMIT_TICKS", $TfilCommitTicks, sourceOf("TR_TFIL_COMMIT_TICKS"))
|
|
emit("TR_TFIL_NO_REV", onOff(TfilNoRev), sourceOfPresence("TR_TFIL_NO_REV"))
|
|
emit("TR_TFIL_COMMIT_LOG", TfilCommitLogPath, sourceOfPresence("TR_TFIL_COMMIT_LOG"))
|
|
# time-indexed bullet heat (default off = shipped flat model)
|
|
emit("TR_TFIL_HEAT_TIME", onOff(TfilHeatTime), sourceOf("TR_TFIL_HEAT_TIME"))
|
|
emit("TR_TFIL_HEAT_TAU", $TfilHeatTau, sourceOf("TR_TFIL_HEAT_TAU"))
|
|
emit("TR_TFIL_HEAT_POWER_GAIN", $TfilHeatPowerGain,
|
|
sourceOf("TR_TFIL_HEAT_POWER_GAIN"))
|
|
emit("TR_TFIL_PILLAR_ON", onOff(PillarHotness > 0.0),
|
|
sourceOf("TR_TFIL_PILLAR_ON"))
|
|
|
|
# ── movement (STRAFE) ─────────────────────────────────────────────────────
|
|
emit("TR_STRAFE_BAND", $StrafeBand, sourceOf("TR_STRAFE_BAND"))
|
|
emit("TR_STRAFE_SPREAD", $StrafeSpread, sourceOf("TR_STRAFE_SPREAD"))
|
|
emit("TR_STRAFE_REACH", $StrafeReach, sourceOf("TR_STRAFE_REACH"))
|
|
emit("TR_STRAFE_DWELL_MIN", $StrafeDwellMin, sourceOf("TR_STRAFE_DWELL_MIN"))
|
|
emit("TR_STRAFE_DWELL_MAX", $StrafeDwellMax, sourceOf("TR_STRAFE_DWELL_MAX"))
|
|
emit("TR_STRAFE_LOG", onOff(StrafeLog), sourceOfPresence("TR_STRAFE_LOG"))
|
|
emit("TR_STRAFE_RANGE", $StrafeRange, sourceOf("TR_STRAFE_RANGE"))
|
|
emit("TR_STRAFE_RANGE_TOL", $StrafeRangeTol, sourceOf("TR_STRAFE_RANGE_TOL"))
|
|
emit("TR_STRAFE_TILT_MAX", $StrafeTiltMax, sourceOf("TR_STRAFE_TILT_MAX"))
|
|
emit("TR_STRAFE_TILT_GAIN", $StrafeTiltGain, sourceOf("TR_STRAFE_TILT_GAIN"))
|
|
# curved wings + wall escape (j112)
|
|
emit("TR_STRAFE_KAPPA", $StrafeKappa, sourceOf("TR_STRAFE_KAPPA"))
|
|
emit("TR_STRAFE_WALL_MARGIN", $StrafeWallMargin, sourceOf("TR_STRAFE_WALL_MARGIN"))
|
|
emit("TR_STRAFE_WING_MAX", $StrafeWingMax, sourceOf("TR_STRAFE_WING_MAX"))
|
|
emit("TR_STRAFE_WALL_BIAS", $StrafeWallBias, sourceOf("TR_STRAFE_WALL_BIAS"))
|
|
emit("TR_STRAFE_WALL_SAFE", $StrafeWallSafe, sourceOf("TR_STRAFE_WALL_SAFE"))
|
|
emit("TR_STRAFE_ESCAPE", onOff(StrafeEscape), sourceOf("TR_STRAFE_ESCAPE"))
|
|
emit("TR_STRAFE_FIRE_FIX", onOff(StrafeFireFix), sourceOf("TR_STRAFE_FIRE_FIX"))
|
|
# j134: the global switch the shared `movement_harness/fire_tracker` reads.
|
|
# Every mover's effective value is this AND (for STRAFE) TR_STRAFE_FIRE_FIX.
|
|
emit("TR_FIRE_FIX", onOff(TfilFireFix), sourceOf("TR_FIRE_FIX"))
|
|
# j134 TASK B diagnostic (off by default): per-reading tick/raw/correction trace.
|
|
emit("TR_FIRE_DIAG", onOff(StrafeFireDiag), sourceOf("TR_FIRE_DIAG"))
|
|
emit("TR_STRAFE_HEAT_GRID", onOff(StrafeHeatGrid), sourceOf("TR_STRAFE_HEAT_GRID"))
|
|
# STRAFE's heat shape is its own RETUNE (bullet 20/10, corridor 10, wall
|
|
# 15/5), override-able per run so the shipped field can be A/B'd on one
|
|
# binary at boot.
|
|
emit("TR_STRAFE_BULLET_CORE", $StrafeBulletCore, sourceOf("TR_STRAFE_BULLET_CORE"))
|
|
emit("TR_STRAFE_BULLET_AURA", $StrafeBulletAura, sourceOf("TR_STRAFE_BULLET_AURA"))
|
|
emit("TR_STRAFE_CORRIDOR_HEAT", $StrafeCorridorHeat, sourceOf("TR_STRAFE_CORRIDOR_HEAT"))
|
|
emit("TR_STRAFE_WALL_HOTNESS", $StrafeWallHotness, sourceOf("TR_STRAFE_WALL_HOTNESS"))
|
|
emit("TR_STRAFE_WALL_RADIANCE", $StrafeWallRadiance, sourceOf("TR_STRAFE_WALL_RADIANCE"))
|
|
|
|
# ── movement (WAVE SURFER) ────────────────────────────────────────────────
|
|
emit(SurfPrefDistEnv, $SurfPrefDist, sourceOf(SurfPrefDistEnv))
|
|
emit(SurfDistBandEnv, $SurfDistBand, sourceOf(SurfDistBandEnv))
|
|
emit(SurfWallMarginEnv, $SurfWallMargin, sourceOf(SurfWallMarginEnv))
|
|
emit(SurfRadialFracEnv, $SurfRadialFrac, sourceOf(SurfRadialFracEnv))
|
|
emit(SurfLogEnv, onOff(SurfLog), sourceOfPresence(SurfLogEnv))
|
|
|
|
# ── ramming ───────────────────────────────────────────────────────────────
|
|
emit("TR_RAM_OPPORTUNITY", onOff(RamOppEnabled), sourceOf("TR_RAM_OPPORTUNITY"))
|
|
emit("TR_RAM_OPP_DIST", $RamOppDist, sourceOf("TR_RAM_OPP_DIST"))
|
|
emit("TR_RAM_OPP_MARGIN", $RamOppMargin, sourceOf("TR_RAM_OPP_MARGIN"))
|
|
emit("TR_RAM_ABORT_DMG", $RamAbortDmg, sourceOf("TR_RAM_ABORT_DMG"))
|
|
emit("TR_RAM_PLAN", onOff(RamPlanEnabled), sourceOf("TR_RAM_PLAN"))
|
|
emit("TR_RAM_PLAN_DIST", $RamPlanDist, sourceOf("TR_RAM_PLAN_DIST"))
|
|
emit("TR_RAM_PLAN_MARGIN", $RamPlanMargin, sourceOf("TR_RAM_PLAN_MARGIN"))
|
|
emit("TR_RAM_PLAN_HITRATE", $RamPlanHitRate, sourceOf("TR_RAM_PLAN_HITRATE"))
|
|
emit("TR_RAM_LOG", onOff(RamLog), sourceOfPresence("TR_RAM_LOG"))
|
|
|
|
# ── the horizon TM gun ────────────────────────────────────────────────────
|
|
# `resetLearning`/`targetChanged` resolve the lazily-read fields at round
|
|
# start; force them here on a COPY so the report reads real resolved values
|
|
# without touching the live gun's state.
|
|
var tmh = ctx.tmHorizon
|
|
discard tmh.targetChanged(-1)
|
|
emit("TR_TMHORIZON_SHIFT", $tmh.shiftDeg, sourceOf("TR_TMHORIZON_SHIFT"))
|
|
emit("TR_TMHORIZON_BIG_MULT", $tmh.bigMult, sourceOf("TR_TMHORIZON_BIG_MULT"))
|
|
emit("TR_TMHORIZON_LOG", onOff(tmh.logEnabled), sourceOfPresence("TR_TMHORIZON_LOG"))
|
|
emit("TR_TMHORIZON_RESET_ON_TARGET", onOff(tmh.resetOnTarget),
|
|
sourceOf("TR_TMHORIZON_RESET_ON_TARGET"))
|
|
emit("TR_TMHORIZON_NSTATES", $tmh.nStates, sourceOf("TR_TMHORIZON_NSTATES"))
|
|
emit("TR_TMHORIZON_WINDOW", $tmh.windowN, sourceOf("TR_TMHORIZON_WINDOW"))
|
|
emit("TR_TMHORIZON_RESET_DROP", $tmh.resetDrop, sourceOf("TR_TMHORIZON_RESET_DROP"))
|
|
emit("TR_TMHORIZON_ACCURVE", onOff(tmh.accurveEnabled),
|
|
sourceOfPresence("TR_TMHORIZON_ACCURVE"))
|
|
emit("TR_TMHORIZON_RETRAIN_EVERY", $tmh.retrainEvery,
|
|
sourceOf("TR_TMHORIZON_RETRAIN_EVERY"))
|
|
emit("TR_TMHORIZON_EPOCHS", $tmh.retrainEpochs, sourceOf("TR_TMHORIZON_EPOCHS"))
|
|
|
|
# ── the BitBrain ADE+SBC corrector ────────────────────────────────────────
|
|
# All knobs are resolved at gun construction (`initBitBrainGun`), so the
|
|
# context holds the real values. The network itself is only built on the first
|
|
# `predict`, which is why `[rack]` membership is the actual enable switch.
|
|
emit("TR_BITBRAIN_MEM", memModeName(ctx.bitbrain.memMode),
|
|
sourceOf(BB_MEM_ENV))
|
|
emit("TR_BITBRAIN_GAINS", bbGainsString(ctx.bitbrain.cands),
|
|
sourceOf(BB_GAINS_ENV))
|
|
emit("TR_BITBRAIN_N", $ctx.bitbrain.nClasses, sourceOf(BB_N_ENV))
|
|
emit("TR_BITBRAIN_NADE", $ctx.bitbrain.nAde, sourceOf(BB_NADE_ENV))
|
|
emit("TR_BITBRAIN_RANGE", $ctx.bitbrain.maxDeg, sourceOf(BB_RANGE_ENV))
|
|
emit("TR_BITBRAIN_LOG", onOff(ctx.bitbrain.logEnabled),
|
|
sourceOfPresence(BB_LOG_ENV))
|
|
emit("TR_BITBRAIN_MIN_OBS", $ctx.bitbrain.minObs, sourceOf(BB_MIN_OBS_ENV))
|
|
emit("TR_BITBRAIN_WARMUP", $ctx.bitbrain.warmupN, sourceOf(BB_WARMUP_ENV))
|
|
emit("TR_BITBRAIN_ADAPT", $ctx.bitbrain.adaptEvery, sourceOf(BB_ADAPT_ENV))
|
|
emit("TR_BITBRAIN_CALIB", $ctx.bitbrain.calibEvery, sourceOf(BB_CALIB_ENV))
|
|
emit("TR_BITBRAIN_DECAY", $ctx.bitbrain.decayEvery, sourceOf(BB_DECAY_ENV))
|
|
emit("TR_BITBRAIN_DECAY_FRAC", $ctx.bitbrain.decayFrac,
|
|
sourceOf(BB_DECAY_FRAC_ENV))
|
|
emit("TR_BITBRAIN_SEED", $ctx.bitbrain.seed, sourceOf(BB_SEED_ENV))
|
|
emit("TR_BITBRAIN_RESET_ON_TARGET", onOff(ctx.bitbrain.resetOnTarget),
|
|
sourceOf(BB_RESET_ON_TARGET_ENV))
|
|
|
|
# ── Pattern match-shape + radial knobs ────────────────────────────────────
|
|
# These are resolved lazily inside `predict` (which has not run at boot), so
|
|
# unless something already forced them we report the raw env value and say so.
|
|
if ctx.patternMatcher.paramConfigured:
|
|
emit("TR_PATTERN_LEN", $ctx.patternMatcher.patternLen, sourceOf("TR_PATTERN_LEN"))
|
|
emit("TR_PATTERN_DEPTH", $ctx.patternMatcher.histDepth, sourceOf("TR_PATTERN_DEPTH"))
|
|
else:
|
|
echo "[env] TR_PATTERN_LEN = ",
|
|
getEnv("TR_PATTERN_LEN", $PatternLen), " (raw - not resolved here)"
|
|
echo "[env] TR_PATTERN_DEPTH = ",
|
|
getEnv("TR_PATTERN_DEPTH", $PatternDepth), " (raw - not resolved here)"
|
|
if ctx.patternMatcher.radConfigured:
|
|
emit("TR_PATTERN_RAD_OFFSET", $ctx.patternMatcher.radOffset, sourceOf("TR_PATTERN_RAD_OFFSET"))
|
|
emit("TR_PATTERN_RAD_SCALE", $ctx.patternMatcher.radScale, sourceOf("TR_PATTERN_RAD_SCALE"))
|
|
else:
|
|
echo "[env] TR_PATTERN_RAD_OFFSET = ",
|
|
getEnv("TR_PATTERN_RAD_OFFSET", "0.0"), " (raw - not resolved here)"
|
|
echo "[env] TR_PATTERN_RAD_SCALE = ",
|
|
getEnv("TR_PATTERN_RAD_SCALE", "1.0"), " (raw - not resolved here)"
|
|
|
|
# ── the report's own switch ───────────────────────────────────────────────
|
|
emit(EnvReportEnableEnvVar, getEnv(EnvReportEnableEnvVar, "1"),
|
|
sourceOf(EnvReportEnableEnvVar))
|
|
|
|
# ── build identity (which binary is the GUI actually running?) ───────────────
|
|
|
|
proc printBuildIdentity() =
|
|
## The user has been bitten by a stale binary. CompileDate/CompileTime are
|
|
## available at runtime (even if a reproducible-build toolchain pins them),
|
|
## and the binary path + mtime + size pin down the exact artifact.
|
|
echo "[env] --- build identity ---"
|
|
echo "[env] build nim=", NimVersion,
|
|
" compileDate=", CompileDate, " compileTime=", CompileTime
|
|
let bin = getAppFilename()
|
|
var size = -1
|
|
var mtime = "unknown"
|
|
if fileExists(bin):
|
|
try:
|
|
size = getFileSize(bin)
|
|
mtime = $getLastModificationTime(bin)
|
|
except CatchableError:
|
|
discard
|
|
echo "[env] build binary=", bin, " size=", size, " mtime=", mtime
|
|
|
|
# ── Part 2: warnings about env mistakes ──────────────────────────────────────
|
|
#
|
|
# This is the point of the report. A typo must be LOUD, not a silent no-op.
|
|
# Two mistakes are caught:
|
|
#
|
|
# 1. an unknown TR_*/GUN_* name (e.g. TR_TMHORIZON_NSTATE, missing S);
|
|
# 2. a compile-time `{.intdefine.}`/`{.strdefine.}`/`{.booldefine.}` symbol
|
|
# exported as an env var (e.g. `TMH_NSTATES=2`), which is NEVER read at
|
|
# runtime and therefore does nothing.
|
|
#
|
|
# The known-name set is assembled from the modules' exported env-name
|
|
# constants; the remaining inline reads are listed once here. The guard test
|
|
# `common_libs/tests/test_env_report.nim` scans the tree for env reads and for
|
|
# intdefine/strdefine/booldefine symbols and fails loudly if a name read
|
|
# anywhere (or a new define) is missing from these tables, so neither can
|
|
# silently drift.
|
|
|
|
proc knownEnvNames*(): seq[string] =
|
|
## Every TR_*/GUN_* name this project reads at runtime. Order is irrelevant;
|
|
## callers put it in a set. Keep in sync with the tree scan in the guard test.
|
|
result = @[
|
|
# exported constants — the single source of truth (do not re-spell these)
|
|
MetricEnvVar, SelectorModeEnvVar, TieBreakEnvVar,
|
|
RackShareEnvVar,
|
|
PowerPolicyEnvVar, PowerFarDistEnvVar, PowerFarCapEnvVar, PowerMidCapEnvVar,
|
|
PowerRefEnvVar, PowerEnergyHiEnvVar, PowerEnergyLoEnvVar,
|
|
PowerEnergyMinEnvVar, PowerEnergyMaxEnvVar, PowerFinishKillEnvVar,
|
|
PatternRadOffsetEnvVar, PatternRadScaleEnvVar,
|
|
PatternLenEnvVar, PatternDepthEnvVar,
|
|
TMH_SHIFT_ENV, TMH_BIG_MULT_ENV, TMH_LOG_ENV, TMH_RESET_ON_TARGET_ENV,
|
|
TMH_WINDOW_ENV, TMH_RESET_DROP_ENV, TMH_NSTATES_ENV, TMH_ACCURVE_ENV,
|
|
TMH_RETRAIN_EVERY_ENV, TMH_EPOCHS_ENV,
|
|
BB_MEM_ENV, BB_GAINS_ENV, BB_N_ENV, BB_NADE_ENV, BB_RANGE_ENV, BB_LOG_ENV,
|
|
BB_MIN_OBS_ENV, BB_WARMUP_ENV, BB_ADAPT_ENV, BB_CALIB_ENV, BB_DECAY_ENV,
|
|
BB_DECAY_FRAC_ENV, BB_SEED_ENV, BB_RESET_ON_TARGET_ENV,
|
|
# common_libs/bitbrain (counted-SBC library) reads these inline; job j102
|
|
"TR_BITBRAIN_MODE", "TR_BITBRAIN_DECAY_EVERY", "TR_BITBRAIN_DECAY_SHIFT",
|
|
]
|
|
# rack names are constructed from the prefix + gun table, not spelled out
|
|
for g in RackGunNames:
|
|
result.add RackEnvPrefix & g
|
|
# inline reads with no exported constant (the guard test scans for these)
|
|
for n in [
|
|
"GUN_SELECTOR_WINDOW", "GUN_SELECTOR_MINOBS", "GUN_SELECTOR_TIE",
|
|
"GUN_SELECTOR_FLOOR", "GUN_SELECTOR_POOL", "GUN_SELECTOR_RANK",
|
|
"GUN_SELECTOR_SHRINK", "GUN_SELECTOR_DWELL", "GUN_SELECTOR_MARGIN",
|
|
"GUN_SELECTOR_POINT_TIE", "GUN_SELECTOR_SEED",
|
|
"GUN_RACK_DISABLE", "GUN_STATS_PATH", "GUN_SHOTLOG_PATH",
|
|
"TR_MOVEMENT", "TR_MOVEMENT_LOG", "TR_RECORD_WORLDSTATE",
|
|
"TR_RADAR_FORCE_SPIN", "TR_RADAR_SCANLOG", "TR_RADAR_SCAN_LOG_PATH",
|
|
"TR_TRACKER_PROBE", "TR_TRACKER_PROBE_PATH", "TR_VBULLET_ADMIT_ONLY",
|
|
VBulletDebugEnv, VBulletDebugGunEnv, VBulletDebugMaxEnv,
|
|
"TR_POWER_LOG",
|
|
"TR_RESULT_LOG",
|
|
# the .env loader's own environment hook (TR_ENV_FILE names the file)
|
|
"TR_ENV_FILE",
|
|
"TR_RAM_OPPORTUNITY", "TR_RAM_OPP_DIST", "TR_RAM_OPP_MARGIN",
|
|
"TR_RAM_ABORT_DMG", "TR_RAM_PLAN", "TR_RAM_PLAN_DIST",
|
|
"TR_RAM_PLAN_MARGIN", "TR_RAM_PLAN_HITRATE", "TR_RAM_LOG",
|
|
"TR_TFIL_RANGE_LO", "TR_TFIL_RANGE_HI", "TR_TFIL_RANGE_TEMP",
|
|
"TR_TFIL_RANGE_K", "TR_TFIL_CORRIDOR_HEAT", "TR_TFIL_WALL_HOTNESS",
|
|
"TR_TFIL_WALL_RADIANCE",
|
|
"TR_TFIL_TILE_REPLAN", "TR_TFIL_COMMIT_TICKS", "TR_TFIL_NO_REV",
|
|
"TR_TFIL_COMMIT_LOG",
|
|
"TR_TFIL_HEAT_TIME", "TR_TFIL_HEAT_TAU", "TR_TFIL_HEAT_POWER_GAIN",
|
|
"TR_TFIL_PILLAR_ON",
|
|
"TR_STRAFE_BAND", "TR_STRAFE_SPREAD", "TR_STRAFE_REACH",
|
|
"TR_STRAFE_DWELL_MIN", "TR_STRAFE_DWELL_MAX", "TR_STRAFE_LOG",
|
|
"TR_STRAFE_RANGE", "TR_STRAFE_RANGE_TOL", "TR_STRAFE_TILT_MAX",
|
|
"TR_STRAFE_TILT_GAIN",
|
|
"TR_STRAFE_KAPPA", "TR_STRAFE_WALL_MARGIN", "TR_STRAFE_WING_MAX",
|
|
"TR_STRAFE_WALL_BIAS", "TR_STRAFE_WALL_SAFE", "TR_STRAFE_ESCAPE",
|
|
"TR_STRAFE_FIRE_FIX",
|
|
"TR_FIRE_FIX",
|
|
"TR_FIRE_DIAG",
|
|
"TR_STRAFE_HEAT_GRID", "TR_STRAFE_BULLET_CORE", "TR_STRAFE_BULLET_AURA",
|
|
"TR_STRAFE_CORRIDOR_HEAT", "TR_STRAFE_WALL_HOTNESS",
|
|
"TR_STRAFE_WALL_RADIANCE",
|
|
SurfPrefDistEnv, SurfDistBandEnv, SurfWallMarginEnv, SurfRadialFracEnv,
|
|
SurfLogEnv,
|
|
# movements/learned_surfer.nim (the state-conditional learned danger mover)
|
|
LearnedDecayEveryEnv, LearnedDecayShiftEnv, LearnedAlphaEnv,
|
|
LearnedTravelEnv, LearnedReversalEnv, LearnedPrefDistEnv,
|
|
LearnedDistBandEnv, LearnedRadialFracEnv, LearnedWallMarginEnv,
|
|
LearnedGlobalEnv, LearnedLabelEnv, LearnedRealEventsEnv, LearnedLogEnv,
|
|
# harness vars (read by the test framework, inherited by the bot, so they
|
|
# must NOT be reported as typos)
|
|
"TR_SERVER_JAR", "TR_BATTLE_RUNNER", "TR_BATTLE_RUNNER_DIR",
|
|
# the report's own switch
|
|
EnvReportEnableEnvVar,
|
|
]:
|
|
result.add n
|
|
|
|
proc unknownEnvNames*(pairs: openArray[(string, string)]): seq[string] =
|
|
## Pure: every TR_*/GUN_* name in `pairs` the bot does not read, sorted.
|
|
## Anything else (e.g. PATH, JAVA_HOME) is deliberately ignored.
|
|
let known = knownEnvNames().toHashSet()
|
|
for (key, _) in pairs:
|
|
if (key.startsWith("TR_") or key.startsWith("GUN_")) and key notin known:
|
|
result.add key
|
|
result.sort()
|
|
|
|
type
|
|
DefineMapping* = object
|
|
## A `{.intdefine.}`/`{.strdefine.}`/`{.booldefine.}` symbol and the runtime
|
|
## env var (if any) that actually controls the same knob. `suggestedEnv` is
|
|
## the env name a user would naturally guess, used only for the
|
|
## "does not exist" text when `runtimeEnv` is empty.
|
|
defineName*: string
|
|
runtimeEnv*: string
|
|
suggestedEnv*: string
|
|
|
|
## Derived by grepping the tree for intdefine/strdefine/booldefine (see the
|
|
## guard test, which re-greps and fails if this table drifts):
|
|
## tm_horizon.nim:101,102,103,106,108,139 — TMH_* ; only NSTATES has an env
|
|
## tm_pattern.nim:50,53,55,57,59,62,65,68,69,85,87,92,94,96 — TM_*
|
|
## tsetlin.nim:29,116,118,119,120 — TM_*
|
|
## docs/env_reference.md documents the same table.
|
|
const CompileTimeDefineMap*: seq[DefineMapping] = @[
|
|
# tm_horizon.nim — the ONLY define with a runtime twin (`TR_TMHORIZON_NSTATES`)
|
|
DefineMapping(defineName: "TMH_NSTATES", runtimeEnv: "TR_TMHORIZON_NSTATES"),
|
|
DefineMapping(defineName: "TMH_NCLAUSES", suggestedEnv: "TR_TMHORIZON_NCLAUSES"),
|
|
DefineMapping(defineName: "TMH_S_DEF", suggestedEnv: "TR_TMHORIZON_S_DEF"),
|
|
DefineMapping(defineName: "TMH_MIN_OBS", suggestedEnv: "TR_TMHORIZON_MIN_OBS"),
|
|
DefineMapping(defineName: "TMH_STALE_MAX", suggestedEnv: "TR_TMHORIZON_STALE_MAX"),
|
|
# tm_pattern.nim — no env form at all; do not invent a plausible one
|
|
DefineMapping(defineName: "TM_NCLAUSES"),
|
|
DefineMapping(defineName: "TM_NSTATES"),
|
|
DefineMapping(defineName: "TM_MIN_OBS"),
|
|
DefineMapping(defineName: "TM_CLASSES"),
|
|
DefineMapping(defineName: "TM_CONF_MARGIN_DEF"),
|
|
DefineMapping(defineName: "TM_SHRINK_DEF"),
|
|
DefineMapping(defineName: "TM_GF_MODE"),
|
|
DefineMapping(defineName: "TM_SOFT_BETA_DEF"),
|
|
DefineMapping(defineName: "TM_RADIAL_RANGE_DEF"),
|
|
DefineMapping(defineName: "TM_RAD_MARGIN_DEF"),
|
|
DefineMapping(defineName: "TM_REV_TURN_DEG_DEF"),
|
|
DefineMapping(defineName: "TM_REV_MARGIN_DEF"),
|
|
DefineMapping(defineName: "TM_REV_GAIN_DEF"),
|
|
# tsetlin.nim — no env form at all
|
|
DefineMapping(defineName: "TM_N_CLAUSES"),
|
|
DefineMapping(defineName: "TM_N_STATES"),
|
|
DefineMapping(defineName: "TM_T_DEF"),
|
|
DefineMapping(defineName: "TM_WINDOW_SIZE"),
|
|
# TM_S_DEF is shared by tm_pattern.nim AND tsetlin.nim (one -d sets both)
|
|
DefineMapping(defineName: "TM_S_DEF"),
|
|
]
|
|
|
|
proc isCompileTimeDefine*(name: string): bool =
|
|
## True when `name` is one of our -d: symbols (never a runtime env var).
|
|
for m in CompileTimeDefineMap:
|
|
if m.defineName == name: return true
|
|
|
|
proc setCompileTimeDefines*(pairs: openArray[(string, string)]): seq[string] =
|
|
## Pure: the -d: symbols present as env vars, sorted.
|
|
for (key, _) in pairs:
|
|
if isCompileTimeDefine(key): result.add key
|
|
result.sort()
|
|
|
|
proc defineWarningMessage*(defineName, value: string): string =
|
|
## The exact warning text for one -d: symbol set as an env var. "" when the
|
|
## name is not a known define.
|
|
for m in CompileTimeDefineMap:
|
|
if m.defineName != defineName: continue
|
|
let head = "[env] WARNING: " & defineName & "=" & value &
|
|
" is a COMPILE-TIME define (-d:" & defineName & "=" & value &
|
|
") and is NOT read at runtime; "
|
|
if m.runtimeEnv.len > 0:
|
|
return head & "the runtime equivalent is " & m.runtimeEnv & "=" & value & "."
|
|
if m.suggestedEnv.len > 0:
|
|
return head & m.suggestedEnv &
|
|
" does not exist - this knob is compile-time only."
|
|
return head & "no runtime env equivalent exists - this knob is " &
|
|
"compile-time only."
|
|
return ""
|
|
|
|
proc printMistakeWarnings*() =
|
|
## The two warning paths, boot-only. Emits nothing when the env is clean, so
|
|
## a correct run keeps the documented A/B/build shape.
|
|
var pairs: seq[(string, string)]
|
|
for key, val in envPairs():
|
|
pairs.add (key, val)
|
|
|
|
let unknown = unknownEnvNames(pairs)
|
|
let defines = setCompileTimeDefines(pairs)
|
|
if unknown.len == 0 and defines.len == 0: return
|
|
|
|
echo "[env] --- warnings: misnamed variables ---"
|
|
for key in unknown:
|
|
echo "[env] WARNING: ", key,
|
|
" is not a recognized TR_*/GUN_* variable; it is IGNORED. " &
|
|
"Check the spelling (see docs/env_reference.md)."
|
|
for key in defines:
|
|
echo defineWarningMessage(key, getEnv(key, ""))
|
|
|
|
# ── the entry point ──────────────────────────────────────────────────────────
|
|
|
|
var envReportDone = false
|
|
|
|
proc printEnvReport*(ctx: EnvReportContext) =
|
|
## Print the whole report exactly once per process, to stdout. Suppressed
|
|
## (with one visible note) by TR_ENV_REPORT=0/false/no/off.
|
|
if envReportDone: return
|
|
envReportDone = true
|
|
|
|
let raw = getEnv(EnvReportEnableEnvVar, "").strip().toLowerAscii()
|
|
if raw in ["0", "false", "no", "off"]:
|
|
echo "[env] report suppressed by ", EnvReportEnableEnvVar, "=", raw,
|
|
" (unset or 1 to enable)"
|
|
return
|
|
|
|
echo "[env] === ENVIRONMENT (boot report) ==="
|
|
echo "[env] env file: ",
|
|
(if envFilePath().len > 0: envFilePath() & " (source: " & envFileSource() & ")"
|
|
else: "none")
|
|
printRawEnvironment()
|
|
printMistakeWarnings()
|
|
printEffectiveValues(ctx)
|
|
printBuildIdentity()
|
|
echo "[env] === END ENVIRONMENT ==="
|