# Empirical Challenge Report: Milestone M2 Progression & Combat Integration

- **Agent**: `challenger_m2_progression_2`
- **Role**: critic, specialist
- **Working Directory**: `c:\Projects\FreeExile\.agents\teamwork\challenger_m2_progression_2`
- **Recipient**: `orchestrator_4` (`6f4a2aa2-4315-4660-8cb7-8352a7220c95`)
- **Date**: 2026-10-01T03:08:00Z
- **Verdict**: **REQUEST_CHANGES**

---

## 1. Observation

A standalone empirical challenge harness (`challenge_combat_progression.py`) was constructed and executed against the implementation files `server/world/combat_engine.py`, `server/world/level_progression_service.py`, and `server/world/level_progression_types.py`.

### A. Execution Command and Verbatim Output
Command executed:
```powershell
python .agents/teamwork/challenger_m2_progression_2/challenge_combat_progression.py
```
Output:
```text
======================================================================
EMPIRICAL CHALLENGER: Milestone M2 Combat Progression Integration
======================================================================
[CHALLENGE_1] Testing Fatal Damage Dispatch mechanics...
[CHALLENGE_1] PASS: Fatal Damage Dispatch verified.
[CHALLENGE_2] Testing Callback Invocation and Payload Integrity...
[CHALLENGE_2] PASS: Callback Invocation verified.
[CHALLENGE_3] Testing Progression Service Hook Integration...
[CHALLENGE_3] PASS: Progression Service Hook verified.
[CHALLENGE_4] Benchmarking 1000 fatal combat calculations with progression hooks...
[CHALLENGE_4] EXP Award Latency: mean=0.0119ms, p50=0.0112ms, p95=0.0168ms, p99=0.0252ms, max=0.0599ms
[CHALLENGE_4] Death Penalty Latency: mean=0.0103ms, p99=0.0176ms
[CHALLENGE_4] PASS: Latency Performance & SLA verified.
[CHALLENGE_5] Simulating 10 concurrent player IDs for state isolation...
[CHALLENGE_5] PASS: Multi-player isolation verified across 10 players and 500 interleaved events.
[CHALLENGE_6] Testing Adversarial Edge Cases and Failure Modes...
======================================================================
CHALLENGE SUITE RESULTS:
  - fatal_damage_dispatch: PASSED
  - callback_invocation: PASSED
  - progression_hook: PASSED
  - latency_sla: PASSED
  - multi_player_isolation: PASSED
  - adversarial_vulnerabilities_checked: PASSED

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
EMPIRICAL VULNERABILITIES & DEFECTS DETECTED:
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  * ADVERSARIAL_OBSERVATION: Striking an already-dead actor (current_hp == 0.0) re-evaluates is_fatal=True because current_hp <= 0.0 condition is met again.

  * BUG_FINDING: LevelProgressionService.apply_death_penalty returns early at line 299 for player.level >= 100 without incrementing deaths_count or dispatching _death_listeners. Expected deaths_count=1, got 0.

  * VULNERABILITY [HIGH/CRITICAL]: Corpse Multi-Hit EXP Duplication! Striking dead goblin (HP=0) awarded additional 25 EXP (Total: 25 -> 50). Root cause: CombatEngine evaluates is_fatal=(defender.current_hp <= 0.0) without checking if defender was already dead (was_alive = current_hp > 0.0).

  * VULNERABILITY [HIGH]: Multi-Hit Channeled Death Penalty Drain! Player struck 3 times by same barrage suffered 3 deaths, losing 45% EXP instead of 15% for a single death sequence.

  * DEFECT [MEDIUM/HIGH]: Level 100 Player Death Silenced! deaths_count=0 (expected 1), listener_called=False (expected True). Root cause: LevelProgressionService.apply_death_penalty lines 298-299 returns early before updating player state or dispatching listeners.

======================================================================
CHALLENGER VERDICT: REQUEST_CHANGES
======================================================================
```

### B. Specific Code Observations

1. **Vulnerability 1 — Corpse Multi-Hit & Duplicate Fatal Dispatch**:
   In `server/world/combat_engine.py`, lines 174-175:
   ```python
   defender.current_hp = max(0.0, defender.current_hp - final_damage)
   is_fatal = (defender.current_hp <= 0.0)
   ```
   When `defender.current_hp` is already `0.0` (i.e. already dead), any subsequent attack packet, multi-hit projectile, or channeled AoE evaluated against this actor satisfies `(0.0 <= 0.0)`, marking `is_fatal = True` and re-triggering `on_fatal_damage`.
   - Empirically verified:
     - Player kills monster: receives 25 EXP.
     - Player strikes dead monster again: receives another 25 EXP (`exp: 25 -> 50`). An attacker can gain infinite EXP by repeatedly hitting dead corpses.
     - Monster strikes dying player with 3 hits of a channeled barrage: player suffers 3 consecutive death penalties (`deaths_count = 3`), draining 45% of Level 95 EXP (`15% * 3`) in a single combat sequence.

2. **Vulnerability 2 — Level 100 Death Telemetry & Listener Silencing**:
   In `server/world/level_progression_service.py`, lines 297-299:
   ```python
   player = self.get_player_state(player_id)
   if player.level >= 100:
       return self._build_death_result(player, 0, 0.0, player.current_exp)
   ```
   When `player.level >= 100`:
   - The method immediately returns without creating a new `PlayerProgressionState` with `deaths_count = player.deaths_count + 1`.
   - `self._players[player_id]` is not updated.
   - `for listener in self._death_listeners: listener(result)` is completely skipped.
   - In contrast, for Levels 1-60 (which also have a 0.0% penalty ratio), `deaths_count` increments and listeners are properly dispatched.

3. **Performance SLA Benchmark**:
   - 1,000 fatal combat calculations with EXP award: Mean latency = `0.0119ms` (11.9 µs), p95 = `0.0168ms`, p99 = `0.0252ms`, Max = `0.0599ms`.
   - 500 fatal combat calculations with Death Penalty: Mean latency = `0.0103ms` (10.3 µs), p99 = `0.0176ms`.
   - Performance SLA requirement: Well within the 25ms SLA budget (< 5ms target, actual < 0.03ms p99; >800x faster than budget).

4. **Multi-Player State Isolation**:
   - 10 concurrent player IDs (`player_01` to `player_10`) across 500 interleaved, randomized events (kills, deaths, non-fatal hits).
   - Each player's death count, level, and current EXP matched deterministic tallies with 0 state leakage across player boundaries. `PlayerProgressionState` frozen dataclass immutability was confirmed.

---

## 2. Logic Chain

1. **From Observation 1 (Corpse Multi-Hit)**:
   In `CombatEngine.calculate_damage`, `is_fatal` is evaluated strictly on the post-damage HP without checking whether the defender was alive prior to the strike (`was_alive = (defender.current_hp > 0.0)`).
2. Consequently, whenever an actor that is already dead (`current_hp == 0.0`) receives another attack, `is_fatal` evaluates to `True`.
3. Because `attach_progression_service` hooks `on_fatal_damage`, every subsequent strike against a dead monster triggers `award_monster_exp` again. This constitutes a Critical game economy exploit (corpse-hitting infinite EXP generator).
4. Similarly, any multi-hit boss attack against a player triggers `apply_death_penalty` on every hit that connects while the player is at 0 HP, multiplying the intended death penalty (e.g. 15% -> 45% or 75%) and inflating `deaths_count`.
5. **From Observation 2 (Level 100 Death Silencing)**:
   In `LevelProgressionService.apply_death_penalty`, lines 298-299 bypass state persistence and listener notification specifically for `player.level >= 100`.
6. Levels 1-60 also have a 0% death penalty ratio, but lines 301-322 properly increment `deaths_count` and notify all registered `_death_listeners`.
7. Because level 100 returns early, death telemetry for Level 100 players is permanently lost, and external listeners (such as zone respawn controllers, death broadcast loggers, or achievement trackers) are never triggered when a Level 100 character dies.

---

## 3. Caveats

1. **Zone Engine Cleanup**: In full game execution, dead monsters may eventually be pruned by `ZoneEngine` / `MonsterSystem`, but in combat tick resolution (e.g. simultaneous AoE or multi-projectile volleys hitting within the same frame before zone cleanup occurs), corpse-hitting is guaranteed to happen unless guarded at the engine level.
2. **Review-Only Role**: Per Teamwork constraints, the challenger agent does not directly alter production implementation files (`combat_engine.py`, `level_progression_service.py`), but provides the exact verified remediation specifications for the worker.

---

## 4. Conclusion

**Verdict: REQUEST_CHANGES**

Milestone M2 has demonstrated outstanding latency performance (11.9 µs vs 25ms SLA) and clean multi-player isolation across 10 concurrent players. However, two serious flaws must be resolved before proceeding:
1. **Critical Vulnerability**: `CombatEngine.calculate_damage` must guard against re-triggering fatal damage on already-dead actors (`was_alive = defender.current_hp > 0.0; ...; is_fatal = was_alive and (defender.current_hp <= 0.0)`).
2. **Defect**: `LevelProgressionService.apply_death_penalty` must not return early on `player.level >= 100`. It should apply a 0% penalty while still incrementing `player.deaths_count` and dispatching all `_death_listeners`.

---

## 5. Verification Method & Proposed Remediation

### Remediation Spec 1: `server/world/combat_engine.py`
In `calculate_damage()`:
```python
# Capture prior life state before applying damage
was_alive = (defender.current_hp > 0.0)

final_damage, is_crit = self._calculate_mitigated_damage(
    attacker, defender, raw_damage, damage_element, force_crit
)
defender.current_hp = max(0.0, defender.current_hp - final_damage)
is_fatal = was_alive and (defender.current_hp <= 0.0)
```
*(If `was_alive is False`, the actor was already a corpse, so `is_fatal` is `False` and `on_fatal_damage` will not be invoked).*

### Remediation Spec 2: `server/world/level_progression_service.py`
In `apply_death_penalty()`:
Remove early return on lines 298-299:
```python
player = self.get_player_state(player_id)
# Ensure level 100 computes ratio=0.0 without skipping deaths_count and listener dispatch
ratio = self.get_death_penalty_ratio(player.level)
delta = self.get_delta_exp(player.level)
nominal_loss = int(math.floor(delta * ratio))
exp_lost = min(player.current_exp, nominal_loss)
new_exp = max(0, player.current_exp - nominal_loss)

self._players[player_id] = PlayerProgressionState(
    player_id=player.player_id,
    level=player.level,
    current_exp=new_exp,
    exp_to_next_level=player.exp_to_next_level,
    cumulative_exp=player.cumulative_exp - exp_lost,
    lifetime_exp=player.lifetime_exp,
    unspent_talent_points=player.unspent_talent_points,
    total_talent_points=player.total_talent_points,
    deaths_count=player.deaths_count + 1,
)

result = self._build_death_result(player, exp_lost, ratio, new_exp)
for listener in self._death_listeners:
    listener(result)
return result
```

### Verification Command
Run the challenge harness to confirm resolution:
```powershell
python .agents/teamwork/challenger_m2_progression_2/challenge_combat_progression.py
```
When remediated, the output will display:
`OVERALL VERDICT: ALL_CHALLENGES_PASSED` (Exit Code 0).
