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.
This commit is contained in:
@@ -25,6 +25,9 @@ p=0.69, damage 279 vs 284, survival 17/49 vs 16/49 (p=1.0), ram contacts 1 vs 2.
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Proactive ramming: a negative result (do not re-attempt straight-line pursuit)
|
||||
|
||||
**Question.** The bot declined visible ram opportunities ("it could jump over the
|
||||
enemy and shred it"). The proactive `opportunity` gate was relaxed to fire from a
|
||||
closable range (`dist < 200`, energy lead `> 15`, was `dist < 50`). Did it convert?
|
||||
**No** — a log-replay diagnosis (no new battles), 49 rounds/arm: it fires, never
|
||||
reaches contact.
|
||||
|
||||
## The five measurements
|
||||
|
||||
| # | Diagnostic | Result |
|
||||
|---|---|---|
|
||||
| 1 | **opportunity -> contact** | **0/6** (base), **0/40** (ring), **0/12** (ringhot), **0/1** (nopower) |
|
||||
| 2 | **abort reasons** | `triggerGone` **94/108 (87%)**, `duration` 14, `stuck` **0**, `bulletRain` **0** |
|
||||
| 3 | **closest approach in an opportunity episode** | never below ~80 px; base closest **median 126 px** |
|
||||
| 4 | **ticks spent during ram** | `<40: 10, 40-80: 16, 80-120: 60, 120-200: 165, >=200: 85` |
|
||||
| 5 | **A/B, `oldram` vs `base`** | p=**0.69**, damage **279 vs 284**, survival **17/49 vs 16/49** (p=1.0) |
|
||||
|
||||
The only proactive conversion in the corpus came from a **finisher** (enemy at
|
||||
16 -> 1 energy), not an opportunity. Two smoking guns: a base episode ran the FULL
|
||||
60-tick `duration` cap and closed only **198 -> 171 px**; a perfectly aligned
|
||||
(`delta ~= 0 deg`), full-speed-8 episode closed **195 -> 114 px** then PLATEAUED.
|
||||
|
||||
## Why it cannot work (the geometric reason)
|
||||
|
||||
Both bots have `MAX_SPEED = 8`. A straight-line pursuit can **never** catch an
|
||||
evading equal-speed opponent: every turn spent turning is a turn the evader pulls
|
||||
away, and any juke re-opens the gap. Closing requires **interception** (cutting
|
||||
off the escape path) or **cornering**, not chasing. That is a *movement* problem,
|
||||
and it is disproportionate to **0.6 damage per contact** (one-shot; NOT 0.6/turn).
|
||||
Corroboration: cornering was independently refuted (wall-adjacent enemies are
|
||||
*less* predictable; closest approach to DrussGT in 15 rounds was 118.7 px). Both
|
||||
routes to point-blank have now failed for the same underlying reason.
|
||||
|
||||
## The one exception: the finisher
|
||||
|
||||
The finisher (`enemy < 20 energy`, `dist < 300`, we are healthier) DOES convert,
|
||||
because a **<20-energy DrussGT stops fleeing**: that episode closed at 6-8 px/tick
|
||||
and reached contact. It is kept and is now the **default** gate. The rare
|
||||
`desperation` case (both < 5 energy, `dist < 150`) is kept as a cheap last-ditch
|
||||
play. `opportunity` and `plan` are OFF by default.
|
||||
|
||||
**Re-enable for re-testing:** `TR_RAM_OPPORTUNITY=1` (with `TR_RAM_OPP_DIST`,
|
||||
`TR_RAM_OPP_MARGIN`) restores the proactive gate; `TR_RAM_PLAN=1` restores the
|
||||
change-of-plan trigger. Verified live: the same ModularBot-vs-RamFire battle logs
|
||||
**0 opportunity ON** by default and **4 opportunity ON** with `TR_RAM_OPPORTUNITY=1`.
|
||||
|
||||
## The two dead code paths, dealt with
|
||||
|
||||
1. **`bulletRain` abort — units fixed.** `onHitByBullet` accumulated bullet
|
||||
**firepower**, while `TR_RAM_ABORT_DMG = 0.5`/turn implied a *damage* rate. The
|
||||
server's damage is `calcBulletDamage(p) = 4p` (plus `2*(p-1)` above 1), so the
|
||||
abort was ~4x too insensitive and never fired (max observed `dmgRate` 0.27 in
|
||||
all 108 OFF lines). It now accumulates **real energy** via `bulletDamage()` and
|
||||
the threshold is `TR_RAM_ABORT_DMG = 2.0` **energy/turn** over 15 turns (~30 HP)
|
||||
— the same effective bar for normal firepower, now in correct units. The live
|
||||
run reports `dmgRate=1.07/turn` (was 0.27) and does not abort the finisher.
|
||||
2. **`ramStuckTicks` — removed.** It required `dist < 5 px` on consecutive ticks,
|
||||
but two 18 px radii make contact occur at ~36 px and collision resolution
|
||||
rewinds positions — so the closest approach is ~36 px and the counter could
|
||||
never increment (diagnostic: `stuck 0`). Removed rather than documented as
|
||||
protection it cannot provide; the 60-tick `duration` cap is the only self-abort.
|
||||
|
||||
## Do not re-attempt a proactive straight-line ram
|
||||
|
||||
Do **not** re-relax the opportunity gate, add "get closer" logic to satisfy it, or
|
||||
add a new chase trigger. Any future attempt at forcing point-blank must be an
|
||||
**interception/cornering** movement problem (predict the escape path and occupy
|
||||
it), and must clear the same `opportunity -> contact` bar above. Proactive ramming
|
||||
is worth at most 0.6 damage per contact and measured **zero** outcome change.
|
||||
Reference in New Issue
Block a user