v1.16.3 — Cartography uses inference for non-attributable deaths
v1.16.1 wired non-attributable-death inference (cast proximity + cactbot drift) into `compute_fault_scores_for_fight`, but the cartography "What's killing us" view read death even…
Why
v1.16.1 wired non-attributable-death inference (cast proximity + cactbot drift) into compute_fault_scores_for_fight, but the cartography "What's killing us" view read death events raw — so FFLogs-sourceID=-1 deaths still lumped into a single bucket and usually dominated the chart. User asked: "Can we fix non-attributable by attributing it to the closest cactbot mechanic? Just label it as guessed or something."
Added — analysis/death_inference.py
Shared inference module pulled out of fault_attribution. Exports:
INFER_LOOKBACK_MS = 8_000/INFER_CACTBOT_TOLERANCE_MS = 2_500/INFER_ACTIONABLE_LABELSinfer_killer_from_cast_proximity(death_ts, enemy_casts, label_of)— most recent enemy cast within 8s whose type_label is actionableinfer_killer_from_cactbot_drift(death_ts, phase, phase_start, cactbot_entries, drift, label_of)— drift-adjusted cactbot lookup, ±2.5sbuild_phase_drift_map(timeline_diff_result)— phase_index → median_drift_msboss_cast_events(session, fight_id)— enemy-sourced casts for one fightbuild_inference_context(session, fight_id, encounter_id)— pre-loads everything needed forinfer_killerinfer_killer(ctx, death_ts) → (aid, label, source)— two-layer pipeline: cast_proximity first, cactbot_drift fallback
fault_attribution re-exports the prior private names (_infer_killer_from_cast_proximity, etc.) so existing tests keep working.
Changed — cartography_for_encounter uses inference
analysis/cartography.py. For each death with ability_id IS NULL, build a per-fight inference context (lazy-loaded only for fights that actually have non-attributable deaths) and call infer_killer. If a match is found, increment that ability's bucket and add to inferred_deaths. Otherwise the death stays in the non-attributable bucket.
Per-bucket response gained inferred_deaths: int — how many of this row's deaths came from inference (vs real FFLogs attribution).
UI — "(guessed)" pill on cartography rows
web/src/Home.jsx. When a mechanic row has inferred_deaths > 0, a yellow pill appears next to the mechanic name: just guessed if every death in the bucket was inferred, or N guessed showing how many of the total were guesses. Tooltip explains the inference source.
Tests
2 new in tests/test_cartography_inference_v1_16_3.py: cast-proximity attribution end-to-end + fallback to non-attributable bucket when inference can't match. 513 tests passing (511 → 513).
Live AC against DSR data
Top mechanic table before this ship had non-attributable as a giant lump at the top. After:
- Sacred Sever: 156 deaths (70 guessed, recovered from non-attributable)
- Holy Bladedance: 106 deaths (80 guessed)
- Ancient Quaga: 84 deaths (72 guessed)
- Heavensflame: 105 deaths (31 guessed)
Non-attributable residual: 615 deaths. These are real "we can't tell" cases — sub-cast VFX outside both our events table and cactbot's timeline body. Future improvement: harvest cactbot's commented-out #Ability entries (already done for label fallback in v1.4.1) into the drift inference too.