games-development-ai/cheap-worker/hard_genre_xtheme.py
lili 8d851ea716
Some checks failed
contract-gates / contract-gates (push) Has been cancelled
docs-gate / docs-gate (push) Has been cancelled
feat(cheap-gen): W-AXIS-V2 波2——取证契约退役+提示全加载链清洗,四门∧测试员为唯一验收链
opus 执行位交付、主会话四步验收过(三套测试亲跑/红线 diff 亲读/冒烟截图亲眼):

- 验收消费:check 新增源码级插件调用存在断言(_pluginCallPresenceErrors 词法层,
  接替 F_wiring 真接线护栏);_forensicsView「必须实现」拦截摘除,降级为可选调试
  接口(handleTap 输入契约与 ctx.log 日志纪律保留);gate_judge 放行措辞对齐四门
- play-spec/驱动器退役(逐件裁定 10 件):gen.mjs done 门自动产 play-spec 摘除、
  cheap_gates/cheap_studio/cheap_modify/service_driver 调用摘除、_read_driver_type
  改读 evidence/playtest/playtest.json;golden-spec 三工具(auto_vs_golden/bake_off/
  compare_node)显式封存;ensure_play_spec 两实现保留一个版本窗口(只摘调用不删码)
- prompt 全加载链清洗:cheap-system.md v1.6.3→1.7.0(删 _forensicsView 自动验收
  契约红线与全部 targets/occupied 语义,五法句/品类指路同步),cheap_roles 内置
  回退逐字节同步(test_roles 12/12 证 parity),modify 切片经运行时切块自动同步;
  5 品类 skills+littlejs 手册+recipes×2+模板 8 件+README 6 件教学清洗,真设计
  价值(单击 casual/可解性质/双层反馈/latch)保留
- 批账:hard_genre_batch/xtheme 行加 playtest 段,_row_total_cost 修 shadow 下
  测试员成本漏算(v2 取 max 防双计)

验收(主会话亲验):pytest cheap-worker 512 绿 + tier2 141 绿 + node 49/49;
25 局基线新旧口径重放对照——真坏 5 局全拦(floor 拦 2/测试员拦 3)、无一例旧拦
新放,翻案 2 局均为破案坐实的仪器误杀;n=3 冒烟(mode=v2)全链绿:w2v2-sim
harness verdict.pass=True 而测试员 reject(机械放行测试员逮住,盘上双证)、
w2v2-sim-fix 现象反馈两轮回喂→fix2 收敛(配方显示修成,局内截图亲眼);
rg 零残留(模板残留均为「验收不读它」的降级调试接口合规形态);真跑 ¥8.4。

已知余项:repairFeedback 有产地、Service 续修环消费者未接(测试员 reject 不入
RepairMiddleware,续修判据仍四门)——是否接入随波3 校准数据一并裁;v1 配置级
回退窗口关闭为创始人知悉项,genconfig 默认 mode 仍 shadow、切 v2 归创始人。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 09:21:04 -07:00

103 lines
6.3 KiB
Python
Raw Permalink 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, "accepted": False, "floorPass": None, "acceptanceVersion": None, "verdictPass": None,
"fail": f"{type(e).__name__}: {e}", "wallSec": round(time.time() - t0, 1)}
v = summary.get("verdict") or {}
floor = summary.get("floor") or {}
rich = summary.get("richness") or {}
pt = summary.get("playtest") or {} # 测试员段(W-AXIS-V2 波2 批账观测口径)
return {
"tag": tag, "gid": gid, "template": scaffold_template or "_template(通用)",
"locked": write_whitelist is not None,
"ok": bool(summary.get("ok")),
"accepted": bool(summary.get("accepted")), # W-AXIS-V2:最终验收(四门 ∧ 测试员)
"floorPass": floor.get("pass"), # 预筛权威 = 四门投影(取代 verdictPass)
"acceptanceVersion": summary.get("acceptanceVersion"),
"verdictPass": v.get("pass"), # harness 原生九门/七门,仅对照、不作判定输入
"playtest": { # 测试员裁决摘要(裁决/degraded/rolls/成本)
"verdict": pt.get("verdict"), "degraded": pt.get("degraded"),
"rollCount": pt.get("rollCount"), "costRmb": pt.get("costRmb"),
} if pt else None,
"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:
acc = bool(r.get("accepted"))
fp = r.get("floorPass") is True
passed += 1 if acc else 0
mark = "" if acc else ("⚠️测试员拒" if fp else ("FALSE-PASS" if r.get("ok") else ""))
pt = r.get("playtest") or {}
print(f"{mark} {r['tag']:16s} tpl={r.get('template')} lock={r.get('locked')} "
f"accepted={r.get('accepted')} floorPass={r.get('floorPass')} ver={r.get('acceptanceVersion')} "
f"verdictPass(对照)={r.get('verdictPass')} playtest={pt.get('verdict')}/rolls={pt.get('rollCount')} "
f"ptDegraded={pt.get('degraded')} failedGates={r.get('failedGates')} "
f"attempts={r.get('attempts')} rich={r.get('richness')}/12 "
f"cost={r.get('costRmb')} ptCost=¥{pt.get('costRmb')} wall={r.get('wallSec')}s {('FAIL='+r['fail']) if r.get('fail') else ''}")
print(f">>> 跨主题换皮达标(accepted 最终验收 = 四门 ∧ 测试员):{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())