Vigil
← All news

v1.11.0 — fault signals expanded: avoidable damage, Damage Down, confidence

T-302 fault attribution was death-only. Per PLAN §3 Invariant 5 the spec was always "**killing-blow ability + avoidable damage-taken** as primary signals; **Damage Down** as a sec…

Why

T-302 fault attribution was death-only. Per PLAN §3 Invariant 5 the spec was always "killing-blow ability + avoidable damage-taken as primary signals; Damage Down as a secondary survive-fault flag" — we shipped the killing-blow half and ignored the rest. Result: silent contributors (players who eat 6 avoidables and survive because healers top them, or get Damage Down 4× across a wipe without dying) were invisible. v1.11.0 closes that gap.

Added — survive-fault signals

  • _avoidable_damage_by_player() (analysis/fault_attribution.py): per-player sum of damage taken from abilities labeled tankbuster in fight_model where the target's job isn't a tank. The clear-cut case; aoe_party stays unscored until v1.14.0 strat-aware ship can disambiguate body-check expected-targets. damage events only (excludes calculateddamage to avoid double-count).
  • _damage_down_count_by_player(): per-player count of applydebuff events whose ability has the T-108 damage_down label. Skips refreshdebuff (re-stack of the same application — only count the moment of the botch). Generic Damage Down + encounter-specific damage-downs both feed in via the label, not a hardcoded ID.
  • New job-role table at analysis/_jobs.py: TANK_JOBS/HEALER_JOBS/MELEE_DPS/PHYS_RANGED_DPS/CASTER_DPS frozensets + role_of(job) helper. Used here for tankbuster avoidability; reused by v1.14.0 body-check ship.

Changed — composite score formula

Score is now roots × 1.0 + cascades × 0.1 + damage_downs × 0.5 + min(avoidable / 100k, 5.0) where the avoidable component is capped at 5.0 per fight so one extreme hit can't dominate. Constants DAMAGE_DOWN_SCORE = 0.5, AVOIDABLE_DAMAGE_PER_POINT = 100_000, AVOIDABLE_DAMAGE_SCORE_CAP = 5.0 — all tuneable, all documented inline.

Changed — survivors now appear in fault_scores

Before v1.11.0, a player who never died had no fault_scores row even if they ate 6 avoidables and got Damage Down twice. Now the per-fight aggregator unions players-with-deaths and players-with-survive-faults, so a healer who botched two body-checks shows up on the Home table.

Changed — confidence (classified_fraction) on per-player rows

Per PLAN §9 M-FAULT "(c) emit fault_scores (score + reasons), aggregated weekly. Never a single-name verdict." — v1.11.0 makes the partial-knowledge case visible. Per-player: (root+cascade+enrage) / total_deaths. Encounter aggregate carries the cumulative ratio. UI renders a Conf column color-graded (green ≥80%, yellow ≥50%, red below) so a player whose 5 deaths are mostly unknown shows as low-confidence — pushes the user toward the T-108 review queue rather than blaming on a guess.

Changed — fault_scores.reasons JSONB shape

New keys: avoidable_damage (int), damage_downs (int), death_score / avoidable_score / damage_down_score (rounded contributions to the composite score), classified_fraction (float or null). Existing keys preserved. No migration needed (JSONB).

UI (web/src/Home.jsx)

"Who's contributing to wipes" table now has 9 columns: Player · Job · Roots · Cascades · Avoidable · DD · Conf · Wipes · Score. Empty values render as so a clean player doesn't look noisy. Avoidable rendered as 500k style; Conf as color-graded percentage. Updated the section description to explain all three new signals + nudge users at the Abilities review queue when confidence is low.

Tests

  • 7 new in tests/test_fault_signals_v1_11.py: tankbuster-to-non-tank surfaces; tankbuster-to-tank doesn't; Damage Down count; survivor-with-survive-fault appears; classified_fraction reflects unknown deaths; composite score blends three signals; encounter aggregate carries new fields.
  • All existing 15 fault attribution + 4 fault disambiguation tests pass unchanged — the new signals are additive.
  • 426 tests passing (419 → 426, +7).

Note on disambiguation interaction

T-304 disambiguation rewrites score and reasons for fights where a cascade becomes mit_failure. That code path also needs to know about the new keys; verified that the existing disambiguation tests still pass because the keys it doesn't touch flow through unchanged. v1.12.0 (classifier overhaul) will integrate the mit-aware classification more deeply.