j140 rename the lead-gain corrector: BitBrain -> LEADGAIN (+ legacy TR_BITBRAIN_* aliases)

The gun at rack id 16 learned a multiplier for Pattern's lead, separately
per range band. It was called BITBRAIN and shipped a TR_BITBRAIN_* prefix,
which is why the name read as a neural network it no longer contains.

  guns/bitbrain_gun.nim -> guns/lead_gain.nim  (rack id 16 UNCHANGED)
  RackGunNames[16]       BITBRAIN -> LEADGAIN
  TR_BITBRAIN_* knobs    -> TR_LEADGAIN_*
  [bb] log line          -> [lg]

BACKWARD COMPATIBILITY is mandatory: the live .env carries
TR_RACK_BITBRAIN=both, TR_BITBRAIN_GAINS, TR_BITBRAIN_MEM=decay and
TR_BITBRAIN_LOG=1, and those must keep behaving identically. The new ADE+SBC
gun (next commit) claims the BITBRAIN name and the TR_BITBRAIN_* prefix, so
the namespace is disambiguated by ONE deterministic switch, TR_BITBRAIN_NET
(default 0):

  TR_BITBRAIN_NET unset/0 -> LEGACY: the 14 frozen legacy suffixes are aliases
                              for TR_LEADGAIN_*, and TR_RACK_BITBRAIN still
                              selects rack id 16. One [depr] line on stderr
                              names the new spelling of each honoured knob.
  TR_BITBRAIN_NET = 1      -> the TR_BITBRAIN_* names belong to the new gun.

The legacy suffix set and the new gun's knob set are DISJOINT, so no name is
ever claimed twice; the new name always wins over its alias.

Parity: shipped rack is still onlyPattern, shipped movement is still strafe.
Guards unchanged: test_env_report 25, test_rack_membership 48,
test_tm_pattern_registration 20, test_lead_gain_registration 13 (was
test_bitbrain_registration), test_bitbrain 56, test_gun_harness 39,
test_tfil_commit_env 30. New: test_lead_gain_legacy 24.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-26 15:54:00 +02:00
parent 0dc5552c73
commit 7a6237ec20
10 changed files with 827 additions and 412 deletions
+76 -29
View File
@@ -31,7 +31,7 @@ import movements/strafe
import movements/wave_surfer
import movements/learned_surfer
import guns/tm_horizon
import guns/bitbrain_gun
import guns/lead_gain
import guns/pattern_matcher
import vbullet_draw
import geo_overlay
@@ -64,7 +64,7 @@ type
shotLogPath*: string
disabledGuns*: HashSet[int]
tmHorizon*: TmHorizonGun
bitbrain*: BitBrainGun
leadGain*: LeadGainGun
patternMatcher*: PatternMatcherGun
# ── helpers ──────────────────────────────────────────────────────────────────
@@ -113,6 +113,32 @@ proc sourceOfPresence(name: string): string =
elif existsEnv(name): "env"
else: "default"
proc legacyNameFor(name: string): string =
## The legacy `TR_BITBRAIN_<X>` alias for a `TR_LEADGAIN_<X>` knob, or "" when
## the knob has no legacy name / the namespace now belongs to the new gun.
if netSwitchOwnsBitbrainName(): return ""
if not name.startsWith(NewPrefix): return ""
let suffix = name[NewPrefix.len .. ^1]
for s in LegacyKnobEnvNames:
if s == suffix: return LegacyPrefix & suffix
""
proc sourceOfLg(name: string): string =
## `sourceOf`, plus credit for the legacy `TR_BITBRAIN_*` alias. Tagged
## `legacy` so a migrated name is distinguishable from an unmigrated one.
let legacy = legacyNameFor(name)
if legacy.len > 0 and getEnv(name, "").len == 0 and getEnv(legacy, "").len > 0:
if isFromEnvFile(legacy): "legacy/.env" else: "legacy"
else:
sourceOf(name)
proc sourceOfLgPresence(name: string): string =
let legacy = legacyNameFor(name)
if legacy.len > 0 and not existsEnv(name) and existsEnv(legacy):
if isFromEnvFile(legacy): "legacy/.env" else: "legacy"
else:
sourceOfPresence(name)
proc disabledGunsString(guns: HashSet[int]): string =
if guns.len == 0: return "none"
var ids: seq[int]
@@ -373,29 +399,34 @@ proc printEffectiveValues(ctx: EnvReportContext) =
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))
# ── the LEADGAIN per-range-band lead-gain corrector ───────────────────────
# All knobs are resolved at gun construction (`initLeadGainGun`), so the
# context holds the real values. `sourceOfLg`/`sourceOfLgPresence` also
# credit the LEGACY `TR_BITBRAIN_*` alias, so a pre-rename `.env` shows up as
# coming from the env (tagged `legacy`) instead of silently reading default.
let lg = ctx.leadGain
emit("TR_LEADGAIN_MEM", memModeName(lg.memMode), sourceOfLg(LG_MEM_ENV))
emit("TR_LEADGAIN_GAINS", lgGainsString(lg.cands), sourceOfLg(LG_GAINS_ENV))
emit("TR_LEADGAIN_N", $lg.nClasses, sourceOfLg(LG_N_ENV))
emit("TR_LEADGAIN_NADE", $lg.nAde, sourceOfLg(LG_NADE_ENV))
emit("TR_LEADGAIN_RANGE", $lg.maxDeg, sourceOfLg(LG_RANGE_ENV))
emit("TR_LEADGAIN_LOG", onOff(lg.logEnabled), sourceOfLgPresence(LG_LOG_ENV))
emit("TR_LEADGAIN_MIN_OBS", $lg.minObs, sourceOfLg(LG_MIN_OBS_ENV))
emit("TR_LEADGAIN_WARMUP", $lg.warmupN, sourceOfLg(LG_WARMUP_ENV))
emit("TR_LEADGAIN_ADAPT", $lg.adaptEvery, sourceOfLg(LG_ADAPT_ENV))
emit("TR_LEADGAIN_CALIB", $lg.calibEvery, sourceOfLg(LG_CALIB_ENV))
emit("TR_LEADGAIN_DECAY", $lg.decayEvery, sourceOfLg(LG_DECAY_ENV))
emit("TR_LEADGAIN_DECAY_FRAC", $lg.decayFrac, sourceOfLg(LG_DECAY_FRAC_ENV))
emit("TR_LEADGAIN_SEED", $lg.seed, sourceOfLg(LG_SEED_ENV))
emit("TR_LEADGAIN_RESET_ON_TARGET", onOff(lg.resetOnTarget),
sourceOfLg(LG_RESET_ON_TARGET_ENV))
# The disambiguation switch + the deprecation line it gates, in the report so
# an owner staring at a `[depr]` line can see exactly why it fired.
emit("TR_BITBRAIN_NET", onOff(netSwitchOwnsBitbrainName()),
sourceOfPresence("TR_BITBRAIN_NET"))
let dep = lgDeprecationLine()
if dep.len > 0: emit("legacy TR_BITBRAIN_* aliases honoured", "yes", "legacy")
else: emit("legacy TR_BITBRAIN_* aliases honoured", "no", "default")
# ── Pattern match-shape + radial knobs ────────────────────────────────────
# These are resolved lazily inside `predict` (which has not run at boot), so
@@ -451,9 +482,14 @@ proc printModules(ctx: EnvReportContext) =
# Guns. Rack membership IS the on/off switch: `off` = disabled, any other
# mode = enabled in that rack.
for i in 0..<len(RackGunNames):
var src = sourceOf(RackEnvPrefix & RackGunNames[i])
if src == "default" and not netSwitchOwnsBitbrainName():
for (key, gid) in RackLegacyAlias:
if gid == i and getEnv(key, "").len > 0:
src = if isFromEnvFile(key): "legacy/.env" else: "legacy"
emitModule("gun." & RackGunNames[i].toLowerAscii(),
ActiveRackMembership[i] != rmOff,
sourceOf(RackEnvPrefix & RackGunNames[i]),
src,
"rack: " & rackMembershipName(ActiveRackMembership[i]))
# Movement engines + the engine the tick actually runs.
@@ -552,15 +588,26 @@ proc knownEnvNames*(): seq[string] =
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,
LG_MEM_ENV, LG_GAINS_ENV, LG_N_ENV, LG_NADE_ENV, LG_RANGE_ENV, LG_LOG_ENV,
LG_MIN_OBS_ENV, LG_WARMUP_ENV, LG_ADAPT_ENV, LG_CALIB_ENV, LG_DECAY_ENV,
LG_DECAY_FRAC_ENV, LG_SEED_ENV, LG_RESET_ON_TARGET_ENV, LG_NET_SWITCH_ENV,
# LEGACY aliases of the corrector knobs above (honoured while
# LG_NET_SWITCH_ENV is off). Registered so the tree-scan guard does not
# flag the owner's pre-rename .env as unknown.
"TR_BITBRAIN_GAINS", "TR_BITBRAIN_MEM", "TR_BITBRAIN_MIN_OBS",
"TR_BITBRAIN_DECAY", "TR_BITBRAIN_DECAY_FRAC", "TR_BITBRAIN_LOG",
"TR_BITBRAIN_RESET_ON_TARGET", "TR_BITBRAIN_N", "TR_BITBRAIN_NADE",
"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",
]
# 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
# inline reads with no exported constant (the guard test scans for these)
for n in [
"GUN_SELECTOR_WINDOW", "GUN_SELECTOR_MINOBS", "GUN_SELECTOR_TIE",