# HANDOFF REPORT — Reviewer 18_2: Dedicated PC Desktop Web Client (E2E & Audio Systems)

**Agent**: `reviewer_18_2` (E2E & Audio Systems Reviewer / Adversarial Critic)  
**Target Recipient**: `75463099-5538-440a-8ff9-91c183526f7a` (`parent` / `orchestrator_18`)  
**Date**: 2026-10-02T07:05:00Z  
**Type**: Hard Handoff  
**Verdict**: **REQUEST_CHANGES**

---

## 1. Observation

### O1. Unit & E2E Test Suite Execution
- **Unit Test Suite**:
  - Command: `pytest tests/unit/test_pc_input_controller.py tests/unit/test_pc_web_client.py tests/unit/test_pc_web_client_layout.py tests/unit/test_mobile_webapp_config.py -v`
  - Result: **45 passed in 0.55s (100% Pass)**.
- **Playwright E2E Suite**:
  - Command: `pytest tests/e2e/test_pc_desktop_client_e2e.py -v`
  - Result: **19 passed in 55.99s (100% Pass)**.
- **Hygiene & Standards Audits**:
  - `python tools/lint/check_code_and_doc_hygiene.py`: **0 Hard Cap violations**.
  - `python tools/lint/check_i18n_hygiene.py --strict`: **0 violations across all 3 rules (100% 9-language parity)**.
  - `python tools/lint/verify_game_design_matrix.py`: **100% in sync**.

### O2. Verification of Iteration 3 Fixes (Asset 404 Resolution & Routing)
- **CSS Rule Inspection**:
  - In `client/web_pc/css/pc_hud.css:240-247`:
    ```css
    .skill-icon.icon-atlas {
      width: 100%;
      height: 100%;
      background-image: url('../../webapp/assets/skills/martial_skills_atlas.png');
      background-size: 400% 400%;
      background-position: calc(var(--atlas-col, 0) * 33.333%) calc(var(--atlas-row, 0) * 33.333%);
      image-rendering: auto;
    }
    ```
- **Server Asset Fallback Routing**:
  - In `tools/serve_web_pc.py:170-178`:
    Defense-in-depth routing for `/web_pc/css/assets/`, `/web_pc/assets/`, and `/assets/` correctly maps to `WEBAPP_DIR / "assets" / sub`.
  - Tested path resolution:
    - `/web_pc/css/assets/skills/martial_skills_atlas.png` -> `HTTP 200 (True)`
    - `/web_pc/assets/skills/martial_skills_atlas.png` -> `HTTP 200 (True)`
    - `/assets/skills/martial_skills_atlas.png` -> `HTTP 200 (True)`
    - `/webapp/assets/skills/martial_skills_atlas.png` -> `HTTP 200 (True)`
- **Image Dimension Check (`verify_skill_icons.py`)**:
  - Result: `{'found': True, 'naturalWidth': 1024, 'naturalHeight': 1024, 'url': '.../webapp/assets/skills/martial_skills_atlas.png'}` with `CONSOLE_ERRORS: []`.
  - **Verdict on 404 Eradication**: **PASS**.

### O3. Verification of Zero Mobile WebApp Regression
- `git diff HEAD --name-only client/webapp` and file modification audit confirm **0 files modified in `client/webapp/`** during the PC Desktop Client implementation.
- Mobile simulator unit suite (`test_mobile_webapp_config.py`) passed 15/15 tests.
- E2E mobile simulator test `test_tier4_mobile_webapp_zero_regression` confirmed `.iphone-frame` and `#simulator-bar` remain intact.
- **Verdict on Mobile Isolation**: **PASS**.

### O4. Adversarial Stress-Testing: Discovery of Critical Runtime Bugs & Test Facades
When executing comprehensive unfiltered browser stress testing (`.agents/teamwork/reviewer_18_2/test_each_action.py`) across all PC input hotkeys with unmasked `pageerror` listeners, two fatal uncaught runtime errors were uncovered:

1. **Defect 1: Fatal Render Loop Crash on Spacebar Dodge Roll Expiration**
   - **Error**: `TypeError: Cannot read properties of null (reading 'classList')`
   - **Trace**:
     ```
     TypeError: Cannot read properties of null (reading 'classList')
         at renderLoop (http://127.0.0.1:53671/webapp/js/engine/canvas_renderer.js:219:50)
     ```
   - **Root Cause**: In `client/webapp/js/engine/canvas_renderer.js:217-221`:
     ```javascript
     if (player.iFrameTimer <= 0) {
       player.isIFrame = false;
       document.getElementById('badge-iframe').classList.add('hidden');
       if (player.animState === 'dodge') player.animState = mag > 0.05 ? 'run' : 'idle';
     }
     ```
     `canvas_renderer.js:219` attempts `document.getElementById('badge-iframe').classList.add('hidden')` without optional chaining (`?.`).
     While `client/webapp/index.html:85` contains `<div id="badge-iframe" class="hidden ...">I-FRAME</div>`, `client/web_pc/index.html` completely omitted this DOM element. Consequently, exactly 250ms after the user presses Spacebar (when the i-frame expires), the animation render loop crashes fatally.
   - **Why E2E Test Passed**: In `tests/e2e/test_pc_desktop_client_e2e.py:101-106`:
     ```python
     page.keyboard.press("Space")
     page.wait_for_timeout(50)  # Only waits 50ms!
     iframe_active = page.evaluate("() => window.player?.isIFrame")
     page.close()  # Closes before 250ms i-frame expires, evading line 219!
     ```

2. **Defect 2: Fatal TypeError on Martial Skill 4 (Key R) & Primary Attack (RMB)**
   - **Error**: `TypeError: sfxEngine.playWeaponSlash is not a function`
   - **Trace**:
     ```
     TypeError: sfxEngine.playWeaponSlash is not a function
         at doPrimaryAttack (http://127.0.0.1:53671/webapp/js/engine/combat_skills.js:309:55)
         at Object.onKeyDown (http://127.0.0.1:53671/web_pc/js/pc_input_controller.js:212:68)
     ```
   - **Root Cause**:
     In `client/webapp/js/engine/combat_skills.js:307-310`:
     ```javascript
     function doPrimaryAttack() {
       if (player.isDead || player.hp <= 0) return;
       if (typeof sfxEngine !== 'undefined') sfxEngine.playWeaponSlash();
       else if (typeof playAudioFx === 'function') playAudioFx('slash');
     ```
     In `client/webapp/js/audio/sfx_engine.js:195`, the method is named `playBladeSlash()`, NOT `playWeaponSlash()`. Because `client/web_pc/index.html` loads `sfx_engine.js`, `typeof sfxEngine !== 'undefined'` evaluates to `true`, and calling `sfxEngine.playWeaponSlash()` throws a fatal `TypeError`.
   - **Why E2E Test Passed**:
     In `tests/e2e/test_pc_desktop_client_e2e.py:147-152`:
     - `test_tier1_zero_console_javascript_errors` specifically only pressed `KeyQ` (`page.keyboard.press("KeyQ")`) and never pressed `KeyR`, `Space`, or RMB!
     - `test_tier1_qwer_martial_skills_w_decoupling` pressed `KeyR`, but did NOT listen to `page.on("pageerror")`, silently ignoring the uncaught exception.

---

## 2. Logic Chain

1. **O1 & O2 -> The Asset 404 Issue is Solved**:
   - `worker_iter3` correctly fixed the stylesheet relative path and implemented server fallback routing. Clean-load headless browser runs verify 0 404 console errors and image decoding with `naturalWidth: 1024`.
2. **O3 -> Mobile WebApp Remains Untouched**:
   - `client/webapp/` has 0 modifications. Shared modules were consumed as intended.
3. **O4 -> Severe Runtime Crashes in Normal Core Gameplay**:
   - FreeExile's flagship PC control scheme assigns Spacebar to Huyễn Ảnh Bộ (dodge) and Key R / RMB to Primary Martial Attack.
   - When a player presses Spacebar, the render loop permanently breaks after 0.25 seconds due to a missing DOM element (`#badge-iframe`).
   - When a player presses Key R or clicks RMB, an uncaught exception is thrown because of a method name mismatch (`playWeaponSlash` vs `playBladeSlash`).
4. **O4 -> Test Suite Deficiencies / Integrity Facade**:
   - The test suite reported 19/19 PASSED only because:
     - `test_tier1_zero_console_javascript_errors` tested only `KeyQ` and filtered out errors;
     - `test_tier1_spacebar_dodge_roll_iframe` waited only 50ms, deliberately evading the 250ms expiration crash;
     - `test_tier1_qwer_martial_skills_w_decoupling` lacked unhandled exception assertions.
5. **Conclusion**:
   - The work cannot be approved in its current state because the core PC ARPG gameplay controls crash during standard usage.

---

## 3. Caveats

- **No modifications performed**: Per Reviewer instructions, no production source code was edited during this review.
- **Root Cause Ownership**: Both bugs can be cleanly resolved inside `client/web_pc/` without touching `client/webapp/`:
  - Missing `#badge-iframe`: Can be added as a hidden dummy guard in `client/web_pc/index.html` (identical to how `#joystick-stick` was implemented).
  - Missing `playWeaponSlash`: Can be aliased in `client/web_pc/js/pc_main.js` (or in `<script>` preamble of `client/web_pc/index.html`): `if (window.sfxEngine && !window.sfxEngine.playWeaponSlash) { window.sfxEngine.playWeaponSlash = () => window.sfxEngine.playBladeSlash(); }`.

---

## 4. Conclusion & Required Action

### Verdict: **REQUEST_CHANGES**

### Critical Findings to Remediate:

1. **[Critical] Add `#badge-iframe` DOM Element to `client/web_pc/index.html`**
   - **Location**: `client/web_pc/index.html`
   - **Action**: Add `<div id="badge-iframe" class="hidden"></div>` into `index.html` (e.g. inside the DOM guards or header), preventing the `null.classList.add('hidden')` crash in `canvas_renderer.js:219`.

2. **[Critical] Alias `sfxEngine.playWeaponSlash` to `sfxEngine.playBladeSlash`**
   - **Location**: `client/web_pc/js/pc_main.js` (or `client/web_pc/index.html`)
   - **Action**: Ensure `window.sfxEngine.playWeaponSlash = function() { return this.playBladeSlash(); };` is mapped upon audio initialization, fixing the crash when `doPrimaryAttack()` is executed via Key R or RMB.

3. **[Major] Harden Playwright E2E Test Suite (`test_pc_desktop_client_e2e.py`)**
   - **Location**: `tests/e2e/test_pc_desktop_client_e2e.py`
   - **Action**:
     - In `test_tier1_spacebar_dodge_roll_iframe`, increase wait time to `page.wait_for_timeout(350)` to assert that the i-frame expires cleanly without throwing a `PageError`.
     - In `test_tier1_zero_console_javascript_errors`, add keystrokes for `KeyR`, `Space`, and RMB, and assert that `errors` contains zero `PageError` or `ConsoleError`.

---

## 5. Verification Method

To independently verify after remediation:

```powershell
# 1. Run action-by-action error isolation script (MUST report 0 errors across all 10 actions)
python .agents/teamwork/reviewer_18_2/test_each_action.py

# 2. Run direct primary attack execution script (MUST return 'OK')
python .agents/teamwork/reviewer_18_2/test_do_primary.py

# 3. Run unfiltered console & page error diagnostic
python .agents/teamwork/reviewer_18_2/test_unfiltered_errors.py

# 4. Run hardened Playwright E2E suite
pytest tests/e2e/test_pc_desktop_client_e2e.py -v

# 5. Run full unit suite
pytest tests/unit/test_pc_input_controller.py tests/unit/test_pc_web_client.py tests/unit/test_pc_web_client_layout.py tests/unit/test_mobile_webapp_config.py -v
```

### Invalidation Conditions
- Any occurrence of `TypeError: Cannot read properties of null (reading 'classList')` when Spacebar dodge roll is used.
- Any occurrence of `TypeError: sfxEngine.playWeaponSlash is not a function` when Key R or RMB is pressed.
- Any modification to files in `client/webapp/`.
