v1.16.2 — Cross-report player_id collision bugfix
User-caught attribution bug. Their per-job breakdown showed PLD 336 / DRK 203 / WAR 13 for "Aoi Bomber", but they remembered being Paladin almost the whole prog. Empirical check a…
Why
User-caught attribution bug. Their per-job breakdown showed PLD 336 / DRK 203 / WAR 13 for "Aoi Bomber", but they remembered being Paladin almost the whole prog. Empirical check against the Combatant table showed the real distribution was PLD 359 / DRK 24 / WAR 13. The 203 DRK number was fabricated by aggregation bug.
Root cause: FFLogs player_id is report-scoped, not character-scoped. The same numeric pid maps to different characters across different reports. pid=12 specifically was:
- "Aoi Bomber" PLD/DRK in one report (60 fights)
- "Louis Moinet" Dancer in another (51 fights)
- "Zun Lapix" Samurai in another (22 fights)
- "Multiple Players" / "LimitBreak" actor elsewhere (22 fights)
The v1.16.1 fault_aggregate_for_encounter keyed everything by pid alone — so ALL 203 of pid=12's fights got attributed to whichever (name, job) was seen first in name_job_lookup. Result: Aoi Bomber's wipe count inflated by other players' fights, and the per-job breakdown placed them on jobs they never played.
Changed — fault_aggregate_for_encounter keys by (name, server) not pid
analysis/fault_attribution.py. The aggregate now:
- Builds
char_at_fight[(fid, pid)] → (name, server, job)from Combatant rows. - Converts per-fight active pids into per-character attendance:
(name, server) → [(fid, pid)]. - Walks attendance per CHARACTER, not per pid. Per-fight FaultScore lookup still uses (pid, fid), but the per-fight pid is now correct for the character we're aggregating.
- Emits one row per
(name, server)withjobs_breakdown: {job_name: {fights, root, cascade, mit_failure, heal_failure, heal_failure_caused, enrage, unknown, avoidable_damage, damage_downs, score}}nested inside.
Output row schema additions: name, server, primary_job (most-played), player_ids (representative list), jobs_breakdown. Legacy player_id field retained as the first pid for backward compat with the breakdown drill-down.
Changed — fault_breakdown_for_encounter keys by name not pid
analysis/fault_breakdown.py. Joint key switched from (player_id, ability_game_id) to (name, ability_game_id). Also folded the v1.16.1 inferred_ability_id into the joint key so non-attributable deaths get bucketed under the inferred mechanic (was producing a single "ability_id: null" lump).
Changed — Frontend FaultSection reads jobs_breakdown from backend
web/src/Home.jsx. Previously the frontend rebuilt the per-job table by aggregating p.job across player rows — broken when the same pid showed up under different jobs in different reports. Now reads the backend's per-character jobs_breakdown and sums across the merged-group's characters. Also: topMechanicsForPlayerSet and topOffendersForMechanicSet switched from pid-based filters to name-based filters (same root cause).
Tests
- 2 new in tests/test_pid_attribution_v1_16_2.py:
test_same_pid_different_characters_does_not_conflate_attendance: seed pid=42 as Alice (PLD, report A, 5 wipes) AND Bob (Dancer, report B, 3 wipes). Assert Alice has 5 fights / PLD, Bob has 3 fights / Dancer, no cross-contamination.test_same_pid_same_character_multiple_jobs_breakdown: Alice plays 4 PLD + 2 DRK wipes under one pid. Assertjobs_breakdownsurfaces both with correct counts andprimary_job = Paladin.
- 1 test fixed:
test_create_with_aliases_and_listno longer assumes the dev DB starts empty (asserts"Alice" in namesnotlen == 1). - 511 tests passing (509 → 511, +2).
Live AC against dev DSR data
Pre-fix Aoi Bomber row: 552 fights (PLD 336, DRK 203, WAR 13). Post-fix: 396 fights (PLD 359, DRK 24, WAR 13) — matches the ground-truth count from the Combatants table exactly. Aoi Bomberman correct at 49 PLD.
Operator note
Existing FaultScore rows are unaffected. The fix is entirely at read-time in fault_aggregate_for_encounter. No recompute needed — open Home, your numbers correct on next refresh.