Spaces:
Sleeping
Sleeping
File size: 7,292 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 | """Tầng SIM — chỉ nói sự thật vật lý, không luật, không xếp hạng.
24/07/2026 (FullRack phase A): `simulate_shot_multi` nhận dict {id: xy} bất
kỳ, trả FACTS (potted list / scratch / first_contact / balls_final).
31/07/2026 thêm hai facts cho V2 (`bb_events`, `cue_roll_after` — xem
docstring hàm). `simulate_shot` 3-bi ở lại làm wrapper cho `warmup`.
31/07/2026 (việc D): các khoá xếp hạng EV (`rank_key`, `rank_key_factory`,
`rank2_key`, `EV2_MAX`) xoá theo engine oracle/hybrid — chúng sống ở nhánh
`v1-full`. POS_COEF/SCRATCH_EV Ở LẠI: `rules.judge_shot` và reward env vẫn
định nghĩa Q/EV bằng chúng (hồ sơ nghiên cứu, xem BRIEF bối cảnh 5).
"""
from __future__ import annotations
POS_COEF = 0.5 # EV = pot·(1 + POS_COEF·Q) — khớp reward env
SCRATCH_EV = -0.3 # khớp scratch_penalty env
def path_len_after(xy, ts, t1, *, eps=1e-12):
"""Độ dài polyline (mét) của ``xy`` từ thời điểm ``t1`` trở đi — THUẦN.
``xy``: mảng ``(n, 2)`` vị trí theo thời gian; ``ts``: ``(n,)`` mốc thời
gian tương ứng. Nguồn dự kiến là ``ball.history`` event-based của pooltool
(KHÔNG cần continuize): snapshot tại MỌI event nên mọi cú dội băng và mọi
chuyển pha sliding→rolling đều là đỉnh polyline. Nghiệm thu 31/07/2026 so
với continuize ``dt=0.01`` trên 6 cú trộn xoáy/lực: lệch 0.2–8.9 mm —
nhỏ hơn nhiều bước lưới ứng viên, đủ cho xếp hạng.
``eps`` nới mốc so sánh: ``t1`` chép từ ``event.time`` và ``ts`` chép từ
cùng nguồn, nhưng qua hai đường float khác nhau — so ``>=`` trần trụi có
thể rớt đúng snapshot va chạm.
"""
import numpy as np
xy = np.asarray(xy, dtype=np.float64).reshape(-1, 2)
m = np.asarray(ts, dtype=np.float64).ravel() >= float(t1) - eps
if int(m.sum()) < 2:
return 0.0
return float(np.linalg.norm(np.diff(xy[m], axis=0), axis=1).sum())
def simulate_shot_multi(env_h, balls, phi, v0, a, b, render=False):
"""1 cú với N bi bất kỳ. ``balls = {"cue": xy, "1": xy, "5": xy, ...}``
(bắt buộc có "cue"; id bi = chuỗi số như FE/API gửi).
Chỉ trả FACTS vật lý — không luật, không EV (tầng đó ở phase B):
system : pooltool System nếu render=True (vẽ quỹ đạo) | None
potted : list id bi VÀO LỖ trong cú này (không tính cue)
scratch : bool — bi cái vào lỗ
first_contact : id bi đầu tiên cue chạm | None nếu không chạm gì
(event BALL_BALL đầu tiên có "cue" — luật 9-ball
phase B sẽ so với target để bắt foul)
balls_final : {id: np.ndarray (2,) | None nếu vào lỗ} — mọi bi,
kể cả cue (nút "Đánh cú này" của FE dùng dict này)
HAI KEY THÊM 31/07/2026 (ZonePlanner V2, BRIEF bối cảnh 7 — MỞ RỘNG
THUẦN: hai phép gán, không nhánh cũ nào đọc chúng):
bb_events : tuple[(t, (id, id))] — MỌI event BALL_BALL theo thứ tự
thời gian. Lọc (i)/(iii) của V2 đọc từ đây ("đường bi
mục tiêu không chạm bi khác", "bi cái sau va chạm không
đụng bi nào — kể cả double-kiss").
cue_roll_after: float | None — quãng đường bi cái LĂN SAU cú chạm bi
đầu tiên (mét, arc length event-polyline, xem
``path_len_after``) | None nếu cue không chạm bi nào.
Khoá xếp hạng số 1 của V2 (§8.2 design).
Trả None nếu pooltool ném exception (giữ hợp đồng cũ).
"""
import numpy as np
import pooltool as pt
import pooltool.constants as ptc
system = pt.System(
table=env_h.table,
balls={bid: pt.Ball.create(bid, xy=tuple(xy))
for bid, xy in balls.items()},
cue=pt.Cue(cue_ball_id="cue"),
)
system.cue.set_state(V0=v0, phi=phi, a=a, b=b)
try:
pt.simulate(system, inplace=True)
except Exception:
return None
if render:
try:
pt.continuize(system, dt=0.01, inplace=True)
except Exception:
pass # fallback: vẽ theo history event-based
def _pk(bid):
return system.balls[bid].state.s == ptc.pocketed
def _xy(bid):
return np.asarray(system.balls[bid].state.rvw[0][:2], dtype=np.float64)
first_contact = None
for ev in system.events:
if ev.event_type == pt.EventType.BALL_BALL and "cue" in ev.ids:
first_contact = next(i for i in ev.ids if i != "cue")
break
bb_events = tuple((float(ev.time), tuple(ev.ids))
for ev in system.events
if ev.event_type == pt.EventType.BALL_BALL)
# quãng đường bi cái SAU cú chạm đầu — từ history event-based (có sẵn
# không cần continuize; extract_trajectories vẫn fallback vào đúng nguồn
# này). vectorize hỏng thì trả None thay vì làm chết cả sim: hai key mới
# là mở rộng, không được phép kéo sập hợp đồng cũ.
cue_roll_after = None
t1 = next((t for t, ids in bb_events if "cue" in ids), None)
if t1 is not None:
try:
rvw, _ss, ts = system.balls["cue"].history.vectorize()
except Exception:
rvw = None
if rvw is not None and len(rvw) > 1:
cue_roll_after = path_len_after(rvw[:, 0, :2], ts, t1)
return {
"system": system if render else None,
"potted": [bid for bid in balls if bid != "cue" and _pk(bid)],
"scratch": _pk("cue"),
"first_contact": first_contact,
"balls_final": {bid: (None if _pk(bid) else _xy(bid))
for bid in balls},
"bb_events": bb_events,
"cue_roll_after": cue_roll_after,
}
def simulate_shot(env_h, cue_xy, b1_xy, b2_xy, phi, v0, a, b, render=False):
"""1 cú 3 bi (wrapper tương thích v1) — output y hệt bản cũ 23/07,
thêm 2 key mới vô hại: ``first_contact``, ``balls_final``."""
balls = {"cue": cue_xy, "1": b1_xy}
if b2_xy is not None:
balls["2"] = b2_xy
m = simulate_shot_multi(env_h, balls, phi, v0, a, b, render=render)
if m is None:
return None
potted = "1" in m["potted"]
scratch = m["scratch"]
b2p = "2" in m["potted"]
bf = m["balls_final"]
q = 0.0
if b2_xy is not None and potted and not scratch:
q = 1.0 if b2p else env_h._position_q(bf["cue"], bf["2"])
if scratch:
ev = SCRATCH_EV
elif potted:
ev = 1.0 + POS_COEF * q
else:
ev = 0.0
return {
"system": m["system"],
"potted": potted, "scratch": scratch, "b2_potted": b2p,
"q": q, "ev": ev,
"cue_final": bf["cue"], # None khi scratch (bi cái pocketed) — như cũ
"b2_final": bf["2"] if b2_xy is not None else None, # None khi b2 vào lỗ
"first_contact": m["first_contact"],
"balls_final": bf,
}
|