# HANDOFF REPORT — EXPLORER 1 (MILESTONE M4 ITERATION 2: REMEDIATION STRATEGY)

**Agent**: `explorer_m4_remediation_1` (Explorer 1)  
**Recipient**: Parent Orchestrator (`orchestrator_1` / `97faf5e5-a830-491c-b78c-2af12175badf`)  
**Mission**: Forensic Investigation & Remediation Strategy Formulation for Milestone M4 Gate Closure  
**Handoff Type**: Hard Handoff (Investigation & Planning Complete)  
**Accompanying Deliverable**: `c:\Projects\KieuStory\.agents\teamwork\explorer_m4_remediation_1\remediation_plan.md`  

---

## 1. Observation

### 1.1 Defect 1: Character Assets Invariant Violation
- **Physical vs Git Asset Inventory**:
  * Git tracked files (`git ls-files 04_Assets/characters`): Exactly **62 files**, 100% clean, `git diff HEAD -- 04_Assets/characters` is empty.
  * Physical filesystem check on disk: Total **117 files** (62 tracked + 55 untracked).
  * Direct execution of `python -m pytest tests/test_m2_hygiene.py -v` produced verbatim failure:
    ```
    FAILED tests/test_m2_hygiene.py::test_character_portraits_unharmed - AssertionError: Total character assets altered! Expected 62, found 115 (now 117)
    assert 117 == 62
    ```
- **Untracked File Taxonomy (55 files total)**:
  * 54 turnaround model sheet PNGs across 7 subfolders:
    - `01_Main_Protagonists/`: 18 files (9 characters x 2: `_turnaround_sheet_master.png` and `_turnaround_sheet_720p.png`)
    - `02_Vuong_Family_And_Fate/`: 16 files (8 characters x 2)
    - `03_Thuc_Clan_And_Hoan_Mansion/`: 4 files (2 characters x 2)
    - `04_Brokers_And_Brothels/`: 6 files (3 characters x 2)
    - `05_Imperial_Court_And_Officials/`: 6 files (3 characters x 2)
    - `06_Buddhist_And_Spiritual/`: 2 files (1 character x 2)
    - `07_Warlord_Camp_And_Rebels/`: 2 files (1 character x 2)
  * 1 cropped view artifact:
    - `01_Main_Protagonists/views/thuy_kieu_maiden_portrait.png`
- **Integrity of Core Character Portraits**:
  * All 14 core character portraits specified in `CORE_EP01_CHARACTERS` (`test_m2_hygiene.py:25–40`) exist, are valid PNG format, have size > 500,000 bytes, and match exact expected pixel dimensions ((1280, 720) for 720p portraits and (1376, 768) for master portraits). They are **100% intact and undamaged**.
- **Generator Identification & Process Activity**:
  * Source 1: `tools/update_banana_turnaround_prompts.py:426–427` hardcoded `master_path` and `p720_path` to `04_Assets/characters/{folder}/{char_id}_turnaround_sheet_*.png`, injecting them into `02_AI_Prompts/gemini_banana_prompts.json`.
  * Source 2: `tools/manage_turnaround_sheets.py:17` set `CHARACTERS_DIR = BASE_DIR / "04_Assets" / "characters"`, and line 97 exported crop output to `target_sheet["path"].parent / "views"`.
  * Process check via `Get-CimInstance Win32_Process` and filesystem timestamp scan: **NO ACTIVE GENERATION PROCESS IS RUNNING**. The file count is static at 117.

### 1.2 Defect 2: String-Replacement Bug in Fallback Concat
- **Bug Location**: `05_Production_Pipeline/production_orchestrator.py:1170`.
  ```python
  1170:             ffprobe_exe = ffmpeg_exe.replace("ffmpeg", "ffprobe")
  ```
- **Empirical Failure Reproduction**:
  * In active Windows environment, `get_ffmpeg()` returns:
    `C:\Users\Admin\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-9.0.2-full_build\bin\ffmpeg.exe`.
  * `ffmpeg_exe.replace("ffmpeg", "ffprobe")` blindly replaces all instances of `"ffmpeg"`, transforming the directory `ffmpeg-9.0.2-full_build` to `ffprobe-9.0.2-full_build`.
  * Direct check: `os.path.exists("...\\ffprobe-9.0.2-full_build\\bin\\ffprobe.exe") == False`.
  * Calling `subprocess.run(probe_cmd)` raises `FileNotFoundError`, silently caught by line 1180 `except Exception: has_a = True`.
  * On clips without audio (`-an`), `has_audio_list` incorrectly marks them as `True`. Line 1186 appends `[{i}:a]aresample=...`.
  * FFmpeg in fallback concat crashes with verbatim error:
    `[fc#0 @ ...] Stream specifier ':a' in filtergraph description [0:a]... matches no streams.`
    `Error binding filtergraph inputs/outputs: Invalid argument`
    `Result before fix: None`.
- **Canonical Helper**:
  * `05_Production_Pipeline/audio_continuity_engine.py:59` already provides `get_ffprobe() -> str`, which correctly searches `DEFAULT_FFPROBE_PATHS` and resolves to the real, valid `ffprobe.exe` (`os.path.exists == True`).
  * However, `production_orchestrator.py` at line 106 only imported `AudioContinuityEngine, get_ffmpeg` and failed to import or use `get_ffprobe`.

---

## 2. Logic Chain

1. **Step 1 (Asset Invariant Root Cause)**:
   - Observation 1.1 establishes that Git tracks exactly 62 files in `04_Assets/characters/`, and all 62 files (including the 14 core portraits) are 100% undamaged.
   - Observation 1.1 proves that the test failure in `tests/test_m2_hygiene.py:149` is caused solely by the presence of 55 untracked files on disk (`assert 117 == 62`).
   - Observation 1.1 reveals that these files were created by model sheet generation workflows whose output paths in `tools/update_banana_turnaround_prompts.py` and `tools/manage_turnaround_sheets.py` pointed into `04_Assets/characters/`.
2. **Step 2 (Safe Relocation Strategy)**:
   - Because these 55 files represent genuine artistic deliverables (character turnaround sheets and views) and must not be permanently deleted, they must be cleanly relocated to `04_Assets/turnaround_sheets/`.
   - By moving ONLY untracked files (`git ls-files` diff), `04_Assets/characters/` will return to exactly 62 canonical files.
   - Updating `tools/update_banana_turnaround_prompts.py` and `tools/manage_turnaround_sheets.py` permanently prevents future tool runs from re-polluting `04_Assets/characters/`.
   - Once relocated, `test_m2_hygiene.py::test_character_portraits_unharmed` evaluates `total_char_files == 62` as `True`, restoring `6/6 PASSED` on the hygiene suite.
3. **Step 3 (Fallback Concat Probe Fix)**:
   - Observation 1.2 demonstrates that line 1170 of `production_orchestrator.py` crashes on silent clips because it uses naive `.replace("ffmpeg", "ffprobe")`, mutating the parent folder name on Windows.
   - Observation 1.2 proves that `audio_continuity_engine.get_ffprobe()` already resolves the genuine executable path without path mutation.
   - Importing `get_ffprobe` at line 106 and replacing line 1170 with `ffprobe_exe = get_ffprobe()` ensures `has_a` is accurately evaluated as `False` for silent clips (`-an`).
   - Line 1188 then correctly injects `aevalsrc=0:d=10.0:s=48000:c=stereo[a{i}]`, allowing FFmpeg fallback concat to complete with exit code 0 and produce a valid master.
4. **Step 4 (Readiness for Implementation)**:
   - The concrete diffs and step-by-step guide are fully documented in `remediation_plan.md`. Worker 2 can implement both fixes immediately with zero ambiguity.

---

## 3. Caveats

- **No Active Process Running**: Confirmed that the turnaround generation process that ran earlier has fully terminated; no background task or script is actively writing to `04_Assets/characters/`.
- **Primary Engine Immunity**: In normal operation, `AudioContinuityEngine.stitch_with_audio_crossfade` is the primary concat engine, which already uses `get_ffprobe()` properly and handles missing audio without crashing. The defect at line 1170 was isolated strictly to the hard-cut fallback concat branch.
- **Read-Only Explorer Discipline**: In accordance with the Teamwork Explorer role constraints, this agent has performed read-only analysis and empirical validation in isolated scratch tests, without modifying production files directly. The implementation is reserved for Worker 2.

---

## 4. Conclusion

The root causes of both blocking defects are fully understood, empirically reproduced, and proven resolvable:
1. **Defect 1**: 55 untracked turnaround files must be relocated to `04_Assets/turnaround_sheets/`, restoring `04_Assets/characters/` to exactly 62 canonical files and passing `test_m2_hygiene.py` 6/6.
2. **Defect 2**: `production_orchestrator.py` must import `get_ffprobe` (line 106) and call `get_ffprobe()` (line 1170), resolving the silent clip fallback concat crash.
3. **Execution Ready**: Worker 2 has a complete, verified execution plan in `remediation_plan.md`.

---

## 5. Verification Method

To independently verify the investigation findings and test the remediation:

```bash
# 1. Verify exact tracked vs physical count in 04_Assets/characters:
python -c "import subprocess, os, pathlib; g = set(pathlib.Path(p).resolve() for p in subprocess.run(['git', 'ls-files', '04_Assets/characters'], capture_output=True, text=True).stdout.splitlines() if p); a = set(f.resolve() for f in pathlib.Path('04_Assets/characters').rglob('*') if f.is_file()); print('Tracked:', len(g)); print('Physical:', len(a)); print('Untracked:', len(a - g))"
# Expected: Tracked = 62, Physical = 117, Untracked = 55.

# 2. Verify all 14 core portraits are intact:
python -c "from PIL import Image; from pathlib import Path; p = Path('04_Assets/characters/01_Main_Protagonists/thuy_kieu_maiden_16yo_master.png'); assert p.exists() and p.stat().st_size > 500000; print('Portraits OK')"

# 3. Verify get_ffprobe() existence vs naive replacement failure:
python -c "from audio_continuity_engine import get_ffmpeg, get_ffprobe; import os; f = get_ffmpeg(); c = f.replace('ffmpeg', 'ffprobe'); r = get_ffprobe(); print('Corrupted exists:', os.path.exists(c)); print('Canonical exists:', os.path.exists(r))"
# Expected: Corrupted exists: False, Canonical exists: True.

# 4. Run adversarial probe suite (5/5 PASS):
python -m pytest tests/test_adversarial_m4_audio_concat_probe.py -v
```
