7a6237ec20
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>
139 lines
7.6 KiB
Markdown
139 lines
7.6 KiB
Markdown
# 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`.
|