Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c2064836ce | |||
| 361daf374d | |||
| dd24690b02 | |||
| 29e84a77d1 | |||
| 687bad0adf | |||
| 2111078483 | |||
| 8e6fb7d078 | |||
| 94bd7310a9 | |||
| 1d50f4fef5 | |||
| 23bb0bf131 | |||
| 758ef5ceec | |||
| ea843641e8 | |||
| 26d31d73e2 | |||
| 2a89d4c20b | |||
| a319a24cba | |||
| 2bce007a0a | |||
| 3dfb8056d0 | |||
| 337cb39340 | |||
| 6d4f7a976f | |||
| 12092ceb50 | |||
| d9151219ab | |||
| bcbf398b65 | |||
| 151c61ad08 | |||
| dc8e03988f | |||
| 79b49519b6 | |||
| ec2180f281 | |||
| 341080f42d | |||
| 45401de57e | |||
| 908d248f27 | |||
| 65598b9886 | |||
| 64ab959d4f | |||
| 9833a53e06 | |||
| 0847b1bb83 | |||
| 8f691d4c8c | |||
| d3b9f326f0 | |||
| 7eb2326a76 | |||
| 9fc28790a4 | |||
| cea45a608d | |||
| ee03ade156 | |||
| 4933c6c380 | |||
| 931f3eae0a | |||
| c5f8fa1ef6 |
@@ -32,6 +32,7 @@ Prefer `scripts/genarrative_external_api.py` for runnable REST calls. It uses on
|
||||
- Use stable references such as `objectKey`, project resource ID, or asset ID where each operation permits them. Image edit/redraw is stricter: `sourceReferenceId` accepts only a registered project resource ID or asset ID; upload confirmation alone is not enough. Use `/assets/read-url` only for temporary preview/download access.
|
||||
- Preserve both warning channels after completion. A general `warning` can coexist with `sliceWarning`; do not discard either.
|
||||
- Do not invent missing derivatives. A source-preserved warning means the main source remains usable but requested post-processing failed. A slice warning means the complete transparent sheet is usable but individual slices are absent.
|
||||
- Icon spritesheet generation accepts `sliceMode="connected-components"` (default alpha-connectivity detection) or `sliceMode="grid"`. Grid mode requires `gridX` and `gridY` (1-32); use `sliceCount` only to constrain connected-component output.
|
||||
- For successful `style="pixelArt"`, treat completed-result and nested resource/asset dimensions as the final logical-grid PNG dimensions. They may differ from `size`, `imageSize`, the provider image, and `canvasCompletion.placeholder`; do not rescale or reject the artifact to match those inputs.
|
||||
- Keep generated artifacts in the canvas and asset library together. Character animation accepts `assetFolderId` and `assetLabel`; its completed result directly returns the final `assetKind="character-animation"` resource and asset with formal sequence fields. Do not create a duplicate first-frame record.
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ Every generation row requires a stable `Idempotency-Key` header and returns HTTP
|
||||
| Image generation | `/api/external/v1/editor/images/generations` | `prompt` | `kind`, `style`, `model`, `aspectRatio`, `imageSize`, `size`, `referenceImageSrcs`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion`, `generationInputs` |
|
||||
| Image edit/redraw | `/api/external/v1/editor/images/edits` | `prompt`, `sourceReferenceId` | `referenceImageSrcs`, `model`, `size`, `projectId`, `assetFolderId`, `assetLabel`, `targetLayerId`, `canvasCompletion` |
|
||||
| Background removal | `/api/external/v1/editor/images/background-removals` | `sourceImageSrc` | `projectId`, `sourceResourceId`, `targetLayerId`, static-image `assetKind`, `assetFolderId`, `assetLabel`, `canvasCompletion`, `generationInputs` |
|
||||
| Icon spritesheet | `/api/external/v1/editor/icon-spritesheets/generations` | `referenceId`, `iconDescriptions` | `sliceLayout`, `style`, `referenceImageSrcs`, `screenColor`, `model`, `aspectRatio`, `imageSize`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion` |
|
||||
| Icon spritesheet | `/api/external/v1/editor/icon-spritesheets/generations` | `referenceId`, `iconDescriptions` | `sliceMode`, `gridX`, `gridY`, `sliceCount`, `style`, `referenceImageSrcs`, `screenColor`, `model`, `aspectRatio`, `imageSize`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion` |
|
||||
| UI asset extraction | `/api/external/v1/editor/ui-designs/assets/extractions` | `sourceImageSrc`, `aspectRatio`, `imageSize` | `screenColor`, `model`, `referenceImageSrcs`, `projectId`, `assetFolderId`, `spritesheetLabel`, `canvasCompletion` |
|
||||
| Character animation | `/api/external/v1/editor/character-animations/generations` | `sourceLayerId`, `sourceImageSrc`, `sourceWidth`, `sourceHeight`, `promptText`, `resolution`, `ratio`, `frameCount`, `durationSeconds`, `model` | `projectId`, `sourceResourceId`, `assetFolderId`, `assetLabel`, `canvasCompletion` |
|
||||
| Video generation | `/api/external/v1/editor/videos/generations` | `prompt`, `model`, `aspectRatio`, `durationSeconds`, `resolution`, `mode`, `sound` | `referenceImageSrcs`, `referenceVideoSrcs`, `referenceAudioSrcs`, `webSearchEnabled`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion` |
|
||||
@@ -94,7 +94,7 @@ For image edit/redraw, confirming an upload is not sufficient: create a project
|
||||
|
||||
The icon-spritesheet primary `referenceId` is intentionally stricter than ordinary image references: it accepts only a current-owner project resource ID or asset ID whose authoritative `assetKind` is `icon-spec`. It does not accept an `objectKey`, URL, Data URL, or Blob URL.
|
||||
|
||||
`sliceLayout: "grid-2x2"` is an opt-in contract for four fixed game-runtime assets. The provider prompt and server persistence both preserve the ordered slots left-top, right-top, left-bottom, right-bottom. Omit it to retain the default connected-component slicing behaviour for ordinary free-form icon sheets.
|
||||
`sliceMode` controls atlas splitting. Use `"connected-components"` (default) to detect independent opaque regions by alpha connectivity, or `"grid"` with positive `gridX` and `gridY` values (maximum 32 each). `sliceCount` optionally constrains the connected-component result.
|
||||
|
||||
## Common Values
|
||||
|
||||
|
||||
@@ -79,9 +79,9 @@ Keep the existing autonomous-build task graph. Do not add a parallel task system
|
||||
|
||||
1. `art-director` generates `assets/art-spec.png` with image generation, `kind: "spec"`, then registers it as `assetKind: "icon-spec"`. This image is the authoritative visual spec; `generationInputs.artSpec` is supporting structured context.
|
||||
2. `design-foundation` generates `assets/ui-prototype.png` with `kind: "ui-design"`, using the registered art-spec resource ID in `referenceImageSrcs`.
|
||||
3. `art-asset-plan` generates transparent `assets/art-spritesheet.png` through icon spritesheet generation, using the same registered art-spec resource ID as `referenceId` plus concrete `iconDescriptions`. For the four-category game contract it must also send `sliceLayout: "grid-2x2"`; this is an explicit fixed-slot contract, not a client-side guessed crop.
|
||||
3. `art-asset-plan` generates transparent `assets/art-spritesheet.png` through icon spritesheet generation, using the same registered art-spec resource ID as `referenceId` plus concrete `iconDescriptions`. For a fixed four-category game contract it may send `sliceMode: "grid"`; for free-form assets use `sliceMode: "connected-components"` (the default).
|
||||
|
||||
For a playable Canvas game, do not stop at generation. Make `code-prototype` depend on `art-asset-plan` and consume the persisted `iconImageSrcs` slices for core players, blocks or targets, scene obstacles, and feedback. For the four-category game-chat contract, require response `sliceLayout: "grid-2x2"` and exactly four slices before registering the local runtime sheet; both fewer and extra components fail closed. Treat `art-spec.png` as reference-only. A full-sheet `<img>`, CSS background, path-only mention, guessed equal-grid crop, or code-drawn replacement for core entities is not runtime asset use. If slicing produces `sliceWarning`, keep the complete transparent sheet as a valid editor artifact, but fail the playable game asset gate until real slice files or verified atlas coordinates exist; never invent coordinates or replace the icon-spritesheet route with ordinary image generation.
|
||||
For a playable Canvas game, do not stop at generation. Make `code-prototype` depend on `art-asset-plan` and consume the persisted `iconImageSrcs` slices for core players, blocks or targets, scene obstacles, and feedback. When using the fixed four-category contract, require response `sliceMode: "grid"` and exactly four slices before registering the local runtime sheet; both fewer and extra components fail closed. Treat `art-spec.png` as reference-only. A full-sheet `<img>`, CSS background, path-only mention, guessed equal-grid crop, or code-drawn replacement for core entities is not runtime asset use. If slicing produces `sliceWarning`, keep the complete transparent sheet as a valid editor artifact, but fail the playable game asset gate until real slice files or verified atlas coordinates exist; never invent coordinates or replace the icon-spritesheet route with ordinary image generation.
|
||||
|
||||
Never use `assets/ui-prototype.png` as the spritesheet visual-spec reference. UI extraction is outside this canonical DAG.
|
||||
|
||||
|
||||
@@ -881,12 +881,14 @@ def _self_test() -> None:
|
||||
["蛇头向上", "蛇身直线", "转角", "尾部", "四类食物"],
|
||||
canvasSession=session,
|
||||
assetLabel="贪吃蛇透明图集",
|
||||
sliceMode="connected-components",
|
||||
referenceId="must-not-override-explicit-reference",
|
||||
iconDescriptions=["不得覆盖显式图标描述"],
|
||||
)
|
||||
assert calls[0]["path"] == "/api/external/v1/editor/icon-spritesheets/generations"
|
||||
assert calls[0]["body"]["referenceId"] == "editor-resource-spec"
|
||||
assert calls[0]["body"]["screenColor"] == "auto"
|
||||
assert calls[0]["body"]["sliceMode"] == "connected-components"
|
||||
assert calls[0]["body"]["iconDescriptions"][0] == "蛇头向上"
|
||||
assert calls[1]["path"] == "/api/external/v1/generations/task-operation-demo"
|
||||
print("self-test ok")
|
||||
|
||||
+10
-5
@@ -1,8 +1,8 @@
|
||||
# Server-side OpenAI-compatible LLM endpoint base URL.
|
||||
LLM_BASE_URL="https://api.vectorengine.cn/v1"
|
||||
LLM_BASE_URL="https://api.tiantoken.com/v1"
|
||||
|
||||
# Server-side API key used by the local Vite proxy.
|
||||
# Recommended: set `LLM_API_KEY` locally, or use `VECTOR_ENGINE_API_KEY`
|
||||
# Recommended: set `LLM_API_KEY` locally, or use `TIANTOKEN_API_KEY`
|
||||
# through the Rust api-server proxy.
|
||||
# Legacy compatibility: `VITE_LLM_API_KEY` is still supported by the proxy,
|
||||
# but it should not be relied on by browser code.
|
||||
@@ -122,7 +122,7 @@ WECHAT_MINIPROGRAM_MESSAGE_ENCODING_AES_KEY=""
|
||||
# Model name for chat completions.
|
||||
VITE_LLM_MODEL="gpt-5.4-mini"
|
||||
GENARRATIVE_LLM_PROVIDER="openai-compatible"
|
||||
GENARRATIVE_LLM_BASE_URL="https://api.vectorengine.cn/v1"
|
||||
GENARRATIVE_LLM_BASE_URL="https://api.tiantoken.com/v1"
|
||||
GENARRATIVE_LLM_API_KEY=""
|
||||
GENARRATIVE_LLM_MODEL="gpt-5.4-mini"
|
||||
|
||||
@@ -130,10 +130,15 @@ GENARRATIVE_LLM_MODEL="gpt-5.4-mini"
|
||||
DASHSCOPE_BASE_URL="https://dashscope.aliyuncs.com/api/v1"
|
||||
DASHSCOPE_API_KEY="YOUR_DASHSCOPE_API_KEY"
|
||||
|
||||
# VectorEngine LLM and GPT-image-2 / Gemini image generation config.
|
||||
# Tiantoken LLM and GPT-image-2 / Gemini image generation config.
|
||||
TIANTOKEN_BASE_URL="https://api.tiantoken.com"
|
||||
TIANTOKEN_API_KEY=""
|
||||
TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS="1000000"
|
||||
|
||||
# VectorEngine is retained for Suno audio generation only.
|
||||
VECTOR_ENGINE_BASE_URL="https://api.vectorengine.cn"
|
||||
VECTOR_ENGINE_API_KEY=""
|
||||
VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS="1000000"
|
||||
VECTOR_ENGINE_AUDIO_REQUEST_TIMEOUT_MS="180000"
|
||||
|
||||
# ElevenLabs editor sound-effect generation is server-side only.
|
||||
ELEVENLABS_BASE_URL="https://api.elevenlabs.io"
|
||||
|
||||
@@ -957,9 +957,6 @@ async function runInteractiveCargo(cliArguments, setActiveChild) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 立项策划跑 standard 档,`agent.delegate` 这类动作按项目权限策略必须逐个确认,
|
||||
// 而确认和问询都只从 CLI 的 stdin 读。自主构建档没有这一步,所以只有 --plan 需要
|
||||
// 一个把「人坐在终端前敲 approve」自动化掉的应答器;判据本身仍然走后端确认命令。
|
||||
const swarmConfirmationPromptPattern = /输入 approve 或 reject:$/u;
|
||||
const swarmUserInputPromptPattern = /请选择 1-\d+,或直接输入其他答案:$/u;
|
||||
|
||||
|
||||
@@ -1295,7 +1295,7 @@ for (const requiredSource of [
|
||||
}
|
||||
}
|
||||
|
||||
if (tauriConfig.productName !== 'Genarrative AI Game Creator') {
|
||||
if (tauriConfig.productName !== '陶泥儿') {
|
||||
throw new Error('AI game creator shell productName drifted');
|
||||
}
|
||||
|
||||
@@ -1307,19 +1307,19 @@ const expectedBundledDesignAgentResources = {
|
||||
'design-agent': 'design-agent',
|
||||
};
|
||||
const expectedBundledWindowsResources = {
|
||||
'resources/codex/win-x64/bin/codex.exe': 'codex/win-x64/bin/codex.exe',
|
||||
'resources/codex/win-x64/bin/codex.exe': 'coding-agent/win-x64/bin/codex.exe',
|
||||
'resources/codex/win-x64/bin/codex-code-mode-host.exe':
|
||||
'codex/win-x64/bin/codex-code-mode-host.exe',
|
||||
'coding-agent/win-x64/bin/codex-code-mode-host.exe',
|
||||
'resources/codex/win-x64/codex-path/rg.exe':
|
||||
'codex/win-x64/codex-path/rg.exe',
|
||||
'coding-agent/win-x64/codex-path/rg.exe',
|
||||
'resources/codex/win-x64/codex-resources/codex-command-runner.exe':
|
||||
'codex/win-x64/codex-resources/codex-command-runner.exe',
|
||||
'coding-agent/win-x64/codex-resources/codex-command-runner.exe',
|
||||
'resources/codex/win-x64/codex-resources/codex-windows-sandbox-setup.exe':
|
||||
'codex/win-x64/codex-resources/codex-windows-sandbox-setup.exe',
|
||||
'coding-agent/win-x64/codex-resources/codex-windows-sandbox-setup.exe',
|
||||
'resources/codex/win-x64/codex-package.json':
|
||||
'codex/win-x64/codex-package.json',
|
||||
'resources/codex/win-x64/NOTICE.md': 'codex/win-x64/NOTICE.md',
|
||||
'resources/codex/win-x64/manifest.json': 'codex/win-x64/manifest.json',
|
||||
'coding-agent/win-x64/codex-package.json',
|
||||
'resources/codex/win-x64/NOTICE.md': 'coding-agent/win-x64/NOTICE.md',
|
||||
'resources/codex/win-x64/manifest.json': 'coding-agent/win-x64/manifest.json',
|
||||
'resources/plugins': 'plugins',
|
||||
};
|
||||
assert.deepEqual(
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -74,7 +74,7 @@ description: 写游戏策划案(GDD)系统架构时使用。在顶层设计
|
||||
三个接口:**对上**承顶层系统范围表并跑循环覆盖检查;**对内**地图↔职责↔依赖
|
||||
三方一致、主数据归属唯一;**对下**目录映射 + MVP 闭环喂系统文档站。
|
||||
|
||||
## 四、怎么写(模板即流程,十二节按序)
|
||||
## 四、怎么写(模板参考结构,建议按此组织)
|
||||
(本节是带写法要领的教学版;实际填写的纯净模板在 templates/architecture.md)
|
||||
|
||||
### 1. 架构定位与目标
|
||||
@@ -85,7 +85,7 @@ description: 写游戏策划案(GDD)系统架构时使用。在顶层设计
|
||||
→ 没有变更记录的架构文档,第二轮迭代就会变成黑箱。
|
||||
|
||||
### 2. 系统地图
|
||||
Sxx 编号清单(核心系统 2~12 个)+ 支撑层(存档/UI,不拥有核心规则)。
|
||||
Sxx 编号清单(核心系统通常 1-5 个,有明确要求可超出 5 个)+ 支撑层(存档/UI,不拥有核心规则)。
|
||||
P0 段五列表:
|
||||
| 系统 | 目的 | 输入 | 输出 | P0 原因 |
|
||||
→ 每行 P0 原因必须答"删了它,__ 塌";答不出的降级或合并。
|
||||
|
||||
@@ -69,7 +69,7 @@ description: 写游戏策划案(GDD)概念层时使用。把一句话游戏
|
||||
记住三个接口:**对内**锚点仲裁一切;**对下**张力变取舍表、定稿变硬约束;
|
||||
**对上**边界画线防止越层。九节不是清单,是一台咬合的机器。
|
||||
|
||||
## 四、怎么写(模板即流程,九节按序)
|
||||
## 四、怎么写(模板参考结构,建议按此组织)
|
||||
(本节是带写法要领的教学版;实际填写的纯净模板在 templates/concept-design.md)
|
||||
|
||||
### 1. 一句话概念
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
---
|
||||
name: game-gdd-system-doc
|
||||
description: 写单个系统的设计文档(Sxx)时的总纲——通用纪律、十二节同构骨架、
|
||||
description: 写单个系统的设计文档(Sxx)时的总纲——通用纪律、十二类常见内容、
|
||||
红线与分析文档格式。每类系统的专属写法与模板在 modules/system-types/ 下对应目录的 SKILL.md
|
||||
与对应模块的模板.md 里,按需取用。
|
||||
---
|
||||
@@ -26,9 +26,9 @@ description: 写单个系统的设计文档(Sxx)时的总纲——通用纪
|
||||
1. 架构已定稿:找到本系统的 Sxx 编号、职责表行、依赖方向——这是合同。
|
||||
2. 在 01~12 文件夹里选最接近的系统类型(可组合,如"钓鱼"=05 采集+06 战斗
|
||||
的判定部分),读取对应的 `SKILL.md` 与 `模板.md`。
|
||||
3. 该文件夹标注"必读例子"的,先读例子全文做密度锚。
|
||||
3. 该文件夹标注"参考例子"的,可先读例子了解写法。
|
||||
|
||||
## 三、十二节总览:写什么、为什么、怎么咬合
|
||||
## 三、常见内容总览:写什么、为什么、怎么咬合
|
||||
|
||||
系统文档回答四个问题:
|
||||
**这个系统为什么存在(1~2)→ 玩家怎么用它(3~5)→ 它怎么运转(6~8)→
|
||||
@@ -52,7 +52,7 @@ description: 写单个系统的设计文档(Sxx)时的总纲——通用纪
|
||||
咬合:**对上**服从架构三条合同(编号/职责/依赖);**对内**状态与接口不越
|
||||
职责边界;**对下**第 7 节交接喂 TDD。
|
||||
|
||||
## 四、十二节通用写法
|
||||
## 四、常见内容的参考写法
|
||||
(各系统类型的特殊写法见对应文件夹 SKILL.md;纯净模板在其 模板.md)
|
||||
|
||||
1 系统目的:若删除它,__ 会塌——一句话说不出 = 该系统不该存在。
|
||||
@@ -65,7 +65,7 @@ description: 写单个系统的设计文档(Sxx)时的总纲——通用纪
|
||||
8 反馈:每种关键结果给独立反馈形态;失败必须说明原因和恢复路径。
|
||||
9 内部循环:动词链;可拆单次/区域/长期三层。
|
||||
10 输入输出与依赖:引用具名系统与具名数据,禁泛称"资源"。
|
||||
11 边界与非目标:照该类型 skill 的"三不"写全;必含"字段数值归 TDD"一条。
|
||||
11 边界与非目标:参考该类型 skill 的“三不”说明边界;建议说明字段与数值的交接边界。
|
||||
12 开放问题:结构级才留;手感数值类标"待原型验证"。
|
||||
|
||||
## 五、分析文档(全局一份,按层分节)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -80,7 +80,7 @@ description: 写游戏策划案(GDD)顶层设计时使用。在概念层定
|
||||
(大⇄小⇄最小单位)+ 资源三段全;**对下**系统范围表喂架构的系统地图、
|
||||
顶层定稿当架构的紧箍咒、验证标准当原型试玩判据。
|
||||
|
||||
## 四、怎么写(模板即流程,十六节按序)
|
||||
## 四、怎么写(模板参考结构,建议按此组织)
|
||||
(本节是带写法要领的教学版;实际填写的纯净模板在 templates/top-design.md)
|
||||
|
||||
### 1. 顶层定位与规模锚点
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
|
||||
你是游戏策划协作 Agent,与用户持续协作完成游戏设计。像普通策划同事一样交流,使用工作区文件工具读写资料;所有文件路径使用相对路径。根据当前对话、阶段上下文和已有文档决定下一步行动。修改文件后,简要说明修改内容和相对路径。对不确定内容区分用户确认、Agent 建议和待原型验证事项;不要把建议写成用户已确认的决定。
|
||||
|
||||
优先完成能够依据已有信息推进的工作,不要为每个设计空白都询问用户。局部、可逆的问题可以先提出合理方案并标为暂定。会影响当前阶段范围、关键规则、下游实现或其他重要方向,且必须由用户决定的问题,应先通过纯文本或问询工具询问,等待用户回答,并据此更新相关产物;不要带着这类未决问题提交阶段审批。
|
||||
优先完成能够依据已有信息推进的工作,不要为每个设计空白都询问用户。局部、可逆的问题可以先提出合理方案并标为暂定。会影响当前阶段范围、关键规则、下游实现或其他重要方向,且必须由用户决定的问题,应先通过纯文本或问询工具询问,等待用户回答。决定稳定后,再更新受影响的正式产物和必要的过程记录;不要带着这类未决问题提交阶段审批。
|
||||
|
||||
分析阶段优先记录当前目标、上层约束、候选方案、取舍、用户已确认或 Agent 暂定的边界,以及必须检查的验收项。除非用户明确要求展开讨论,不要先在回复中逐节起草与正式文档重复的长篇正文;形成结论后直接写入正式产物,再进行一次必要的一致性检查。文件操作前只需说明简短计划、目标文件和主要变化。
|
||||
|
||||
正式策划文档在文档头部写明版本标记,例如“版本:v1”。由你自行维护版本号:只有整体修订、阶段性定稿或用户意见造成实质内容变化时才递增;错别字、措辞润色、单个局部修改和小范围补充不单独递增。
|
||||
|
||||
阶段审批是每个阶段的最终检查,表示本阶段产物已经完成,无未决内容,交给用户做最终检阅,不承担问询功能。提交前,解决所有影响本阶段完成的关键问题,或明确说明它们不阻塞本阶段交付,并更新相关产物。可以保留不阻塞当前阶段的后续事项和待原型验证项。
|
||||
|
||||
过程文档用于记录关键依据、决定和待办,不要求实时完整,也不应重复正式设计文档。阶段内优先完成主要设计内容;只有稳定且影响后续工作的决定才需要同步到多个过程文档。阶段提交前,补齐影响验收的关键记录。
|
||||
|
||||
阶段获批后,产物中已经采用的方案作为后续工作的依据,并保留原有决策来源。除非用户主动质疑或出现新的约束冲突,不要反复要求确认历史暂定决定。
|
||||
|
||||
用户说“继续”时,继续推进当前阶段最有价值的工作。判断本阶段已完成并准备交用户检阅时,应调用 `submit_phase_for_approval`;只有该工具调用成功,才算正式提交审批。
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{"type":"function","function":{"name":"get_workflow_status","description":"读取当前策划工作流状态,只返回阶段列表、当前阶段、已批准阶段和待审批阶段;不推进阶段、不提交审批、不修改文件。","parameters":{"type":"object","properties":{},"additionalProperties":false}}},
|
||||
{"type":"function","function":{"name":"list_resources","description":"列出固定资源的逻辑目录、资源 ID、标题和简介。资源是只读的随包文档;不要猜测物理路径。","parameters":{"type":"object","properties":{},"additionalProperties":false}}},
|
||||
{"type":"function","function":{"name":"read_resource","description":"读取一份固定资源文档全文。每次读取一个 resource_id;资源只读。读到未实现占位文档时由你自行判断和处理。","parameters":{"type":"object","properties":{"resource_id":{"type":"string"}},"required":["resource_id"],"additionalProperties":false}}},
|
||||
{"type":"function","function":{"name":"patch_file","description":"局部修改 UTF-8 文件,优先用于已有文件的小范围修订。先读文件,以唯一且非空的 old_text 精确匹配并替换为 new_text;new_text 为空可删除片段,保留原文并追加可插入。匹配失败不修改文件。path 使用相对路径。","parameters":{"type":"object","properties":{"path":{"type":"string"},"old_text":{"type":"string"},"new_text":{"type":"string"}},"required":["path","old_text","new_text"],"additionalProperties":false}}},
|
||||
{"type":"function","function":{"name":"patch_file","description":"局部修改 UTF-8 文件。使用 old_text/new_text,或使用 edits 一次进行多个独立替换;每个 old_text 必须非空且在原文件中唯一,匹配失败、重复或范围重叠时不修改文件。path 使用相对路径。","parameters":{"type":"object","properties":{"path":{"type":"string"},"old_text":{"type":"string"},"new_text":{"type":"string"},"edits":{"type":"array","items":{"type":"object","properties":{"old_text":{"type":"string"},"new_text":{"type":"string"}},"required":["old_text","new_text"],"additionalProperties":false}}},"required":["path"],"additionalProperties":false}}},
|
||||
{"type":"function","function":{"name":"delete_path","description":"谨慎使用;永久删除工作区内的文件或目录;目录会连同全部内容递归删除,不备份。先确认目标及删除范围。path 使用相对路径,不能删除工作区根目录,也不能经过链接。","parameters":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"],"additionalProperties":false}}},
|
||||
{"type":"function","function":{"name":"list_dir","description":"列出工作目录内的文件和目录。path 使用相对路径。","parameters":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"],"additionalProperties":false}}},
|
||||
{"type":"function","function":{"name":"read_file","description":"读取工作目录内的 UTF-8 文本文件。path 使用相对路径。","parameters":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"],"additionalProperties":false}}},
|
||||
|
||||
@@ -2,8 +2,4 @@
|
||||
|
||||
需要等待专业 Agent 时不得调用 respond_to_user;Runtime 会通过 delegate/all-join 完成屏障保持同一父 run,取得 readyDelegateReceipts 或 readyIsolatedJoins 后直接整合结果。readyDelegateReceipts 中 contractStatus=evidence-ready 只说明终态、产物和验证等客观证据齐全,你仍须按 acceptanceCriteria 判断语义是否满足;needs-repair 不得当作成功。contractStatus=needs-user-input 时,Runtime 会按原 delivery 逐一发起 user.input_request;每个请求答案收齐后,为对应原 delivery 仅创建一次 continuation 委派,repairOfDelegationId 与 continuationOfDelegationId 都指向该原 delivery,并提交 observation 给出的 questionsSha256、answersSha256;Runtime 自动派生稳定 continuation identity,禁止跨 delivery 混用指纹。客观或语义不满足时可以发起一次新 agent.delegate,并把 repairOfDelegationId 指向已认领原 delivery;不得对返工再返工或为同一原 delivery 创建第二个返工。专业结果冲突且无法依据用户目标裁决时,合并问题后用一次 user.input_request 询问用户。只有实现路径、产品取舍或缺失事实会实质改变结果时才调用 user.input_request;项目内可读取事实、权限确认和工具失败不得伪装成用户问题。
|
||||
|
||||
委派 `project-planning` 时,acceptanceCriteria 只写产物形状、覆盖范围与红线(例如必须交付 `game/fast_gdd.md`、必须原创、必须只定义一个 MVP 闭环),**不得替用户预先裁定产品取舍**。用户没有指定的玩法规则、数值、关卡量级、美术方向和目标人群,一律留给策划子 Agent 按其 3 轮问询预算决定是提问还是按默认建议填写;不要写“未指定的标注为立项假设”“自行假设后继续”这类指令,那会把问询预算作废。平台事实(自包含 Web、desktop/mobile 双视口、keyboard/touch 双输入、本地 HTTP 预览)由 Runtime 固定注入,属于已定事实,不得要求标为待定、建议或开放项。
|
||||
|
||||
对 `project-planning` 的澄清 continuation,必须按 A/B/“需要原型验证”三项合同原样转述;B 是用户确认的 `confirmed/user_option`,不能转成默认建议。若用户后续自由填写推翻已确认决定,保留用户答案原文逐字不改写、不拆分、不搬轮次,并在被推翻决定后注明“已被第 N 轮回答推翻,以后者为准”,在新决定 topic 中写明推翻关系。
|
||||
|
||||
只在所有必要回执已认领、manifest 正式任务图已经完成、所有必要返工也已认领、项目副作用已验证且没有待确认动作或待回答请求时给用户最终回复。不要向用户暴露内部 task/event、工具计划、动态 child ID 或调试状态。
|
||||
|
||||
+7
-1
@@ -19,8 +19,14 @@ Use this Skill as the top-level SOP for a new game or a substantial game brief.
|
||||
|
||||
## Stage transitions
|
||||
|
||||
Advance only when the current stage has its output: brief → inventory; inventory → art decision; art decision → usable registered assets or an explicit no-art decision; implementation → source references to those assets; build → playable entry; playtest → evidence; delivery → truthful report. If a tool fails, preserve its error and stop or repair at that stage instead of silently substituting a later-stage placeholder.
|
||||
Advance only when the current stage has its output: brief → inventory; inventory → art decision; art decision → usable registered assets or an explicit no-art decision; implementation → source references to those assets; build → playable entry; playtest → evidence; delivery → truthful report. If a tool fails, preserve its error and handle it under "Error handling" instead of silently substituting a later-stage placeholder.
|
||||
|
||||
For a small edit to an existing game where the brief and suitable assets are unchanged, use the focused edit path and do not regenerate art. This exception does not apply to a new game or a substantial planning brief.
|
||||
|
||||
## Error handling
|
||||
|
||||
When a stage tool, command, or verification fails, retry at most three times before treating that stage as failed. Keep the retries serial and scoped to the same stage and the same input: a retry must not open a parallel path, skip ahead to a later stage, or substitute a placeholder for the missing output.
|
||||
|
||||
Only after the third attempt also fails, stop and tell the user the failure reason — which stage failed, which tool or command reported the error, what the error says, and what is still missing. A stage whose three attempts never succeeded is not complete, and its missing output cannot be reported as delivered.
|
||||
|
||||
Read the referenced specialist Skills for their detailed contracts: `agc-project-structure`, `taonier-art-assets`, `agc-web-game-development`, `agc-client-projection`, and `agc-browser-playtest`.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schemaVersion": "agc-skill-pack.v1",
|
||||
"version": "2026-08-26.13",
|
||||
"version": "2026-08-26.14",
|
||||
"skills": [
|
||||
{
|
||||
"name": "agc-game-production-workflow",
|
||||
@@ -22,7 +22,7 @@
|
||||
"agents/openai.yaml",
|
||||
"references/workflow-contract.md"
|
||||
],
|
||||
"sha256": "91082fdff4123f1e1fcf930af433cbea51a8c9d26991678b19028b344ea49f39"
|
||||
"sha256": "d9d8e7e0a6bc512e0b463e0e4bd77edee1cc57f4a6965c9553e0920e38985d5c"
|
||||
},
|
||||
{
|
||||
"name": "agc-project-structure",
|
||||
|
||||
@@ -19,6 +19,12 @@ image, UI design image, or publication material; use `agc_edit_image` for an
|
||||
edit of an existing registered image; use `taonier_prepare_game_art` only for
|
||||
the complete game-art package and its canonical slices.
|
||||
|
||||
When `agc_generate_image` is used with `kind="art-spritesheet"`, pass
|
||||
`sliceMode="connected-components"` (the default alpha-connectivity splitter)
|
||||
or `sliceMode="grid"` with `gridX` and `gridY` (1-32 each). The selected mode is carried
|
||||
through the client request and returned result; do not infer it from the number
|
||||
of slices.
|
||||
|
||||
## Authorization boundary
|
||||
|
||||
`agc_tools` is an AGC client-owned bridge to the AGC backend. In the normal client build it uses the current client login session and account routes; the user and model never need to provide, configure, paste, create, or rotate an API Key, Token, Cookie, URL, or `.env` value. If the tool returns `401` or `403`, report only that the AGC client login or permission state is unavailable, stop the operation, and do not ask the user for credentials or expose an internal URL.
|
||||
|
||||
+1
@@ -15,6 +15,7 @@
|
||||
- On timeout or uncertain delivery, reuse the recorded operation; never create a replacement request.
|
||||
- `postprocess-failed-source-preserved` means the complete provider source remains usable, but the requested transparent derivative is absent.
|
||||
- `sliceWarning` means the complete transparent sheet remains usable, but individual slices are absent.
|
||||
- For direct `agc_generate_image` spritesheet requests, `sliceMode="connected-components"` selects alpha-connectivity detection and `sliceMode="grid"` uses the caller-provided `gridX` and `gridY` (1-32 each). The client preserves the selected mode and grid dimensions in the request identity and result metadata.
|
||||
- General and slice warnings can coexist. The tool returns them separately through `warnings` and `sliceWarnings`; callers must preserve every entry and must not downgrade a slice warning into a successful independent-asset claim.
|
||||
- `assetPaths` contains the complete package paths. `slicePaths` contains only slices that the client downloaded, validated, and registered with their platform source identities.
|
||||
- `resources` contains only safe registered identity fields: local asset/path/kind/media type, Canvas project/resource/asset/task IDs, and reference resource IDs. It never exposes prompts, models, provider routes, absolute paths, URLs, tokens, cookies, or API keys.
|
||||
|
||||
@@ -28,6 +28,7 @@ mod prompt;
|
||||
mod runtime_actions;
|
||||
mod runtime_adapter;
|
||||
mod runtime_driver;
|
||||
mod runtime_error;
|
||||
mod runtime_protocol;
|
||||
mod runtime_state;
|
||||
mod runtime_tools;
|
||||
@@ -56,6 +57,7 @@ pub(crate) use prompt::*;
|
||||
pub(crate) use runtime_actions::*;
|
||||
pub(crate) use runtime_adapter::*;
|
||||
pub(crate) use runtime_driver::*;
|
||||
pub(crate) use runtime_error::*;
|
||||
pub(crate) use runtime_protocol::*;
|
||||
pub(crate) use runtime_state::*;
|
||||
pub(crate) use runtime_tools::*;
|
||||
|
||||
@@ -271,6 +271,51 @@ fn game_creator_codex_app_server_error_kind(kind: &str) -> platform_llm::LlmErro
|
||||
))
|
||||
}
|
||||
|
||||
fn game_creator_codex_app_server_error_kind_with_machine_detail(
|
||||
kind: &str,
|
||||
error: &serde_json::Value,
|
||||
) -> platform_llm::LlmError {
|
||||
let mut fields = Vec::new();
|
||||
if let Some(object) = error.as_object() {
|
||||
if let Some(code) = object.get("code").and_then(serde_json::Value::as_str) {
|
||||
if !code.is_empty()
|
||||
&& code.len() <= 80
|
||||
&& code
|
||||
.bytes()
|
||||
.all(|byte| byte.is_ascii_alphanumeric() || b"._-".contains(&byte))
|
||||
{
|
||||
fields.push(format!("code={code}"));
|
||||
}
|
||||
}
|
||||
let keys = object
|
||||
.keys()
|
||||
.filter(|key| {
|
||||
matches!(
|
||||
key.as_str(),
|
||||
"httpConnectionFailed"
|
||||
| "responseStreamConnectionFailed"
|
||||
| "responseStreamDisconnected"
|
||||
| "responseTooManyFailedAttempts"
|
||||
| "activeTurnNotSteerable"
|
||||
| "codexErrorInfo"
|
||||
)
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
if !keys.is_empty() {
|
||||
fields.push(format!("fields={}", keys.join(",")));
|
||||
}
|
||||
}
|
||||
let suffix = if fields.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" detail={}", fields.join(" "))
|
||||
};
|
||||
platform_llm::LlmError::InvalidRequest(format!(
|
||||
"{GAME_CREATOR_CODEX_APP_SERVER_ERROR_KIND_PREFIX}{kind}{suffix}"
|
||||
))
|
||||
}
|
||||
|
||||
fn game_creator_codex_app_server_error_http_status(
|
||||
info: &serde_json::Value,
|
||||
field: &str,
|
||||
@@ -425,7 +470,7 @@ fn game_creator_codex_app_server_failed_turn_error(
|
||||
return game_creator_codex_app_server_error_kind("unauthorized");
|
||||
}
|
||||
let Some(info) = error.get("codexErrorInfo").filter(|info| !info.is_null()) else {
|
||||
return game_creator_codex_app_server_error_kind("other");
|
||||
return game_creator_codex_app_server_error_kind_with_machine_detail("other", error);
|
||||
};
|
||||
if let Some(kind) = info.as_str() {
|
||||
return match kind {
|
||||
@@ -449,8 +494,8 @@ fn game_creator_codex_app_server_failed_turn_error(
|
||||
game_creator_codex_app_server_error_kind("thread-rollback-failed")
|
||||
}
|
||||
"sandboxError" => game_creator_codex_app_server_error_kind("sandbox-error"),
|
||||
"other" => game_creator_codex_app_server_error_kind("other"),
|
||||
_ => game_creator_codex_app_server_error_kind("other"),
|
||||
"other" => game_creator_codex_app_server_error_kind_with_machine_detail("other", error),
|
||||
_ => game_creator_codex_app_server_error_kind_with_machine_detail("other", error),
|
||||
};
|
||||
}
|
||||
for field in [
|
||||
@@ -466,7 +511,7 @@ fn game_creator_codex_app_server_failed_turn_error(
|
||||
if info.get("activeTurnNotSteerable").is_some() {
|
||||
return game_creator_codex_app_server_error_kind("active-turn-not-steerable");
|
||||
}
|
||||
game_creator_codex_app_server_error_kind("other")
|
||||
game_creator_codex_app_server_error_kind_with_machine_detail("other", error)
|
||||
}
|
||||
|
||||
async fn isolate_game_creator_codex_app_server_terminal_unknown(
|
||||
|
||||
@@ -6,8 +6,9 @@ use sha2::{Digest, Sha256};
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
const GAME_CREATOR_CODEX_CLI_EXECUTABLE: &str = "codex";
|
||||
const GAME_CREATOR_BUNDLED_CODEX_CLI_RELATIVE_PATH: &str = "codex/win-x64/bin/codex.exe";
|
||||
const GAME_CREATOR_BUNDLED_CODEX_CLI_MANIFEST_RELATIVE_PATH: &str = "codex/win-x64/manifest.json";
|
||||
const GAME_CREATOR_BUNDLED_CODEX_CLI_RELATIVE_PATH: &str = "coding-agent/win-x64/bin/codex.exe";
|
||||
const GAME_CREATOR_BUNDLED_CODEX_CLI_MANIFEST_RELATIVE_PATH: &str =
|
||||
"coding-agent/win-x64/manifest.json";
|
||||
const GAME_CREATOR_BUNDLED_CODEX_CLI_REQUIRED_FILES: [&str; 6] = [
|
||||
"bin/codex.exe",
|
||||
"bin/codex-code-mode-host.exe",
|
||||
|
||||
@@ -997,15 +997,10 @@ pub(crate) async fn continue_design_agent_at(
|
||||
.ok_or("策划 Agent 当前正在工作")?;
|
||||
let mut session = match read_design_session(root)? {
|
||||
Some(session) => session,
|
||||
None => {
|
||||
if read_planning_session_v2(root)?.is_some() {
|
||||
return Err("此项目包含旧策划会话,请查看原有记录或在新项目开始五阶段策划".into());
|
||||
}
|
||||
new_design_session(
|
||||
&project_id,
|
||||
&load_game_creator_app_config()?.selected_model_id,
|
||||
)
|
||||
}
|
||||
None => new_design_session(
|
||||
&project_id,
|
||||
&load_game_creator_app_config()?.selected_model_id,
|
||||
),
|
||||
};
|
||||
if session.project_id != project_id {
|
||||
return Err("策划会话与当前项目不匹配".into());
|
||||
|
||||
@@ -264,22 +264,45 @@ pub(crate) fn execute_design_file_tool(
|
||||
}
|
||||
"patch_file" => {
|
||||
let relative = required_tool_path(args)?;
|
||||
let old = args
|
||||
.get("old_text")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or("缺少 old_text")?;
|
||||
let new = args
|
||||
.get("new_text")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or("缺少 new_text")?;
|
||||
if old.is_empty() {
|
||||
return Err("old_text 不能为空".to_string());
|
||||
}
|
||||
let edits = if let Some(items) = args.get("edits").and_then(Value::as_array) {
|
||||
if items.is_empty() {
|
||||
return Err("edits 不能为空".to_string());
|
||||
}
|
||||
items
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, item)| {
|
||||
let old = item
|
||||
.get("old_text")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| format!("edits[{index}].old_text 必须是字符串"))?;
|
||||
let new = item
|
||||
.get("new_text")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| format!("edits[{index}].new_text 必须是字符串"))?;
|
||||
if old.is_empty() {
|
||||
return Err(format!("edits[{index}].old_text 不能为空"));
|
||||
}
|
||||
Ok((old.to_string(), new.to_string()))
|
||||
})
|
||||
.collect::<Result<Vec<_>, String>>()?
|
||||
} else {
|
||||
let old = args
|
||||
.get("old_text")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or("缺少 old_text")?;
|
||||
let new = args
|
||||
.get("new_text")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or("缺少 new_text")?;
|
||||
if old.is_empty() {
|
||||
return Err("old_text 不能为空".to_string());
|
||||
}
|
||||
vec![(old.to_string(), new.to_string())]
|
||||
};
|
||||
let (display, path) = resolve_design_workspace_path(root, &relative)?;
|
||||
if !path.is_file() {
|
||||
return Ok(Value::String(format!(
|
||||
"局部修改失败:文件不存在:{display}"
|
||||
)));
|
||||
return Err(format!("文件不存在:{display}"));
|
||||
}
|
||||
let content =
|
||||
fs::read_to_string(&path).map_err(|error| format!("读取失败:{error}"))?;
|
||||
@@ -288,20 +311,52 @@ pub(crate) fn execute_design_file_tool(
|
||||
} else {
|
||||
"\n"
|
||||
};
|
||||
let old = old.replace("\r\n", "\n").replace('\n', newline);
|
||||
let new = new.replace("\r\n", "\n").replace('\n', newline);
|
||||
let count = content.matches(&old).count();
|
||||
if count != 1 {
|
||||
return Err(format!(
|
||||
"原文匹配 {count} 处,需要唯一匹配;请重新读取文件并扩大匹配范围"
|
||||
));
|
||||
let normalized = edits
|
||||
.into_iter()
|
||||
.map(|(old, new)| {
|
||||
(
|
||||
old.replace("\r\n", "\n").replace('\n', newline),
|
||||
new.replace("\r\n", "\n").replace('\n', newline),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut matches = Vec::new();
|
||||
for (index, (old, new)) in normalized.iter().enumerate() {
|
||||
let count = content.matches(old).count();
|
||||
if count == 0 {
|
||||
return Err(format!("edits[{index}] 原文未找到:{display}"));
|
||||
}
|
||||
if count != 1 {
|
||||
return Err(format!(
|
||||
"edits[{index}] 原文匹配 {count} 处,必须唯一:{display}"
|
||||
));
|
||||
}
|
||||
let start = content.find(old).expect("count checked");
|
||||
let end = start + old.len();
|
||||
if let Some((other_index, _other_start, _other_end)) = matches
|
||||
.iter()
|
||||
.find(|(_, other_start, other_end)| start < *other_end && *other_start < end)
|
||||
{
|
||||
return Err(format!(
|
||||
"edits[{index}] 与 edits[{other_index}] 修改范围重叠:{display}"
|
||||
));
|
||||
}
|
||||
matches.push((index, start, end));
|
||||
let _ = new;
|
||||
}
|
||||
crate::write_game_creator_private_file(
|
||||
&path,
|
||||
content.replacen(&old, &new, 1).as_bytes(),
|
||||
"策划工作区文件",
|
||||
)?;
|
||||
Ok(Value::String(format!("已局部修改 {display}")))
|
||||
let mut updated = content.clone();
|
||||
for (index, start, end) in matches.into_iter().rev() {
|
||||
let (_, new) = &normalized[index];
|
||||
updated.replace_range(start..end, new);
|
||||
}
|
||||
if updated == content {
|
||||
return Err(format!("没有产生修改:{display}"));
|
||||
}
|
||||
crate::write_game_creator_private_file(&path, updated.as_bytes(), "策划工作区文件")?;
|
||||
Ok(Value::String(format!(
|
||||
"已局部修改 {display}({} 处)",
|
||||
normalized.len()
|
||||
)))
|
||||
}
|
||||
"delete_path" => {
|
||||
let relative = required_tool_path(args)?;
|
||||
@@ -645,6 +700,29 @@ mod tests {
|
||||
)
|
||||
.expect("patch");
|
||||
assert!(patched.as_str().unwrap().contains("已局部修改"));
|
||||
execute_design_file_tool(
|
||||
root,
|
||||
"write_file",
|
||||
&json!({"path":"notes/multi.md","content":"甲\n乙\n丙"}),
|
||||
)
|
||||
.expect("write multi");
|
||||
let multi = execute_design_file_tool(
|
||||
root,
|
||||
"patch_file",
|
||||
&json!({
|
||||
"path":"notes/multi.md",
|
||||
"edits":[
|
||||
{"old_text":"甲","new_text":"一"},
|
||||
{"old_text":"丙","new_text":"三"}
|
||||
]
|
||||
}),
|
||||
)
|
||||
.expect("multi patch");
|
||||
assert!(multi.as_str().unwrap().contains("2 处"));
|
||||
assert_eq!(
|
||||
fs::read_to_string(root.join("design_artifacts/notes/multi.md")).expect("read multi"),
|
||||
"一\n乙\n三"
|
||||
);
|
||||
execute_design_file_tool(root, "delete_path", &json!({"path":"notes"}))
|
||||
.expect("delete dir");
|
||||
assert!(!root.join("design_artifacts/notes").exists());
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user