"""Verify kha thi cua thu vien drill (app/drills.json) — BRIEF 04/08/2026 buoc 1. CURATION, KHONG PHAI EVAL (tien le 11b ban giao 9): so "cu dat goal" o day chi de chung minh drill TAP DUOC — dung de lan vao so do engine trong research log. Voi moi drill: quet dung grid ky thuat x luc cua engine (11 ky thuat x nac 2-10 = 99 o, `v2_specs`) tren phi ngam ghost-ball vao lo goal (goal.pot.pocket = "any" thi quet moi lo kha thi tu `feasible_pockets_full`), sim tung cu bang `simulate_shot_multi`, dem: n_pot : cu pot SACH bi muc tieu (khong scratch, first_contact dung bi, bi vao lo) — gate cung: moi drill phai co >= 1 n_pass : cu dat TRON goal = pot sach + cue dung trong cue_zone (scoring.pass "pot && cue_zone") — drill 0 cu dat la drill khong tap duoc: chinh zone/khoang cach roi quet lai Gate G1: 100% drill layout hop le (validate_full khong nem) VA n_pot >= 1. Bang so n_pass tung drill di vao HANDOFF. Chay SERIAL co chu dich, khong pool: tong scan ~9 drill x 99-500 sim ~ 10-30s sau warmup, trong khi moi worker process phai tra ~40s JIT Numba rieng — pool o day cham hon serial. Khong hardcode so worker vi khong co worker nao (POOLCOACH_WORKERS chi danh cho cho nao that su co pool). python scripts/verify_drills.py [--drill stop_shot_short] [--drills-file ...] """ from __future__ import annotations import argparse import json import sys import time from pathlib import Path ROOT = Path(__file__).resolve().parents[1] if str(ROOT / "src") not in sys.path: sys.path.insert(0, str(ROOT / "src")) DRILLS_FILE = ROOT / "app" / "drills.json" def load_drills(path: Path) -> list[dict]: data = json.loads(path.read_text(encoding="utf-8")) return data["drills"] def check_layout(env_h, balls, target): """Layout hop le = validate_full khong nem + bi muc tieu co mat. Tra ve None neu ok, message loi neu khong.""" from poolcoach_rl.recommend import validate_full if target not in balls: return f"goal.pot.ball={target!r} khong co trong layout" try: validate_full(env_h, balls) except ValueError as e: return str(e) return None def scan_drill(env_h, drill): """Quet grid engine tren MOT drill. Tra dict so lieu cho bang bao cao.""" import numpy as np from poolcoach_rl.recommend import feasible_pockets_full from poolcoach_rl.recommend.zone_v2 import v2_specs from poolcoach_rl.recommend import simulate_shot_multi balls = {bid: np.array([p["x"], p["y"]], dtype=np.float64) for bid, p in drill["layout"].items()} target = drill["goal"]["pot"]["ball"] want_pocket = drill["goal"]["pot"]["pocket"] zone = drill["goal"]["cue_zone"] zc = np.array([zone["cx"], zone["cy"]], dtype=np.float64) err = check_layout(env_h, balls, target) if err: return {"id": drill["id"], "layout_err": err} pockets = feasible_pockets_full(env_h, balls, target) if want_pocket != "any": pockets = [p for p in pockets if p[0] == int(want_pocket)] specs = v2_specs() n_sim = n_pot = n_pass = 0 example = None t0 = time.time() for idx, phi, _cut, _blocked in pockets: for side, vert, v0 in specs: m = simulate_shot_multi(env_h, balls, phi, v0, side, vert) n_sim += 1 if m is None: continue pot_ok = (not m["scratch"] and m["first_contact"] == target and list(m["potted"]) == [target]) if not pot_ok: continue n_pot += 1 cue_final = m["balls_final"].get("cue") if cue_final is None: continue d_zone = float(np.linalg.norm( np.asarray(cue_final, dtype=np.float64) - zc)) if d_zone <= zone["r"]: n_pass += 1 if example is None: example = (side, vert, v0, d_zone) return {"id": drill["id"], "layout_err": None, "n_pockets": len(pockets), "n_sim": n_sim, "n_pot": n_pot, "n_pass": n_pass, "example": example, "elapsed_s": time.time() - t0} def main() -> int: # Console Windows co the la cp1252 — title tieng Viet khong duoc lam chet script if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(encoding="utf-8", errors="replace") ap = argparse.ArgumentParser(description=__doc__.splitlines()[0]) ap.add_argument("--drill", help="chi quet 1 drill theo id (chinh sua nhanh)") ap.add_argument("--drills-file", type=Path, default=DRILLS_FILE) args = ap.parse_args() drills = load_drills(args.drills_file) if args.drill: drills = [d for d in drills if d["id"] == args.drill] if not drills: print(f"khong co drill id={args.drill!r}") return 2 print(f"[verify_drills] {len(drills)} drill tu {args.drills_file}") print("[verify_drills] dung env + warmup JIT Numba (~40s lan dau)...", flush=True) t0 = time.time() from poolcoach_rl.envs import PositionPlayEnv from poolcoach_rl.recommend import warmup env_h = PositionPlayEnv() warmup(env_h) print(f"[verify_drills] warmup xong sau {time.time() - t0:.1f}s", flush=True) rows = [] for d in drills: r = scan_drill(env_h, d) rows.append(r) if r["layout_err"]: print(f" {r['id']:18s} LAYOUT LOI: {r['layout_err']}", flush=True) else: ex = r["example"] ex_txt = (f"side {ex[0]:+.1f} vert {ex[1]:+.1f} v0 {ex[2]:.2f} " f"(cach tam zone {ex[3]:.3f} m)" if ex else "-") print(f" {r['id']:18s} pockets {r['n_pockets']} sim {r['n_sim']:4d}" f" pot {r['n_pot']:3d} PASS {r['n_pass']:3d}" f" vd: {ex_txt} [{r['elapsed_s']:.1f}s]", flush=True) # ------------------------------------------------------------- gate G1 bad_layout = [r["id"] for r in rows if r["layout_err"]] no_pot = [r["id"] for r in rows if not r["layout_err"] and r["n_pot"] == 0] no_pass = [r["id"] for r in rows if not r["layout_err"] and r["n_pot"] > 0 and r["n_pass"] == 0] print() print("| drill | pockets | sim | pot sach | dat tron goal |") print("|---|---|---|---|---|") for r in rows: if r["layout_err"]: print(f"| {r['id']} | - | - | - | LAYOUT LOI |") else: print(f"| {r['id']} | {r['n_pockets']} | {r['n_sim']} " f"| {r['n_pot']} | {r['n_pass']} |") print() if bad_layout: print(f"GATE DO: layout loi: {', '.join(bad_layout)}") return 1 if no_pot: print(f"GATE DO: khong ton tai cu pot: {', '.join(no_pot)}") return 1 if no_pass: # khong phai gate cung cua G1, nhung drill 0 cu dat tron goal la drill # khong tap duoc — BRIEF bat chinh zone/khoang cach roi quet lai print(f"CANH BAO: pot duoc nhung 0 cu dat tron goal (phai chinh + quet " f"lai): {', '.join(no_pass)}") return 1 print("GATE G1 DAT: 100% layout hop le, moi drill co cu pot va co cu dat " "tron goal.") return 0 if __name__ == "__main__": raise SystemExit(main())