Runner v2 全弧第一相(HJ-RUNNER-ARC-001 / P1):用王蓝莓把"上层游戏宿主装载契约"
从零 spike 到"引擎真掌帧 + 游戏美术与引擎粒子同屏可玩",解锁 P2 泛化 / P3 生成。
契约与共享件
- 新增装载契约 game-runtime/src/core/game-host.d.ts(第9类 additive·暂名):
GameHostBootContext / GameInstance / GameHostFactory;游戏=纯模块工厂,零引擎 import,
能力经下层 ctx.getEngine()(受控面 6 项零改),绘制面 render(g) 的 g=引擎 mainContext
- makeEngineCaps 闭包提升为可 import 模块 host-dev/engine-caps.js(createEngineCaps):
host-dev/ref/未来生成游戏共用;engineMode 短路 / __engineCalls 探针 / null-engine 语义逐行不变
王蓝莓 real 通道(games/wanglanmei-ref)
- 增 ?engine=real:主循环从浏览器 RAF 改挂 engineInit 五回调(掌帧唯一源=引擎);
游戏美术绘制面从 #game 改引擎 mainContext(同屏合并);bundle 延后到 setupReal 内以
mainContext 建(getContext2d 闭包快照修);保 ?engine=stub 逐像素确定性回滚
- game-shop.js 抽为独立 GameHostFactory(零引擎 import,P3 生成 agent 可复制范本)
- entry.js 动态 import('littlejsengine')=node-import-safe ∧ esbuild iife 入包两全
- 驱动 e2e-input.cdp.cjs real 改造:禁 stepFrames 手摇(防双掌帧),真触摸+轮询;
门②同屏像素判据(暖色 warmHits ∧ 引擎粒子 brightHits 同张 #game-engine)
真机验收(mini-desktop real 引擎,5 门)
引擎掌帧 dFrame=31∈[20,45] / 同屏 warmHits=682∧brightHits=13028 同张 #game-engine /
particles.spawnEmitter+synthSfx call-ID 运行时真调引擎 / CDP 517 真触摸跑通整局
(第1天接待18·营收¥79.9·连击9) / console 零错;本机 88 单测全绿(含修复 4 红+补回归)
证据:games/wanglanmei-ref/evidence/p1-real/(e2e-evidence.json pass=true + 5 截图肉眼复核)
诚实 caveat:门② brightHits 在结算屏混入亮 UI,同屏合并由 warmHits(美术落引擎画布)+
call-ID(粒子真调引擎)+ 引擎架构(粒子渲同一 mainContext)三证成立,非阻断。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
172 lines
21 KiB
Markdown
172 lines
21 KiB
Markdown
# 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`](./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 ↔ 引擎)**:
|
||
|
||
```mermaid
|
||
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)
|
||
|
||
```mermaid
|
||
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)不变**。改名零成本,不阻断执行。
|