e9302bc9f6
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>
90 lines
3.4 KiB
Nim
90 lines
3.4 KiB
Nim
## Default-parity + registration guard for the LEADGAIN gun (id 16).
|
|
##
|
|
## No Java, no battle, no network build. Covers:
|
|
## * the rack table carries LEADGAIN at id 16 and it defaults to `off`;
|
|
## * the shipped rack still admits exactly Pattern;
|
|
## * TR_RACK_LEADGAIN=both is what admits it, and the spawn gate honours it;
|
|
## (the LEGACY TR_RACK_BITBRAIN alias is pinned by
|
|
## `test_lead_gain_legacy.nim`);
|
|
## * `initLeadGainGun()` is LAZY (no learner) and does NOT touch the global
|
|
## RNG, so the default path cannot perturb the selector's random draws;
|
|
## * the mode parser defaults to `perRound`.
|
|
##
|
|
## Run: nim c -r common_libs/tests/test_lead_gain_registration.nim
|
|
|
|
import std/[random, os, math]
|
|
import gun_harness/virtual_bullets
|
|
import gun_harness/selector
|
|
import guns/lead_gain
|
|
|
|
const LeadGainId = 16
|
|
const PatternId = 5
|
|
|
|
var failures = 0
|
|
proc check(name: string, ok: bool) =
|
|
if ok: echo "PASS: ", name
|
|
else: echo "FAIL: ", name; inc failures
|
|
|
|
proc clearRackEnv() =
|
|
for name in RackGunNames: delEnv("TR_RACK_" & name)
|
|
|
|
proc testTable() =
|
|
check "rack: LEADGAIN is registered at id 16", RackGunNames[LeadGainId] == "LEADGAIN"
|
|
check "rack: LEADGAIN defaults to `off`", DefaultRackMembership[LeadGainId] == rmOff
|
|
var onlyPattern = true
|
|
for i in 0..<RackGunNames.len:
|
|
let want = if i == PatternId: rmBoth else: rmOff
|
|
if DefaultRackMembership[i] != want: onlyPattern = false
|
|
check "rack: the shipped default is still the onlyPattern rack", onlyPattern
|
|
check "rack: the default rack admits only Pattern (1v1)",
|
|
admittedGuns(18, rm1v1, DefaultRackMembership) == @[PatternId]
|
|
check "gate: LEADGAIN is NOT spawned under the default rack",
|
|
not vBulletAdmitted(LeadGainId, rm1v1, DefaultRackMembership, true)
|
|
|
|
proc testEnvOverride() =
|
|
clearRackEnv()
|
|
putEnv("TR_RACK_LEADGAIN", "both")
|
|
let m = loadRackMembership()
|
|
check "env: TR_RACK_LEADGAIN=both admits LEADGAIN",
|
|
m[LeadGainId] == rmBoth and
|
|
vBulletAdmitted(LeadGainId, rm1v1, m, true)
|
|
check "env: admitting LEADGAIN leaves Pattern as the only other member",
|
|
admittedGuns(18, rm1v1, m) == @[PatternId, LeadGainId]
|
|
clearRackEnv()
|
|
|
|
proc testLazyAndRngClean() =
|
|
delEnv("TR_LEADGAIN_MEM"); delEnv("TR_BITBRAIN_MEM")
|
|
var g = initLeadGainGun()
|
|
check "lazy: constructing the gun does NOT build the learner (0 bytes)",
|
|
g.networkBytes == 0
|
|
check "default: unset TR_LEADGAIN_MEM is perRound",
|
|
g.memMode == lgPerRound
|
|
check "parse: retained/decay/unknown",
|
|
parseLgMemMode("retained") == lgRetained and
|
|
parseLgMemMode("decay") == lgDecay and
|
|
parseLgMemMode("banana") == lgPerRound
|
|
# Global RNG parity: constructing the gun must not consume global randomness.
|
|
randomize(1234)
|
|
let a = rand(1_000_000)
|
|
randomize(1234)
|
|
var g2 = initLeadGainGun()
|
|
discard g2
|
|
let b = rand(1_000_000)
|
|
check "parity: initLeadGainGun() does not perturb the global RNG", a == b
|
|
|
|
proc testGeometry() =
|
|
check "geometry: class 0 centre is the low edge + half a bin",
|
|
abs(lgCenterDeg(0, 32, 40.0) - (-40.0 + 0.5 * 80.0 / 32.0)) < 1e-9
|
|
check "geometry: lgClassOf round-trips the centre",
|
|
lgClassOf(degToRad(lgCenterDeg(17, 32, 40.0)), 32, 40.0) == 17
|
|
|
|
testTable()
|
|
testEnvOverride()
|
|
testLazyAndRngClean()
|
|
testGeometry()
|
|
|
|
if failures > 0:
|
|
echo "\n", failures, " check(s) FAILED"
|
|
quit(1)
|
|
echo "\nAll LEADGAIN registration checks passed."
|