poolcoach / CLAUDE.md
masterdanh's picture
deploy: snapshot for HF Space
78738de
|
Raw
History Blame Contribute Delete
8.38 kB
# 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****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
## <ngày giờ> — <việc gì, một dòng>
**BRIEF**: <mốc chép từ BRIEF.md; "không có" nếu làm theo prompt miệng>
**Trạng thái**: XONG | XONG-CÓ-LỆCH | DỪNG-GIỮA-CHỪNG
**Commit**
- `<hash>` <subject> (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
- <làm khác gì · vì sao · ảnh hưởng thế nào tới số liệu>
- 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)
- <crash, hành vi lạ, phát hiện phụ, nghi ngờ chưa xác minh>
**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
```