test(wg1): gamedef 路 lili-mac 快走查驱动 + U3 cutover 门早读(pong 9/9 真过)
plan 2026-06-18-001 U3 验证:run.py 的 gamedef 版——generate(GAMEDEF_SYSTEM,从 SaaPrompts.java 抽取 drift-free) → build-from-source.mjs(校验 schema/引用/静态扫描+装配)→ scaffold/build/play 九门(harness 复用零改)。 早读(deepseek-v4-flash 便宜档,lili-mac 小样): - gd-pong 9/9 真过门(声明式 gameDefinition→可玩→满九门,范式端到端证实)。 - gd-runner/whack 8/9、gd-flappy 7/9,共性缺口 G_input——本快走查跳了 design 步、多数 brief 的 play_spec 无 controlCheck/driver(gatespec),故 G_input 无控制驱动可施=快走查 harness 工件,非 gamedef 生成缺陷 (游戏真渲染/真构建/输入真接线:runner justTapped→jump 在;pong 满过)。 - authoritative ≥60% cutover 门(全图含 design 产 gatespec) = mini-desktop SaaFullGraphE2eTest -Dsaa.e2e=1。 密钥经 _client 读 .env(gitignored)、绝不入码;throwaway 生成游戏(gd-*)已清,不入仓。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
fd5b245baa
commit
d8df5f27ec
123
wg1/gen-worker/gamedef_quickcheck.py
Normal file
123
wg1/gen-worker/gamedef_quickcheck.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
"""gamedef_quickcheck.py —— plan 2026-06-18-001 U3 gamedef 路 lili-mac 快走查(cutover 门早读)。
|
||||||
|
|
||||||
|
run.py 的 gamedef 版:把 generate(iife) → validate(静态) 换成
|
||||||
|
generate(GAMEDEF_SYSTEM) → build-from-source.mjs(校验 schema/引用/静态扫描 + 装配出工厂),
|
||||||
|
其余(scaffold/build/play 九门)复用 run.py(harness 零改)。
|
||||||
|
|
||||||
|
GAMEDEF_SYSTEM **从后端 SaaPrompts.java 抽取**(drift-free:跑的就是后端真用的 prompt)。
|
||||||
|
模型/网关/代理旁路复用 _client;play-spec/brief 复用现有 briefs/*.json(gatespec driver 同口径)。
|
||||||
|
|
||||||
|
用法:.venv/bin/python gamedef_quickcheck.py runner flappy whack pong # 默认这 4
|
||||||
|
仅 lili-mac 快走查(小样早读);authoritative ≥60% cutover 门在 mini-desktop(SaaFullGraphE2eTest)。
|
||||||
|
密钥经 _client 读 .env,绝不打印。
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||||
|
from worker import _client, run # noqa: E402 复用 chat + scaffold/build/play
|
||||||
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
SAA_PROMPTS = (REPO_ROOT / "game-cloud/game-module-aigc/game-module-aigc-server/src/main/java/"
|
||||||
|
"com/wanxiang/huijing/game/module/aigc/saa/SaaPrompts.java")
|
||||||
|
GAME_RUNTIME = REPO_ROOT / "game-runtime"
|
||||||
|
GEN_DIR = GAME_RUNTIME / "games" / "_wg1-gen"
|
||||||
|
BRIEFS = Path(__file__).resolve().parent / "briefs"
|
||||||
|
|
||||||
|
|
||||||
|
def extract_gamedef_system():
|
||||||
|
"""从 SaaPrompts.java 抽 GAMEDEF_SYSTEM 文本块(去 Java 文本块 12 空格缩进)。"""
|
||||||
|
java = SAA_PROMPTS.read_text(encoding="utf-8")
|
||||||
|
m = re.search(r'GAMEDEF_SYSTEM = """\n(.*?)\n\s*""";', java, re.DOTALL)
|
||||||
|
if not m:
|
||||||
|
raise RuntimeError("未能从 SaaPrompts.java 抽取 GAMEDEF_SYSTEM")
|
||||||
|
lines = [(ln[12:] if ln.startswith(" " * 12) else ln) for ln in m.group(1).split("\n")]
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_json_obj(content):
|
||||||
|
"""从模型输出抠最外层 {...}(去 ```json 围栏/前后说明)。"""
|
||||||
|
s = content.strip()
|
||||||
|
s = re.sub(r"^```(?:json)?\s*", "", s)
|
||||||
|
s = re.sub(r"\s*```$", "", s)
|
||||||
|
lo, hi = s.find("{"), s.rfind("}")
|
||||||
|
if lo < 0 or hi <= lo:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return json.loads(s[lo:hi + 1])
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def assemble_via_cli(game_id, gamedef_obj):
|
||||||
|
"""写 source.json → 跑 build-from-source.mjs 校验+装配 → generated-factory.js。返回 (ok, err)。"""
|
||||||
|
gdir = GEN_DIR / game_id
|
||||||
|
gdir.mkdir(parents=True, exist_ok=True)
|
||||||
|
src = gdir / "source.json"
|
||||||
|
src.write_text(json.dumps(gamedef_obj, ensure_ascii=False), encoding="utf-8")
|
||||||
|
r = subprocess.run(
|
||||||
|
["node", "src/host/build-from-source.mjs", str(src), str(gdir / "generated-factory.js")],
|
||||||
|
cwd=str(GAME_RUNTIME), capture_output=True, text=True, timeout=60,
|
||||||
|
)
|
||||||
|
return r.returncode == 0, (r.stdout + r.stderr).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def run_one(game_id, gamedef_system, model):
|
||||||
|
brief_text, play_spec = run._load_brief(game_id)
|
||||||
|
user = "请为下面这款游戏产出一份 gameDefinition(只输出一个 JSON 对象,不要 ```代码块、不要解释):\n\n" + brief_text
|
||||||
|
gid = "gd-" + game_id
|
||||||
|
out = {"game_id": gid, "stage": None, "pass": False, "guards": {}, "err": ""}
|
||||||
|
try:
|
||||||
|
resp = _client.chat(model, gamedef_system, user, max_tokens=16000)
|
||||||
|
except Exception as e:
|
||||||
|
out["stage"] = "generate"; out["err"] = str(e)[:200]; return out
|
||||||
|
out["tokens"] = (resp.get("prompt_tokens"), resp.get("completion_tokens"))
|
||||||
|
gd = extract_json_obj(resp["content"])
|
||||||
|
if gd is None:
|
||||||
|
out["stage"] = "parse"; out["err"] = "未解析出 gameDefinition JSON"; return out
|
||||||
|
ok, err = assemble_via_cli(gid, gd)
|
||||||
|
if not ok:
|
||||||
|
out["stage"] = "validate/assemble"; out["err"] = err[:400]; return out
|
||||||
|
# 复用 run.py 的 scaffold 余下(拷模板 + play-spec)+ build + play(factory 已由 CLI 写好)。
|
||||||
|
gdir = GEN_DIR / gid
|
||||||
|
shutil.copyfile(GEN_DIR / "_shared" / "entry-bundle.template.js", gdir / "entry-bundle.js")
|
||||||
|
shutil.copyfile(GEN_DIR / "_shared" / "index.template.html", gdir / "index.html")
|
||||||
|
(gdir / "play-spec.json").write_text(json.dumps(play_spec, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||||
|
bok, blog = run.build(gid)
|
||||||
|
if not bok:
|
||||||
|
out["stage"] = "build"; out["err"] = blog[:400]; return out
|
||||||
|
rc, _, verdict = run.play(gid)
|
||||||
|
out["stage"] = "play"
|
||||||
|
out["guards"] = {k: g.get("pass") for k, g in ((verdict or {}).get("guards") or {}).items()}
|
||||||
|
out["pass"] = bool(rc == 0 and verdict and verdict.get("pass"))
|
||||||
|
if not out["pass"]:
|
||||||
|
out["err"] = run._verdict_feedback(verdict)[:300]
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
games = sys.argv[1:] or ["runner", "flappy", "whack", "pong"]
|
||||||
|
model = _client.DEFAULT_MODEL
|
||||||
|
gds = extract_gamedef_system()
|
||||||
|
print(f"[gamedef-qc] model={model} games={games} GAMEDEF_SYSTEM={len(gds)}字 (extracted from SaaPrompts.java)\n")
|
||||||
|
results = []
|
||||||
|
for g in games:
|
||||||
|
r = run_one(g, gds, model)
|
||||||
|
results.append(r)
|
||||||
|
mark = "✅ PASS" if r["pass"] else f"❌ {r['stage']}"
|
||||||
|
gpass = sum(1 for v in r["guards"].values() if v)
|
||||||
|
gtot = len(r["guards"])
|
||||||
|
print(f" {r['game_id']:14s} {mark:16s} gates={gpass}/{gtot} {('' if r['pass'] else r['err'][:120])}")
|
||||||
|
npass = sum(1 for r in results if r["pass"])
|
||||||
|
print(f"\n[gamedef-qc] 过门 {npass}/{len(results)} (cutover 门基线=iife 60%; 本为 lili-mac 小样早读, authoritative=mini-desktop)")
|
||||||
|
Path(__file__).resolve().parent.joinpath("results", "gamedef-quickcheck.json").write_text(
|
||||||
|
json.dumps(results, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
x
Reference in New Issue
Block a user