Files
SirRoboGarage/docs/feature_ab_results.md
T
SirStone eb74f9b2e3 Ram: finisher-only by default, and the bullet-rain abort now measures real energy
Follows the diagnosis that proactive straight-line ramming CANNOT work: both bots
have MAX_SPEED=8, so a pursuit cannot catch an evading equal-speed opponent.
Measured over 49 rounds per arm, opportunity -> contact was **0/6** (base), 0/40
(ring), 0/12 (ringhot). The only proactive conversion in the whole corpus came
from a FINISHER, and only because a <20-energy DrussGT stops fleeing (that episode
closed at 6-8 px/tick). Opportunity episodes never got below ~80px; one ran the
full 60-tick duration cap and closed only 198->171px; a perfectly aligned
full-speed one closed 195->114px then plateaued.

CHANGES
- **Finisher-only default.** `finisher` (<20 energy, dist<300, we are healthier)
  and the rare `desperation` (both <5, dist<150) are kept; `opportunity` and the
  speculative `plan` are OFF. Both are env-reenableable with no rebuild:
  `TR_RAM_OPPORTUNITY=1` (tune via TR_RAM_OPP_DIST/MARGIN) and `TR_RAM_PLAN=1`.
  Justification: it removes 100+ non-converting episodes per fixture at zero
  measured loss (oldram vs base was p=0.69, damage 279 vs 284, survival 17/49 vs
  16/49) - and each of those episodes spent up to 60 ticks driving STRAIGHT at
  the enemy, abandoning the mover's dodging and disrupting aim.
- **`desperation` KEPT** deliberately: it is cheap and rare, fires only when both
  bots are nearly dead at short range (a coin-flip where 0.6 contact can decide
  it), and it is not the refuted straight-line pursuit.
- **THE BULLET-RAIN ABORT WAS DEAD CODE AND IS NOW FIXED.** `onHitByBullet`
  accumulated raw bullet FIREPOWER while `TR_RAM_ABORT_DMG = 0.5` was documented
  as a DAMAGE rate - so the bar was implicitly "sum of power > 7.5 over 15 turns"
  and the maximum rate ever observed was 0.27. It now accumulates REAL ENERGY via
  a `bulletDamage(power)` helper matching the server's `4p` / `6p-2` formula, and
  `TR_RAM_ABORT_DMG` defaults to **2.0 energy/turn** (~30 HP over 15 turns):
  "abort an in-progress ram if we take > 2.0 energy per turn". Same effective bar
  for normal firepower, and it can now actually fire - the live run reports
  `dmgRate=1.07/turn` where the old units said 0.27.
- **`ramStuckTicks` REMOVED.** It required `dist < 5px`; contact occurs at ~36px
  (two 18px radii) and position rewind prevents getting closer, so it could never
  increment. Only the 60-tick duration cap can now self-end a ram.

LIVENESS (measured, default config, vs a charging Java RamFire, 3 rounds):
  default              -> `[ram] ON reason=finisher` x3, `reason=opportunity` x0
  TR_RAM_OPPORTUNITY=1 -> `reason=opportunity` x4, `reason=finisher` x2
So the opportunity states DID occur and are suppressed by the new default - the
removal is real, not an arm that never fires. A line also read
`[ram] OFF reason=duration dmgRate=1.07/turn`, confirming the new energy units.

Adds docs/ramming_negative_result.md (70 lines) recording the question, the five
diagnostic answers, the geometric reason, the finisher exception, the two dead
code paths, and an explicit "do not re-attempt a proactive straight-line ram; if
point-blank forcing is ever wanted it is an INTERCEPTION/cornering movement
problem" note - the same pattern that stopped the corpse bug recurring.

Guards: test_ram_decision 40 (was 28), test_gun_harness 39, test_vbullet_metric 11,
test_power_selection 3, test_adaptive_radar 41, test_tfil_ring_weights 24,
test_power_policy 26, test_rack_membership 48, test_selector_tiebreak 19,
test_tm_pattern_registration 20, test_vbullet_admit_gate 12,
acceptance_offline_vs_online 12/12. ModularBot compiles.

Honest note: the abort-threshold fix is a real (tiny) behaviour change, NOT
measured-neutral - it only bites while a finisher ram is under sustained fire,
which is exactly the user's stated wish. The finisher-only removal itself is
measured-neutral per the given A/B.
2026-09-22 08:14:03 +02:00

73 lines
4.1 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.
# The three unmeasured features, finally A/B'd — and a methodology correction
Five arms × 7 runs × 7 rounds (35 real-DrusGT bridge battles, 8 concurrent), ONE frozen binary
built from `git archive HEAD` at `185a32e` (includes the shipped Pattern-only rack), env knobs
only, server-side event sidecar ground truth, exact two-sided permutation tests.
| arm | real % | dmg/run | survival (rounds won) | per-run range | p vs base |
|---|---:|---:|---:|---|---:|
| `base` (shipped) | 10.61 | 284.1 | **16/49** (32.7%) | 8.71–12.18 | — |
| `nopower` (policy off) | 7.88 | 247.1 | 9/49 (18.4%) | 6.51–9.52 | **0.0012** |
| `oldram` (old gates) | 10.78 | 279.4 | 17/49 (34.7%) | 9.67–11.43 | 0.6888 |
| `ring` (`tfil_ring`) | **20.28** | 267.4 | **6/49** (12.2%) | 15.70–23.36 | **0.0006** |
| `ringhot` (ring, original heat) | 11.73 | 285.3 | 15/49 (30.6%) | 10.39–12.45 | 0.0303 |
Every round ends with exactly one death (0 timeouts), so **survival = round win**.
## Verdicts
**1. Power policy — HELPS. Keep.** With it off, real hit rate drops 10.61 → 7.88 (p=0.0012),
damage/run drops 284 → 247, and FEWER rounds are won (16 → 9). The cap trades per-shot damage for
many more shots and a higher per-shot rate, and that trade is a clear win.
**2. Proactive ramming (200px / +15 energy gates) — INDISTINGUISHABLE.** `oldram` vs `base`:
p=0.69, damage 279 vs 284, survival 17/49 vs 16/49 (p=1.0), ram contacts 1 vs 2. The lever is
provably LIVE (6 `opportunity` ON events vs **0** under the old 50px/+30 gates, and `base` reached
<40px on 10 ticks vs 0) — but it converts to essentially no extra collisions and no measurable
outcome change. Safe to keep; it is not earning its keep, and reverting it is equally defensible.
**Follow-up:** a log-replay diagnosis later showed `opportunity -> contact` was **0/59** and the
gate never got below ~80px, so the default is now **finisher-only** and the proactive trigger is
opt-in (`TR_RAM_OPPORTUNITY=1`). See `docs/ramming_negative_result.md`.
**3. Range-weighted ring mover — HURTS the objective. Do NOT ship.** See below; this is the
important one.
## METHODOLOGY CORRECTION: "real hit rate is the only ground truth" is UNSAFE for movement
The ring arm has the best hit rate of anything measured all night — **20.28% vs 10.61% (+9.67pp,
p=0.0006, non-overlapping ranges)**. Read alone, that says "ship it immediately".
It is a confound. The ring halves engagement range (median **460 → 240 px**), which halves round
length (**1542 → 636 ticks**) and shots (**4466 → 1834**). So:
- **damage/run is FLAT**: 284 → 267
- **survival / round-win more than halves**: 16/49 → **6/49** (p=0.0122, i.e. base survives
significantly more)
- the bot dies ~2.4× faster
It is a **glass cannon**: the same damage dealt, twice as many deaths. The hit-rate gain is a
geometric artefact of fighting closer, not an improvement.
**Rule for movement arms: hit rate alone INVERTS the verdict.** A movement change alters range,
shots fired and round length simultaneously, so the objective metrics are **damage/run and
round-win rate (survival)**. Report all three. (For gun/selection arms, where range and round
length are held fixed, real hit rate remains the right ground truth.)
## Heat taming is the knob that moves the tradeoff
Tamed heat (`corridor 5 / wall 10`) is what lets the range weighting pull the bot to ~240px
(20.28% / 6 wins). The original heat (`20/30`) keeps it at ~400px (11.73% / 15 wins, only p=0.0303
above base). So heat taming buys hit rate at the cost of survival — **a knob to keep conservative**,
and the reason the ring is not the default.
Nothing to revert: the shipped movement is already `tfil`, and the ring is opt-in.
## Caveats
- **Only adversary: the real DrussGT jar** (the shim only hosts `jk.mega.DrussGT`). The ring-vs-winning
result should be re-checked on other bots before any final decision.
- `base` (10.61%) is consistent with the committed `onlyPattern` result (9.99%), validating the
frozen binary and pipeline.
- The observability envs (`TR_POWER_LOG`, `TR_RAM_LOG`, `TR_MOVEMENT_LOG`) were enabled uniformly
across all arms; they gate `echo` only and cannot alter decisions.