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
+29 -26
View File
@@ -1,5 +1,5 @@
## ModularBot — plugin gun architecture tracer bullet.
## Guns: HeadOnGun (0), LinearGun (1), TsetlinGun (2), CircularGun (3), GFGun (4), PatternMatcherGun (5), WallBounceGun (6), AccelGun (7), StopShotGun (8), DisplacementGun (9), AveragedLeadGun (10), DecayGFGun (11), KNNGun (12), TmSelectorGun (13), TmPatternGun (14), TmHorizonGun (15), BitBrainGun (16) via GunHarness.
## Guns: HeadOnGun (0), LinearGun (1), TsetlinGun (2), CircularGun (3), GFGun (4), PatternMatcherGun (5), WallBounceGun (6), AccelGun (7), StopShotGun (8), DisplacementGun (9), AveragedLeadGun (10), DecayGFGun (11), KNNGun (12), TmSelectorGun (13), TmPatternGun (14), TmHorizonGun (15), LeadGainGun (16) via GunHarness.
## Radar: RadarLockModule (1v1) / AdaptiveMeleeRadarModule (2+ enemies), auto-switched per tick.
## Movement: OscillatorModule (perpendicular strafing).
@@ -30,7 +30,7 @@ import guns/knn_gun
import guns/tm_selector
import guns/tm_pattern
import guns/tm_horizon
import guns/bitbrain_gun
import guns/lead_gain
import movements/phantom_meteor
import movements/rammer
import movements/ram_decision
@@ -150,7 +150,7 @@ let ResultLog* =
block:
let v = getEnv("TR_RESULT_LOG", "1").strip().toLowerAscii()
not (v == "0" or v == "false" or v == "no" or v == "off")
const GunNames = ["HeadOn", "Linear", "Tsetlin", "Circular", "GuessFactor", "Pattern", "WallBounce", "Accel", "StopShot", "Displace", "AvgLead", "DecayGF", "KNN", "TMSelect", "TMPattern", "TMHorizon", "BitBrain"]
const GunNames = ["HeadOn", "Linear", "Tsetlin", "Circular", "GuessFactor", "Pattern", "WallBounce", "Accel", "StopShot", "Displace", "AvgLead", "DecayGF", "KNN", "TMSelect", "TMPattern", "TMHorizon", "LeadGain"]
## Rack id of the new TM pattern gun. It defaults to `TR_RACK_TMPATTERN=off`;
## unlike the other guns, its virtual-bullet spawn is gated on rack admission
@@ -165,11 +165,14 @@ const TmPatternId = 14
## head — hence every other gun's learning order — is byte-for-byte unchanged.
const TmHorizonId = 15
## Rack id of the BitBrain ADE+SBC fine-grained aim corrector. It defaults to
## `TR_RACK_BITBRAIN=off` and its virtual-bullet spawn is gated on rack
## admission, so the shipped default never spawns it and the shared tracker ring
## head — hence every other gun's learning order — is byte-for-byte unchanged.
const BitBrainId = 16
## Rack id of the LEADGAIN per-range-band lead-gain corrector (it used to be
## called BitBrain, before the ADE+SBC network was removed from it; the rack id
## is unchanged). It defaults to `TR_RACK_LEADGAIN=off` and its virtual-bullet
## spawn is gated on rack admission, so the shipped default never spawns it and
## the shared tracker ring head — hence every other gun's learning order — is
## byte-for-byte unchanged. `TR_RACK_BITBRAIN` still selects it while
## `TR_BITBRAIN_NET` is off (backward compatibility; see `gun_harness/selector`).
const LeadGainId = 16
## ── virtual-bullet debug overlay (TR_VBULLET_DEBUG*) ───────────────────────
## OFF by default: the shipped bot draws nothing. When on, the tracker's
@@ -242,7 +245,7 @@ type
tmSelector: TmSelectorGun
tmPattern: TmPatternGun
tmHorizon: TmHorizonGun
bitbrain: BitBrainGun
leadGain: LeadGainGun
mover: TFILModule
ringMover: TFILRingModule
strafeMover: StrafeModule
@@ -937,11 +940,11 @@ method onRoundStarted*(bot: ModularBot, e: RoundStartedEvent) =
bot.tmHorizon.resetRoundState()
if e.roundNumber <= 1:
bot.tmHorizon.resetLearning("round1")
# BitBrain: per-round wipe always clears observations/labels; `perRound` mode
# LEADGAIN: per-round wipe always clears observations/labels; `perRound` mode
# also wipes its SBCs (the gate test's winning regime).
bot.bitbrain.resetRoundState()
bot.leadGain.resetRoundState()
if e.roundNumber <= 1:
bot.bitbrain.resetLearning("round1")
bot.leadGain.resetLearning("round1")
bot.isRamming = false
bot.ramDurationTicks = 0
bot.ramCooldownTicks = 0
@@ -1021,7 +1024,7 @@ method onGameStarted*(bot: ModularBot, e: GameStartedEventForBot) =
# state, so this is the only place the learning is dropped at a battle
# boundary. The round-1 fallback in `onRoundStarted` covers a missed callback.
bot.tmHorizon.resetLearning("game_start")
bot.bitbrain.resetLearning("game_start")
bot.leadGain.resetLearning("game_start")
# minNumberOfParticipants == maxNumberOfParticipants for fixed battles; self is -1
bot.initialEnemyCount = e.gameSetup.minNumberOfParticipants - 1
# New battle: reset the cumulative outcome counters and remember how many
@@ -1129,7 +1132,7 @@ method run*(bot: ModularBot) =
# target changes to a different bot id (gated by
# TR_TMHORIZON_RESET_ON_TARGET, default on; no-op in 1v1).
discard bot.tmHorizon.targetChanged(candidateId)
discard bot.bitbrain.targetChanged(candidateId)
discard bot.leadGain.targetChanged(candidateId)
if bot.currentTargetId >= 0:
bot.lastKnownTargetId = bot.currentTargetId
@@ -1273,7 +1276,7 @@ method run*(bot: ModularBot) =
var tmselPreds: array[len(PowerBins), GunPrediction]
var tmpPreds: array[len(PowerBins), GunPrediction]
var tmhPreds: array[len(PowerBins), GunPrediction]
var bbPreds: array[len(PowerBins), GunPrediction]
var lgPreds: array[len(PowerBins), GunPrediction]
# ── TR_VBULLET_ADMIT_ONLY gate ──────────────────────────────────────
# Rack membership used to filter only SELECTION, so every unselected gun
# still ran predict()+spawnBullets() each tick to feed a fitness table the
@@ -1290,7 +1293,7 @@ method run*(bot: ModularBot) =
admit[gi] = ModuleVBullets and
vBulletAdmitted(gi, bot.rackMode, ActiveRackMembership,
VBulletAdmitOnly or gi == TmPatternId or
gi == TmHorizonId or gi == BitBrainId)
gi == TmHorizonId or gi == LeadGainId)
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]))
@@ -1313,9 +1316,9 @@ method run*(bot: ModularBot) =
# TMHORIZON likewise: Pattern base + horizon TM correction.
if admit[TmHorizonId]:
tmhPreds[i] = bot.tmHorizon.predict(bot.lastState, bulletSpeed(PowerBins[i]))
# BITBRAIN: Pattern base + ADE/SBC fine-grained angular correction.
if admit[BitBrainId]:
bbPreds[i] = bot.bitbrain.predict(bot.lastState, bulletSpeed(PowerBins[i]))
# LEADGAIN: Pattern base, lead scaled by a per-band learned gain.
if admit[LeadGainId]:
lgPreds[i] = bot.leadGain.predict(bot.lastState, bulletSpeed(PowerBins[i]))
if admit[0]: bot.tracker.spawnBullets(0, headsUp, bot.lastState, tid)
if admit[1] and not gunDisabled(1): bot.tracker.spawnBullets(1, linPreds, bot.lastState, tid)
@@ -1337,8 +1340,8 @@ method run*(bot: ModularBot) =
bot.tracker.spawnBullets(TmPatternId, tmpPreds, bot.lastState, tid)
if admit[TmHorizonId] and not gunDisabled(TmHorizonId):
bot.tracker.spawnBullets(TmHorizonId, tmhPreds, bot.lastState, tid)
if admit[BitBrainId] and not gunDisabled(BitBrainId):
bot.tracker.spawnBullets(BitBrainId, bbPreds, bot.lastState, tid)
if admit[LeadGainId] and not gunDisabled(LeadGainId):
bot.tracker.spawnBullets(LeadGainId, lgPreds, bot.lastState, tid)
# Build slim enemy table for tickBullets
var enemyPositions: Table[int, tuple[x, y: float, lastSeenTick: int, alive: bool]]
@@ -1374,7 +1377,7 @@ method run*(bot: ModularBot) =
of 13: bot.tmSelector.onResult(fe)
of TmPatternId: bot.tmPattern.onResult(fe)
of TmHorizonId: bot.tmHorizon.onResult(fe)
of BitBrainId: bot.bitbrain.onResult(fe)
of LeadGainId: bot.leadGain.onResult(fe)
else: discard
if fe.hit: inc bot.virtualHits else: inc bot.virtualMiss
if DebugDrawOn and VBulletDebugOn:
@@ -1429,7 +1432,7 @@ method run*(bot: ModularBot) =
of 13: bot.tmSelector.predict(bot.lastState, bulletSpeed(power))
of TmPatternId: bot.tmPattern.predict(bot.lastState, bulletSpeed(power))
of TmHorizonId: bot.tmHorizon.predict(bot.lastState, bulletSpeed(power))
of BitBrainId: bot.bitbrain.predict(bot.lastState, bulletSpeed(power))
of LeadGainId: bot.leadGain.predict(bot.lastState, bulletSpeed(power))
else: bot.headOn.predict(bot.lastState, bulletSpeed(power))
let aimTarget = aimAngle(getX(), getY(), pred.x, pred.y)
@@ -1497,7 +1500,7 @@ proc seedSelectorRng() =
when isMainModule:
var bot = ModularBot(
tracker: vb.initTracker(17), # 0: HeadOn, 1: Linear, 2: Tsetlin, 3: Circular, 4: GuessFactor, 5: Pattern, 6: WallBounce, 7: Accel, 8: StopShot, 9: Displace, 10: AvgLead, 11: DecayGF, 12: KNN, 13: TMSelect, 14: TMPattern, 15: TMHorizon, 16: BitBrain
tracker: vb.initTracker(17), # 0: HeadOn, 1: Linear, 2: Tsetlin, 3: Circular, 4: GuessFactor, 5: Pattern, 6: WallBounce, 7: Accel, 8: StopShot, 9: Displace, 10: AvgLead, 11: DecayGF, 12: KNN, 13: TMSelect, 14: TMPattern, 15: TMHorizon, 16: LeadGain
headOn: HeadOnGun(),
linear: LinearGun(),
circular: CircularGun(),
@@ -1514,7 +1517,7 @@ when isMainModule:
tmSelector: initTmSelectorGun(),
tmPattern: initTmRadialGun(),
tmHorizon: initTmHorizonGun(),
bitbrain: initBitBrainGun(),
leadGain: initLeadGainGun(),
radar: RadarLockModule(),
meleeRadar: initAdaptiveMeleeRadar(),
mover: TFILModule(debugGraphics: DebugDrawOn),
@@ -1553,6 +1556,6 @@ when isMainModule:
disabledGuns: DisabledGuns,
tmHorizon: bot.tmHorizon,
patternMatcher: bot.patternMatcher,
bitbrain: bot.bitbrain,
leadGain: bot.leadGain,
))
start(bot, botJsonPath)
+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",