BitBrain campaign phase 1: the missing gain sweep and BitBrain as a lead-gain corrector

Task A — the gain region Phase 0 never covered (gain < 1). Extend the
prediction-quality ruler with gain 0.25/0.50/0.75 arms and a fixed causal
per-band arm. Full 70-run result: the hitProxy-argmax curve is
[1.00, 1.00, 1.00, 0.00, 0.00] — Pattern below 300 px, HeadOn above —
worth +0.13 pp at 300-450 and +2.16 pp at 450+ (0.0767 -> 0.0984). Lead
correlation is identical for every g>0 (Pearson is scale-invariant), so a
shrinking gain adds no lead information; and the least-squares optimum
[1,1,1,1,0.25,0.25] diverges from the hitProxy optimum because Pattern's
lead errors are bimodal.

Task B — rebuild guns/bitbrain_gun.nim as a lead-gain corrector:
aim = LOS + gain*(patternAim - LOS), gain learned online per range band by
ranking candidate gains on the hit-probability proxy (the observed lead
label via tmhObservedAt), gated to range >= 300 px. ADE+SBC output removed.
Offline (70 runs): Pattern below 300 px, +0.37 pp at 300-450, +1.90 pp at
450+ (hitProxy 0.0957 vs 0.0767), matching the fixed rule to within 0.27 pp
at 450+ and exceeding it at 300-450. ~0.0007 ms/tick marginal (old gun
~0.114 ms/tick). Default off; rack membership, env report and guard tests
(bitbrain 32, registration 13, rack 48, tm_pattern 20, env_report) unchanged
and green.

Ledger: docs/bitbrain_campaign.md Phase 1, including the three on-file
negatives and the causal-shippability note. No live claim.
This commit is contained in:
2026-09-25 00:10:19 +02:00
parent 140fe2519a
commit c305ef4212
4 changed files with 553 additions and 313 deletions
@@ -6,8 +6,8 @@ ruler : continuous (physically exact)
runs : 70
recorded ticks: 899607
tick x bin : 3598428
wall time : 406.88s (0.1131 ms per tick-bin)
per-arm speed : 0.1131 s per 1000 tick-bins per arm
wall time : 271.23s (0.0754 ms per tick-bin)
per-arm speed : 0.0754 s per 1000 tick-bins per arm
NOTE: offline OPEN-LOOP prediction quality only. No win/damage/survival claim.
@@ -18,7 +18,7 @@ VALIDATION -- the ruler must pass ALL of these before any number below is trus
ruler=continuous hits n=5480 mean|err|= 1.360 deg / 10.5 px | misses n=48304 mean|err|= 16.597 deg / 140.3 px | separation 12.20x deg / 13.34x px -> OK
ruler=integer hits n=5480 mean|err|= 1.478 deg / 11.4 px | misses n=48304 mean|err|= 16.724 deg / 141.3 px | separation 11.32x deg / 12.43x px -> OK
2. perfect-oracle gun max |err| over all tick-bins = 0.000000 deg -> OK
3. HeadOn (static LOS) mean|err| = 13.217 deg vs Pattern 16.609 / TMHorizon 16.627 / BitBrain 16.635
3. HeadOn (static LOS) mean|err| = 13.217 deg vs Pattern 16.609 / TMHorizon 16.627 / BitBrain 13.627
-> UNEXPECTED: a predictive gun is worse than static LOS
NaiveLinear mean|err| = 22.086 deg (over-leads; see the lead-gain sweep for why a larger
lead *response* does not mean a smaller angular error)
@@ -85,23 +85,47 @@ TMHorizon 200-300 74215 16.644 21.142 1.091 0.1786 84.4
TMHorizon 300-450 1119777 17.572 21.878 0.691 0.0996 84.01
TMHorizon 450+ 2311323 16.199 20.025 -0.687 0.0757 81.19
(TMHorizon: 63782 tick-bins had no valid interception)
BitBrain 0-100 4423 11.118 15.268 -1.507 0.6810 64.72
BitBrain 100-200 24908 15.107 19.782 1.289 0.3241 76.63
BitBrain 200-300 74215 16.838 21.426 1.341 0.1747 105.04
BitBrain 300-450 1119777 17.575 21.894 0.933 0.1025 100.98
BitBrain 450+ 2311323 16.200 20.032 -0.647 0.0767 96.38
BitBrain 0-100 4423 10.555 14.796 -0.404 0.6993 64.72
BitBrain 100-200 24908 14.745 19.510 1.276 0.3418 76.63
BitBrain 200-300 74215 16.610 21.174 1.350 0.1850 81.45
BitBrain 300-450 1119777 15.518 19.170 0.773 0.1073 85.65
BitBrain 450+ 2311323 12.608 15.431 -0.351 0.0957 79.92
(BitBrain: 63782 tick-bins had no valid interception)
PatternGain0.25 0-100 4423 16.984 19.724 -1.060 0.3914 46.00
PatternGain0.25 100-200 24908 17.791 20.374 -0.027 0.1661 51.44
PatternGain0.25 200-300 74215 15.950 18.761 0.698 0.1276 53.22
PatternGain0.25 300-450 1119777 14.131 16.972 0.755 0.1025 52.64
PatternGain0.25 450+ 2311323 12.261 14.888 -0.358 0.0931 51.24
(PatternGain0.25: 63782 tick-bins had no valid interception)
PatternGain0.50 0-100 4423 14.583 16.877 -0.841 0.4689 50.78
PatternGain0.50 100-200 24908 16.103 18.679 0.407 0.1673 58.27
PatternGain0.50 200-300 74215 15.316 18.261 0.915 0.1366 62.44
PatternGain0.50 300-450 1119777 14.491 17.566 0.819 0.1003 61.45
PatternGain0.50 450+ 2311323 12.938 15.799 -0.453 0.0883 59.63
(PatternGain0.50: 63782 tick-bins had no valid interception)
PatternGain0.75 0-100 4423 12.356 15.103 -0.622 0.6396 57.28
PatternGain0.75 100-200 24908 14.965 18.369 0.841 0.2337 66.41
PatternGain0.75 200-300 74215 15.528 19.120 1.133 0.1389 71.95
PatternGain0.75 300-450 1119777 15.639 19.275 0.884 0.0952 72.71
PatternGain0.75 450+ 2311323 14.280 17.588 -0.548 0.0813 68.72
(PatternGain0.75: 63782 tick-bins had no valid interception)
PatternBandGain 0-100 4423 10.555 14.796 -0.404 0.6993 64.72
PatternBandGain 100-200 24908 14.745 19.510 1.276 0.3418 76.63
PatternBandGain 200-300 74215 16.610 21.174 1.350 0.1850 81.45
PatternBandGain 300-450 1119777 14.607 17.606 0.690 0.1049 46.38
PatternBandGain 450+ 2311323 12.326 15.017 -0.263 0.0984 45.49
(PatternBandGain: 63782 tick-bins had no valid interception)
========================================================================================================================
HEADROOM -- the direct answer: how far each arm is from the oracle ceiling, per band
========================================================================================================================
band Pattern n Pattern|err| Pattern hpx Oracle hpx headroom pp naive hpx TMHoriz hpx BitBrain hpx
---------------------------------------------------------------------------------------------------------------
0-100 4423 10.555 0.6993 1.0000 0.3007 0.6505 0.6955 0.6810
100-200 24908 14.745 0.3418 1.0000 0.6582 0.3879 0.3418 0.3241
200-300 74215 16.610 0.1850 1.0000 0.8150 0.1924 0.1786 0.1747
300-450 1119777 17.531 0.1036 1.0000 0.8964 0.0995 0.0996 0.1025
450+ 2311323 16.193 0.0767 1.0000 0.9233 0.0537 0.0757 0.0767
0-100 4423 10.555 0.6993 1.0000 0.3007 0.6505 0.6955 0.6993
100-200 24908 14.745 0.3418 1.0000 0.6582 0.3879 0.3418 0.3418
200-300 74215 16.610 0.1850 1.0000 0.8150 0.1924 0.1786 0.1850
300-450 1119777 17.531 0.1036 1.0000 0.8964 0.0995 0.0996 0.1073
450+ 2311323 16.193 0.0767 1.0000 0.9233 0.0537 0.0757 0.0957
hitProxy = fraction of tick-bins aimed within atan(18/range) of the true interception point.
headroom pp = oracle hitProxy - Pattern hitProxy = the absolute hit-probability points available
@@ -129,6 +153,51 @@ band gain=1.0 gain=1.5 gain=2.0 gain=3.0 best-gain
300-450 17.531 23.279 29.955 44.274 1.0 (17.531)
450+ 16.193 21.245 26.997 39.271 1.0 (16.193)
========================================================================================================================
PHASE 1 — THE MISSING GAIN SWEEP: Pattern lead x gain in [0.00, 1.00] (0 = HeadOn, 1 = Pattern)
========================================================================================================================
Format per cell: mean|err| deg [hitProxy]. hitProxy is the objective. gain 0.0 is HeadOn,
gain 1.0 is Pattern. The per-band gain table IS causal to APPLY (range is known at fire time,
so a per-band lookup needs no learning); its ESTIMATION from these same runs is in-sample.
band |req| deg g=0.00 [hpx] g=0.25 [hpx] g=0.50 [hpx] g=0.75 [hpx] g=1.00 [hpx] bestHpx dHpx bestErr
--------------------------------------------------------------------------------------------------------------------
0-100 19.619 19.619 [0.342] 16.984 [0.391] 14.583 [0.469] 12.356 [0.640] 10.555 [0.699] 1.00 +0.0000 1.00
100-200 19.982 19.982 [0.172] 17.791 [0.166] 16.103 [0.167] 14.965 [0.234] 14.745 [0.342] 1.00 +0.0000 1.00
200-300 17.341 17.341 [0.133] 15.950 [0.128] 15.316 [0.137] 15.528 [0.139] 16.610 [0.185] 1.00 +0.0000 0.50
300-450 14.607 14.607 [0.105] 14.131 [0.102] 14.491 [0.100] 15.639 [0.095] 17.531 [0.104] 0.00 +0.0013 0.25
450+ 12.326 12.326 [0.098] 12.261 [0.093] 12.938 [0.088] 14.280 [0.081] 16.193 [0.077] 0.00 +0.0216 0.25
OPTIMAL GAIN CURVE (hitProxy-argmax per band) and its implied hit-probability gain vs Pattern:
0-100 best gain 1.00 hitProxy 0.6993 vs Pattern 0.6993 => +0.0000 pp
100-200 best gain 1.00 hitProxy 0.3418 vs Pattern 0.3418 => +0.0000 pp
200-300 best gain 1.00 hitProxy 0.1850 vs Pattern 0.1850 => +0.0000 pp
300-450 best gain 0.00 hitProxy 0.1049 vs Pattern 0.1036 => +0.0013 pp
450+ best gain 0.00 hitProxy 0.0984 vs Pattern 0.0767 => +0.0216 pp
gain = [ 0-100->1.00 100-200->1.00 200-300->1.00 300-450->0.00 450+->0.00 ]
DIRECT COMPARISON — Pattern vs the FIXED causal per-band rule [1,1,1,0.00,0.00] vs BitBrain (learned online):
band Pattern hpx fixed-band hpx BitBrain hpx fixed-Pat pp BB-Pat pp
--------------------------------------------------------------------------------
0-100 0.6993 0.6993 0.6993 +0.0000 +0.0000
100-200 0.3418 0.3418 0.3418 +0.0000 +0.0000
200-300 0.1850 0.1850 0.1850 +0.0000 +0.0000
300-450 0.1036 0.1049 0.1073 +0.0013 +0.0037
450+ 0.0767 0.0984 0.0957 +0.0216 +0.0190
fixed-band hpx = the [1,1,1,0,0] table applied causally; it was selected in-sample.
BitBrain is learned online from labels inside each run (cold start at gain 1.0).
LEAD CORRELATION PER GAIN (Pearson of applied lead with required lead). Pearson is invariant
under positive scaling, so every g>0 column must be IDENTICAL to Pattern; g=0 has no lead and
therefore no correlation. If they match, a shrinking gain does NOT add lead information — it
only shrinks the magnitude of an uninformative signal (the gain-sweep mechanism).
band corr(g) g=0.25 g=0.50 g=0.75 g=1.00
0-100 0.774 0.774 0.774 0.774
100-200 0.612 0.612 0.612 0.612
200-300 0.457 0.457 0.457 0.457
300-450 0.266 0.266 0.266 0.266
450+ 0.165 0.165 0.165 0.165
========================================================================================================================
LEAD INFORMATIVENESS -- capture slope (regression of applied lead on required lead) and lead correlation
========================================================================================================================
@@ -139,8 +208,8 @@ hits less' tension.
band HO |err| HO |req| Pat|req| Pat cap Pat corr Lin cap Lin corr TMH cap TMH corr BB cap BB corr
-----------------------------------------------------------------------------------------------------------------------
0-100 19.619 19.619 19.619 0.649 0.774 0.573 0.362 0.668 0.776 0.701 0.768
100-200 19.982 19.982 19.982 0.553 0.612 0.592 0.523 0.560 0.614 0.570 0.611
200-300 17.341 17.341 17.341 0.449 0.457 0.519 0.429 0.452 0.460 0.459 0.457
300-450 14.607 14.607 14.607 0.278 0.266 0.476 0.324 0.273 0.262 0.280 0.266
450+ 12.326 12.326 12.326 0.175 0.165 0.310 0.178 0.174 0.164 0.175 0.165
0-100 19.619 19.619 19.619 0.649 0.774 0.573 0.362 0.668 0.776 0.649 0.774
100-200 19.982 19.982 19.982 0.553 0.612 0.592 0.523 0.560 0.614 0.553 0.612
200-300 17.341 17.341 17.341 0.449 0.457 0.519 0.429 0.452 0.460 0.449 0.457
300-450 14.607 14.607 14.607 0.278 0.266 0.476 0.324 0.273 0.262 0.148 0.213
450+ 12.326 12.326 12.326 0.175 0.165 0.310 0.178 0.174 0.164 0.036 0.101
+98 -1
View File
@@ -30,8 +30,29 @@ const
A_NAIVE* = 7
A_TMH* = 8
A_BB* = 9
# ── Phase 1: the MISSING gain sweep. Gains >= 1 were measured worse at every
# band in Phase 0; the unexplored region is gain < 1. gain 0.0 is HeadOn
# (A_HEADON) and gain 1.0 is Pattern (A_PATTERN), so only 0.25/0.50/0.75 are
# new arms. Their `leadCorr` is IDENTICAL to Pattern's by construction (Pearson
# correlation is invariant under positive scaling) — printed only to prove it.
A_G025* = 10
A_G050* = 11
A_G075* = 12
# Phase 1 fixed causal per-band gain rule: the hitProxy-argmax curve measured by
# the sub-unity sweep ([1,1,1,0,0] == Pattern below 300 px, HeadOn above). This
# is the rule BitBrain must match; it needs no learning (range is known at fire
# time). The table was selected in-sample from this corpus.
A_BAND* = 13
BandGainTable* = [1.0, 1.0, 1.0, 0.0, 0.0]
ArmNames* = ["Oracle", "OracleQuant", "HeadOn", "Pattern", "PatternGain1.5",
"PatternGain2.0", "PatternGain3.0", "NaiveLinear", "TMHorizon", "BitBrain"]
"PatternGain2.0", "PatternGain3.0", "NaiveLinear", "TMHorizon", "BitBrain",
"PatternGain0.25", "PatternGain0.50", "PatternGain0.75", "PatternBandGain"]
const
## The five sub-unity gain arms, in increasing order, resolved to arm indices.
## gain 0.0 == HeadOn, gain 1.0 == Pattern.
GainArmIdx* = [A_HEADON, A_G025, A_G050, A_G075, A_PATTERN]
GainValues* = [0.0, 0.25, 0.50, 0.75, 1.0]
# ── the naive-linear control (job-95's LIN_M = 4 extrapolation) ──────────────
#
@@ -133,6 +154,13 @@ proc runRound(ctx: var Ctx, arms: var seq[ArmAcc], r: int) =
arms[A_G15].record(rng, wrap180(1.5 * plead - targetLead), 1.5 * plead, targetLead)
arms[A_G20].record(rng, wrap180(2.0 * plead - targetLead), 2.0 * plead, targetLead)
arms[A_G30].record(rng, wrap180(3.0 * plead - targetLead), 3.0 * plead, targetLead)
# sub-unity gains (Phase 1) — the region Phase 0 never covered
arms[A_G025].record(rng, wrap180(0.25 * plead - targetLead), 0.25 * plead, targetLead)
arms[A_G050].record(rng, wrap180(0.50 * plead - targetLead), 0.50 * plead, targetLead)
arms[A_G075].record(rng, wrap180(0.75 * plead - targetLead), 0.75 * plead, targetLead)
# the fixed causal per-band rule (Phase 1 hitProxy-argmax curve)
let bg = BandGainTable[bandOf(rng)]
arms[A_BAND].record(rng, wrap180(bg * plead - targetLead), bg * plead, targetLead)
# naive linear
let np = predict(ctx.naive, ctx.st, speed)
let nl = wrap180(bearingDeg(ox, oy, np.x, np.y) - los)
@@ -332,6 +360,75 @@ proc main() =
if g30 < bestV: bestV = g30; best = "3.0"
echo fmt"{BandLabels[b]:<9} {fmt3(g1):>10} {fmt3(g15):>10} {fmt3(g20):>10} {fmt3(g30):>10} {best} ({fmt3(bestV)})"
echo ""
echo "=".repeat(120)
echo "PHASE 1 — THE MISSING GAIN SWEEP: Pattern lead x gain in [0.00, 1.00] (0 = HeadOn, 1 = Pattern)"
echo "=".repeat(120)
echo "Format per cell: mean|err| deg [hitProxy]. hitProxy is the objective. gain 0.0 is HeadOn,"
echo "gain 1.0 is Pattern. The per-band gain table IS causal to APPLY (range is known at fire time,"
echo "so a per-band lookup needs no learning); its ESTIMATION from these same runs is in-sample."
echo ""
var hdrg = "band |req| deg"
for gi in 0 ..< GainValues.len: hdrg.add fmt" g={GainValues[gi]:.2f} [hpx]"
hdrg.add " bestHpx dHpx bestErr"
echo hdrg
echo "-".repeat(hdrg.len)
for b in 0 ..< NBands:
var line = fmt"{BandLabels[b]:<9} {fmt3(meanAbsReq(arms[A_PATTERN].bands[b])):>9}"
var bestHi = 0
var bestHp = -1.0
var bestEi = 0
var bestEr = Inf
for gi in 0 ..< GainValues.len:
let s = arms[GainArmIdx[gi]].bands[b]
let e = meanAbs(s)
let hp = s.hitProxy
line.add fmt"{fmt3(e):>7} [{fmt3(hp)}] "
if hp > bestHp: bestHp = hp; bestHi = gi
if e < bestEr: bestEr = e; bestEi = gi
let patHp = arms[A_PATTERN].bands[b].hitProxy
line.add fmt" {GainValues[bestHi]:.2f} {bestHp-patHp:+.4f} {GainValues[bestEi]:.2f}"
echo line
echo ""
echo "OPTIMAL GAIN CURVE (hitProxy-argmax per band) and its implied hit-probability gain vs Pattern:"
var curve = " gain = [ "
for b in 0 ..< NBands:
var bestHi = 0
var bestHp = -1.0
for gi in 0 ..< GainValues.len:
let hp = arms[GainArmIdx[gi]].bands[b].hitProxy
if hp > bestHp: bestHp = hp; bestHi = gi
curve.add fmt"{BandLabels[b]}->{GainValues[bestHi]:.2f} "
let patHp = arms[A_PATTERN].bands[b].hitProxy
echo fmt" {BandLabels[b]:<9} best gain {GainValues[bestHi]:.2f} hitProxy {bestHp:.4f} vs Pattern {patHp:.4f} => {bestHp-patHp:+.4f} pp"
echo curve & "]"
echo ""
echo "DIRECT COMPARISON — Pattern vs the FIXED causal per-band rule [1,1,1,0.00,0.00] vs BitBrain (learned online):"
let hdrd = "band Pattern hpx fixed-band hpx BitBrain hpx fixed-Pat pp BB-Pat pp"
echo hdrd
echo "-".repeat(hdrd.len)
for b in 0 ..< NBands:
let patHp = arms[A_PATTERN].bands[b].hitProxy
let fixHp = arms[A_BAND].bands[b].hitProxy
let bbHp = arms[A_BB].bands[b].hitProxy
echo fmt"{BandLabels[b]:<9} {patHp:>11.4f} {fixHp:>16.4f} {bbHp:>14.4f} {fixHp-patHp:>+14.4f} {bbHp-patHp:>+10.4f}"
echo "fixed-band hpx = the [1,1,1,0,0] table applied causally; it was selected in-sample."
echo "BitBrain is learned online from labels inside each run (cold start at gain 1.0)."
echo ""
echo "LEAD CORRELATION PER GAIN (Pearson of applied lead with required lead). Pearson is invariant"
echo "under positive scaling, so every g>0 column must be IDENTICAL to Pattern; g=0 has no lead and"
echo "therefore no correlation. If they match, a shrinking gain does NOT add lead information — it"
echo "only shrinks the magnitude of an uninformative signal (the gain-sweep mechanism)."
let hdrc = "band " & " corr(g) "
var hdrc2 = hdrc
for gi in 1 ..< GainValues.len: hdrc2.add fmt" g={GainValues[gi]:.2f}"
echo hdrc2
for b in 0 ..< NBands:
var line = fmt"{BandLabels[b]:<9}"
for gi in 1 ..< GainValues.len:
line.add fmt" {fmt3(leadCorr(arms[GainArmIdx[gi]].bands[b])):>8}"
echo line
echo ""
echo "=".repeat(120)
echo "LEAD INFORMATIVENESS -- capture slope (regression of applied lead on required lead) and lead correlation"