Files
SirRoboGarage/common_libs/tests/test_env_report.nim
T
SirStone e9302bc9f6 j140 rebuild a real BitBrain gun: ADE+SBC at rack id 17, default off, and measure its scaling
The BITBRAIN name was sitting on a gun with no network in it. This is the gun
that actually runs the algorithm: an ADE layer (thresholded random projections
with ONLINE threshold adaptation) feeding the SBC head from
common_libs/bitbrain/, with the counted+decay mode available.

  common_libs/guns/bitbrain_net.nim     the gun
  rack name BITBRAIN, rack id 17 (rack 17 -> 18 guns), both new guns default OFF
  admitted by TR_RACK_BITBRAIN=both AND TR_BITBRAIN_NET=1 (the switch that also
  disowns LEADGAIN's legacy TR_BITBRAIN_* aliases)

OUTPUT: a fine-grained aim CORRECTION on top of Pattern - the probability-
weighted mean of the nClasses class centres under inferProb - not a direct aim
point from the argmax. That is the shape docs/bitbrain_gate.md measured, and
Pattern is already a strong predictor, so the net's job is the signed residual.
Below TR_BITBRAIN_MINOBS the shift is exactly 0 and Pattern is returned
unchanged.

INPUT: a CONFIGURED set of FEATURE BLOCKS (TR_BITBRAIN_FEATURES=name:W), each
block's width == its resolution, laid out as a thermometer code over 0/255 slots
(so an ADE synapse 'matches' when its polarity agrees with the slot and a random
ADE fires iff its w synapses all match, rate 2^-w). Default is 52 slots over 9
blocks. NO long temporal window, per docs/state_window_gate.md: the only history
is a 12-tick ring feeding three rate/turn quantities.

Every knob env-configurable: _INPUT (width), _NCLASSES, _NADES, _WIDTHS
(clause widths), _FEATURES, _SPAN, _MODE, _DECAY_EVERY, _DECAY_SHIFT,
_MINOBS, _ADAPT_EVERY, _TARGET, _NETSEED, _NETLOG, _NET_RESET_ON_TARGET.

MEASURED SCALING (measure_bitbrain_scaling.nim, 3 recorded runs, 37412 ticks,
-d:release, one predict per power bin per tick, timed region = predicts only):
  RAM 1.59 MB default (98.6% SBC tensors); linear in nClasses, QUADRATIC in
      nAde, FLAT in input width; counted/bitset = 7.30x on RAM, ~1x on time.
  ms/tick 2.70 default = 21% of the 13.16 ms budget; 64 classes busts it (149%),
      nAde 512 uses 74%, nAde 64 uses 2%.
  CAPACITY vs ACCURACY: over a 100x RAM range the offline mean |err| moves
      17.254 -> 17.115 deg around Pattern's 16.964, and the sign flips along the
      nClasses axis, so it is noise, not a trend. The corrector is consistently
      slightly WORSE than Pattern. The ceiling is the STATE, not the classifier.
      VETO-CAPABLE OFFLINE CHECK ONLY (docs/offline_harness_trust.md), never
      presented as a live win.

ENGAGEMENT is proven, not assumed: test_bitbrain_net.nim (42 checks) shows 0
bytes before first use, different inputs -> different class outputs, a learn
raises SBC occupancy, bitset learn idempotent while counted learn is monotone,
threshold adaptation runs, and the global RNG is untouched.

Parity: shipped rack still onlyPattern, shipped movement still strafe. Guards:
test_env_report 25, test_rack_membership 48, test_tm_pattern_registration 20,
test_lead_gain_registration 13, test_lead_gain_legacy 24, test_bitbrain 56,
test_gun_harness 39, test_tfil_commit_env 30, test_bitbrain_net 42.
Clean archive build: [SuccessX].

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-26 16:50:21 +02:00

162 lines
6.8 KiB
Nim

## Guard test for the boot-time env report's Part 2 (the warnings).
##
## NO battle, NO Java, NO server. Run with:
## nim c -r common_libs/tests/test_env_report.nim
##
## It pins the two tables that must not drift:
## 1. the KNOWN TR_*/GUN_* name set covers every env read in the tree, so a
## new knob cannot silently start producing a false "unknown variable"
## warning (and, conversely, the set cannot rot);
## 2. the compile-time define map covers every `{.intdefine.}`/`{.strdefine.}`
## /`{.booldefine.}` symbol in the tree, and classifies the ONE real env
## twin (`TMH_NSTATES` -> `TR_TMHORIZON_NSTATES`) correctly.
import std/[os, strutils, sets, sequtils]
import gun_harness/selector
import "../../ModularBot_garage/src/env_report"
var failures = 0
proc check(name: string, ok: bool) =
if ok: echo "PASS: ", name
else: echo "FAIL: ", name; inc failures
const repoRoot = currentSourcePath().parentDir.parentDir.parentDir
# ── the source surface the scan walks (the bot's real modules) ───────────────
proc scannedFiles(): seq[string] =
## .nim files under common_libs (excluding common_libs/tests, which are
## probes/measurement scripts, not bot code) and ModularBot_garage/src.
## env_report.nim is excluded: it DEFINES the known set, so scanning it is
## circular (it also names "suggested but nonexistent" env vars on purpose).
for dir in [repoRoot / "common_libs", repoRoot / "ModularBot_garage" / "src"]:
for path in walkDirRec(dir):
if not path.endsWith(".nim"): continue
if "/tests/" in path: continue
if path.endsWith("/env_report.nim"): continue
result.add path
proc literalsOn(line: string): seq[string] =
## Double-quoted string literals on one line (env names have no escapes).
var i = 0
while i < line.len:
if line[i] == '"':
var j = i + 1
while j < line.len and line[j] != '"': inc j
if j >= line.len: break
result.add line[i + 1 ..< j]
i = j + 1
else:
inc i
proc isKnownOrPrefix(name: string, known: HashSet[string]): bool =
## A literal counts as covered when it is a known name OR a prefix of one
## (`TR_RACK_` is the rack prefix; `TR_`/`GUN_` are the filter prefixes).
if name in known: return true
for k in known:
if k.startsWith(name): return true
proc defineSymbolsOn(line: string): seq[string] =
## ` TMH_NCLAUSES* {.intdefine.} = 40` -> "TMH_NCLAUSES"
for pragma in ["{.intdefine.}", "{.strdefine.}", "{.booldefine.}"]:
if pragma in line:
var sym = line.split('{')[0].strip()
if sym.endsWith("*"): sym.setLen(sym.len - 1)
let parts = sym.splitWhitespace()
if parts.len > 0: result.add parts[^1]
# ── 1. the known-name set ────────────────────────────────────────────────────
proc testKnownNames() =
let known = knownEnvNames()
let s = known.toHashSet()
check "known set has no duplicates", known.len == s.len
check "known set only holds TR_*/GUN_* names",
known.allIt(it.startsWith("TR_") or it.startsWith("GUN_"))
check "known set covers all 18 rack names",
RackGunNames.allIt((RackEnvPrefix & it) in s)
for name in ["TR_MOVEMENT", "TR_POWER_ENERGY_MIN", "TR_TMHORIZON_NSTATES",
"GUN_VBULLET_METRIC", "TR_ENV_REPORT", "GUN_SELECTOR_SEED",
"TR_RACK_TMHORIZON", "TR_TMHORIZON_NSTATES"]:
check "known set contains " & name, name in s
# ── 2. the pure warning helpers ──────────────────────────────────────────────
proc testUnknownEnvNames() =
let unknown = unknownEnvNames(@[
("TR_POWER_LOG", "1"), ("GUN_STATS_PATH", "/tmp/x"),
("TR_TMHORIZON_NSTATE", "5"), ("PATH", "/bin"),
("TMH_NSTATES", "2"), ("JAVA_HOME", "/jdk")])
check "unknownEnvNames finds the misspelled TR_* only",
unknown == @["TR_TMHORIZON_NSTATE"]
check "unknownEnvNames ignores non TR_*/GUN_* names",
unknownEnvNames(@[("PATH", "/bin")]) == @[]
proc testDefineMap() =
check "isCompileTimeDefine(TMH_NSTATES)", isCompileTimeDefine("TMH_NSTATES")
check "isCompileTimeDefine(TM_S_DEF)", isCompileTimeDefine("TM_S_DEF")
check "isCompileTimeDefine(TR_TMHORIZON_NSTATES) is false",
not isCompileTimeDefine("TR_TMHORIZON_NSTATES")
check "defineWarningMessage is empty for a non-define",
defineWarningMessage("TR_MOVEMENT", "x") == ""
let m1 = defineWarningMessage("TMH_NSTATES", "2")
check "TMH_NSTATES warns it is compile-time and names TR_TMHORIZON_NSTATES",
"COMPILE-TIME define (-d:TMH_NSTATES=2)" in m1 and
"TR_TMHORIZON_NSTATES=2" in m1
let m2 = defineWarningMessage("TMH_NCLAUSES", "40")
check "TMH_NCLAUSES says TR_TMHORIZON_NCLAUSES does not exist",
"TR_TMHORIZON_NCLAUSES does not exist" in m2
let m3 = defineWarningMessage("TM_S_DEF", "1.5")
check "TM_S_DEF says this knob is compile-time only",
"compile-time only" in m3
let defs = setCompileTimeDefines(@[
("TMH_NSTATES", "2"), ("TR_MOVEMENT", "tfil"), ("TM_S_DEF", "1.5")])
check "setCompileTimeDefines finds both -d symbols",
defs == @["TMH_NSTATES", "TM_S_DEF"]
# ── 3. the tree scans (the anti-drift guard) ─────────────────────────────────
proc testTreeScan() =
let known = knownEnvNames().toHashSet()
var literals = 0
var missing: seq[string]
for path in scannedFiles():
for line in lines(path):
for lit in literalsOn(line):
if lit.startsWith("TR_") or lit.startsWith("GUN_"):
inc literals
if not isKnownOrPrefix(lit, known):
missing.add path.extractFilename & ":" & lit
check "tree scan is alive (>= 50 TR_*/GUN_* literals)", literals >= 50
check "every TR_*/GUN_* literal in the tree is in the known set: " &
$missing, missing.len == 0
proc testDefineScan() =
let mapped = CompileTimeDefineMap.mapIt(it.defineName).toHashSet()
var found: seq[string]
for path in scannedFiles():
for line in lines(path):
for sym in defineSymbolsOn(line):
found.add sym
var missing: seq[string]
for sym in found:
if sym notin mapped: missing.add sym
check "tree define scan is alive (>= 20 symbols)", found.len >= 20
check "every intdefine/strdefine/booldefine is in the map: " &
$missing, missing.len == 0
# ── driver ───────────────────────────────────────────────────────────────────
testKnownNames()
testUnknownEnvNames()
testDefineMap()
testTreeScan()
testDefineScan()
if failures > 0:
echo "\n", failures, " check(s) FAILED"
quit(1)
echo "\nAll env-report checks passed."