diff --git a/tier2/gen-worker/worker/roles.py b/tier2/gen-worker/worker/roles.py index a78ef66c..36287956 100644 --- a/tier2/gen-worker/worker/roles.py +++ b/tier2/gen-worker/worker/roles.py @@ -67,7 +67,12 @@ def writer_system(brief: str, design_text: str = "", *, fixture_hint: str = "") "据失败门 write_source 针对性修,再 build→run_gates,一轮轮逼到门绿。** 门没绿时调 finish 会被拒。\n" "⑥ 数据表是头号雷区:平台 tables.js 按固定 key 读 data/datatable.gold.json——**就地填 sentinel 的值," "别改 key、别自创 schema**(详见下方品类约定)。填完先 validate_datatable 确认结构对,再 build。\n" - "⑦ 收敛即 finish:门全绿才调 finish 交付;别无谓地反复刷探索,也别没绿就想 finish。\n", + "⑦ 平台文件写死、绝不许改:**你只能改 src/scenes/play-scene.js(表现层)+ data/datatable.gold.json(数据表)," + "或在 src/scenes/ 下新增表现文件**。src/main.js(装载胶水)、src/game-core.js、src/systems/*、src/layout.js、" + "src/data/tables.js、src/util/* 全是平台锁定的——它们已把引擎接线(recHook 记 addCoins/consumeIngredient)、" + "状态机、结算、latch、readState 导出、命中映射坐标都写好了。**改它们 = 破坏接线**(实测:重写 main.js 丢了 " + "recHook → F 门 calls=0、合成不记引擎调用、G/H 门连环挂)。write_source 会拒绝改这些文件。\n" + "⑧ 收敛即 finish:门全绿才调 finish 交付;别无谓地反复刷探索,也别没绿就想 finish。\n", "【典型循环节奏】scaffold_init(开局一次)→ write_source 填表现层 → build →(失败则据日志改源再 build)" "→ headless_check 快筛 → run_gates 真玩 → read_verdict 看哪门没过 → write_source 针对性修 →" " 再 run_gates …… 直到门全绿 → finish。\n", diff --git a/tier2/gen-worker/worker/toolkit.py b/tier2/gen-worker/worker/toolkit.py index 6b95df33..4dc10566 100644 --- a/tier2/gen-worker/worker/toolkit.py +++ b/tier2/gen-worker/worker/toolkit.py @@ -26,6 +26,28 @@ from agentscope.tool import Toolkit, FunctionTool from . import run +# ── 平台锁定文件(写死、agent 不许改;spike feie-002 实证根因 3)── +# fixture-spec 预建分工:平台预建(装载胶水 + 三系统纯逻辑 + 状态机 + 结算 + latch + readState 导出 + +# 输入路由/命中映射约定)写死不动;agent 只写表现层 play-scene.js + 数据表 + 新增表现文件。 +# 实证:M3 重写了 main.js(装载胶水)→ 丢了 recHook 透传 → F 门 calls=0、合成不记引擎调用、G/H 门连环挂。 +# 故把 fixture 平台预建文件焊成只读:write_source 改它直接拒,并提示「只改 play-scene.js + 数据表」。 +LOCKED_PLATFORM_FILES = frozenset({ + "src/main.js", # entry · PhaserGameFactory 装载胶水(recHook/caps/scene 接线在此) + "src/game-core.js", # 三系统编排 + 状态机 + 结算 + latch + _forensicsView/readState 导出 + "src/layout.js", # 棋盘/订单面板布局常量单一源(命中映射 + driver tap 同源坐标) + "src/data/tables.js", # 数据表适配层(按固定 key 读 datatable.gold.json) + "src/util/seeded-random.js", # 受控随机源(确定性铁律) + "src/systems/resource-system.js", # 资源系统纯逻辑(addCoins/consumeIngredient 经 recHook 记调用) + "src/systems/merge-system.js", # 合成系统纯逻辑(tapCell 两次点同物品合成) + "src/systems/order-system.js", # 订单系统纯逻辑 + "src/systems/reachability.js", # 可达性纯逻辑 +}) + + +def is_locked_platform_file(rel: str) -> bool: + """该工程内相对路径是否平台锁定(写死、agent 不许改)。""" + return rel in LOCKED_PLATFORM_FILES + def _content_hash(files: list[dict]) -> str: """整工程内容指纹 = sha256(各文件 path+content 规范化拼接)。对齐 A3 contentHash 语义。""" @@ -130,7 +152,7 @@ def build_toolkit(session: Tier2Session) -> Toolkit: files: 文件数组,每项 {path: 工程内相对路径(如 'src/scenes/PlayScene.js',禁 '..'), content: 文件全文 UTF-8, role?: 文件角色(entry/scene/system/ui/config/lib/other)}。 """ - written, errors = [], [] + written, errors, locked = [], [], [] for f in files or []: rel = (f or {}).get("path") or "" content = (f or {}).get("content") @@ -140,6 +162,17 @@ def build_toolkit(session: Tier2Session) -> Toolkit: if not isinstance(content, str): errors.append({"path": rel, "reason": "content 必须是字符串"}) continue + # 平台锁定文件保护(根因 3):若改的是写死的平台文件且内容真变了,拒绝并提示。 + # 允许「内容完全相同」的无操作重写(幂等;不阻断 agent 把整树回写)。 + if is_locked_platform_file(rel) and session.files.get(rel) != content: + locked.append({ + "path": rel, + "reason": "这是平台锁定文件(装载胶水/三系统纯逻辑/状态机/结算/latch/readState/布局)," + "写死不许改——改它会破坏引擎接线(如丢 recHook → F 门 calls=0、合成不记调用)。" + "你只该改 src/scenes/play-scene.js(表现层)+ data/datatable.gold.json(数据表)," + "或在 src/scenes/ 下新增表现文件。请把这个文件的改动撤回。", + }) + continue session.files[rel] = content if (f or {}).get("role"): session.roles[rel] = f["role"] @@ -148,8 +181,10 @@ def build_toolkit(session: Tier2Session) -> Toolkit: written.append(rel) # 同步落盘(供后续 build/headless_check 子进程读)。 run.scaffold(session.game_id, session.file_list()) - return json.dumps({"ok": len(errors) == 0, "written": written, "errors": errors}, - ensure_ascii=False) + return json.dumps( + {"ok": len(errors) == 0 and len(locked) == 0, + "written": written, "errors": errors, "lockedRejected": locked}, + ensure_ascii=False) # ── 核心环:validate_datatable(数据表 schema/可达性预检)── async def validate_datatable() -> str: