fix(tier2): 焊死平台锁定文件——write_source 拒改装载胶水/三系统(根因3)

spike feie-002 实证:loop 自纠(commit b43d59c)已让 M3 续修、数据表 schema 也修对了
(mergeChains/currencies/requires:{id:qty} 全对,三联动门 requiresReachable/dagAcyclic 转绿),
但 F_wiring/G_input/H_progress/callsAddCoins 仍连环挂。diff 出真因:M3 重写了平台锁定的
src/main.js(装载胶水,130 行差异)——丢了 recHook 透传 → 资源系统 addCoins/consumeIngredient
不再记进 window.__engineCalls → F 门 calls=0、合成不记调用、G/H 门跟着挂。

fixture-spec 预建分工本就规定平台预建文件「写死不动」,但只是 prompt advisory、没机器门。
现把它焊成硬约束:write_source 对 9 个平台锁定文件(main.js/game-core.js/systems/*/layout.js/
tables.js/util/*)的真实修改直接拒(幂等回写放行),并提示「只改 play-scene.js + 数据表」。
agent 可写域 = src/scenes/play-scene.js + data/datatable.gold.json + src/scenes/ 新增表现文件。
单测:9 锁定文件命中、play-scene/datatable/新 scene 放行。prompt 同步加固铁律⑦。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
zizi 2026-06-23 21:37:41 +00:00
parent b43d59cc9a
commit f8ec30c875
2 changed files with 44 additions and 4 deletions

View File

@ -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",

View File

@ -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: