j140 fix: a pre-rename .env must not admit the disabled BitBrain net gun
TR_RACK_BITBRAIN=both is what the owner's live .env carries, and with the new ADE+SBC gun registered at id 17 under the SAME rack name that value was also landing on id 17 - so a gun that was not enabled (TR_BITBRAIN_NET unset) was admitted into the rack and its placeholder predictions were pushed into the shared VirtualTracker ring, which shifts every other gun's learning order. While the namespace is LEGACY, loadRackMembership now skips id 17's TR_RACK_BITBRAIN entirely, so that value addresses ONLY the gun it always addressed (LEADGAIN, id 16). ModularBot additionally gates admission on BitbrainNetGun.gunAdmitted(), and test_bitbrain_net pins the truth table: over 6 (rack, switch) settings there is NO configuration that admits the gun while leaving it disabled. Guards: test_env_report 25, test_rack_membership 49 (was 48; the revert one-liner now sets TR_BITBRAIN_NET=1 and one truth-table check was added), test_tm_pattern_registration 20, test_bitbrain 56, test_gun_harness 39, test_tfil_commit_env 30, test_lead_gain_registration 13, test_lead_gain_legacy 24, test_bitbrain_net 44. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1310,7 +1310,17 @@ method run*(bot: ModularBot) =
|
||||
vBulletAdmitted(gi, bot.rackMode, ActiveRackMembership,
|
||||
VBulletAdmitOnly or gi == TmPatternId or
|
||||
gi == TmHorizonId or gi == LeadGainId or
|
||||
gi == BitbrainNetId)
|
||||
gi == BitbrainNetId) and
|
||||
# BITBRAIN (id 17) needs BOTH switches. The rack table alone
|
||||
# is not enough: a pre-rename `.env` still carries
|
||||
# `TR_RACK_BITBRAIN=both` (it used to select LEADGAIN), and
|
||||
# admitting a DISABLED gun would let its placeholder
|
||||
# predictions into the shared VirtualTracker ring and shift
|
||||
# every other gun's learning order. With this gate an
|
||||
# incomplete configuration is exactly as inert as an unset
|
||||
# one.
|
||||
(gi != BitbrainNetId or
|
||||
bot.bbn.gunAdmitted(true))
|
||||
for i in 0..<len(PowerBins):
|
||||
if admit[0]: headsUp[i] = bot.headOn.predict(bot.lastState, bulletSpeed(PowerBins[i]))
|
||||
if admit[1]: linPreds[i] = bot.linear.predict(bot.lastState, bulletSpeed(PowerBins[i]))
|
||||
|
||||
@@ -455,6 +455,9 @@ proc printEffectiveValues(ctx: EnvReportContext) =
|
||||
emit("TR_BITBRAIN_NETLOG", onOff(bbn.logEnabled),
|
||||
sourceOfPresence(BBN_LOG_ENV))
|
||||
emit("bitbrain_net.ram", $bbn.networkBytes, "default") ## 0 until the lazy build
|
||||
emit("bitbrain_net.admitted", onOff(bbn.gunAdmitted(
|
||||
ActiveRackMembership[17] != rmOff)),
|
||||
"default") ## rack table AND TR_BITBRAIN_NET; both default off
|
||||
|
||||
# ── Pattern match-shape + radial knobs ────────────────────────────────────
|
||||
# These are resolved lazily inside `predict` (which has not run at boot), so
|
||||
|
||||
@@ -107,6 +107,7 @@ const
|
||||
## it on while the namespace is still in legacy mode.)
|
||||
RackLegacyAlias*: array[1, (string, int)] = [("TR_RACK_BITBRAIN", 16)]
|
||||
RackLegacyAliasGunName* = "LEADGAIN" ## what the legacy name selects today
|
||||
RackNetGunId* = 17 ## the ADE+SBC gun that owns the name once switched
|
||||
|
||||
proc netSwitchOwnsBitbrainName*(): bool =
|
||||
## `TR_BITBRAIN_NET` (default 0) is THE disambiguation switch for the whole
|
||||
@@ -139,12 +140,17 @@ proc loadRackMembership*(): array[len(RackGunNames), RackMembership] =
|
||||
## corresponding CURRENT name is unset, so a migrated config always wins.
|
||||
result = DefaultRackMembership
|
||||
var legacyTouched: seq[string]
|
||||
let netOwns = netSwitchOwnsBitbrainName()
|
||||
for i in 0..<len(RackGunNames):
|
||||
let key = RackEnvPrefix & RackGunNames[i]
|
||||
# While the namespace is LEGACY, `TR_RACK_BITBRAIN` must address ONLY the
|
||||
# gun it always addressed (LEADGAIN, id 16) — never id 17, or a pre-rename
|
||||
# `.env` would silently admit a gun that did not exist then.
|
||||
if not netOwns and i == RackNetGunId: continue
|
||||
let v = getEnv(key, "")
|
||||
if v.len > 0:
|
||||
result[i] = parseRackMembership(v)
|
||||
if not netSwitchOwnsBitbrainName():
|
||||
if not netOwns:
|
||||
for (key, gid) in RackLegacyAlias:
|
||||
let current = RackEnvPrefix & RackGunNames[gid]
|
||||
if getEnv(key, "").len > 0 and getEnv(current, "").len == 0:
|
||||
|
||||
@@ -14,11 +14,17 @@ that delivers forgetting and true per-class probabilities —
|
||||
`docs/bitbrain_counted_sbc.md`).
|
||||
|
||||
It took the `BITBRAIN` rack name (id **17**) and the `TR_BITBRAIN_*` knob prefix
|
||||
that the renamed corrector gave up. **Default OFF**: it needs BOTH
|
||||
`TR_RACK_BITBRAIN` (rack table) and `TR_BITBRAIN_NET=1` (the gun's own master
|
||||
switch, the same switch that disowns the legacy `TR_BITBRAIN_*` aliases of
|
||||
`LEADGAIN`). The network is built lazily, so an unset environment never
|
||||
allocates a byte.
|
||||
that the renamed corrector gave up. **Default OFF.** It needs
|
||||
`TR_RACK_BITBRAIN=both` in the rack table **and** `TR_BITBRAIN_NET=1` — the one
|
||||
switch that decides whether the `TR_BITBRAIN_*` namespace is legacy
|
||||
(`LEADGAIN`'s) or the new gun's, and the new gun's master on/off at the same
|
||||
time. The two are gated by the SAME switch, so no env configuration can admit
|
||||
the gun while leaving it disabled
|
||||
(`test_bitbrain_net.nim` pins that over a 6-setting truth table) — which
|
||||
matters, because a pre-rename `.env` still carries `TR_RACK_BITBRAIN=both` and a
|
||||
disabled gun's placeholder predictions in the shared `VirtualTracker` ring would
|
||||
shift every other gun's learning order. The network is built lazily, so an unset
|
||||
environment never allocates a byte.
|
||||
|
||||
## OUTPUT SHAPE — a fine-grained correction ON TOP of Pattern
|
||||
|
||||
@@ -69,7 +75,7 @@ ring), i.e. the same causal information Pattern itself uses.
|
||||
| Env | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `TR_RACK_BITBRAIN` | `off` | rack admission for id 17 (the *current* name of the rack key) |
|
||||
| `TR_BITBRAIN_NET` | `0` | **master switch.** `0` = off and the `TR_BITBRAIN_*` names are LEGACY aliases of `LEADGAIN`; `1` = on and the names below are the new gun's |
|
||||
| `TR_BITBRAIN_NET` | `0` | **master switch + namespace disambiguator.** `0` = the gun is off AND the `TR_BITBRAIN_*` names are LEGACY aliases of `LEADGAIN` (and `TR_RACK_BITBRAIN` selects id 16); `1` = the gun is on and the names below are this gun's (and `TR_RACK_BITBRAIN` selects id 17) |
|
||||
| `TR_BITBRAIN_INPUT` | 52 | total input slots; pads or truncates the block layout so the ADE codes can never index out of range |
|
||||
| `TR_BITBRAIN_FEATURES` | all blocks at their shipped width | `name:W` list, comma separated. `name:0` switches a block OFF; an unlisted block keeps its shipped width; an unknown name warns and is ignored |
|
||||
| `TR_BITBRAIN_NCLASSES` | 8 | output resolution |
|
||||
@@ -172,14 +178,16 @@ anything about damage, survival or round wins, and it is never presented as one.
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
# 42 checks: the gun really engages the network (not just that it links)
|
||||
# 44 checks: the gun really engages the network (not just that it links)
|
||||
nim c -r -d:release --path:common_libs common_libs/tests/test_bitbrain_net.nim
|
||||
# the library itself (56 checks, unchanged)
|
||||
nim c -r -d:release --path:common_libs common_libs/tests/test_bitbrain.nim
|
||||
```
|
||||
|
||||
`test_bitbrain_net.nim` proves engagement by observation, not by linkage: the
|
||||
network is 0 bytes before first use, two different inputs give different class
|
||||
outputs, a learn visibly raises SBC occupancy, a bitset learn is idempotent
|
||||
while a counted learn is monotone, ADE threshold adaptation runs, and
|
||||
construction + first predict leave the global RNG untouched.
|
||||
network is 0 bytes before first use, a **trained** head returns different
|
||||
classes for different states and separates two taught populations, a learn
|
||||
visibly raises SBC occupancy, a bitset learn is idempotent while a counted
|
||||
learn's evidence is monotone, ADE threshold adaptation runs, no env setting can
|
||||
admit a disabled gun, and construction + first predict leave the global RNG
|
||||
untouched.
|
||||
|
||||
@@ -586,6 +586,10 @@ proc sbcBytes*(g: BitbrainNetGun): int =
|
||||
proc predict*(g: var BitbrainNetGun, state: WorldState,
|
||||
bulletSpeed: float): GunPrediction =
|
||||
g.ensureInit()
|
||||
## A disabled gun must never be called: ModularBot gates admission on
|
||||
## `g.enabled`, and this branch is only a belt-and-braces guard. It returns
|
||||
## the CURRENT position (a valid, if useless, prediction) rather than anything
|
||||
## that could poison a shared tracker.
|
||||
if not g.enabled: return GunPrediction(x: state.enemyX, y: state.enemyY)
|
||||
if state.tick < g.lastTick: g.resetRoundState()
|
||||
if state.tick != g.lastTick:
|
||||
@@ -665,3 +669,11 @@ proc evidenceFor*(g: var BitbrainNetGun, input: openArray[uint8], klass: int): i
|
||||
## every counted learn, so it is the observable that proves the counted mode
|
||||
## is accumulating rather than just flipping bits.
|
||||
g.net.infer(input).counts[klass]
|
||||
|
||||
proc gunAdmitted*(g: BitbrainNetGun, rackAdmitted: bool): bool {.inline.} =
|
||||
## The FULL admission predicate for rack id 17: the rack table AND this gun's
|
||||
## own master switch. Both default off, so an incomplete configuration is
|
||||
## exactly as inert as an unset one — which matters, because a pre-rename
|
||||
## `.env` still carries `TR_RACK_BITBRAIN=both` and must not be able to push a
|
||||
## disabled gun's placeholder predictions into the shared VirtualTracker ring.
|
||||
rackAdmitted and g.enabled
|
||||
|
||||
@@ -33,6 +33,9 @@ proc check(name: string, ok: bool) =
|
||||
|
||||
proc clearEnv() =
|
||||
for n in BitbrainNetEnvNames: delEnv(n)
|
||||
# the master switch is shared with LEADGAIN, so it is NOT in
|
||||
# BitbrainNetEnvNames and must be cleared explicitly here
|
||||
delEnv("TR_BITBRAIN_NET")
|
||||
for n in RackGunNames: delEnv("TR_RACK_" & n)
|
||||
|
||||
proc mkState(t: int, ex, ey, sx, sy, eh: float): WorldState =
|
||||
@@ -80,6 +83,33 @@ proc testRackEnv() =
|
||||
check "env: TR_RACK_BITBRAIN=both + TR_BITBRAIN_NET=1 admits id 17",
|
||||
m[BitbrainNetId] == rmBoth and
|
||||
vBulletAdmitted(BitbrainNetId, rm1v1, m, true)
|
||||
# A pre-rename .env carries TR_RACK_BITBRAIN=both WITHOUT the master switch.
|
||||
# That must not admit the gun: a disabled gun's placeholder predictions would
|
||||
# enter the shared VirtualTracker ring and shift every other gun's learning
|
||||
# order, i.e. it would break the default path.
|
||||
clearEnv()
|
||||
putEnv("TR_RACK_BITBRAIN", "both") # exactly what a pre-rename .env carries
|
||||
let mLegacy = loadRackMembership()
|
||||
check "env: TR_RACK_BITBRAIN with the switch OFF still means LEADGAIN (id 16)",
|
||||
mLegacy[BitbrainNetId] == rmOff and mLegacy[16] == rmBoth
|
||||
# TR_RACK_BITBRAIN and TR_BITBRAIN_NET are gated by the SAME switch, so there
|
||||
# is no env configuration that admits the gun without enabling it. Pin that:
|
||||
# a disabled gun must never reach the shared VirtualTracker ring, because its
|
||||
# placeholder predictions would shift every other gun's learning order.
|
||||
var consistent = true
|
||||
for (rackVal, netVal) in [("both", ""), ("both", "0"), ("both", "1"),
|
||||
("off", "1"), ("1v1", "1"), ("1v1", "")]:
|
||||
clearEnv()
|
||||
putEnv("TR_RACK_BITBRAIN", rackVal)
|
||||
if netVal.len > 0: putEnv(BBN_NET_ENV, netVal)
|
||||
let mm = loadRackMembership()
|
||||
let inRack = mm[BitbrainNetId] != rmOff
|
||||
let admitted = initBitbrainNetGun().gunAdmitted(inRack)
|
||||
if inRack != admitted: consistent = false
|
||||
if admitted and not initBitbrainNetGun().enabled: consistent = false
|
||||
check "env: TR_RACK_BITBRAIN alone can NEVER admit a disabled id 17 (6 settings)",
|
||||
consistent
|
||||
clearEnv()
|
||||
clearEnv()
|
||||
|
||||
# ── the knobs really resolve ─────────────────────────────────────────────────
|
||||
|
||||
@@ -204,9 +204,14 @@ proc testRevertOverrideRestoresFullRack() =
|
||||
## stays `off`). This must reproduce the old full rack without a rebuild.
|
||||
clearRackEnv()
|
||||
putEnv("TR_RACK_PATTERN", "both")
|
||||
# The ADE+SBC gun (id 17) additionally needs TR_BITBRAIN_NET=1; without the
|
||||
# switch TR_RACK_BITBRAIN is the LEGACY name for LEADGAIN (id 16), so the
|
||||
# documented one-liner has to flip the namespace over as well.
|
||||
putEnv("TR_BITBRAIN_NET", "1")
|
||||
for name in RackGunNames:
|
||||
if name != "TMPATTERN": putEnv("TR_RACK_" & name, "both")
|
||||
let m = loadRackMembership()
|
||||
delEnv("TR_BITBRAIN_NET")
|
||||
var full = true
|
||||
for i in 0..<RackGunNames.len:
|
||||
let want = if i == 14: rmOff else: rmBoth
|
||||
@@ -214,6 +219,8 @@ proc testRevertOverrideRestoresFullRack() =
|
||||
check "revert: the documented one-liner restores the all-`both` full rack", full
|
||||
check "revert: 1v1 rack admits every gun again (TMPATTERN excluded)",
|
||||
admittedGuns(18, rm1v1, m) == @[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17]
|
||||
check "revert: the legacy TR_RACK_BITBRAIN alone still selects LEADGAIN only",
|
||||
loadRackMembership()[17] == rmOff
|
||||
clearRackEnv()
|
||||
|
||||
proc testEnvOverrides() =
|
||||
|
||||
Reference in New Issue
Block a user