32 KiB
固定游戏架构 + SAA 智能工作室
🚧 架构演进中 —— 生成引擎子树正处于 gameDefinition→
src/终态迁移 + 产线化(plan2026-06-18-001U1–U4)中,文中标注的现行结论可能随推进变化;以子树 README 与最新裁定 / plan 为准。
这是什么:绘境AI「一句话造游戏」背后的生成引擎设计。它回答一个核心问题——一个便宜的小模型,怎么可靠地造出一款能上线、能玩的游戏。 给谁看:生成引擎方向的工程师、架构评审、新加入的同事。 怎么读:先读 §1 抓住"固定脚手架 + 便宜模型填槽"这一个主意,其余各节都是它的展开。 品牌:本文统一用「绘境AI」。
1. 一句话与一个主意:为什么是"固定架构"
让一个便宜的小模型从零发明一款游戏的完整代码结构,是不可靠的——它每次都得重新设计实体、循环、胜负判定,错一处就崩。绘境AI 的破题思路反过来:由一个最强的模型(Opus)一次性把游戏的骨架设计好、固定下来,便宜模型不再发明结构,只在这副固定骨架的"槽位"里填东西——填逻辑、填参数、填关卡、填资产、填胜负规则。
这里有两个名词先解释清楚:
- Opus 是绘境AI 用来做架构设计与最高复杂度终裁的最强模型;便宜模型(cheap model,文中也叫小模型)指 deepseek-v4-flash、MiniMax-M3 这类单价极低、用来批量干活的模型。
- 填槽(fill-the-slot):便宜模型不负责"游戏该长什么样"的结构决策,只负责把固定骨架里预留好的空位补全。
这么做的根本理由,是把命门从"便宜模型能不能发明出结构"降级成"便宜模型能不能在一副已知的好骨架里把内容填好"——后者远比前者可信。两个评审都曾把"便宜模型产不出结构化、可维护的源码"列为致命问题(P0,即最高优先级、必须先解决的阻断项);固定架构正是对这个 P0 的回答。
但光有固定骨架还不够,便宜模型需要一个"工作环境"来被榨出最大价值。这就是第二个主意——SAA 智能工作室:
flowchart LR
A["创作者<br/>一句话 + 素材"] --> B["SAA 智能工作室<br/>多个便宜 agent 协作"]
B --> C["源项目<br/>(可维护的结构化定义)"]
C --> D["确定性构建<br/>→ 可玩 bundle"]
D --> E["九门真玩<br/>(自动质量门)"]
E --> F["GamePackage 产物<br/>(schema 不变)"]
F --> G["游戏信息流<br/>真玩"]
- SAA 是绘境AI 后端采用的生成编排底座(Spring AI Alibaba),用它的"裸状态图"(StateGraph,把生成流程画成一张节点+连线的有向图,每个节点是一个独立步骤)把多个便宜 agent 串成一个能迭代、能自检、能反馈的协作系统。文中的 agent 指图里的一个有专长的工作节点(分类 / 设计 / 写逻辑 / 修 bug 等)。
- 工作室(studio)= 由 SAA 编排的这一整套多 agent 协作系统。
四样东西合起来,才把便宜模型榨满:SAA 提供编排、固定架构提供结构、便宜模型提供生成、自动质量门提供把关。给定单款生成预算 < $1(便宜模型单价低,一美元容得下几十次调用),这个环境就能放心地多 agent、多迭代、多重试。
这套设计不是空想——但要把"成立到哪一步"说准。便宜模型在固定结构上填出连贯的游戏定义中间表示(gameDefinition)、再构建成真能过门的游戏,这一段已在代码层得到双重验证(见 §9 实测证据):双证证明的是"便宜模型能可靠产出连贯的 gameDefinition 中间表示",并未、也不能证明"gameDefinition 就是合格的终态产物"。 创始人 2026-06-20 已就产物终态定调:终态产物必须是
src/多文件代码工程,gameDefinition 只是"通往 src/ 的中间脚手架";当前实现把玩法逻辑塞进behavior.codeJSON 串、运行时用new Function解释执行,属"已知偏离终态的债",缺的正是"gameDefinition → src/"产物侧那一段展开。所以本档凡言"可维护源项目 / 把大模型当工作室",均指方向已经走通、终态尚待兑现,不是已闭合的终局——口径以同目录 README §3.2 终态硬约束 与 设计合理性裁决(Tier0 混合范式 · 名实分层)为准。
2. 固定的部分 vs 可填的部分
整套架构的第一刀,是把"谁也不许动的固定骨架"和"每款游戏都不一样的可填内容"切开。
graph TB
subgraph FIXED["固定架构(Opus 设计一次 · 全游戏共用)"]
A["项目骨架 / 构建管线"]
B["游戏运行时契约:固定生命周期"]
C["游戏定义 schema:<br/>实体 / 组件 / 行为 / 场景 / 规则"]
D["引擎适配器:2D LittleJS 现 / 3D Cocos 后"]
E["可填槽定义 + 结构校验门"]
end
subgraph FILL["可填(便宜 agent 每款生成,符合上面契约)"]
F["实体 / 组件 / 行为模块"]
G["参数 / 平衡 数据"]
H["关卡 / 内容 数据"]
I["资产规格 / 引用"]
J["胜负规则"]
end
FILL -->|符合契约| FIXED
FIXED -->|适配器构建| K[("可玩产物 bundle<br/>宿主 / 信息流 不变")]
- 固定的部分:不随游戏改变,由 Opus 设计、长期按版本演进。包括项目骨架、运行时契约、游戏定义的数据结构(schema)、引擎适配器、以及把关用的结构校验门。
- 可填的部分:每款游戏由便宜 agent 现场生成,但必须符合固定契约,由结构校验门把关。
之所以能这样切,关键在于可填的部分是数据驱动的:逻辑读参数、资产用引用、行为是一个个符合契约的模块。正因为内容是数据,"修改一款游戏 = 改源里某个槽 + 重新构建"才能成立——这也是后面"改源不改产物"生命周期的地基。
这一刀还带来三个连锁好处,正好对上创始人定下的三条新约束:
- 缓存命中高:固定架构是一段稳定不变的上下文,可以被模型的"前缀缓存"命中,大幅降成本(详见 §7)。
- 便宜模型可靠:它永远在填同一套已知结构,不必每次现学。
- 长期可维护:游戏以结构化源项目存在,而不是一坨黑盒代码。
一条重要纪律:架构与玩法解耦。 固定架构是领域模型框架,玩法是"填进去的数据 + 行为模块",两者正交。所谓"玩法模板"只是某个品类的预设(引导便宜模型怎么填),不是另一套独立架构。遇到极端不同的品类,用"通用核心 + 品类附加画像"来吸收差异,绝不分叉出第二套架构。
3. 怎么做到 2D/3D 兼容:游戏定义模型 + 引擎适配器
固定骨架的核心,是一套声明式、维度无关的游戏定义模型。"声明式"指它描述"游戏由什么构成",而不是写一段过程式代码;"维度无关"指同一份定义既能渲染成 2D 也能渲染成 3D。它故意做得轻量——是领域模型,不是 AAA 大作那种重型 ECS(ECS = Entity-Component-System,一种把游戏拆成实体、组件、系统调度器的架构;这套定义没有系统调度器、没有原型存储、没有查询 DSL,守住"简单"二字)。
模型由五类元素构成:
- 实体(entity):带一个
transform(位置 / 旋转 / 缩放)和一组组件。位置用同一套字段表达两个维度——2D 用 x/y(z 默认 0),3D 用 x/y/z,同一字段两维都成立。 - 组件(component):声明式属性,如渲染(sprite / mesh)、碰撞、物理,由适配器去解释。
- 行为(behavior):游戏逻辑——读参数、操作实体。它是逻辑,与维度无关。
- 场景(scene)/ 规则(rule):关卡如何构成、胜负如何判定,都是数据驱动。
光有定义还不能跑,需要引擎适配器把定义"翻译"成具体引擎能执行的东西:
flowchart LR
DEF["游戏定义模型<br/>(维度无关 · 一份)"]
DEF -->|2D 适配器| L["LittleJS<br/>实体 → sprite"]
DEF -->|3D 适配器<br/>(Phase 2)| C["Cocos<br/>实体 → mesh / node"]
L --> P2D["可玩 2D 游戏"]
C --> P3D["可玩 3D 游戏"]
- LittleJS 是绘境AI 选定的轻量 2D 引擎(分层引擎里的 Tier1,即首选层);Cocos 是更重的 3D 引擎,服务 3D / 渠道导出轴(编辑器 + 人在环),与 tier2 自治生成轨正交、不是 tier2 富游戏轨的引擎(tier2 富游戏自治轨用 Phaser/Pixi,见同目录 自治富游戏引擎)。
- 本期只实现 2D 适配器,3D 适配器留到 Phase 2;但定义格式从一开始就不写死 2D 假设,所以 Phase 2 加 3D 适配器无需改格式。
最大的收益一句话:同一份游戏定义,换适配器即换引擎。所谓"尽量 2D/3D 兼容"的真实含义,是格式前向兼容,而非 Phase 1 就把 3D 全套建出来。
4. SAA 智能工作室:9 个生成 agent + 1 个评审 + 1 个离线
工作室是 SAA 裸状态图编排的多 agent 协作系统。它从现有的 11 节点 SAA 图演进而来,经 Opus 深度分析(并参考了开源项目 OpenGame 的设计),精化为 9 个生成 agent + 1 个条件触发的叙事评审 agent + 1 个离线技能沉淀 agent。全图 16 节点已建成并合入主干。
先说一个最关键的澄清:写游戏代码和修 bug 是同一个核心代码 agent 的两面,不是两个独立 agent。它在 generate 时写代码、在 repair 时修代码,构成一个自纠错环。其余 agent(分类 / 设计 / 资产 / 配置 / 构建 / 质检)都不写游戏代码。
各 agent 的职责:
| agent | 职责 | 备注 |
|---|---|---|
| classify(分类,新) | 把一句话 brief 判成品类原型 + tick/input/progress 三维画像 | 成功率最大的杠杆——填错品类比写错码更致命 |
| design(设计) | brief / 对话 → 结构化游戏设计(机制 / 实体 / 胜负) | 产出 GDD(Game Design Document,游戏设计文档) |
| logic(写逻辑) | 填实体 / 组件 / 行为模块(符合契约) | 核心代码 agent 的"写"一面 |
| config/balance(配置平衡) | 产参数 / 关卡(数据驱动) | 与 logic 同源拆出 |
| asset(资产,新) | 产六类资产的规格 / 引用 | provider 可插拔,默认走 mmx-cli |
| build(构建) | 适配器构建 → 可玩 bundle | 调唯一构建脚本 |
| QA(质检) | 九门真玩 + 结构校验 | 由 validate + play + player 三节点构成 |
| repair(修复) | 门失败定位 + 回喂修复 | 核心代码 agent 的"修"一面 |
| modify(修改,新) | 生命周期:在固定结构上改一处 + 重构建 | 旧 11 节点图的真空白,本期补上 |
另有两个特殊角色:
- narrative-designer-reviewer(叙事设计评审,条件触发):剧情 / 叙事 / TRPG(桌上角色扮演)类游戏保留在 MVP(不另开独立轨道)。因为九门这类确定性测试测不了"叙事是否连贯",所以给这类游戏配一个独立的"设计 + 代码评审"agent 当质量门。这是创始人的明确裁决:质量门按品类换机制——动作类用确定性九门,叙事类用 agentic 评审(终判留创始人)。
- skill-curator(技能沉淀,离线冷路径):把成功经验沉淀成可复用技能,经 Git 注册表与生成热路径解耦。MVP 只做 Debug Skill(把"错误签名 → 验证过的修复"沉淀下来),Template Skill(骨架萃取晋升)推迟到远期。
演进路线(接法 A / 接法 B): 当前 16 节点是接法 A——裸图确定性编排,由九门判定"做完了没"(已建七成、是躯干)。接法 B 是让 generate/repair 长成一个 ReactAgent 自治工具循环(把"写源、构建、跑九门、读错误、改配置和资产"都做成它能自己调用的工具)。九门兜底化解了"自治"与"铁律"的张力——自治在门内、裁决在门外。策略是先用 A 稳住地基,再用 B 增量演进,避免返工。
5. 八个契约:两条开发线的唯一事实源
这套架构由两条开发线并行实现:后端线(负责源项目存储、SAA 拓扑、救场路由、工作室编排)和引擎+前端线(负责 2D 适配器、资产产消、构建实现、九门质检、前端创作/修改/预览界面)。两条线要能各自独立开工而不打架,前提是先把交汇面冻结成契约——这就是契约先行铁律:契约没冻结,两条线都不许开工。
交汇面正好是 8 个契约:
graph TB
subgraph 契约["8 契约(开工前冻结 = 两线交汇面)"]
C1["① 源项目 schema"]
C2["② 源项目 DB 存储"]
C3["③ 源/产物分离"]
C4["④ SAA state key 全表"]
C5["⑤ asset 产消"]
C6["⑥ modifyPatch"]
C7["⑦ 唯一确定性构建 API"]
C8["⑧ trace+cost 字段"]
end
BE["后端线<br/>主锁 ①②④⑥⑧"] --> 契约
EN["引擎+前端线<br/>主锁 ①(消费)⑤⑦③"] --> 契约
下面逐个说清每个契约是什么、为什么这么定,字段级细节见原始 execution 文档,这里只保留结论与硬约束。
契约① 源项目 schema —— 便宜模型填的唯一结构化产物
新文件 contracts/agent-loop/source-project.schema.json,由 Opus 设计、长期版本化升级。它是便宜 agent 在固定架构上填出来的唯一结构化产物,顶层包含:
schemaVersion(源项目契约版本,独立于 GamePackage 版本)、sourceHash(源 JSON 规范化后的 sha256,既是可寻址键也是幂等键)、buildInputHash(= sourceHash + 构建画像的 sha256,作为构建缓存命中键)。profile三维画像:tickModel(realtime / turn-based / event,游戏怎么推进时间)、inputModel(continuous / discrete-choice / text-command,玩家怎么输入)、progressModel(metric / narrative,靠数值还是靠叙事推进)。为什么必须三维:只有纯物理一维会让剧情 / TRPG 类绷断,三维才覆盖得住。progressModel=narrative会走叙事评审质量门。gameDefinition(§3 那套实体 / 组件 / 行为 / 场景 / 规则)、assets(六类资产规格)、config(平衡参数,确定性修改就改这里、免调用 LLM)。
一条诚实边界(含一处尚未兑现的缺口):
behaviors是声明式的模块描述,真正的逻辑代码由核心代码 agent 产出。设计的终态目标是——逻辑必须以可维护的多文件src/源项目存在,而不是一坨裸 iife(iife = 立即执行函数表达式,这里指那种逻辑只以 JSON 字符串或new Functionblob 形式塞着的黑盒产物)。但这条目标当前并未兑现:gameDefinition 仍把整段玩法逻辑塞进behavior.code这个 JSON 字符串字段,运行时用new Function直接解释执行(见game-runtime/src/host/gd-runtime.js:236/251)——这恰恰是设计明令反对的"硬编码 blob",是已知偏离终态的债(创始人 2026-06-20 定调,见 README §3.2)。缺的正是"gameDefinition →src/工程"这一段产物侧展开:gameDefinition 只是通往src/的中间脚手架,必须再经一道"编译/展开成src/工程"的构建,而不是停在 JSON 里被new Function跑掉。这条债要由一道 doc↔code 兑现门(断言产物存在src/多文件结构、逻辑落在真实源码文件、不存在"逻辑只以 JSON 串形态存在 / 运行时new Function跑内嵌串")来守住,属后续 plan,不在本期展开。
契约② 源项目 DB 存储 —— 独立的 game_source_project 表
源项目落在一张全新的独立表 game_source_project(建表走 Flyway 迁移 V18.0.0,接在已合入的 V17 之后;后续 V20.0.0 追加并发兜底唯一键 uk_game_source_hash,见 commit d57032a6 U2 数据完整性,属 additive 完整性补丁。Flyway 已合入的脚本禁改,回滚只能写补偿迁移)。
为什么必须独立建表、绝不塞进 game_version:这是架构评审的明令。源项目(可维护、会被改)和打包产物(可玩、冻结)是两个物理解耦的存储面,塞在一起会破坏 GamePackage 产物的纯净性。这里曾与另一份 DB 评审的"game_version 双存"口径冲突,本架构采独立表口径胜出。
表里用一个 status 字段(0 草稿 / 1 已构建 / 2 孤儿 / 3 已发布)串起事务边界:
flowchart TB
A["工作室编排 → 图产源项目"] --> B["源落库 status=0"]
B --> C["触发确定性构建"]
C -->|构建成功| D["建预览包(走唯一写入路径)<br/>回填 version_id + status=1"]
C -->|构建失败| E["源 status=2(孤儿)<br/>不建包 · 不动 currentVersion"]
关键一点:源落库和建包不在同一个大事务里(构建要几十秒,长事务会拖垮连接池),用 status 机器态串联,而非分布式事务。
契约③ 源/产物分离 —— GamePackage 维持产物-only,不改
game-package.schema.json 一个字节不改。它的顶层是 additionalProperties:false(不允许出现 schema 未定义的字段),所以源项目字段禁止塞进 GamePackage。engineBundle 仍是 GamePackage 里一个可选的(additive,新增不升版本号)产物字段,由构建写入、由唯一写入路径落包。两个面的对照:
| 面 | 存储 | schema | 可变性 |
|---|---|---|---|
| 源项目(可维护) | game_source_project(新) |
source-project.schema.json(新) |
便宜模型填、确定性修改改 |
| 打包产物(可玩) | game_version + game_runtime_package |
game-package.schema.json(既冻) |
构建产出、宿主消费,不变 |
契约④ SAA state key 全表 —— 严格 additive
SAA 图里所有节点共享一张 state(状态表),每个键的写入策略都是"节点返回即覆盖写"。这次在现有键之上只新增、不改语义。新增键包括:archetype(品类原型)、三维画像 tickModel/inputModel/progressModel、sourceProject(新核心产物 JSON 串)、assetSpec、modifyMode/modifyPatch/baseVersionId(修改路用)、failCount(连续九门失败计数)、modelTier(当前模型档)、escalationEvents(升档事件)、giveupDumpPath(放弃前 dump 落盘路径)、narrativeReviewVerdict(叙事质量门裁决)。
为什么安全:严格 additive——存量节点不读新键就零影响;trace 抽取是 best-effort(尽力而为),新键缺了就省略,trace_json 字节兼容。
契约⑤ asset 产消 —— 六类资产一次定死
asset 节点根据品类和游戏定义产出六类资产规格,写进源项目的 assets[];下游 logic / 构建消费它,最终解析成 GamePackage 的 assets[],由宿主加载渲染。
- 六类枚举一次定死(四处同引):
sprite / character / effect / scene / ui / music。 - provider 可插拔:默认
mmx-cli(投资人版默认走 mmx-cli,音乐走"记谱 → 合成"两步);换 provider 只改 asset 节点,不动消费侧。 - MVP 边界:asset 节点首版可以只产规格、不真生图(用 canvas 几何兜底),真资产生成作为 additive 升级;但
assets[]这条产消通道必须打通,否则就是个孤儿设计。
契约⑥ modifyPatch —— "改源不改产物"的载体
modify = 在固定结构上改一处 + 重构建。它的载荷 modifyPatch 指明改谁(base 版本)、怎么改(deterministic 确定性编辑 vs regenerate-module 让 LLM 重生成一个模块)、改哪个部件(资产 / 配置 / 关卡 / 行为,用 JSON 指针或部件 id 寻址)。口径锁定如下:
- 换美术 / 调参 / 改关卡 = 确定性编辑:直接覆写源文件 → 重构建 → 新预览版,免 LLM、秒级。
- 改玩法逻辑 = 重生成模块:LLM 只重生成那一个 behavior 模块,不是全量重出,其余部件不动。
- 产物:新预览版,不动 currentVersion、不自动进信息流,仍走发布审核;失败则不建新版、base 不动。
- 旧设计里"manifest 可寻址 diff"的方案已废、不采用。
契约⑦ 唯一确定性构建 API —— 化解构建权威分裂
输入 源项目 + 构建画像,输出 engineBundle + checksum + bundleSize + buildLog。这里有一个曾经的隐患:构建到底谁说了算?裁决如下:
- 构建执行权在 worker / SAA build 节点共调的那一个
scripts/build.mjs(esbuild 打包,产出顶层全局名__GameBundle)。它现在已经真跑、九门验过,不另造第二个构建实现。worker 和 SAA build 节点本就调同一个脚本同一套参数,天然统一。 - 后端
RuntimeBuildServiceImpl只管落包(消费构建产物 → 落game_runtime_package+ 回填game_version三字段),不要求它自己跑 esbuild。一句话:构建执行权在 worker/build 节点,落包权在后端,职责单一、不分裂。 - 确定性的定义:同一个
buildInputHash必须构建出字节等价的 bundle(从源能重新构建出一模一样的产物,证明它"非一次性")。靠锁引擎版本和 esbuild 参数保证。
构建产物还携带两个硬性体积入场券(B1 发布链要求):raw ≤ 1.5MB、gz ≤ 350KB。
契约⑧ trace+cost 字段 —— 可观测与成本台账
在现有的追踪账本上扩字段:modelTier(终态模型档)、escalationEvents(升档事件)、cacheHit(缓存命中 token,落成本台账)、giveupDumpPath(放弃前完整 dump 路径)、cost(折算人民币)。
缓存命中检测要兼容两套字段:DeepSeek 用 usage.prompt_cache_hit_tokens,MiniMax 用 usage.prompt_tokens_details.cached_tokens,取两者最大值落账。命中 token 只进成本台账(token 计量),不接收益结算——这是生成侧与收益侧的硬边界。
6. 救场阶梯:便宜模型扛不住时怎么办
便宜模型有时就是填不出能过门的游戏。这时不能无限重试烧钱,也不能一失败就放弃。绘境AI 用一道带限额的救场阶梯——这是创始人的原话落地:
flowchart TB
S0["便宜模型 stage1 起步"] --> P{"九门 verdict.pass?"}
P -->|通过| OK["收口 → 产物"]
P -->|失败 failCount++| C5{"连续 5 次失败?"}
C5 -->|否 failCount<5| R1["repair 修复(仍 stage1)"]
R1 --> P
C5 -->|是 failCount=5| ESC["escalate 升档<br/>stage1 → stage2"]
ESC --> P2{"stage2 九门?"}
P2 -->|通过| OK
P2 -->|再失败 3 次 failCount≥8| GU["giveup<br/>完整 dump 落盘"]
P2 -->|未到 3 次| R2["repair(stage2)"]
R2 --> P2
几个要点,都是硬约束:
- 救场的"判定锚"是九门的
verdict.pass=false——确定性结果,不是模型的主观自评。这是为了防止一种叫 split-brain(脑裂) 的毛病:模型一边自评"我做得很好"、一边其实没过门,自评和客观门各说各话。锚死客观门,就杜绝了脑裂。 - 阶梯:便宜模型(stage1)→ 连续 5 次九门失败 → 升一档模型(stage1→stage2) → stage2 再失败 3 次 → 放弃本次生成。
- 放弃前必须完整 dump:把所有尝试的源码和裁决整包落盘(路径记在
giveupDumpPath),喂给 Opus 离线分析、反哺优化。这是硬观测要求,不是可选项——旧的 giveup 会丢中间源码,这次补上。 - recursionLimit 重算:状态图有个"最多走多少步"的上限(recursionLimit),防止死循环。救场总轮数从原来的 5 升为 5+3=8 主轮,这个上限按新口径重算(建议 ≥90,留足余量),确保正常收口不会误触上限。对应配置项也从单一的"最多修 5 次"扩成可配,新增"stage2 额外修 3 次"。
品类原型映射表(避免孤儿分类)
classify 出来的 archetype 必须落到一张映射表的某一行,否则就是"孤儿分类"——分了类却没人接。这张表把品类锚到现有的品类画像:
| archetype | tickModel | inputModel |
|---|---|---|
clicker(点击) |
event | discrete-choice |
dodge(躲避) |
realtime | continuous |
runner(跑酷) |
realtime | continuous |
bubble(瞄准发射) |
realtime | continuous |
match3/line-clear(点选消除) |
turn-based | discrete-choice |
merge(合成) |
event | discrete-choice |
idle(放置) |
event | discrete-choice |
tycoon(经营) |
turn-based | discrete-choice |
generic(兜底) |
* | * |
- 物理优先分类(借鉴 OpenGame 的 Physics-First 思路):classify 先定 tickModel(实测 100% 准),再定 archetype(实测 93% 准)。
- 映射表本身就是契约:新增一个 archetype,必须同时补这张表和对应的品类画像。注意这里的品类映射是用作引导(给 prompt / 脚手架做品类预设),不是填参校验执行器——废弃的是旧的"游戏模板",而"玩法模板"作为品类框架并未废弃。
7. 缓存三段式:把固定架构变成省钱的资产
固定架构有个隐藏红利:它是一段永不变化的稳定上下文,正好能被模型的"前缀缓存"命中。前缀缓存指的是——如果两次请求的开头(前缀)字节完全一致,模型对这段前缀的计算可以复用,命中部分便宜约 10 倍。绘境AI 把 prompt 切成三段来吃这个红利:
flowchart LR
P1["固定前缀<br/>(契约 / few-shot / 能力面)<br/>永不变 · 最大块"] --> P2["共享中段<br/>(GDD)"] --> P3["可变后缀<br/>(brief / 反馈)"]
P1 -.->|前缀缓存命中| SAVE["第二款起省 ~90% input"]
- 固定前缀(契约、few-shot 示例、能力描述)永不变,是最大的一块,第二款游戏起就能省掉约 90% 的输入 token。
- 共享中段是 GDD,可变后缀是这一款的 brief 和反馈;repair 回喂只追加后缀,不动前缀。
要让缓存稳定命中,前缀必须字节级一致。现状有个坑:few-shot 示例是从文件读的,字节不稳。对策:把系统前缀 + few-shot 冻结成版本化的 Prompt Registry 资产(放进 contracts/prompts/),由 CI 卡死"字节不变"。
这条缓存策略不是赌的,已实测证绿。 deepseek-v4-flash / MiniMax-M3 / M2.7 经过 new-api 网关(绘境AI 的模型调用统一网关)转发后,前缀缓存全部透传,第二款起命中 76–93%。曾经的最大单点风险"网关会不会把缓存吃掉"已经解除,剩下的只是"冻结 few-shot 字节稳 + 命中 token 落账"这点工程动作。
8. 失败路径:每一种崩法都有去处
一套生成系统的可靠性,体现在它对失败的处置上。下面是已经定好的边界失败路径:
| 失败情形 | 处置 |
|---|---|
| 源落库成功但构建失败 | 源标 status=2(孤儿),不建包,不动 currentVersion |
构建产物缺 __GameBundle 全局名 |
emit 判 failed,不落坏包 |
| LLM 调用超时 / 429 / 5xx | 单次 180 秒超时 + 最多 3 次重试,超限不裸退图(写 feedback) |
| 九门连续失败 | failCount++ → 5 次升档 → 8 次 giveup + 完整 dump |
| 叙事评审连续要求修复 | 同九门 failCount 阶梯计数 |
| 缓存前缀不透传(已证透传,仅理论兜底) | 命中字段缺则按 miss 计价,生成不阻断,仅成本回退 |
| modify 失败 | 不建新版、base 不动 |
| classify 出未知 archetype | 落 generic 兜底 + 告警,不污染下游 |
| trace 抽取异常 | best-effort 吞掉 + warn,trace_json 留空,不阻断生成 |
一个共同原则贯穿其中:失败要么有兜底、要么标记隔离,绝不污染主链、绝不落坏数据。
9. 实测证据与验收口径
这套架构的地基已在代码层双重验证,不是纸面设计——但要把验证范围说准:双证证明的是"便宜模型能可靠产出连贯的 gameDefinition 中间表示",而非"它已是合格的终态产物":
- spike(小范围验证性实验)证地基:便宜模型对
source-project.schema.json产出连贯的游戏定义(gameDefinition 中间表示),准确率 92–100%,单款成本约 ¥0.011,行为模块带真逻辑 JS。 - build 段证可玩:游戏定义经"从源构建"流程,在真浏览器里跑过九门——deepseek-v4-flash 满分、便宜模型 3/4 款过门,有截图实证。口径限定:这是 spike 阶段的证据,不是现行产线成绩——gamedef 路尚未切为默认产线(U7 cutover 按门未切),现默认产线仍为 factory/iife 路;SAA 真全图 e2e(
SaaFullGraphE2eTest)当前成功率 3/5=60% < 80% 门,端到端达标与 cutover 尚未完成(见 MVP进度总账 §2 M2 行 与 生成主线架构演进路线 cutover 阶段)。
由此方向已经走通:"改源不改包 / 可维护源项目 / 把大模型当工作室"的中间表示一段在代码层成立,配套产出了"运行时访问约定 v0"(便宜模型只能经一个受控的 rt 对象访问引擎能力)和"从源构建"的装配原型。但终态尚待兑现——产物侧"gameDefinition → src/ 工程"那一段展开仍缺(创始人 2026-06-20 定调,见 §1、README §3.2、设计合理性裁决),当前 new Function 内嵌串属已知债,不是已闭合的终局。
九门是这套质检的核心,指九道确定性的真玩自动门(serve-and-play 起服务、CDP 真浏览器玩、build 构建等组成的 harness),它们的脚本是子进程复用、禁止重写的既有资产。
最终验收口径分三层:
- 后端线门:编译 + 单测绿、Flyway V18/V20 迁移绿(V18 建表 + V20 唯一键)、救场阶梯单测(5 次升档 / 8 次 giveup + dump 落盘)、trace 字节兼容、modify 局部性。
- 引擎+前端线门:2D 适配器把定义渲成可玩、九门真玩绿、asset 产消打通、构建确定性(字节等价)、前端创作/修改/预览真机走查。
- 联合 spike 门:缓存透传 ✅ 已绿(76–93%);classify 准确率 ✅ 已绿(tickModel 100% / archetype 93%、约 $0.003/次);端到端门(待实施后验)= 一句话 → 工作室 → 源项目 → 构建 → 九门过门 → GamePackage → 信息流真玩,成功率对齐 MVP ≥80%、单款 < $1(¥0.15 门)、modify 局部性达标。
核心安全垫:GamePackage 产物 schema、宿主装载契约、回调唯一写入路径、九门 harness 全不变。所以任一新增环节失败,生成主线都能回退到现有 worker 直产 bundle + 现 11 节点 SAA 图,发布链和信息流真玩零回归。
10. 范围、归属与开工前待解
本期范围内:固定游戏定义模型 + 2D 适配器、源项目契约 + 独立 DB、SAA 图扩节点(classify/asset/modify + 叙事分支 + 救场阶梯 + 完整观测)、唯一确定性构建 API、三段式缓存 + 版本化 Prompt Registry、trace+cost 扩展、modify 生命周期。
本期范围外:3D 适配器(Cocos,Phase 2,格式留前向兼容)、Template Skill(只做 Debug Skill)、per-creator 计量计费(沿用 new-api 现有对账)、渠道线(小游戏)引擎(另行竞标)、以及打包产物 schema / 宿主 / 信息流的任何改动(零改,收窄影响范围)。
仍需在开工门逐条收口的待解项(诚实列出,不糊弄):
- 源项目物理归属:已收口 = 归 studio 模块(已落地——
game-module-studio下有SourceProjectApi/SourceProjectLandReqDTO/SourceProjectStatusEnum,落库挂在DifyCallbackServiceImpl外层回调,commitd57032a6)。create/modify 编排入口本就在 studio,归属与之一致,不再悬而未决。 - 后端构建桩的范围:
RuntimeBuildServiceImpl本期建议只接"消费构建产物落包"路(已通),不强求它自驱 esbuild。 - classify→archetype 映射语义:确认是"引导"而非"校验"。
- 叙事失败计入救场口径:确认 narrative 评审的
needsRepair=true等价一次九门失败计入 failCount,且评审自身 LLM 失败也计入(避免抖动卡死)。 - studio 路由形态:已收口 = 三路由已建(
/studio/{create,modify,extend}均已存在于AppStudioController,见 line 89/97/105;create一步式封装与既有/draft+/generate两步路并存,additive 未破存量)。残留子问题仅一处:create一步式与两步式的职责边界(何时用哪条)随产线化沉淀,不必整条当未决。
11. 相关文档
| 文档 | 关系 |
|---|---|
| 架构域主文档 | 上层导航:本文是"生成引擎"方向的一份子设计 |
| 产品域主文档 | 本文实现的是产品域"一句话造游戏"那一环 |
contracts/agent-loop/source-project.schema.json |
契约① 字段级定义 |
contracts/game-package.schema.json |
契约③ 既冻产物 schema |
.agents/skills/saa-graph-orchestration.md |
SAA 裸状态图编排手册 |
.agents/skills/cheap-model-game-generation.md |
便宜模型造游戏的 worker loop 与九门 harness |
纪律:本文只讲"固定游戏架构 + SAA 工作室怎么设计、为什么";字段级 schema、逐步实现清单、过程取证留在
contracts/与原始 execution 文档里,各司其职、互不重复。