fix(saa): GAMEDEF_SYSTEM driver 对齐加固 + 快走查升级全图镜像(design+gamedef)——据 U3 早读根因
plan 2026-06-18-001 U3 迭代1轮:据 lili-mac gamedef 快走查根因诊断,加固 GAMEDEF_SYSTEM 与九门 driver 的对齐。 快走查根因(design+gamedef 全图镜像, deepseek-v4-flash 单发无 repair): - 路径端到端真成立:声明式 gameDefinition→装配→构建→真九门;pong 9/9(无 design 简版)、breakout 8/9。 - 单发首攻失败模式根因:① I_control——挡板命名变体(paddlePlayer≠gatespec 的 paddle)/ 只接键盘不接指针(driver 靠点指针位驱动) / 控制体 clamp 范围窄于 driver 边缘 tapX(340);② H_progress——设计稿 assertAfterPlay 引用取证读不到的自定义字段(totalRound)。 GAMEDEF_SYSTEM 加固(SaaPrompts.java,gamedef 路专属,不动现 SYSTEM): - 命名逐字对齐 gatespec(挡板=paddle、球=ball,别用变体——读到 undefined=挂)。 - 可控体必须响应指针(`if(rt.input.pointer.down) paddle.x=rt.input.pointer.x`——driver 靠点指针位驱动,只接键盘=I_control 挂)。 - 进展落在可取证字段(score/remaining/实体位置;assertAfterPlay 别用读不到的自定义字段)。 验证:再跑 breakout 模型确按指令接指针控制(`if(pointer.down) paddle.x 追 pointer`)+ I_control tapX:50 已过; 残留 tapX:340 未过=首攻 clamp 范围精度,属**repair 回环**(已建:验收失败→verdict 回喂→重生成)territory。 快走查工具升级(gamedef_quickcheck.py):加 design 步(抽 DESIGN_SYSTEM+复刻 extractGatespec)→ 全图镜像 (classify 略/design→gamedef→装配→九门),getter truer 早读;prompt 抽自 SaaPrompts.java drift-free。 定位:单发快走查=首攻质量早读(非 cutover 门)。**≥60% cutover 门=全图含 repair 回环, authoritative=mini-desktop SaaFullGraphE2eTest -Dsaa.e2e=1**(单发 0/6≠失败:repair 回环抬升率,本机不跑 repap 多轮)。密钥经 _client 读 .env 不入码。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
d8df5f27ec
commit
da4801828d
@ -176,11 +176,16 @@ final class SaaPrompts {
|
||||
5. condition 是无副作用布尔表达式: 禁 ; / 赋值 / 调 win/lose/addScore/spawn/destroy。
|
||||
6. 死循环: 禁 while(true)/for(;;)。
|
||||
|
||||
【可玩 + 被九门驱动(关键命名约定,务必对齐)】
|
||||
【可玩 + 被九门驱动(关键约定,务必对齐——否则机制门必挂)】
|
||||
- 实体须有 render 组件才可见; 放一个 shape:"fill" 实体铺满 390x844 作背景(否则判白屏)。
|
||||
- 九门据【命名路径】驱动你的游戏:
|
||||
· 挡板接球类: 球实体 id 命名 "ball"、挡板命名 "paddle"(九门读 ball.x/paddle.x 真玩对齐)。
|
||||
· 离散点击类(井字棋/打地鼠/翻牌/扫雷): 每个可点目标实体打 tags:["target"], 设 idx(序号)/occupied(是否已占)(九门据 targets[] 逐个点; 不打 target 标→点不到→机制门必挂)。
|
||||
- **命名逐字对齐设计稿 gatespec**: 设计稿末尾 ```gatespec 块的 driver.ballPath / controlCheck.paddlePath / assertAfterPlay.path
|
||||
引用的名字,你的实体 id 与进展字段必须**逐字一致**:
|
||||
· 挡板接球类: 球实体 id **就叫 "ball"**、挡板 **就叫 "paddle"**(别用 paddlePlayer/ball1 等变体——九门读 ball.x/paddle.x,变体=读到 undefined=挂)。
|
||||
· 离散点击类(井字棋/打地鼠/翻牌/扫雷): 每个可点目标实体打 tags:["target"], 设 idx(序号)/occupied(是否已占)(九门据 targets[] 逐个点; 不打 target 标=点不到=挂)。
|
||||
- **可控体必须响应指针**(九门 driver 靠点击指针位驱动你的控制体): 挡板/玩家等可控实体务必随指针移动,如
|
||||
`const p=rt.getEntity('paddle'); if(p && rt.input.pointer.down){ p.x = rt.input.pointer.x; }`(只接键盘=driver 推不动=I_control 挂)。
|
||||
- **进展落在可取证字段**: 取证只暴露 score / remaining(=存活实体数,destroy 目标即降)/ 实体位置——进展务必体现在 rt.addScore 或
|
||||
rt.destroy(目标/砖)上; 确保设计稿 assertAfterPlay 的 path 落在 score 或 remaining(别用取证读不到的自定义字段)。
|
||||
- HUD/进展可见: 分数/剩余看得到; 前 30 秒能上手、有正反馈、看得到分数增长。
|
||||
|
||||
【参照(严格按此结构产 JSON, 但实现成题目要求的那款游戏; 这是个躲避类示例)】
|
||||
|
||||
@ -29,16 +29,35 @@ GEN_DIR = GAME_RUNTIME / "games" / "_wg1-gen"
|
||||
BRIEFS = Path(__file__).resolve().parent / "briefs"
|
||||
|
||||
|
||||
def extract_gamedef_system():
|
||||
"""从 SaaPrompts.java 抽 GAMEDEF_SYSTEM 文本块(去 Java 文本块 12 空格缩进)。"""
|
||||
def extract_block(name):
|
||||
"""从 SaaPrompts.java 抽指定 SYSTEM 文本块(去 Java 文本块 12 空格缩进)。drift-free=跑后端真 prompt。"""
|
||||
java = SAA_PROMPTS.read_text(encoding="utf-8")
|
||||
m = re.search(r'GAMEDEF_SYSTEM = """\n(.*?)\n\s*""";', java, re.DOTALL)
|
||||
m = re.search(name + r' = """\n(.*?)\n\s*""";', java, re.DOTALL)
|
||||
if not m:
|
||||
raise RuntimeError("未能从 SaaPrompts.java 抽取 GAMEDEF_SYSTEM")
|
||||
raise RuntimeError("未能从 SaaPrompts.java 抽取 " + name)
|
||||
lines = [(ln[12:] if ln.startswith(" " * 12) else ln) for ln in m.group(1).split("\n")]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def extract_gatespec(design_text):
|
||||
"""复刻 SaaPrompts.extractGatespec:抠 ```gatespec JSON 块 → 补 expectLatch=true + ballPath .y→.x。无则 None。"""
|
||||
m = re.search(r"```gatespec\s*(.+?)```", design_text or "", re.DOTALL)
|
||||
if not m:
|
||||
return None
|
||||
gs = extract_json_obj(m.group(1))
|
||||
if not isinstance(gs, dict):
|
||||
return None
|
||||
if "assertAfterPlay" in gs and not isinstance(gs["assertAfterPlay"], list):
|
||||
gs["assertAfterPlay"] = []
|
||||
gs.setdefault("expectLatch", True)
|
||||
drv = gs.get("driver")
|
||||
if isinstance(drv, dict) and drv.get("type") == "paddle-intercept":
|
||||
bp = drv.get("ballPath")
|
||||
if isinstance(bp, str) and bp.endswith(".y"):
|
||||
drv["ballPath"] = bp[:-2] + ".x"
|
||||
return gs
|
||||
|
||||
|
||||
def extract_json_obj(content):
|
||||
"""从模型输出抠最外层 {...}(去 ```json 围栏/前后说明)。"""
|
||||
s = content.strip()
|
||||
@ -66,11 +85,25 @@ def assemble_via_cli(game_id, gamedef_obj):
|
||||
return r.returncode == 0, (r.stdout + r.stderr).strip()
|
||||
|
||||
|
||||
def run_one(game_id, gamedef_system, model):
|
||||
def run_one(game_id, gamedef_system, design_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": ""}
|
||||
out = {"game_id": gid, "stage": None, "pass": False, "guards": {}, "err": "", "driver": None}
|
||||
# ① design 步(mirror 真图 design 节点):产 gatespec(driver/controlCheck/assert) 覆写 play_spec + enriched。
|
||||
enriched = brief_text
|
||||
try:
|
||||
dresp = _client.chat(model, design_system, brief_text, max_tokens=16000)
|
||||
enriched = brief_text + "\n\n## 设计稿\n" + dresp["content"]
|
||||
gs = extract_gatespec(dresp["content"])
|
||||
if gs:
|
||||
for k in ("exportState", "driver", "controlCheck", "assertAfterPlay", "expectLatch"):
|
||||
if k in gs and gs[k] is not None:
|
||||
play_spec[k] = gs[k]
|
||||
out["driver"] = (gs.get("driver") or {}).get("type")
|
||||
except Exception as e:
|
||||
out["err"] = "design 降级:" + str(e)[:80] # design 失败不致命,凭 brief 继续 generate
|
||||
# ② generate gamedef(用 enriched=brief+设计稿)
|
||||
user = "请为下面这款游戏产出一份 gameDefinition(只输出一个 JSON 对象,不要 ```代码块、不要解释):\n\n" + enriched
|
||||
try:
|
||||
resp = _client.chat(model, gamedef_system, user, max_tokens=16000)
|
||||
except Exception as e:
|
||||
@ -102,16 +135,18 @@ def run_one(game_id, gamedef_system, model):
|
||||
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")
|
||||
gds = extract_block("GAMEDEF_SYSTEM")
|
||||
dsys = extract_block("DESIGN_SYSTEM")
|
||||
print(f"[gamedef-qc] model={model} games={games} (design+gamedef 全图镜像; prompts 抽自 SaaPrompts.java)\n")
|
||||
results = []
|
||||
for g in games:
|
||||
r = run_one(g, gds, model)
|
||||
r = run_one(g, gds, dsys, 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])}")
|
||||
drv = r.get("driver") or "-"
|
||||
print(f" {r['game_id']:14s} {mark:16s} drv={drv:16s} gates={gpass}/{gtot} {('' if r['pass'] else r['err'][:110])}")
|
||||
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(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user