games-development-ai/docs/agent-specs/_archive/2026-06-13-P1-王蓝莓上引擎-review.md
zizi 7f24a344d0 docs(agent-specs): B2 归档——64 个闭线工作记录移入 _archive/,热目录顶层 90→26
承接目录治理:上轮只压缩内容未减文件数,闭线工作记录仍平铺致目录看着仍一堆(创始人指出)。本次执行 B2 归档。

64 个 ≤06-15 闭线档(25 压缩桩 + 闭线 review/report/纪要/edit-plan)git mv 入 docs/agent-specs/_archive/(文件名不变、仍 git 跟踪可查)。热目录 ≤06-15 仅留 13 活档(决策/纲领/SoT/活spike)+13 个 06-16 在飞。

活资产 20 处旧路径引用(.agents/docs/memory/_index)同步改 _archive/,引用断裂复测=0;_index 活地图 + 治理档状态收口。约束:0 个 06-16 被移、orchestrator 等未跟踪在飞档零误纳。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 13:26:19 +00:00

21 KiB
Raw Blame History

P1 · 王蓝莓上引擎(spike 游戏宿主装载契约)· 评审版

状态:review 版 · 2026-06-13 · 给创始人决策(结论先行,不写代码级执行细节,多用 Mermaid)。已过对抗复审(2026-06-13 deep·全程读真源码):无致命项;4 重大项 + 5 项不明假设已全部纳入 execution 版钉死(逐条裁决见 execution §12)。readiness = ready-with-caveats(仅 1 个非阻断 open item:契约工件命名,见 §8)。 定位:本文是 Runner v2 全弧 P1 相(第一相)的评审版。母梁见 2026-06-13-runner-v2-arc-review.md §5.1。P1 的任务 = 把"游戏宿主装载契约"(arc 上层契约)用王蓝莓 spike 到真跑在引擎上;P2/P3 归并入母 spec(见 §7)。 前提(创始人已拍):① 推进 P1(全弧第一相);② O1 装载契约 = 新立 additive 第 9 类契约(不污染既有插件契约 PluginContext),生命周期 = init/update/render/onInput + destroy,boot 时把引擎 mainContext 注入游戏层;③ O2 = P2 合并进母 spec Phase D;④ 不用 fable,一律 opus。 复审改点(结论级,详见 execution §12):契约工件落 game-runtime/src/core/game-host.d.ts(与 api.d.ts 同目录,非 contracts/——contracts 是跨三仓五工位业务契约 SSOT,本契约是 game-runtime 内部 host↔游戏工厂接线面,让 SSOT 反向依赖业务源码且拆仓即断);real 通道补 __refBootedReal 就绪标记 + driver 范式从「stepFrames 手摇」切到「真触摸+轮询」(引擎掌帧下手摇=双掌帧违铁律);同屏门暖色阈值开工前坐实(不留占位);bundle 延后到 engineInit().then 内以 mainContext 建(防渲染面分裂)。 关联:边界铁律 engine-plugin-boundary-model、A0 地基 a0-engine-takeover-done、harness 配方 .agents/skills/game-e2e-cdp-harness.md(§5 门0 / §6 门②)、A0 双通道(?engine=stub 回滚)。


1. 结论先行

P1 = 给 wanglanmei-ref 加 ?engine=real 通道,用它把"游戏宿主装载契约"(新立 additive 第 9 类契约)spike 实,证三件事并人在 mini-desktop 浏览器真玩:

  1. 引擎真掌帧(engineInit 五回调驱动游戏 update/render,废 main.js 自管的浏览器 RAF);
  2. 游戏美术与引擎粒子同屏可玩(游戏绘制面从自带 #game 2D 画布改引擎 mainContext,两面合并成一张画布);
  3. 回滚保真(?engine=stub 通道逐行保旧行为 + 逐像素确定性,A0 双通道范式)。

同时把已存在但仍是闭包的 makeEngineCaps(host.js:197-318)提升为可 import 模块 host-dev/engine-caps.js,host-dev / ref / 未来生成游戏共用同一份引擎能力包装层——这是"模块化已有件"(搬运),非从零造(arc §0-F2 已订正原稿"engine-caps.js 不存在=真新增工作")。

装载契约的本质(O1 已拍形态):一款游戏 = 一个纯模块工厂(零引擎 import,Q4 铁律),暴露稳定生命周期 init(ctx, mainCtx) / update(dt) / render(g) / onInput(ev) / destroy();其中 render(g) 的 g = 引擎 mainContext(boot 时注入),插件能力仍经下层已冻结的 ctx.getEngine() 拿。该契约落 game-runtime/src/core/game-host.d.ts(与既有 api.d.ts 同目录,复审改点;理由见上「复审改点」),与 api.d.ts 是 additive 关系——受控面 6 项契约零改(getContext2d/getInput/getAudioContext/getEngine,api.d.ts:269/281/286/303)。game 工厂必须是独立、可被 P3 复制的实现(独立文件默认导出、零引擎 import,禁 main.js 内联糊过——这是 P3 生成 agent 的真实参照靶,复审钉死的硬边界)。

⚠️ 为何必须新立、不能借 PluginContext:ref 的游戏层(core.update/drawFrame(g),main.js:621/648)不是插件、不走 PluginContext;今天全仓没有"游戏整体↔引擎 mainContext"的约定面(grep GameModule/装载契约 在 contracts/+src/ 零命中,已复证)。getEngine()(api.d.ts:303)是插件拿引擎能力的面,凑不上"游戏 update/render 主体怎么挂引擎"。这正是装载契约要补的缺口——它与 PluginContext 一个都不能替另一个(边界模型"同一职不留两条并行路")。


2. 装载契约决策(O1 已拍)

决策点 已拍结论 理由 / 证据
归属 新立 additive 第 9 类契约,不折进 api.d.ts 插件面 避免污染插件契约语义,与边界模型"一职一路"一致;PluginContext=插件面、GameHost=游戏面,两者正交(arc O1 建议①)。
落点(复审改) game-runtime/src/core/game-host.d.ts(与 api.d.ts 同目录),非 contracts/ contracts/ 是跨三仓五工位的业务契约 SSOT(contracts/README.md §1,含拆仓计划);本契约是 game-runtime 内部 host↔游戏工厂接线面,消费方只在 game-runtime,落同目录 import 方向顺(./api.d.ts);落 contracts 会让 SSOT 反向相对 import ../game-runtime/src,违「契约即 SSOT」且拆仓即断(execution §12-A1)。
生命周期粒度 init(ctx, mainCtx) / update(dt) / render(g) / onInput(ev) / destroy() 五件 init/update/render/onInput 对齐引擎五回调(engGameInit/Update/Render,host.js:883-887);destroy 补卸载卫生(P2 装载多游戏时复位)。其余(resize/onVisibility)additive 后补。
绘制面约定 render(g) 的 g = 引擎 mainContext,boot 时注入游戏层 A0 已证 setGLEnable(false) 后引擎纯 2D 渲进 mainContext(host.js:904/919);游戏绘制面改它即与引擎粒子同屏(消除渲染面分裂)。不借 PluginContext。
能力获取 插件能力经下层已冻结 ctx.getEngine();游戏绝不 import littlejsengine Q4 铁律:引擎 import 只活 entry/host(entry.js:18-21/host.js:44)。

装载关系图(游戏 ↔ host ↔ 引擎):

flowchart TB
  subgraph host["集成段 host 层(唯一 import 引擎处·Q4 铁律)"]
    EI["LJS.engineInit 五回调<br/>(引擎内部 RAF 驱动)"]
    EC["engine-caps.js 模块<br/>(提升自 host.js:197 闭包·非从零)<br/>particles/audio.synth/math 三面薄包装"]
    BOOT["bootRef real 分支<br/>setGLEnable(false)+建 engineFactory<br/>+ 注入 mainContext 给游戏层"]
  end
  subgraph game["游戏侧(装载契约·零引擎 import·新立第9契约)"]
    GH["game.init(ctx, mainCtx)<br/>game.update(dt)<br/>game.render(g=mainContext)<br/>game.onInput(ev)<br/>game.destroy()"]
  end
  subgraph low["下层契约(已冻结·复用·零改)"]
    PC["PluginContext.getEngine()<br/>受控面 6 项 + 引擎能力面"]
  end
  EI -->|gameUpdate→| GH
  EI -->|gameRender→ 引擎 mainContext| GH
  BOOT -->|注入 mainCtx| GH
  EC -->|engineFactory| BOOT
  PC -. 插件能力 ctx.getEngine() .-> GH
  GH -. 禁 .->|import littlejsengine| X1["❌ Q4 违例"]

3. 同屏方案与最大风险

3.1 同屏方案(real 通道改造主干)

改造点 stub 通道(保旧,回滚锚) real 通道(新增,引擎掌帧)
掌帧源 浏览器 RAF + mockNowMs(墙钟近似),main.js:668-691 引擎 engineInit 五回调;RAF 旁置/门控;mockNowMs 改逻辑帧钟(每逻辑步 +FIXED_DT×1000)
逻辑段 frame(dtReal) 内 core.update/bundle.tick/applyEvents/fx.step 同段移入 game.update(dt),由 gameUpdate 回调驱动
绘制段 drawFrame(g)+palettePost.renderPost(g),g=#game 2D 同段移入 game.render(g),g=引擎 mainContext(boot 注入),由 gameRender 回调驱动
插件受控面 context2d host prepareCanvas(#game) 的 ctx 快照引擎 mainContext(createHostDevContext 的 context2d 改它)
粒子能力 engineFactory 未传 → getEngine()=null → 粒子 NOOP_HANDLE(无粒子) 经 engine-caps.js 建 engineFactory → 粒子真经引擎 ParticleEmitter 自渲落 mainContext

3.2 最大风险(先标在最前)

风险 等级 缓解 / 回滚
真渲同屏过不了 / 假阳性(粒子渲到另一张分离画布,游戏画布无粒子命中) P0 这是 P1 第一道真门,也是全弧第一道门。判据必须堵死假阳性:截取引擎接管后 DOM 可见那张 canvas(=mainContext 的 canvas,A0 已给它打 id #game-engine,host.js:830),断言同一张画布上既有游戏美术像素命中(店面暖色主色)又有引擎粒子像素命中(burst 白 maxCh>80)。复审改(M3):店面暖色阈值开工前已从 palette.js 坐实具体色域(r>140 && r−b>85 && g<r,排纸底/天光/近白),不留执行时占位——gate0/a6 的青蓝/亮度口径与暖色正相反,留占位会假阳/假阴(execution §8「门②(a)」)。若粒子渲到另一张画布 → 游戏画布粒子命中=0 → fail。回滚=?engine=stub。
掌帧冲突(main.js 自管 RAF + mockNowMs 与引擎掌帧直接冲突) P0 A0 双通道已证引擎掌帧可行;real 通道废 RAF、mockNowMs 改逻辑帧钟,对齐 host.js real 分支既定做法(host.js:526/884)。
real 通道就绪标记缺失(复审 M1)——driver 在引擎/game 就绪前驱动→打空/间歇绿 P0 real 下 __refBooted(index.html:81 bootRef().then 置)早于引擎就绪;补 real 专属 __refBootedReal(只在 engineInit().then 内 game.init 完成后置,仿 host-dev __hostBootedReal host.js:920),driver real 分支等它(execution 步骤 4.3/8a)。
driver 范式失效(复审 M2)——stepFrames 手摇在引擎掌帧下双掌帧违铁律、#game rect 取错画布 P0 real driver 范式切换(非增量):禁 stepFrames,改真触摸+轮询;rect 选择器切 #game-engine。与 stub「手摇定量推帧」分道(stub 兜逐像素,real 只证掌帧+渲染+真玩,harness §5-坑6)。execution 步骤 8。
渲染面分裂(复审 M4)——real 顶层同步建 bundle 会快照错 ctx(受控面 context2d≠游戏 render 面) P0 bundle 必须延后到 engineInit().then 内、以 context2d=mainContext 建(getContext2d 一次性快照闭包捕获值,host.js:323-324);输入桥+game.init+就绪标记同移进。execution 步骤 4.3(M4)。
engine-caps 模块化引入回归(探针/短路语义漂移) P1 提升须保 engineMode 短路 / __engineCalls 探针 / null-engine 回归语义逐行不变;门0 8/8 + real 门 6/6 作回归基线。recHook 设计成可注入(默认 no-op)——探针绝不入渠道产物(见 §6)。
receHook 探针泄进渠道产物 P1 物理保证:createEngineCaps({recHook}) 默认 recHook=no-op;ref-bundle 构建时不传 recHook(或传 no-op),仅王蓝莓真玩取证脚本注入 rec。渠道产物 esbuild 入口不引取证注入点 → 探针代码 tree-shake 不进包。
体积(ref 197KB→~540KB,引擎真入包) P2 ~540KB raw min(host-dev entry 实测 544KB 量级);gz 远在 B1 入场券 gz≤350KB 内(LittleJS 完整版 raw≈55KB gz)→ 不触红线。SIZES.md 增"游戏产物=引擎+包装层"口径(与插件增量分离)。勿引"13KB"(已废)。
受控面被动 P0 受控面 6 项契约零改是硬约束(engineFactory 是既有可选入参 api.d.ts:405);装载契约是 additive 新增,不改下层既冻 6 项。

4. 验收门概览(arc §5.1 六门 + 韧性)

门 判据 口径来源
① 掌帧 引擎掌帧帧增量区间 [20,45]/500ms gate0 G0-2(gate0-engine-takeover.cjs:75)
② 同屏像素 同一张画布(#game-engine)上:游戏美术暖色命中(warmHits>200,色域 r>140 && r−b>85 && g<r,开工前坐实非占位·M3)∧ 引擎粒子像素命中(burst 白 maxCh>80、brightHits>0);粒子渲到另一画布则游戏画布命中=0=fail a6 A6-5(maxCh/brightHits)+ 暖色域自 palette.js:23-41(execution §8「门②(a)」)
③ 真接线门② window.__engineCalls 含 particles.spawnEmitter call-ID(插件运行时真调引擎) a6 A6-2(a6-engine-wiring.cjs:79)
④ CDP 真玩 CDP 真触摸逐 zone 真玩(开店→进货→营业→结算→升级)+ 四件套 evidence;driver 范式=真触摸+轮询,禁 stepFrames 手摇·M2;rect 选择器 #game-engine、就绪等 __refBootedReal;zone 名以 main.js computeZones 为唯一真相 ref e2e 扩 real 通道(execution 步骤 8)+ harness §2-律4
⑤ stub 回归 ?engine=stub 逐像素确定性回归仍绿(双开哈希一致) ref e2e 门 B + harness §5-坑6
⑥ 提升回归 engine-caps 提升后门0 8/8 + real 门 6/6 逐项不变 gate0 + a6
⑦ console 干净 console 零未捕获错误 ref e2e(uncaught=[])

5. 回滚

  • 代码回滚:?engine=stub 通道与今天 main.js 行为逐行等价(RAF + #game 2D 画布 + mockNowMs 墙钟)。real 通道过不了 → 用户/取证一律走 stub,stub 既有交付(T1b-α 49 单测 + e2e 四件套)零连累。
  • 契约回滚:装载契约 game-host.d.ts 是 additive 新增工件,受控面 6 项零改 → 移除新增不破存量。
  • 模块回滚:engine-caps.js 提升若引入回归(门0/real 门红),可临时让 host.js 退回内联闭包(git revert 该文件 + host.js import 行),不连累 ref real 通道(ref 自带 engineFactory 经 createEngineCaps)。

6. 爆炸半径

触及 文件 性质
ref 主循环重做 games/wanglanmei-ref/{entry.js:15, src/main.js:112/583-602/617-691/648} real 通道改造(stub 保旧);含输入改挂 mainCanvas(M4)
装载契约新工件 game-runtime/src/core/game-host.d.ts(新增,复审改落点)+ contracts/README.md(仅指针登记) additive 第 9 类契约(game-runtime 内部)
engine-caps 模块化 host-dev/engine-caps.js(新增,搬 host.js:197-318)+ host-dev/host.js(改 import) 搬运已有件,语义零变
ref 装载适配 games/wanglanmei-ref/src/game-shop.js(新增·硬独立文件,A3):拆出符合装载契约的 game 工厂(默认导出、零引擎 import,禁 main.js 内联);entry import 引擎 real 通道接线
ref 就绪标记 games/wanglanmei-ref/index.html(real 通道接 __refBootedReal,M1) 就绪信号(stub 仍用 __refBooted)
SIZES 口径 SIZES.md 增"游戏产物(引擎+包装层)"新表 文档(与插件增量分离)
受控面 6 项契约 api.d.ts:269/281/286/303 零改(硬约束)
harness driver e2e-input.cdp.cjs real 通道范式切换(真触摸+轮询/选择器 #game-engine/等 __refBootedReal/同屏暖色判据);非「加断言」增量(M1/M2/M3) 取证范式重写

P1 不触及:后端发布主链 / game-studio / feed / 生成管线(那些是 P2/P3,归母 spec)。

双通道全景(real / stub)

graph TB
  URL{"?engine="}
  URL -->|real(新增·引擎掌帧)| R0
  URL -->|stub(保旧·回滚锚)| S0
  subgraph R["real 通道"]
    R0["entry import LJS<br/>引擎真入 ref-bundle ~540KB"] --> R1["setGLEnable(false)<br/>engineInit 五回调"]
    R1 --> R2["createEngineCaps 建 engineFactory<br/>注入 createHostDevContext"]
    R2 --> R3["game.update←gameUpdate<br/>game.render(mainContext)←gameRender<br/>mockNowMs=逻辑帧钟·废 RAF"]
    R3 --> R4["验收:门①掌帧/②同屏/③call-ID/④真玩/⑦console"]
  end
  subgraph S["stub 通道"]
    S0["不 import 引擎<br/>ref-bundle 197KB"] --> S1["prepareCanvas(#game) 2D<br/>浏览器 RAF + mockNowMs 墙钟"]
    S1 --> S2["frame(dtReal) 原样<br/>drawFrame(g=#game)"]
    S2 --> S3["验收:门⑤逐像素双开哈希一致"]
  end
  R4 -.过不了回滚.-> S3

7. P2 / P3 归并(O2 已拍)

  • P2(泛化宿主:装载任意 GamePackage)合并进母 spec 2026-06-13-T1b-β引擎真接线与Runner-v2-execution.md Phase D(O2 已拍)。P1 验实的装载契约 = P2 的产品化基底:母 spec Phase D 的"ref 经发布主链入 feed"升级为"engine-aware 包(按装载契约 + packageUrl 加载,game-package.schema.json:66)入 feed 跑引擎真玩"。P2 不在本 spec 展开。
  • P3(W-G1 生成游戏按同一装载契约产出) 归 W-G1 自身 spec:agent 写码于插件库、产装载契约 bundle、20 基准 bake-off。P1 的装载契约 IDL 是 P3 生成 agent 的唯一产出靶。

8. 待确认 / 风险与假设(诚实标注)

对抗复审结论(2026-06-13·deep·全程读真源码):无致命项。装载契约 IDL 五件(init/update/render/onInput/destroy)形态合理、与引擎五回调对齐准确(host.js:883-887);additive 关系成立(受控面 6 项零改 api.d.ts:269/281/286/303/405);双通道回滚有 A0 实证。4 重大项已全部纳入 execution 版钉死(real 就绪标记 M1 / driver 范式切换 M2 / 暖色阈值坐实 M3 / bundle 延后建 M4),均为「会致执行 agent 临场返工或门假绿/假红的硬装配问题」,不翻盘但必须在 execution 钉死。5 项不明假设逐条处置(落点改 game-runtime/src·缺省分叉显式·game 工厂硬独立·部署 git-clone-scratch·步骤0 降常规核对)。逐条裁决见 execution §12。 唯一 open item(非阻断):装载契约工件命名(见末条)。

缺省值分叉(复审 A2·显式声明此分叉有意为之):host-dev 缺省 engine=real(host.js:340),ref 缺省 engine=stub(main.js:82 play 模式走 stub 通道)。有意不统一:真玩取证显式带 &engine=real 更稳、stub 兜逐像素回归;但须知 P3 复制 ref 作模板时,缺省 stub=「新游戏默认不上引擎」是过渡现状,引擎是正路(boot 注入装载契约)——这点 P3 spec 须承接。

  • [verified] 装载契约全仓不存在(grep GameModule/装载契约/engineInit 在 contracts/+src/ 零命中);既有契约 = PluginContext(api.d.ts:269/281/286/303,6 受控面 + getEngine())+ packageUrl(game-package.schema.json:66)。
  • [verified] engine-caps.js 文件不存在,但内容已大半存在:makeEngineCaps 闭包(host.js:197-318)已包装 particles/audio.synth/math 三面并已注入 engineFactory(host.js:359);engine-math.js 已独立成模块(host.js:47 import)。→ 提升=搬运。
  • [verified] ref 游戏层 core.update(main.js:621)/drawFrame(g)(main.js:648,g=prepareCanvas(#game))/RAF(main.js:668-691)/mockNowMs 步进钟(main.js:618);createHostDevContext 仅 4 键无 engineFactory(main.js:112)→ 粒子今天全 NOOP_HANDLE。
  • [verified] real 通道引擎掌帧范式已在 host.js 跑通:engineInit 五回调(host.js:917)+ setupAfterEngine(LJS.mainContext)(host.js:919)+ 就绪标记 __hostBootedReal 在 then 内 game 就绪后置(host.js:920,注释明告 CDP 须等它再驱动 :851/931)+ bundle 在 then 内建(host.js:323-324/353-371/379-381)+ 输入挂 mainCanvas(host.js:536-538)+ setGLEnable(false)(host.js:904)+ 双时钟 window.__engine(host.js:840)+ mainCanvas.id='game-engine'(host.js:830)。这些 host-dev 已踩并解决的坑(M1/M4),ref 必须继承(execution §12)。
  • [verified] burst 粒子 colorStart 缺省白 {r:1,g:1,b:1,a:...}(toEngineSpec,particles-juice/impl.js:217),故引擎粒子像素门判 brightHits/maxCh(青蓝口径已对引擎渲染失效,harness §6-2);游戏美术暖色门另判暖色域(M3,自 palette.js:23-41)。
  • [verified·复审降级] ref 接引擎 import 能入包——host-dev/entry.js 末行已 import 引擎且 real 门 6/6 已过(同 monorepo node_modules);常规构建 grep ParticleEmitter 核对(A5),非阻断门。
  • [inferred] ref ~540KB 不触 B1 入场券——基于 host-dev entry 544KB 实测 + gz 量级;P1 用 size.mjs 实测游戏产物 gz 坐实。
  • [assumption] "好玩基线 v2 五要素"精确判据不在本 spec 范围(P3 验收门事);P1 只证装载契约可让真游戏真跑在引擎上同屏可玩。
  • [open item·留创始人·非阻断] 装载契约工件命名 game-host.d.ts(暂名,arc 称"游戏宿主装载契约");若创始人偏好其它名(如 game-loadable.d.ts),执行版改名即可,形态/落点(game-runtime/src/core)不变。改名零成本,不阻断执行。