# Range vs approach: melee is unavailable against DrussGT, so the gun at range is the only lever **Date:** 2026-09-27 · **Job:** j167 · **Branch:** `research/lead-targeting` **Evidence base:** j166 (`worktrees/j166-aim` @ `a5a49bd`), j165 (`fe77056`), j159 (`4a1f3e1`), j165/j151/j152/j154, j160/j163 (`0df7763` / `51bfa57`), j161 (`docs/ram_floor_exhaustion_ab.md:219`). **Instrument for the new numbers below:** `worktrees/j167-ceiling/j167_probe.py` (branch `j167-ceiling`) — pure replay of the recorded corpora (`/tmp/tfil_ab2/out/`, 60 252 of our own scored shots over 140 battles; and `/tmp/firelag_live2/`, 1 700 shots / 1 664 incoming bullets over 4 battles). **No battle, A/B, server or GUI was run for this document.** --- ## 1. The ceiling **Melee is structurally unavailable against DrussGT, and the exhaust/ram line is a niche rather than a lever.** The evidence is two-sided and independent: (a) *our mover's own ruler* — 94% of forced (no-safe-tile) picks happen at range > 300 u and only 6-7% of picks reach the chosen tile at the estimated arrival time, with the destination hot on arrival 35-42% of the time; and (b) *the j166 pursuit probe* — 35 windows × 250 ticks of open-loop kinematics in which **every** steering law is equal-or-worse than doing nothing clever: | steering law (j166) | closing (u/tick) | contact % | TTI (ticks) | |---|---:|---:|---:| | current-position closing | 4.03 | 65.7 | 72.3 | | body/barrel ray | 1.38 | 40.0 | 131.4 | | velocity intercept (degenerate at equal speed) | — | 0 over 2 118 ticks | — | | best case: lag-5 lead | 4.20 | 65.7 | 68.9 | The root cause of the historical **0/59 proactive-ram** result (`docs/ramming_negative_result.md`) is not a bad gate: **DrussGT never let the distance drop.** Per-round minimum distance 152-338 u, median ~490 u, and `frac(dist < 50) = 0.000` in all four recorded rounds. A pursuit that never gets below 152 u cannot make contact, whatever the gate says. It is also not a gun-side problem: **the server never transmits the enemy's gun direction** (`ScannedBotEvent` = `energy, x, y, direction` where `direction` is the BODY heading, plus `speed`; `TurnProcessor.kt:313-323`). There is no aim-based lead, no aim-based dodge and no early warning available. Against DrussGT the body-to-bullet angle has median **90.1 deg**, and the body ray passes within 10 deg of us on **0.0% of 1 794 ticks** — its gun is always on us, its body never is. **Recorded so the idea is not re-proposed:** the j166 lag-5 residue does improve TTI (72.3 → 68.9) and **converts to contact 0% of the time**. A 4% TTI gain with zero contact conversion is noise, not a lead. ### The honest remaining niches for exhaust/ram 1. **An opponent that closes on us.** Ram works whenever the other side comes to us. Nothing here generalises away from that. 2. **A late-round exhaustion when they are already near.** The one conversion ever recorded came from a *finisher* (enemy 16 → 1 energy), which is already the default gate. 3. **Any 2v1+ mode**, where closing dynamics are not symmetric. Against DrussGT specifically none of these will move the score, and `TR_RAM_FLOOR_ENERGY` is under test in j163 — do not duplicate it. --- ## 2. What the ceiling implies **If range is held, the only remaining lever is the gun at range, and the binding numbers are the gun's, not the tile picker's.** The long-range hit rate is **~9-10%** (Pattern live: 12.3% at 300-450 px, 9.2% at 450+; overall 10.5% — `docs/headon_longrange_live.md`), the live hit half-window at 450 px is **`atan(18/450) = 2.29°`** (`docs/gun_campaign.md:59`), and the measured arrival aim error is **16.19° mean-abs at 450+** (`docs/bitbrain_campaign.md:107`, `docs/headon_longrange_live.md:85`). 16.19° is **7× the window**. The tile picker cannot close a 7× gap that sits downstream of the gun. ### New measurement — arrival aim error decomposed (j167, 23 275 shots at 450+ px) Arrival aim error is defined non-circularly: the angle between the fired bearing and the bearing to where the target *actually is* when the bullet arrives (`tof = 20 - 3·power`, so the flight time comes from the power, not from the shot's own geometry). It splits **exactly**, as signed angles, into * **B, the model part** = the error the gun's own lead model leaves behind, and * **C, manoeuvre** = the target's path curvature relative to the constant-velocity extrapolation from the true state at fire time. | band (px) | n | mean|A| | mean|B| (model) | mean|C| (manoeuvre) | sd(B) | sd(C) | corr(B,C) | |---|---:|---:|---:|---:|---:|---:|---:| | 0-100 | 24 767 | 83.36 | 101.56 | 49.38 | 124.5 | 75.9 | −0.65 | | 300-450 | 11 372 | 13.07 | 21.77 | 11.00 | 26.0 | 13.0 | −0.87 | | **450+** | **23 275** | **11.26** | **17.18** | **7.73** | **20.7** | **9.3** | **−0.85** | At 450+ the model part's variance is **2.2× the manoeuvre part's**, and `corr(B,C) = −0.85` means the two largely *cancel* — the net 11.26° is much smaller than either part. **The 16° is a lead-model number, not a dodge number.** Two supporting numbers: a naive constant-velocity extrapolation of a **2-tick-old** position scores 8.45° mean-abs at 450+, and the time-of-flight implied by the shot's own geometry (holding the current velocity) sits a **median 10 ticks short** of the power-derived arrival tick (p10 −18, p90 +31) — i.e. the gun systematically **under-leads in time**, consistent with `docs/lead_capture_by_range.md` (capture 0.135 at 450+). > **Do not read "a stale-CV model scores 8.45°" as "simplify the gun".** This is > exactly the offline-ruler trap that killed HeadOn: the ruler said a no-lead gun > was equal-or-better at 300+ and live it hit **20×/23× less** > (`docs/headon_longrange_live.md`). The corpus is closed-loop — the target's > manoeuvre is a *reaction to our own bullet* — so (B) and (C) are not separable > here, and per `docs/offline_harness_trust.md` (j89: 0/6 on closed-loop) this > instrument ranks per-gun single-tick prediction, it does not predict a live A/B. ### Cross-reference: what is still open in the gun docs | doc | finding | status after this ceiling | |---|---|---| | `docs/gun_campaign.md:59` | hit half-window 2.29° at 450 px; measured signal 4.6-7.6° | **STILL OPEN and now the load-bearing number.** The decomposition says the gap is in the *model*, and the model is systematically 10 ticks short in time-of-flight. | | `docs/gun_campaign.md:40-45` | lead amplitude is dead (1.0/1.5/2.0/3.0 all worse); radial knobs are bearing-invariant by construction | **CLOSED.** | | `docs/gun_campaign.md:737-753` | `len6` +0.49 wins/run (p=0.039, n=15) did not replicate on n=33 | **CLOSED.** | | `docs/bitbrain_campaign.md:189` | BitBrain / TMHorizon corrector adds no measurable aim (16.199 vs 16.193) | **CLOSED.** | | `docs/bitbrain_campaign.md:107` | Pattern's own lead correlation with the required lead is 0.165 at 450+ | **STILL OPEN.** It is the same defect the decomposition names. | | `docs/state_window_gate.md` | single wave-relative state at Q=4 predicts the miss bin at 0.4094 vs 0.2348 majority, but bins are 4.58-7.63° wide | **STILL OPEN, and now the best-placed surviving idea** — it is a *model* correction, which is where the error is. | | `docs/gun_rack_analysis.md:423-455` | the 16-candidate rack ranking A/B found no winner; knobs added, all neutral | **CLOSED** (13 guns, `onlyPattern` shipped). | --- ## 3. Negative-results ledger — mechanisms closed by measurement Do not re-litigate any row. The unit of evidence is the **opponent**. | mechanism | knob / job | headline number | verdict | |---|---|---|---| | Geometry-weighted tile draw | `TR_TFIL_GEO_MODE/TAU`, j152 `38fbc6e`, A/B'd j159 `4a1f3e1` | **−8.83 damage/run, p=0.0061**; wins −0.05, p=0.46; +26.3 px mean distance on 15/15 opponents | **REJECTED.** Default off, stays off. | | The bounded hold | `TR_TFIL_HOLD_MAX_TICKS`, j154 `2223ca6` | mechanism-positive, outcome-null (j146/j153) | **Default off.** No live win. | | The proactive ram | `oldram` vs `base` gate `dist<200` | **p=0.69**, damage 279 vs 284, survival 17/49 vs 16/49; **0/59 opportunity→contact** | **CLOSED** (`docs/ramming_negative_result.md`). | | The aim-based ram | j166 `a5a49bd` | body ray within 10° of us on **0.0% of 1 794 ticks**; body/barrel ray contact 40.0% vs 65.7% for doing nothing clever | **IMPOSSIBLE** — the server never sends gun direction (`TurnProcessor.kt:313-323`). | | Arrival commitment (`tfil`) | j144 `d2005ab` | mechanism-positive, outcome-null | Default off. | | Turn-cost tiebreak among safe tiles | j145 `39c90fd` | real but small mechanism, under-powered outcome null (300 battles, 5 arms) | Default off. | | Field shape (safety) | j146 `de5d02b` | safe-set broken 63.5% → 30.4% offline; live null on damage and wins (375 battles, 5 arms) | **Default off.** | | Corridor bound | j148 `5e213df` `TR_{TFIL,STRAFE}_CORRIDOR_TICKS` | never landed in a live A/B | Untested, not a candidate. | | Ring arrival commitment | j165 `fe77056` `TR_TFIL_RING_COMMIT_ARRIVAL` | reach 0.24% → **3.05%**, picks 5 521 → 525, byte-for-byte default parity over 20 026 ticks, 148 guards | **Mechanism-positive, default off.** The strongest surviving movement mechanism. | | Firing floor / enemy-exhaustion ram | j160 `23bce2d`, A/B'd j163 `51bfa57` | **clean negative**; the offline energy corpus missed the live game by 200× | **Under test in j163 — do not duplicate.** | | Fire-detection lag | j147 `d21f7ce` `TR_FIRE_LAG` | displacement 19.06 → 5.37 px, deadline error 0.99 → 0.06 ticks; **live outcome-neutral**; ceiling ~10% of incoming damage (measured below) | **Default off, permanently.** | | Hard arrival bound | j151 `a01141c` `TR_TFIL_ARRIVE_TICKS` | mechanism-positive, outcome-null | Default off. | > **Methodological caution (j161), binding on everything above.** Pooled tests > can hide real per-opponent effects: j159's safety signal was **p=0.0008 > per-opponent while the pooled test was null** (`docs/ram_floor_exhaustion_ab.md:219`). > **Any future mechanism claim must report per-opponent mechanism metrics, not a > pooled mean.** A pooled null is not evidence of absence; it is evidence that > the heterogeneity was not averaged down. --- ## 4. Lead-time lever 1 — what a 2-tick-stale ghost really costs `TR_FIRE_LAG` back-dates the bullet ghost (default 0). Energy-drop shot detection lags **1.9 ticks mean**; median bullet flight is **19 ticks** (`onHitByBullet` gives 82 hits / 7 421 ticks, one update per ~90 ticks). **Measured on 55 750 incoming bullets** (`/tmp/tfil_ab2/out/`). For each bullet: the time to closest approach of the target's recorded path to the bullet line (**median 9 ticks**, p10 1, p90 39), and the minimum number of ticks of lead time a max-speed hard-turn dodge needs to build 17 px of lateral displacement: | minimum dodge lead time (ticks) | 0 | 1 | 2 | 3 | 4 | 5+ | |---|---:|---:|---:|---:|---:|---:| | share of incoming bullets | **57%** | 33% | 4% | 2% | 1% | 2% | **57% of incoming bullets are already undodgeable at the instant they are fired**, and only **~10%** (need ≥ 2 ticks) are in a regime where a 2-tick detection lag can change anything. Applying the lag to the open-loop dodge model: | ghost lag (ticks) | modelled hits | Δ vs perfect | share of all bullets whose hit/miss verdict flips | |---|---:|---:|---:| | 0 | 22 419 | — | — | | **1.9 / 2** | **25 100** | **+2 681 (+12.0%)** | **10.18%** | | 3 | 26 230 | +14.6% | 14.63% | | 5 | 28 949 | +22.0% | 22.04% | **Verdict: the 1.9-tick lag costs on the order of 10% more incoming hits** — at the measured ~200 damage/run, roughly **20 damage/run**, an order of magnitude below the movement A/B damage MDE. This is consistent with `TR_FIRE_LAG`'s already measured live outcome-neutral result. The ghost is *wrong*, but wrongness at 10% of incoming damage cannot be turned into wins at this sample size. **Recommendation: `TR_FIRE_LAG` stays off permanently.** It is a correctness fix with a measured, bounded, sub-MDE payoff. --- ## 5. Lead-time lever 2 — the 16° decomposed, component by component At 450+ px (23 275 shots), against the 11.26° net arrival error: | component | measured | addressable? | |---|---|---| | **(a) enemy body-gun decoupling** | `\|gun dir − body heading\|` median **89.9°** (p10 25.9, p90 154.0, n=60 928). Extrapolating the target along its **gun** instead of its **body** would put the arrival bearing **79.5° median** wrong. | **Not present, and not addressable.** The intercept model uses the target's *recorded position and velocity*, both of which are the true body quantities and both exactly observed. Body-gun decoupling therefore contributes **exactly 0** to our arrival error. It is fatal for *aim-based* leading and threat warning (j166) and irrelevant to *position-based* leading. | | **(b) our own leading model** | mean|·| **17.18°**, sd **20.7**; implied time-of-flight a **median 10 ticks short** of the power-derived arrival tick | **DOMINANT, and addressable.** This is ~2.2× the manoeuvre variance and it is the whole of the 16°. | | **(c) target manoeuvre between scan and fire** | mean|·| **7.73°**, sd **9.3** | Small relative to (b), and **irreducible** — it is the dodger's own unpredictability, exactly the ~half of the under-lead `docs/lead_capture_by_range.md` attributes to a trivial predictor's own ceiling. | | **(d) gun turn rate / time-to-fire** | the correct solution drifts a **median 0.416°/tick** (p90 5.45). The gun turns at 10°/tick, so a 17° correction takes **1.7 ticks ≈ 0.40°** of drift. | **Not binding.** Contributes ~**0.4°, i.e. ~3% of the 11.26° error.** The gun can always reach the answer; it aims at the wrong answer. | **So the 16° is not (a), not (c) and not (d). It is (b) — the lead model's time-of-flight, short by ~10 ticks.** Caveat, stated once and load-bearing: on a closed-loop corpus (B) and (C) are not cleanly separable, since the target's manoeuvre is a reaction to our own shot; the `corr(B,C) = −0.85` is exactly that confound showing up. The *rank order* (b) ≫ (c) ≫ (d) > (a)=0 is robust to it because (b) and (c) differ by 2.2× in variance and (d) is 3%. --- ## 6. The proposed lever: pre-multiply before learning — PREMISE DEAD The design: aim error is largely a *product* (bearing-rate × time-of-flight), so pre-multiply the two features and feed one small Tsetlin machine. **Measured on the same corpus, the premise does not hold and the experiment should not be built.** `y` = the required lead angle (current bearing → arrival bearing), i.e. exactly the quantity the gun must predict; `b` = the observable 4-tick finite difference of the bearing; `t = 20 − 3·power`. | band (px) | n | corr(**b·t**, y) | corr(b+t, y) | R² additive [1,b,t] | R² product [1,b·t] | held-out side acc, additive | held-out side acc, product | held-out residual rms (deg) | |---|---:|---:|---:|---:|---:|---:|---:|---:| | 0-200 | 24 934 | −0.1020 | −0.1022 | 0.0105 | 0.0104 | 0.579 | 0.580 | 75.98 | | 200-300 | 671 | 0.1190 | 0.0766 | 0.0147 | 0.0142 | 0.488 | 0.487 | 13.70 | | 300-450 | 11 372 | **0.1501** | 0.0299 | 0.0256 | 0.0225 | 0.544 | 0.534 | 8.43 | | **450+** | **23 275** | **0.2833** | 0.1052 | **0.0831** | 0.0803 | **0.603** | **0.601** | **6.68** | | pooled | 60 252 | −0.1005 | −0.1009 | 0.0102 | 0.0101 | — | — | — | *(side accuracy is 2-fold held-out and balanced; the TM record is ~0.47-0.49)* Two things are true and the second kills the idea: 1. **As a single scalar, the product is much the better feature at range**: `corr(b·t, y) = 0.283` vs `corr(b+t, y) = 0.105` at 450+ — 2.7× better, and 5× better at 300-450. So the *premise* ("the error is a product, not a sum") is **confirmed as a statement about correlation**. 2. **But it buys nothing a weight-sum cannot already express.** The best linear additive model on the same two features reaches **R² 0.0831 vs the product's 0.0803**, and the held-out balanced side accuracy is **0.603 (additive) vs 0.601 (product)** — a 0.002 difference, i.e. nothing. Pooled, the two are identical (−0.1005 vs −0.1009; R² 0.0102 vs 0.0101). A TM with two input features **already reconstructs the product term**; the multiplication is what the network was doing anyway. **Even the ceiling is out of reach.** The best held-out residual on the required lead at 450+ is **6.68° rms**, against a live hit half-window of **2.29°** — a 2.9× shortfall. Pre-multiplying does not get a classifier to 2.29°; nothing in this family does. **Do not build it.** The spec is recorded here so the idea is closed on measurement rather than on taste. *(Had it survived, the spec would have been: one TM, ONE input feature `b·t` binarised on sign, plus the 4-bit horizon one-hot as today; offline gate = held-out balanced side accuracy above 0.55 and residual rms below 3° at 450+; live gate = wins/run with CI excluding 0 and sign-flip p<0.05 at 210 runs/arm, damage not detectably down, MDE 0.17 wins/run. Predicted accuracy was 0.60 side accuracy, which is a real signal against the 0.47-0.49 record — and still not close enough to the window to convert.)* --- ## 7. The A/B queue, in priority order, with the MDE honestly restated Throughput **22.7-23.2 runs/min**; movement gate resolved **0.17 wins/run at 210 runs/arm**; the `1/√n` extrapolation to 0.10 wins/run is **607 runs/arm ≈ 1.4 h — a FLOOR on elapsed time, not an estimate**, because opponent heterogeneity does not average down. A null at this sample size **only excludes a LARGE effect.** (j163 additionally measured 14-22 runs/min, not 22.7-23.2, so even the floor is optimistic.) | # | experiment | what it tests | cost | a null would license | |---|---|---|---|---| | **1** | **The lead-model time-of-flight correction** (j167's (b)): re-derive the gun's arrival prediction so the implied flight is the power-derived tick, not 10 ticks short. | The one component that carries 2.2× the error variance at 450+, and the only open axis in `docs/gun_campaign.md` (lead *information*, not amplitude). | Offline gate first: arrival aim error at 450+ must fall below 11.26° mean-abs on held-out battles, ideally <8°; only then 2 arms × 15 opponents × 14 runs = 420 battles ≈ **0.3-0.4 h** wall. | Closing the single open gun axis. Nothing left in the gun. | | 2 | `TR_TFIL_RING_COMMIT_ARRIVAL` (j165, default off) | Whether the largest surviving *movement* mechanism (reach 0.24% → 3.05%, picks 5 521 → 525, 148 guards) converts to wins. | 210 runs/arm ≈ **1.4 h floor**. | Retiring the whole ring/approach programme: if even a 12× reach gain is outcome-null, the ceiling argument is confirmed end to end. | | 3 | `TR_FIRE_LAG` (tfil/strafe, default off) | Nothing worth testing — its ceiling is now measured at **~10% of incoming damage ≈ 20 dmg/run**, below the MDE. | Would be 1.4 h to learn nothing. | Nothing. **Skip it**; the measurement has already answered it. | | 4 | `TR_TFIL_ARRIVE_TICKS` (j151, default off) | Whether a hard arrival bound converts now that the ring is rehabilitated. | 1.4 h. | Retiring it with j151's own null attached. | | 5 | `TR_RAM_FLOOR_ENERGY` (j160, j163) | **Under test in j163. DO NOT DUPLICATE.** | — | — | **Recommendation.** Run **only experiment 1**, and only after the *offline* gate passes; if the offline gate does not move the 450+ arrival error below ~8°, run nothing at all. Given five consecutive nulls or near-nulls (j144, j145, j146, j147, j159) plus a clean negative in j163, spending 1.4 h of live time on experiments 2-4 is not justified — those are mechanism-positive mechanisms whose outcome nulls are already the standing record, and a null there teaches nothing that the ledger does not already say. **"The ceiling is real and we should stop spending on movement" is the answer.** The remaining budget belongs to the gun's lead model, or it is not spent.