# PoolCoach RL — hướng dẫn cho Claude Code Luận văn thạc sĩ: huấn luyện tư duy điều bi 9-ball bằng RL. Stack: `pooltool` (sim vật lý) → Gymnasium env → Stable-Baselines3 (PPO/SAC), cộng web app FastAPI + canvas gợi ý cú đánh. Trả lời và giải thích bằng **tiếng Việt**. Commit message bằng **tiếng Anh**. ## Môi trường - Python 3.12, venv ở `D:\Khoa luan\poolcoach-env` (NGOÀI repo). - Cài gói bằng **uv**, không dùng pip: `uv pip install -r requirements.txt --index-strategy unsafe-best-match` (flag bắt buộc — `panda3d==1.11.0.dev3702` chỉ có trên `archive.panda3d.org`). - 4 tầng requirements, cài tách để mạng chậm không kéo hỏng nhau: `requirements.txt` (core: pooltool + gymnasium) · `-rl.txt` (SB3 + torch) · `-app.txt` (fastapi + uvicorn) · `-dev.txt` (pytest + httpx). - Lần chạy pooltool đầu tiên mỗi process tốn **~40s Numba JIT**. Đừng tưởng treo. ## Quy ước repo — đọc trước khi viết code **Package KHÔNG được cài.** Mọi file trong `scripts/`, `tests/`, `examples/` phải tự đẩy `src/` vào path trước khi import `poolcoach_rl`: ```python ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "src")) ``` Quên dòng này là lỗi đã tái phạm (24/07). Trong `tests/` việc này nằm ở `conftest.py` — đừng dựa vào `app/main.py` chèn hộ, vì khi đó kết quả test phụ thuộc thứ tự collect của pytest. **`.bat` có hai vai trò khác nhau — đừng lẫn:** - **Launcher chạy tay** đặt ở `D:\Khoa luan\` (thư mục CHA, **ngoài git**). Script Python nằm trong `scripts/`; `.bat` chỉ là lớp bọc activate venv + `cd` + gọi script. Thêm script mới cần chạy thường xuyên → thêm `.bat` tương ứng ở thư mục cha, **đừng tạo launcher trong repo**. Hiện có: `poolcoach.bat` (mở shell đã activate) · `run_app.bat` · `run_tests_app.bat` · `run_eval_fullrack.bat` · `run_benchmark_fullrack.bat` · `run_recommend_smoke.bat` + các `run_experiments_*.bat` theo ngày. - **Bản lưu lịch sử của thí nghiệm ĐÃ CHẠY** thì ngược lại: **trong repo**, ở `scripts/experiments/` (29/07/2026). Đó là **copy byte-for-byte** của launcher đã thực sự sinh ra số liệu — bản ở thư mục cha vẫn còn nguyên để double-click. Lý do: comment đầu mỗi file ghi **lý do chạy + ngưỡng nghiệm thu**, thứ `git log` không tải nổi; và `run_qfield_20260721.bat` là lệnh tái lập chính model đang chạy trong app. Ngoài git thì mất là mất hẳn. Luật + bảng nội dung: `scripts/experiments/README.md`. Copy vào đó **sau khi** đã chạy xong, và **không sửa** bản đã lưu. `.gitattributes` khai `*.bat text eol=crlf` — `cmd` Windows cần CRLF. **`models/`, `logs/` bị gitignore.** Model cần deploy phải ép: `git add -f models/qfield_20260721/qfield.pt` (đang track qua Git LFS). ## Cấu trúc ``` src/poolcoach_rl/ envs/ SinkOneBallEnv (stage 1), PositionPlayEnv (stage 2a) recommend/ simulate.py (chỉ trả FACTS vật lý) · rules.py (luật + EV) geometry.py (đường bị chắn) · engines.py (oracle search + pool) core.py (validate + orchestrate) app/ main.py (FastAPI, 3 endpoint) · schemas.py · static/ (canvas FE) scripts/ train_* · eval_* · benchmark_* · oracle_* · gen_bc_dataset tests/ contract test (rules/fallback/api-v2/v1-compat) + conftest stub ``` Tách tầng cố ý: `simulate.py` chỉ nói *chuyện gì đã xảy ra*, `rules.py` mới phán *hợp lệ hay không*. Đừng nhét luật vào tầng sim. ## Test & nghiệm thu - `pytest` (hoặc `run_tests_app.bat`). Test kiểm **contract** — luật 9-ball, shape response, message lỗi — KHÔNG kiểm vật lý. `conftest.py` cố ý nhét stub `pooltool` rỗng, thiếu `System`/`simulate`/`EventType` để test nào lỡ chạm tầng vật lý sẽ ném `AttributeError` ngay. - Gate thật của engine là `scripts/eval_fullrack.py --tables 20`: **0 cú phạm luật** và **search < 5s/cú**. Mốc hiện tại: median 1.13s, max 2.48s. - Chỉ số RL đọc bằng eval mẫu lớn: `eval_position.py --episodes 1000`. Eval 200 cú cho Q|pot nhiễu tới ±0.05 — đã bị lừa 2 lần (14/07, 16/07). Kết luận về Q phải kèm SE. ## Bẫy đã trả giá — đừng lặp lại - **`os.cpu_count()` không thấy cgroup quota.** Trong container HF (2 vCPU) nó trả về số core host → pool 7-15 worker. Số worker khai báo tường minh qua env `POOLCOACH_WORKERS` (Dockerfile set `2` = đúng số vCPU thật của Space; `0` = serial). `sched_getaffinity` cũng không cứu. - **Quy ước spin của pooltool** (nghiệm thu từ source wheel 0.6.0): `a=+1` là mép **TRÁI** bi, `b=+1` là **đỉnh** bi. FE đã sửa `cx = 50 − side·46` cho khớp. Đổi dấu ở đâu cũng phải đối chiếu lại docstring `Cue`. - **Tín hiệu "thắng" thô bị may mắn thổi phồng.** Hai lần: `b2` rớt lỗ may mắn (20/07) và "lucky 9" trong grid search (24/07). Cách sửa đúng là **điều kiện hoá theo ngữ cảnh** (WIN_EV chỉ khi target = 9), không phải hạ hệ số. - **`.git/index.lock` sót lại** → xoá tay file đó. Nguyên nhân đã truy ra (25/07): `git status` chạy từ sandbox Linux của Cowork tạo lock rồi không unlink được qua mount Windows. Claude Code chạy native nên không dính; Cowork từ nay dùng `git --no-optional-locks status`. ## Mẫu block `HANDOFF.md` Ghi thêm một block vào `..\HANDOFF.md` sau MỖI lần chạy — xong hay dừng giữa chừng đều ghi. Chép từ `..\poolcoach-docs\Documents\PoolCoach_Workflow_Cowork_ClaudeCode.md` §5.5; bản đó là bản gốc, sửa mẫu thì sửa ở đó trước. Một block cho mỗi lần chạy, mới nhất **ở dưới cùng**: ```markdown ## **BRIEF**: **Trạng thái**: XONG | XONG-CÓ-LỆCH | DỪNG-GIỮA-CHỪNG **Commit** - `` (rỗng nếu không commit gì — nói rõ vì sao) **Gate** | gate | ngưỡng | đo được | ĐẠT/ĐỎ | **Lệch khỏi BRIEF** ← MỤC QUAN TRỌNG NHẤT - - ghi thẳng "không lệch gì" nếu đúng vậy — đừng bỏ trống **Bất ngờ** (thứ tự nhận ra mà BRIEF không hỏi) - **File sinh ra** | file | sinh bởi lệnh nào | commit hash lúc chạy | **Câu hỏi cho Cowork** (quyết định cố ý KHÔNG tự đưa ra) **Chưa làm** ``` Ba mục cứu được ca 27/07: **Lệch khỏi BRIEF** (khai script chữa cháy), **Bất ngờ** (khai crash), và cột **commit hash lúc chạy** trong bảng file (lộ ngay hai cấu hình chạy ở hai phiên bản code khác nhau). > **Ghi `HANDOFF.md` kể cả khi thất bại — nhất là khi thất bại.** Một lần chạy chết giữa chừng mang nhiều thông tin hơn một lần chạy trơn tru, mà nó lại chính là lần không để lại commit nào. ## Ranh giới với Cowork Claude Code sở hữu mọi thứ trong repo này. **Không sửa** `..\poolcoach-docs\research_log.md` — nhật ký nghiên cứu do phiên Cowork ghi cuối ngày, dựa trên `git log` của repo này. Design doc + implementation plan nằm ở `..\poolcoach-docs\Documents\` — đọc để lấy yêu cầu, nhưng không sửa; thiết kế được chốt ở Cowork. ## Commit Conventional Commits, tiếng Anh, cả subject lẫn body. Mỗi bước trong implementation plan = 1 commit, chỉ commit sau khi qua gate nghiệm thu của bước đó. Scope đang dùng: `rules` `recommend` `geometry` `engines` `api` `fe` `app` `envs` `eval` `deploy` `stage2a` `examples`. ``` feat(rules): WIN_EV only for direct 9-ball target (anti lucky-9) fix(app): flip spin-dot x-axis to match pooltool side-spin convention chore(deploy): dockerignore tests, HF metadata for full-rack build ```