合并主分支并解决图标生成模块冲突

保留图标规范与图集生成独立模块边界

融合主分支角色动画与图片序列字段
This commit is contained in:
2026-08-06 17:13:41 +08:00
156 changed files with 15312 additions and 1619 deletions
+224 -15
View File
@@ -2051,6 +2051,47 @@
},
"ExternalEditorAssetCreateRequest": {
"type": "object",
"allOf": [
{
"if": {
"properties": {
"assetKind": {
"const": "character-animation"
}
},
"required": [
"assetKind"
]
},
"then": {
"required": [
"imageSequenceFrames",
"imageSequenceDurationMs"
],
"properties": {
"generationInputs": {
"$ref": "#/components/schemas/ExternalCharacterAnimationGenerationInputs"
}
}
},
"else": {
"not": {
"anyOf": [
{
"required": [
"imageSequenceFrames"
]
},
{
"required": [
"imageSequenceDurationMs"
]
}
]
}
}
}
],
"required": [
"folderId",
"label",
@@ -2132,16 +2173,71 @@
"type": [
"string",
"null"
]
],
"description": "权威媒体类别。character-animation 渲染为序列帧,video 渲染为视频,audio/sound-effect/background-music 渲染为音频,其余渲染为图片。"
},
"imageSequenceFrames": {
"type": "array",
"minItems": 2,
"items": {
"$ref": "#/components/schemas/EditorImageSequenceFrame"
}
},
"imageSequenceDurationMs": {
"type": "integer",
"minimum": 1,
"description": "assetKind=character-animation 时完整图片序列播放一次的毫秒时长;与音频、视频生成参数 durationSeconds 无关。"
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue"
"$ref": "#/components/schemas/JsonValue",
"description": "可重放的生成输入。assetKind=character-animation 时不得包含 characterAnimation、frames、previewVideoPath、frameCount、fps 或 durationSeconds 等旧运行字段;正式媒体数据必须写入 imageSequenceFrames 和 imageSequenceDurationMs。服务端会移除 screenColorHex、mattingProvider、mattingModel 等内部处理审计字段。"
}
},
"additionalProperties": false
},
"ExternalEditorProjectResourceCreateRequest": {
"type": "object",
"allOf": [
{
"if": {
"properties": {
"assetKind": {
"const": "character-animation"
}
},
"required": [
"assetKind"
]
},
"then": {
"required": [
"imageSequenceFrames",
"imageSequenceDurationMs"
],
"properties": {
"generationInputs": {
"$ref": "#/components/schemas/ExternalCharacterAnimationGenerationInputs"
}
}
},
"else": {
"not": {
"anyOf": [
{
"required": [
"imageSequenceFrames"
]
},
{
"required": [
"imageSequenceDurationMs"
]
}
]
}
}
}
],
"required": [
"imageSrc",
"width",
@@ -2215,14 +2311,71 @@
"type": [
"string",
"null"
]
],
"description": "权威媒体类别。character-animation 渲染为序列帧,video 渲染为视频,audio/sound-effect/background-music 渲染为音频,其余渲染为图片。"
},
"imageSequenceFrames": {
"type": "array",
"minItems": 2,
"items": {
"$ref": "#/components/schemas/EditorImageSequenceFrame"
}
},
"imageSequenceDurationMs": {
"type": "integer",
"minimum": 1,
"description": "assetKind=character-animation 时完整图片序列播放一次的毫秒时长;与音频、视频生成参数 durationSeconds 无关。"
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue"
"$ref": "#/components/schemas/JsonValue",
"description": "可重放的生成输入。assetKind=character-animation 时不得包含 characterAnimation、frames、previewVideoPath、frameCount、fps 或 durationSeconds 等旧运行字段;正式媒体数据必须写入 imageSequenceFrames 和 imageSequenceDurationMs。服务端会移除 screenColorHex、mattingProvider、mattingModel 等内部处理审计字段。"
}
},
"additionalProperties": false
},
"ExternalCharacterAnimationGenerationInputs": {
"not": {
"anyOf": [
{
"type": "object",
"required": [
"characterAnimation"
]
},
{
"type": "object",
"required": [
"frames"
]
},
{
"type": "object",
"required": [
"previewVideoPath"
]
},
{
"type": "object",
"required": [
"frameCount"
]
},
{
"type": "object",
"required": [
"fps"
]
},
{
"type": "object",
"required": [
"durationSeconds"
]
}
]
},
"description": "assetKind=character-animation 时 generationInputs 不得包含旧运行字段;正式媒体数据使用 imageSequenceFrames 和 imageSequenceDurationMs。"
},
"ExternalEditorAssetUpdateRequest": {
"type": "object",
"properties": {
@@ -2545,7 +2698,19 @@
"type": [
"string",
"null"
]
],
"description": "权威媒体类别。character-animation 渲染为序列帧,video 渲染为视频,audio/sound-effect/background-music 渲染为音频,其余渲染为图片。"
},
"imageSequenceFrames": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EditorImageSequenceFrame"
}
},
"imageSequenceDurationMs": {
"type": "integer",
"minimum": 1,
"description": "assetKind=character-animation 时完整图片序列播放一次的毫秒时长;与音频、视频生成参数 durationSeconds 无关。"
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue"
@@ -2688,6 +2853,18 @@
"sourceType": {
"type": "string"
},
"imageSequenceFrames": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EditorImageSequenceFrame"
},
"description": "assetKind=character-animation 时的完整可播放帧集合。"
},
"imageSequenceDurationMs": {
"type": "integer",
"minimum": 1,
"description": "assetKind=character-animation 时完整图片序列播放一次的毫秒时长;与音频、视频生成参数 durationSeconds 无关。"
},
"prompt": {
"type": [
"string",
@@ -2716,7 +2893,8 @@
"type": [
"string",
"null"
]
],
"description": "权威媒体类别。character-animation 渲染为序列帧,video 渲染为视频,audio/sound-effect/background-music 渲染为音频,其余渲染为图片。"
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue"
@@ -3855,7 +4033,7 @@
"string",
"null"
],
"description": "角色动作绿幕预览视频写入账号素材库的文件夹;省略时写入默认项目素材文件夹。"
"description": "角色动作预览视频和最终透明序列帧素材写入账号素材库的文件夹;省略时写入默认项目素材文件夹。"
},
"assetLabel": {
"type": [
@@ -3868,22 +4046,29 @@
},
"additionalProperties": false
},
"EditorCharacterAnimationFrame": {
"EditorImageSequenceFrame": {
"type": "object",
"required": [
"frameIndex",
"imageSrc",
"objectKey",
"assetObjectId",
"width",
"height"
],
"properties": {
"frameIndex": {
"type": "integer",
"minimum": 0
},
"imageSrc": {
"type": "string"
},
"objectKey": {
"type": "string",
"minLength": 1,
"description": "帧对应的稳定 OSS Object Key;服务端据此重建持久 imageSrc,不能只提交临时签名 URL。"
},
"assetObjectId": {
"type": "string",
"minLength": 1,
"description": "帧对应的稳定资产对象 ID。"
},
"width": {
"type": "integer",
"minimum": 1
@@ -3892,7 +4077,9 @@
"type": "integer",
"minimum": 1
}
}
},
"additionalProperties": false,
"description": "图片序列帧。数组位置是唯一播放顺序,不携带额外序号字段;每帧必须同时携带 objectKey 与 assetObjectId,imageSrc 按 objectKey 规范化为持久站内路径。"
},
"EditorCharacterAnimationGenerationResponse": {
"type": "object",
@@ -3929,7 +4116,7 @@
"frames": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EditorCharacterAnimationFrame"
"$ref": "#/components/schemas/EditorImageSequenceFrame"
}
},
"frameCount": {
@@ -3967,6 +4154,28 @@
],
"description": "当请求携带 canvasCompletion 且服务端成功写入画布布局时返回最新项目快照。"
},
"resource": {
"anyOf": [
{
"$ref": "#/components/schemas/EditorProjectResource"
},
{
"type": "null"
}
],
"description": "最终透明角色动作对应的项目资源。携带 projectId 时,画布图层必须直接引用其 resourceId,不得再次创建重复资源。"
},
"asset": {
"anyOf": [
{
"$ref": "#/components/schemas/EditorAsset"
},
{
"type": "null"
}
],
"description": "最终透明角色动作素材。assetKind 为 character-animation,并直接包含序列帧字段。"
},
"queueState": {
"anyOf": [
{
@@ -67,19 +67,46 @@
---
## 2026-08-06 game-chat 固定规则只提供上下文,Supervisor 决定条件 Graph
- 背景:用户要求把已有美术接入当前俄罗斯方块时,Runtime 在 Supervisor 首次 Provider turn 之前按关键词重置 Graph、选择是否复用美术并自动调度 child;Supervisor 没有固定快车道计划时又直接 `fixed-task-graph-stalled`。结果是模型没有理解和决策机会,`art-asset-plan` 在已有资产与 baseline 门互相冲突时重复空规划直至耗尽预算。
- 决策:关键词、否定/重做信号、占位状态和当前资产探测统一降级为 `advisoryOnly=true` 上下文。game-chat 根 Run 在持久 `GameChatWorkflowDecision` 前禁止启动任何 manifest child,并允许 Supervisor 正常请求 Provider;Supervisor 通过 auto-safe `agent.route_manifest` 选择 `audit-existing-first` 或 `regenerate-art`。审计路线首波只启动 `design-director + code-director`;已登记但无效的旧派生视觉不得在这两项审计启动前截断 scheduler。code-director 必须先成功 `asset.list`,并在 Provider 请求中读取 Supervisor 已持久化且经校验的 `authoritative=true` 决策,再提交绑定当前 root/completion contract/revision 的覆盖合同与 `use-existing-art / generate-missing-art / regenerate-art` 路由。Runtime 只校验身份、正式资产、Canvas、私有合同、切片、manifest、缺口和 fingerprint,并执行条件分支,不从关键词替 Supervisor 作决定。
- 资产边界:首版缺口槽位固定为 `art-spec` 与原子 `core-spritesheet`。art spec 缺失若使图集的引用/Canvas 合同同时失效,则两个槽位都是真实缺口;仅图集缺失时复用 art spec,只运行 `art-asset-plan`。完整覆盖零生成;显式重做只授权当前 root 下两个正式 owner 原位替换。已登记但校验失败的固定资产按真实缺口交回其 canonical owner,并只在持久路由绑定当前 root 时授权原位替换。art manifest baseline 豁免只读取持久路由,不重新运行关键词分类。
- 连续性边界:正式 failed continuation 继续继承原完成合同;普通美术措辞不再按关键词跨历史根 Run 借用具体试玩类型,避免新项目误继承旧 `tetris-v1`。code-prototype 仍须先读取并最小 patch 非占位入口、可见使用四类切片,再经过静态检查和试玩门禁。
- 恢复与权限:`agent.route_manifest` 在共享 Rust/TypeScript 命令目录中为 `auto`,否则 autonomous profile 会因不能等待人工确认而拒绝唯一决策动作;仅根 Supervisor 和其 code-director 审计 child 可执行,隔离 Agent 与其它角色拒绝。执行中断只允许按 durable pending identity 幂等恢复,同 Run 已持久决策/路由禁止改写。
- 验证方式:覆盖决策前零 child、用户原句只产生 hint、旧无效派生视觉不阻断首波、首波只有设计/程序审计、code-director 收到真实持久策略、完整复用、仅缺图集、无效已登记资产由 owner 原位替换、重做两个 owner、baseline 豁免只认持久 route、code-director 的 asset.list/coverage/route 完成门、自动权限,以及旧 root/错误 binding/fingerprint/缺口/重复 route 失败关闭。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`、`docs/project-memory/shared-memory/pitfalls.md`。
---
## 2026-08-06 AGC 根长任务启动与终态失败采用 Runtime 公开消息硬门
- 背景:AGC 已有模型 final-reply、Runtime `eventId/publicText` 和进度卡,但根任务“已入队”没有后端持久公开回执;失败消息又散落在 main loop 多个 `let _ = append conversation` 分支。Provider 或 Runtime 在首条公开事件前失败时可能零消息,同一次失败也可能被 `turn.failed` 和 conversation 重复播报。
- 决策:Supervisor / 专业 Agent 继续负责业务语义,Runtime 只增加两项不依赖模型的最低可观测性。用户直接投递的 Project Supervisor 根后台任务先落为不可执行的 `preparing / public-status-pending`,再以 run 绑定的 `runtime-public-status-*` message ID 幂等写入启动确认,成功后才转为 `pending / queued`;恢复预检只验证业务状态且不改写 task/conversation,仅真实 resume 持有 Agent 锁后才能把已验证的 accepted 任务持久提升为 `pending / queued`,写入失败则把 task 落为 `failed / public-status-write-failed`。根 Supervisor 通过正式失败 / 预算耗尽收束或 game-chat 绝对硬期限进入 reconciliation 时,在其它终态投影前先写一条脱敏失败消息;专业 Agent 命中同一全局硬期限时,必须通过权威 Run Profile 与根 task 在项目 conversation 幂等写根终态,同时只在精确匹配 agent/run/session、两个 parent 和 binding 的专业 Agent Session 保留 child 终态。自称根 agent/run 但 session 或两个 parent 不符的 Runtime 必须失败关闭,不得生成第二条项目终态。该前缀的 Runtime 公开状态只供 UI 展示,不合入任何 Agent prompt。前端把它视为 Runtime-owned,同秒时排在触发它的 Supervisor 用户消息之后,并仅过滤重复的根 `turn.started / turn.failed / turn.budget_exhausted`,专业 Agent 启动事件仍可见。
- 恢复补充(以本条为准):`preparing` 只要同 run 的用户消息或 accepted 任一已完整持久即属可恢复;preflight 不改写业务文件,resume 才在 Agent 锁内补写 accepted 并入队。用户消息或 accepted conversation 已存在而审计补写失败不得把任务改判为启动失败。根终态首次公开写入遇到瞬时失败时,完成终态投影后必须用同 message ID 再幂等写一次。带 parent 的 Supervisor receipt / isolated-join 续跑只保留一份 Runtime 公开终态,不再追加 Session 重复消息;`runtime-task-*` 与 `runtime-public-status-*` 共享同 run 的不透明关联摘要,多个同秒任务在 UI 中按实际 run 关联的 `user -> accepted -> terminal` 交错排序。
- 边界:不改变 Supervisor 条件 Graph、`agent.route_manifest`、美术路由、Provider tool-plan / final-reply 协议、response stream 或最终 assistant 唯一性;不把 tool 参数、原始错误、路径、fingerprint、凭据和专业 Agent 私有失败诊断公开。Runtime 也不根据工具事件推断业务进展,后续若引入模型 `commentary/final` phase 应沿独立消息协议扩展。
- 验证方式:覆盖 `preparing -> accepted -> pending` 顺序与崩溃恢复、启动确认幂等、启动确认无法持久化时任务零执行、公开状态不进入实际 Agent prompt、状态文件写入本身失败时仍先产生公开失败、根终态事件不重复进聊天、专业 Agent 启动事件仍可见、普通项目消息不被误标 Runtime-owned,并运行 Runtime 定向 Rust、AppSurface、模型测试、typecheck、编码和 diff 门禁。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`、`docs/project-memory/shared-memory/pitfalls.md`。
---
## 2026-08-05 game-chat 当前 ready child 对 manifest Pending 漂移保持活性
> 本条只保留 ready child 的 Pending 漂移处理;历史试玩类型继承与旧三 Director 首波验收已由 `2026-08-06 game-chat 固定规则只提供上下文,Supervisor 决定条件 Graph` 取代。
- 背景:ready scheduler 已为当前根 Run 持久化并启动 `code-prototype`,但并发 hydration 持有的旧 manifest 快照随后把该节点从 running 覆盖回 pending。父 Run 只读取 manifest 时会在 child 仍运行的情况下误判固定 Graph 已停滞并先行失败;child 完成门又因 pending 连续拒绝真实交付,最终耗尽 loop。
- 决策:父 Supervisor 的“是否调度新节点”和“是否等待已存在 child”必须分开。派生视觉需要父规划修复时可以暂停新调度,但只要当前最新且仍活跃的根 Run 下存在确定性 runId、正确父绑定且 durable journal 非终态的 ready child,父 Run 必须保持 `waiting-for-manifest-tasks`。game-chat child 仅在同一当前根 Run、确定性 runId、可信 scheduler source、正确父绑定、journal 仍为 queued/running 且不处于确认、用户输入或 reconciliation 时,容忍 manifest 的 pending 漂移并继续走全部产物、静态检查和试玩门禁。
- 失败关闭:GUI/CLI、旧父 Run child、终态 child、错误/伪造绑定、非确定性 runId、WaitingForConfirmation、WaitingForUserInput 和 needs-reconciliation 都不得借用该容忍;创建更新的根 Run 后,旧 child 立即失去父 DAG 活性与 pending 完成资格。
- 玩法连续性:既有美术增量任务可跨可信 Supervisor 入口回溯同 Session 的最近具体试玩场景;只跳过纯继续和既有美术接入语义,遇到更新的详细 Generic 新目标立即停止,避免从更老项目错误借用 `tetris-v1`。
- 验证方式:覆盖当前 game-chat child 的 running -> manifest pending 漂移仍保持 DAG 活跃并可完成投影;更新根 Run 后旧 child 被拒绝;派生视觉需要修复但已有 child 活跃时父 Run仍持久化等待;GUI child 继续拒绝 pending;GUI 创建俄罗斯方块后从 game-chat 接入既有美术仍使用 `tetris-v1`。
- 玩法连续性:普通美术措辞不再触发历史玩法类型回溯;只有正式 failed continuation 继承原完成合同,纯“继续”沿用现有 continuation 识别边界。
- 验证方式:覆盖当前 game-chat child 的 running -> manifest pending 漂移仍保持 DAG 活跃并可完成投影;更新根 Run 后旧 child 被拒绝;派生视觉需要修复但已有 child 活跃时父 Run仍持久化等待;GUI child 继续拒绝 pending;普通美术措辞使用当前任务分类,不借用旧 `tetris-v1`。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/pitfalls.md`。
---
## 2026-08-05 增量接入既有美术时复用已验收 Graph 节点
> 已由 `2026-08-06 game-chat 固定规则只提供上下文,Supervisor 决定条件 Graph` 取代。下述内容保留为问题演进记录,不再作为当前实现依据。
- 背景:game-chat 增量指令“把 UI 和方块替换成美术资源”会重置七项首版 Graph,把已有且已验收的 `art-director / art-asset-plan` 重新打开。`art-asset-plan` 确定性快车道同时因图集已存在而返回零 action,child 完成门又要求 `assets/manifest.art.json` 相对本轮 baseline 发生变化,最终连续六轮得到同一 `unchanged-from-run-baseline` blocker 并耗尽 loop;这不是 Provider 故障。
- 决策:只有 game-chat 用户明确表达替换、接入、使用或复用美术资源,项目已有非占位游戏,且 art spec、透明图集、Canvas 登记、私有图集合同、四类切片与 art manifest 全部通过现有严格验收时,Graph reset 才保留 `art-director / art-asset-plan=completed`,只重新打开设计、代码和试玩节点。根 Supervisor 完成门仅对该合法复用场景豁免 `assets/manifest.art.json(unchanged-from-run-baseline)`;文件存在、JSON、Canvas、私有合同、切片和代码可见使用门禁继续执行,任一证据失效即撤销豁免。普通新游戏、全新/重做美术、否定使用或占位项目一律把美术节点恢复为 pending。
- 玩法连续性:合法增量美术接入从同 Session 的可信 Supervisor 历史根 Run 继承最近一个非 Generic 试玩场景;只跨过纯继续与既有美术接入语义,遇到更新的详细 Generic 新目标立即停止。例如 GUI 创建的俄罗斯方块转入 game-chat 继续使用 `tetris-v1`,不能因为本轮文字只描述 UI/美术而降级成 Generic,也不能跨过后来的新游戏目标借用旧场景。
@@ -5917,6 +5944,21 @@
- 微信边界:小程序客户端仍只上传 `wechatPhoneCode`;`platform-auth` 必须要求微信成功响应中的 `phoneNumber`、`countryCode` 与 `purePhoneNumber` 均存在且非空,但只使用后两项执行国家码校验和 E.164 构造。腾讯官方仅说明境外 `phoneNumber` 会带区号,并未承诺 E.164 格式,中国号码示例中它与纯号码相同,因此不得校验 `phoneNumber == +{countryCode}{purePhoneNumber}`。微信字段缺失时失败关闭,不能使用普通请求的 `86` 默认值。
- 数据边界:认证投影与 SpacetimeDB 的 `phone_number_e164` 保持不变,不新增国家码或纯号码列,也不需要 schema 迁移或 bindings 生成。
## 2026-07-28 编辑器媒体类型统一由 assetKind 判定
- 决策:`assetKind` 是编辑器资源和素材唯一权威媒体类别;不新增或返回 `mediaType`。`character-animation` 渲染为序列帧,`video` 渲染为视频,`audio/sound-effect/background-music` 渲染为音频,其余类别渲染为图片。前端内部可保留派生的 `CanvasMediaType` 选择渲染器,但不能把它作为后端事实。
- 持久化边界:`editor_project_resource` 与 `editor_asset` 表尾只保存 `image_sequence_frames_json` 与 `image_sequence_duration_ms`,默认均为 `None`;`editor_showcase_asset` 作为提交时冻结的审核与公开快照,同样在表尾保存这两个字段并从账号素材逐字段复制,旧行默认均为 `None`。正式帧对象和角色动作生成响应都不保存或返回 `frameIndex`,数组位置是唯一播放顺序;帧数取数组长度,FPS 由帧数和毫秒时长即时推导,不持久化 `frame_count`、`fps` 或通用 `duration_seconds`。后端处理抽帧和逐帧去背时仍保留内部 `frame_index`,仅用于乱序并发收口、OSS 命名、日志和错误定位;仓库内旧 helper 同步升级,不为它保留外部兼容字段。
- 时长口径:`image_sequence_duration_ms` 只表示角色图片序列完整播放一次的毫秒时长,与音频 / 视频生成请求中的 `durationSeconds` 完全分离。角色动作与视频生成响应仍可携带各自既有的请求 / 结果级 `frameCount/fps/durationSeconds`;通用音视频秒数不进入资源 / 素材正式列、`EditorAsset`、`CanvasLayer` 或画布 layout,只允许把上传探测值或生成请求值格式化为用户可见字符串后写入 `generation_inputs_json.fields[]` 供素材详情展示。素材详情和画布 ZIP 用户可见元数据只透传实际存在的 `fields[]` 时长项;缺少时直接省略,不生成 `--:--` 占位。素材放置与工程恢复不做媒体探测,也不从 layout / resource 回退;音频播放只使用媒体 `loadedmetadata.duration`。不得为音频生成响应新增 `durationSeconds`,也不得把音视频秒数写入图片序列字段。前端角色动作播放器使用 `imageSequenceDurationMs / imageSequenceFrames.length`,Spine 导出时才换算秒数并推导 FPS。
- 写入与复用边界:`assetKind=character-animation` 必须在 SpacetimeDB storage/procedure 边界同时提供至少两帧有效数组与大于 0 的图片序列毫秒时长;其他类别不得携带图片序列字段。同项目同源同媒体资源只允许 `None → Some` 单调回填,非空冲突失败关闭,延迟重试的 `updated_at` 取请求时间与既有时间的较大值。
- 生成关联边界:角色动作生成响应同时返回已经持久化的最终 `resource` / `asset`;带项目上下文时前端结果图层必须直接使用 `resource.resourceId` 及其预览视频来源血缘,缺少 resource 直接失败。无项目放置也必须绑定响应中的正式账号素材,不能从响应 `frames` 构造无资产真相的本地动作结果。“动作(原始视频)”只是 `asset_kind = video` 的 provider 中间产物,其 `editor_project_resource` 与 `editor_asset` 两行都固定 `generation_inputs_json = NULL`,不得携带引用或开放参数复用;完整用户生成配方只保存在最终 `asset_kind = character-animation` 的序列资源 / 素材。角色动作规范化迁移对权威证明的 preview video 同样清空整份生成输入,但不改写其 `source_resource_id`。
- repair 幂等边界:legacy 音频 repair 不再派生或回填任何资源级通用时长,因此新列上线前已完成 repair 的重放继续按原字段精确匹配;图片序列字段在音频行上均为 `None`。
- 布局边界:`editor_project_resource` 的正式字段是角色动作唯一媒体真相;动作 layout 只保存资源引用和 placement,不保存帧、时长、预览、生成输入、资源元数据或顶层 `mediaType`。前端仍可从 `assetKind` 派生内部 `CanvasMediaType`,但后端响应清洗不得把普通 layer 的旧 `mediaType` 传回前端,也不得递归删除 `generationInputs.references[*].mediaType`。
- 迁移边界(2026-08-04 收口):存量 `generation_inputs_json.characterAnimation`、已证明动作行的 helper 顶层 `frames/previewVideoPath/frameCount/fps/durationSeconds`、`screenColorHex`、正式帧 `frameIndex`、误标预览 MP4 和动作 layout 副本,由 `normalize_editor_character_animation_metadata_and_return` 按 `asset → project-resource → showcase → canvas` 一次性规范化。顶层字段只有先证明动作身份后才解释;普通图片 / 视频任意 JSON 中的同名字段不动。canvas dry-run 可消费前置 scope 的计划态结果,但 apply 仍要求前置 scope 已物理完成;同 task 候选先按权威对象规划分类并排除预览视频,只有唯一最终图片序列可补建资源。正式序列每帧必须按首帧 bucket 的稳定路径精确匹配同 owner / task 的图片 `editor_character_animation` 对象并补齐 `objectKey/assetObjectId`。迁移永不验证 layout 复制的 `sourceResourceId`,补建资源采用最终素材的 DB 血缘;新生成直接来源链仍严格校验。正式/旧版冲突、最终候选为零或多个、对象不匹配形成 blocker;apply 必须绑定同批 dry-run SHA-256,结束后全量复核零匹配、零 blocker。迁移完成后删除 api-server、admin、Web 与 helper 的 action fallback。
- 新写入边界:`assetKind=character-animation` 的 `generationInputs` 若含旧运行字段或 `screenColorHex`,正式帧若含 `frameIndex`,api-server 和 SpacetimeDB storage 均失败关闭;门禁只对角色动作生效,不误伤其它素材的任意 generation input JSON。
- 交互边界:角色动作素材下载导出完整序列 ZIP;点击、HTML5 拖放和指针拖放创建可移动、循环播放且可保存恢复的序列图层。`assetKind=character-animation` 但缺少有效帧时按损坏素材失败关闭,不回退首帧 PNG。
- 精选展示边界:`/creation` 精选卡片和预览弹窗继续扩展各自现有媒体 renderer,不复用或重构画布图层组件;卡片静止时只读取首帧,hover / focus 后才加载并播放完整序列,预览弹窗提供播放暂停。后台素材查询与精选审核继续共用 `AdminEditorAssetMedia`,列表只读首帧,预览弹窗才逐帧使用管理员换签。两端均按 `imageSequenceDurationMs / imageSequenceFrames.length` 切帧,下一帧未就绪时保留上一帧且禁止淡入;损坏动作不退回普通图片。
- 精选帧授权边界:公开换签只对当前已通过、已展示、返还完成且未删除的精选动作,按同 owner 的冻结帧 `assetObjectId` / `objectKey` 形成 exact grant;不从 `imageSrc` 或前缀推导。隐藏、拒绝、删除或快照损坏后逐帧授权随当前事务真相撤销,`read-url` 与 `read-bytes` 继续共用该判断。
## 2026-07-24 后台用户详情展示历史花费泥点
- 口径:`historicalConsumedPoints` 表示用户历史总消费,只累计 `profile_wallet_ledger.source_type = asset_operation_consume` 且 `amount_delta < 0` 的绝对值;`asset_operation_refund` 不冲减,充值退款追回、余额重置、赠送和退款 hold 均不计入。
@@ -6108,6 +6150,8 @@
## 2026-08-03 game-chat 开发态前后端同源与快车道恢复
> game-chat 的旧三 Director 首波、无条件美术生成和对应 Pending 容忍范围已由 `2026-08-06 game-chat 固定规则只提供上下文,Supervisor 决定条件 Graph` 取代;本条其余 GUI / CLI、启动、source 与输出决策继续有效。
- 启动决策:`npm run agc` 与 `npm run agc:game-chat` 统一先经外层 Node 启动器预检 `3080`。在 marker 尚不能证明 worktree 归属时,任何已占用的 3080 都不得复用,并必须在原生窗口创建前失败关闭;Tauri CLI 退出后必须收束已启动的客户端进程树,不允许终端已退出但窗口与 Runner 仍假在线。
- 首波决策:普通 GUI / CLI 的正式 16 节点 DAG 与 game-chat 首版 lane 都只把 `design-director / art-director / code-director` 作为首波 ready Agent。正式 DAG 的 `design-foundation` 等待策划与美术 Director,`code-prototype` 等待程序 Director 及数值、美术、音频三条底层产物链;game-chat 在策划与美术 Director 完成后启动 `art-asset-plan` 生成透明图集,`code-prototype` 必须同时等待三个 Director 与该图集任务,再串行执行静态检查和试玩。首波以外的非 repair 底层 Agent 只能由依赖就绪调度或上层明确返工合同按需激活。
- Runtime 决策:game-chat 七任务 lane、平台美术、单轮收束和自动预览只由持久 `project-supervisor-game-chat` root source 启用。hydration 对 `Pending` 的容忍只适用于当前 source-aware lane 的三个零依赖首波任务。页面进度、阶段记录和 final-reply 白名单统一使用七项任务与 `x/7`。
@@ -6116,6 +6160,8 @@
## 2026-08-03 game-chat 失败续跑与首版产物保护
> game-chat 的无条件美术生成部分已由 2026-08-06 的 Supervisor 条件路由取代;失败 continuation、非占位入口保护和美术产物验收边界继续有效。
- 事故事实:`game01` 的首个根 run 已交付并验证水晶下落方块原型,但父 run 在上下文压缩后因 token 上限失败;用户随后输入“继续”时,客户端创建了 task 仅为“继续”的新 game-chat root,完成合同重置 seed manifest,五分钟 fallback 再把该短语当主题整文件覆盖 `game/index.html`。这不是模型随机换题,而是 root 目标未继承、每新 run 无条件 reset 和 fallback 无条件 `file.write` 叠加形成的确定性缺陷。
- 续跑决策:同一 Supervisor Session、同一持久 source 的最近失败根 run 后,严格继续短语创建 successor root,并继承前序原始任务、baseline revision / artifact 身份;纯继续识别统一由一个精确函数负责,覆盖“继续 / 接着 / continue / go on”等无新约束短语。真正新需求、跨 Session、跨 GUI / CLI / game-chat source、前序正常完成或含具体新约束的输入继续创建独立新任务并重置本轮 manifest。successor 不复用旧网络请求、pending、action、Provider lifecycle 或 sidecar,只继承业务目标和已提交项目事实。
- 覆盖决策:game-chat fallback 只允许初始化缺失/占位入口的首次落盘。非占位 `game/index.html` 必须保留,并由当前 `code-prototype` 先读取和实际 patch,取得本人 `mutationRevision` 后才能运行 `game.static_smoke` 与交付;只读 smoke 不得冒充续作。确定性 fallback 只允许已实现真实语义的显式玩法模板:俄罗斯方块模板必须具备 10×20 棋盘、下落、移动、旋转、锁定、消行和触顶失败,收集模板只用于明确收集类目标,未知玩法失败关闭。纯继续目标未恢复时同样失败关闭。完成门新增 baseline 玩法连续性和 action-driven state 检查,generic Canvas 非空、三个按钮存在或静态 smoke 通过都不能单独证明任务没有换题。
@@ -6403,6 +6449,8 @@
- local 状态:`local-*` 只是 ID 形状,不能直接解释为“素材仍在保存”。新上传 / 新生成素材是否 pending 取资源登记在途状态;严格满足兼容谓词的历史自包含本地角色动作序列是持久化终态,不得误报等待。若当前版本尚不能复制这类序列,以准确原因失败关闭;既非 pending 又不满足历史谓词的 unresolved local 图层也失败关闭,但不得承诺稍后一定自动恢复。layout PATCH pending 不参与资源登记判断,系统剪贴板图片导入不受影响。
- schema 与迁移:在现有 `EditorCanvasLayer` 结构体末尾追加 `#[default(None::<String>)] asset_kind_override: Option<String>`,不删除、改名、重排或改类型。legacy 图层类型与资源默认相同则迁移为 `None`,不同则迁移为 override;资源无默认值时只有全部引用图层显式同值才补资源默认,否则保留各自 override;自包含历史序列的显式类型迁入 override,不伪造资源。同步 `migration.rs`、表目录 / 数据契约、生成 bindings、HTTP DTO 与结构化 canonical hash,并运行 `npm run spacetime:generate`、`npm run check:spacetime-schema`。
- 并发边界:未登记图层被禁止复制后,不再按临时资源 ID 合并项目资源创建请求,也不再用一次响应批量改写共享临时 ID。每个合法新增图层保留自己的响应快照与回调;layout PATCH 的串行 latest-wins 队列、共享资源的多布局引用和 session 资源快照按 `resourceId` 去重继续保留,它们与资源创建 single-flight 是不同机制。
- 媒体兼容边界:override 只允许在资源默认类型的同一媒体族内变化。动作、视频各自独立成族,`audio/sound-effect/background-music` 同属音频族,其余类型与空默认值同属图片族。前端菜单禁用跨族标签且更新入口重复校验;后端对每个结构化图层按资源完整校验,跨族值清空后回退资源默认类型,不因该兼容错误拒绝整个保存。
- 历史恢复:客户端读取到已持久化的跨族 override 时必须保留图层、回退资源默认类型、显示明确提示并自动提交清理后的布局;不得再用 `hydrateLayer() -> null -> filter(Boolean)` 静默隐藏持久层仍存在的图层。修复保存只替换命中图层的规范化布局项,其他尚不能 hydrate 的历史项原样保留,避免修复一个标签时顺带删除无关数据。
## 2026-08-03 Agent Runtime 原生工具合同本地失败关闭
@@ -81,9 +81,19 @@ cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml real_
修改 game-chat release flavor 后,至少执行壳配置门禁、AppSurface game-chat 定向测试、前端类型检查、AppData / 诊断日志 / release flavor 相关 Rust 定向测试、`npm run check:encoding` 和 `git diff --check`。打包 smoke 必须确认:安装信息和产物版本为 `0.1.1`;无参数启动直接进入且只能停留在 game-chat 页面;停止或断开 `api-server` 后本地工作台仍能打开;普通 dev / release 与 debug game-chat 仍走原认证入口;独立 AppData 生效。预览 smoke 应先让当前 run 成功验证 revision N,确认 Tauri registry 启动一个 server 且 iframe 自动出现;在 validate 后、start 取得锁前推进项目 revision,必须确认原子 `expectedRevision` 门禁拒绝启动且授权保留等待新证据;再验证 revision N+1,确认 server 进程和 loopback origin 不变、iframe 显示新版本且响应为 `no-store`。same-run steer 还要覆盖旧验证不消费新授权、旧异步 attempt 不清新 generation;Runner registry 单独 running、失败 / 相同 / 更低 revision 均不能触发用户预览或重复刷新,停止后顶部显示“预览未启动”。独立包退出时必须通过 `runner.shutdown_for_client_exit` 先进入 draining 再结束本 boot,保留 durable sidecar 供下次 reconciliation,不把中断任务写成 completed;Windows Runner 必须 `CREATE_SUSPENDED -> AssignProcessToJobObject -> ResumeThread`,分配或恢复失败时 kill + wait,客户端持有 kill-on-close Job 兜底,关闭主窗口后 Runner、MCP、command、ConPTY 及其后代都应消失。普通 dev / release 和 CLI 继续使用 `runner.shutdown_if_idle`。
game-chat 迭代还必须确认以下行为:Supervisor ready 输出、`design-director / art-director / art-asset-plan / code-director / code-prototype / preview-readiness / preview-playtest` 七个专业 Agent 的 durable `final-reply`,以及 Rust 明确生成 `eventId + publicText` 的每条公开 Runtime 输出都作为独立 assistant 消息逐条固化在项目聊天。消息通过顶层 `messageId` 幂等追加,事件 / 轮询 / hydration 重放不重复。前端禁止从原始 `summary / detail`、tool plan、Provider / Runner 元数据、命令输出或路径自行拼接持久消息;UI 把一个父 Run 统一显示为“本轮生成进度”,最新状态也不得暴露内部“第 N 轮”,完整 GUI / CLI 任务图可显示 `x/14`,首版快车道显示 `x/7`,终态不保留运行中进度卡;可信 `project-supervisor-game-chat` 一轮完成 `preview-playtest` 后直接收束,不请求下一次 Provider tool-plan;所有调度入口均不得暴露或启动 `publish-strategy` / `publish-package`。game-chat 必须配置可用的 External Editor API,按 `art-spec.png -> icon-spritesheet -> art-spritesheet.png + iconImageSrcs 本地切片 -> code-prototype Canvas 使用四类切片` 推进;缺少任一环节都必须失败关闭。
game-chat 迭代还必须确认以下行为:Supervisor ready 输出、本轮实际启动的 `design-director / art-director / art-asset-plan / code-director / code-prototype / preview-readiness / preview-playtest` 专业 Agent 各自产生 durable `final-reply`,条件路由跳过的节点只要求 manifest 正确投影为 completed,不伪造 Agent 回复;Rust 明确生成 `eventId + publicText` 的每条公开 Runtime 输出都作为独立 assistant 消息逐条固化在项目聊天。消息通过顶层 `messageId` 幂等追加,事件 / 轮询 / hydration 重放不重复。前端禁止从原始 `summary / detail`、tool plan、Provider / Runner 元数据、命令输出或路径自行拼接持久消息;UI 把一个父 Run 统一显示为“本轮生成进度”,最新状态也不得暴露内部“第 N 轮”,完整 GUI / CLI 任务图可显示 `x/14`,首版快车道显示 `x/7`,终态不保留运行中进度卡;可信 `project-supervisor-game-chat` 一轮完成 `preview-playtest` 后直接收束,不请求下一次 Provider tool-plan;所有调度入口均不得暴露或启动 `publish-strategy` / `publish-package`。固定关键词与资产探测只进入 Supervisor 的 advisory context;没有持久 Supervisor 路由时不得启动任何 child。只有程序侧审计判定存在真实缺口或 Supervisor 明确选择整体重做时才要求可用 External Editor API,并按 `art-spec.png -> icon-spritesheet -> art-spritesheet.png + iconImageSrcs 本地切片 -> code-prototype Canvas 使用四类切片` 补齐;`use-existing-art` 不得重复生成或扣费,但仍须通过相同正式资产和代码可见使用门禁。
game-chat 素材完整快车道采用七任务口径。父 Run 与全部 child Run 共用 4200 秒软预算和 4500 秒累计硬上限;先确定性生成并登记 `art-spec.png`,再由 `art-asset-plan` 通过专用 icon-spritesheet 路由生成透明 `art-spritesheet.png`,下载并持久化响应中的独立切片,最后才允许 code-prototype 写入或局部修复入口。软预算后只允许使用已登记图集、当前 resourceId 对应切片清单的确定性本地 fallback、`game.static_smoke` 和 `preview.validate`;不得退回普通生图、猜测 atlas 网格或纯代码核心画面。完成门要求活动 Canvas 分别绘制 player、blocks-and-targets、obstacles-and-scene、feedback-effects 四类不同切片;整图 `<img>`、CSS background、完整图集直绘、单个猜测裁切和路径诱饵均失败。对应定向测试至少包括:
Project Supervisor 根后台任务还必须验证公开消息硬门:任务先落为不可执行的 `preparing / public-status-pending`,项目 conversation 中存在且仅存在一条同 run 的 `runtime-public-status-* / accepted` 后才能转为 `pending / queued`;恢复 preflight 只验证并临时分类可恢复状态,不改写 task/conversation,真实 resume 持有 Agent 锁后才可把 accepted preparing 持久提升为 `pending / queued`;否则不得执行。若该消息无法持久化,task 必须进入 `failed / public-status-write-failed` 且 Provider、工具与 child 调度均为零。这类 Runtime 公开状态不得合入 Agent prompt。根 Supervisor 通过正式失败 / 预算耗尽收束或 game-chat 绝对硬期限进入 reconciliation 时,即使后续 task/event/state 写入失败,也必须已经存在一条脱敏终态失败消息;专业 Agent 命中全局硬期限时,项目 conversation 仍必须幂等保留根终态,但 child 终态只能写入与权威 task 的 agent/run/session、两个 parent 和 binding 全部一致的私有 Session;自称根 agent/run 却 session 或 parent 不符时失败关闭。只有根 Supervisor 的 `turn.started / turn.failed / turn.budget_exhausted` 不得再形成第二条聊天消息,专业 Agent 的公开启动事件仍应显示。非 Supervisor 专业 Agent 的失败状态留在对应 Agent Session,不得把私有诊断送进项目 conversation。定向复验至少运行:
提交前还要覆盖启动崩溃窗口:仅 `preparing` 时 preflight/resume 均不执行;用户消息已持久但 accepted 缺失时,preflight 只报可恢复且 task/conversation 不变,resume 才补写 accepted 并转为 `pending / queued`。还要验证用户消息或 accepted 已落盘但审计失败均继续入队、根终态首次公开写入瞬时失败会用同 ID 重试、receipt / isolated-join 失败只有一条公开结果,以及同秒连续任务通过共享的 run 关联摘要按 `user -> accepted -> terminal` 逐任务展示。
```bash
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml public_start_status -- --test-threads=1
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml structured_plan_state_write_failure_stops_before_context_and_audit -- --test-threads=1
npm run test -- apps/ai-game-creator-shell/tests/agentRuntimeModel.test.ts apps/ai-game-creator-shell/tests/appSurface.test.ts
```
game-chat 条件快车道仍采用七任务口径,但执行顺序由持久合同控制。父 Run 与全部 child Run 共用 4200 秒软预算和 4500 秒累计硬上限;Supervisor 首轮先调用 `agent.route_manifest`,决策前零 child。`audit-existing-first` 的首波仅为 `design-director + code-director`,已登记但无效的旧派生视觉不得阻断这两项审计启动;code-director 必须先 `asset.list`,读取 Supervisor 已持久化的 authoritative 决策,再提交正式资产覆盖合同。覆盖完整时跳过图片生成,存在缺口时只开放缺口对应 owner,`regenerate-art` 才强制重新开放两个美术 owner;已登记但校验失败的固定资产只允许当前路由绑定的 canonical owner 原位替换。新生成仍按 `art-spec.png -> art-spritesheet.png + 独立切片` 推进,最后才允许 code-prototype 写入或局部修复入口。软预算后只允许使用已登记图集、当前 resourceId 对应切片清单的确定性本地 fallback、`game.static_smoke` 和 `preview.validate`;不得退回普通生图、猜测 atlas 网格或纯代码核心画面。完成门要求活动 Canvas 分别绘制 player、blocks-and-targets、obstacles-and-scene、feedback-effects 四类不同切片;整图 `<img>`、CSS background、完整图集直绘、单个猜测裁切和路径诱饵均失败。对应定向测试至少包括:决策前零 child、旧无效视觉不阻断首波、首波无美术 child、code-director 收到真实持久策略、完整覆盖零生成、仅缺图集只运行 `art-asset-plan`、无效已登记资产由 owner 原位替换、art spec 缺失导致引用合同失效时同时补齐两个槽位、显式重做原位替换两个正式资产、旧 root/fingerprint/缺口或重复路由失败关闭,以及补齐后恢复 code-prototype。
失败续跑还必须覆盖同 Session 同 source 继承、跨 Session / 跨 source 不继承、首次与连续 successor 的 effective task / contract / scheduler 一致性,以及中英文纯继续短语使用同一识别函数。非占位入口的新 `code-prototype` 必须先产生本人 mutation 再 smoke;连续只读 smoke 不得收束。占位 fallback 只允许显式支持的真实玩法模板,俄罗斯方块必须验证棋盘、下落、旋转、锁定和消行语义,未知玩法必须失败关闭。
@@ -654,7 +664,7 @@ npm run ai-game-creator-shell:agent-runtime:supervisor-swarm-tool-plan-handoff-r
## AI 游戏创作长耗时与退出恢复验证
- 修改 autonomous 调度时,必须证明缺省 policy 不产生 manifest 之外的首波专业委派,Editor Key、已有/损坏视觉资产均不能改变该事实;显式项目 policy 只验证“原样尊重”,不能由 Runtime 隐式扩充。
- 修改 autonomous 调度时,必须证明缺省 policy 不产生 manifest 之外的首波专业委派,Editor Key、已有/损坏视觉资产均不能改变该事实;game-chat 还必须证明持久 Supervisor 决策前零 child,决策后的首波只有 `design-director + code-director`,固定关键词不能直接改 Graph。显式项目 policy 只验证“原样尊重”,不能由 Runtime 隐式扩充。
- 修改预览完成门时,分别覆盖 child `preview-readiness` 当前 revision smoke、child `preview-playtest` 到根 Supervisor 合同的 browser receipt,以及 WebSocket 启动前退出的稳定基础设施分类。基础设施错误必须在一次浏览器调用后让 run 失败,不能只做到后续调用快速失败而继续消耗 Provider 轮次。
- 修改 game-chat Runner 生命周期时,至少覆盖 busy durable sidecar 拒绝 client-exit、拒绝后 `draining=false` 可继续执行、清空后 idle shutdown、重复 shutdown 幂等;Windows target check 继续保留,不能以删除 Job Object 或放任后台继续来规避 reconciliation。
- LLM 配置回归必须穷举全部规范 Agent,校验无遗漏/重复、显式 patch 覆盖默认,并锁定 GUI 展示映射和 Rust resolver 一致;模板与 GUI 初始草稿不得把规范默认持久化成 `agentLlm` 显式覆盖。`--llm-status` 要输出逐 Agent 实际 reasoning/timing/retry 值。运行日志与当前配置冲突时,先区分 durable run snapshot 和后来修改的文件,不能按当前文件反推历史请求。
+58 -4
View File
@@ -14,6 +14,15 @@
- 关联:相关文件、文档、提交或 Issue
```
## Runtime 状态写失败不能发生在公开失败消息之前
- 现象:用户提交长任务后只看到运行失败或任务直接消失,聊天里一条有用消息都没有;另一些失败又同时出现 Runtime event 和 conversation 两条近似提示。
- 原因:启动确认依赖实际 `turn.started`,任务只入队或 Runner 在 start transition 前失败时没有公开回执;终态失败先写 task/event/state,最后才由各 main-loop 分支尽力追加 assistant。坏掉的若正是状态文件,流程会在公开消息前返回;散落的 `let _` 又无法提供幂等身份。
- 处理:用户直接投递的 Project Supervisor 根任务先落为不可执行的 `preparing / public-status-pending`,持久化同 run 的 `runtime-public-status-* / accepted` 后才转为 `pending / queued`;恢复 preflight 只验证业务状态,不改写 task/conversation,仅 resume 持有 Agent 锁后才可持久提升。根 Supervisor 通过正式失败 / 预算耗尽收束或 game-chat 绝对硬期限进入 reconciliation 时,先写相同协议的终态消息,再处理 Runtime 其它投影。专业 Agent 命中全局硬期限时,应由权威根 task 在项目 conversation 中幂等写根终态,child 仅在身份完整匹配时写私有 Session;根 agent/run 的 session 或两个 parent 冲突时必须失败关闭。消息正文只能来自封闭脱敏映射,prompt 构建必须按稳定前缀排除;前端按前缀标记 Runtime-owned,只过滤根 Supervisor 的 `turn.started / turn.failed / turn.budget_exhausted`,不吞掉专业 Agent 启动进度。模型仍负责业务 commentary/final,Runtime 的两条硬门不扩展成业务判断器。
- 恢复补充:不能把“accepted 还没写完”等同于“用户从未投递”。用户消息已持久时,真实 resume 必须补写 accepted 后才入队;用户消息或 accepted conversation 已存在时,后续审计失败不得留下“正在启动”但永不执行的假状态,但同 message ID 的 role/content 冲突必须把 task 明确收束为 `conversation-write-failed`。根终态首次公开写入的瞬时失败必须在终态投影后用相同 message ID 重试。带 parent 的 Supervisor continuation 不得同时产生 Session 终态和 Runtime 事件两条公开消息;秒级时间戳下必须以 task/status message ID 共享的 run 关联摘要排序,不能用不同消息类别的计数猜测顺序。
- 验证:任务 journal 必须显示 `preparing -> pending`,仅有 accepted 时恢复才可提升;破坏项目 conversation 时断言任务为 `public-status-write-failed` 且无可运行 pending;破坏 Runtime state 路径时断言公开失败已经存在;重复写同一 run/status 只有一条 message ID;渲染实际 prompt 断言不包含 Runtime 公开状态;AppSurface 证明根启动/失败事件不重复,专业 Agent 启动仍可见。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_state.rs`、`agent/runtime_driver/task_start.rs`、`src/features/agent-runtime/model.ts`、`src/features/project-workspace/SupervisorChatOnlyView.tsx`。
## manifest 被旧快照写回 Pending 时不能让父 Run 抛下真实运行中的 child
- 现象:`code-prototype` 已有确定性 child run、running journal 和工具事件,父 Supervisor 却在几秒后以 fixed graph stalled 失败;child 随后完成代码与静态检查,但 completion gate 持续报告 `task=code-prototype status=pending`,最后 `loop-budget-exhausted`。
@@ -22,12 +31,20 @@
- 验证:人工把当前 child 的 manifest 状态回写 pending,断言父 DAG 仍 in progress、父上下文可持久化为 `waiting-for-manifest-tasks`、child 可投影 completed;随后创建更新根 Run,断言旧 child 不再保持 DAG 活性且 completion blocker 恢复 `status=pending`。不要靠增加 loop 次数或伪造 completed 掩盖竞态。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/autonomous_policy.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs`。
## 固定关键词提示不能代替 Supervisor 选择条件任务图
- 现象:用户只说“现在没有用到任何美术资源”,Graph 就在 Supervisor 输出任何计划前自动打开美术节点;或者用户想复用现有素材,Runtime 直接按关键词预完成节点。Supervisor 无固定计划时随即 `fixed-task-graph-stalled`,看起来像模型不理解意图,实际上模型根本没有获得决策机会。
- 原因:同一套关键词函数同时承担 prompt hint、Graph reset、baseline 豁免和历史试玩类型继承,启发式信号越过 Supervisor 成为了控制面真相;main loop 又在 Provider 请求前优先调度 ready task。
- 处理:启发式结果只序列化成 `advisoryOnly=true` 的 Supervisor context。Scheduler 以持久 `GameChatWorkflowDecision` 为首轮前置门;Supervisor 先选审计或整体重做,code-director 再用成功 `asset.list` 和 Runtime 复核的覆盖合同选择复用或精确补缺。Runtime 可以拒绝过期、伪造、遗漏或重复 route,但不得替 Supervisor 补写决定。
- 验证:直接使用用户原句,断言 hint 命中但 manifest 全部保持 pending、决策前零 child、Provider request 包含路由工具;决策后首波只有 design/code,code-director 未完成 asset.list/route 时不能完成;再分别覆盖完整复用、真实缺口和显式重做。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/game_chat_fast_path.rs`、`runtime_driver/main_loop.rs`、`runtime_driver/task_start.rs`、`runtime_protocol/autonomous_completion.rs`。
## 既有正式产物不能同时被快车道视为已完成、被本轮 baseline 门视为未变化
- 现象:增量任务已有完整美术图集,`art-asset-plan` 每轮都返回零 action 和“已验证交付”,但 completion gate 每轮都报告 `assets/manifest.art.json(unchanged-from-run-baseline)`;最终 child `loop-budget-exhausted`,随后 Graph 和父 Run 失败。日志中没有本轮 Provider request、tool plan 或 action receipt。
- 原因:Graph reset 无差别重新打开稳定的美术 owner 节点;快车道按“当前产物有效”判断完成,owner 完成合同则按“本轮必须修改 baseline 产物”判断完成,两套语义互相冲突。增加 loop 预算、伪造版本号或机械改写 manifest 都不能消除冲突,还会引入 verification loop、字段丢失或错误复用旧主题。
- 处理:先在 Graph 层区分“复用已验收产物”和“需要重新生成”。仅对明确的既有美术接入意图、非占位游戏和整套严格有效的视觉合同保留美术节点 completed;根完成门只忽略这一路径的 art manifest baseline 相同,所有结构、Canvas、私有回执、切片和可见使用验收继续失败关闭。普通新游戏、全新美术或证据损坏必须重新打开 owner 节点。
- 验证:不要只断言 child 未被调度;还要直接经过父完成门,证明合法复用不再出现 art baseline gap,并证明删除切片后 baseline gap 与视觉合同 gap 重新出现。另覆盖普通新目标和全新美术请求不复用旧美术。
- 处理:关键词和资产探测只作为 Supervisor 的 advisory context,不能直接修改 Graph。根 Run 先持久化 `audit-existing-first / regenerate-art` 决策;前者只启动 `design-director + code-director`,由 code-director 在成功 `asset.list` 后提交权威覆盖合同和精确缺口。Runtime 验证合同后才把已有 owner 投影为 completed,或只打开缺口 owner;根完成门只对持久路由明确复用的 art manifest 忽略 baseline 相同,所有结构、Canvas、私有回执、切片和可见使用验收继续失败关闭。
- 验证:先断言 Supervisor 决策前零 child、固定关键词不会预完成节点,再覆盖完整复用、仅缺图集和明确重做。还要直接经过父完成门,证明合法持久路由不再出现 art baseline gap,并证明删除切片后覆盖合同拒绝复用;旧 root、错误 fingerprint、虚构或遗漏缺口和重复改写路由都应失败关闭。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/game_chat_fast_path.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs`。
## 执行锁移交给未确认启动的异步 future 会制造永久 queued
@@ -395,10 +412,18 @@
- 现象:产品要求画板 `生成角色动作` 返回后按透明序列帧播放和下载,但旧实现或旧测试可能继续把结果当作预览视频处理。
- 原因:后端仍需要先生成 `previewVideoPath` 再抽帧、绿幕去背和落 OSS;如果前端把预览视频当主媒体,就会绕过已经扣绿幕的 PNG 帧,也无法按序列帧打包下载。
- 处理:角色动作结果图层主 `src` 使用 `frames[0].imageSrc`,`mediaType` 固定为 `image-sequence`,`assetKind` 固定为 `character-animation`,完整帧列表写入 `imageSequenceFrames`,`previewVideoPath` 只作为来源信息保留。单图层下载必须生成序列帧 ZIP;画布素材 ZIP 中角色动作写入 `sequences/<编号-标题>/frames/`。不得移除后端原有视频生成、抽帧、绿幕去背和帧落盘流程。
- 验证:`ImageCanvasGenerationLayerModel` 应断言动作结果 `src` 为首帧且 `mediaType="image-sequence"`;画布集成测试应出现 `画布序列帧:角色动作` 图片播放器,不应出现角色动作 `<video>`;导出测试应断言角色动作下载和画布素材导出都包含序列帧 ZIP / frames 目录。
- 处理:角色动作结果图层主 `src` 使用 `frames[0].imageSrc`,`mediaType` 固定为 `image-sequence`,`assetKind` 固定为 `character-animation`,完整帧列表写入 `imageSequenceFrames`,`previewVideoPath` 只作为来源信息保留。生成端确认每帧对象后必须把该帧 `objectKey` 与 `assetObjectId` 一起写入正式 payload 和 `generation_inputs_json.characterAnimation.frames`。单图层下载必须生成序列帧 ZIP;画布素材 ZIP 中角色动作写入 `sequences/<编号-标题>/frames/`。不得移除后端原有视频生成、抽帧、绿幕去背和帧落盘流程。
- 验证:`ImageCanvasGenerationLayerModel` 应断言动作结果 `src` 为首帧且 `mediaType="image-sequence"`;画布集成测试应出现 `画布序列帧:角色动作` 图片播放器,不应出现角色动作 `<video>`;导出测试应断言角色动作下载和画布素材导出都包含序列帧 ZIP / frames 目录;生成测试还应断言首帧与非首帧的稳定引用都被保留。
- 关联:`src/components/image-editor/ImageCanvasGenerationLayerModel.ts`、`src/components/image-editor/ImageCanvasWorldView.tsx`、`src/components/image-editor/ImageCanvasExportModel.ts`、`server-rs/crates/api-server/src/character_animation_assets.rs`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`。
## OSS 导出兜底必须覆盖响应体读取阶段
- 现象:浏览器控制台显示 OSS `206 Partial Content` 后紧跟 `net::ERR_FAILED`,画布预览或素材导出失败,但没有出现预期的 `/api/assets/read-bytes` 兜底请求。
- 原因:`fetch(signedUrl)` 可能先返回一个 `ok` 的 `Response`,网络、浏览器 Range 缓存或传输错误随后才在 `arrayBuffer()` / `blob()` 消费响应体时暴露;如果直连保护边界只包住 `fetch` 和状态码,响应体失败会绕过 fallback。
- 处理:私有素材字节读取必须在直连 OSS 分支内完整消费响应体并重新构造可重复读取的 `Response`;换签、请求、非成功状态或响应体读取任一阶段失败时统一回退同源 `/api/assets/read-bytes`。Abort 仍应直接上抛,不能转化为额外服务器读取。
- 验证:前端服务测试模拟 OSS 返回 `206/ok`、但 `blob()` reject,断言随后请求 `/api/assets/read-bytes` 并返回 fallback 完整字节;同时保留直连成功、局部分片、直连非成功、请求 reject 和 Abort 边界。
- 关联:`src/services/assetReadUrlService.ts`、`src/services/assetReadUrlService.test.ts`、`src/components/image-editor/ImageCanvasExportModel.ts`。
## 图片画布序列帧播放不要复用普通图片淡入样式
- 现象:角色动作序列帧播放时看起来像每帧之间在渐变或闪烁。
@@ -4005,6 +4030,35 @@
- 处理:灰度页只能以 `/admin/api/feature-gates` 为数据源,固定目标列表只登记现役功能;新增或退役业务 target 只修改固定目标注册,不得让通用页面依赖业务列表接口。旧 `creation-entry:*` 目标、接口和页面保持退役。
- 验证:`adminRoutes` 必须包含 `gray-release`,admin-web TypeScript/ESLint/Vitest 不得排除灰度页;页面测试必须断言只请求 feature-gates,并继续覆盖现役固定 target、直接 Gate Key 保存与新 target 状态重置。
- 关联:`apps/admin-web/src/pages/AdminGrayReleaseConfigPage.tsx`、`apps/admin-web/src/app/adminRoutes.ts`、`server-rs/crates/api-server/src/modules/admin.rs`、`docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md`。
## 角色动作不能靠素材主图或通用生成输入恢复
- 现象:角色动作在整画布导出时正常,但从素材库单项下载只得到第一帧 PNG,拖回画布也成为普通静态图片。
- 原因:`editor_asset.image_src` 只指向首帧;若 worker 把完整帧集塞进 `generation_inputs_json`,素材 DTO、用户输入清洗或画布布局任一层丢字段,就会退化成 PNG。再增加一个 `mediaType` 只能掩盖结果字段没有落到正式资源的问题。
- 处理:worker 只把完整帧集与图片序列毫秒时长写入 `editor_project_resource` / `editor_asset` 的 `image_sequence_frames_json`、`image_sequence_duration_ms`;数组位置是唯一帧序,不保存 `frameIndex`,帧数和 FPS 均按需派生。`assetKind=character-animation` 决定序列渲染。素材映射、单项下载和拖回画布只读取这两个正式字段,项目 resource 在保存 / 刷新后继续作为主真相;动作 layout 只保留资源引用和 placement,不再复制正式媒体结果。账号素材提交精选审核时,`editor_showcase_asset` 必须冻结复制相同字段,公开 read model 只返回正式字段。外部 helper 只调用一次动作生成接口并直接使用响应 `resource` / `asset`。
- 画布回填:角色动作会形成“原角色资源 → 预览视频资源 → 最终序列资源”的血缘链。生成响应必须返回已经持久化的最终 resource,前端图层直接使用其 `resourceId`;不能继续构造 `local-resource-character-animation-*`,否则 `appendCanvasLayersWithResources` 会再次创建重复资源。新图层的 `sourceResourceId` 同时使用最终 resource 的直接来源(预览视频 resource),不能继续沿用请求中的原角色 resource;否则结构化保存会在已生成并计费后因血缘不一致而拒绝。修复时只替换资源关联与血缘字段,不要顺带把动作图层显示尺寸从生成占位尺寸改成原始帧分辨率。
- 历史处理:不要再在 read mapper 增加 `generationInputs` / layout fallback。使用 migration operator procedure 按 `asset → project-resource → showcase → canvas` 迁移;只有动作身份已由 `assetKind`、正式字段、嵌套 `characterAnimation` 或权威对象证明后,才解释顶层 `frames/durationSeconds`,否则会把无关任意 JSON 误分类。同一 task 可能同时存在误标为动作的预览 MP4 和最终首帧 PNG,候选查找必须先按权威对象类型做计划态分类,排除视频并要求唯一正式图片序列,不能按原始 `assetKind` 计数。账号素材仍有旧帧、但后来拖入画布的 project-resource 只剩清洗后 `fields/references` 时,project-resource dry-run 必须按同 owner / task / 首帧对象精确消费 asset 计划态结果;apply 仍要求前置 asset scope 已物理完成。canvas 判断已有 resource 是否为动作时也必须消费 project-resource 的计划态类型:旧库误标为动作、但权威对象证明为 preview MP4 且 layout 本身是 video 的图层直接跳过动作清理;layout 明确为 `image-sequence` 却指向该视频时继续形成 blocker,资源规划本身有 blocker 时也不得静默跳过。正式序列还要逐帧用稳定对象路径匹配同 owner / task 的已登记图片对象并补齐 `objectKey/assetObjectId`。迁移不得验证 layout 复制的 `sourceResourceId`:历史 layer 可能仍指向原角色,而最终素材已指向预览资源;清理副本后采用最终素材的 DB 血缘即可,新生成链路仍保持严格校验。正式与旧版结果冲突、候选为零或多个均形成 blocker;脚本诊断应直接打印 scope、ID、原因、owner/project/task、对象身份和来源资源,不能只报 blocker ID。普通 layer 顶层 `mediaType` 在迁移和响应清洗时删除,但嵌套生成参考的 `mediaType` 保留。
- 新写入与验证:动作 `generationInputs` 出现 `characterAnimation/frames/previewVideoPath/frameCount/fps/durationSeconds/screenColorHex`,或正式帧出现 `frameIndex`,HTTP 与 storage 双层拒绝;其它 asset kind 的任意 JSON 不受该动作门禁影响。测试覆盖两种历史 JSON、无关顶层同名字段、正式/旧版相等与冲突、可选帧引用合并、screen color 和 frameIndex 清理、预览 MP4 重分类、幂等、blocker/hash apply、画布 placement 清理/资源补建、正式字段缺失失败关闭和 helper 单请求。
## 图片序列时长不要复用通用媒体秒数
- 现象:把角色动作、视频、音频和上传媒体都写进通用 `duration_seconds`,随后又尝试用持久化 `frame_count/fps/duration_seconds` 互相校验,造成取整口径、生成参数和实际播放时长彼此污染。
- 原因:角色动作需要的是一组图片完整播放一次的精确时长;视频 / 音频的 `durationSeconds` 是生成请求或临时运行态参数。帧数已经由数组长度唯一确定,FPS 也可按需要推导,无需维护三份可冲突真相。
- 处理:资源 / 素材只保存 `image_sequence_frames_json` 与 `image_sequence_duration_ms`,精选审核快照只冻结复制这两个正式字段。角色动作要求至少两帧且毫秒时长大于 0;播放器按 `时长毫秒 / 数组长度` 计算间隔,Spine 导出时再换算秒数并推导 FPS。音频 / 视频 `durationSeconds` 不映射到这两个字段。
- 关联:`server-rs/crates/spacetime-module/src/editor_project_storage.rs`、`src/components/image-editor/ImageCanvasWorldView.tsx`、`src/components/image-editor/ImageCanvasExportModel.ts`。
## 精选角色动作显示首帧还要检查前端 renderer 与逐帧授权
- 现象:精选接口已经返回 `imageSequenceFrames` 和正确的 5 / 6 秒成本,但创作主页或后台审核仍只显示首帧;接入播放器后又可能只有第一帧成功、后续帧换签返回 404。
- 原因:快照字段、展示 renderer 和私有对象授权是三道独立边界。公开 `imageSrc/objectKey` 只代表首帧,不能让前端自动获得完整帧集;顶层精选 exact grant 也不会自动覆盖其它帧对象。
- 处理:公开精选模型必须把 `assetKind=character-animation` 映射到序列 renderer,并携带完整帧与毫秒时长;后台素材查询和精选审核共同透传同一字段并复用 `AdminEditorAssetMedia`。生成端不能在 `ProcessedEditorCharacterAnimationFrame → EditorCharacterAnimationFramePayload` 收口时丢弃逐帧 `assetObjectId/objectKey`,正式序列 JSON 必须保留已确认对象的稳定引用。公开授权在 SpacetimeDB 同一事务快照中只按有效精选动作的同 owner 逐帧 `assetObjectId/objectKey` 匹配,不能放宽 generated 前缀。列表未交互时只读首帧,打开或激活动作预览后也只挂载当前帧和有界预读窗口,避免再次制造换签突发;单帧换签或解码失败时跳过该帧、暂停全帧失败的序列并提供显式重试,不能长期显示空白或旧帧。卡片 hover 与 focus 分别跟踪,只要任一状态仍成立就继续播放,系统请求 `prefers-reduced-motion` 时卡片和弹窗默认暂停,用户仍可在弹窗中手动播放。
- 验证:模型 / 组件测试覆盖 4 / 5 / 6 秒动作、损坏序列不回退 PNG、后台两页共用播放器和未激活列表不逐帧请求;SpacetimeDB 测试覆盖主对象、每帧对象、无关对象、跨 owner 与取消展示后的授权撤销。真实浏览器和端到端验收由人工单独执行,不把 unit / component 结果写成 E2E PASS。
## 可复用资源回填必须保持时间戳单调
- 现象:延迟重试携带比既有行更旧的调用方时间,回填图片序列字段时若无条件写入,会使 `updated_at` 倒退,导致基于时间戳的同步看不到更新或排序错误。
- 处理:同源图片序列字段只允许 `None → Some`,非空冲突失败关闭;发生回填时 `updated_at = max(existing.updated_at, request_timestamp)`。legacy 音频 repair 不派生资源级图片序列或通用时长,重放继续精确匹配。
## 历史钱包消费不能从最近流水或通用订单快照推算
- 现象:后台用户详情要展示累计花费时,直接复用只返回最近 50 条的 `list_profile_wallet_ledger`,或在充值订单每行使用的通用钱包快照里扫描该用户全部流水。
@@ -166,7 +166,7 @@ assetObjectId > objectKey > sourceAssetId > src
4. 对每个素材源读取 Blob:
- `data:image/...` 直接转换为 Blob。
- 同源或可访问 URL 使用 `fetch` 拉取 Blob。
- 私有 generated / OSS 素材先走 `/api/assets/read-url` 换签并由浏览器直接 `fetch` OSS 签名 URL;只有换签或 OSS 字节读取失败时,才 fallback 到同源 `/api/assets/read-bytes`。
- 私有 generated / OSS 素材先走 `/api/assets/read-url` 换签并由浏览器直接 `fetch` OSS 签名 URL;必须在同一保护边界内完整消费响应体,换签、请求、状态码或响应体读取任一阶段失败时,才 fallback 到同源 `/api/assets/read-bytes`,不得在只拿到 `2xx/206` 响应头后提前视为读取成功。
- `mediaType="image-sequence"` 逐帧读取 `imageSequenceFrames`,写入 `sequences/<编号-标题>/frames/`。
- 画布素材 ZIP 内的 `mediaType="image-sequence"` 同步写入 `skeleton.json`,供 Spine Editor 或 runtime 以 slot attachment timeline 方式播放序列帧。
- 单图层普通序列帧导出写入 `preview.gif`,由前端基于成功读取的帧生成动画预览,不依赖压缩包读取时再临时播放 PNG。
@@ -194,6 +194,7 @@ assetObjectId > objectKey > sourceAssetId > src
- 图层的锁定、翻转、分组状态写入元数据。
- `data:image` 图片不经过网络请求即可导出。
- URL 图片 fetch 失败时不中断其他素材导出。
- OSS 返回 `2xx/206`、但响应体读取失败时,自动改走 `/api/assets/read-bytes`,并使用 fallback 的完整字节生成 Blob。
- 动作图层右键菜单把 `导出为` 作为一级入口,二级菜单提供 `序列帧导出(zip)` 和 `Spine 导出(zip)`。
- 单图层普通序列帧 ZIP 包含 `frames/`、`preview.gif`、`metadata.json` 和 `manifest.txt`,且不包含 `skeleton.json`。
- 序列帧导出的 `skeleton.json` 可被独立验证器解析并预览。
@@ -14,7 +14,7 @@
- 中央画布支持背景拖拽平移、滚轮二维平移、`Ctrl / Cmd + 滚轮` 缩放、缩放百分比菜单、显示所有元素和固定比例缩放。
- 画布左下角提供 Lovart 式状态控件:背景色圆点、素材 / 图层入口、小地图开关;小地图显示图层缩略分布和当前视口框,点击小地图执行显示所有元素。
- 画布 chrome 的边框、hover / 选中态、吸附 / 框选参考线、生成类按钮和通用 active 控件使用陶泥儿暖色主题(以 `--platform-accent`、陶土橙主按钮和深棕文字为基准),不得回退为黑色或蓝色主题;元素类型自身的识别色可继续保留。
- 画布中的图片可展示、悬浮显示图片 Resolution 尺寸与边框,点击后在图片上方显示浮动工具栏;浮动工具栏只保留当前可执行的编辑动作,不放调整 / 复制 / 删除 / 查看信息占位按钮。图片右上角素材类型标签、图片信息角标和悬浮尺寸标签在画布缩小时必须按 viewport 反向缩放,保持屏幕可读尺寸。素材类型采用双层语义:`editor_project_resource.asset_kind` 是跨布局共享的资源默认类型,`editor_canvas_layer.asset_kind_override` 是当前布局实例可空的覆盖值,显示与功能判断统一使用 `effectiveAssetKind = assetKindOverride ?? resource.assetKind`;两层都为空时显示 `未知`。点击标签只修改或清除当前图层的 `assetKindOverride`,不能创建新资源、替换 `resourceId` 或修改其他引用同一资源的图层;清除覆盖后立即恢复继承资源默认类型,也不能触发图层选择 / 拖拽事件。图片信息角标使用圆形 `i` 图标,不使用中括号或花括号样式。图片不再维护独立展示 `Size` 字段,画布显示宽高统一取 `originalWidth/originalHeight`(图片信息中的 `Resolution`)。
- 画布中的图片可展示、悬浮显示图片 Resolution 尺寸与边框,点击后在图片上方显示浮动工具栏;浮动工具栏只保留当前可执行的编辑动作,不放调整 / 复制 / 删除 / 查看信息占位按钮。图片右上角素材类型标签、图片信息角标和悬浮尺寸标签在画布缩小时必须按 viewport 反向缩放,保持屏幕可读尺寸。素材类型采用双层语义:`editor_project_resource.asset_kind` 是跨布局共享的资源默认类型,`editor_canvas_layer.asset_kind_override` 是当前布局实例可空的覆盖值,显示与功能判断统一使用 `effectiveAssetKind = assetKindOverride ?? resource.assetKind`;两层都为空时显示 `未知`。点击标签只修改或清除当前图层的 `assetKindOverride`,不能创建新资源、替换 `resourceId` 或修改其他引用同一资源的图层;清除覆盖后立即恢复继承资源默认类型,也不能触发图层选择 / 拖拽事件。标签覆盖必须与资源默认类型属于同一媒体族:`character-animation` 独立为动作族,`video` 独立为视频族,`audio/sound-effect/background-music` 属于音频族,其余类型和空默认值属于图片族;标签菜单禁用跨族选项,更新回调再次校验。加载到历史跨族 override 时不得静默过滤图层,必须清除内存 override、按资源默认类型恢复图层、向用户提示并排队修复保存。图片信息角标使用圆形 `i` 图标,不使用中括号或花括号样式。图片不再维护独立展示 `Size` 字段,画布显示宽高统一取 `originalWidth/originalHeight`(图片信息中的 `Resolution`)。
- 默认工具为选择模式;底部工具栏采用 AI 画布工作流工具组:选择、抓手、上传、生成图片、生成视频、生成音乐、生成规范、生成角色形象、生成图标素材、生成 UI 设计图。底部栏不再展示文字工具、形状标注工具和导出工具;上传与生成图片之间、生成音乐与生成规范之间各有一个半图标高度分割线。
- 鼠标中键拖拽始终平移画布;长按 Space 临时进入抓手模式,松开后恢复原工具。
- 图片拖拽时显示水平 / 垂直吸附参考线,吸附到其它图层、生成占位框或画板的边缘与中心线;当移动元素接近两个同轴元素形成的等距位置时,支持横向或纵向等距吸附。
@@ -206,7 +206,8 @@
- 上传按钮和拖拽上传都支持多文件;底部工具栏的上传入口选择文件后直接进入“上传素材”并在当前画布视口中心创建画布图层,素材栏文件夹内的上传入口只写入对应素材文件夹、不自动入画布;拖到文件夹或该文件夹内素材时进入目标文件夹;拖到画布时进入“上传素材”并在投放点创建画布图层。上传图片必须在创建占位素材、画布图层和账号级素材记录前先读取原图 Resolution,图层宽高、`originalWidth/originalHeight` 和素材库 `width/height` 都使用图片本身尺寸;上传视频同样在创建素材和图层前读取视频 metadata 宽高,保证单层下载或 ZIP 导出的真实视频文件重新导入后仍按文件自身尺寸入画布;仅在无法解析尺寸时才使用对应媒体兜底尺寸。OSS 直传确认成功后,即时占位素材、即时画布图层和补充图片尺寸探测必须使用本次换签得到的 signed URL,不得把私有对象的裸 `/generated-*` 兼容路径交给浏览器读取;账号素材持久化仍只写 `legacyPublicPath`、`objectKey` 和 `assetObjectId`,禁止把会过期的 signed URL 写入资源或素材记录。
- 音频 / 视频素材卡和画布媒体图层必须提供稳定的非文字视觉预览:优先使用 `thumbnailSrc` / 视频 `poster`,没有真实首帧或音频封面时使用由媒体类型、素材名和地址派生的确定性视觉底图。视频图层使用原生 `<video controls preload="metadata" playsInline>` 播放,外层图层仍承接选择和拖拽语义;音频图层播放前继续通过 `/api/assets/read-url` 换签。画布素材导出按 `mediaType` 保留真实媒体格式:图片进入 `images/`,音频 / 视频进入 `media/`,文件扩展名从响应 MIME、`objectKey` 或源 URL 推断,不得把音频 / 视频导出成 PNG。
- 画布素材 ZIP 的 `metadata.json` 只保存前端信息弹窗和导出文件列表可见的展示快照:项目标题、导出时间、图层标题、文件路径、类型、生成输入、模型显示名、Task 短 ID、Object 显示值、Resolution / 时长和导出错误。导出的生成输入只保留用户实际填写或选择的内容;系统默认兜底提示词、固定工作流提示词、内置图标描述、UI 提取素材固定提示词等内置提示词即使存在于历史 `generationInputs`,也不得写入导出元数据。不得把 `projectId`、`layerId`、`resourceId`、`sourceAssetId`、`sourceResourceId`、原始 `prompt` / `actualPrompt` / `provider` 或画布坐标、锁定、隐藏等布局状态写入导出元数据;Object 字段仅沿用信息弹窗当前可见值。
- `生成角色动作` 的完成结果按序列帧素材处理:图层主 `src` 使用 `frames[0].imageSrc`,`mediaType` 固定为 `image-sequence`,`assetKind` 固定为 `character-animation`,完整帧列表写入 `imageSequenceFrames`;`previewVideoPath` 只保留为上游预览视频来源,不作为画布主媒体。下载和 ZIP 导出必须因此得到序列帧 ZIP / frames 目录,不能回退为预览视频或首帧 PNG;后端抽帧后逐帧保存带绿幕源图,上传时消费并释放原帧字节,再用该对象的 600 秒 OSS URL 完成透明化并落盘透明帧素材。
- `生成角色动作` 的完成结果按序列帧素材处理:`assetKind` 固定为 `character-animation`,图层主 `src` 使用首帧,前端内部据 `assetKind` 派生 `CanvasMediaType="image-sequence"` 选择播放器。`previewVideoPath` 只作为上游来源,不作为画布主媒体。下载和 ZIP 导出必须得到序列帧 ZIP / frames 目录,不能回退为预览视频或首帧 PNG;后端抽帧后逐帧保存带绿幕源图,上传时消费并释放原帧字节,再用该对象的 600 秒 OSS URL 完成透明化并落盘透明帧素材。
- 素材库和项目资源 API 不返回独立 `mediaType`;前端只按 `assetKind` 映射渲染器:`character-animation → image-sequence`、`video → video`、`audio/sound-effect/background-music → audio`、其余 → image,不按扩展名猜类型。最终动作 asset / resource 平铺返回 `imageSequenceFrames/imageSequenceDurationMs`;正式帧对象不含 `frameIndex`,数组位置是唯一播放顺序,帧数取数组长度,FPS 按帧数与毫秒时长即时推导。只有至少两帧且播放时长大于 0 的角色动作素材才能进入序列播放器,否则按损坏素材失败关闭。预览视频作为独立 `assetKind=video` 资源保存,最终动作以 `sourceResourceId` 保留来源关系,不重复保存路径。从素材库点击、HTML5 拖放或指针拖放到画布时必须复制完整字段并先创建正式 project resource;新 layout 只保存 resource 引用和几何,不保存帧集或预览视频,刷新后从 project resource 恢复循环播放。历史 `local-*` 自包含动作布局仍可读取。
- 素材面板支持按素材名、文件夹名、生成信息、用户可见模型、任务和媒体类型搜索,并支持选择模式框选,一次选中多个素材,并可批量移动或删除上传素材。Provider 与 `isEditorInternalProcessingModel(...)` 识别的内部处理模型不得进入搜索索引,避免出现隐藏字段可命中或命中但无可见匹配字段;原始审计元数据只保留给服务端和后台管理读取,普通用户(包括素材 owner)不得通过响应取得。
- 图层面板支持按图层名、生成信息、用户可见模型、任务和媒体类型搜索;Provider 与内部处理模型同样不得进入搜索索引。普通用户图层快照也不得携带这些内部字段。支持选择多个图层后创建图层组,组名和 groupId 随画布布局保存。
- 小地图支持拖拽视口框,拖动时画布 viewport 跟随移动;`pointermove` 更新必须通过 `requestAnimationFrame` 合帧,结束拖拽时 flush 最后一帧,避免高频 pointermove 直接压垮 React 渲染和项目持久化链路。
@@ -32,10 +32,12 @@
- Windows Runner 私有文件初始化:父 AppData 已归当前 `TokenUser` 后,新建 `agent-runner.lock`、endpoint 临时文件、project-owner 诊断临时文件与 real-E2E 私有文件的 owner 仍可能采用 token 默认 owner `Administrators`。固定 stale lock 只有在父目录已验证为当前用户 protected 私有 DACL、Windows 不共享独占句柄已取得、且句柄确认普通文件、非 reparse point、链接数为一时才允许修复;其它三类文件只允许在本进程 `create_new` 成功且仍持有同一独占句柄时初始化 `TokenUser` owner / DACL,再写入、原子安装并严格复核,初始化失败必须清理刚创建的文件。既有 durable endpoint / diagnostic 读取不得自动接管;活锁不得截断,只有 sharing / lock violation `32/33` 表示占用,access denied 等其它错误立即返回。父进程观察到 Runner 子进程退出后立即返回错误,不等待完整 30 秒 deadline。
- 启动诊断:独立 release 的 `startup.log` 和 `agent-runner.log` 只记录有界、脱敏的阶段与 stdout / stderr 摘要,凭据、AppData 路径和其它绝对路径不得原样落盘;单文件达到 256 KiB 后只轮转保留一份 `.previous.log`。`startup.log` 优先写独立 AppData,目录不可写时回退到系统 TEMP 下的 `Genarrative-Game-Chat-Diagnostics`;Tauri context、窗口 URL、AppData、Runner 或 `.setup()` / `.build()` 初始化失败时,Windows 必须显示可见错误对话框并给出诊断日志位置,不能只在无控制台 release 中静默退出。
- 对话与事件:窗口固定使用 `project-supervisor + autonomous-game-build`,继续复用 active Session、External Runner、持久 conversation、流式回复、same-run steer、工具确认与用户追问。以 `/` 开头的输入必须继续走现有内置命令解析,例如 `/preview` 只能生成 `preview.start` 确认卡,不得作为自主构建任务投递给 Supervisor。界面聚合当前 Supervisor 父 run 及其直接委派专业 Agent 的最新原始事件,按时间倒序稳定去重并标注 Agent;默认显示 4 条,可展开至最新 20 条。原始 `summary / detail` 仍只作 Runtime 状态投影,不直接写入 conversation。需要进入聊天的事件必须由 Rust 同步生成唯一 `eventId` 与安全 `publicText`;前端只按这两个字段形成独立 assistant 消息,无 `eventId`、空 `publicText`、legacy 事件和内部 tool / Provider / Runner 协议一律忽略。
- 公开消息硬门:模型仍负责 Supervisor / 专业 Agent 回复的业务语义,Runtime 不根据 tool 或 Provider 事件自行补写业务结论;但用户直接投递的 Project Supervisor 根后台任务必须先落为不可执行的 `preparing / public-status-pending`,再以 `runtime-public-status-*` 稳定 message ID 把“任务已接收,正在启动处理”写入项目 conversation,成功后才转为 `pending / queued`;恢复预检只读,只能在验证到同 run accepted 消息后把该任务临时分类为可恢复,真实 resume 持有 Agent 锁后才可持久提升为 `pending / queued`;写入失败则落为 `failed / public-status-write-failed`,不得继续执行。这些 Runtime 公开状态只供 UI 展示,prompt 构建器必须按稳定前缀排除。根 Supervisor 通过正式失败 / 预算耗尽收束或 game-chat 绝对硬期限进入 reconciliation 时,必须在 task、event、state 等其它终态投影之前先幂等写入一条脱敏、用户可理解的失败消息;专业 Agent 命中该全局硬期限时,也必须通过权威 Run Profile 和根 task 将同一根终态写入项目 conversation,同时保留 child 私有 Session 状态;状态文件本身写坏也不能导致零公开结果。当前 Runtime 自称根 agent/run 时,其 session 和两个 parent 字段必须与权威根 task 一致;任一身份冲突必须失败关闭,不得以另一 session 派生第二条项目终态。前端把该前缀识别为 Runtime-owned,同秒时排在触发它的 Supervisor 用户消息之后,不二次持久化;仅根 Supervisor 的 `turn.started / turn.failed / turn.budget_exhausted` 只保留在 Runtime 详情和进度投影中,不能再生成第二条聊天消息,专业 Agent 的公开启动事件仍可见。该硬门不改变 final-reply 的唯一性;非 Supervisor 专业 Agent 的失败消息继续留在对应 Agent Session,不把私有诊断写进项目 conversation。
- 启动恢复和续跑边界:本条取代上一条中“只有 accepted 才可恢复”的窄口径。若进程在 Supervisor 用户消息已持久、accepted 未持久之间崩溃,只读 preflight 可以把该 `preparing` 识别为可恢复,但不改写 task/conversation;真实 resume 持有 Agent 锁后必须先幂等补写 accepted,再提升为 `pending / queued`。用户消息或 accepted conversation 已落盘而辅助审计失败时,以 conversation 为公开真相继续入队,不留下“已接收但永不执行”的任务;根终态首次公开写入的瞬时失败必须在终态投影后用相同 message ID 重试。receipt / isolated-join 等带 parent 的 Supervisor continuation 不再另写 Session 终态,只保留单一后端公开事件;`runtime-task-*` 与 `runtime-public-status-*` 共享同 run 的不透明关联摘要,秒级时间戳下多个连续任务必须按实际 run 对应的 `user -> accepted -> terminal` 顺序交错展示。
- Supervisor 进度播报:聊天消息流内保留且只保留一条当前 run 的 Runtime-owned 播报卡,由客户端从 manifest 任务图、Supervisor 结构化计划、`loopIteration`、当前动作、直接委派专业 Agent 及其持久事件确定性整理;显示当前轮次、任务 / 计划进度、活跃 Agent、最近试玩与静态检查、返工决定、代码修改和截图检查证据。同一 run 原位更新,切换 run 时替换,不调用额外模型、不追加持久 conversation,也不改变最终 assistant 回复的唯一性;任意详情必须有界且不展示绝对路径、Provider 元数据或内部指纹。
- ready-task 启动活性:`background_task.queued`、`autonomous_ready_task.scheduled`、Runner heartbeat 或执行锁已移交都不等于 child 已启动。实际持有执行权的 Runner 必须在释放项目写锁后同步写入 child 的 running task、`turn.started` 与 started journal,再把已启动 state 和 per-Agent 执行锁交给已确认开始轮询的独立 execution worker;同步启动或 worker 接管失败时,要在仍持有执行锁期间依次把 child 和 manifest Graph 节点明确落为 failed,再释放锁并让 parent 收到调度错误。`autonomous_ready_task.scheduled` 只作诊断审计,其写入失败不能阻断 durable child 启动;external client 只 wake Runner,不在客户端抢占执行。Supervisor 进度卡通过 durable `startedAt`(旧 Run 从完整 task journal 恢复,最新 task-record fallback 保持 0)显示真实持续时间,并以父 Run 与当前关联专业 Agent 的最大事件时间计算运行态活跃度:运行超过 5 分钟无新事件时显示“运行中 · 疑似停滞”和静默时长;等待用户、等待确认、Provider retry、视觉资产、进程会话、pausing 与 paused 不误报。父 Run terminal 后,持续时间冻结在父 Run 自身最后活动,不随 child 晚到收口事件增长。消息时间统一校验为 JavaScript 可表示的 Date;越界值显示“时间未知”且不写无效 `datetime`。实时回复只显示 response stream 自己的 `updatedAt`,缺失时同样显示“时间未知”,不能借用其它 Runtime 活动时间或随前端时钟漂移。该提示只提供可观测性,不改变 Runtime/manifest 正式状态。
- ready-task manifest 漂移:父 Supervisor 必须分别判断“能否调度新节点”和“是否存在必须等待的工作”。派生视觉需要父规划修复时不再调度新 child,但当前最新且活跃的根 Run 下,只要存在确定性 runId、scheduler source、正确父绑定且 durable journal 为 queued/running 的 ready child,父 Run 就保持 `waiting-for-manifest-tasks`,不能因旧 hydration 快照把 manifest running 覆盖成 pending 而提前 fixed-graph-stalled。game-chat child 可在相同严格身份下容忍 pending 漂移;正式产物、Canvas、revision、`game.static_smoke` 与 `preview.validate` 门禁不放宽。GUI/CLI、旧父 Run、终态、确认/用户输入/reconciliation、伪造绑定或非确定性 runId 全部失败关闭;更新根 Run 后旧 child 不得继续维持新 DAG 或投影完成。
- 增量既有美术复用:game-chat 用户明确要求把 UI、方块或当前画面替换、接入、使用或复用美术资源时,只有项目已有非占位游戏,且 art spec、透明图集、Canvas 登记、私有图集合同、四张语义切片和 art manifest 全部通过严格验收,Graph reset 才保留 `art-director / art-asset-plan=completed`,重新打开设计、代码和试玩节点。根完成门只在同一合法复用条件仍成立时忽略 `assets/manifest.art.json(unchanged-from-run-baseline)`,其余产物、视觉合同和代码可见使用验收不放宽;任一切片或私有证据损坏后豁免立即失效。普通新游戏、明确要求全新/重做美术、否定使用旧美术或占位项目必须把美术节点恢复 pending。该增量任务从同 Session 的可信 Supervisor 历史根 Run 继承最近的非 Generic 试玩场景,只跳过纯继续与既有美术接入语义,遇到更新的详细 Generic 新目标立即停止;GUI 创建的俄罗斯方块转入 game-chat 后仍使用 `tetris-v1`,但不能跨后续项目目标借用更老场景。不得以增加 loop 预算、伪造产物 revision、机械改写 manifest 或重放历史图片 action 代替 Graph 语义修复。
- Supervisor 主导的条件美术路由:game-chat 的关键词、用户是否报告“美术未接入”、占位状态和当前资产探测只形成 `advisoryOnly=true` 的补充上下文,不得直接重置 Graph、预完成美术节点、选择复用/生成分支或继承历史试玩类型。当前根 Run 没有持久化 Supervisor 决策时,scheduler 不启动任何 manifest child,Supervisor Provider 必须先通过 auto-safe 的 `agent.route_manifest` 选择 `audit-existing-first` 或用户明确要求整体重做时的 `regenerate-art`。决策后首波只开放 `design-director + code-director`;`code-director` 必须先成功调用 `asset.list`,再以同一工具提交 `use-existing-art / generate-missing-art / regenerate-art` 与精确 `missingAssetSlots`。Runtime 只负责校验 root/child 身份、当前 revision、Canvas 登记、私有图集合同、四张语义切片、art manifest、覆盖 fingerprint 和路由 fingerprint,并据此把 `art-director / art-asset-plan` 投影为 completed 或 pending;缺少 art spec 若同时使图集引用合同失效,两个槽位都属于真实缺口,不能为了少调一个 Agent 伪称图集可复用。只有持久路由明确复用 `art-asset-plan` 时,根完成门才忽略 `assets/manifest.art.json(unchanged-from-run-baseline)`;其余产物、可见代码使用与试玩门禁不放宽。明确 `regenerate-art` 时两个正式图片 owner 使用严格绑定当前 root Run 的原位替换授权。纯“继续”仍走既有正式 continuation 合同;普通美术措辞不得借用更老项目的具体试玩场景。不得以增加 loop 预算、伪造 revision、机械改写 manifest 或重放历史图片 action 代替 Supervisor 决策和程序侧审计。
- ready-task 对账取消续跑:未知工具结果仍停在 `needs-reconciliation` 且禁止自动重放;人工核对后显式取消原 child,保留 cancel tombstone,旧 child 和旧父 Run 按真实终态收口。若随后创建同 Session、同 Supervisor source、同有效任务语义的 continuation,新完成合同只对同时具有历史 `failed / needs-reconciliation`、最终 `cancelled` 和 durable tombstone 的 ready-task,把当前 manifest 对应 failed 节点恢复为 pending,并由 scheduler 创建全新 child Run。manifest 的读取、failed 筛选、每任务一次的 child journal 索引、证据重验和写回必须位于同一项目写锁域;较新的无 child 根 Run 只有在 durable journal 精确表明为旧 failed Graph 在进入调度前即失败时才能跨过,scheduler 自身失败必须阻断借用更老 tombstone。普通失败、无 tombstone、不同 source/Session/任务语义或证据冲突均保持失败关闭;不得复活旧 pending action、补造 observation 或把取消任务标成 completed。
- 完成门静态分析预算:Canvas 视觉门必须先做只会提前拒绝的词法预检。经典或模块脚本同时不含大小写精确的 `import` 与 `export` 字节序列时,不运行模块依赖语义分析;纯 `export ... from` / `export * from` 仍须进入正式模块图分析。当前脚本不含目标文件名或任一已绑定 DOM 图片元素 ID 时,先低成本解码 `\\xNN`、`\\uNNNN`、`\\u{...}`、简单转义和续行;解码后仍无候选才不运行完整 Canvas alias / 函数可达性分析,解码不确定则保守进入 Oxc。存在任一候选时仍执行原 parser、semantic binding、解码后的 computed 属性/StringLiteral 路径、可达 `drawImage`、可见 Canvas、路径大小写和动态 namespace 写入门禁;HTML 中存在某个绑定元素不得使所有无关 JavaScript 单元进入重分析,禁止把词法命中当作通过条件。
- Provider 故障展示:Provider retry 的“是否可重试”继续使用 `upstream-5xx` 等稳定类别判断,但 durable retry record 保留安全的精确 `upstream-<HTTP status>` 身份。等待态必须从真实 record 显示 HTTP 状态、`nextAttempt/maxRetries` 与当前持久退避剩余秒数,例如“Provider 上游返回 HTTP 503,准备自动重试 1/3;预计 8 秒后重试”;不得以动画或前端自增计时伪造 attempt。重试耗尽的 Runtime 私有错误只保存 `kind/httpStatus/fingerprint/chars/retryAttempt/maxRetries/retryState`,前端和持久 conversation 仅在字段顺序、范围、状态一致且无尾随正文时派生“上游服务返回 HTTP 503;自动重试已耗尽(3/3)”;其它错误使用固定安全摘要。Provider 响应正文、URL/query、凭据、本地绝对路径、fingerprint、字符数和 `[redacted ...]` 占位符均不得进入用户可见消息。
@@ -57,26 +59,27 @@
## 2026-07-31 game-chat 输出、单轮预览与平台美术资源
- 对话输出:game-chat 的 Supervisor `ready` response stream 继续以稳定身份显示;`design-director / art-director / art-asset-plan / code-director / code-prototype / preview-readiness / preview-playtest` 的 `requestKind=final-reply` 且 `status=ready|committed` 的非空安全回复也分别以 Agent、Session、run、request slot 和 response revision 形成 durable message ID,并带 Agent 标签逐条追加到项目聊天。每条 Rust `eventId + publicText` 公开输出同样形成独立 durable 消息。所有这些消息通过 `append_local_conversation_message` 的顶层 `messageId` 幂等写入,事件、轮询、React StrictMode 和 hydration 重放不重复;tool-plan、半成品 stream、原始事件 detail、命令正文、绝对路径、Provider / Runner 元数据、哈希和凭据不得进入聊天。普通 `supervisor-chat` 保持原有 transient response 行为。
- 对话输出:game-chat 的 Supervisor `ready` response stream 继续以稳定身份显示;七任务目录中本轮实际启动的专业 Agent,其 `requestKind=final-reply` 且 `status=ready|committed` 的非空安全回复分别以 Agent、Session、run、request slot 和 response revision 形成 durable message ID,并带 Agent 标签逐条追加到项目聊天。条件路由跳过的美术节点只要求 manifest 正确投影为 completed,不伪造 Agent 回复。每条 Rust `eventId + publicText` 公开输出同样形成独立 durable 消息。所有这些消息通过 `append_local_conversation_message` 的顶层 `messageId` 幂等写入,事件、轮询、React StrictMode 和 hydration 重放不重复;tool-plan、半成品 stream、原始事件 detail、命令正文、绝对路径、Provider / Runner 元数据、哈希和凭据不得进入聊天。普通 `supervisor-chat` 保持原有 transient response 行为。
- 对话输出中的 `eventId + publicText` 只指需要独立进入聊天的进度事件;`turn.started` 和根 Run 终态失败事件由上一条 `runtime-public-status-*` 硬门覆盖,不得同时转成事件消息。专业 Agent child 的失败消息继续留在其 Agent Session,根项目聊天只接收 Supervisor 终态失败、明确公开进度和安全 final-reply,避免一项失败被 Runtime event 与 conversation 各播报一次。
- 单轮收束:game-chat source 只生成至 `preview-playtest` 的 manifest seed task,试玩完成后父 Run 直接进入完成门,不再调度 `publish-strategy` / `publish-package`;`agent.schedule_ready` 必须按当前 Supervisor Run 的持久 source/profile 选择同一 source-aware scheduler,不能绕过该边界。`task.list` 对同一 root source 必须从任务行、readyTaskIds 和统计中排除两个发布节点,`agent.delegate` 也必须按 root binding 拒绝直接委派这两个节点,不能让 Provider 用“读取完整 DAG 后手工委派”恢复已裁掉的发布阶段。完成门满足且 collaboration、Provider batch、进程会话、视觉资源等非验证屏障全部清零后,Runtime 必须用确定性回复直接收束结构化计划并结束父 Run,不再请求下一次 Provider 工具计划。普通 GUI / CLI 仍执行完整发布 DAG。
- 轮次展示:`loopIteration` 只是同一父 Run 内的 Provider / 工具规划循环,用于委派、回执、返工和验收,不是用户发起的游戏生成轮次。game-chat 的进度卡、当前工作和“最新状态”事件统一显示“本轮”,整个页面不向用户显示“第 N 轮”;完整 GUI / CLI pre-publish 任务图仍可显示 `x/14`,但首版只按七项任务显示 `x/7`(详见 2026-08-03 小节),不得把两个发布节点计入任一分母。父 Run 终态后移除运行中进度卡,只保留终态阶段记录与预览。
- 平台美术资源:game-chat 恢复正式视觉 DAG。`art-director` 先通过平台 `images/generations(kind=spec)` 生成并登记 `assets/art-spec.png`;`art-asset-plan` 再以该规范图的稳定 resourceId 调用 `icon-spritesheets/generations` 生成真实透明 `assets/art-spritesheet.png`,并把响应中的 `iconImageSrcs` 下载为本地独立切片,写入 `assets/art-spritesheet-slices/manifest.json`。`code-prototype` 必须等待图集与切片清单,并在活动 Canvas 中分别绘制玩家、方块/目标、障碍/场景与反馈四类切片;规范图只作 reference,纯代码核心画面、猜测图集等分网格、整图 `<img>` / CSS 背景、完整图集直绘或只出现路径均不得完成。
- 平台美术资源:game-chat 保留正式视觉 DAG,但是否进入生成节点由 2026-08-06 的持久条件路由决定。现有正式资源覆盖完整时直接复用;存在真实缺口或 Supervisor 明确选择整体重做时,`art-director` 才通过平台 `images/generations(kind=spec)` 生成并登记 `assets/art-spec.png`,`art-asset-plan` 再以该规范图的稳定 resourceId 调用 `icon-spritesheets/generations` 生成真实透明 `assets/art-spritesheet.png`,并把响应中的 `iconImageSrcs` 下载为本地独立切片,写入 `assets/art-spritesheet-slices/manifest.json`。`code-prototype` 必须等待复用或补齐后的图集与切片清单,并在活动 Canvas 中分别绘制玩家、方块/目标、障碍/场景与反馈四类切片;规范图只作 reference,纯代码核心画面、猜测图集等分网格、整图 `<img>` / CSS 背景、完整图集直绘或只出现路径均不得完成。
- 验证:前端运行时模型定向测试、Rust completion/source/asset 合同测试、`cargo fmt --check`、`npm run check:encoding` 与 `git diff --check` 必须全部执行;Windows 文件锁竞态只可作为既有测试失败单独记录,不得将其改写为本次改动的通过证据。
## 2026-08-01 game-chat 首版七任务素材完整快车道与美术硬门
- 首版任务边界:game-chat 首版只展示 `design-director`、`art-director`、`art-asset-plan`、`code-director`、`code-prototype`、`preview-readiness`、`preview-playtest` 七项任务,进度统一显示为 `x/7`;首波并行激活三个 Director,`art-asset-plan` 依赖 `design-director + art-director`,`code-prototype` 同时等待三个 Director 与 `art-asset-plan`,后续验证串行推进。不把完整 GUI / CLI 任务图的其它节点投影到该页面,也不显示内部 Provider / child loop 轮次。
- 首版任务边界:game-chat 首版只展示 `design-director`、`art-director`、`art-asset-plan`、`code-director`、`code-prototype`、`preview-readiness`、`preview-playtest` 七项任务,进度统一显示为 `x/7`;当前根 Run 在 Supervisor 持久路由前零 child,`audit-existing-first` 决策后的首波只激活 `design-director + code-director`。code-director 审计后,完整资源把两个美术节点投影为 completed;真实缺口或整体重做才按依赖开放对应美术 owner,随后 `code-prototype` 与验证节点串行推进。不把完整 GUI / CLI 任务图的其它节点投影到该页面,也不显示内部 Provider / child loop 轮次。
- 时间预算:从 game-chat 父 Run 接受用户请求开始,素材完整首版使用 `4200` 秒软预算;父 Run 与其全部 child Run、等待和回收阶段共享从 root `bound_at` 计算的 `4500` 秒绝对硬上限。该值来自现有两次串行生成各自最长 35 分钟的客户端等待合同,并为代码兜底、静态检查和双视口试玩保留 5 分钟。整个 Runtime pass 受同一 `timeout_at` 约束;硬上限内未通过完成门必须失败关闭,不得为了守时跳过图集、换普通生图或回退纯代码核心画面。即使完成证据恰好在上限后到齐,单轮确定性收束也必须再次检查累计预算并拒绝写入 `single_round_converged`。
- Provider 次数:首波 `design-director / code-director` 的规划请求与 `art-director` 的确定性规范图生成并行;规范图和设计方向就绪后,Runtime 确定性派发 `art-asset-plan` 的 icon-spritesheet 生成。图集登记后 `code-prototype` 最多执行一次 Provider 首版写入请求;软预算耗尽时只允许生成真实加载并裁切图集的确定性本地兜底。Provider 成功返回后由 Runtime 依次执行确定性的 `game.static_smoke` 与 `preview.validate`。
- Provider 次数:Supervisor 先用一个 Provider turn 理解目标并持久化条件路由;`audit-existing-first` 的首波仅请求 `design-director / code-director`,code-director 成功 `asset.list` 并提交覆盖合同后,Runtime 才确定性派发真实缺口对应的 `art-director / art-asset-plan`。完整复用不得调用图片生成接口;图集复用或登记完成后 `code-prototype` 最多执行一次 Provider 首版写入请求。软预算耗尽时只允许使用已登记图集和当前 resourceId 切片清单的确定性本地兜底,Provider 成功返回后由 Runtime 依次执行确定性的 `game.static_smoke` 与 `preview.validate`。
- 可玩兜底:软预算或首版 Provider 无法及时完成时,只能为已显式实现真实语义的玩法生成完整、自包含、无远程运行依赖的中文 HTML 模板;未知玩法失败关闭,不能只替换标题后套用固定收集游戏。俄罗斯方块模板必须包含 10×20 棋盘、下落、移动、旋转、锁定、消行和触顶失败;收集模板只匹配明确收集类目标。模板必须从 `ready` 开始,包含真实 Canvas 绘制、`requestAnimationFrame`、键盘 / 触控主要操作、唯一可见且启用的 start / primary-action / restart 控件,状态 JSON 只随真实输入、状态迁移或模拟状态变化推进,并能在 primary-action 后保持 `playing`、在 restart 后稳定回到 `ready | playing`;不得在开始前固定进入 `lost`,不得通过固定失败冒充试玩通过,也不得由纯渲染帧空转 `sequence`。兜底只允许写入缺失或精确初始化占位的 `game/index.html`;存在非占位入口时,当前 `code-prototype` 必须读取并实际 patch,取得本人 `mutationRevision` 后再静态检查和试玩,不得反复用只读 smoke 冒充续作。
- 平台图片:`art-spec.png` 只用于约束色板、材质、形状与后续派生,不是运行时背景、角色、目标或图集。`art-asset-plan` 必须以其稳定资源 ID 走专用 icon-spritesheet 路由,透明像素、generation route/kind、同画布归属和 reference resource ID 继续由 Runtime 验证。完整透明图集在编辑器合同中即使产生 `sliceWarning` 仍可登记,但 game-chat 不能据此猜测 atlas 坐标;图集 resourceId 缺失或空白时必须在任何本地写入前失败,四个切片均须保留与整图完全一致的 `sourceResourceId`,切片数量也必须在正式图集登记前严格等于四,多或少都失败关闭。四类唯一性按尺寸与解码后的规范 RGBA 像素摘要判断,不能用不同 PNG 压缩或 ancillary chunk 冒充不同素材。主图、四张 canonical 切片和切片清单必须共同提交,并把主图/切片摘要及 Canvas 身份冻结到 `.agent/runtime/art-spritesheet-contract.json` 私有回执;后续主图安装、回执或登记失败时恢复旧合同,避免出现旧图集绑定新切片。进程在固定主图落盘后崩溃时,保留的 generation 账本只允许按远端预期摘要幂等补齐同一组合同,路径内容冲突继续失败关闭。全部切片下载累计最多 `32 MiB`。完成门重新读取本地素材时仍执行相同文件大小、解码内存、内容摘要、规范像素摘要、可见 alpha 与四类唯一性校验,并要求公开切片清单、当前 Canvas 登记与私有回执完全一致,后续代码 Agent 不能通过同时改写素材和公开清单重定义合同。`postprocess-failed-source-preserved`、无真实 alpha、缺文件或缺登记同样失败关闭。首版只在 `preview-playtest` 后单轮收束,不进入发布节点。
- 关联验收:快车道必须分别验证三 Director 首波并行、`art-asset-plan` 在代码前完成、`x/7` 投影、七个专业 Agent 安全 final-reply、4200 / 4500 秒累计预算、规范图到 icon-spritesheet 的真实引用、`iconImageSrcs` 本地持久化与资源 ID 绑定、失败续跑目标继承、非占位入口禁止整文件覆盖、纯代码核心画面、猜测单个 atlas 裁切与整图展示失败、四类独立切片可见使用通过、action-driven `sequence`,以及当前 revision 的静态 smoke 与浏览器试玩。
- 关联验收:快车道必须分别验证 Supervisor 决策前零 child、审计首波只有 design/code、完整覆盖零图片生成、精确缺口只启动对应 owner、显式重做两个正式 owner、`art-asset-plan` 在代码前完成、`x/7` 投影、实际启动的专业 Agent 安全 final-reply、跳过节点正确终态投影、4200 / 4500 秒累计预算、规范图到 icon-spritesheet 的真实引用、`iconImageSrcs` 本地持久化与资源 ID 绑定、失败续跑目标继承、非占位入口禁止整文件覆盖、纯代码核心画面、猜测单个 atlas 裁切与整图展示失败、四类独立切片可见使用通过、action-driven `sequence`,以及当前 revision 的静态 smoke 与浏览器试玩。
## 2026-08-03 game-chat 开发态同源与持久输出修复
- 开发态启动必须在 Tauri CLI 之前预检固定 `3080`。现有 marker 只包含 API target,不能证明监听器属于当前 worktree;因此只有端口空闲时才允许继续,任何已存在的 AGC Vite、非 HTTP 监听器或其它服务都必须在原生窗口创建前失败关闭。启动器不擅自终止无法证明归属的旧服务,也不得把当前 Rust 壳 / Runner 与其它 worktree 的旧 Vite 前端混用。Tauri CLI 任意退出后,外层启动器必须有界收束已启动的客户端进程树,避免 `beforeDevCommand` 失败后留下假在线窗口。
- game-chat root binding 的 `source` 必须精确为 `project-supervisor-game-chat`。只有该持久 source 才能选择首波并行 `design-director + art-director + code-director`,随后按 `art-asset-plan → code-prototype → preview-readiness → preview-playtest` 推进七任务 lane、规范图派生图集美术门、单轮确定性收束和自动预览;若绑定为 `project-supervisor-gui`,必须视为启动链路错误,不能用完整 16 节点 DAG 的运行状态伪装 game-chat 进度。
- source-aware lane 的首波 ready child 可能在 UI hydration 写回时短暂恢复为 `Pending`。该例外必须从当前 root source 的种子 lane 解析全部零依赖任务,不得硬编码某个 Agent;当前 game-chat 首波是 `design-director / art-director / code-director`,后续 code prototype / preview child 仍严格拒绝 `Pending` 收束。
- game-chat root binding 的 `source` 必须精确为 `project-supervisor-game-chat`。只有该持久 source 才能进入 Supervisor 决策前零 child 的条件 lane;`audit-existing-first` 路由后先运行 `design-director + code-director`,再按覆盖合同选择复用或补齐美术,最后推进 `code-prototype → preview-readiness → preview-playtest`、单轮确定性收束和自动预览。若绑定为 `project-supervisor-gui`,必须视为启动链路错误,不能用完整 16 节点 DAG 的运行状态伪装 game-chat 进度。
- source-aware lane 的 ready child 可能在 UI hydration 写回时短暂恢复为 `Pending`。该例外必须从当前 root source、持久工作流决策和资产路由解析本轮已开放节点,不得硬编码旧三 Director 首波;当前审计首波是 `design-director / code-director`,后续美术、code prototype 与 preview child 仍严格拒绝未授权或不满足依赖的 `Pending` 收束。
- 专业 Agent 的非流式 final reply 继续由既有 finalization journal 重建并提交 `responseStream`。`streaming / ready` 投影仍必须匹配当前项目 revision;已经 finalization 提交的 `committed` 回复以 Agent / Session / run / request slot / response revision 稳定身份为准,不得因后续阶段推进项目 revision 而从 game-chat 查询中消失。
## Runtime 边界
File diff suppressed because one or more lines are too long
@@ -56,6 +56,8 @@ v1 只开放以下能力:
- `unsupported-image-style`:请求的图片后处理风格未知或不适用于当前生成类型,接口按无风格继续生成。
- `multiple-generation-warnings`:同一成功响应合并了不同 `code` 的多条非阻断告警,具体原因按顺序拼接在 `reason`。
手工调用 `POST /api/external/v1/editor/assets` 或 `POST /api/external/v1/editor/projects/{projectId}/resources` 创建 `assetKind=character-animation` 记录时,`generationInputs` 只保存可重放的生成输入,不接受 `characterAnimation`、`frames`、`previewVideoPath`、`frameCount`、`fps`、`durationSeconds` 等旧运行字段;完整正式帧序列和总时长必须分别写入 `imageSequenceFrames` 与 `imageSequenceDurationMs`。`screenColorHex`、`mattingProvider`、`mattingModel` 属于内部处理审计字段,External v1 会在持久化前移除。角色动作生成接口已经直接返回正式 resource / asset,正常调用方不应再手工复制第一帧创建重复记录。
provider 原图已保存但透明背景处理最终失败时,worker 保留原图稳定引用,不返回不存在的透明处理图,图标和 UI 也不继续拆分;有 `projectId + canvasCompletion` 时由原图完成画布写回。调用方应展示告警,但不得把 completed 任务改判为失败。该降级只覆盖透明背景处理的最终失败,phase 上报、原图或透明处理图持久化、画布写回失败仍使任务失败。图标 / UI 已成功生成透明图、只有自动拆分失败时继续使用既有 `sliceWarning`。通用 `warning` 与 `sliceWarning` 只在「透明背景最终失败」这一条上互斥;风格归一化或像素规整产生的通用 `warning` 可以与 `sliceWarning` 并存,compact result 不得丢弃任一条。
## 异步提交、查询与幂等
@@ -92,6 +92,8 @@ BgFilter 对已经落入私有 OSS 的生成原图、动作抽取帧和手动去
外部生成任务摘要投影与历史 payload 维护使用 `npm run spacetime:external-generation:maintain -- ...`,且只能由已授权 migration operator 的 SpacetimeDB CLI 登录态执行。脚本默认 dry-run、每次只处理一批,绝不自动循环全表;`--apply` 才写入。先发布包含 `external_generation_job_summary` 与 cursor 索引的 SpacetimeDB 模块,在维护模式内对事故时间以前的编辑器终态任务执行小批 dry-run,例如 `npm run spacetime:external-generation:maintain -- --database <database> --server-url <url> --limit 5 --completed-before-micros <micros>`;核对 `matched_count`、`before_bytes`、`after_bytes` 和 `inline_media_count` 后,保持本批输入 cursor 不变并追加 `--apply` 重跑同一批,即使最后一批 `has_more = false`,只要 dry-run 仍有 `matched_count` / `selected_count` 也必须 apply;只有 apply 成功后才使用它返回的 `next_cursor_job_id` 继续。B-tree cursor 的选择阶段最多反序列化 `limit + 1` 行,apply 会再按主键逐条读取选中行但不会同时保留整批 payload;如怀疑存在单行异常巨型历史 JSON,先用 `--limit 1`。payload 压缩硬限制 `source_module = editor-canvas`;终态压缩完成后,用 `--backfill-summaries` 先 dry-run、再 `--apply` 分批补齐仍缺失的活动任务或无内联媒体历史任务摘要,直到 `has_more = false`,最后再切换使用 summary procedure 的 api-server。Stdb 构建 artifact 和完整 release 包都必须包含 `scripts/spacetime-maintain-external-generation-jobs.mjs` 与 `scripts/spacetime-migration-common.mjs`。首次上线不得让 Full Build 从 Stdb 自动直落 API:`STDB_API_ROLLOUT_MODE` 默认 fail-closed 为 `pause-after-stdb`,必须填写受限的 `STDB_API_ROLLOUT_APPROVERS`;Stdb Publish 通过 `KEEP_MAINTENANCE_MODE` 保持维护文件并停止旧 API/controller/worker,暂停点最多等待 4 小时,完成上述维护并确认无后续批次后才由指定审批人放行 API。定时构建缺少审批人时必须在发布前失败,不能静默退回 `normal`;也可分开运行 Stdb publish、维护、API deploy 三个受控 Job。任一批次都不得处理 pending / running payload;不要用 runtime writer、bootstrap secret 或匿名 identity 代替 migration operator,也不要在未核对 dry-run 时直接 apply。
角色动作正式字段收口使用 `node scripts/spacetime-normalize-editor-character-actions.mjs --database <database> --server-url <url>`,且同样只能由已授权 migration operator 执行。必须先发布包含 normalization cursor 索引和 `normalize_editor_character_animation_metadata_and_return` 的 SpacetimeDB 模块,在 API / worker 仍处于维护模式时先运行默认全量 dry-run;脚本固定按 `asset → project-resource → showcase → canvas` 扫描,普通 scope 每批最多 25 行,canvas 每批最多 5 行。全量 dry-run 会在不写库的情况下把 asset 计划结果投影给同 owner / task / 首帧对象精确匹配的 project-resource,再把前置 scope 的计划结果投影给 canvas 检查;因此同 task 的误标预览 MP4 会先按权威视频对象排除,最终图片序列会逐帧核对并补齐精确 `asset_object` 身份。canvas 中仍引用误标 preview resource 的普通 video layer 会按 project-resource 计划态 `video` 跳过,只有 layout 明确声明动作却指向视频,或资源规划本身失败时才形成 blocker。apply 时仍要求前置 scope 已按顺序物理完成,不能跳过 asset 直接让 project-resource 借未落库结果。历史 canvas 复制的 `sourceResourceId` 不是迁移证据,不要因它仍指向原角色而手工改库,补建资源会采用最终账号素材的 DB 血缘。出现 blocker 时脚本会打印 ID、原因、owner、project、task、对象身份和来源资源;先据此区分最终候选为零 / 多个、正式与旧版冲突、帧对象不匹配或缺失资源,不得跳过 scope。确认 dry-run 后追加 `--apply`,脚本会对每批重新 dry-run、携带该批 SHA-256 apply,并在最后从头要求四个 scope 均为零匹配、零 blocker。只有该复核通过后才发布移除 action fallback 的 API / Web。Stdb build artifact 和完整 release 包必须同时包含 `scripts/spacetime-normalize-editor-character-actions.mjs` 与 `scripts/spacetime-migration-common.mjs`。本地切换分支时若要避免 dev publish 因 schema 冲突使用 `-c=on-conflict` 清库,启动命令必须追加 `--preserve-database`,让冲突直接失败。
自 2026-07-11 起,`Genarrative-Full-Build-And-Deploy` 的每日 04:00 timer 默认以 `DEPLOY_TARGET=development`、`STDB_API_ROLLOUT_MODE=normal` 对仅供开发使用的 dev 服务器执行 Stdb → API → Web 完整发布,不进入人工 rollout gate。三个下游 Build 都由 Full Job 显式传 `PUBLISH_AFTER_BUILD=false`,不得依赖下游 Job 默认值或提前各自发布;统一 Build 完成后仍由 Full Job 按固定顺序发布。人工维护窗口才选择 `pause-after-stdb`,且必须配置 `STDB_API_ROLLOUT_APPROVERS`。上文“定时构建缺少审批人时失败”的旧口径不再作为当前 dev 定时发布行为。
Full Job 通过 `EXIT_MAINTENANCE_MODE_AFTER_COMPLETION` 明确选择完整发布成功后是否退出维护,默认勾选以保持历史行为。Full 对 Stdb Publish 和 API Deploy 两个下游阶段都固定传 `KEEP_MAINTENANCE_MODE=true`,让 maintenance marker 持续覆盖 Stdb → API → Web 整段发布;Web Deploy 成功后才进入独立 `Exit Maintenance` 阶段。该阶段只能通过 `agent none` 和显式 `node(...)` 分配目标机,直接执行 `/opt/genarrative/current/scripts/deploy/maintenance-off.sh`;目标机不得 checkout Git、挂载 Git SSH 凭据或依赖 Jenkins workspace 源码。取消勾选时跳过最终退出阶段,便于内网验收完成后人工恢复公网。`Genarrative-Api-Deploy` 也单独暴露 `KEEP_MAINTENANCE_MODE` 参数,并转换为随发布包脚本的 `--keep-maintenance-mode`;失败路径仍按既有 current 切换边界保留或退出维护,不受成功态选项覆盖。外部生成 queue 的 `warning` 由 API/worker 固化为可直接展示的完整文案,Web 不再补前缀,因此 API/worker 与 Web 必须在同一维护窗口按同一版本协调发布;分开运行 Job 时先保持维护态完成 API/worker,再发布 Web,二者完成后才能恢复公网,不得在公网可用期间只滚动其中一侧。
@@ -589,7 +591,7 @@ Pingora current release 自审脚本 `scripts/ops/pingora-current-release-audit.
同一 API release 随包依赖还必须包含 `scripts/check-pingora-release-readiness.mjs` 与 `scripts/check-pingora-canary-live.mjs`。前者在 current release 上以 `--release-runtime-only` 汇总运行时复核,后者支撑目标 Nginx canary live smoke;缺少任一脚本时不能进入直连切换窗口。
`Genarrative-Stdb-Module-Build` 的 Jenkins 归档产物必须包含 `build/<version>/spacetime_module.wasm`、`spacetime_module.wasm.sha256`、`release-manifest.json`、`scripts/deploy/production-stdb-publish.sh`、`scripts/deploy/production-runtime-writer-identity-rotate.mjs`、`scripts/deploy/maintenance-on.sh`、`scripts/deploy/maintenance-off.sh`、`scripts/spacetime-migration-common.mjs`、`scripts/spacetime-maintain-external-generation-jobs.mjs`、`scripts/spacetime-migrate-editor-canvas-layout.mjs` 和 `scripts/database-backup-to-oss.mjs`,不得包含 `migration-bootstrap-secret.txt` 或任何原始 bootstrap secret。`Genarrative-Stdb-Module-Build` 只接受 `MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID` 指向的受保护 Jenkins Secret File:构建 shell 从临时文件读取原始值,强制校验为 64 位十六进制,计算 SHA-256,随后只通过 `GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET_SHA256` 注入 Rust 编译;WASM 因而只包含摘要,不包含可下载的原文,Stdb `release-manifest.json` 以 `migration_bootstrap_secret_sha256` 记录该非敏感摘要。`Genarrative-Stdb-Module-Publish` 只通过 `copyArtifacts` 复制上述非敏感产物,不在目标机器 checkout Git,并在发布阶段用同一个凭据 ID 再次挂载 Secret File;publish 必须再次校验 64 位十六进制、重算 SHA-256,并与 manifest 的 `migration_bootstrap_secret_sha256` 强制匹配后才可发布。Full Build 必须保证 Stdb Build / Publish 的 `MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID` 完全相同并把同一个 ID 同时透传,不能从构建 artifact 传 secret;ID 不同、manifest 缺摘要或摘要不匹配都必须在发布前失败。
`Genarrative-Stdb-Module-Build` 的 Jenkins 归档产物必须包含 `build/<version>/spacetime_module.wasm`、`spacetime_module.wasm.sha256`、`release-manifest.json`、`scripts/deploy/production-stdb-publish.sh`、`scripts/deploy/production-runtime-writer-identity-rotate.mjs`、`scripts/deploy/maintenance-on.sh`、`scripts/deploy/maintenance-off.sh`、`scripts/spacetime-migration-common.mjs`、`scripts/spacetime-maintain-external-generation-jobs.mjs`、`scripts/spacetime-normalize-editor-character-actions.mjs`、`scripts/spacetime-migrate-editor-canvas-layout.mjs` 和 `scripts/database-backup-to-oss.mjs`,不得包含 `migration-bootstrap-secret.txt` 或任何原始 bootstrap secret。`Genarrative-Stdb-Module-Build` 只接受 `MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID` 指向的受保护 Jenkins Secret File:构建 shell 从临时文件读取原始值,强制校验为 64 位十六进制,计算 SHA-256,随后只通过 `GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET_SHA256` 注入 Rust 编译;WASM 因而只包含摘要,不包含可下载的原文,Stdb `release-manifest.json` 以 `migration_bootstrap_secret_sha256` 记录该非敏感摘要。`Genarrative-Stdb-Module-Publish` 只通过 `copyArtifacts` 复制上述非敏感产物,不在目标机器 checkout Git,并在发布阶段用同一个凭据 ID 再次挂载 Secret File;publish 必须再次校验 64 位十六进制、重算 SHA-256,并与 manifest 的 `migration_bootstrap_secret_sha256` 强制匹配后才可发布。Full Build 必须保证 Stdb Build / Publish 的 `MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID` 完全相同并把同一个 ID 同时透传,不能从构建 artifact 传 secret;ID 不同、manifest 缺摘要或摘要不匹配都必须在发布前失败。
三个 SCM Jenkinsfile 将 `MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID` 默认固定为 `genarrative-spacetime-bootstrap-secret-dev-file`。Secret File 的原文只存在于 Jenkins Credentials;credential ID、参数默认值和定时 / 发布行为以仓库 Jenkinsfile 为事实源,不能只改 Job UI,因为 Declarative Pipeline 下一次载入会重写参数定义。旧 Secret Text `genarrative-spacetime-bootstrap-secret-dev` 继续保留给 Database Import / Export,不得原地改类型或删除。
@@ -163,7 +163,7 @@
### Prompt 与生成契约
- 前端提交到 `POST /api/editor/character-animations/generations`。
- 请求必须带上角色图片来源、原始尺寸、动画描述、分辨率、画面比例、帧数和时长,并可通过 `assetFolderId` 指定中间视频素材落点。`sourceImageSrc` 只允许传当前账号的 `objectKey`、项目资源 ID 或素材 ID;未持久化的本地临时图片必须先上传 OSS,不能提交 Data URL 或 Blob URL。后端解析稳定引用后必须校验对象属于当前 owner,再读取 OSS 内容。
- 请求必须带上角色图片来源、原始尺寸、动画描述、分辨率、画面比例、帧数和时长,并可通过 `assetFolderId` 指定中间视频素材落点。携带 `canvasCompletion` 时必须同时提供非空 `projectId`,该校验必须在解析来源、调用 provider、持久化和预扣泥点之前完成,禁止无项目画布完成请求在产生费用或产物后才失败。`sourceImageSrc` 只允许传当前账号的 `objectKey`、项目资源 ID 或素材 ID;未持久化的本地临时图片必须先上传 OSS,不能提交 Data URL 或 Blob URL。后端解析稳定引用后必须校验对象属于当前 owner,再读取 OSS 内容。
- 后端使用角色图片作为首帧和尾帧参考,模型固定映射到 `doubao-seedance-2-0-fast-260128`。
- 后端在内联执行和队列入队前都会拒绝 Data URL / Blob URL;路由使用默认 JSON body limit,不再为内联图片请求单独放宽。
- 后端 prompt 使用以下固定骨架,并把面板输入追加到 `动作描述:` 后:
@@ -182,9 +182,11 @@
- 后端先计算整批精确采样时刻,再用单个 FFmpeg filter graph 统一解码预览视频并输出 `32 / 40 / 48` 张源帧;不得为每帧重新启动 FFmpeg、重复解码同一视频,也不得用会改变现有尾帧安全时刻的粗粒度 `fps` 抽帧替代。批量命令成功后必须逐一确认全部目标帧文件存在,缺少任一帧都按整批失败处理并保留缺帧编号、目标时刻和输出路径诊断。
- 每帧绿幕源图字节由上传 owned 消费(`frame.bytes` 移入 put,上传完成后释放原帧缓冲,不克隆保留);后续父流程只持 object key,并为每帧向唯一 loopback `bgfilter-worker` 发起一次内部 HTTP RPC,不直接签发 BgFilter URL、不直连 provider,也不重试已被 worker 接收的内部 RPC(连接从未建立时按调度方案 §5.1 有界重连)。每帧请求分别携带按父剩余绝对预算派生的 `maxQueueWaitMs` 和公式化 `callBudgetMs`;子 worker 在默认 `Q=2048` admission 保险丝和 `Semaphore(N=16)` 约束下排队,取得 provider permit 后才启动调用预算,每次 provider attempt 前重新签发 600 秒 GET URL,multipart 仅传 `image_url`(加 `background_mode=flat`、`seg_model=birefnet`、`cross_check=on` 与同一次生成已选定的 `screenColor`),不传 `file`。真实 provider attempt 按 `N × est × 2` 派生,调用预算按 `2 × attempt + 1s` 派生;冻结 `est=5000ms` 时分别为 `160s / 321s`,排队不侵蚀最多两次顺序 attempt 的完整窗口,`32 / 40 / 48` 帧也不再增加单帧 attempt。成功图片由子 worker 以内部 HTTP 二进制 body 返回父流程,不在子侧落 OSS;BgFilter 最终失败且父业务预算仍有效时,由父流程进入 `阿里云通用抠图(按签名 URL 单独下载)→ 本地 editor_green_screen(再按 object key 独立下载一次并在产出后释放)`。
- BgFilter、阿里云或本地键色返回透明结果后,父流程继续通过现有最终帧 finalizer 转为 RGBA8,按宽高比居中放入最终目标尺寸,并使用 `RGBA(0,0,0,0)` 补边;最终帧 OSS 与业务写回仍由父流程完成。上述样例最终输出必须为 `323×480 RGBA8 PNG`,顶部和底部各 `23px` 透明 padding,内容区域完整保留抠图结果。
- 全部 `32 / 40 / 48` 帧以覆盖本次所有帧的无序在途集合向内部 worker 提交,允许乱序完成并最终按 `frameIndex` 排序;BgFilter provider 的实际在途请求受唯一 worker 的 `N / Q` 限制。任一帧最终失败时先排空全部已启动 Future,再让整项任务失败退款,不发布缺帧动画。
- 全部 `32 / 40 / 48` 帧以覆盖本次所有帧的无序在途集合向内部 worker 提交,允许乱序完成并最终按后端内部 `frame_index` 排序;该索引只服务并发收口、OSS 命名、日志和错误定位,不写入正式 asset / resource 帧对象。BgFilter provider 的实际在途请求受唯一 worker 的 `N / Q` 限制。任一帧最终失败时先排空全部已启动 Future,再让整项任务失败退款,不发布缺帧动画。
- 抽帧结果写入 OSS,并返回帧路径、帧尺寸、帧数、fps、预览视频路径、模型、价格和实际 prompt。
- 画板前端回填角色动作结果时,必须以 `frames[0].imageSrc` 创建 `mediaType: "image-sequence"`、`assetKind: "character-animation"` 图层,并把完整 `frames` 保存为图层 `imageSequenceFrames`;`previewVideoPath` 只保留为上游预览视频来源,不作为画布主媒体。
- 角色动作图层在画布中使用序列帧播放器循环展示透明 PNG 帧;刷新恢复时必须继续读取 `imageSequenceFrames`,不能回退到 `<video>` 预览。
- 画板前端回填角色动作结果时,以正式 resource / asset 的 `assetKind: "character-animation"` 选择序列帧播放器并以首帧作为图层主图;带项目上下文的生成必须返回正式 project resource,图层直接使用其 `resourceId`。无项目放置只允许绑定响应中的正式账号素材,不从生成结果 `frames` 构造自包含 `local-resource-character-animation-*`;此时图层的 `sourceResourceId` 继续使用正式账号素材记录的直接来源,不回退成请求中的原角色资源。预览视频作为独立 `assetKind: "video"` 资源保存,但不复制进最终动作 layout。
- 角色动作图层在画布中循环展示透明 PNG 帧;刷新恢复只从 project resource 的 `imageSequenceFrames/imageSequenceDurationMs` 读取正式结果。动作 layer layout 只保存 `layerId/resourceId` 和几何、层级、可见性等放置信息,不保存 `mediaType`、动作帧、时长、预览、生成输入或资源元数据副本;资源缺字段时按损坏数据失败关闭。
- 点击角色动作图层下载时,必须打包下载序列帧 ZIP;画布素材 ZIP 导出时,角色动作写入 `sequences/<编号-标题>/frames/`,而不是导出预览视频。
- 角色动作生成 BFF 在同一请求内保存两类账号素材:上游预览 MP4 使用 `assetKind=video` 并承载生成成本,最终透明帧集使用 `assetKind=character-animation` 且派生成本为 0;响应中的 `resource` / `asset` 固定指向最终透明帧集对应的项目资源与账号素材。调用方必须直接使用它们,不得再以第一帧调用资源或素材创建接口。
- 最终项目资源和账号素材只把 `imageSequenceFrames/imageSequenceDurationMs` 写入正式媒体字段,并以 `sourceResourceId` 指向独立预览视频资源;正式帧对象和角色动作生成响应都不含 `frameIndex`,数组位置是唯一播放顺序,帧数和 FPS 均按需派生。`generationInputs` 只保存 `fields/references` 等用户可重放输入,`screenColorHex` 和动作运行结果均不落入其中。存量 `characterAnimation`、已确认动作行的 helper 顶层字段、`frameIndex`、误标预览 MP4 及动作 layout 副本由受 migration operator 限制的四阶段数据库 procedure 一次性规范化;迁移按权威对象类型排除同 task 的预览视频、只接受唯一最终图片序列并逐帧补齐登记对象身份,且不把 layout 复制的 `sourceResourceId` 当作历史血缘证据。CLI tuple 与 HTTP object 两种 procedure 返回都必须先归一为脚本内部统一的 `batch_sha256`,分页还必须拒绝未推进或重复出现的 cursor,避免安全校验误拒绝或生产迁移无限循环。该宽容只属于迁移;新生成仍严格使用“原角色资源 → 预览视频资源 → 最终序列资源”直接来源链。上线完成后 api-server、后台、Web 和外部 helper 不再读取 legacy fallback。新动作写入若提交旧运行字段或 `frameIndex`,HTTP 与 SpacetimeDB storage 均明确拒绝。
- `frames[0].imageSrc` 仍作为后续动作素材快速编辑或再次生成动作时的透明帧来源。该规则不得跳过后端原有的视频生成、抽帧、透明化处理和帧素材落盘流程。