Files
SirRoboGarage/common_libs/guns/lead_gain.README.md
T
SirStone 7a6237ec20 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>
2026-09-26 15:54:00 +02:00

139 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# lead_gain — quick recap (inputs / outputs)
Recap card. Everything below is read off `common_libs/guns/lead_gain.nim`.
## The name
This gun is **LEADGAIN** (rack id 16, `TR_LEADGAIN_*`). It used to be called
`BITBRAIN` and to live in `guns/bitbrain_gun.nim`, but the ADE+SBC network was
removed when it was rebuilt into what it actually is: **it learns a multiplier
for Pattern's lead, separately per range band.** The rack id is unchanged. The
real ADE+SBC gun is `guns/bitbrain_net.nim` (rack id 17).
### Backward compatibility (read this before editing your `.env`)
The `TR_BITBRAIN_*` names your `.env` already contains still work, and still
select **this** gun. The disambiguation is one switch, `TR_BITBRAIN_NET`
(default `0`):
| `TR_BITBRAIN_NET` | who owns `TR_BITBRAIN_*` |
|---|---|
| unset / `0` | LEGACY — these are aliases for `TR_LEADGAIN_*`; the new ADE+SBC gun is off |
| `1` | the new ADE+SBC gun (rack id 17) |
So the owner's existing `TR_RACK_LEADGAIN=both TR_BITBRAIN_GAINS=… TR_BITBRAIN_MEM=decay
TR_BITBRAIN_LOG=1` keeps behaving exactly as before, and one `[depr]` line on
stderr names the new `TR_LEADGAIN_*` spelling of each knob it honoured. The two
name sets are disjoint by construction, so no name is ever claimed twice.
Migrated names are `TR_LEADGAIN_GAINS`, `_MEM`, `_MIN_OBS`, `_DECAY`,
`_DECAY_FRAC`, `_LOG`, `_RESET_ON_TARGET`, `_N`, `_NADE`, `_RANGE`, `_WARMUP`,
`_ADAPT`, `_CALIB`, `_SEED` and `TR_RACK_LEADGAIN`.
## What it is today
- A **lead-gain corrector on top of Pattern's prediction**. It scales Pattern's lead over the line of sight by a learned gain.
- The **ADE/SBC neural network is REMOVED from this gun** (it lives in `guns/bitbrain_net.nim` now). The generic `common_libs/bitbrain/` library is intact and tested separately (`test_bitbrain.nim`, 56 checks).
## INPUTS
| Input | Source in code |
|---|---|
| World state (self pos, enemy pos, tick) | `WorldState state` |
| **Base aim** — Pattern's prediction | `g.tmh.pattern.predict(state, bulletSpeed)`: `TmHorizonGun.pattern`, a `PatternMatcherGun` (`guns/pattern_matcher`) |
| Range band (5 bands: 0/100/200/300/450) | `lgBandOf(dist)`, `LG_BAND_LO/HI` |
| **Gate** — gain applies only from band 3 up | `LG_GAIN_BAND_MIN = 3` → range **≥ ~300 px** |
| **Label / feedback** — deferred observation lookup | at fire time store lead + tolerance; `h = round(dist/speed)` ticks later `tmhObservedAt(g.tmh, state.tick, selfX, selfY)` returns the enemy's OBSERVED bearing |
| Aim tolerance (target's angular half-width) | `lgTolDeg(dist) = atan(18/range)` in degrees |
| Config knobs | env, resolved once in `initLeadGainGun` (see table) |
## OUTPUTS
| Output | Formula / meaning |
|---|---|
| Aim point | `aim = LOS + gain * (patternAim - LOS)` — applied as angular `shift = (gain - 1.0) * lead` deg via `tmhApplyShift`; when `gain == 1.0` the base prediction is returned unchanged |
| `gain` | argmax **hit rate** per band over the candidate list; `LG_CAND` default has **5 candidates** `{0.0, 0.25, 0.5, 0.75, 1.0}` (0 = HeadOn, 1 = Pattern); one candidate = fixed gain, no learning |
| `[lg]` log line (only if `TR_LEADGAIN_LOG=1`; emitted only when `(gain, band)` changes) | see below |
| Does **NOT** output | a predicted angle / bearing. It never aims on its own — it only rescales Pattern's lead. |
`[lg]` fields, one at a time:
| field | meaning |
|---|---|
| `t` | current tick |
| `band` | lower edge of the range band in use (e.g. `300+`) |
| `gain` | the gain being applied to Pattern's lead |
| `shift` | angular shift actually applied = `(gain-1)*lead`, degrees |
| `rate` | hit rate of the chosen gain in this band |
| `n` | resolved samples in this band |
| `ncand` | number of candidate gains |
| `trained` | total resolved samples this battle |
| `pend` | deferred labels still waiting |
| `dropped` | labels that could not be resolved (stale / out-of-order) |
| `mode` | memory mode: perRound / retained / decay |
## KNOB TABLE (`TR_LEADGAIN_*`)
Every old `TR_BITBRAIN_<X>` in the **LIVE** and **INERT** tables below is still
honoured as an alias, and one `[depr]` line on stderr names the `TR_LEADGAIN_*`
spelling (see "The name" above). The ADE+SBC gun uses a **different** set of
`TR_BITBRAIN_*` names (`TR_BITBRAIN_INPUT`, `_NCLASSES`, `_NADES`, …) plus
`TR_BITBRAIN_MODE` / `_DECAY_EVERY` / `_DECAY_SHIFT` from the library; the two
sets are disjoint, so nothing is claimed twice.
**LIVE** — the resolved field is read by the learner/predict path:
| Env (new / legacy alias) | Meaning |
|---|---|
| `TR_LEADGAIN_GAINS` / `TR_BITBRAIN_GAINS` | comma-separated candidate gains (replaces `LG_CAND`) |
| `TR_LEADGAIN_MEM` / `TR_BITBRAIN_MEM` | perRound / retained / decay memory |
| `TR_LEADGAIN_MIN_OBS` / `TR_BITBRAIN_MIN_OBS` | samples before a band is trusted (in `lgGainFor`) |
| `TR_LEADGAIN_DECAY` / `TR_BITBRAIN_DECAY` | decay interval in resolved samples (`resolvePending`) |
| `TR_LEADGAIN_DECAY_FRAC` / `TR_BITBRAIN_DECAY_FRAC` | per-decay shrink of the hit counts (`lgApplyDecay`) |
| `TR_LEADGAIN_LOG` / `TR_BITBRAIN_LOG` | `1` = emit the `[lg]` line |
| `TR_LEADGAIN_RESET_ON_TARGET` / `TR_BITBRAIN_RESET_ON_TARGET` | wipe learning when the enemy id changes (`targetChanged`) |
| `TR_RACK_LEADGAIN` / `TR_RACK_BITBRAIN` | the rack admission switch |
| `TR_BITBRAIN_NET` | `0` (default) keeps `TR_BITBRAIN_*` legacy; `1` hands the namespace to the new ADE+SBC gun |
**INERT** — kept only so old configs and the boot report don't warn; never touch the gain learner:
| Env | Stored as (only read by boot report / guard test) |
|---|---|
| `TR_LEADGAIN_N` / `TR_BITBRAIN_N` | `nClasses` |
| `TR_LEADGAIN_NADE` / `TR_BITBRAIN_NADE` | `nAde` |
| `TR_LEADGAIN_RANGE` / `TR_BITBRAIN_RANGE` | `maxDeg` |
| `TR_LEADGAIN_WARMUP` / `TR_BITBRAIN_WARMUP` | `warmupN` |
| `TR_LEADGAIN_ADAPT` / `TR_BITBRAIN_ADAPT` | `adaptEvery` |
| `TR_LEADGAIN_CALIB` / `TR_BITBRAIN_CALIB` | `calibEvery` |
| `TR_LEADGAIN_SEED` / `TR_BITBRAIN_SEED` | `seed` |
## HOW TO TURN IT ON
Default is **off**; the shipped rack never calls it. Minimal `.env`:
```
TR_RACK_LEADGAIN=both
TR_RACK_PATTERN=off
TR_LEADGAIN_GAINS=1.0 # 1.0 = identity = aims EXACTLY like Pattern
TR_LEADGAIN_MEM=decay
TR_LEADGAIN_LOG=1
```
⚠️ **`TR_LEADGAIN_GAINS=1.0` is the identity** — with one candidate at 1.0 the gun aims **exactly like Pattern**.
**Do not read the candidate list as a recommendation.** Measured live: fixed gains **above** 1.0 are
*decisively harmful* (1.5 → −93.8 dmg/run, p=0.0006; 1.25 → −34.2 dmg/run), a learner restricted to **≤ 1.0**
is a **wash** (−2.5 dmg/run, p=0.87), and zero lead (= HeadOn) is **catastrophic** (14 vs 279 dmg/run).
So the default set `{0, 0.25, 0.5, 0.75, 1.0}` is **not** a recommendation either — it merely *allows* the gun
to shrink the lead toward HeadOn. Multi-candidate lists are for running the experiment, not for playing.
## MEASURED VERDICT (measured under the old `BITBRAIN` name)
- **Neutral vs Pattern** across many opponents: damage/run 214.5 vs 210.9, round wins 49.4% vs 49.8% over 32 opponents (`docs/gauntlet_bitbrain_vs_pattern.md`).
- **Specifically worse on DrussGT** alone: −18.1 damage/run (`docs/gauntlet_bitbrain_vs_pattern.md`, `docs/bitbrain_gun_verdict.md`).
- The **lead-amplitude (gain) axis is CLOSED** — nothing beats Pattern in either direction (`docs/bitbrain_campaign.md` §Phase 2 / §2.6).
## PROVENANCE
- **Derived from code (this file):** what it is today, all inputs, the output formula, the `[lg]` fields, the LIVE/INERT split, and the "how to turn it on" env lines.
- **Taken from the named evidence docs:** the numbers in MEASURED VERDICT above — see `docs/gauntlet_bitbrain_vs_pattern.md`, `docs/bitbrain_gun_verdict.md`, `docs/bitbrain_campaign.md`.