- 受控面 v0 六项冻结:getContext2d/onFrame/getInput/getAudioContext/time/random - getInput=归一化事件订阅面(5枚举,host注入桥+_emit测试口,dispose自动清理防泄漏);getAudioContext=lazy单例无工厂返null告警降级 - per-plugin 派生随机流(FNV-1a子种子,序列互不影响同seed复现,向后兼容回退);useContext 守卫(友好错误带插件名) - 向后兼容增量不bump版本;manifest schema 未动;三 lane 按此冻结面开工 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
151 lines
15 KiB
Markdown
151 lines
15 KiB
Markdown
# Gate0 报告 —— T1b-α lane-core 插件协议地基(core-protocol-v0)
|
||
|
||
> owner:T1b-α lane-core(续作工位)|状态:Gate0 七件齐备,待主会话审 → 冻结为 `core-protocol-v0`。
|
||
> 配对 spec:`docs/agent-specs/2026-06-12-T1b-α插件库与参考件-execution.md`(v1.1 §2)。
|
||
> 纪律:ESM JS + JSDoc + 手写 d.ts,零 npm 依赖,本机只跑 `node`,不跑 git,不碰 game-runtime/ 外文件。
|
||
|
||
---
|
||
|
||
## 1. 七件清单状态(执行版 §2)
|
||
|
||
| # | 件 | 路径 | 状态 | 验证 |
|
||
|---|---|---|---|---|
|
||
| 1 | Plugin 接口 + 注册器 + 受控面 + d.ts | `src/core/plugin.js` + `src/core/api.d.ts` | 前任交付(收编) | core 17 测全绿 |
|
||
| 2 | manifest schema | `schema/plugin-manifest.schema.json` | 前任交付(收编) | 正反向校验通过(见 §4) |
|
||
| 3 | `_example` 空插件(五件全) | `src/plugins/_example/{impl.js,api.d.ts,manifest.json,PLUGIN.md,test/example.test.mjs}` | 本工位补齐 | _example 5 测全绿 + manifest 过 schema |
|
||
| 4 | PLUGIN.md 规格模板 | `docs/PLUGIN-TEMPLATE.md` | 本工位补齐 | `_example/PLUGIN.md` 即其活样板 |
|
||
| 5 | scripts(build/size/test + 校验) | `scripts/{build.mjs,size.mjs,test.sh,validate-manifest.mjs}` | 本工位补齐 | test.sh/validate 本机跑绿;size `--selftest` 绿;build/size 打包段仅集成段 |
|
||
| 6 | 浏览器证据 harness 骨架 | `test/harness/browser-evidence.cjs` | 本工位补齐(纯计算口径已实现 + CDP 占位) | 纯计算口径自检通过;CDP 真跑留集成段 |
|
||
| 7 | 目录与证据约定 README | `README.md` | 本工位补齐 | 含协议版本声明位 + 证据约定表 |
|
||
|
||
**附加件(超出七件、为守纪律而加)**:`scripts/validate-manifest.mjs`(零依赖 manifest 校验器,替代 ajv 守「本机零依赖」纪律)。
|
||
|
||
> 命名取舍:执行版 §2 第3条示例文件名给的是 `test/`,prompt 允许 `.cjs 或 .mjs`。本工位选 **`test/example.test.mjs`**(ESM),与 core 的 `plugin.test.mjs` 同形、与 `impl.js` 的 ESM 导出最自然对接。harness 按执行版明确要求用 **`.cjs`**。
|
||
|
||
---
|
||
|
||
## 2. 前任 4 件收编声明
|
||
|
||
本工位以前任落的 4 件为**既成权威**,逐件精读后**未改一字**,所有补件严格对齐其 API/风格/约定:
|
||
|
||
- `src/core/plugin.js`(注册器/生命周期,17 测全绿)—— 收编,作为协议唯一实现源。
|
||
- `src/core/api.d.ts` —— 收编,作为类型契约一等入口;`_example/api.d.ts` 用 `import type { Plugin, PluginContext } from '../../core/api.d.ts'` 复用其类型,不重复定义。
|
||
- `src/core/plugin.test.mjs` —— 收编,本工位重跑确认 17/17 绿(见 `test/RESULT.txt`)。
|
||
- `schema/plugin-manifest.schema.json` —— 收编,`_example/manifest.json` 与 `validate-manifest.mjs` 均以它为契约源。
|
||
|
||
**无冲突**:本工位补件与 4 件无任何 API/字段冲突。`_example/manifest.json` 字段(name/version/gzBudgetBytes/dependencies/exports{impl,types,named}/description)与 schema 实际字段逐项对齐 —— 即 prompt 所给「前任死前草稿」与 schema 校验对齐后的落地版(草稿值全部合法,已采纳)。
|
||
|
||
---
|
||
|
||
## 3. 三个关键协议取舍(从既有 api.d.ts / plugin.js 反读 + 本工位判断)
|
||
|
||
### 取舍一:受控面宁小勿大(YAGNI)—— PluginContext 只 4 项
|
||
**反读依据**:`api.d.ts` 的 `PluginContext` 仅声明 `getContext2d / onFrame / time / random` 四项,且注释钉死「插件能从引擎拿到的**全部**能力,仅此 4 项」「绝不暴露 littlejsengine 裸类型——保引擎可换」。
|
||
**取舍**:能力面做减法而非加法;插件要更多能力须走 Gate0 后扩展申请,不在地基预扩。
|
||
**代价/收益**:代价 = lane 可能短期觉得不够用(如缺直接的输入/音频句柄);收益 = 「引擎可换」(换引擎只换 host 实现,插件零改)+ 受控面是取证可复现的唯一闸口(time/random 全可 mock)。本工位 `_example` 严格只用这 4 项,作减法纪律的活证。
|
||
|
||
### 取舍二:错误隔离不连坐 —— 单插件崩不带崩全局
|
||
**反读依据**:`api.d.ts` 的 `InitResult.errors` 是数组(`Array<{name,error}>`)而非「首错即抛」;`initAll` 注释「重复调用幂等」「依赖声明校验」;`plugin.js` 的 `initAll` 对单插件 init 抛错 `try/catch` 记 `_initErrors` 后 `continue`,host-dev `tick` 对帧回调抛错亦逐个隔离。
|
||
**取舍**:init/dispose/帧回调三处都「单点故障隔离,记错误继续」,不回滚已 init 的插件(回滚语义复杂,留 v2 边界)。
|
||
**代价/收益**:代价 = 部分 init 失败时系统处于「部分可用」态(须靠 `InitResult.ok=false` + `errors` 显式感知,不能假设全成功);收益 = 取证/运行不被单插件崩溃带垮,符合「可调试、可审计」红线。**lane 须注意**:插件不能假设「同批其它插件都 init 成功了」,跨插件依赖要靠 `dependencies` 声明 + 运行时防御。
|
||
|
||
### 取舍三:生命周期严格有序 + dispose 幂等,但依赖不做拓扑重排
|
||
**反读依据**:`api.d.ts` 注释「init 正序/dispose 逆序」「dispose 逆序、幂等」「本波不做拓扑重排(注册顺序即 init 顺序)」;`plugin.js` 的 `disposeAll` 用 `_disposed` 标记保幂等、逆序遍历 `_initOrder`,`_missingDeps` 只校验「声明依赖是否已注册」不重排。
|
||
**取舍**:注册顺序 = init 顺序(正序)、dispose 逆序(后进先出,符合资源释放直觉);`dependencies` 仅作「缺失即隔离」的声明式校验,**不**自动拓扑排序。
|
||
**代价/收益**:代价 = 有依赖关系的插件**必须由 host 按正确顺序 register**(先注册被依赖者),地基不替你排;收益 = 地基最小(Gate0 骨架不背拓扑排序复杂度),幂等 dispose 防重复释放。**lane 须注意**:若你的插件 A 依赖 B,务必保证 register(B) 在 register(A) 之前,否则 A 会因依赖缺失被隔离。
|
||
|
||
---
|
||
|
||
## 4. schema 校验证据
|
||
|
||
```
|
||
正向:node scripts/validate-manifest.mjs
|
||
OK src/plugins/_example/manifest.json [example@1.0.0, gzBudget=2048]
|
||
共 1 件,通过 1,失败 0
|
||
|
||
反向自检(故意构造非法 manifest,校验器须拒绝,防空过假阳性):
|
||
逮住 6 处违规:未声明字段 evil / name 非 kebab-case / version 非 semver /
|
||
gzBudgetBytes 超 65536 / exports 缺 types / exports.impl 非 .js -> 退出码 1(正确拦截)
|
||
```
|
||
|
||
> 校验器为零依赖手写(守「本机零 npm 依赖」纪律),覆盖本 schema 实际用到的全部约束(required / additionalProperties:false / type / pattern / min-max / uniqueItems)。集成段如需 draft-07 全量校验可另接 ajv(属工具链,集成段跑)。
|
||
|
||
---
|
||
|
||
## 5. 其余 lane 开工前,主会话该审的 3 个重点
|
||
|
||
### 重点一:受控面够不够(YAGNI 边界是否需在 Gate0 松动)
|
||
seed 子集 lane(lane-math/vfx/sys)的真实需求是否能被 **4 项受控面**满足?已知潜在缺口:
|
||
- **输入**:gamefeel(P4) 的「输入缓冲/coyote」需要输入事件源 —— 当前受控面无 input 项。是经 `onFrame` 内自取输入(host 注入输入快照到哪?),还是必须扩 `PluginContext.input`?**这是最可能逼迫 Gate0 受控面扩项的点,须先裁。**
|
||
- **音频**:audio-music(P6) 的发声需要 AudioContext 句柄 —— 当前受控面无。spec 说「P6 node 层只测序列器数据流,发声=集成段」,故 Gate0 可不扩,但须确认「发声句柄走哪条受控面」的方向,免得 lane-sys 卡壳。
|
||
**建议**:开 lane 前,主会话先就「input 受控面是否纳入 v0」拍板(要么扩为 5 项并补 core 测试与 d.ts,要么明确 lane 走何种 workaround 并记边界)。
|
||
|
||
### 重点二:context 注入模型(单 context 共享 vs 每插件独享)
|
||
当前 `plugin.js` 的 `initAll` 把**同一份 `this._context`**(host 经 `useContext` 注入)传给所有插件 init。隐含约定:**全插件共享一个受控上下文**(共享 time/random/canvas)。须审:
|
||
- 共享 `random` 意味着插件 A 取随机会推进序列、影响插件 B 的随机 —— 对「每插件确定性」是否构成问题?(取证复现时要不要每插件独立子随机源?)
|
||
- `useContext` 必须在 `initAll` 前调用,否则 `this._context` 为 undefined -> 插件 init 时 `ctx` 为 undefined 崩。**core 当前未对「忘记 useContext」给出友好报错**(会以 TypeError 形式在插件内崩)。建议主会话评估是否要在 `initAll` 入口加「未注入 context 即显式报错」的守卫(属 core 微调,需补测试,归 lane-core)。
|
||
|
||
### 重点三:vfx/audio 视觉/发声证据的集成段闭环(禁 node-canvas 的落地)
|
||
spec §2/§5 钉死:vfx 渲染哈希、audio 发声证据**必须**集成段浏览器出,**禁 node-canvas 兜底**。`browser-evidence.cjs` 已把**纯计算口径**(FNV-1a 哈希/非空/直方图/几何)在 Gate0 实现并冻结,但 **CDP 连真 Chrome 的 4 个占位函数**(connectCdp/driveFrames/captureImageData/saveScreenshot)尚未实现。须审:
|
||
- 这 4 个占位的实现归属(lane-vfx 自己接,还是 lane-core 在集成段统一接?)—— 建议**集成段由主会话统一接**,避免各 lane 重复造 CDP 轮子、口径漂移。
|
||
- 「mock 时钟接管 onFrame 驱动」在集成段如何与 LittleJS 主循环对接(当前 host-dev 用 `tick`,集成段需把 tick 桥到引擎更新且时钟可注入)—— 这是 vfx 确定性证据的前提,须在 lane-vfx 开工前定方案。
|
||
|
||
---
|
||
|
||
## 6. 本工位自验汇总
|
||
|
||
| 验证项 | 命令 | 结果 |
|
||
|---|---|---|
|
||
| core 17 测 | `node --test src/core/plugin.test.mjs` | 17/17 |
|
||
| _example 5 测 | `node --test src/plugins/_example/test/example.test.mjs` | 5/5 |
|
||
| 全量遍历 | `bash scripts/test.sh` | 22/22(原始输出见 `test/RESULT.txt`) |
|
||
| manifest 正向校验 | `node scripts/validate-manifest.mjs` | 通过 |
|
||
| manifest 反向自检 | 非法样例 | 逮 6 违规、退出码 1 |
|
||
| size gz 口径 | `node scripts/size.mjs --selftest` | 通过 |
|
||
| harness 纯计算口径 | 临时自检(哈希/非空/几何/占位/路径) | 通过 |
|
||
|
||
**未在本机跑(按纪律留集成段)**:esbuild 打包(build.mjs/size.mjs 打包段)、CDP 浏览器真跑(harness 4 占位)—— 均仅 mini-desktop 跑,本机无 esbuild、禁 headless Chrome。
|
||
|
||
---
|
||
|
||
## 7. Gate0.1 受控面补丁(主会话 Gate0 终审三裁定落地,2026-06-12)
|
||
|
||
> 主会话 Gate0 终审在 §5 三重点基础上拍板三项裁定,本节记「改了什么 + 三裁定如何落的 + 自验」。
|
||
> 定性:**向后兼容增量**(既有 22 测零改判全绿),随 `core-protocol-v0` 一并冻结,**不 bump 版本号**。
|
||
|
||
### 7.1 改动文件清单
|
||
|
||
| 文件 | 改动 |
|
||
|---|---|
|
||
| `src/core/plugin.js` | 受控面扩 `getInput`/`getAudioContext`;新增 `HostDevInputBridge`(输入桥,带 `_emit`/`disposeScope`);`createHostDevContext` 增 `audioFactory` 选项 + `inputBridge`/`deriveContextFor` 出口 + 非枚举派生钩子;`PluginRegistry` 增 per-plugin 上下文派生(`_contextFor`)、`disposeAll` 协作回收输入订阅、`useContext`/`initAll` 友好守卫;新增 `deriveSeed`(FNV-1a 子种子派生);导出 `HostDevInputBridge`/`deriveSeed`/`INPUT_EVENT_TYPE_LIST` |
|
||
| `src/core/api.d.ts` | 新增 `InputEventType`/`InputEvent`/`InputSubscription`/`InputSource` 类型;`PluginContext` 增两方法 + per-plugin random 说明;`HostDevContextOptions` 增 `audioFactory`;`HostDevContextBundle` 增 `inputBridge`/`deriveContextFor`;新增 `HostDevInputBridge`/`deriveSeed`/`INPUT_EVENT_TYPE_LIST` 声明 |
|
||
| `src/core/plugin.test.mjs` | 新增 §7(受控面两项:输入订阅/退订/归一化/时间戳/dispose 自动清理/非法类型/handler 隔离/音频降级+lazy 单例)、§8(per-plugin 派生随机:独立性/复现/deriveSeed 确定性)、§9(useContext 三守卫)——计 14 新测 |
|
||
| `src/plugins/_example/{impl.js,api.d.ts,PLUGIN.md,test/example.test.mjs}` | 样板演示新两面:init 订阅 `pointerdown`(句柄留 dispose 注销)+ lazy 取音频(容忍 null);probe 增 `inputEvents`/`hasAudio`;新增 2 例测(输入驱动/音频降级与注入);既有 5 例测注入 mock `audioFactory` 保 RESULT.txt 干净 |
|
||
| `README.md` | 协议版本声明位填「v0(2026-06-12 冻结,含 Gate0.1 受控面补丁)」+ 受控面 6 项冻结口径;测数 17→31 / 5→7 |
|
||
|
||
### 7.2 三裁定如何落的
|
||
|
||
**裁定一·受控面 v0 扩两项(具名消费者 P4/P10/P6)**
|
||
- `getInput()` → 返回 `InputSource`(`on/off`,5 枚举 `pointerdown/pointermove/pointerup/keydown/keyup`);事件对象 = 归一化 `{type,x,y,key,tMs}`,**时间戳取自受控时间源**(取证可复现,禁原生 `event.timeStamp`)。host 注入事件桥 `HostDevInputBridge`,测试口 `_emit(type,payload)`。**dispose 自动清理**:注册器为每插件派生上下文时给该插件 `scope`,`disposeAll` 中**无条件**对每插件调 `inputBridge.disposeScope(name)` 回收其订阅(对「无 dispose 方法但订阅过输入」的插件同样生效,且插件 dispose 抛错也仍回收,防泄漏)。
|
||
- `getAudioContext()` → lazy:host 注入工厂,首调即创建、后续返回同一实例(全 host 单例);**无工厂返回 null 并 `console.warn` 仅一次(绝不抛错)**——P6 容错降级。工厂抛错亦降级为 null(不连坐插件)。
|
||
|
||
**裁定二·per-plugin 派生随机流**
|
||
- `context` 改为每插件实例化:共享 canvas/帧/时间源/事件桥/音频,**random 独立**——`deriveSeed(mainSeed, pluginName)` 用 **FNV-1a 32-bit**(与全发行版证据哈希族同口径)派生子种子,注册器 `initAll` 时为每插件 `deriveContextFor(name)`。根治 §5 重点二的「共享 random 互推」隐患。
|
||
- 实现接缝:host-dev `context` 上挂**非枚举** Symbol 钩子指回 bundle,注册器据此把 plain `context` 升级为每插件派生上下文;非枚举 → 不污染受控面、不影响既有 `deepEqual`/形状断言。**兼容回退**:手搓无钩子的 plain context 仍可用(退化为共享上下文,不参与 per-plugin 随机隔离)——既有「直接 `plugin.init(context)`」用法零改。
|
||
|
||
**裁定三·useContext 守卫**
|
||
- 忘调 `useContext` 即 `initAll` → 抛含修复提示的友好错误(「未注入受控上下文:请在 initAll() 之前调用 useContext(context)」+ host-dev 用法示例),而非插件内裸 `TypeError`。
|
||
- `useContext` 传 null/非对象 → 友好抛错;`initAll` 之后再 `useContext` → 抛错并点出当前阶段。
|
||
|
||
### 7.3 自验汇总(Gate0.1)
|
||
|
||
| 验证项 | 命令 | 结果 |
|
||
|---|---|---|
|
||
| core 31 测(17 旧 + 14 新) | `node --test src/core/plugin.test.mjs` | 31/31 |
|
||
| _example 7 测(5 旧 + 2 新) | `node --test src/plugins/_example/test/example.test.mjs` | 7/7 |
|
||
| 全量遍历 | `bash scripts/test.sh` | **38/38**(原始输出见 `test/RESULT.txt`) |
|
||
| 既有 22 测向后兼容 | 同上(未改一条旧断言) | 全绿 |
|
||
| manifest 正/反向校验 | `node scripts/validate-manifest.mjs`(manifest 未改) | 通过 / 逮 6 违规 |
|
||
|
||
**受控面 v0 最终成员(冻结口径,6 项)**:`getContext2d` / `onFrame` / `getInput` / `getAudioContext` / `time` / `random`。
|