# HANDOFF REPORT — Milestone 1 Remediation & Milestone 2 Integration Analysis

**Author**: `explorer_rem_3` (Explorer / Architecture Investigator)  
**Parent Agent**: `orchestrator_22` (`34037784-62e1-41f8-bfe6-912696fdec14`)  
**Scope**: Holistic analysis of Milestone 1 remediation, interface contracts with Cocos Creator 3.8.x `SpriteAtlasRenderer.ts`, and step-by-step remediation plan for the Worker  
**Date**: 2026-10-04T13:23:00Z  
**Handoff Type**: Hard (Investigation Complete)  

---

## 1. Observation

1. **Scaffold Tool Defect in `monster_character_pipeline_scaffold.py`**:
   - `tools/asset_pipeline/monster_character_pipeline_scaffold.py:82-83` and `153-154`: Sets `"textureWidth": 2048, "textureHeight": 2048` unconditionally.
   - Lines `107` and `178`: Calculates `"uv": [x / 2048.0, y / 2048.0, (x + fw) / 2048.0, (y + fh) / 2048.0]`.
   - Grid layout lines `91-118` and `162-189`:
     - Monsters: 6 actions $\times$ 8 canonical directions = 48 rows $\times 160\text{ px} = 7,680\text{ px}$ height.
     - Characters: 10 actions $\times$ 8 canonical directions = 80 rows $\times 192\text{ px} = 15,360\text{ px}$ height.
   - Verification command:
     ```python
     python -c "
     import json, pathlib
     for p in pathlib.Path('client/cocos/assets/resources/monsters/archetypes').glob('*/*_anim_manifest.json'):
         m = json.load(open(p, encoding='utf-8'))
         bad = [f['uv'] for f in m['frames'].values() if f['uv'][3] > 1.0]
         print(p.parent.name, 'Frames out of bounds:', len(bad), 'Max V:', max(x[3] for x in bad))
     "
     ```
     Result: All 10 monster archetypes show `160 frames out of bounds, Max V: 3.75`.
     All 6 exile character classes show `400 frames out of bounds, Max V: 7.50`.

2. **Cocos `SpriteAtlasRenderer.ts` Consumption**:
   - `client/cocos/assets/scripts/animation/SpriteAtlasRenderer.ts:31`: `public cols: number = 8;`.
   - Lines `94-98`:
     ```typescript
     const colIndex = frameIndex % this.cols;
     const sx = colIndex * this.frameWidth;
     const sy = rowIndex * this.frameHeight;
     return new Rect(sx, sy, this.frameWidth, this.frameHeight);
     ```
   - In Milestone 2:
     - Feature 10 refactors the loop to eliminate `new Rect` allocations at 120 FPS.
     - Feature 11 adds the Flexible Manifest Adapter to support both sequential grid math and explicit `frame.uv` lookup.
   - Supplying $V > 1.0$ coordinates directly to Metal/WebGL vertex buffers corrupts texture sampling (`MTLSamplerAddressModeClampToEdge` clamps to row 12; `Repeat` samples wrong actions).

3. **Hygiene Audit Hard Cap Violation**:
   - `python tools/lint/check_code_and_doc_hygiene.py --strict` fails with exit code 1:
     ```
     ❌ [CODE] tests\e2e_cocos\test_vfx_texture_atlas_pipeline_e2e.py (574 dòng) -> Vượt quá Hard Cap (574 > 500 dòng). Bắt buộc tách module, catalog hoặc DTOs!
     ```
   - There are **0 other files** in the entire repository violating the 500-line Hard Cap.

4. **Pipeline Test Baseline**:
   - `pytest tests/unit/test_asset_pipeline_tools.py tests/e2e/test_asset_campaign_and_pipeline_e2e.py tests/e2e_cocos/test_vfx_texture_atlas_pipeline_e2e.py -v`:
     - **57 passed in 43.81s** (100% pass rate for VFX pipeline, dilation padding, Sobel normals, ASTC, and current tests).
   - `pytest tests/e2e_cocos/`: **202 passed in 3.48s**.
   - `npm run build:web` (`tsc --noEmit` in `client/cocos/`): **Exit code 0**.

---

## 2. Logic Chain

1. **From Observation 1**: The out-of-bounds UV coordinates in monster and character manifests are caused entirely by hardcoding `2048.0` as the vertical divisor in `monster_character_pipeline_scaffold.py`, despite the grid spanning 7,680px (monsters) and 15,360px (characters).
2. **From Observation 2**: In `SpriteAtlasRenderer.ts`, the canonical layout relies on 8 columns (`cols = 8`). For monsters, 48 rows $\times$ 160px require an atlas height of 7,680px; the nearest Power-of-Two height is $8192$ ($2^{13}$). For characters, 80 rows $\times$ 192px require 15,360px; the nearest Power-of-Two height is $16384$ ($2^{14}$).
3. **Connecting 1 & 2**: Setting `textureHeight = 8192` for monsters and `textureHeight = 16384` for characters, and normalizing UVs with these dimensions:
   - Monsters: $u_1 \le 0.625$, $v_1 \le 0.9375 \le 1.0$. (0 frames out of bounds).
   - Characters: $u_1 \le 0.625$, $v_1 \le 0.9375 \le 1.0$. (0 frames out of bounds).
   - Conforms strictly to the `PROJECT.md` interface contract and enables Milestone 2's Flexible Manifest Adapter to read `frame.uv` directly without out-of-bounds sampling.
4. **From Observation 3**: Modularizing `test_vfx_texture_atlas_pipeline_e2e.py` by extracting canonical catalogs and fixtures into `vfx_pipeline_test_helpers.py` or splitting into two test files brings all files under the 500-line Hard Cap (and under the 350-line Soft Cap), allowing `check_code_and_doc_hygiene.py --strict` to pass with exit code 0.
5. **From Observation 4**: The underlying VFX assets, ASTC containers, dilation padding, normal maps, and Cocos TypeScript compilation are already verified green. Fixing the scaffold generator and test file will achieve 100% Milestone 1 gate acceptance without regressions.

---

## 3. Caveats

- **Spritesheet Generation vs. Scaffolding**: Milestone 1 scope is strictly scaffolding (directories, JSON manifests, and pipeline configs). Actual sprite pixel rendering for 10 monsters and 6 characters is slated for subsequent milestones.
- **ASTC Fallback on Windows**: `astc_compressor.py` synthesizes valid canonical 16-byte header void-extent ASTC containers when external `astcenc` is not in system PATH. This is standard and approved for staging verification.
- **Legacy WebApp Tests in `tests/unit/`**: Full `pytest tests/unit/` includes historical WebApp DOM tests requiring Node.js mocks. As established by `worker_m1`, the relevant target test suites for Milestone 1 are `tests/unit/test_asset_pipeline_tools.py`, `tests/e2e/test_asset_campaign_and_pipeline_e2e.py`, and `tests/e2e_cocos/`.

---

## 4. Conclusion

The integration path between Milestone 1 remediation and Milestone 2 is completely clear and validated.

### Step-by-Step Remediation Plan for Worker:
1. **Step 1: Fix `monster_character_pipeline_scaffold.py`**:
   - Add `next_power_of_two(n: int) -> int`.
   - Dynamically compute `tex_w = 2048` and `tex_h = next_power_of_two(total_rows * frame_height)`:
     - Monsters: `tex_w = 2048, tex_h = 8192`.
     - Characters: `tex_w = 2048, tex_h = 16384`.
   - Calculate UVs: `[x / float(tex_w), y / float(tex_h), (x + fw) / float(tex_w), (y + fh) / float(tex_h)]`.
   - Update `pipeline_config.json` target resolution to `[tex_w, tex_h]`.
   - Verify file length remains $\le 240$ lines.
2. **Step 2: Regenerate Manifests**:
   - Run `python tools/asset_pipeline/monster_character_pipeline_scaffold.py`.
   - Confirm 0 out-of-bounds UVs across all 10 monster and 6 character manifests.
3. **Step 3: Refactor Test Suite & Add Strict UV Boundary Assertions**:
   - Extract canonical constants/fixtures into `tests/e2e_cocos/vfx_pipeline_test_helpers.py`.
   - Update `test_t1_monster_and_character_scaffolding_structure` to iterate over all 10 monsters and 6 characters, asserting $0.0 \le u_0, v_0, u_1, v_1 \le 1.0$, $u_1 > u_0$, $v_1 > v_0$, and `pivot == [0.5, 0.90]`.
   - Ensure `test_vfx_texture_atlas_pipeline_e2e.py` is $\le 500$ lines.
4. **Step 4: Execute Verification Gates**:
   - `pytest tests/unit/test_asset_pipeline_tools.py tests/e2e/test_asset_campaign_and_pipeline_e2e.py tests/e2e_cocos/test_vfx_texture_atlas_pipeline_e2e.py -v` (57+ passed).
   - `pytest tests/e2e_cocos/ -v` (202+ passed).
   - `python tools/lint/check_code_and_doc_hygiene.py --strict` (exit code 0).
   - `cd client/cocos && npm run build:web` (exit code 0).

---

## 5. Verification Method

To verify the remediation:

1. **Verify Monster & Character UV Boundedness**:
   ```bash
   python -c "
   import json, pathlib
   for p in list(pathlib.Path('client/cocos/assets/resources/monsters/archetypes').glob('*/*_anim_manifest.json')) + list(pathlib.Path('client/cocos/assets/resources/characters').glob('*/*_anim_manifest.json')):
       m = json.load(open(p, encoding='utf-8'))
       bad = [f['uv'] for f in m['frames'].values() if any(c < 0.0 or c > 1.0 for c in f['uv'])]
       print(p.parent.name, 'Bad UVs:', len(bad), 'Max V:', max(f['uv'][3] for f in m['frames'].values()))
       assert len(bad) == 0, f'Out of bounds UVs in {p}'
   print('ALL MANIFESTS CLEAN: 100% UVs bounded in [0.0, 1.0]')
   "
   ```
   *Expected condition*: 0 bad UVs across all 16 manifests, all $V \le 1.0$.

2. **Verify Code & Doc Hygiene Gate**:
   ```bash
   python tools/lint/check_code_and_doc_hygiene.py --strict
   ```
   *Expected condition*: Exit code 0, 0 files exceeding 500-line Hard Cap.

3. **Verify Pipeline & Cocos Test Suite**:
   ```bash
   pytest tests/unit/test_asset_pipeline_tools.py tests/e2e/test_asset_campaign_and_pipeline_e2e.py tests/e2e_cocos/test_vfx_texture_atlas_pipeline_e2e.py -v
   pytest tests/e2e_cocos/ -v
   ```
   *Expected condition*: 100% passed with 0 failures.

4. **Verify Cocos TypeScript Build**:
   ```bash
   cd client/cocos && npm run build:web
   ```
   *Expected condition*: Exit code 0.
