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>
This commit is contained in:
2026-09-26 16:50:21 +02:00
parent 7a6237ec20
commit e9302bc9f6
14 changed files with 1520 additions and 39 deletions
+34 -4
View File
@@ -32,6 +32,7 @@ import movements/wave_surfer
import movements/learned_surfer
import guns/tm_horizon
import guns/lead_gain
import guns/bitbrain_net
import guns/pattern_matcher
import vbullet_draw
import geo_overlay
@@ -65,6 +66,7 @@ type
disabledGuns*: HashSet[int]
tmHorizon*: TmHorizonGun
leadGain*: LeadGainGun
bitbrainNet*: BitbrainNetGun
patternMatcher*: PatternMatcherGun
# ── helpers ──────────────────────────────────────────────────────────────────
@@ -139,6 +141,9 @@ proc sourceOfLgPresence(name: string): string =
else:
sourceOfPresence(name)
proc sbcModeName(m: SbcMode): string {.inline.} =
(if m == smCounted: "counted" else: "bitset")
proc disabledGunsString(guns: HashSet[int]): string =
if guns.len == 0: return "none"
var ids: seq[int]
@@ -428,6 +433,29 @@ proc printEffectiveValues(ctx: EnvReportContext) =
if dep.len > 0: emit("legacy TR_BITBRAIN_* aliases honoured", "yes", "legacy")
else: emit("legacy TR_BITBRAIN_* aliases honoured", "no", "default")
# ── the ADE+SBC gun (rack id 17) ─────────────────────────────────────────
# Two switches admit it, both default off: `TR_RACK_BITBRAIN` (rack table) and
# `TR_BITBRAIN_NET` (the gun's own master switch, which is also the switch
# that disowns the legacy `TR_BITBRAIN_*` aliases above).
let bbn = ctx.bitbrainNet
emit("TR_BITBRAIN_NET", onOff(bbn.enabled), sourceOfPresence(BBN_NET_ENV))
emit("TR_BITBRAIN_INPUT", $bbn.inputWidth, sourceOf(BBN_INPUT_ENV))
emit("TR_BITBRAIN_FEATURES", bbn.features, sourceOf(BBN_FEATURES_ENV))
emit("TR_BITBRAIN_NCLASSES", $bbn.nClasses, sourceOf(BBN_CLASSES_ENV))
emit("TR_BITBRAIN_NADES", $bbn.nAde, sourceOf(BBN_NADES_ENV))
emit("TR_BITBRAIN_WIDTHS", bbn.widths.join(","), sourceOf(BBN_WIDTHS_ENV))
emit("TR_BITBRAIN_SPAN", $bbn.maxDeg, sourceOf(BBN_SPAN_ENV))
emit("TR_BITBRAIN_MODE", sbcModeName(bbn.mode), sourceOf(BBN_MODE_ENV))
emit("TR_BITBRAIN_DECAY_EVERY", $bbn.decayEvery, sourceOf(BBN_DECAY_EVERY_ENV))
emit("TR_BITBRAIN_DECAY_SHIFT", $bbn.decayShift, sourceOf(BBN_DECAY_SHIFT_ENV))
emit("TR_BITBRAIN_MINOBS", $bbn.minObs, sourceOf(BBN_MINOBS_ENV))
emit("TR_BITBRAIN_ADAPT_EVERY", $bbn.adaptEvery, sourceOf(BBN_ADAPT_ENV))
emit("TR_BITBRAIN_TARGET", $bbn.targetRate, sourceOf(BBN_TARGET_ENV))
emit("TR_BITBRAIN_NETSEED", $bbn.seed, sourceOf(BBN_SEED_ENV))
emit("TR_BITBRAIN_NETLOG", onOff(bbn.logEnabled),
sourceOfPresence(BBN_LOG_ENV))
emit("bitbrain_net.ram", $bbn.networkBytes, "default") ## 0 until the lazy build
# ── 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.
@@ -600,14 +628,16 @@ proc knownEnvNames*(): seq[string] =
"TR_BITBRAIN_RANGE", "TR_BITBRAIN_WARMUP", "TR_BITBRAIN_ADAPT",
"TR_BITBRAIN_CALIB", "TR_BITBRAIN_SEED",
# common_libs/bitbrain (counted-SBC library) reads these inline; job j102
"TR_BITBRAIN_MODE", "TR_BITBRAIN_DECAY_EVERY", "TR_BITBRAIN_DECAY_SHIFT",
]
# the ADE+SBC gun (rack id 17) owns the rest of the TR_BITBRAIN_* namespace
for n in BitbrainNetEnvNames: result.add n
# rack names are constructed from the prefix + gun table, not spelled out
for g in RackGunNames:
result.add RackEnvPrefix & g
# the LEGACY rack name of the renamed corrector (still honoured while
# LG_NET_SWITCH_ENV is off — see gun_harness/selector's RackLegacyAlias)
for (key, _) in RackLegacyAlias: result.add key
# NOTE: the legacy rack alias `TR_RACK_BITBRAIN` needs no extra entry — it is
# the CURRENT name of rack id 17, so the RackGunNames loop above already
# registers it. See gun_harness/selector's RackLegacyAlias for how it is
# routed to LEADGAIN while LG_NET_SWITCH_ENV is off.
# inline reads with no exported constant (the guard test scans for these)
for n in [
"GUN_SELECTOR_WINDOW", "GUN_SELECTOR_MINOBS", "GUN_SELECTOR_TIE",