20 KiB
Raw Blame History

引擎与运行时 · 架构设计文档

🚧 架构演进中 —— 生成引擎子树正处于 gameDefinition→src/ 终态迁移 + 产线化(plan 2026-06-18-001 U1U4)中,文中标注的现行结论可能随推进变化;以子树 README 与最新裁定 / plan 为准。

适用范围(tier2 上线后收窄) —— 本档讲的 engineBundle 单包装载路,是 Tier0/1 超休闲廉价线的交付层。绘境AI 另起了第二条生成轨 tier2(agent 自治造多系统富游戏),它的产物是真 Phaser 多文件工程,走自己的一套源项目契约和第二装载分支,与这里的单包路解耦并存、互不替换。所以本档凡言「唯一」都限定在 Tier0/1 这一条线内,不覆盖 tier2。tier2 的装载设计见同目录 自治富游戏引擎tier2 实现详设

这是什么绘境AI 游戏引擎层与运行时装载体系的架构设计主文档,回答「选了哪个引擎、为什么选、游戏怎么挂上去、包怎么交付」。 给谁看:后端 / 前端工程师、AI 生成链路开发者、引擎集成评审方。 怎么读:先读第 12 节建立全局认知(选型结论 + 一张架构图),再按模块深入第 35 节。


1. 一句话与一张图:引擎层做什么

绘境AI 的游戏以「信息流即刷即玩」的方式送达玩家,首屏冷启动时间直接决定用户是否留下来。引擎层的核心任务就是:让 AI 生成的游戏在千元机 + 4G 网络下,从点击到可玩不超过 3.5 秒

为此,整个引擎层由三件事共同支撑:

  1. 选一个足够轻量的游戏引擎——这是体积与冷开速度的根本。
  2. 定义一套稳定的装载契约——让 AI 生成的游戏代码、引擎宿主、Runner运行时载体三者能通过同一套接口连接不互相耦合。
  3. 明确能力边界——哪些能力由引擎提供、哪些需要补层,边界清晰才不会让游戏代码直接依赖引擎内部。

下图展示了从「AI 生成的 bundle游戏代码包」到「在玩家设备上渲染出可交互画面」的完整链路

flowchart TB
  subgraph gen["AI 生成侧(便宜模型 agent 按装载契约产出)"]
    BD["engineBundle<br/>游戏代码包<br/>随 manifest JSON 内嵌"]
  end

  subgraph runtime["game-runtime · Runner v2运行时"]
    LOAD["bootGameHost<br/>装载入口"]
    HOST["集成段 host<br/>唯一 import 引擎处<br/>Q4 铁律)"]
    CAP["engine-caps.js<br/>能力包装层<br/>particles / audio / math"]
    CTX["PluginContext<br/>受控面 6 项<br/>game-host.d.ts 第9类契约"]
  end

  subgraph engine["LittleJS 增强发行版Tier1 引擎 · 55KB gz"]
    EI["engineInit<br/>五回调驱动主循环"]
    MC["mainContext<br/>唯一绘制面Canvas 2D"]
  end

  subgraph game["游戏侧(装载契约·零引擎 import"]
    GM["game.init(ctx)<br/>game.update(dt)<br/>game.render(g)<br/>game.onInput(ev)"]
  end

  BD -->|window.__GameBundle 全局挂载| LOAD
  LOAD --> HOST
  HOST --> EI
  EI -->|gameUpdate 回调| GM
  EI -->|gameRender → mainContext| GM
  HOST --> CAP
  CAP --> CTX
  CTX -->|ctx.getEngine() 受控面| GM
  GM -. 禁止 .-> |直接 import littlejsengine| ERR["❌ Q4 违例"]

2. 引擎选型:为什么是 LittleJS

2.1 终裁结论

Tier1 游戏引擎 = LittleJS 增强发行版55KB gz2026-06-12 创始人终裁,对比候选引擎 Phaser计分 85 vs 82

LittleJS轻量级 JavaScript 游戏引擎GitHub 开源项目)是针对极简、快速加载场景设计的 2D 引擎,体积约为 Phaser功能更全的主流 2D 引擎)的 1/10。

绘境AI 选择它的核心理由只有一条:冷启动速度

2.2 硬数据对比

「冷开」Cold Open即用户首次打开游戏、从零加载到可交互状态的时间是信息流场景的决定性指标——这是玩家点击一款从未玩过的游戏时的真实体验。

指标 LittleJS Phaser
裸冷开时间(千元机 + 4G 0.54 秒 2.86 秒
gz 包体积 55KB 326.7KBraw 1.24MB
综合评分S2 实测) 85 分 82 分

首屏 P75第 75 百分位用户的加载时间)目标是 < 3 秒,点卡可玩(从点击到可交互)目标是 S2 常态 ≤ 2 秒。Phaser 在千元机 + 4G 环境下的冷开时间已占去大部分预算3G / 弱网长尾用户的出线风险显著上升因此不适合绘境AI 的分发场景。

敏感性分析的结论是:即使把四组主观裁量维度全部拉平双方同分LittleJS 仍凭 S2 硬数据差20 分 vs 12 分)领先——选型结论对裁量分不敏感。

2.3 体积约束的演进

早期曾有「13KB 极限」js13k 游戏极限分支的约束和「15KB 红线」srcdoc 内联嵌入架构的衍生约束)两条旧规则。这两个前提架构已废除,对应约束一并失效。请勿再引用「13KB」或「15KB」口径。

现行约束是三层框架

  • SLO 地板(服务质量下限,必须满足):千元机 + 4G 首屏 P75 < 3 秒;点卡可玩 S2 常态 ≤ 2 秒。
  • 预算入场券 B1超出则体积风险预警gz ≤ 350KB、raw ≤ 1.5MB。
  • S2 实测为主考:以真机在 S2标准 2G/4G 弱网)环境下的实测数据作为最终评判依据。

2.4 复议条件

终裁包保留复议权(终裁包 §4.5-3若后续拔高样板游戏时暴露引擎级阻塞,可复议。切换成本仅限模板层重写Runner运行时载体和装载契约层不受影响——这是两层架构设计的重要价值之一。


3. 模板哲学:模板是什么,不是什么

在绘境AI 的语境里,「模板」一词有严格的边界定义。厘清这一点,可以避免生成链路的实现方向走偏。

3.1 宪法级定义

模板 = LittleJS 能力插件 / 二次开发件。每个插件封装一项引擎能力(碰撞检测、粒子特效、物理模拟、手感反馈……),对外只暴露公开 API可即插即用、可组合、版本化管理。

模板不包含玩法设计、美术成品、关卡数据、UI 布局——这四项是 AI agent 在生成游戏时的生成域,由 agent 按游戏定义产出,不由模板预制。

3.2 「游戏模板」与「玩法模板」的区分

历史上曾有「游戏模板」概念,即填参式的整局代码、预先写好的 4 套游戏。这类模板已于 W-CLEAN清场阶段对应 Wave 清场任务)中删除,不再存在。

「玩法模板」(品类框架,用于引导 AI 生成特定品类的游戏,本身是框架性描述而非预建代码)则是有效功能,状态是待建、非最高优先级——最高优先级是 Tier 0 生成可靠性(即 AI 能稳定地生成可运行的游戏),玩法模板排其后。

这一区分由 HJ-DEMO-AUDIT-0012026-06-17 创始人纠偏裁定,本档唯一权威口径)确立。历史 spec 中出现的「玩法模板永久废除」措辞属 2026-06-12 旧记录,已被本节取代。

3.3 可测性红线(硬门)

AI agent 生成的游戏必须导出取证清单:可交互几何(画布上的可点击区域)、锚点接线(事件绑定关系)、胜败可达断言(存在可以触发游戏结束的路径)。不可机器测试的游戏不许通过评估门。

好玩基线 v2 五要素(手感 / 美术统一 / 音乐 / 结构深度 / 角色壳)已从模板层迁出,挂在评估门上判结果值,不由模板预置。


4. 两层装载契约:引擎、宿主、游戏如何连接

这是引擎层最核心的架构设计解决「AI 生成的游戏如何安全、稳定地挂载到引擎上」的问题。

4.1 为什么需要两层

历史上存在三个「断口」:

  • 真引擎宿主:有掌帧(控制渲染循环),但没有游戏挂进去。
  • 真游戏 ref:有游戏代码,但没接引擎。
  • 生成产物AI 生成的代码喂给一个掏空的旧版 runtime运行时实际跑不起来。

如果给三者各造一套接引擎的方式,就会产生三套漂移——这违反「同一职责不留两条并行路」的边界原则,并且迟早需要三次返工才能对齐。两层装载契约的设计,就是给三者提供一份共同遵守的约定,让它们通过同一套接口连接。

4.2 下层契约(已冻结)

下层解决「插件如何安全获取引擎能力」和「游戏包如何交付」。

  • PluginContext.getEngine():受控引擎能力面,共 6 个方向,定义在 api.d.ts。插件只能通过这个接口获取引擎能力,不能直接访问引擎内部。
  • GamePackage engineBundle:契约 #4additive 追加字段),游戏代码包内嵌于 manifest JSON随包交付。
  • SDK storage 根契约:游戏存档与状态持久化的统一接口。

下层于 commit 28a57b8 冻结,不再修改。

4.3 上层契约(本弧 spike 实现)

上层解决「一款完整游戏(不只是插件)如何挂到引擎宿主」。

游戏宿主装载契约,定义在 game-runtime/src/core/game-host.d.ts,是第 9 类契约additive 追加)。它落在 game-runtime 内部而非 contracts/ 顶层目录,原因是消费方只在 game-runtime 这一个代码库内,放到跨端契约目录会污染其他消费方。

游戏侧只需实现四个函数:

// game-host.d.ts · 第9类契约精简示意
interface GameModule {
  init(ctx: PluginContext): void;       // 初始化,接受受控引擎面
  update(dt: number): void;            // 每帧逻辑更新dt=帧间隔秒
  render(g: CanvasRenderingContext2D): void; // 渲染g=引擎 mainContext
  onInput(ev: InputEvent): void;       // 输入事件
}

4.4 四条不可破约定

这四条约定是装载契约的护城墙,违反任何一条都会导致渲染错位、帧率失控或引擎版本耦合:

约定 内容 违例后果
掌帧唯一源 = 引擎 游戏不自起 RAFRequestAnimationFrame浏览器原生动画帧循环update/renderengineInit 五回调驱动 双帧循环冲突,帧率翻倍或紊乱
绘制面唯一 = 引擎 mainContext 所有美术与粒子渲到同一张 CanvassetGLEnable(false) 纯 2D 模式 渲染层分裂,游戏画面与引擎效果错位
插件能力唯一面 = ctx.getEngine() 引擎有的能力→薄包装提供;引擎缺的→插件补层,但都经同一接口 游戏代码直接依赖引擎内部,引擎升级即断
引擎 import 唯一活点 = 集成段 host 游戏代码和插件代码零直接 import LittleJSQ4 铁律,即第 4 季度确立的最终规则) 多处 import 导致引擎多实例,状态不一致

4.5 装载时序

sequenceDiagram
  participant Feed as 游戏信息流
  participant Runner as Runner v2 (bootGameHost)
  participant Host as 集成段 host
  participant Engine as LittleJS engineInit
  participant Game as 游戏模块 (game.js)

  Feed->>Runner: 用户点击游戏卡片
  Runner->>Runner: 解析 manifest JSON取出 engineBundle
  Runner->>Host: 挂载 window.__GameBundle
  Host->>Engine: engineInit(五回调注册)
  Engine-->>Host: 引擎主循环启动
  Host->>Host: createHostDevContext → PluginContext 受控面
  loop 每帧
    Engine->>Game: gameUpdate 回调 → game.update(dt)
    Engine->>Game: gameRender 回调 → game.render(mainContext)
    Game->>Host: ctx.getEngine() 取能力(如需)
  end
  Game-->>Feed: phase==='gameover' 轮询 → 游戏结束事件

注意最后一步:游戏结束时,游戏模块通过将 phase 状态字段置为 'gameover' 来通知宿主,宿主轮询这个字段(game_end 靠 latch 终态非 emit。游戏模块没有主动推送通道不能也不应该直接 emit发射事件给宿主。这一设计经 commit 238ec2d(井字棋真玩入 feed验证。


5. engineBundle 交付链与能力边界

本节讲的是 Tier0/1 超休闲线的交付层。tier2 富游戏走另一套源项目契约,不复用这条单包路(见 §1 适用范围横幅)。

5.1 包交付路径

Tier0/1 线上 AI 生成的游戏最终以 engineBundle 字段的形式随 manifest JSON 内嵌交付,而不是通过独立 URL 从 OSS对象存储加载。这个决策有两个好处

  • 省一条基建线:不需要部署和维护独立的 OSS 服务来托管游戏包。
  • 整包单一校验面manifest JSON 本身带 sha256 校验游戏代码随包一起验证不存在「manifest 版本与包版本不一致」的窗口。

装载时序manifest JSON 下发 → 取出 engineBundle 字段 → window.__GameBundle 全局挂载 → bootGameHost({canvas, seed}) 装载启动。

packageUrl 外链字段OSS 切换后用)和 immutable 强缓存头,是显式标注的 future-state 占位(未来可能启用的预留字段),当前未启用,不是孤儿设计,有明确的启用场景。

5.2 引擎能力边界三分

引擎能力分三类处理,边界由 engine-plugin-boundary-model(引擎插件边界模型)管理:

引擎原生有 → 薄包装6 件)

能力 LittleJS 提供 包装方式
粒子系统 ParticleEmitter 薄包装暴露给插件
音频合成核 zzfxG / zzfxM 薄包装暴露合成接口
数学工具 lerp / smoothStep / easing(Ease) 薄包装统一门面

引擎没有 → 自研补层4 件)

能力 为何需要补层
碰撞检测collision 引擎只返回 boolean是否碰撞补层返回 MTV Manifold(碰撞法向量)和 RayHit(射线命中点),给游戏更精细的物理信息
轻量物理physics-lite 引擎是全刚体仿真(精确但重),补层提供半隐式欧拉抛体 / 弹簧 / 运动学约束(够用且轻量)
后处理滤镜palette-post 引擎后处理走 WebGL 语义,与纯 2D 模式不兼容P5第5优先级渲染层红线规定只换色不给成品色板补层提供 vignette / scanline / dither
音频播放补层 引擎合成核不烤播放层增益,补层显式补 ×0.3 master 音量

铁律A2引擎接线阶段 2禁留 sim模拟器存根A3 禁 vendored供应商打包转正——二者均有意替代源码可证伪。自研只限「引擎之外的补层」不替代引擎本体。

5.3 历史兼容

插件公开 APIEmitterConfig)一经冻结不变,老游戏照跑。包装层和补层是实现内部事,公开接口稳定性由下层契约保证。


6. Runner v2 三阶段收口记录

Runner运行时负责把游戏 bundle 装载进引擎并呈现给玩家经过三个阶段P1 → P2 → P3完成实质收口于 2026-06-15 T1b-βT1b 第二个 beta 迭代,引擎接线与 Runner 第二轮验证阶段)阶段完成。

flowchart LR
  P1["P1 · 装载契约 spike<br/>game-host.d.ts 建立<br/>实证 5 门"] --> P2["P2 · 通用宿主泛化<br/>bootGameHost 落地<br/>引擎游戏真渲入 feed<br/>真机 6 门"] --> P3["P3 · 派发面解封<br/>SUPPORTED_TEMPLATE_IDS<br/>接通生成主线"]

  style P1 fill:#e8f4e8
  style P2 fill:#e8f4e8
  style P3 fill:#e8f4e8

P1 装载契约 spike:建立 game-host.d.ts(第 9 类契约),实证 5 个验收门确认契约可行。spike尖刺指为验证可行性做的最小实现阶段只验证接口不做泛化。

P2 通用宿主泛化bootGameHost 通用宿主落地,引擎游戏真实渲入信息流(不是「重构中」的占位,而是真渲染),真机验证 6 门。视觉终局:井字棋游戏空盘 → 落子 → 「X 胜!」全程在 feed 中真渲入。

P3 派发面解封SUPPORTED_TEMPLATE_IDS = List.of("generic") 解封,接通 AI 生成主线generic 是通用模板标识,意味着 Runner 不再只接特定 ID 的游戏,而是接受 AI 生成的任意游戏 bundle

Phase A 引擎真接线(与 Runner v2 并行A0 引擎掌帧接管门0 8/8+ A1A4 能力包装 + A6 真机门 6/6动态 call-ID 取证验证真实调用链(particles.spawnEmitter / audio.synth.synthSfx / math.easing.quadIn 均验证为真调引擎叶方法)。

零灰度闭环缺口、零 split-brainsplit-brain即「脑裂」指系统中两个部分对同一状态持有不一致的认知是分布式系统和前后端协作的常见问题——这是收口的核心验收标准。


7. 当前状态与待办

7.1 已完成(截至 2026-06-15 T1b-β)

  • 引擎选型终裁落锤LittleJS55KB gz85 分)
  • Phase A 引擎真接线A0A6 全部通过真机门)
  • Runner v2 全弧P1 → P2 → P3 完成)
  • 井字棋真玩入 feed 视觉终局验证commit 238ec2d

7.2 当前最高优先级(在飞)

W-G1 开闸验收门Wave G1即便宜模型游戏生成第一波验证便宜模型低成本 AI 模型agent 按装载契约产出 bundle九门 harness九个验收门组成的自动化测试框架兜底目标是全 20 款游戏 bake-off烤箱对比逐款对比测试+ 四模型横比 + Claude-free judge 门(使用非 Claude 模型作为评判者,避免自评偏差)。

7.3 Backlog不在完成线关键路径

以下属于技术债审计 / 样板拔高,不阻塞当前主线:

待办项 说明
render.js 切引擎 draw API 现为 100% Canvas2D引擎掌帧已达成「经引擎画游戏」的目标切 API 是样板拔高,不是必须
SIZES 单口径并表 现「插件增量主表 + 引擎产物附录」两口径并存,可合并
逐像素 WebGL readPixels 回归 A0 有意降级real 通道追帧 overshoot 不可控 → 逐像素由 stub 2D 通道独占兜),回归属优化
`outcome:'win' 'lose'` additive 字段
tier2 第二装载分支 tier2 Phaser 多文件工程的源项目契约 + 第二装载分支为待开项(见 tier2 实现详设),不在 Runner v2 现弧内

8. 关键指针

代码文件

文件 作用
game-runtime/src/core/game-host.d.ts 第 9 类装载契约(上层,游戏-宿主接口)
game-runtime/src/host/boot-game-host.js 装载入口实现
game-runtime/src/host-dev/engine-caps.js 引擎能力包装层6 件薄包装 + 4 件补层)
contracts/game-package.schema.json GamePackage 契约(含 engineBundle / immutable 字段)
contracts/sdk-interface.d.ts SDK storage 根契约

关键 commit

commit 内容
204eaed 装入引擎
11c8eaf + 7efe8a4 A0 引擎掌帧接管
e591e9b A1A4 能力包装
3e5cab4 A6 真机 6 门
99b6c82 P1 装载契约 spike
5d3f7a8 P2 通用宿主泛化
238ec2d 井字棋真玩入 feed视觉终局

延伸阅读

  • .agents/knowledge/tech-decisions.md §1.1:引擎选型权威蒸馏(已对齐现行真相)
  • .agents/skills/add-game-template.md:玩法模板 / 插件 onboarding含能力插件库 v1 P1P10 字节预算清单
  • .agents/skills/game-e2e-cdp-harness.md §5/§6:引擎真接线门坑与 driver 六规则CDP = Chrome DevTools Protocol用于驱动浏览器的自动化协议
  • .agents/skills/runtime-and-multichannel.md:打包 / 沙箱 / SDK / 多渠道导出手册
  • docs/agent-specs/_archive/:本档取代的 4 份历史 specT1 引擎终裁包 / W-T1b-Runner 双层模板 / runner-v2-arc / T1b-β 收口报告)