v1.12.0 — fault classifier overhaul (mit-aware primary, strict causality, continuous decay)
The death classifier had three structural weaknesses called out in the fault-improvement review: a binary 5s cliff for cascade detection (#9), over-eager cascade attribution that…
Why
The death classifier had three structural weaknesses called out in the fault-improvement review: a binary 5s cliff for cascade detection (#9), over-eager cascade attribution that lumped any preceding death together regardless of type (#3), and mit-awareness as a downstream T-304 patch rather than primary classification (#4). v1.12.0 fixes all three at the root in _death_kind. The disambiguation pass (T-304) becomes a backward-compat no-op for the normal flow; it stays around for fault_scores rows persisted before this ship.
Changed — _death_kind signature and semantics
Old: _death_kind(killing_ability_id, ability_label, preceding_death_in_window: bool)
New: _death_kind(killing_ability_id, ability_label, cascade_pressure: float, mit_audit_info: dict | None = None)
Three new behaviors for raidwide deaths:
- Mit-aware primary (#4): if a strat plan exists for the killing raidwide's occurrence and mits were missed →
mit_failure(full root weight, 1.0). If the plan fully fired →cascade(heal/mit overwhelm despite plan, weight 0.1). If no plan exists → fall through to the preceding-death heuristic. Means raidwide deaths get classified by the actually-load-bearing signal (mit state) rather than the proxy (was there a recent death). - Strict causality (#3): preceding-death pressure only counts deaths whose killing ability is
raidwideoraoe_party(raid-wounding). A tank dying to a single-target tankbuster no longer makes the next raidwide death "cascade" — those are independent faults. Closes the over-cascading hole called out in the review. - Continuous decay (#9): preceding-death weight decays linearly from 1.0 at t-0 to 0.0 at
PRECEDING_DEATH_WINDOW_MS(5s). Threshold flip atCASCADE_PRESSURE_THRESHOLD = 0.5. Replaces the binary cliff with smooth behavior — but a single death 100ms before still flips cleanly, so the practical "noisy timing" cases are unchanged.
Added — _cascade_pressure() helper
Pure function in analysis/fault_attribution.py. Takes now_ts and a list of (ts, label) preceding deaths, returns the summed decay weight. Reusable + testable in isolation.
Changed — compute_fault_scores_for_fight flow
Loads the T-303 mit audit upfront and builds an ability_id → sorted casts index. For each raidwide death, walks the index backwards from death_ts within RAIDWIDE_DEATH_LOOKBACK_MS = 15_000 to find the cast that most likely killed the player, then passes that occurrence's plan info to _death_kind. Same death→occurrence matching pattern T-304 used; lifted into the primary classifier.
Per-death reasons now carries cascade_pressure (rounded) and mit_audit ({no_plan, missed_count} snapshot) for full transparency on classification choices.
Changed — T-304 disambiguation is now effectively a no-op
Under the new flow, mit_failure is set upstream — T-304's "walk cascades, upgrade to mit_failure when audit shows misses" has nothing to do in the normal pipeline. The function stays for backward compat: fault_scores rows persisted before v1.12.0 still get correct mit_failure attribution if disambiguate_for_fight runs on them. PullDetail's "compute then disambiguate" button now does the work in step 1, with step 2 a confirming pass.
Changed — score formula carries mit_failure × 1.0
Same weight as root, since mit_failure IS the originating fault (the cooldown didn't go out). Constants added: MIT_FAILURE_SCORE = 1.0, RAID_WOUNDING_LABELS = ("raidwide", "aoe_party"), RAIDWIDE_DEATH_LOOKBACK_MS = 15_000.
Changed — fault_scores.reasons JSONB carries mit_failure count
Encounter aggregate (fault_aggregate_for_encounter) carries mit_failure per player. Confidence math counts it as classified (alongside root/cascade/enrage).
UI (web/src/Home.jsx)
"Who's contributing to wipes" table grew a Mit fail column between Roots and Cascades (red when > 0). Updated section description to explain mit_failure semantics.
Tests
- 9 new in tests/test_fault_classifier_v1_12.py: cascade_pressure decay math, strict-causality filter (tankbuster doesn't contribute), pressure summation, threshold-flip behavior, missed-mit → mit_failure directly via T-302, tankbuster-then-raidwide produces two roots not one+cascade.
_death_kindpure tests updated in tests/test_fault_attribution.py to the new signature:False/Trueboolean preceding-death replaced with0.0/0.9cascade pressure; 4 new tests for the mit-aware path (missed/fired/no_plan).- Seeded-fixture tests updated:
test_compute_classifies_root_vs_cascadenow expects 2 roots + 1 cascade (was 1+2) because the leading tankbuster no longer cascades the follow-up raidwides — exactly the strict-causality outcome. - T-304 disambiguation tests rewritten to verify the final state (after full pipeline) rather than asserting T-304 did the upgrade itself. The fixture's missed-mit scenario still produces mit_failure; the path is just upstream now.
- All 4 fault_disambiguation tests pass alongside the new T-302 mit-aware tests, proving the backward-compat no-op behavior works.
- 439 tests passing (426 → 439, +13).