# Handoff Report — explorer_survey_2

**To**: `orchestrator_22` (Conversation ID: `34037784-62e1-41f8-bfe6-912696fdec14`)  
**From**: `explorer_survey_2`  
**Date**: 2026-10-04  
**Subject**: Cocos Creator 3.8.x & Apple Metal iOS Rendering Pipeline Investigation

---

## 1. Observation

1. **`SpriteAtlasRenderer.ts` (`client/cocos/assets/scripts/animation/SpriteAtlasRenderer.ts`)**:
   - Lines 28-34: Define `frameWidth: number = 160`, `frameHeight: number = 192`, `cols: number = 8`, `pivotX = 0.5`, `pivotY = 0.90`.
   - Lines 46-53: Hardcodes `clipRowMap` to 6 static rows:
     ```ts
     private clipRowMap: Record<string, number> = {
       'idle': 0, 'run': 1, 'attack': 2, 'hurt': 3, 'dodge': 4, 'death': 5
     };
     ```
   - Lines 71-100: Calculates pixel source coordinates as:
     `const sx = colIndex * this.frameWidth; const sy = rowIndex * this.frameHeight; return new Rect(sx, sy, this.frameWidth, this.frameHeight);`
     Every call allocates a new `Rect` instance.
   - Lines 18-25: `AtlasManifest` expects `clips: Record<string, FrameManifestClip>`, which does NOT recognize `animations` (found in `char_*_anim_manifest.json`) or explicit `frames: Record<string, {x, y, w, h, pivot}>`.
   - Grep search in `client/` confirms `SpriteAtlasRenderer` is only referenced in `index.ts` and `SpriteAtlasRenderer.ts` itself; it is not hooked to any node or `Sprite` component.

2. **`sprite_pbr.effect` (`client/cocos/assets/resources/shaders/sprite_pbr.effect`)**:
   - Lines 69-72: Defines 3 samplers: `uniform sampler2D mainTexture; uniform sampler2D normalMap; uniform sampler2D emissiveMap;`.
   - Lines 77-80: Samples tangent normal as `vec3 normalSample = texture(normalMap, v_uv).rgb * 2.0 - 1.0; vec3 normal = normalize(normalSample);`.
   - Lines 81-92: Computes directional lighting with key light `lightDir` (`[0.577, 0.577, 0.577, 0.0]`) and Blinn-Phong specular (`specular = pow(NdotH, specPower) * (1.0 - roughness);`).
   - Lines 11-13: Defaults for samplers are `mainTexture: white`, `normalMap: normal` (flat $[0.5, 0.5, 1.0, 1.0]$ normal facing camera), and `emissiveMap: black`.
   - Grep search confirmed no TypeScript file in `client/cocos/assets/scripts/` currently loads or binds this material dynamically.

3. **Existing VFX Atlases & Manifests**:
   - `client/cocos/assets/resources/vfx/` contains only 3 files:
     - `savage_primal_skills_vfx_atlas.png` ($1024 \times 1024$, 51.8 KB)
     - `savage_primal_skills_vfx_atlas_normal.png` ($1024 \times 1024$, 212.8 KB)
     - `savage_primal_skills_vfx_atlas.json` (1.87 KB)
   - Inspection of `savage_primal_skills_vfx_atlas.json` reveals only 7 skills/sigils are packaged: `blood_drain_aoe`, `bone_totem_summon`, `fire_whirlwind_slash`, `ground_slam_crater`, `ghost_trail_evasion`, `multi_projectile_split`, `chain_lightning_arc`.
   - 10 active skills and 3 support sigils from `skill_catalog.ts` and `support_skill_catalog.ts` are completely missing.
   - `client/cocos/assets/textures/` does not exist on disk.

4. **Combat Controller Event Dispatch**:
   - `client/cocos/assets/scripts/combat/CombatController.ts` lines 214: Emits `EventBus.emit('skillCasted', { skillId: ctx.skillId, damage, isCrit, ctx })`.
   - Grep search for `skillCasted` across `client/cocos` confirms no listener exists.

5. **Apple Metal Shaders & Performance Checks**:
   - `client/assets/shaders/SpritePBRNormal.metal` lines 77-80: Implements rim lighting `pow(1.0 - NdotV, uniforms.rimPower)` and `ghostAlpha` parameter.
   - `tests/e2e_cocos/test_tier1_cocos_assets.py`: Ran with `pytest` -> 4 tests passed in 0.10s.
   - `client/cocos`: Ran `npm run build:web` (`tsc --noEmit`) -> exited with code 0 (zero type errors).

---

## 2. Logic Chain

1. **Premise 1**: FreeExile targets 120 FPS ProMotion ($8.33\text{ ms/frame}$) on Apple Silicon iOS and WebGL 2.0 with a strict zero-allocation hot path constraint (Directives in `GEMINI.md` §2.2).
2. **Premise 2**: `SpriteAtlasRenderer.ts` currently instantiates `new Rect` and array/object literals during frame updates (Observation 1). Under 50 entities at 120 FPS, this generates over 18,000 heap allocations per second, triggering GC pauses that break the 8.33ms budget.
3. **Premise 3**: `SpriteAtlasRenderer.ts` assumes a 1-clip-per-row grid matching `clipRowMap` (Observation 1). Both character manifests (which pack frames sequentially across 8 columns) and VFX manifests (which contain non-standard clip names like `fire_whirlwind_slash`) fail to resolve correct pixel coordinates under this assumption.
4. **Premise 4**: The skill system requires 12 active skills and 5 support sigils (Observation 3). Currently, only 7 skills are present in `savage_primal_skills_vfx_atlas`, leaving a gap of 10 active skills and 3 sigils.
5. **Premise 5**: While `CombatController` dispatches `skillCasted` events, no VFX component listens to or renders these effects (Observation 4).
6. **Conclusion**: To fulfill the user request of transitioning from procedural canvas rendering to true PBR PoT texture atlases in Cocos Creator and Apple Metal:
   - `SpriteAtlasRenderer.ts` must be upgraded to a zero-allocation component with a unified manifest adapter.
   - A `SkillVfxPlayer.ts` component must be introduced to listen to `skillCasted` and play pooled sprite quads.
   - A comprehensive Python atlas generator must produce a Power-of-Two ($2048 \times 2048$) atlas covering all 12 active skills + 5 support sigils with Dilation Padding and Tangent-Space Sobel Normal maps.

---

## 3. Caveats

- **Blender 3D Models**: The current asset pipeline uses procedural mathematical synthesis (Bezier curves, arcs, L-systems) in Python rather than 3D rendered sprites from Blender.
- **ASTC Hardware Compression**: ASTC 4x4 `.astc` compression requires the Apple Metal / Xcode toolchain during native build time; development assets are stored as Power-of-Two PNG + WebP which are transparently converted by Cocos Creator's build pipeline for iOS targets.
- **No other caveats.**

---

## 4. Conclusion

The Cocos Creator 3.8.x and Metal integration has a robust foundation (`sprite_pbr.effect` and `SpritePBRNormal.metal` are structurally sound, and TypeScript compiles cleanly), but suffers from four critical blockers:
1. `SpriteAtlasRenderer.ts` produces garbage allocations and cannot parse sequential/explicit manifests.
2. 10 of 12 active skills and 3 of 5 support sigils lack flipbook texture frames.
3. No VFX playback component connects `CombatController` events to on-screen sprite rendering in Cocos.
4. Alpha dilation padding is required on atlas textures to avoid dark fringe artifacts against dark dungeon floors.

---

## 5. Verification Method

1. **Type-Check Verification**:
   ```bash
   cd c:\Projects\FreeExile\client\cocos
   npm run build:web
   ```
   *Expected*: Exit code 0, 0 diagnostic errors.
2. **Cocos Asset Integrity Test**:
   ```bash
   pytest tests/e2e_cocos/test_tier1_cocos_assets.py
   ```
   *Expected*: 4 passed in < 0.2s.
3. **Manifest Schema Validation**:
   Inspect `client/cocos/assets/resources/vfx/savage_primal_skills_vfx_atlas.json` and compare against `SpriteAtlasRenderer.ts:AtlasManifest`.
4. **Invalidation Conditions**:
   - If `SpriteAtlasRenderer.ts` creates new heap objects during frame updates, zero-allocation compliance is invalidated.
   - If any active skill or support sigil cannot resolve its frame coordinates in `SpriteAtlasRenderer`, atlas mapping is invalidated.
