v0.29.0 — ### Added — T-302 M-FAULT strat-aware fault attribution
- `analysis/fault_attribution.py::compute_fault_scores_for_fight()` walks deaths in one fight and classifies each as **root** / **cascade** / **enrage** / **unknown** based on the…
analysis/fault_attribution.py::compute_fault_scores_for_fight()walks deaths in one fight and classifies each as root / cascade / enrage / unknown based on the killing ability'stype_labelfromfight_model(T-203):tankbuster/aoe_party→ root (the targeted player failed)raidwide→ root if it's the first death, cascade if a preceding death sits within 5s- non-attributable (FFLogs sourceID=-1 / null ability) → cascade
enrage→ enrage (boss DPS check)
- Writes per-(fight, player) rows to
fault_scores(already in schema from T-003).score= roots × 1.0 + cascades × 0.1.reasonsJSONB carries the per-death breakdown. classify_wipe_type()categorizes whole fights askill/enrage_dps/body_check/mechanics/mixed— headline for T-307 Discord reports.fault_aggregate_for_encounter()aggregates across all our wipes per encounter, per player — the "weekly summary" view.- API:
POST /api/fights/{id}/fault-scores/compute,GET /api/fights/{id}/fault-scores,GET /api/fights/{id}/wipe-type,GET /api/encounters/{id}/fault-aggregate. - React
FaultScorespanel inPullDetail— colored score column (red ≥1.0, orange ≥0.5), per-player root/cascade/unknown counts, compute/re-compute button. - 15 new tests (7 pure-function
_death_kindbranches + 8 end-to-end including live M5S AC). 246 tests total. - Live AC on M5S wipe #75 (9 deaths): classified as
mechanicswipe type, 0 root / 7 cascade / 2 unknown. The high unknown-count is honest: M5Sfight_modelonly has 18 canonical abilities (sparse consensus on a short fight), so many killing abilities lack labels. Confirming more labels via the T-108 review queue tightens this directly. T-303 mit audit + T-304 disambiguation will improve cascade classification further.