lili 6201f95146 feat(wg1-gen): 便宜模型生成可真玩轻游戏——L1 worker + 七门真玩 harness(HJ-GEN-001 成立)
W-G1 生成核心:brief→便宜模型写 GameHostFactory→esbuild __GameBundle iife→P1 bootGameHost
装载→本机 Chrome+CDP 七门真玩取证;失败回喂错因重试。只消费冻结契约,零改 P1/P2。

实测(deepseek-v4-flash 最便宜档):Pong/Simon/打地鼠 三款【七门全过真玩】,
单款 ¥0.01–0.03(远低于 ¥0.15 L1 闸门)。最优模型:flash 打底 + 难档升 deepseek-v4-pro(Simon 0 重试 ¥0.06)。

关键:实证「能生成代码 ≠ 能玩」——一处 prompt 输入坐标契约写错(误称 x/y∈[0,1],实为逻辑像素)
让生成游戏能编译/渲染/动但点击全失灵;Pong 因自走动画假绿混过、Simon 被正确判否。
为此加装 G 门(确定性 A/B 对照,隔离自走动画假绿)+ 修 prompt 后三款真过。

- harness:games/_wg1-gen/_shared/{entry-bundle 模板,index 模板,play.cdp.cjs 七门,serve-and-play.sh}(复用 ref buildRefHostConfig + browser-evidence.cjs)
- worker:wg1/gen-worker/worker/{_client(代理旁路),prompt(生成上下文包),validate(静态门+node --check),cost(¥折算),run(§3 编排+重试门)}
- golden-path 自检:ref 工厂 5/6(F=0 菜单态正确)+ probe 工厂 7/7 → harness 无假绿
- game-runtime/package.json:加 ws(CDP 驱动 devDep)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 22:50:24 -07:00

112 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""validate.py —— L1 校验闸门:从模型输出抽源码 + 静态契约检查 + node --check 语法门。
spike 无此模块(gate3 只算 token),故 L1 新建(worker-reusables 地图明示)。
两层:① 静态契约扫描(默认导出/五方法/禁用词)——快、拦低级违约;② node --check ESM 语法——拦编译失败。
"""
import re
import subprocess
import tempfile
import os
def extract_code(content):
"""从模型输出抽出 ```js 代码块;无 fence 则退而取最长 ``` 块;再无则用全文。"""
if not content:
return ""
# 优先 ```js / ```javascript
m = re.search(r"```(?:js|javascript)\s*\n(.*?)```", content, re.S | re.I)
if m:
return m.group(1).strip()
# 任意 ``` 块(取第一个)
m = re.search(r"```\s*\n(.*?)```", content, re.S)
if m:
return m.group(1).strip()
# 无 fence:若看着像代码(含 export default)直接用全文
if "export default" in content:
return content.strip()
return ""
def _strip_comments(src):
"""去行注释/块注释,避免禁用词扫描被注释里的字样误伤。"""
src = re.sub(r"/\*.*?\*/", "", src, flags=re.S)
src = re.sub(r"//[^\n]*", "", src)
return src
# 禁用模式(正则, 说明)——违反即【契约违约】,拦在打包前。
_FORBIDDEN = [
(r"(^|\n)\s*import\s", "禁 import:工厂必须自包含单文件(含禁 import littlejsengine)"),
(r"\bimport\s*\(", "禁动态 import()"),
(r"\brequire\s*\(", "禁 require"),
(r"littlejsengine", "禁引擎名字空间"),
(r"\bMath\.random\b", "禁 Math.random(用 ctx.random)"),
(r"\bDate\.now\b", "禁 Date.now(用 ctx.time.nowMs)"),
(r"\bperformance\.now\b", "禁 performance.now(用 ctx.time)"),
(r"\baddEventListener\b", "禁 addEventListener(用 ctx.getInput().on)"),
(r"\brequestAnimationFrame\b", "禁 requestAnimationFrame(宿主驱动)"),
(r"new\s+AudioContext", "禁 new AudioContext(用 ctx.getAudioContext)"),
(r"\bdocument\s*\.", "禁直接操作 document"),
(r"\bwindow\s*\.", "禁直接操作 window"),
]
def static_check(src):
"""静态契约检查。返回 (ok, errors[])。"""
errors = []
if not src or len(src.strip()) < 60:
return False, ["源码为空或过短(疑似未产出有效代码)"]
if "export default" not in src:
errors.append("缺少 export default(必须默认导出工厂函数)")
scan = _strip_comments(src)
# 五方法存在性(宽松:方法名作为标识符出现)。
for meth in ("init", "update", "render", "destroy"):
if not re.search(r"\b" + meth + r"\b", scan):
errors.append(f"缺少 {meth} 方法")
# 必须经 boot.ctx 取能力(至少出现 ctx. 用法)。
if "ctx" not in scan:
errors.append("未见 ctx 用法(能力须经 boot.ctx)")
# 必须至少用一次 getEngine(硬性验收点:真接线门要有信号)。
if "getEngine" not in scan:
errors.append("未调用 getEngine()(必须在某事件上用一次引擎能力,否则真接线门无信号)")
# 禁用词扫描。
for pat, desc in _FORBIDDEN:
if re.search(pat, scan):
errors.append("违禁:" + desc)
return (len(errors) == 0), errors
def node_syntax_check(src):
"""node --check ESM 语法门(拦编译失败)。返回 (ok, stderr)。"""
tmp = tempfile.NamedTemporaryFile("w", suffix=".mjs", delete=False, encoding="utf-8")
try:
tmp.write(src)
tmp.close()
r = subprocess.run(
["node", "--check", tmp.name],
capture_output=True, text=True, timeout=30,
)
if r.returncode == 0:
return True, ""
# 清理临时路径噪声,只留错因。
return False, (r.stderr or r.stdout or "node --check 失败").strip()
except Exception as e:
return False, f"node --check 异常:{e}"
finally:
try:
os.unlink(tmp.name)
except Exception:
pass
def validate(src):
"""综合校验门。返回 (ok, errors[])。静态过了才跑 node --check(省时)。"""
ok, errors = static_check(src)
if not ok:
return False, errors
ok2, stderr = node_syntax_check(src)
if not ok2:
return False, ["语法错误(node --check):" + stderr[:500]]
return True, []