diff --git a/cheap-worker/cheap_verify.py b/cheap-worker/cheap_verify.py index 13226ea9..56e15a3c 100644 --- a/cheap-worker/cheap_verify.py +++ b/cheap-worker/cheap_verify.py @@ -50,20 +50,15 @@ _MAX = len(RICHNESS_CHECKLIST) # = 11 _LAYERS = ("L2", "L3", "L4") # 三分组固定顺序(消费面按分组小计各自取阈,见 §3.6 档位表) # ── 品类扩展 rubric(W-GENRE 品类件④;质量模型 SoT §4 规范二)── -# 每品类另加 ≥4 条品类特有维度:同一字段形状 (标准名, 层标, 含义含一对正反例)、同样标层, +# 数据源唯一 = fixtures/genre-rubrics/.json(items 键,条目 {name,layer,meaning,positive,negative}), +# 经 load_genre_checklist 加载。2026-07-02 rubric 挂点归一:原内联 GENRE_CHECKLISTS 字典退役, +# TRPG 条目一字不动迁入 trpg.json——品类条目自此只有 fixture 一路,不再留内联第二源。 +# 每品类另加 ≥4 条品类特有维度:同一字段形状 (标准名, 层标, 含义)、同样标层, # **分母独立小计**(与通用底座分开、不混合平均——评分输出落 richness.genre.groups,通用 groups 不变)。 # 这仍是【喂给 LLM judge 的评分尺】数据、不是代码校验(红线:不进九门/不进 verdict/不进脚手架); # 设计侧同源对齐 = .agents/skills/-game-design.md §10 品类自检。 # 锚定纪律(规范三):品类条目文本变更须金标复验(金标正例 + 薄反例,分组小计漂移 ≤±1 即回退)。 -GENRE_CHECKLISTS = { - "trpg": [ - ("掷骰过程可见", "L2", "掷骰判定(骰值+加成 vs 难度)以飘字/动画亮给玩家,不是黑箱出结果(正:飘「d20:15+2 vs 12 ✓」;反:点卡直接加钱、不见骰)"), - ("风险回报取舍", "L2", "同屏遭遇/选项的难度与奖励差异化亮牌,挑哪个的决定影响期望收益(正:骷髅卫 ≥9 +7金 vs 宝箱 ≥4 +3金;反:所有卡同难同赏点哪张都一样)"), - ("成长进判定公式", "L2", "等级/伙伴等成长真实改变掷骰判定数值且 UI 可见,不是摆设数字(正:升级→骰+1 写面板、进公式;反:等级只是显示数字、判定恒裸 d20)"), - ("难度爬坡登顶", "L3", "难度随层/进度递升,有明确通关终点与进度可见(正:第 N/6 层 HUD + 每层难度 +1 + 登顶 win;反:无限平难度刷分无终点)"), - ("冒险战报炫耀", "L4", "结算屏讲这局冒险的故事(层数/击败/大成功/胜负因由),可截图分享(正:「爬到第 5 层·击败 7·大成功 2·勇者倒下」;反:只有一个总分数字)"), - ], -} +_GENRE_RUBRICS_DIR = Path(__file__).resolve().parent / "fixtures" / "genre-rubrics" # 品类脚手架目录名 → 品类 key(run_studio 据 scaffold_template 透传 genre;None/未登记 → 不评品类扩展)。 GENRE_BY_TEMPLATE = { @@ -71,13 +66,9 @@ GENRE_BY_TEMPLATE = { "_template-puzzle": "puzzle", } -# 品类条目第二来源 = fixtures/genre-rubrics/.json(数据件;解谜首用)。查找顺序 = 内联 -# GENRE_CHECKLISTS 优先、fixture 兜底(两源归一是 rubric 挂点收敛工单的事,本处只保证两路都能生效)。 -_GENRE_RUBRICS_DIR = Path(__file__).resolve().parent / "fixtures" / "genre-rubrics" - def load_genre_checklist(genre) -> list: - """读品类 rubric fixture → [(标准名, 层标, 判据)*];genre 空/文件缺/坏条目 → [](绝不抛)。 + """读品类 rubric fixture(品类条目唯一数据源)→ [(标准名, 层标, 判据)*];genre 空/文件缺/坏条目 → [](绝不抛)。 条目形状随 fixture(name/layer/meaning,正反例 positive/negative 只作金标锚材料、不进 judge prompt ——prompt 里逐条只给一句可判定判据,与通用底座条目同形)。 @@ -342,9 +333,9 @@ async def verify_richness(game_id: str, *, brief: str = "", model=None, sources= sources: 可选直接注入的源码文本(单测用,绕过文件 I/O);None → 从 game_id 收集。 timeout: judge LLM 调用超时秒数;超时 → degraded(绝不卡死收口)。 max_src_bytes: 喂 judge 的源码上限。 - genre: 品类 key(W-GENRE 件④);命中 GENRE_CHECKLISTS 或 fixtures/genre-rubrics/.json → + genre: 品类 key(W-GENRE 件④);命中 fixtures/genre-rubrics/.json(唯一数据源)→ 同一次评分 additive 追加品类扩展条目(分母独立小计落 result["genre"]); - None/两处均未登记 → 行为与既有版本完全一致(prompt 逐字节不变)。 + None/fixture 缺 → 行为与既有版本完全一致(prompt 逐字节不变)。 Returns: 成功 → parse_judge_output 的结构化评分(含 judgeTokens 观测,best-effort); @@ -362,9 +353,9 @@ async def verify_richness(game_id: str, *, brief: str = "", model=None, sources= import _bootstrap # 代理旁路 + key 注入由 _bootstrap.build_cheap_model 内部处理 m = _bootstrap.build_cheap_model(max_tokens=_JUDGE_MAX_TOKENS) - # 品类扩展(内联 GENRE_CHECKLISTS 优先、fixtures/genre-rubrics/.json 兜底; - # 两处都没有 → 静默按 None——评分尺没有它的条目,别让评分半路造尺)。 - genre_checklist = (GENRE_CHECKLISTS.get(genre) or load_genre_checklist(genre) or None) if genre else None + # 品类扩展(唯一数据源 = fixtures/genre-rubrics/.json,经 load_genre_checklist; + # 没有 fixture → 静默按 None——评分尺没有它的条目,别让评分半路造尺)。 + genre_checklist = (load_genre_checklist(genre) or None) if genre else None genre_key = genre if genre_checklist else None sys_msg = SystemMsg(name="system", content=_JUDGE_SYSTEM_GENRE if genre_checklist else _JUDGE_SYSTEM) diff --git a/cheap-worker/fixtures/genre-rubrics/trpg.json b/cheap-worker/fixtures/genre-rubrics/trpg.json new file mode 100644 index 00000000..ae2f96cb --- /dev/null +++ b/cheap-worker/fixtures/genre-rubrics/trpg.json @@ -0,0 +1,41 @@ +{ + "genre": "trpg", + "_note": "TRPG 品类丰富度 rubric 扩展(W-GENRE 品类件④·机器版)。喂 cheap_verify 的 LLM 验证 agent:与通用底座 11 条同一次评分调用、同一 0/1+层标形状,但分母独立小计(质量模型 SoT §3.3 规范二,不与底座混合平均);纯 LLM 非阻塞,绝不写成代码校验/不进九门/不进脚手架。2026-07-02 rubric 挂点归一:条目自 cheap_verify.GENRE_CHECKLISTS['trpg'](caadfe4c 内联)机器迁入本文件、一字不动——meaning 内嵌「(正:…;反:…)」是内联时代原文,为锚定纪律(judge prompt 逐字节不变)原样保留;positive/negative 字段为自 meaning 机器拆出的同文抄录(金标锚材料,不进 judge prompt)。与 .agents/skills/trpg-game-design.md §10 品类自检同源——改任一处必须同改另一处,且评分尺变更须金标复验(单款分组小计漂移 >±1 即回退,规范三)。金标锚:正例=_template-trpg 骨架(merge 前基线:品类 5/5、通用 8/11),薄反例=合成黑箱点卡(点卡直接加钱、无骰无取舍无成长无终点;merge 前基线:品类 0/5、通用 0/11)。", + "items": [ + { + "name": "掷骰过程可见", + "layer": "L2", + "meaning": "掷骰判定(骰值+加成 vs 难度)以飘字/动画亮给玩家,不是黑箱出结果(正:飘「d20:15+2 vs 12 ✓」;反:点卡直接加钱、不见骰)", + "positive": "飘「d20:15+2 vs 12 ✓」", + "negative": "点卡直接加钱、不见骰" + }, + { + "name": "风险回报取舍", + "layer": "L2", + "meaning": "同屏遭遇/选项的难度与奖励差异化亮牌,挑哪个的决定影响期望收益(正:骷髅卫 ≥9 +7金 vs 宝箱 ≥4 +3金;反:所有卡同难同赏点哪张都一样)", + "positive": "骷髅卫 ≥9 +7金 vs 宝箱 ≥4 +3金", + "negative": "所有卡同难同赏点哪张都一样" + }, + { + "name": "成长进判定公式", + "layer": "L2", + "meaning": "等级/伙伴等成长真实改变掷骰判定数值且 UI 可见,不是摆设数字(正:升级→骰+1 写面板、进公式;反:等级只是显示数字、判定恒裸 d20)", + "positive": "升级→骰+1 写面板、进公式", + "negative": "等级只是显示数字、判定恒裸 d20" + }, + { + "name": "难度爬坡登顶", + "layer": "L3", + "meaning": "难度随层/进度递升,有明确通关终点与进度可见(正:第 N/6 层 HUD + 每层难度 +1 + 登顶 win;反:无限平难度刷分无终点)", + "positive": "第 N/6 层 HUD + 每层难度 +1 + 登顶 win", + "negative": "无限平难度刷分无终点" + }, + { + "name": "冒险战报炫耀", + "layer": "L4", + "meaning": "结算屏讲这局冒险的故事(层数/击败/大成功/胜负因由),可截图分享(正:「爬到第 5 层·击败 7·大成功 2·勇者倒下」;反:只有一个总分数字)", + "positive": "「爬到第 5 层·击败 7·大成功 2·勇者倒下」", + "negative": "只有一个总分数字" + } + ] +} diff --git a/cheap-worker/tests/test_cheap_verify.py b/cheap-worker/tests/test_cheap_verify.py index d159e570..18027eb3 100644 --- a/cheap-worker/tests/test_cheap_verify.py +++ b/cheap-worker/tests/test_cheap_verify.py @@ -191,19 +191,26 @@ def test_verify_empty_sources_skips_llm(): # ── W-GENRE 品类件④:品类扩展 rubric(分母独立小计)──────────────────────────── def test_genre_checklist_trpg_shape(): - """trpg 品类扩展 ≥4 条、形状同通用底座 (名, 层标, 含义含正反例)、层标合法(§4 规范二)。""" - gc = cheap_verify.GENRE_CHECKLISTS["trpg"] + """trpg 品类扩展(fixture 单源)≥4 条、形状同通用底座 (名, 层标, 含义含正反例)、层标合法(§4 规范二)。""" + gc = cheap_verify.load_genre_checklist("trpg") assert len(gc) >= 4, f"品类扩展应 ≥4 条,得 {len(gc)}" for name, layer, meaning in gc: assert isinstance(name, str) and name, "标准名非空" assert layer in ("L2", "L3", "L4"), f"层标非法:{layer}" assert "正:" in meaning and "反:" in meaning, f"「{name}」含义应带一对正反例(规范三锚定)" + # 条目名/顺序锁定 = 内联时代原文(caadfe4c);迁出 fixture 后锚定不动(锚定纪律,同通用 8 条锁定) + assert [n for n, _l, _m in gc] == ["掷骰过程可见", "风险回报取舍", "成长进判定公式", "难度爬坡登顶", "冒险战报炫耀"] assert cheap_verify.GENRE_BY_TEMPLATE["_template-trpg"] == "trpg" +def test_inline_genre_checklists_retired(): + """rubric 挂点归一不变量:内联 GENRE_CHECKLISTS 已退役,品类条目只剩 fixtures/genre-rubrics/ 一路(防两路复活)。""" + assert not hasattr(cheap_verify, "GENRE_CHECKLISTS"), "内联品类字典应已退役(单一数据源=fixture)" + + def test_parse_with_genre_checklist_independent_denominator(): """品类段独立分母:通用 score/groups 不被品类条目污染;richness.genre 形状齐全。""" - gc = cheap_verify.GENRE_CHECKLISTS["trpg"] + gc = cheap_verify.load_genre_checklist("trpg") checks = [{"name": n, "hit": True, "why": "w"} for (n, _l, _m) in cheap_verify.RICHNESS_CHECKLIST] checks += [{"name": n, "hit": (i != len(gc) - 1), "why": "w"} for i, (n, _l, _m) in enumerate(gc)] r = cheap_verify.parse_judge_output(json.dumps({"checks": checks, "notes": "n"}), @@ -217,7 +224,7 @@ def test_parse_with_genre_checklist_independent_denominator(): def test_parse_genre_positional_fallback_offset(): """品类段位置兜底:name 全对不上时按「通用在前、品类紧随」的偏移对位。""" - gc = cheap_verify.GENRE_CHECKLISTS["trpg"] + gc = cheap_verify.load_genre_checklist("trpg") n = 11 + len(gc) checks = [{"name": f"未知{i}", "hit": True, "why": ""} for i in range(n)] r = cheap_verify.parse_judge_output(json.dumps({"checks": checks}), @@ -235,7 +242,7 @@ def test_parse_no_genre_backcompat_no_genre_key(): def test_build_judge_user_genre_block_additive(): """user prompt:无品类段落基线不变;有品类只 additive 加品类清单段 + 条数契约(11+N)。""" base = cheap_verify._build_judge_user("SRC", "B") - gc = cheap_verify.GENRE_CHECKLISTS["trpg"] + gc = cheap_verify.load_genre_checklist("trpg") withg = cheap_verify._build_judge_user("SRC", "B", genre_key="trpg", genre_checklist=gc) assert "品类扩展清单" not in base and "共 11 条" in base assert "trpg 品类扩展清单" in withg and f"共 {11 + len(gc)} 条" in withg