j134 ledger: propagated fire fix to all 5 movers via one shared fire_tracker (TR_FIRE_FIX), per-mover catch table 98.888->100, live tick alignment measured (correction belongs on event_tick+2)
This commit is contained in:
@@ -2540,3 +2540,110 @@ the fix.
|
|||||||
**Status: the shipped movement default is UNCHANGED (`TR_MOVEMENT=strafe`); the
|
**Status: the shipped movement default is UNCHANGED (`TR_MOVEMENT=strafe`); the
|
||||||
detector fix is ON by default behind `TR_STRAFE_FIRE_FIX` (revert with
|
detector fix is ON by default behind `TR_STRAFE_FIRE_FIX` (revert with
|
||||||
`TR_STRAFE_FIRE_FIX=0`).**
|
`TR_STRAFE_FIRE_FIX=0`).**
|
||||||
|
|
||||||
|
## Fire fix propagated to all movers
|
||||||
|
|
||||||
|
Job **j134** (2026-09-26; commits `6ad5d99`, `8827338`). Scope: ModularBot /
|
||||||
|
modules / tuning / the test harness only.
|
||||||
|
|
||||||
|
### The shared helper — one implementation, not five
|
||||||
|
|
||||||
|
The j133 detector was fixed **only in `strafe.nim`**; the other four movers kept
|
||||||
|
their own copy of the same broken `0.09 <= drop <= 3.01` classifier. Four copies
|
||||||
|
is exactly how the bug survived, so the fix now lives **once** in
|
||||||
|
`common_libs/movement_harness/fire_tracker.nim` (`FireTracker`). Each mover owns
|
||||||
|
its own wave geometry and spawn code but calls `m.fire.detect(id, energy, lo,
|
||||||
|
hi, fix)`; the mover supplies its **shipped window** (`0.09..3.01` for tfil /
|
||||||
|
tfil_ring / strafe / learned, `0.1..3.0` for surf) so the fix-off path is the
|
||||||
|
old code exactly, and its own switch so the tracker holds **no** enable flag.
|
||||||
|
|
||||||
|
One global switch, `TR_FIRE_FIX` (default **ON**), gates every mover; STRAFE also
|
||||||
|
still honours `TR_STRAFE_FIRE_FIX` (j133 back-compat) and is on only when **both**
|
||||||
|
are on. Both names are registered in `env_report.nim` + `knownEnvNames()`
|
||||||
|
(`TR_FIRE_DIAG`, the live trace, is registered too).
|
||||||
|
|
||||||
|
`ModularBot.nim` forwards both events to **every** mover (`onHitByBullet` ->
|
||||||
|
`e.bullet.power`; `onBulletHit` -> `e.damage`); previously only STRAFE received
|
||||||
|
them.
|
||||||
|
|
||||||
|
### TASK C — per-mover catch rate (offline, 70-battle corpus, 67 065 true enemy fires)
|
||||||
|
|
||||||
|
Every mover now calls the same `FireTracker`; only the window differs, so the
|
||||||
|
fixed rate must be (and is) identical. The SHIPPED rate differs by one shot for
|
||||||
|
`surf` because its window is `0.1..3.0` vs `0.09..3.01`. Full report:
|
||||||
|
`common_libs/tests/fixtures/strafe_fire_catch_report.txt` (regenerated by
|
||||||
|
`common_libs/tests/measure_strafe_fire_catch.py`, extended with the per-mover
|
||||||
|
table).
|
||||||
|
|
||||||
|
| mover | window | shipped | fixed | blind before | blind after |
|
||||||
|
|---|---|---:|---:|---:|---:|
|
||||||
|
| tfil | 0.09–3.01 | 0.98888 | **1.00000** | 746 | **0** |
|
||||||
|
| tfil_ring | 0.09–3.01 | 0.98888 | **1.00000** | 746 | **0** |
|
||||||
|
| strafe | 0.09–3.01 | 0.98888 | **1.00000** | 746 | **0** |
|
||||||
|
| learned | 0.09–3.01 | 0.98888 | **1.00000** | 746 | **0** |
|
||||||
|
| surf | 0.10–3.00 | 0.98886 | **1.00000** | 747 | **0** |
|
||||||
|
|
||||||
|
**Every mover is at 100%.** No mover was left unfixed. The shipped path is still
|
||||||
|
byte-identical with the switch off: `test_tfil_commit_env.nim` replays the
|
||||||
|
15 000+-tick TFIL trajectory against the pre-change golden with `TfilFireFix =
|
||||||
|
false` and still matches every call/speed/turnRate/target/commitTicks.
|
||||||
|
|
||||||
|
### TASK B — live tick alignment (this is where the fix was wrong, and fixed)
|
||||||
|
|
||||||
|
One real 7-round battle, strafe, vs `/tmp/tr_bots/WaveSurferGF`, with the
|
||||||
|
env-gated `TR_FIRE_DIAG=1` trace (kept; default off). **MEASURED:** the server
|
||||||
|
emits the hit event on turn N but applies the energy change to turn **N+1**'s
|
||||||
|
reading, and the bot's event handler runs with `bot.tick = getTurn - 1`. So the
|
||||||
|
correction must land on the reading **two `bot.tick`s after** the event, not the
|
||||||
|
next one. Paired post-fix lines (verbatim):
|
||||||
|
|
||||||
|
```
|
||||||
|
[firediag] EV dmg tick=62 getTurn=63 damage=4.0
|
||||||
|
[firediag] READ tick=64 raw=4.0 bonus=0.0 dealt=4.0 <- correction on the reading that carries the +4.0 drop
|
||||||
|
[firediag] EV hit tick=109 getTurn=110 power=1.2437
|
||||||
|
[firediag] READ tick=111 raw=-3.731198... bonus=3.731198... dealt=0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Before this job the correction was applied on the **immediately next** reading:
|
||||||
|
on the same battle that put `bonus=5.803` on a reading with `raw=0.0` (a
|
||||||
|
spurious wave) while the real `raw=-5.803` gain one tick later was left
|
||||||
|
uncorrected — i.e. j133's fix was **correct in the offline model but mis-timed
|
||||||
|
live**. Fixed by a one-slot double buffer in `FireTracker` (`incoming` -> rotated
|
||||||
|
`pending` at `endScan`), which makes the live path agree with the corpus model.
|
||||||
|
|
||||||
|
Aggregate over the whole trace: enemy-hit corrections on the reading of
|
||||||
|
`event_tick+2` **44 aligned, 8 events ended a round with no later reading, 3
|
||||||
|
misaligned (two simultaneous hits in one turn, matched as one sum)**; our-damage
|
||||||
|
corrections **52 aligned, 6 round-boundary, 0 misaligned**.
|
||||||
|
|
||||||
|
### Guard tests + clean-archive compile
|
||||||
|
|
||||||
|
`git archive HEAD | tar -x` into a clean dir, then:
|
||||||
|
`ModularBot` compiles; `test_strafe_fire_fix` **14/14**, `test_tfil_commit_env`
|
||||||
|
**30/30**, `test_tfil_ring_weights` **24/24**, `test_wavesurfer_velocity`
|
||||||
|
**7/7**, `test_learned_surfer` **24 checks / 0 failures** — **99 checks, 0
|
||||||
|
failures**.
|
||||||
|
|
||||||
|
### Direct answers
|
||||||
|
|
||||||
|
1. **Are all movers now at 100% catch?** **Yes.** tfil, tfil_ring, strafe,
|
||||||
|
learned, and surf all go 0.98888 (or 0.98886 for surf) -> **1.00000** on the
|
||||||
|
67 065-fire corpus; each was blind to 746/747 shots, now 0.
|
||||||
|
2. **Is the live tick alignment confirmed?** **Yes — and it was NOT same-turn.**
|
||||||
|
The server applies the energy change one turn after the event, so the
|
||||||
|
correction is applied on the reading two `bot.tick`s after the event; 44/47
|
||||||
|
in-window hit corrections and 52/52 in-window damage corrections land on the
|
||||||
|
exact reading that carries the change (the rest are round boundaries or
|
||||||
|
simultaneous events). The j133 guess that "the reading is same-turn and the
|
||||||
|
corpus +1 is a capture artifact" was **wrong**; the fix now encodes the
|
||||||
|
measured lag.
|
||||||
|
|
||||||
|
### MEASURED vs INFERRED
|
||||||
|
|
||||||
|
**MEASURED:** the per-mover catch table on 67 065 fires; the live paired
|
||||||
|
event/reading lines and the `event_tick+2` alignment counts from one real
|
||||||
|
7-round battle; the two-buffer fix; the clean-archive compile; 99/99 guard
|
||||||
|
checks. **INFERRED:** that the correction size (`3*power`, `damage`) is
|
||||||
|
unchanged — it is read straight from the server events, not re-derived.
|
||||||
|
**NOT MEASURED:** the live movement/damage effect of the fix (the change is
|
||||||
|
~1.11% of fires, far below any panel's MDE; no panel was run, per scope).
|
||||||
|
|||||||
Reference in New Issue
Block a user