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
+50 -3
View File
@@ -47,8 +47,14 @@ const
RackGunNames*: array[17, string] = [
"HEADON", "LINEAR", "TSETLIN", "CIRCULAR", "GUESSFACTOR", "PATTERN",
"WALLBOUNCE", "ACCEL", "STOPSHOT", "DISPLACE", "AVGLEAD", "DECAYGF",
"KNN", "TMSELECT", "TMPATTERN", "TMHORIZON", "BITBRAIN"]
"KNN", "TMSELECT", "TMPATTERN", "TMHORIZON", "LEADGAIN"]
RackEnvPrefix* = "TR_RACK_"
## LEADGAIN (id 16) is the per-range-band lead-gain corrector
## (`guns/lead_gain.nim`). It USED to be called BITBRAIN; the name now
## describes its internals (it learns a multiplier for Pattern's lead per
## range band). Its rack id is UNCHANGED so every test that asserts the id
## literals still holds. The `BITBRAIN` name and the `TR_BITBRAIN_*` prefix
## now belong to the ADE+SBC gun `guns/bitbrain_net.nim` (rack id 17).
## SHIPPED DEFAULT: `onlyPattern`. Pattern (id 5) is admitted in both racks;
## every other gun is `off`. The selection mechanism is untouched and remains
## fully functional — only the rack's membership changed. Re-enable any gun
@@ -74,11 +80,36 @@ const
rmOff, # 13 TMSELECT — off
rmOff, # 14 TMPATTERN — off (already shipped off; TM pattern gun)
rmOff, # 15 TMHORIZON — off (horizon-based TM corrector; expected to lose)
rmOff] # 16 BITBRAIN — off (fine-grained ADE+SBC corrector)
rmOff] # 16 LEADGAIN — off (per-range-band lead-gain corrector)
## NOTE: the table is registered in the SAME commit as the gun id (16) and the
## live wiring, so `TR_RACK_BITBRAIN=both` is the ONLY thing that admits it and
## live wiring, so `TR_RACK_LEADGAIN=both` is the ONLY thing that admits it and
## an unset environment is byte-for-byte the shipped Pattern-only rack.
const
## ── BACKWARD COMPATIBILITY: the legacy rack knob names ────────────────────
## `TR_RACK_<gun name>` is derived from `RackGunNames`, so renaming a gun
## silently retires its old switch. These entries keep an old switch alive:
## each maps a legacy `TR_RACK_*` name onto the gun id it used to address.
## `TR_RACK_BITBRAIN` is the ONE genuinely ambiguous legacy name — the rack is
## keyed by gun name, and the new ADE+SBC gun is now the one called
## `BITBRAIN`. It is disambiguated by the same switch the knobs use,
## `TR_BITBRAIN_NET` (default 0):
## * unset/0 -> LEGACY: `TR_RACK_BITBRAIN` selects LEADGAIN (id 16), the gun
## it always selected, and the new gun stays off (the shipped default);
## * 1 -> `TR_RACK_BITBRAIN` selects the new BITBRAIN gun (id 17).
RackLegacyAlias*: array[1, (string, int)] = [("TR_RACK_BITBRAIN", 16)]
RackLegacyAliasGunName* = "LEADGAIN" ## what the legacy name selects today
proc netSwitchOwnsBitbrainName*(): bool =
## `TR_BITBRAIN_NET` (default 0) is THE disambiguation switch for the whole
## `TR_BITBRAIN_*` namespace. Unset/0 => the namespace is LEGACY and belongs
## to the renamed lead-gain corrector; 1 => it belongs to the ADE+SBC gun.
## Defined here (and identically in `guns/lead_gain.nim`) because
## `gun_harness/selector` must not depend on a concrete gun module.
case getEnv("TR_BITBRAIN_NET", "").strip().toLowerAscii()
of "1", "true", "yes", "on": true
else: false
proc parseRackMembership*(value: string): RackMembership =
## Parse a `TR_RACK_<GUN>` value. Empty / unknown values fall back to the
## shipped `both` and warn on stderr, so a typo cannot silently move a gun and
@@ -96,12 +127,28 @@ proc parseRackMembership*(value: string): RackMembership =
proc loadRackMembership*(): array[len(RackGunNames), RackMembership] =
## Default table plus every `TR_RACK_<GUN>` override. A proc (not inlined into
## the `let`) so the unit test can exercise env parsing in-process.
## Legacy alias names (`RackLegacyAlias`) are applied only when the
## corresponding CURRENT name is unset, so a migrated config always wins.
result = DefaultRackMembership
var legacyTouched: seq[string]
for i in 0..<len(RackGunNames):
let key = RackEnvPrefix & RackGunNames[i]
let v = getEnv(key, "")
if v.len > 0:
result[i] = parseRackMembership(v)
if not netSwitchOwnsBitbrainName():
for (key, gid) in RackLegacyAlias:
let current = RackEnvPrefix & RackGunNames[gid]
if getEnv(key, "").len > 0 and getEnv(current, "").len == 0:
result[gid] = parseRackMembership(getEnv(key, ""))
legacyTouched.add key
if legacyTouched.len > 0:
let gid = RackLegacyAlias[0][1]
stderr.writeLine("[depr] legacy rack knob " & legacyTouched.join(",") &
" now names the ADE+SBC gun (BITBRAIN, rack id 17); it still selects " &
RackGunNames[gid] & " (rack id " & $gid &
") until TR_BITBRAIN_NET=1. Set TR_RACK_" & RackGunNames[gid] &
" to make it explicit.")
let ActiveRackMembership* = loadRackMembership()
## Process-wide rack table, frozen at startup.