# 11. Anti-Abuse Safeguards

[< Back to Index](README.md) | [Next: API Endpoints >](12-api.md)

---

## 11.1 Milestone Threshold Tuning

The primary anti-abuse lever is milestone threshold configuration. Thresholds defined in
`evolution_milestone.threshold` are tunable by admin without code changes. If data shows
milestones being completed too quickly (e.g., full T4 in under a week), admin raises thresholds.
If players stall at a specific milestone, admin can lower the threshold. Already-credited
completions are not retroactively revoked.

## 11.2 Game Mode Diversity Requirements

"Win games" milestones do not specify a mode and can be completed in Exhibition (vs AI). This is
intentional — exhibition games are still real games with real D20 outcomes. Gauntlet and campaign
milestones specifically cannot be completed in exhibition, creating incentive to engage with
competitive and narrative modes.

If exploitation is observed (e.g., players spinning up exhibition games specifically to complete
milestones without meaningful play), a safeguard can restrict "win games" milestones to campaign,
gauntlet, or unlimited modes via the `game_mode` field on `evolution_milestone`.

## 11.3 Evaluation Deduplication

The post-game evaluator must be idempotent. If the game pipeline retries a post-game callback,
progress counts should not double. The evaluator recalculates current counts by re-querying the
source tables (not by incrementing cached values) and compares against the stored `current_count`.
Progress is updated only when the newly computed count exceeds the stored count.

## 11.4 Rarity Cap Enforcement

A fully evolved card receiving a rarity upgrade cannot exceed HoF (the maximum rarity). If a Sta
card completes T4, it becomes All-Star. If a card is already HoF, it receives the T4 boost deltas
but no rarity change. This is enforced at application layer by checking `player.rarity_id`
against the HoF ceiling before incrementing.

**Rating cap enforcement:** Individual stat columns have hard caps (e.g., hold rating at -5).
The `apply_evolution_boosts()` function truncates any delta that would exceed a cap and
discards the excess (no redistribution — see [05-rating-boosts.md](05-rating-boosts.md)).
This is an intentional soft penalty for cards already near their ceiling.

## 11.5 Evaluation Performance

Milestone progress is resolved via `player_season_stats` (a materialized view of aggregate
stats), not raw `stratplay` table scans. Each post-game evaluation is a simple `SELECT` keyed
on `(team_id, player_id)` — lightweight and independent per team. Concurrent games across
different teams do not contend since evolution state is keyed by `(player_id, team_id)`.

The post-game callback should only evaluate cards that participated in the just-completed game,
not the entire roster.
