# BÁO CÁO PHÂN TÍCH KỸ THUẬT (TECHNICAL REPORT) — EXPLORER M1 PROGRESSION 2 GEN2

> **Tác giả**: `explorer_m1_progression_2_gen2` (teamwork_preview_explorer)  
> **Người nhận**: `orchestrator_4` (`6f4a2aa2-4315-4660-8cb7-8352a7220c95`)  
> **Phạm vi**: Khảo sát & đề xuất giải pháp làm sạch Seeder, bảo đảm độ bền vững khi di trú Database và tối ưu chất lượng Schema cho Milestone 1 Iteration 2  
> **Thời điểm**: 2026-10-01T02:30:00Z  

---

## 1. Tóm Tắt Phát Hiện Cốt Lõi (Executive Summary)

1. **Vấn đề Reseed Bị Bỏ Qua Khi Di Trú Database Cũ (Critical Edge Case)**:
   - Trong `server/world/game_design_matrix_service.py` (dòng 88-96), hàm `seed_canonical_data(force=False)` hiện chỉ kiểm tra duy nhất bảng `story_acts`:
     ```python
     if not force:
         cur.execute("SELECT COUNT(*) as cnt FROM story_acts")
         if cur.fetchone()["cnt"] > 0:
             return {"status": "already_seeded"}
     ```
   - Khi một database file có sẵn từ phiên bản cũ chứa `story_acts` nhưng bảng `progression_benchmarks` có schema cũ (< 13 cột), phương thức `_check_and_migrate_schema` sẽ DROP bảng cũ và tạo lại bảng 13 cột rỗng.
   - Khi đó, lệnh `seed_canonical_data(force=False)` nhìn thấy `story_acts > 0` nên trả về ngay `{"status": "already_seeded"}` mà **không nạp dữ liệu cấp độ**. Hệ quả: `progression_benchmarks` hoàn toàn rỗng (0 hàng), khiến `get_level_progression_benchmark(level)` trả về `None`.
   - Lỗi này đã được tái lập và chứng minh qua script đối kháng `.agents/teamwork/reviewer_m1_progression_2/test_migration_adversarial.py`.

2. **Lệch Kiểu Dữ Liệu (Typing Discrepancy in `seed_canonical_data`)**:
   - Chữ ký hàm hiện tại khai báo trả về `Dict[str, int]`:
     ```python
     def seed_canonical_data(self, force: bool = False) -> Dict[str, int]:
     ```
   - Tuy nhiên, nhánh bỏ qua nạp dữ liệu lại trả về `{"status": "already_seeded"}` (value kiểu `str`).
   - Mypy báo lỗi trực tiếp:
     `server\world\game_design_matrix_service.py:95: error: Dict entry 0 has incompatible type "str": "str"; expected "str": "int" [dict-item]`.
   - Cần chuẩn hóa thành `Dict[str, Any]`.

3. **Vị Trí Của `seed_canonical_data` Giữa Seeder và Service**:
   - Yêu cầu nhiệm vụ nêu: *In `server/world/game_design_matrix_seeder.py`: Inspect `seed_canonical_data()`...*
   - Thực tế trong codebase: `server/world/game_design_matrix_seeder.py` chỉ định nghĩa hàm cấp thấp `seed_all_canonical_data(conn: sqlite3.Connection) -> Dict[str, int]`. Phương thức `seed_canonical_data` nằm tại `server/world/game_design_matrix_service.py`.
   - **Giải pháp tối ưu & tương thích kép**: Cung cấp hàm `seed_canonical_data(conn: sqlite3.Connection, force: bool = False) -> Dict[str, Any]` ngay trong `game_design_matrix_seeder.py`, đồng thời cập nhật phương thức `seed_canonical_data` trong `game_design_matrix_service.py` để cả hai tầng Seeder và Service đều bảo đảm an toàn dữ liệu và tuân thủ định kiểu.

4. **Độ An Toàn Kiểu Của `ProgressionBenchmarkRow` Trong `game_design_matrix_types.py`**:
   - `ProgressionBenchmarkRow` đã sử dụng decorator `@dataclass(slots=True, frozen=True)` chuẩn mực 2026.
   - Khảo sát xác nhận toàn bộ 13 trường dữ liệu tương ứng 1-1 chính xác tuyệt đối với 13 cột của bảng SQLite `progression_benchmarks` và vượt qua 100% kiểm tra `mypy`.

---

## 2. Bằng Chứng Thực Nghiệm & Tái Lập (Empirical Evidence)

### 2.1. Tái lập Lỗi Trống Dữ Liệu `progression_benchmarks`

Chạy script đối kháng của Reviewer 2:
```powershell
python .agents/teamwork/reviewer_m1_progression_2/test_migration_adversarial.py
```
**Kết quả thực tế ghi nhận**:
```
Post-init columns (13): ['level', 'target_exp', 'exp_to_next_level', 'cumulative_exp', 'player_base_hp', 'player_benchmark_dps', 'monster_base_hp', 'monster_base_dps', 'max_affix_tier_allowed', 'death_penalty_ratio', 'level_gap_safe_range', 'level_gap_penalty_exp', 'monster_benchmark_exp']
Post-init progression_benchmarks row count: 0
seed_canonical_data(force=False) result: {'status': 'already_seeded'}
Post-seeding progression_benchmarks row count: 0
Benchmark level 1: None
```
-> **Bảng `progression_benchmarks` rỗng 0 dòng sau khi chạy seeder mặc định!**

### 2.2. Kiểm chứng Mypy Type Error

Chạy kiểm tra kiểu:
```powershell
python -m mypy --explicit-package-bases server/world/game_design_matrix_service.py server/world/game_design_matrix_seeder.py server/world/game_design_matrix_types.py
```
**Kết quả thực tế**:
```
server\world\game_design_matrix_service.py:95: error: Dict entry 0 has incompatible type "str": "str"; expected "str": "int"  [dict-item]
server\world\game_design_matrix_seeder.py:148: error: Argument 2 to "_resolve_quest_zone" has incompatible type "str | None"; expected "str"  [arg-type]
```
- Dòng 95 của service bị lệch kiểu `Dict[str, int]` khi trả về chuỗi `"status": "already_seeded"`.
- Dòng 148 của seeder truyền `q_def.act_id` (kiểu `Optional[str]`) vào `_resolve_quest_zone(quest_id: str, act_id: str)`, thiếu khai báo `Optional[str]`.

### 2.3. Kiểm chứng Giải Pháp Thực Nghiệm Khép Kín

Đã thiết lập và chạy thành công script mô phỏng giải pháp tại `.agents/teamwork/explorer_m1_progression_2_gen2/test_seeder_fix_simulation.py`:
```powershell
python .agents/teamwork/explorer_m1_progression_2_gen2/test_seeder_fix_simulation.py
```
**Kết quả thực tế**:
```
Result of proposed seed_canonical_data(force=False): seeded_ok
Row count after proposed seed: 100
Second call correctly returned already_seeded!
ALL SIMULATION CHECKS PASSED 100%!
```

---

## 3. Đề Xuất Mã Nguồn Chi Tiết Dành Cho Worker M1 (Proposed Implementation)

### 3.1. File: `server/world/game_design_matrix_service.py`

#### Vấn đề:
1. Kiểu trả về `seed_canonical_data` là `Dict[str, int]`.
2. Chỉ kiểm tra `story_acts`, bỏ sót kiểm tra `progression_benchmarks`.

#### Đoạn Code Đề Xuất Thay Thế (Lines 88 - 97):

```python
<<<<
    def seed_canonical_data(self, force: bool = False) -> Dict[str, int]:
        """Nạp dữ liệu hạt giống chuẩn vào matrix database."""
        with self._get_connection() as conn:
            cur = conn.cursor()
            if not force:
                cur.execute("SELECT COUNT(*) as cnt FROM story_acts")
                if cur.fetchone()["cnt"] > 0:
                    return {"status": "already_seeded"}
            return seed_all_canonical_data(conn)
====
    def seed_canonical_data(self, force: bool = False) -> Dict[str, Any]:
        """Nạp dữ liệu hạt giống chuẩn vào matrix database."""
        with self._get_connection() as conn:
            cur = conn.cursor()
            if not force:
                cur.execute("SELECT COUNT(*) as cnt FROM story_acts")
                story_cnt = cur.fetchone()["cnt"]
                cur.execute("SELECT COUNT(*) as cnt FROM progression_benchmarks")
                bench_cnt = cur.fetchone()["cnt"]
                if story_cnt > 0 and bench_cnt > 0:
                    return {"status": "already_seeded"}
            return seed_all_canonical_data(conn)
>>>>
```

---

### 3.2. File: `server/world/game_design_matrix_seeder.py`

#### Vấn đề:
1. Thiếu hàm `seed_canonical_data(conn, force=False)` độc lập ở tầng Seeder để hỗ trợ các caller thao tác trực tiếp với `sqlite3.Connection`.
2. Tham số `act_id` trong hàm `_resolve_quest_zone` khai báo `str`, nhưng caller truyền `Optional[str]`.

#### Đoạn Code Đề Xuất Bổ Sung:

**Bổ sung hàm `seed_canonical_data` tại `server/world/game_design_matrix_seeder.py` (ngay dưới `seed_all_canonical_data`)**:
```python
def seed_canonical_data(conn: sqlite3.Connection, force: bool = False) -> Dict[str, Any]:
    """Nạp dữ liệu hạt giống chuẩn có kiểm tra trạng thái bảng nếu force=False."""
    cur = conn.cursor()
    if not force:
        cur.execute("SELECT COUNT(*) as cnt FROM story_acts")
        story_cnt = cur.fetchone()["cnt"]
        cur.execute("SELECT COUNT(*) as cnt FROM progression_benchmarks")
        bench_cnt = cur.fetchone()["cnt"]
        if story_cnt > 0 and bench_cnt > 0:
            return {"status": "already_seeded"}
    return seed_all_canonical_data(conn)
```

**Sửa chữ ký `_resolve_quest_zone` (dòng 298)**:
```python
<<<<
def _resolve_quest_zone(quest_id: str, act_id: str) -> str:
====
def _resolve_quest_zone(quest_id: str, act_id: Optional[str]) -> str:
>>>>
```
*(Cần bổ sung `Optional` vào import `from typing import Any, Dict, List, Optional` tại dòng 10)*.

---

### 3.3. File: `server/world/game_design_matrix_types.py`

Khảo sát đối soát class `ProgressionBenchmarkRow`:
```python
@dataclass(slots=True, frozen=True)
class ProgressionBenchmarkRow:
    """Mathematical progression milestone per level."""
    level: int
    target_exp: int
    exp_to_next_level: int
    cumulative_exp: int
    player_base_hp: float
    player_benchmark_dps: float
    monster_base_hp: float
    monster_base_dps: float
    max_affix_tier_allowed: int
    death_penalty_ratio: float = 0.0
    level_gap_safe_range: int = 5
    level_gap_penalty_exp: float = 0.60
    monster_benchmark_exp: int = 25
```
- **Đánh giá**: Đã hoàn toàn tuân thủ `@dataclass(slots=True, frozen=True)` và `Strict Typing`. Khớp 100% với 13 cột của SQLite `progression_benchmarks`. Không cần thay đổi cấu trúc trường, giữ nguyên tính bất biến và an toàn bộ nhớ.

---

### 3.4. Dọn Dẹp Test E2E: `tests/e2e/test_level_progression_e2e.py`

Ba test case sau đây đã chuyển từ `XFAIL` sang `XPASS` do tính năng F2 đã hoàn thành:
- `test_f02_progression_benchmarks_delta_column` (dòng 154)
- `test_f02_progression_benchmarks_death_penalty_column` (dòng 160)
- `test_f02_progression_benchmarks_cumulative_column` (dòng 166)

Khuyến nghị Worker M1 gỡ bỏ decorator `@pytest.mark.xfail` trên 3 test này để chúng trở thành positive assertion chính thức.

---

## 4. Kế Hoạch Kiểm Chứng Độc Lập Cho Worker M1 (Verification Plan)

Sau khi Worker M1 áp dụng các thay đổi trên, quy trình kiểm chứng 5 bước như sau:

| Bước | Lệnh Kiểm Chứng | Kết Quả Kỳ Vọng |
| :--- | :--- | :--- |
| 1 | `python -m pytest tests/unit/test_game_design_matrix.py -v` | 8/8 passed in < 0.5s |
| 2 | `python .agents/teamwork/reviewer_m1_progression_2/test_migration_adversarial.py` | `Post-seeding progression_benchmarks row count: 100`, `Benchmark level 1` trả về DTO |
| 3 | `python tools/lint/verify_game_design_matrix.py` | `[PASS]`, Code/DB/Wiki 100% in sync |
| 4 | `python -m pytest tests/e2e/test_level_progression_e2e.py -k "test_f02" -v` | 5/5 PASSED (không còn XPASS) |
| 5 | `python tools/lint/check_code_and_doc_hygiene.py --strict` | 0 Hard Cap violations, exit code 0 |

---
*Báo cáo được hoàn tất bởi `explorer_m1_progression_2_gen2` vào ngày 2026-10-01.*
