games-development-ai/cheap-worker/hard_genre_xtheme.py
lili 1c4d5838ec fix(cheap-gen): 撤写锁·全放开 + max_tokens 512K——创始人纠偏 + 浏览器验收坐实
创始人 2026-07-08/09 两次纠偏,各揪出一个写锁法漏的真根因:
① 浏览器验收:写锁 core.js 致「榫卯」brief 出「陶艺」游戏(主题漂)——规范应是文件位置
   + 绝对通用 plumbing(L1_FIXED 已锁 host-config/game/index/entry/main),不该把模型限死到
   只改几个文件(只改几个文件游戏做不好玩、切不中题)。→ 全撤 write_whitelist,game-logic/
   core/render/assets 全放开,模型自由做好玩切题游戏;截断类失败靠 harness 门兜不靠锁。
② max_tokens:实测 M3 单轮能输出 17000 tokens/finish_reason=stop、accepts 40K/512K 无报错
   ——16K 是自设限非模型限;cheap 走 OpenAI inline thinking(thinking 吃 output 额度)致大文件
   整写截断(heritage 黑屏/thrash 病根之一)。→ genconfig max_tokens 16K→512K(统一上限,可调)。

浏览器验收 it8(不锁+stripCode修复+足量token):heritage=榫卯工坊·直榫凳·选料→画线→凿卯→
修整(切题修好);action=撞了得分535·1771米·翻转30(坏死修好);trpg=已清9层3次升级(死锁没了+
内容更全);全 1-2 attempts 无 thrash。残余 idle-sim「↑NaN」(render.js:164 upgradeCost=NaN,
模型数值 bug,待修)。build_reskin_system_prompt 撤锁后未用(留待清理)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 04:27:17 -07:00

88 lines
5.0 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

"""hard_genre_xtheme.py — 轻A 换皮鲁棒性【跨主题】验证(it5 的 brief 与模板同题=偏软,本轮换主题真考换皮)。
it5 的 5 个 brief 恰与模板品类同题(heritage=榫卯、feiyi 模板也=榫卯),换皮近乎恒等、验不出"跨主题换皮"能力。
本脚本给每个路由品类换一个【不同主题】的 brief(仍命中同一 per-genre 模板+写锁),真考:模型能否只写 game-logic
把一款「榫卯」模板换皮成「剪纸」、把「点客」换皮成「奶茶店」等——render/core 锁死下,靠数据位驱动出不同主题。
跑:cheap-worker/.venv/bin/python cheap-worker/hard_genre_xtheme.pyNO_PROXY + 凭据档自动解析 NEWAPI_KEY
"""
import asyncio
import json
import time
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).resolve().parent))
import cheap_studio # noqa: E402
from cheap_genre_route import route_genre # noqa: E402
from cheap_roles import SCAFFOLD_DESC_BY_TEMPLATE # noqa: E402
# 跨主题 brief——每个仍路由到同一 per-genre 模板(关键词命中),但主题换了,真考换皮。
XTHEME_BRIEFS = [
# 非遗:榫卯→剪纸(仍命中 feiyi:非遗/剪纸)
("feiyi-jianzhi", "一个还原传统剪纸技艺的非遗小游戏:按刻纸工序(起稿→折纸→刻镂→揭裱)逐步操作,每步有稳度节奏判定,完成得窗花成品评分"),
# TRPG:奇幻地牢→赛博骰战(仍命中 trpg:掷骰/地下城)
("trpg-cyber", "一个赛博朋克掷骰爬塔:每层地下城遇一个黑客守卫,点击掷骰(可见骰点)按攻防结算,击败进下一层,失败结算,每3层升级选芯片天赋"),
# 经营:通用点客→奶茶店(仍命中 shop:经营/奶茶店/顾客/点单)
("shop-milktea", "一个经营奶茶店:顾客排队点单,按配方调对饮品上对得分,收银攒钱升级解锁新品与自动出杯设备"),
# 解谜:通用→数字华容道(仍命中 puzzle:解谜/华容道)
("puzzle-huarong", "一个数字华容道解谜:滑动数字块把它们按序归位,规则逐关递进,卡壳给提示,通关计步数炫耀成绩"),
# 剧情:通用→校园悬疑(仍命中 story:互动小说/分支/多结局)
("story-campus", "一个校园悬疑互动小说:每屏一段剧情+2-3个选项,选择影响走向,揭开三个不同真相结局,有结局图鉴"),
]
async def _run_one(sem, tag, brief, port, cdp):
async with sem:
gid = f"xt-{tag}"
t0 = time.time()
scaffold_template, routed_genre = route_genre(brief)
# 2026-07-08 创始人纠偏:不写锁,全放开(只锁 L1_FIXED 通用 plumbing)。
write_whitelist = None
scaffold_desc = SCAFFOLD_DESC_BY_TEMPLATE.get(scaffold_template) if scaffold_template else None
try:
summary = await cheap_studio.run_studio(
gid, brief, run_gates=True, port=port, cdp_port=cdp,
scaffold_template=scaffold_template, scaffold_desc=scaffold_desc,
write_whitelist=write_whitelist, genre=routed_genre)
except Exception as e: # noqa: BLE001
return {"tag": tag, "gid": gid, "template": scaffold_template, "locked": write_whitelist is not None,
"ok": False, "fail": f"{type(e).__name__}: {e}", "wallSec": round(time.time() - t0, 1)}
v = summary.get("verdict") or {}
rich = summary.get("richness") or {}
return {
"tag": tag, "gid": gid, "template": scaffold_template or "_template(通用)",
"locked": write_whitelist is not None,
"ok": bool(summary.get("ok")), "verdictPass": v.get("pass"),
"failedGates": v.get("failedGates"), "attempts": summary.get("attempts"),
"costRmb": summary.get("costRmb"), "richness": rich.get("score"),
"wallSec": round(time.time() - t0, 1),
}
async def main():
sem = asyncio.Semaphore(2)
tasks = [_run_one(sem, tag, brief, 4380 + i * 2, 9282 + i * 2)
for i, (tag, brief) in enumerate(XTHEME_BRIEFS)]
results = await asyncio.gather(*tasks)
print("\n\n========== 轻A 跨主题换皮验证 ==========")
passed = 0
for r in results:
vp = r.get("verdictPass") is True
passed += 1 if vp else 0
mark = "" if vp else ("FALSE-PASS" if r.get("ok") else "")
print(f"{mark} {r['tag']:16s} tpl={r.get('template')} lock={r.get('locked')} "
f"verdictPass={r.get('verdictPass')} failedGates={r.get('failedGates')} "
f"attempts={r.get('attempts')} rich={r.get('richness')}/12 "
f"cost={r.get('costRmb')} wall={r.get('wallSec')}s {('FAIL='+r['fail']) if r.get('fail') else ''}")
print(f">>> 跨主题换皮达标(九门 verdict.pass):{passed}/{len(results)} 过门率={passed/len(results):.0%}")
out = Path(__file__).resolve().parent / "results" / "hard-genre-xtheme.json"
out.parent.mkdir(exist_ok=True)
out.write_text(json.dumps(results, ensure_ascii=False, indent=2), encoding="utf-8")
print(f">>> 结果 → {out}")
if __name__ == "__main__":
asyncio.run(main())