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:
lili 2026-06-18 00:58:01 -07:00
parent d8df5f27ec
commit da4801828d
2 changed files with 55 additions and 15 deletions

View File

@ -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, 但实现成题目要求的那款游戏; 这是个躲避类示例)】

View File

@ -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(