docs(amodel): 3 层架构边界设计(评审版)+ §6.8 双审折叠 + D9=全注入

创始人提 3 层模型(L1 工程骨架/L2 标准能力库/L3 游戏代码),目标=降低 LLM 必写量、只写游戏逻辑、根除 host-config wiring 错类(reading 'w')。

设计:3 opus fork 顺序设计 L1→L2→L3 边界(每层 边界=不做什么/标准/在哪里),综合成评审版。
双审(§6.8):Opus 对抗式 fork 全程(3 major)+ codex:codex-rescue(读全 api.d.ts+文档,捞 burst 幻觉;末步 stalled)。已折叠(§0):
- M3 强制三分→必写1(game-logic),core/render 推荐拆/可内联(真达成'省 LLM 活');
- M1 全11 registerOrder 是新组合两参考序冲突→实现期显式定义+L1控制门前置;
- Codex 捞 juice.burst 幻觉(我文档自犯)→手感范式以 api.d.ts 为准、禁臆造;
- minors 入 §9.1。
创始人裁:D9=全注入+实测(A)。

转实现:M1 控制门(全11插件一起 boot 验证)→ game.js 拆 wrapper+game-logic → host-config 注入全11 → harness/prompt/skill 对齐 → standalone+e2e 验证。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
lili 2026-06-22 10:51:52 -07:00
parent d56ed9962e
commit 2c220c1302

View File

@ -0,0 +1,216 @@
---
title: A-model 生成 · 3 层架构边界设计(评审版)
date: 2026-06-22
status: approved(2026-06-22 双审折叠 + 创始人定 D9=A 全注入+实测)→ 转实现
topic: amodel-3layer-boundaries
canonical: true
origin: 会话钻 reading 'w' 根因 → 结构性修复 host-config 固定 plumbing → 创始人提出 3 层模型
authors: 创始人(3 层模型)+ Opus 顺序子代理(L1/L2/L3 边界设计,各 ~60 万 token)
reviewed-by: Opus 对抗式 fork(全程,3 major + minors)+ codex:codex-rescue(读全部 api.d.ts + 文档,捞 burst 幻觉等;末步 stalled)—— §6.8 双审已折叠(见 §0)
---
# A-model 生成 · 3 层架构边界设计(评审版)
> **结论先行**:把 A-model 生成的游戏代码分 **3 层**,每层边界由"**换个游戏会不会变**"一刀切清:
> **L1 工程骨架**(逐字不变的可运行外壳 + wiring,LLM 不碰)/ **L2 标准能力库**(跨游戏复用的零语义能力插件 + skill,LLM 用不写)/ **L3 游戏代码**(这一款的身份:玩法/画面/数值/资产 + 接线,LLM 只写这层)。
> **核心目标 = 降低 LLM 的活、只写必须代码、用法更简洁清晰**。落地后 LLM 从"写 5 文件含易错 wiring"收窄到"改 3 文件(game-logic/core/render)+ 按 canonical 键调插件"。
> 本设计**根除**本会话钻出的 `reading 'w'` 错类根因(M3 反复搞坏 host-config 插件 wiring),并把已做的结构性修复(host-config 固定)推广为完整 3 层契约。
---
## 0. 双审折叠(2026-06-22 · Opus 对抗式全程 + Codex 实质发现)
> §6.8 双审:Opus 对抗式 fork 全程完成;Codex(codex:codex-rescue)读完全部插件 api.d.ts + 文档、captured 关键发现后在"出分级报告"临界点 stalled(已捞实质发现,缺最后汇总)。两侧发现合并如下,**收口前必改的已就地折叠**,1 项需创始人定。
**裁决:方向成立,边界一刀切判据干净;需处理 3 项(M2 待创始人定,M1/M3 已折叠),minor 转实现期 TODO。**
| 发现 | 来源 | 处置 |
|---|---|---|
| **M2(待创始人定)· 全注入 bundle 撞 feed 首屏 P75<3s** | Opus | 全 11 插件静态引用不被 tree-shake、每游戏 ~250-350KB;game-feed 即点即玩、首屏 P75<3s 是 MVP 硬指标(AGENTS §2)。**升级为关键决策 D9**(见 §6)——全注入 vs 按 design 声明子集注入,**需创始人定** |
| M3 · 强制三分反"省 LLM 活" | Opus | **已折叠**:§2/§5 改为**必写 1(game-logic)+ 推荐拆 core/render**;极小游戏可把逻辑/画面内联进 game-logic |
| M1 · 全 11 registerOrder 是新组合、两参考序冲突 | Opus | **已折叠**:§4/附录 A 改"以现有为准"→**合集需显式定义 registerOrder + L1 控制门(全 host-config→build→smoke)作前置 gate** |
| **附录 B `juice.burst()`/`gamefeel.shake()` 是幻觉 API** | Codex | **已折叠**:附录 B 改为"调 juice/gamefeel 事件 API,**签名一律以 api.d.ts 为准**"(不写死方法名);并入 §6 D6 —— 手感「事件→特效」范式表必须由 api.d.ts 生成、禁臆造 |
| D1/D3 落地链(host-config 仍 import './game.js'、check 仍守 game.js) | Codex | 实现期改点(设计描述目标、代码待改,符合预期);记入 §7 影响面 |
| minors(wrapper 撞名/viewport 改 wrapper/juice seed 接线/game-logic 导出名新失败模式/spike 信封保留/render↔hud-ui 软约定) | Opus+Codex | 转实现期 TODO,见 §9 |
---
## 1. 背景与目标
### 1.1 来由
本会话钻 `reading 'w'` 到真根:**M3 游戏逻辑写得了(check+build 过),但反复搞坏 host-config 的插件 wiring** —— viewport 漏键(`reading 'w'`)、registerOrder 传非插件、game.js 取插件漏 `.plugins` 一层(`reading 'define'`),修一个露一个(whack-a-mole)。已做结构性修复(host-config 固定 plumbing,commit `d56ed996`)经 standalone whack 验证绿(wfix4:13 步/smoke PASS)。创始人据此提出 **3 层模型**,要求把边界设计清楚再继续。
### 1.2 目标
- **降低 LLM 必写量**:只写"换游戏会变"的最小集,杜绝写 boilerplate / wiring。
- **用法简洁清晰**:稳定扁平的插件契约 + canonical 键 + 一次写对的 skill 范式。
- **结构性根除 wiring 错类**:错不可能发生(物理锁死)> 靠 footgun 提醒。
### 1.3 非目标
- 不改 SAA 图拓扑 / 集成方式 C(generate shell-out harness)—— 那已收口(批 B)。
- 不在本设计内做"生成质量调参/M3 model 选型"(独立线)。
- 不追求 L3 能用尽 11 插件的全部高级玩法(MVP 以轻量小游戏为靶)。
---
## 2. 3 层模型总览
| 层 | 是什么 | LLM | 一刀切判据 | 物理位置 |
|---|---|---|---|---|
| **L1 工程骨架** | 逐字不变的可运行外壳 + 装配 wiring(sample 起点) | **不碰**(write 拒) | "这段能逐字复制到下个游戏吗?能 → L1" | per-game:index.html/entry-bundle.js/src/host-config.js/src/game.js(wrapper);shared:src/host/、src/core/ 契约 |
| **L2 标准能力库** | 跨游戏复用的**零玩法语义**能力(编排/计分/UI 原语/定时/存档/手感/特效/震动/音频/物理碰撞) | **用,不写**(只读) | "换个游戏这能力还一样吗 + 命名零玩法名词?是 → L2" | src/plugins/<name>/{impl.js,api.d.ts}(11 件);skill littlejs-game-dev.md / sim-business-game-design.md |
| **L3 游戏代码** | 这一款的身份:玩法/画面/数值/资产 + 接 5 法调 L2 | **只写这层** | "换个游戏会变吗?会 → L3" | src/{game-logic,core,render,balance,assets}.js(**必写 1 = game-logic**;core/render 推荐拆、极小游戏可内联;balance/assets 可空) |
```mermaid
flowchart TD
subgraph L1[L1 工程骨架 · LLM 不碰]
HC[host-config.js<br/>实例化11插件+注册+viewport390x844+wiring]
GW[game.js wrapper<br/>吃掉 opts.runtime 嵌套]
IDX[index.html / entry-bundle.js<br/>__GameBundle.bootGameHost / __genBooted]
end
subgraph L2[L2 标准能力库 · LLM 用不写]
P[11 插件 createXxxPlugin opts<br/>零语义 + 默认开箱即用 + api.d.ts 真相]
SK[skill:code 层 API 速查 + design 层经营范式]
end
subgraph L3[L3 游戏代码 · LLM 只写]
GL[game-logic.js 接线<br/>createGame plugins,bundle,viewport]
CO[core.js 纯逻辑 node 可测]
RE[render.js 画面 + 命中区]
end
HC -->|实例化+注入| P
HC -->|扁平 plugins,bundle,viewport| GW
GW -->|干净入参| GL
P -.被调用.-> GL
SK -.read_file 速查.-> GL
GL --> CO
GL --> RE
```
---
## 3. L1 工程骨架 · 边界
- **定位**:把"一份 game-logic + 一套标准插件"变成"浏览器/CDP 能 boot 起来的游戏"。游戏从它(sample)开始,只在它留出的**唯一插槽**填,不碰骨架本身。
- **边界(不做什么)**:① 任何玩法逻辑(→L3)② 任何能力的**实现**(→L2;L1 只 `createXxxPlugin()` 装一下)③ **选用/调用**某能力(→L3;L1 只"全摆上桌")④ 画面/资产/数值(→L3)⑤ 视口/插件集随游戏变(L1 全游戏同一份)。**判据:L1 文件须能逐字复制到下个游戏而不改。**
- **标准(契约/不变量)**:
- 向上:`entry-bundle.js` → esbuild globalName `__GameBundle` 导出 `bootGameHost`;`index.html` 置 `__genBooted/__genBootError/__genHost/__gameState`;viewport **硬编码 390×844**;registerOrder canonical 序固定。
- 向下(唯一插槽,治 reading 'w'/'define'):`game-logic.js: createGame({plugins, bundle, viewport}) → GameInstance(init/update/render/destroy[/_forensicsView])`;`plugins` **扁平**(全 canonical 键已实例化+init);`ctx` 经 `init(boot).boot.ctx`(不经工厂入参);`game.js` wrapper 吃掉 `opts.runtime.plugins/bundle` 两层解构 → **L3 永不见 opts.runtime**。
- 命名不变量:L3 入口固定命名导出 `createGame`;插件 canonical 键由 L1 拥有、L3 消费。
- **在哪里 + 锁定**:per-game 5 文件 scaffold 逐字克隆;**writeFileTool 写入即拒**(现仅拒 host-config → 扩到 game.js/index.html/entry-bundle.js);check 验 L1 文件 == 模板 + game-logic.js 真改写过。
- **L1↔L2 接缝**:L1 拥有实例化/注册/wiring;L2 拥有插件实现 + `createXxxPlugin(opts)` 签名 + 插件对象形状。给 L2 硬约束:11 件全可注入 + **construct 期 node-import-safe** + 默认 config 开箱即用。
- **L1↔L3 接缝**:下行只传 `{plugins,bundle,viewport}`(工厂期)+ `boot.ctx`(init 期);上行只收 GameInstance。
## 4. L2 标准能力库 · 边界
- **定位**:把"换游戏也一样"的通用机制封装成 host 可注入插件 + 给 LLM 的 API 速查/范式 skill。**只给能力 + 清晰 API**,不装配(L1)、不决定用哪个(L3)。
- **边界(不做什么)**:① boot/wiring/实例化/注册(→L1)② 选用/何时调/编排成玩法(→L3)③ **任何玩法语义**(score 不叫 coin、collision 不知双方是"子弹/敌人")④ 画具体画面/声明具体资产/定具体数值(→L3)⑤ 持进程级可变状态。**判据:每件能力须能被任意游戏复用且无需知道"这是什么游戏";出现玩法名词/特定数值 = 越界。**
- **标准(契约)**:① `createXxxPlugin(opts?) → XxxPlugin`,opts 全可选 + 开箱即用默认 ② 插件形状 `interface XxxPlugin extends Plugin {id, init?(ctx), onFrame?(dt), ...公开API}` ③ **api.d.ts = 唯一签名真相**(方法名以它为准,治 `reading 'define'` 幻觉)④ 命名 engine 级零语义 ⑤ node-import-safe(碰受控资源只在 init/运行期经 ctx;audio 经 `ctx.getAudioContext()` 绝不 `new AudioContext`)⑥ 受控面纪律(时间/随机/输入/audio/engine 全经 boot.ctx)。
- **回填 L1 待决点**:11 件 canonical 键 + 默认 config(见附录 A);**audio = plugins.audioMusic 内部接**(L3 只 `audioMusic.play(...)`,永不取 ctx;无声卡 no-op 不崩);**registerOrder(M1 折叠)= 全 11 件是从未一起跑过的新组合,两参考序(template 5 件 save 在前 / generic 7 件 save 第 6)冲突,故须实现期显式定义合集偏序(参考 generic 六锚 + 处理依赖,不是"照搬既有"),并以 §8 L1 控制门〔全 host-config→build→smoke〕作前置 gate 验证 11 件一起能 boot**。
- **在哪里**:src/plugins/<name>/{impl.js,api.d.ts}(11 件,shared 只读);skill littlejs-game-dev.md §3(code 层 API 速查 + ❌→✅ 幻觉表)+ sim-business-game-design.md(design 层经营范式)。消费路径:skill 速查发现 → canonical 键解构 → api.d.ts 确认。
- **经营 UI 模板归属(创始人问点)**:**= L2 `hud-ui` 零语义原语 + L2 `sim-business` skill 配方 + L3 组合,不新建"经营 UI 插件"**。理由:商店/升级/离线收益带玩法语义,做成插件会把语义焊进 L2、违反零语义铁律。例外:真零语义高频件(滚动列表/数字滚动/进度环)可进 hud-ui(仍 L2)。
## 5. L3 游戏代码 · 边界
- **定位**:这一款游戏的身份代码(玩法/画面/数值/资产 + 接 5 法调 L2)。只写"换游戏会变"的,是 L1 唯一插槽里填的内容。
- **边界(不做什么)**:① boot/wiring/实例化/viewport 硬编码(→L1)② 重实现已有能力(→L2;**自研红线:11 件里找不到才许自研,且须反馈"该沉淀成 L2 插件"**)③ 裸用宿主禁区(Math.random/Date.now/setTimeout/new AudioContext/addEventListener/裸 fetch 资产 → 经 ctx/timerScheduler/audioMusic/assets)④ 引擎 import(经 boot.ctx.getEngine())⑤ 碰 opts.runtime(L1 wrapper 已吃掉)⑥ 模块级跨实例可变状态。**判据:换个游戏会变 = L3;不变 = 不是 L3。**
- **标准(契约 + 用法范式)**:
- 入口:`createGame({plugins,bundle,viewport}) → GameInstance`;5 法标准骨架(见附录 B)。
- 文件内分工(**M3 折叠:只 game-logic 必写,余皆可拆可内联**):**game-logic.js(必,唯一接线处;极小游戏可把逻辑+画面直接写在内)** / core.js(推荐,纯逻辑,判据=能 `node --test` 直接跑;复杂游戏才拆) / render.js(推荐,画面+命中区,零逻辑推进;小游戏可内联进 game-logic) / balance.js(可选,数值,可空)/ assets.js(可选,资产键名,可空返空集)。
- 调 L2 按时相:解构期(用到才解构 canonical 键)→ init 期(ctx=boot.ctx、loadAssets、ctx.getInput().on、sceneFsm.define/start)→ update 期(**bundle.tick(dt)** + 推进)→ render 期(sceneFsm.render、hudUi 画、g 画)。
- **回应待决点**:① **手感二分**(服务"省 LLM 活"):**全局氛围型自动**(palettePost vignette 默认开、gamefeel 输入缓冲 onFrame 自动 → L3 零调用);**事件绑定型 L3 一行显式**(在命中/死亡/过关事件点调 juice/gamefeel 的事件 API —— **具体方法名一律以 api.d.ts 为准,禁臆造**〔Codex 实测:`juice.burst` 是幻觉,真入口为 spawnEmitter 类〕;L2 零语义无法自知何时炸,故须 L3 在事件点调)。skill 的「事件→特效」一行范式表**必须由 api.d.ts 生成**。② **viewport 下传采纳**:L3 读 `viewport.w/h`,不硬编码 390×844。
- **在哪里(必/可)**:writeFileTool 允许写 `src/{game-logic,core,render,balance,assets}.js`;**必写 1** = game-logic;推荐拆 core/render(可内联);可空 balance/assets。其余(game.js/host-config/index.html/entry-bundle.js)拒写。
- **最小 L3 形态**:改 3 文件 + 调 4 编排插件(sceneFsm/sessionScore/hudUi/timerScheduler)即够跑够玩;juice/audio/physics 按玩法叠加。
---
## 6. 关键决策(3 层设计已收敛 · 待双审确认)
| # | 决策 | 依据 |
|---|---|---|
| D1 | **game.js 拆分**:game.js(L1 固定 wrapper,吃 opts.runtime 嵌套)+ game-logic.js(L3,收扁平 `{plugins,bundle,viewport}`) | 根除 `reading 'define'` 嵌套坑;创始人选"拆文件" |
| D2 | **全 11 插件注入**固定 host-config(全 node-safe + 默认开箱即用) | 创始人选"注入全部";design 富化要 juice/音效不再错配 |
| D3 | **viewport 下传** `createGame({...,viewport})` | 单一来源,L3 不硬编码 390×844 |
| D4 | **audio 内部接**(audioMusic 经 ctx.getAudioContext;L3 只 .play) | 不破坏 node-import-safe;L3 少操心 |
| D5 | **经营 UI = hud-ui 原语 + sim-business skill 配方 + L3 组合**(不新建经营插件) | 保 L2 零语义铁律 |
| D6 | **手感二分**:全局氛围自动 / 事件绑定 L3 一行 | 既省 LLM 活又有手感 |
| D7 | **check"真改写"守卫迁 game.js → game-logic.js** | game.js 固定后旧守卫恒过=假绿复活 |
| D8 | **生产只认 SAA 信封**(spike main.js/__gameBooted 降本地调试;_template 仍保留 spike 信封供本地 play.mjs play,SAA scaffold 不带) | 避免双 plumbing 漂移 |
| **D9(⚠️待创始人定)** | **全 11 注入 vs 按 design 声明子集注入** —— 双审 M2:全注入每游戏 bundle ~250-350KB、不被 tree-shake,撞 game-feed 首屏 P75<3s 硬指标 | 见 §6.1 |
### 6.1 D9 决策(双审 M2 升级 · 需创始人定)
全 11 插件注入(D2)让"design 富化总能满足 + LLM 不被缺插件卡"(省 LLM 活),但代价 = 每个游戏 bundle 静态背 11 插件(~250-350KB)、game-feed 几十上百游戏首屏受冲击(P75<3s 是 MVP 硬指标 AGENTS §2)。三个选项:
- **(A) 全注入 + 实测**:先全注入跑通,实测单游戏 bundle + feed 首屏;超标再优化(HTTP 缓存/共享 chunk 摊薄)。最省 LLM 活,赌体积可接受/可缓解。
- **(B) 子集注入**:L1 按 SAA design 节点声明的插件子集注入(design 已知道这游戏要哪些)→ bundle 只背用到的。省体积,但破坏"全摆上桌"的简洁(L1 要读 design 输出、registerOrder 动态)。
- **(C) 分档**:轻量靶默认注入精选集(编排 5 + gamefeel + juice + audioMusic = 8),physics/collision 重件按 design 声明追加。折中。
**推荐 (A) 先全注入+实测**(MVP 阶段先证能产出可玩游戏,体积优化是已知可缓解的后续);但若你更看重 feed 首屏,选 (C)。
> **✅ 创始人裁定(2026-06-22)= (A) 全注入 + 实测。** 实现期全 11 注入,跑通后实测单游戏 bundle + feed 首屏 P75;超标再优化(缓存/共享 chunk/转子集)。体积优化列为已知后续 TODO,不阻塞本期"证能稳定产出可玩游戏"。
---
## 7. 影响面(实现将改什么)
- **L1**:`_template/src/game.js` → 薄 wrapper;新增 `_template/src/game-logic.js`(L3 起点);`host-config.js` 注入全 11 插件 + 传 viewport;`buildFactoryOpts` 加 viewport。
- **harness**:`tools.mjs` writeFileTool 拒写扩到全 L1 文件;check "真改写"守卫认 game-logic.js;scaffoldSaa 带新文件集。
- **prompt.mjs**:agent 写 game-logic(不是 game.js)、入口签名、必写 3/可选 2、「事件→特效」范式表。
- **skill**:littlejs-game-dev.md 结构树 + 11 插件速查 + 手感二分表 + 最小 L3 骨架范例;sim-business-game-design.md 经营配方对齐 hud-ui 原语。
- **不改**:SAA 图、集成方式 C、批 B Java(amodel 路已收口)。
## 8. 验收
- **L1 控制门**:pristine _template scaffold-saa→build-saa→stage→smoke PASS(无 reading 'w')。
- **standalone**:M3 跑 2-3 个异构 brief(打地鼠/躲避/点击)→ ok:true + smoke PASS,且 LLM 只动 game-logic/core/render(git 证 L1 文件未变)。
- **全 SAA e2e(amodel)**:design 富化 brief → generate→validate→play 九门→emit ≥1/1(对齐 #41:富化要的 juice/音效插件现已注入)。
- **省活度量**:对比修前(写 5 文件含 host-config wiring)vs 后(改 3 文件),步数/token/收敛率提升。
## 9. 风险 / 开放项(交双审)
1. **bundle 体积**:11 插件被 registerOrder 静态引用 → 不被 tree-shake,bundle 192KB→可能 250-350KB。权衡:"全摆上桌"省 LLM 活 vs 体积/首屏。**双审定**:接受全注入,还是 L1 给"按 brief 选注入"的轻档(但破坏"全摆上桌"的简洁)?
2. **三分法(core/render/game-logic)对极小游戏可能过度**:利于分层 check + 单测,但小游戏显冗余。倾向保三分但 core/render 可极简。
3. **手感"自动 vs 显式"逐件分界**须在 skill 钉死,否则 LLM 要么没手感要么乱调崩。
4. **viewport 下传需改 L1 签名**(createGame + buildFactoryOpts);确认兼容现有 wrapper。
5. **_forensicsView 可选**:不写时 smoke 仍过但九门状态门变弱 → skill 标"建议写"。
6. **registerOrder/默认 config canonical 值**:以 generic-host-config 既有为准,实现期逐件核对勿臆造。
### 9.1 实现期 TODO(双审 minor · 折叠记录)
- **wrapper 撞名**:game.js 默认导出的 wrapper 与 game-logic.js 的 `createGame` 同名易混 → wrapper 另起名(如 `gameFactory`),`import { createGame } from './game-logic.js'`。
- **viewport 下传要改 3 处**:`buildFactoryOpts` 往 runtime 加 viewport + game.js wrapper 透传进 createGame + createGame 签名加 viewport(D3 完整链)。
- **juice seed 接线**:`createParticlesJuicePlugin({seed})` 的 seed 要从 `buildTemplateHostConfig(args).seed` 接(现 _template `void seed` 没接)。
- **新失败模式 = game-logic 导出名**:agent 的 game-logic.js 若没 `export function createGame`(名写错)→ 固定 game.js import 失败 → boot 崩。**check 必验 game-logic 含 `export function createGame` + 加 footgun**(旧 wiring 错换成了一个窄契约,守住它)。
- **check 真改写守卫迁 game-logic.js**(D7);balance/assets 可空不误判假绿。
- **D1/D3 落地改点(Codex)**:host-config 现 `import createGame from './game.js'` 拆后仍指 game.js(wrapper),import 不变;但 check 的 game.js≠模板守卫要迁 game-logic.js。
- **render↔hud-ui 软约定**:render.js 可裸 `g.fillRect` 也可 hud-ui 原语 → skill 给"优先 hud-ui 原语、g 仅补"软约定(非硬门)。
- **附录 B 手感方法名**:范式表/skill 的「事件→特效」一行必须按 api.d.ts 真实方法名生成(Codex 实测 `juice.burst` 是幻觉)。
---
## 附录 A:11 插件 canonical 键 + 默认 config(L2 回填,实现期以 generic-host-config + api.d.ts 为准)
| 类 | 键 | 工厂 + 默认 config |
|---|---|---|
| 编排 | sceneFsm | createSceneFsmPlugin() |
| 编排 | sessionScore | createSessionScorePlugin({best:{load,save}} 接 save) |
| 编排 | hudUi | createHudUiPlugin() |
| 编排 | timerScheduler | createTimerSchedulerPlugin() |
| 持久 | save | createSaveProgressPlugin({namespace, adapter:local→memory 兜底}) |
| 手感 | gamefeel | createGamefeelPlugin({inputBufferMs:260, inputTypes:['pointerdown']}) |
| 特效 | juice | createParticlesJuicePlugin({seed, maxParticles:600}) |
| 后处理 | palettePost | createPalettePostPlugin({vignette:{enabled:true, strength:0.22}}) |
| 音频 | audioMusic | createAudioMusicPlugin({masterVolume:0.8}) |
| 物理 | collision | createCollisionPlugin({cellSize:96}) |
| 物理 | physics | createPhysicsLitePlugin() |
## 附录 B:5 法标准骨架(范式示意 · 非完整实现)
```
export function createGame({ plugins, bundle, viewport }) {
const { sceneFsm, sessionScore, hudUi, timerScheduler } = plugins; // 用到才解构
let ctx; /* 闭包玩法态 */
return {
async init(boot) { ctx = boot.ctx; /* loadAssets → ctx.getInput().on → sceneFsm.define/start */ },
update(dt) { bundle.tick(dt); /* 推进玩法;命中/死亡/过关事件点调 juice/gamefeel 事件 API(方法名以 api.d.ts 为准) */ },
render(g) { sceneFsm.render(g); /* 经 hudUi 原语 + g 画;读 viewport.w/h */ },
destroy() { /* 幂等:cancel timer、reset fsm */ },
_forensicsView(){ return { state:()=>({...}), measures:()=>({...}) }; }, // 可选·建议
};
}
```