59c5af0499
TR_RACK_BITBRAIN=both is what the owner's live .env carries, and with the new ADE+SBC gun registered at id 17 under the SAME rack name that value was also landing on id 17 - so a gun that was not enabled (TR_BITBRAIN_NET unset) was admitted into the rack and its placeholder predictions were pushed into the shared VirtualTracker ring, which shifts every other gun's learning order. While the namespace is LEGACY, loadRackMembership now skips id 17's TR_RACK_BITBRAIN entirely, so that value addresses ONLY the gun it always addressed (LEADGAIN, id 16). ModularBot additionally gates admission on BitbrainNetGun.gunAdmitted(), and test_bitbrain_net pins the truth table: over 6 (rack, switch) settings there is NO configuration that admits the gun while leaving it disabled. Guards: test_env_report 25, test_rack_membership 49 (was 48; the revert one-liner now sets TR_BITBRAIN_NET=1 and one truth-table check was added), test_tm_pattern_registration 20, test_bitbrain 56, test_gun_harness 39, test_tfil_commit_env 30, test_lead_gain_registration 13, test_lead_gain_legacy 24, test_bitbrain_net 44. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
194 lines
12 KiB
Markdown
194 lines
12 KiB
Markdown
# bitbrain_net — quick recap (inputs / outputs)
|
||
|
||
Recap card. Everything below is read off `common_libs/guns/bitbrain_net.nim`.
|
||
Companion to the library's own README (`common_libs/bitbrain/README.md`) and to
|
||
`common_libs/guns/lead_gain.README.md` (the *other* gun, the per-range-band
|
||
lead-gain corrector at rack id 16).
|
||
|
||
## What it is
|
||
|
||
The **real ADE+SBC gun**: an ADE layer (thresholded random projections with
|
||
**online threshold adaptation**) feeding the SBC head from
|
||
`common_libs/bitbrain/`, with the **counted + decay** mode available (the mode
|
||
that delivers forgetting and true per-class probabilities —
|
||
`docs/bitbrain_counted_sbc.md`).
|
||
|
||
It took the `BITBRAIN` rack name (id **17**) and the `TR_BITBRAIN_*` knob prefix
|
||
that the renamed corrector gave up. **Default OFF.** It needs
|
||
`TR_RACK_BITBRAIN=both` in the rack table **and** `TR_BITBRAIN_NET=1` — the one
|
||
switch that decides whether the `TR_BITBRAIN_*` namespace is legacy
|
||
(`LEADGAIN`'s) or the new gun's, and the new gun's master on/off at the same
|
||
time. The two are gated by the SAME switch, so no env configuration can admit
|
||
the gun while leaving it disabled
|
||
(`test_bitbrain_net.nim` pins that over a 6-setting truth table) — which
|
||
matters, because a pre-rename `.env` still carries `TR_RACK_BITBRAIN=both` and a
|
||
disabled gun's placeholder predictions in the shared `VirtualTracker` ring would
|
||
shift every other gun's learning order. The network is built lazily, so an unset
|
||
environment never allocates a byte.
|
||
|
||
## OUTPUT SHAPE — a fine-grained correction ON TOP of Pattern
|
||
|
||
**Not** a direct aim point from the argmax class. The class-resolved angular
|
||
correction added to Pattern's bearing is the shape `docs/bitbrain_gate.md`
|
||
actually measured, and Pattern is already a strong predictor, so the net's job
|
||
is the small signed *residual*, not the whole aim.
|
||
|
||
| Output | Formula / meaning |
|
||
|---|---|
|
||
| Correction | `shift = Σ_k P(k)·centre_k / Σ_k P(k)` — the **probability-weighted mean of the `nClasses` class centres** under `inferProb`, in degrees, over ±`TR_BITBRAIN_SPAN` |
|
||
| Aim point | `tmhApplyShift(self, Pattern prediction, shift)`. Below `TR_BITBRAIN_MINOBS` resolved samples, or when the posterior has no mass, `shift == 0.0` and **Pattern's prediction is returned unchanged** |
|
||
| `[bbn]` log (only `TR_BITBRAIN_NETLOG=1`, change-gated) | `t`, `shift` (deg), `class` (argmax), `in`, `ncl`, `nAde`, `mode`, `trained`, `adapts`, `pend`, `dropped` |
|
||
| Does **NOT** output | an aim point of its own; it never discards Pattern |
|
||
|
||
## INPUT — a CONFIGURED SET OF FEATURE BLOCKS
|
||
|
||
The input vector is a concatenation of feature blocks, each independently
|
||
selectable (`TR_BITBRAIN_FEATURES`) and with a settable width. A block is a list
|
||
of scalar quantities; a block of width `W` lays each quantity out as a `W`-slot
|
||
thermometer code, so **width == resolution**. Slots are `0`/`255` uint8, which the
|
||
ADE scorer centres at 127 (`DefaultCenter`): a synapse *matches* when its
|
||
polarity agrees with the slot, so a random ADE fires iff its `w` synapses all
|
||
match — a thresholded random projection with firing rate `2^-w`, which online
|
||
homeostasis then drives toward `TR_BITBRAIN_TARGET`.
|
||
|
||
**The default block set is 52 slots:**
|
||
|
||
| block | quantities | width | slots | quantity |
|
||
|---|---:|---:|---:|---|
|
||
| `epos` | 2 | 4 | 8 | enemy offset from us, x and y, over the arena span |
|
||
| `evel` | 2 | 3 | 6 | enemy speed; enemy heading minus the bearing to us |
|
||
| `eturn` | 2 | 2 | 4 | turn direction this tick; turn consistency over the ring |
|
||
| `eself` | 2 | 2 | 4 | our speed; our heading minus the bearing to the enemy |
|
||
| `dist` | 2 | 5 | 10 | range; signed range rate over the last 10 ticks |
|
||
| `bear` | 1 | 4 | 4 | relative bearing (enemy bearing minus our heading) |
|
||
| `walls` | 4 | 2 | 8 | distance to each of the four arena walls |
|
||
| `bull` | 2 | 2 | 4 | live-bullet count; nearest bullet's signed lateral offset |
|
||
| `hzn` | 1 | 4 | 4 | bullet flight time to the current range |
|
||
|
||
`docs/state_window_gate.md` measured that a long **temporal window** of states
|
||
destroys recurrence, so there is deliberately **no window block**: the only
|
||
history-derived inputs are the three rate/turn quantities above (a 12-tick
|
||
ring), i.e. the same causal information Pattern itself uses.
|
||
|
||
## KNOB TABLE
|
||
|
||
| Env | Default | Meaning |
|
||
|---|---|---|
|
||
| `TR_RACK_BITBRAIN` | `off` | rack admission for id 17 (the *current* name of the rack key) |
|
||
| `TR_BITBRAIN_NET` | `0` | **master switch + namespace disambiguator.** `0` = the gun is off AND the `TR_BITBRAIN_*` names are LEGACY aliases of `LEADGAIN` (and `TR_RACK_BITBRAIN` selects id 16); `1` = the gun is on and the names below are this gun's (and `TR_RACK_BITBRAIN` selects id 17) |
|
||
| `TR_BITBRAIN_INPUT` | 52 | total input slots; pads or truncates the block layout so the ADE codes can never index out of range |
|
||
| `TR_BITBRAIN_FEATURES` | all blocks at their shipped width | `name:W` list, comma separated. `name:0` switches a block OFF; an unlisted block keeps its shipped width; an unknown name warns and is ignored |
|
||
| `TR_BITBRAIN_NCLASSES` | 8 | output resolution |
|
||
| `TR_BITBRAIN_NADES` | 256 | ADEs per address decoder (RAM is **quadratic** in this) |
|
||
| `TR_BITBRAIN_WIDTHS` | `4,5,6` | ADE clause widths; one AD per width, one cross-AD SBC per pair (3 widths → 3 SBCs) |
|
||
| `TR_BITBRAIN_SPAN` | `40.0` | class half-range, degrees |
|
||
| `TR_BITBRAIN_MODE` | `counted` | `bitset` \| `counted` (saturating counters + decay) |
|
||
| `TR_BITBRAIN_DECAY_EVERY` | 64 | counted mode: learns between global decay passes |
|
||
| `TR_BITBRAIN_DECAY_SHIFT` | 3 | counted mode: `c -= c shr shift` per pass (`0` disables) |
|
||
| `TR_BITBRAIN_MINOBS` | 32 | resolved samples before the correction is applied at all |
|
||
| `TR_BITBRAIN_ADAPT_EVERY` | 200 | inputs between ADE threshold-adaptation passes |
|
||
| `TR_BITBRAIN_TARGET` | 0.01 | the paper's target ADE firing rate |
|
||
| `TR_BITBRAIN_NETSEED` | 20240921 | network seed (a **private** RNG, never the global one) |
|
||
| `TR_BITBRAIN_NETLOG` | `0` | `1` = emit the `[bbn]` line |
|
||
| `TR_BITBRAIN_CALIB_EVERY` | — | compat alias for `TR_BITBRAIN_ADAPT_EVERY` |
|
||
| `TR_BITBRAIN_NET_RESET_ON_TARGET` | `1` | wipe the SBC counters when the enemy id changes |
|
||
|
||
## HOW TO TURN IT ON
|
||
|
||
```
|
||
TR_BITBRAIN_NET=1 # the master switch — the legacy aliases go quiet
|
||
TR_RACK_BITBRAIN=both
|
||
TR_RACK_PATTERN=off
|
||
TR_BITBRAIN_NETLOG=1
|
||
```
|
||
|
||
## MEASURED SCALING (RAM / ms-per-tick / quality)
|
||
|
||
`common_libs/tests/measure_bitbrain_scaling.nim`, replaying 3 recorded live runs
|
||
(37 412 recorded ticks, 149 650 tick × power-bin samples), `-d:release`,
|
||
single-threaded, one `predict` per power bin per tick — the same call pattern
|
||
as the live loop. The timed region contains **only** the gun's `predict` calls:
|
||
the interception solve is done once, up front, so the ruler's own cost cannot
|
||
contaminate the timing.
|
||
|
||
| arm | in | nCl | nAde | mode | RAM B | SBC B | AD B | ms/tick | % of 13.16 | mean\|err\|° | hit% | Pattern\|err\|° |
|
||
|---|---:|---:|---:|---|---:|---:|---:|---:|---:|---:|---:|---:|
|
||
| input-small | 26 | 8 | 256 | counted | 1 594 368 | 1 572 864 | 21 504 | 2.60 | 20 % | 17.329 | 8.00 | 16.964 |
|
||
| input-medium | 52 | 8 | 256 | counted | 1 594 368 | 1 572 864 | 21 504 | 2.70 | 21 % | 17.270 | 8.04 | 16.964 |
|
||
| input-large | 84 | 8 | 256 | counted | 1 594 368 | 1 572 864 | 21 504 | 2.54 | 19 % | 17.259 | 8.06 | 16.964 |
|
||
| classes-small | 52 | 2 | 256 | counted | 414 720 | 393 216 | 21 504 | 1.07 | 8 % | 17.201 | 8.12 | 16.964 |
|
||
| classes-medium| 52 | 8 | 256 | counted | 1 594 368 | 1 572 864 | 21 504 | 2.70 | 21 % | 17.270 | 8.04 | 16.964 |
|
||
| classes-large | 52 | 64 | 256 | counted |12 604 416 |12 582 912 | 21 504 | 19.56 | 149 % | 17.115 | 8.27 | 16.964 |
|
||
| nAde-small | 52 | 8 | 64 | counted | 103 680 | 98 304 | 5 376 | 0.30 | 2 % | 17.254 | 8.08 | 16.964 |
|
||
| nAde-medium | 52 | 8 | 256 | counted | 1 594 368 | 1 572 864 | 21 504 | 2.72 | 21 % | 17.270 | 8.04 | 16.964 |
|
||
| nAde-large | 52 | 8 | 512 | counted | 6 334 464 | 6 291 456 | 43 008 | 9.76 | 74 % | 17.264 | 8.03 | 16.964 |
|
||
| mode-bitset | 52 | 8 | 256 | bitset | 218 112 | 196 608 | 21 504 | 2.72 | 21 % | 16.975 | 8.53 | 16.964 |
|
||
| mode-counted | 52 | 8 | 256 | counted | 1 594 368 | 1 572 864 | 21 504 | 2.71 | 21 % | 17.270 | 8.04 | 16.964 |
|
||
|
||
Reproduce:
|
||
```bash
|
||
nim c -r -d:release --path:common_libs common_libs/tests/measure_bitbrain_scaling.nim --limit 3
|
||
```
|
||
|
||
### The scaling laws, as MEASURED
|
||
|
||
* **RAM is dominated by the SBC tensors (98.6 % at the default).** One SBC is
|
||
`nAde² × nClasses` cells. Bitset: 1 bit/cell. Counted: 1 byte/cell. The
|
||
measured counted/bitset factor is **7.30×** on the default geometry
|
||
(1 594 368 / 218 112), which is the bitset tensor's 1/8-of-a-`uint32`-slot
|
||
overhead — the tensors are `uint32`-slot-packed, so 8 classes share slots.
|
||
* **RAM vs `nClasses`: exactly linear.** 2 → 8 → 64 classes: 0.41 → 1.59 → 12.60 MB
|
||
(4× and 32× for 4× and 8× the classes). RAM vs `nAde`: **quadratic** —
|
||
64 → 256 → 512 gives 0.10 → 1.59 → 6.33 MB (4× then 16× for 4× then 2×).
|
||
* **RAM vs input width: FLAT.** 26 → 52 → 84 slots: 1 594 368 B in all three
|
||
cases. The input width only sets how many of the `2^inputWidth` synapses the
|
||
ADE codes may draw from; the codes are still `nAde × w` per AD. The only
|
||
width-dependent term is the ADE scoring cost and the tiny `AD B` column
|
||
(unchanged here because it is dominated by `nAde × w`, not by input width).
|
||
* **ms/tick vs input width: flat** (2.60 / 2.70 / 2.54 ms). vs `nClasses`:
|
||
**linear** (1.07 / 2.70 / 19.56 ms — the 64-class arm busts the 13.16 ms
|
||
budget at 149 %). vs `nAde`: **quadratic-ish** (0.30 / 2.72 / 9.76 ms), because
|
||
the ADE pass is `O(nAde × w)` and the SBC read is `O(|row| × |col|)`.
|
||
* **counted vs bitset: same time, 7.3× the RAM** (2.71 vs 2.72 ms/tick).
|
||
|
||
### Capacity vs accuracy — the direct answer
|
||
|
||
**More capacity buys essentially nothing here; it costs RAM and, past a point,
|
||
the tick budget.** Across a 100× range of RAM (0.10 MB → 12.60 MB) the offline
|
||
mean |angular error| moves from 17.254° to 17.115° — a 0.14° spread around
|
||
Pattern's own 16.964°, and the sign of the effect flips across the `nClasses`
|
||
axis (17.201° at 2 classes, 17.115° at 64), so it is not a trend, it is noise.
|
||
The only arm that *helps* is `mode-bitset` (16.975° vs 17.270° counted), and
|
||
even that costs nothing in accuracy terms until you switch to counted for
|
||
forgetting — at which point you pay 7.3× the RAM and lose the 0.3°.
|
||
|
||
The honest reading is that the information ceiling here is the **state**, not
|
||
the classifier: `docs/bitbrain_gate.md` measured ~1 bit of information in a
|
||
53-bit input, and this sweep reproduces that at 26, 52 and 84 slots alike. The
|
||
corrector is consistently **slightly WORSE than Pattern** offline (17.2° vs
|
||
17.0°), which is the same verdict the campaign already reached for every
|
||
additive-shift design. Capacity is not the binding constraint and buying more of
|
||
it is not the fix.
|
||
|
||
**VETO-CAPABLE CHECK ONLY.** Per `docs/offline_harness_trust.md` this is the
|
||
single trustworthy use of the offline harness — per-gun single-tick prediction
|
||
quality on a fixed trajectory. It is **not** a live result: nothing here says
|
||
anything about damage, survival or round wins, and it is never presented as one.
|
||
|
||
## Tests
|
||
|
||
```bash
|
||
# 44 checks: the gun really engages the network (not just that it links)
|
||
nim c -r -d:release --path:common_libs common_libs/tests/test_bitbrain_net.nim
|
||
# the library itself (56 checks, unchanged)
|
||
nim c -r -d:release --path:common_libs common_libs/tests/test_bitbrain.nim
|
||
```
|
||
|
||
`test_bitbrain_net.nim` proves engagement by observation, not by linkage: the
|
||
network is 0 bytes before first use, a **trained** head returns different
|
||
classes for different states and separates two taught populations, a learn
|
||
visibly raises SBC occupancy, a bitset learn is idempotent while a counted
|
||
learn's evidence is monotone, ADE threshold adaptation runs, no env setting can
|
||
admit a disabled gun, and construction + first predict leave the global RNG
|
||
untouched.
|