File size: 7,308 Bytes
78738de
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
"""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())