# Milestone 3 Remediation (Iteration 2) Handoff Report: Leader Aura Visual Decal Integration

> **Agent**: `teamwork_preview_explorer` (`explorer_m3_r2_2` - Entity Renderer Aura Decal Wiring Explorer)  
> **Milestone**: Milestone 3 Remediation (Iteration 2) — PoE2 Tactical Monster Pack Leader Aura Visual Decal  
> **Target**: Orchestrator (`orchestrator_1`), Parent (`af73d9ec-d3ff-4501-b986-47b632c5d073`), Downstream Worker (`worker_m3_1`)  
> **Status**: Complete — Ready for Downstream Implementation  
> **Artifacts**: `proposed_entity_renderer_aura.patch`

---

## 1. Observation

### 1.1. Declaration and Window Exposure of `renderLeaderAuraDecal`
In `client/webapp/js/engine/monster_pack_system.js` (lines 91–127):
```javascript
91: export function renderLeaderAuraDecal(ctx, monster, screenX, screenY, timeSec) {
92:   if (!monster.isPackLeader || monster.hp <= 0) return;
93: 
94:   ctx.save();
95:   const auraColor = monster.leaderAura === 'DAMAGE_RESISTANCE' ? '#38bdf8'
96:     : (monster.leaderAura === 'ELEMENTAL_EMPOWERMENT' ? '#f59e0b'
97:     : (monster.leaderAura === 'VAMPIRIC' ? '#ef4444'
98:     : (monster.leaderAura === 'MORTAL_MIGHT' ? '#ec4899' : '#a855f7')));
99: 
100:   const pulse = Math.sin((timeSec || Date.now() * 0.001) * 3) * 4;
101:   const radius = 38 + pulse;
102: 
103:   // Outer pulsating aura ring
104:   ctx.strokeStyle = auraColor;
105:   ctx.lineWidth = 2.5;
106:   ctx.globalAlpha = 0.55 + Math.sin((timeSec || Date.now() * 0.001) * 4) * 0.2;
107:   ctx.beginPath();
108:   ctx.ellipse(screenX, screenY + 4, radius, radius * 0.55, 0, 0, Math.PI * 2);
109:   ctx.stroke();
110: 
111:   // Inner subtle fill
112:   ctx.fillStyle = auraColor;
113:   ctx.globalAlpha = 0.12;
114:   ctx.beginPath();
115:   ctx.ellipse(screenX, screenY + 4, radius * 0.85, radius * 0.45, 0, 0, Math.PI * 2);
116:   ctx.fill();
117: 
118:   ctx.restore();
119: }
120: 
121: // Window Bridge
122: if (typeof window !== 'undefined') {
123:   window.populateZonePacks = populateZonePacks;
124:   window.updateMonsterPackAI = updateMonsterPackAI;
125:   window.renderLeaderAuraDecal = renderLeaderAuraDecal;
126:   window.getWildernessZoneMonsters = (z, fn) => populateZonePacks(z, fn);
127: }
```
- Function signature: `renderLeaderAuraDecal(ctx, monster, screenX, screenY, timeSec)`.
- Internal guard: returns immediately if `!monster.isPackLeader || monster.hp <= 0`.
- Rendering target: Canvas 2D ellipse centered at `(screenX, screenY + 4)`.
- Canvas state: fully isolated via `ctx.save()` (line 94) and `ctx.restore()` (line 118).
- Window bridge: exported to global `window.renderLeaderAuraDecal` at line 125.

### 1.2. Zero Call Sites Across Entire Repository
Running `grep_search` across `client/` and `tests/`:
```json
{"File":"c:\\Projects\\FreeExile\\client\\webapp\\js\\engine\\monster_pack_system.js","LineNumber":91,"LineContent":"export function renderLeaderAuraDecal(ctx, monster, screenX, screenY, timeSec) {"}
{"File":"c:\\Projects\\FreeExile\\client\\webapp\\js\\engine\\monster_pack_system.js","LineNumber":125,"LineContent":"  window.renderLeaderAuraDecal = renderLeaderAuraDecal;"}
{"File":"c:\\Projects\\FreeExile\\client\\webapp\\js\\engine\\monster_pack_system.js","LineNumber":132,"LineContent":"  renderLeaderAuraDecal"}
```
There are exactly 3 occurrences in the codebase, all inside `monster_pack_system.js`. Neither `entity_renderer.js` nor any other renderer invokes this function.

### 1.3. Monster Drawing Loop in `entity_renderer.js`
In `client/webapp/js/engine/entity_renderer.js` (lines 113–132):
```javascript
113:         } else if (item.type === 'monster') {
114:           const m = item.data;
115:           const mPos = worldToIso(m.wx, m.wy);
116: 
117:           // Monster Soft Shadow (dynamically scaled to monster size)
118:           const shadowRx = Math.max(26, m.width * 0.32);
119:           const shadowRy = Math.max(12, m.height * 0.12);
120:           ctx.fillStyle = m.isBoss ? 'rgba(30, 0, 10, 0.70)' : 'rgba(0, 0, 0, 0.55)';
121:           ctx.beginPath();
122:           ctx.ellipse(mPos.x, mPos.y + 4, shadowRx, shadowRy, 0, 0, Math.PI * 2);
123:           ctx.fill();
124: 
125:           let drawnAnimatedMob = false;
126:           if (typeof AnimationEngine !== 'undefined' && m.anim) {
127:             drawnAnimatedMob = AnimationEngine.drawEntityFrame(
128:               ctx, m.anim, mPos.x, mPos.y + 4, m.facing, m.width, m.height,
129:               { hurtFlash: m.hurtTimer }
130:             );
131:           }
```
- `mPos` is obtained from `worldToIso(m.wx, m.wy)` (line 115).
- Monster soft shadow is rendered at `mPos.x, mPos.y + 4` (lines 121–123).
- The sprite animation and fallback sprite drawing begin at line 125.
- Currently, no leader aura check or decal rendering exists.

### 1.4. File Length and Hygiene Audit Status
- File: `client/webapp/js/engine/entity_renderer.js`
- Total lines: 418 lines (417 lines reported by `check_code_and_doc_hygiene.py`).
- Soft Cap: 350 lines (currently triggers a non-failing WARNING).
- Hard Cap: 500 lines.
- Headroom: $500 - 418 = 82$ lines before Hard Cap.
- Command run: `python tools/lint/check_code_and_doc_hygiene.py --strict`
  - Exit code: 0 (PASS).
  - Hard cap violations: 0.

---

## 2. Logic Chain

1. **Observation 1.1 & 1.2 -> Visual Disconnection**: While `monster_pack_system.js` implements `renderLeaderAuraDecal` with distinct thematic colors per aura type (`DAMAGE_RESISTANCE`: `#38bdf8`, `ELEMENTAL_EMPOWERMENT`: `#f59e0b`, `VAMPIRIC`: `#ef4444`, `MORTAL_MIGHT`: `#ec4899`, Haste/other: `#a855f7`), the lack of a call site in `entity_renderer.js` means pack leaders are visually indistinguishable from standard monsters on the ground.
2. **Observation 1.3 -> Optimal Insertion Point**:
   - In 2.5D isometric rendering, ground decals and shadows must be drawn **beneath** the character/monster sprite.
   - Lines 117–123 draw the monster's soft shadow at `(mPos.x, mPos.y + 4)`.
   - `renderLeaderAuraDecal` also centers its pulsating ground ring at `(screenX, screenY + 4)`.
   - Placing the aura decal call immediately after line 123 (after the shadow, before line 125 `let drawnAnimatedMob = false`) ensures:
     a. The pulsating aura decal is rendered on the ground under the mob sprite.
     b. The center point matches the monster's footprint.
     c. The monster sprite renders on top of the glowing aura ring, providing proper depth layering without clipping.
3. **Observation 1.1 -> Call Guard and Signature**:
   - Guard condition: `if (m.isPackLeader && typeof window.renderLeaderAuraDecal === 'function')`.
   - Passing `ctx, m, mPos.x, mPos.y, (window.gameTimeSec || performance.now() * 0.001)`:
     - `ctx`: active CanvasRenderingContext2D.
     - `m`: monster entity with `leaderAura` and `hp`.
     - `mPos.x, mPos.y`: screen coordinates from `worldToIso`.
     - `(window.gameTimeSec || performance.now() * 0.001)`: smooth second-based timer for harmonic oscillation (`Math.sin(timeSec * 3)`).
   - Because `renderLeaderAuraDecal` internally wraps its draw calls in `ctx.save()` / `ctx.restore()`, state pollution is zero.
4. **Observation 1.4 -> Hygiene and Length Compliance**:
   - Adding this 4-line block increases `entity_renderer.js` from 418 to 422 lines.
   - 422 lines remains strictly under the 500-line Hard Cap (78 lines buffer).
   - Automated hygiene check (`tools/lint/check_code_and_doc_hygiene.py --strict`) will continue to pass cleanly.

---

## 3. Caveats

1. **Soft Cap Proximity**: `entity_renderer.js` is already above the 350-line Soft Cap (418 lines). Adding 4 lines keeps it well within the 500-line Hard Cap (422 lines). Full modularization (e.g. splitting into `monster_renderer_helper.js` or `player_renderer_helper.js`) should be deferred to Milestone 4/5 refactoring tracks to avoid regression risks in Milestone 3.
2. **Runtime Script Dependency**: For `window.renderLeaderAuraDecal` to be defined at runtime in the browser, `monster_pack_system.js` must be loaded by `client/webapp/index.html`. This is being addressed concurrently by peer agent `explorer_m3_r2_1`.
3. **Dead Leader Behavior**: `renderLeaderAuraDecal` contains an internal check `if (!monster.isPackLeader || monster.hp <= 0) return;`. Even when a dying pack leader is kept in `renderEntities` for hurt animation (`mob.hurtTimer > 0`), the aura decal will cleanly disappear upon death.
4. **No other caveats.**

---

## 4. Conclusion

The integration hook in `client/webapp/js/engine/entity_renderer.js` is exact, safe, and minimal. A clean unified diff patch has been prepared at:
`c:\Projects\FreeExile\.agents\teamwork\explorer_m3_r2_2\proposed_entity_renderer_aura.patch`.

### Proposed Code Change:
In `client/webapp/js/engine/entity_renderer.js`, at line 124 (immediately after the monster soft shadow):

```javascript
<<<<<<< BEFORE
          // Monster Soft Shadow (dynamically scaled to monster size)
          const shadowRx = Math.max(26, m.width * 0.32);
          const shadowRy = Math.max(12, m.height * 0.12);
          ctx.fillStyle = m.isBoss ? 'rgba(30, 0, 10, 0.70)' : 'rgba(0, 0, 0, 0.55)';
          ctx.beginPath();
          ctx.ellipse(mPos.x, mPos.y + 4, shadowRx, shadowRy, 0, 0, Math.PI * 2);
          ctx.fill();

          let drawnAnimatedMob = false;
=======
          // Monster Soft Shadow (dynamically scaled to monster size)
          const shadowRx = Math.max(26, m.width * 0.32);
          const shadowRy = Math.max(12, m.height * 0.12);
          ctx.fillStyle = m.isBoss ? 'rgba(30, 0, 10, 0.70)' : 'rgba(0, 0, 0, 0.55)';
          ctx.beginPath();
          ctx.ellipse(mPos.x, mPos.y + 4, shadowRx, shadowRy, 0, 0, Math.PI * 2);
          ctx.fill();

          // Leader Aura Ground Decal (PoE2 Pack Leader Visual Aura Indicator)
          if (m.isPackLeader && typeof window.renderLeaderAuraDecal === 'function') {
            window.renderLeaderAuraDecal(ctx, m, mPos.x, mPos.y, (window.gameTimeSec || performance.now() * 0.001));
          }

          let drawnAnimatedMob = false;
>>>>>>> AFTER
```

---

## 5. Verification Method

### 5.1. Code Inspection & Call Site Check
Verify that `renderLeaderAuraDecal` now has a caller in `entity_renderer.js`:
```bash
git grep -n "renderLeaderAuraDecal" client/webapp/js/engine/entity_renderer.js
```
Expected output:
```
client/webapp/js/engine/entity_renderer.js:126:          if (m.isPackLeader && typeof window.renderLeaderAuraDecal === 'function') {
client/webapp/js/engine/entity_renderer.js:127:            window.renderLeaderAuraDecal(ctx, m, mPos.x, mPos.y, (window.gameTimeSec || performance.now() * 0.001));
```

### 5.2. Strict Hygiene Verification
Verify that `entity_renderer.js` remains strictly under the 500-line Hard Cap:
```bash
python tools/lint/check_code_and_doc_hygiene.py --strict
```
Expected: Exit code 0, zero Hard Cap violations.

### 5.3. Recommended Automated Test
Add an assertion in `tests/unit/test_monster_pack_and_affixes.py`:
```python
def test_entity_renderer_invokes_leader_aura_decal(self) -> None:
    """Verifies entity_renderer.js wires renderLeaderAuraDecal for pack leaders."""
    renderer_path = Path(__file__).resolve().parents[2] / "client" / "webapp" / "js" / "engine" / "entity_renderer.js"
    code = renderer_path.read_text(encoding="utf-8")
    assert "renderLeaderAuraDecal" in code, "entity_renderer.js must integrate renderLeaderAuraDecal"
    assert "m.isPackLeader" in code, "entity_renderer.js must guard aura decal by isPackLeader"
```

### 5.4. Unit and E2E Test Suite Run
Execute existing test suites to guarantee zero regression:
```bash
pytest tests/unit/test_monster_pack_and_affixes.py tests/unit/test_wilderness_encounter_density.py tests/unit/test_ambush_trigger_engine.py tests/unit/test_hideout_engine.py -v
pytest tests/e2e/test_poe2_zone_and_encounter_e2e.py -v
```
Expected: 100% PASS.
