Vigil
← All news

v1.4.0 — cactbot Stage 2 (per-pull expected-vs-actual timeline diff)

- **`analysis/timeline_diff.py::timeline_diff_for_fight(session, fight_id)`** computes per-pull expected-vs-actual drift. For each cactbot-annotated canonical mechanic in this enc…

Added — per-pull timeline diff

  • analysis/timeline_diff.py::timeline_diff_for_fight(session, fight_id) computes per-pull expected-vs-actual drift. For each cactbot-annotated canonical mechanic in this encounter's fight_model, finds the closest matching boss-cast event in this pull's events (by ability_game_id within the phase window), reports expected_t_ms, actual_t_ms, drift_ms, and a fired: bool flag. Cosmetic abilities filtered (not actionable). Per-phase median_drift_ms summary surfaces the "everything shifted +Ns after a slow P1" cascade signal in one number.
  • API: GET /api/fights/{fight_id}/timeline-diff — returns the diff payload.
  • React TimelineDiff component added below MitAudit in PullDetail. Collapsible (▸/▾). Per-phase header shows {fired}/{total} fired + median drift colored by magnitude (green ≤500ms, yellow ≤2s, red >2s or missing). Each row: mechanic name, type, expected time, actual time, drift. Missing mechanics highlighted with a pink row + "did not fire" cell.
  • 8 new unit tests (tests/test_timeline_diff.py): unknown fight, empty fight, fired-with-drift, missing mechanic, cosmetic filtered, multi-cast closest-match, no fight_model, per-phase median drift. 334 tests passing (326 → 334).

Live AC — FRU kill fight 1500

P1: median drift +1.2s   (clean, normal cast-fire offset)
P2: median drift +10.9s  ← cascade: P1 ran long, all P2 mechanics shifted +10.9s
P3: median drift -14.0s  (P3 mechanics earlier than expected)
P4: median drift +7.8s
P5: median drift +11.0s

The "Phase 1 ran +Ns long → all of P2 shifted late" diagnostic surfaces directly as a single per-phase number. Working as designed.

Known limitations (Stage 2.1 candidates)

  • Multi-cast assignment. When an ability fires N times in a pull and has M fight_model rows, each row picks the closest cast independently — competing rows can both pick the same cast or pick distant casts. Visible on FRU P1 "Sinsmoke" showing +42.8s drift (algorithm picked the 2nd cast for what should match the 1st). Hungarian-style optimal assignment would fix it.
  • Adds-phase abilities in FRU phase 2 don't get cactbot labels — phase index alignment between T-103 and cactbot timelines isn't always perfect for short transition phases. Not blocking the headline cascade-drift signal.