Vigil
← All news

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 += 1 every iteration
  • If this wipe has a FaultScore row AND is past-wall AND has ≥1 root death → bump past_wall_roots, compute repeat_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 player
  • repeat_multiplier_avg: average multiplier across fault-having wipes (informational)
  • raw_score: pre-weighted score for transparency
  • fights now 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 deaths so the amplification is auditable per row
  • Per-name grouping in the table sums past_wall_roots and raw_score across 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).