v1.14.6 — repeat-offender amplifier for past-wall root deaths
After v1.14.5's prog-relevance de-weighting, a player who repeatedly causes wipes in early phases (already-cleared content) was getting a SCORE BREAK they shouldn't get. User aske…
Why
After v1.14.5's prog-relevance de-weighting, a player who repeatedly causes wipes in early phases (already-cleared content) was getting a SCORE BREAK they shouldn't get. User asked for an exponential penalty on this pattern, rate-based so a 1000-wipe static doesn't get the same curve as a 100-wipe static.
Added — repeat_offender_multiplier(past_wall_roots, total_wipes_attended)
analysis/fault_attribution.py. exp(K × rate) capped at REPEAT_PENALTY_CAP = 5.0. K=4 means:
- 1% rate (1/100) → 1.04×
- 5% rate (5/100 or 50/1000) → 1.22×
- 20% rate → 2.23×
- 40% rate → cap at 5.0×
The rate-based curve self-scales by attendance — 5 offenses in 1000 wipes (0.5%) gives ~1.02× while the same 5 in 100 wipes (5%) gives 1.22×. Different curves for different static sizes, as requested.
Changed — fault_aggregate_for_encounter now uses attendance, not just FaultScore rows
Old behavior: b["fights"] += 1 per FaultScore row meant a player who attended 200 wipes but only had faults in 30 showed Wipes: 30. Wrong denominator for per-wipe rates AND for the repeat-offender rate.
New behavior: walks active-player data (_active_players_by_fight) per fight to establish ATTENDANCE. Then for each player's attendance-ordered wipe list:
total_wipes += 1every iteration- If this wipe has a FaultScore row AND is past-wall AND has ≥1 root death → bump
past_wall_roots, computerepeat_mult = repeat_offender_multiplier(past_wall_roots, total_wipes), multiply into score - Otherwise multiplier stays 1.0 (no amplification)
Time-ordered so each successive offense's multiplier reflects the rate at that point in time. No retroactive amplification of prior offenses — fits the user's "more wipes you cause, more penalty going forward" intent.
Changed — API response carries new fields
past_wall_roots: cumulative count per playerrepeat_multiplier_avg: average multiplier across fault-having wipes (informational)raw_score: pre-weighted score for transparencyfightsnow reflects actual attendance, not just fault-having wipes
UI (web/src/Home.jsx)
- Section copy explains all four multipliers (phase, within-phase, prog relevance, repeat amplifier) with hover tooltips on each
- Score column tooltip shows
raw: X · N past-wall root deathsso the amplification is auditable per row - Per-name grouping in the table sums
past_wall_rootsandraw_scoreacross the player's multiple FFLogs ids
Tests
- 10 new in tests/test_fault_repeat_offender_v1_14_6.py: rate-based math, cap behavior, 100-wipe vs 1000-wipe comparison, exponential growth verification, serial offender end-to-end, no-past-wall = no-amp baseline.
- 1 fixture updated in tests/test_fault_attribution.py to add WatchedReport (the new aggregate scopes attendance via watchlist).
- 1 test reframed in tests/test_fault_phase_weighting_v1_14_5.py: the original "de-weighting" test had only 2 wipes which hit the repeat cap; reframed with many no-offense wipes diluting the rate so de-weighting is observable in isolation.
- 480 tests passing (471 → 480, +9).