# THẬP NGŨ NIÊN (THE FIFTEEN SPRINGS)
## BÁO CÁO KIỂM TOÁN VÀ ĐÁNH GIÁ MỨC ĐỘ SẴN SÀNG CỦA PIPELINE SẢN XUẤT TẬP 01
### Milestone M3: Ep01 10-Scene Production Pipeline Readiness & Start Frame Audit

**Tác giả**: Explorer 1 (Milestone M3)  
**Ngày thực hiện**: 2026-10-09  
**Workspace**: `c:\Projects\KieuStory`  
**Thư mục làm việc**: `c:\Projects\KieuStory\.agents\teamwork\explorer_m3_1`  
**Tài liệu quy chuẩn đối chiếu**: `ORIGINAL_REQUEST.md`, `AGENTS.md`, `GEMINI.md`, `PROJECT.md`, `GATE_STATUS.md`

---

## 1. TỔNG QUAN ĐIỀU HÀNH & BẢNG ĐIỂM KIỂM TOÁN (EXECUTIVE SUMMARY)

Đợt kiểm toán kỹ thuật chuyên sâu cho Milestone M3 đã rà soát toàn diện 140 shots thuộc 10 Cảnh đầu tiên của Tập 01 (Scenes 01 đến 10), đối soát trực tiếp các module cốt lõi của pipeline sản xuất (`05_Production_Pipeline/production_orchestrator.py`, `run_shot.py`, `multi_worker_orchestrator.py`, `antigravity_critic_gate.py`), kho tài nguyên hình ảnh (`04_Assets/characters/`, `04_Assets/keyframes/`, `04_Assets/backgrounds/`), và kho prompt (`episodes/ep01/prompts/muse_prompts.json`, `02_AI_Prompts/gemini_banana_prompts.json`).

### Bảng Điểm Kiểm Toán Sẵn Sàng (Readiness Scorecard)

| Hạng mục kiểm toán | Hiện trạng thực tế | Mức độ sẵn sàng | Hành động bắt buộc trong M3 |
|---|---|:---:|---|
| **Start Frame Resolution (140 shots Ep01)** | 105 Cinematic Cuts (80 tồn tại, 25 trả về None); 35 Continuous Takes (30 có fallback, 5 cần prev_tail) | 🟡 **78.6%** | Bổ sung asset reference mapping cho 25 Cinematic Cuts còn thiếu từ kho Banana prompts và keyframes có sẵn. |
| **Pipeline Critic Gate Integration** | `evaluate_shot_gate` và `evaluate_scene_gate` đã import ở đầu file `production_orchestrator.py` nhưng **CHƯA ĐƯỢC GỌI** ở bất kỳ hàm nào (`render_single_shot`, `batch_render_scene`, `concat_scene_shots`). | 🔴 **Chưa tích hợp** | Tích hợp gọi `evaluate_shot_gate` sau mỗi shot render, bổ sung vòng lặp re-take tự động (score < 0.8, tối đa 2 lần, tăng version `_v*`), và gọi `evaluate_scene_gate` trước khi concat master. |
| **Audio Guard Formatting** | 100% prompt trong `ep01/prompts/muse_prompts.json` (192/192) đã có hậu tố cấm BGM. Tuy nhiên, `run_shot.py` dùng chuỗi fallback thiếu tiền tố `"Quy tắc âm thanh:"` so với AGENTS.md §3. | 🟢 **95%** | Đồng bộ hóa 100% chuỗi Audio Guard fallback trong `run_shot.py` và bổ sung kiểm tra kiểm duyệt trước khi dispatch render. |
| **Worker Execution & Test Isolation** | Đã cài đặt `invisible_playwright` và `agent-browser`. Tuy nhiên pipeline thiếu cờ `dry-run` / `mock` khiến test tự động bị treo (hang) khi chờ browser bên ngoài. | 🟡 **Cần bổ sung Mock** | Triển khai chế độ `MUSE_DRY_RUN=1` sinh synthetic video nhanh qua FFmpeg lavfi để bộ test suite chạy hoàn toàn tự động trong < 15 giây. |
| **Bộ Test Suite M3** | Đã có 30/30 test cho M1 (`test_critic_gate.py`). Chưa có test suite chuyên biệt kiểm tra tích hợp đầu cuối cho toàn bộ 140 shots Ep01 và vòng lặp retake. | 🟡 **Cần viết mới** | Xây dựng test suite `tests/test_production_pipeline_m3.py` bao phủ trọn vẹn 140 shots, cơ chế Cut/Take, Audio Guard, và vòng lặp retake. |

---

## 2. MỤC TIÊU 1: KIỂM TOÁN START FRAME CHO TOÀN BỘ 140 SHOT EP01 (SCENES 01 - 10)

### 2.1. Phương Pháp Kiểm Toán Thực Nghiệm
Sử dụng script kiểm toán thực thi trực tiếp trên Python 3.11:
- Nạp danh mục shot chuẩn qua `production_orchestrator.get_all_shots("ep01")`.
- Duyệt qua từng shot của các Scene từ `ep01_scene01` đến `ep01_scene10`.
- Gọi `production_orchestrator.classify_shot_take(shot_id, shot_data=shot_data)`.
- Gọi `production_orchestrator.resolve_start_frame(shot_id, shot_data, return_classification=True)`.
- Kiểm tra tính tồn tại vật lý trên ổ đĩa (`os.path.exists`) của file ảnh được trả về.

### 2.2. Bảng Phân Bổ Tiến Độ & Start Frame Theo Từng Cảnh

| Mã Cảnh (Scene ID) | Tổng Shot | Cinematic Cuts | Cuts Đã Có Ảnh | Cuts Trả Về None | Continuous Takes | Takes Có Fallback | Takes Trả Về None (Cold) |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| **ep01_scene01** (Prologue 198x) | 21 | 20 | 1 | **19** | 1 | 0 | 1 |
| **ep01_scene02** (Gia đình Họ Vương) | 15 | 13 | 13 | 0 | 2 | 2 | 0 |
| **ep01_scene03** (Khuê phòng Kiều & Vân) | 15 | 11 | 11 | 0 | 4 | 4 | 0 |
| **ep01_scene04** (Hội Thanh Minh) | 12 | 3 | 2 | **1** | 9 | 5 | 4 |
| **ep01_scene05** (Mộ Đạm Tiên) | 27 | 24 | 22 | **2** | 3 | 3 | 0 |
| **ep01_scene06** (Bờ Suối Rặng Liễu) | 14 | 13 | 11 | **2** | 1 | 1 | 0 |
| **ep01_scene07** (Thư Phòng Kim Trọng) | 8 | 1 | 1 | 0 | 7 | 7 | 0 |
| **ep01_scene08** (Bức Tường Hoa) | 8 | 4 | 3 | **1** | 4 | 4 | 0 |
| **ep01_scene09** (Kim Kiều Tương Kiến) | 6 | 5 | 5 | 0 | 1 | 1 | 0 |
| **ep01_scene10** (Thề Nguyền Vườn Thúy) | 14 | 11 | 11 | 0 | 3 | 3 | 0 |
| **TỔNG CỘNG (EP01 SCENES 01-10)** | **140** | **105** | **80 (76.2%)** | **25 (23.8%)** | **35** | **30 (85.7%)** | **5 (14.3%)** |

### 2.3. Phân Tích Bản Chất 105 Cinematic Cuts vs 35 Continuous Takes

1. **Quy tắc phân loại Cú máy (`classify_shot_take`)**:
   - `shot_num == 1`: Luôn là `CINEMATIC_CUT` (bắt đầu cảnh mới, không được dùng tail frame của scene trước).
   - `character_anchor` thay đổi giữa hai shot liên tiếp: `CINEMATIC_CUT` (đổi nhân vật hoặc góc nhìn độc lập).
   - `character_anchor` là `"none"` hoặc rỗng `""`: `CINEMATIC_CUT` (cảnh vật thiên nhiên, đại cảnh, đồ vật, chuyển đổi không gian).
   - `character_anchor` giống hệt nhau (và khác `"none"`): `CONTINUOUS_TAKE` (nối tiếp hành động của cùng nhân vật).

2. **Nguồn gốc của 80 Start Frame hợp lệ hiện tại**:
   - `04_Assets/characters/01_Main_Protagonists/`: 56 shots (Thúy Kiều: 16yo maiden, Thúy Vân: 16yo maiden, Kim Trọng: 18yo).
   - `04_Assets/characters/02_Vuong_Family_And_Fate/`: 23 shots (Vương Ông: 55yo, Vương Bà: 50yo, Vương Quan: 16yo, Đạm Tiên).
   - `04_Assets/keyframes/prologue_shot1/`: 1 shot (`frame_000.jpg` cho `ep01_scene01_shot01`).

3. **Nguyên nhân gốc rễ (Root Cause) của 25 Cinematic Cuts bị thiếu Start Frame (trả về `None`)**:
   - **Nhóm 1: `ep01_scene01` (19 shots thiếu: Shot 02 đến Shot 21, trừ Shot 08 là take)**:
     * Trong `episodes/ep01/prompts/muse_prompts.json`, các shot này không có trường `character_asset_ref`.
     * Trường `reference_start_frame` chỉ ghi chuỗi mô tả văn bản tiếng Việt dạng: `"Frame cuối (239/10s) của ep01_scene01_shot{N-1}_10s_v1.mp4:..."`. Chuỗi này không phải là đường dẫn file hợp lệ.
     * Nhân vật là `cu_ong_198x` và `dua_chau_198x` (thuộc bối cảnh khung truyện hiện đại thập niên 1980), không nằm trong danh mục 32 nhân vật cổ phong thời Gia Tĩnh tại `04_Assets/characters/`. Do đó, hàm `resolve_character_portrait_from_anchor` không tìm thấy và trả về `None`.
     * Tuy nhiên, trong `04_Assets/keyframes/`, đã tồn tại sẵn các thư mục:
       - `prologue_shot1/frame_000.jpg` (đang dùng cho Shot 01)
       - `prologue_shot2/frame_000.jpg` (dành cho Shot 02)
       - `prologue_shot3/frame_000.jpg` (dành cho Shot 03)
       - `prologue_shot4/clean_frame_000.jpg` (dành cho Shot 04)
       - Và trong `04_Assets/archive/ep01_legacy_v1/`, toàn bộ 21 video raw `ep01_scene01_shot01_10s.mp4` đến `shot21_10s_v1.mp4` đã được lưu trữ an toàn. Frame 0 của các video legacy này chính là hình ảnh ngoại cảnh làng quê Bắc Bộ 1980s sạch sẽ, hoàn toàn không bị nhiễm Thúy Kiều!
   - **Nhóm 2: `ep01_scene04_shot01` (1 shot thiếu)**:
     * Cảnh mở đầu Hội Thanh Minh: Đại cảnh ngựa xe như nước áo quần như nêm (`quan_chung_thanh_minh`).
     * `reference_start_frame` trỏ tới `04_Assets/keyframes/ep01_scene04_shot01/start_frame_720p.png` (đã bị xóa trong đợt dọn dẹp M2 do nằm trong thư mục `ep01_*`).
     * Trong khi đó, file ảnh master chuẩn 720p thực tế đang nằm tại: `04_Assets/keyframes/crowd_qingming_festival_720p.png` (kích thước 1280x720, dung lượng 1.8MB, hoàn hảo).
   - **Nhóm 3: Cảnh vật & Ngoại cảnh không nhân vật (5 shots thiếu)**:
     * `ep01_scene05_shot07`: Suối ngọc rặng liễu, ba chị em đi xa dần (anchor: `""`).
     * `ep01_scene05_shot10`: Đặc tả cận cảnh nấm mồ hoang Đạm Tiên cỏ úa (anchor: `""`).
     * `ep01_scene06_shot01`: Toàn cảnh hoàng hôn rặng liễu (anchor: `""`).
     * `ep01_scene06_shot02`: Khúc ngoặt đường mòn hoàng hôn (anchor: `""`).
     * `ep01_scene08_shot01`: Toàn cảnh bức tường hoa ngăn cách hai vườn (anchor: `""`).
     * Cả 5 shot này đều có prompt sinh ảnh Imagen 3 hoàn chỉnh trong `02_AI_Prompts/gemini_banana_prompts.json` (`ep01_start_frames`), nhưng chưa có file ảnh vật lý tương ứng hoặc chưa được map đường dẫn tĩnh trong `resolve_start_frame()`.

4. **Hành vi Cold-Start của 35 Continuous Takes**:
   - Khi chạy từ đầu (chưa render shot trước), `find_tail_frame(prev_shot_id)` trả về `None`.
   - 30 shot có nhân vật trong Character Bible tự động rơi về ảnh chân dung chuẩn (fallback an toàn).
   - 5 shot (`ep01_scene01_shot08`, `ep01_scene04_shot02, 03, 04, 12`) trả về `None` khi cold-start. Tuy nhiên, khi pipeline vận hành tuần tự theo chuỗi Head-Tail, shot trước vừa render xong sẽ trích xuất ngay `clean_frame_239.jpg`, cho phép shot sau nạp vào liền mạch 100%.

---

## 3. MỤC TIÊU 2: KIỂM TOÁN TÍCH HỢP CRITIC GATE TRONG PIPELINE

### 3.1. Hiện Trạng Module `production_orchestrator.py`
Qua phân tích mã nguồn chi tiết tại lines 46-55 và lines 624-840 của `05_Production_Pipeline/production_orchestrator.py`:
- Dòng 46-55:
  ```python
  try:
      from antigravity_critic_gate import evaluate_shot_gate, evaluate_scene_gate, VideoCriticVerdict
  except ImportError:
      ...
  ```
  Module đã import thành công các hàm kiểm duyệt chất lượng.
- **Tuy nhiên**:
  * Hàm `render_single_shot` (lines 624-678): Sau khi gọi `run_shot_pipeline(...)` và nhận kết quả `success`, hàm này **KHÔNG HỀ GỌI** `evaluate_shot_gate`. Video vừa tạo được chấp nhận ngay lập tức mà không qua thẩm định.
  * Hoàn toàn **KHÔNG CÓ VÒNG LẶP RETAKE**: Nếu video bị dị tật thị giác, mắt lé, hoặc sai nhân vật, hệ thống không tự động render lại mà vẫn lưu file.
  * Hàm `batch_render_scene` (lines 680-735): Chỉ kiểm tra `if video_path:` hoặc gọi `render_single_shot`. Khi toàn bộ các shot trong cảnh render xong, hàm này chuyển thẳng sang `concat_scene_shots` mà **KHÔNG GỌI** `evaluate_scene_gate`.
  * Hàm `concat_scene_shots` (lines 753-839): Chỉ ghép video bằng `AudioContinuityEngine` hoặc `ffmpeg filter_complex concat`. Không hề thẩm định trục 180 độ, độ chênh màu, hay nhịp điệu cắt dựng.

### 3.2. Thiết Kế Kiến Trúc Tích Hợp Critic Gate 2 Tầng & Retake Loop

Để đạt chuẩn Acceptance Criteria của Milestone M3 ("Tích hợp cổng kiểm duyệt 2 tầng Antigravity với điểm phê duyệt >= 0.8 và vòng lặp tự động retake"):

```
[Start Frame Resolved] ──► [Muse Worker Render] ──► [Raw Video 10s]
                                                           │
                                                           ▼
                                                [evaluate_shot_gate]
                                                           │
                                        ┌──────────────────┴──────────────────┐
                                  (Score >= 0.8)                        (Score < 0.8)
                                        │                                     │
                                        ▼                                     ▼
                                  [Chấp thuận Shot]                 [Retake Loop (max 2)]
                                  [Trích Frame 239]                 (Tăng version _v2, _v3)
                                        │                                     │
                                        │◄────────────────────────────────────┘
                                        ▼
                        [Toàn bộ Shots trong Scene hoàn tất]
                                        │
                                        ▼
                              [evaluate_scene_gate]
                                        │
                    ┌───────────────────┼───────────────────┐
             (Score >= 0.8)     (Trigger Color Match)  (Trigger Trim Static)
                    │                   │                     │
                    │           [ffmpeg eq/curves]     [Trim freeze > 2s]
                    │                   │                     │
                    └───────────────────┴─────────────────────┘
                                        │
                                        ▼
                      [AudioContinuityEngine Concat & Master]
```

#### Quy Chuẩn Retake Loop Cấp Độ Shot:
1. Khi shot render xong và có file video `target_video`:
2. Lấy `expected_character = get_character_anchor(shot_id, shot_data)`.
3. Gọi `verdict = evaluate_shot_gate(shot_id, str(target_video), expected_character, prompt)`.
4. Nếu `verdict.approved` (`overall_score >= 0.8` và `suggested_action != "RETAKE_SHOT"`):
   - Lưu trữ video đạt chuẩn.
   - Trích xuất Tail Frame `clean_frame_239.jpg`.
5. Nếu `not verdict.approved` (`overall_score < 0.8` hoặc `suggested_action == "RETAKE_SHOT"`):
   - Ghi log cảnh báo: `[CRITIC GATE REJECTED] Score: {verdict.overall_score:.2f}, Defects: {verdict.shot_eval.visual_defects}`.
   - Khởi tạo lần thử lại (retake `attempt <= max_retakes`, mặc định `max_retakes = 2`).
   - Tăng version video (ví dụ từ `_v1` lên `_v2`, `_v3`).
   - Gọi lại worker render với prompt có bổ sung chỉ thị khắc phục từ `verdict.critique_notes`.
   - Nếu sau `max_retakes` vẫn không đạt: báo lỗi dừng batch để can thiệp thủ công, không để video hỏng lọt vào master.

#### Quy Chuẩn Thẩm Định Cấp Độ Scene (`evaluate_scene_gate`):
1. Trước khi ghép master trong `concat_scene_shots`:
2. Lấy danh sách video của tất cả các shot trong cảnh: `shot_paths = [str(find_rendered_video(sid)) for sid, _ in scene_shots]`.
3. Gọi `scene_verdict = evaluate_scene_gate(scene_id, shot_paths)`.
4. Nếu `scene_verdict.scene_eval.trigger_color_match`:
   - Tự động áp dụng bộ cân bằng màu sắc qua FFmpeg trước khi stitch.
5. Nếu `scene_verdict.scene_eval.trigger_trim_static`:
   - Tự động cắt tỉa các khung hình đơ tĩnh kéo dài > 2 giây ở biên tiếp giáp.
6. Khi `scene_verdict.approved` (score >= 0.8): Cho phép xuất Scene Master hoàn thiện.

---

## 4. MỤC TIÊU 3: ĐỐI SOÁT ĐỊNH DẠNG AUDIO GUARD

### 4.1. Quy Chuẩn Gốc Tại AGENTS.md §3
AGENTS.md §3 quy định nguyên văn chỉ thị Audio Guard bắt buộc khi gửi prompt sang Muse.ai:
> `[Mô tả chuyển động hình ảnh] + Âm thanh: [Mô tả foley/thoại]. Quy tắc âm thanh: Tuyệt đối KHÔNG sinh nhạc nền (no music/BGM), không âm thanh điện tử, không tạp âm rè nhiễu. Chỉ sinh âm thanh môi trường tự nhiên (foley, ambience) và thoại nhân vật chân thực.`

### 4.2. Hiện Trạng Đối Soát Trong Codebase
1. **Trong File Prompt (`episodes/ep01/prompts/muse_prompts.json` & `02_AI_Prompts/muse_ai_video_prompts.json`)**:
   - `ep01/prompts/muse_prompts.json`: **192/192 prompts (100%)** đã có chỉ thị cấm BGM. Trong đó **188/192 prompts** chứa chính xác từng ký tự của câu chuẩn:
     `"Quy tắc âm thanh: Tuyệt đối KHÔNG sinh nhạc nền (no music/BGM), không âm thanh điện tử, không tạp âm rè nhiễu. Chỉ sinh âm thanh môi trường tự nhiên (foley, ambience) và thoại nhân vật chân thực."`
   - `02_AI_Prompts/muse_ai_video_prompts.json`: **1149/1149 prompts (100%)** chứa chỉ thị cấm BGM.
2. **Trong `run_shot.py` (lines 136-140)**:
   ```python
   audio_guard = " Âm thanh: Chỉ sinh âm thanh môi trường tự nhiên (foley, ambience) và thoại nhân vật chân thực. Tuyệt đối KHÔNG sinh nhạc nền (no music/BGM), không âm thanh điện tử, không tạp âm rè nhiễu."
   if "Tuyệt đối KHÔNG sinh nhạc nền" not in prompt and "Strictly NO background music" not in prompt:
       prompt = prompt.rstrip() + audio_guard
   ```
   *Nhận xét*: Chuỗi fallback trong `run_shot.py` đảo trật tự ("Âm thanh: Chỉ sinh..." đứng trước "Tuyệt đối KHÔNG sinh nhạc nền...") và thiếu tiền tố `"Quy tắc âm thanh:"`. Cần chuẩn hóa lại cho khớp 100% với văn bản AGENTS.md §3.
3. **Trong `production_orchestrator.py` & `multi_worker_orchestrator.py`**:
   - Cả hai file này chỉ chuyển tiếp chuỗi prompt thô, chưa có hàm tiền xử lý (pre-flight validation) để chặn đứng các prompt thiếu Audio Guard trước khi gửi sang worker.
4. **Trong `antigravity_critic_gate.py` (`_check_audio_stream`)**:
   - Module kiểm duyệt đã tích hợp lệnh `ffprobe` kiểm tra luồng âm thanh AAC/PCM/MP3 và channel > 0. Nếu video không có nhạc nền hoặc silent thì được tính là tuân thủ Audio Guard.

---

## 5. MỤC TIÊU 4: MÔ HÌNH VẬN HÀNH WORKER & GIẢI PHÁP TEST TỰ ĐỘNG KHÔNG TREO

### 5.1. Cấu Hình Runner Muse.ai Thực Tế
Dự án hỗ trợ 2 cơ chế runner:
1. **InvisiblePlaywright Engine (`muse_invpw_driver.py`)**:
   - Sử dụng Firefox C++ Camoufox Stealth Engine.
   - Nạp profile định danh `phu2234` từ `C:\Users\Admin\AntigravityProfiles\phu2234`.
   - Cơ chế headless/headed, tự động vượt qua Cloudflare và checkpoint.
2. **Agent-Browser CLI (`agent-browser --session muse`)**:
   - Quản lý phiên cố định `muse` (Worker 1) và `muse_w2` .. `muse_w6` (Workers 2-6).
   - Thư mục download cô lập: `04_Assets/temp_downloads/w<id>` thông qua biến môi trường `AGENT_BROWSER_DOWNLOAD_PATH`.
   - Kiểm tra trạng thái đăng nhập tức thì (0ms) qua tệp `.target` và `.pid` trong `~/.agent-browser/`.

### 5.2. Giải Pháp Kiểm Thử Tự Động Đầu Cuối Không Phụ Thuộc Browser (Zero-Hang Strategy)
Khi chạy trong môi trường CI/CD hoặc chạy unit test `pytest`, tuyệt đối không thể để runner gọi sang trình duyệt thật vì sẽ bị treo hoặc phụ thuộc vào tài khoản cloud.

**Giải pháp kỹ thuật chuẩn hóa**:
Bổ sung chế độ **Mock / Dry-Run Engine** tích hợp sẵn trong pipeline:
- Kích hoạt qua biến môi trường `MUSE_DRY_RUN=1` hoặc CLI flag `--dry-run`.
- Khi cờ này bật:
  * Hàm `run_shot_pipeline` không gọi `agent-browser` hay `invisible_playwright`.
  * Thay vào đó, nó tự động sinh 1 video MP4 mẫu 10 giây (hoặc 1 giây trong test mode) bằng FFmpeg (`lavfi color=c=navy:s=1280x720:d=10:r=24` kèm âm thanh `sine=f=440:d=10` chuẩn AAC 48kHz).
  * Video này được ghi vào đúng đường dẫn đích `04_Assets/videos/<shot_id>_10s_v1.mp4`.
  * Trích xuất frame 239 bằng OpenCV vào `04_Assets/keyframes/<shot_id>/clean_frame_239.jpg`.
  * Toàn bộ các bước tiếp theo của pipeline (Shot Gate, Scene Gate, Concat FFmpeg, EBU R128 Loudness Normalization) đều được thực thi thật 100% trên file video này.
- **Lợi ích**: Toàn bộ luồng nghiệp vụ phức tạp của M3 có thể được kiểm chứng tự động từ đầu đến cuối chỉ trong 10-15 giây, với độ tin cậy tuyệt đối.

---

## 6. KẾ HOẠCH TRIỂN KHAI CHI TIẾT CHO WORKER (ACTIONABLE IMPLEMENTATION PLAN)

Để chuyển giao sang Worker trong Milestone M3, dưới đây là các nhiệm vụ và công thức mã nguồn (code recipes) cụ thể:

### Nhiệm Vụ 1: Giải Quyết 25 Start Frame Thiếu & Chuẩn Hóa Ánh Xạ
1. **Scene 04 Shot 01**:
   - Cập nhật `character_asset_ref` trong `episodes/ep01/prompts/muse_prompts.json`:
     `"character_asset_ref": "04_Assets/keyframes/crowd_qingming_festival_720p.png"`
   - Bổ sung alias trong `production_orchestrator.py:resolve_start_frame`:
     ```python
     if "crowd_qingming_festival" in str(asset_ref).lower() or "quan_chung_thanh_minh" in curr_anchor:
         cand = KEYFRAMES_DIR / "crowd_qingming_festival_720p.png"
         if cand.exists(): return str(cand.resolve())
     ```
2. **Scene 01 Shots 01 đến 04**:
   - Gán `character_asset_ref` trực tiếp:
     * Shot 01: `04_Assets/keyframes/prologue_shot1/frame_000.jpg`
     * Shot 02: `04_Assets/keyframes/prologue_shot2/frame_000.jpg`
     * Shot 03: `04_Assets/keyframes/prologue_shot3/frame_000.jpg`
     * Shot 04: `04_Assets/keyframes/prologue_shot4/clean_frame_000.jpg`
3. **Scene 01 Shots 05 đến 21 & Các Cảnh Vực Ngoại Cảnh**:
   - Viết helper script trích xuất Start Frame chuẩn 720p từ video legacy sạch hoặc từ keyframes prologue và lưu vào `04_Assets/keyframes/prologue_shot{N}/start_frame_720p.png`.
   - Cập nhật logic `resolve_start_frame` để tự động nạp ảnh start frame từ thư mục keyframe tương ứng khi có sẵn.

### Nhiệm Vụ 2: Tích Hợp Shot Gate & Vòng Lặp Retake Vào `production_orchestrator.py`
Chỉnh sửa hàm `render_single_shot` trong `production_orchestrator.py`:
- Thêm tham số: `enable_critic: bool = True`, `max_retakes: int = 2`.
- Sau khi `run_shot_pipeline` tạo file video thành công:
  ```python
  if enable_critic and evaluate_shot_gate is not None:
      video_path = find_rendered_video(shot_id)
      expected_char = get_character_anchor(shot_id, shot_data)
      verdict = evaluate_shot_gate(
          shot_id=shot_id,
          video_path=str(video_path),
          expected_character=expected_char,
          prompt=prompt
      )
      print(f"   📊 Antigravity Shot Gate: Score = {verdict.overall_score:.2f} | Approved = {verdict.approved}")
      
      retake_count = 0
      while not verdict.approved and retake_count < max_retakes:
          retake_count += 1
          print(f"   🔄 [RETAKE {retake_count}/{max_retakes}] Shot {shot_id} không đạt chuẩn ({verdict.critique_notes}). Đang render lại...")
          success, tail_frame = run_shot_pipeline(
              shot_id, resolved_frame, prompt, prev_video,
              session_name=session_name, download_dir=download_dir,
              reverse_motion=reverse_motion
          )
          if not success:
              break
          video_path = find_rendered_video(shot_id)
          verdict = evaluate_shot_gate(
              shot_id=shot_id,
              video_path=str(video_path),
              expected_character=expected_char,
              prompt=prompt
          )
          print(f"   📊 Antigravity Shot Gate (Retake {retake_count}): Score = {verdict.overall_score:.2f} | Approved = {verdict.approved}")
      
      if not verdict.approved:
          print(f"[!] Cảnh báo: Shot {shot_id} không vượt qua Shot Gate sau {max_retakes} lần retake.")
          return False
  ```

### Nhiệm Vụ 3: Tích Hợp Scene Gate Vào `batch_render_scene` & `concat_scene_shots`
Trong `batch_render_scene` và `concat_scene_shots`:
- Kiểm tra toàn bộ danh sách shot video:
  ```python
  if evaluate_scene_gate is not None:
      shot_video_paths = [str(find_rendered_video(sid)) for sid, _ in scene_shots if find_rendered_video(sid)]
      if len(shot_video_paths) == len(scene_shots):
          scene_verdict = evaluate_scene_gate(scene_id, shot_video_paths)
          print(f"\n🏛️ Antigravity Scene Gate ({scene_id}): Score = {scene_verdict.overall_score:.2f} | Action = {scene_verdict.suggested_action}")
          if not scene_verdict.approved and scene_verdict.suggested_action == "RETAKE_SHOT":
              print(f"[!] Scene Gate từ chối ghép Master cho {scene_id}: {scene_verdict.critique_notes}")
              return None
  ```

### Nhiệm Vụ 4: Chuẩn Hóa Đồng Bộ Audio Guard
Cập nhật `run_shot.py` lines 136-140:
```python
    canonical_guard = " Quy tắc âm thanh: Tuyệt đối KHÔNG sinh nhạc nền (no music/BGM), không âm thanh điện tử, không tạp âm rè nhiễu. Chỉ sinh âm thanh môi trường tự nhiên (foley, ambience) và thoại nhân vật chân thực."
    if "Tuyệt đối KHÔNG sinh nhạc nền" not in prompt and "Strictly NO background music" not in prompt:
        prompt = prompt.rstrip() + canonical_guard
```

### Nhiệm Vụ 5: Xây Dựng Bộ Test Suite `tests/test_production_pipeline_m3.py`
Thiết kế bộ test suite toàn diện gồm 6 nhóm test:
1. `test_01_all_140_shots_start_frame_resolution`: Kiểm tra 100% 140 shots của Ep01 Scenes 01-10 đều resolve thành công ra file tồn tại thực tế trên đĩa.
2. `test_02_character_invariant_safeguards_140_shots`: Đối soát 140 shots, bảo đảm không có shot Kim Trọng, Vương Ông, Vương Quan, Thúy Vân nào bị trả về ảnh Thúy Kiều.
3. `test_03_shot_gate_retake_loop_integration`: Kiểm tra vòng lặp retake khi score < 0.8 và chấp thuận khi score >= 0.8.
4. `test_04_scene_gate_batch_integration`: Kiểm tra đánh giá scene gate khi đủ video các shot.
5. `test_05_audio_guard_directive_enforcement`: Kiểm tra định dạng Audio Guard tuân thủ chuẩn AGENTS.md §3 trên toàn bộ 140 prompts.
6. `test_06_dry_run_pipeline_end_to_end`: Chạy thử nghiệm mock render một scene 3 shots theo chuỗi Head-Tail, thẩm định critic gate và ghép master mà không treo browser.

---

## 7. KẾT LUẬN & ĐỀ XUẤT HÀNH ĐỘNG

Milestone M3 đã hội tụ đủ các tiền đề kỹ thuật từ M1 (module critic gate và classifier đã hoạt động 100%) và M2 (kho lưu trữ sạch sẽ, không còn file rác). Toàn bộ 25 điểm khuyết Start Frame và các điểm nghẽn tích hợp pipeline đã được khoanh vùng chính xác tới từng dòng code và tệp dữ liệu. 

Báo cáo này cung cấp đầy đủ luận cứ và mã mẫu để Worker triển khai trực tiếp, đưa hệ thống sản xuất Tập 01 đạt trạng thái hoàn thiện 100% sẵn sàng cho Milestone M4 (Scene Concat & Audio Mastering).
