From dab51b0c71960b58b9ec5a21e3ba4fa583651432 Mon Sep 17 00:00:00 2001 From: lhk Date: Mon, 5 Oct 2026 07:45:57 +0100 Subject: [PATCH 01/10] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20generationInputs=20?= =?UTF-8?q?=E7=9A=84=20API=20=E5=85=83=E6=95=B0=E6=8D=AE=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补齐 OpenAPI 全部 generationInputs 字段的保存、重建、读取和消费边界 更新 API 指南及美术规范示例,明确元数据不会自动进入提示词 同步长期排障记忆,保持服务端行为及请求校验约束不变 --- .../genarrative-external-editor-api/SKILL.md | 1 + .../references/capability-routing.md | 4 +- .../references/requests-and-outputs.md | 38 +++++++++++++++++-- .../genarrative-external-v1.openapi.json | 31 +++++++-------- docs/project-memory/shared-memory/pitfalls.md | 7 ++++ 5 files changed, 59 insertions(+), 22 deletions(-) diff --git a/.codex/skills/genarrative-external-editor-api/SKILL.md b/.codex/skills/genarrative-external-editor-api/SKILL.md index c23c63777..d57252df5 100644 --- a/.codex/skills/genarrative-external-editor-api/SKILL.md +++ b/.codex/skills/genarrative-external-editor-api/SKILL.md @@ -16,6 +16,7 @@ Connect to `https://www.genarrative.world/api/external/v1/mcp` using Streamable - Upload local references using `prepare_asset_upload`: request a ticket, transfer the file from the client, then confirm the object. Confirmation does not create a canvas layer or a project/library record. Use the reference type accepted by the target tool; some operations require a registered resource or asset ID rather than an object key. - Generation is paid and asynchronous. Keep one stable `idempotencyKey` per logical generation and retain the returned `operationId`. Call `check_generation` according to `pollAfterMs`; consume `result` only after `completed`, and report the safe error on `failed`. A polling timeout does not justify another generation. - Read actual artifacts and warnings before claiming the requested deliverable is complete. Use project/library reads for complete persisted records, and `find_assets` with `action=get_download_url` for temporary media access. +- Treat `generationInputs` as generation context and provenance metadata, subject to each endpoint's preservation and rebuilding rules. Arbitrary fields, including `artSpec`, do not automatically enter the provider prompt or override request parameters. Put generation requirements in the endpoint's explicit inputs; see [Generation Inputs Metadata](references/requests-and-outputs.md#generation-inputs-metadata) for persistence, reads, and known consumers. - Keep API Keys and temporary upload/download credentials out of chat, repository files, and logs. Business calls operate within the API Key's owner and scopes. ## Documentation Navigation diff --git a/.codex/skills/genarrative-external-editor-api/references/capability-routing.md b/.codex/skills/genarrative-external-editor-api/references/capability-routing.md index 945474c68..2d6da819a 100644 --- a/.codex/skills/genarrative-external-editor-api/references/capability-routing.md +++ b/.codex/skills/genarrative-external-editor-api/references/capability-routing.md @@ -20,7 +20,7 @@ For generation, pass `projectId` with `canvasCompletion` when the result should ## Art Spec Routing -For a series of related art requests, an optional reusable spec can carry the shared requirements: +For a series of related art requests, an optional caller-defined spec can record the shared requirements. `artSpec` is an organizational convention inside `generationInputs`, not a server-defined generation parameter schema: ```json { @@ -35,7 +35,7 @@ For a series of related art requests, an optional reusable spec can carry the sh } ``` -Infer what is already clear and ask only for missing fields that block the selected endpoint. Reuse the current spec unless the user changes style, subject family, palette, format, or constraints. Store structured context under `generationInputs.artSpec` where supported and summarize it in the prompt when useful. +Infer what is already clear and ask only for missing fields that block the selected endpoint. Reuse the current spec unless the user changes style, subject family, palette, format, or constraints. Where the endpoint preserves custom metadata, `generationInputs.artSpec` can retain this context for later retrieval. To affect generation, always translate the relevant requirements into the endpoint's explicit inputs: image `prompt`, scene `sceneContent` / `stylePreset` / `customStyle`, or spritesheet `iconDescriptions`, plus the actual size and reference parameters. Neither `artSpec.references` nor `generationInputs.references` supplies reference media by itself. Scene and sound-effect generation rebuild their metadata and do not preserve an arbitrary `artSpec`; keep a caller-side copy when needed. See [Generation Inputs Metadata](requests-and-outputs.md#generation-inputs-metadata) for the rules applying to the entire `generationInputs` field. ## Intent Map diff --git a/.codex/skills/genarrative-external-editor-api/references/requests-and-outputs.md b/.codex/skills/genarrative-external-editor-api/references/requests-and-outputs.md index 10e41f319..37647c6f6 100644 --- a/.codex/skills/genarrative-external-editor-api/references/requests-and-outputs.md +++ b/.codex/skills/genarrative-external-editor-api/references/requests-and-outputs.md @@ -9,6 +9,7 @@ Use this reference to build generation payloads, carry canvas/library context, p - [Polling State Machine](#polling-state-machine) - [Canvas and Asset-Library Completion](#canvas-and-asset-library-completion) - [Saving Existing Canvas Layout](#saving-existing-canvas-layout) +- [Generation Inputs Metadata](#generation-inputs-metadata) - [Art Spec and Image Request](#art-spec-and-image-request) - [Local Reference Requests](#local-reference-requests) - [Compact Completed Result](#compact-completed-result) @@ -135,7 +136,7 @@ Background removal preserves the source image dimensions. For normal canvas plac Character animation accepts `assetFolderId` and `assetLabel` and persists the generated sequence. Consume the returned animation artifacts and persisted identities; do not synthesize a duplicate animation asset from the first frame. Use complete project/library records when complete persisted state is needed. -For the lower-level asset/resource creation endpoints, `generationInputs` is replayable request context rather than a media-runtime container. When `assetKind` is `character-animation`, the server rejects legacy runtime keys including `characterAnimation`, `frames`, `previewVideoPath`, `frameCount`, `fps`, and `durationSeconds`; send the formal sequence through `imageSequenceFrames` and `imageSequenceDurationMs`. Internal processing audit keys such as `screenColorHex`, `mattingProvider`, and `mattingModel` are removed before persistence. +For the lower-level asset/resource creation endpoints, `generationInputs` follows the metadata and media-runtime boundaries described in [Generation Inputs Metadata](#generation-inputs-metadata). ## Saving Existing Canvas Layout @@ -146,15 +147,46 @@ For the lower-level asset/resource creation endpoints, `generationInputs` is rep `edit_canvas/register_resource` registers existing media but does not create a canvas layer. `organize_asset_library/create_asset` creates metadata but does not upload or generate media. For generated media placement, prefer the generation tool's supported `canvasCompletion`; inspect returned identities before registering anything again. +## Generation Inputs Metadata + +`generationInputs` is optional JSON generation context: an input snapshot, provenance, and supported application metadata. An object is the useful shape for named fields; accepting `JsonValue` does not promise lossless storage of every JSON value. The selected endpoint may sanitize, augment, or rebuild it. Arbitrary metadata is not automatically included in the provider prompt, used as generation parameters, or applied to a later request. Put requirements into the endpoint's explicit inputs, such as `prompt`, `iconDescriptions`, `sceneContent`, style/size options, and its actual reference-media fields. + +When an operation persists project resources or library assets, their accepted metadata is saved with those records. Retrieve it through `GET /api/external/v1/editor/projects/{projectId}` (`project.resources[].generationInputs`) or `GET /api/external/v1/editor/assets/library` (`library.assets[].generationInputs`), subject to owner/scopes and record existence. MCP equivalents are `find_assets/get_project_resources` and `find_assets/list_library`. A compact generation result is not a complete metadata read. Metadata is not embedded in the image bytes, and uploading an image does not restore a previous record's metadata. + +| Operation | Current handling of `generationInputs` | +| --- | --- | +| Create a project resource or library asset | Preserve accepted metadata after removing client-supplied `references` and internal audit keys. This registers a record; it does not execute a generation recipe. | +| Generate an image | Preserve custom object fields on the provider's original image record after sanitization; rebuild `references` from actual authorized reference inputs. Character transparency processing may create a separate derived record. | +| Generate a scene | Rebuild V2 `version`, `action=scene.generate`, `fields`, and `references` from normalized scene parameters. Only the exact client marker `source=ai-game-creator-client` is retained additionally; arbitrary custom fields such as `artSpec` are discarded. | +| Edit an image | Preserve accepted custom fields and rebuild source/auxiliary `references` from `sourceReferenceId` and the actual reference inputs. | +| Remove a background | Preserve accepted custom fields and rebuild `references` from the actual source. Processing audit metadata remains internal. This metadata does not configure the removal operation. | +| Generate an icon spritesheet or extract UI assets | Preserve accepted custom fields on the provider's original image record. Transparent sheets and slices have separate processing-stage/source metadata and do not automatically inherit all custom fields. Follow the returned source references to read the original context. | +| Generate a character animation | Preserve accepted generation context on the final sequence record; formal frames and sequence duration are separate media fields, not runtime data inside `generationInputs`. | +| Generate a video | Preserve accepted context; object metadata can also receive an added/updated duration display field from normalized request parameters. | +| Generate a sound effect | Rebuild `fields`, empty `references`, and `soundEffect` metadata from the actual generation. Only `source`, `conversationId`, and `toolCallMessageId` are copied from a caller-supplied object; arbitrary fields such as `artSpec` are discarded. | +| Generate background music | Preserve accepted context after sanitization; generation parameters come from the explicit request fields. | + +Preservation does not mean every field is inert. Known consumers include: + +- The canvas reads `fields` / `references` for input display. Recognized V2 `version`, `action`, and stable field/reference IDs support restoring supported generation panels; arbitrary metadata does not guarantee a UI display or a “modify” action. +- Icon spritesheet generation reads the saved reference spec's `fields` entry titled `游戏类型` to select genre-specific prompt text. This does not cause arbitrary fields in the current request to be interpreted as prompts. +- Integrated clients use recognized `source` markers for queue/idempotency namespaces and result projections. These are application markers, not authentication or model instructions. + +Reference metadata does not grant access or select reference media. Image operations rebuild it from actual inputs and authorized records; direct resource/asset creation drops caller-supplied references. Supply the documented `referenceId`, `sourceReferenceId`, or media-reference fields. Do not assume other operations provide the same provenance rebuilding. + +Persisted metadata is bounded to 64 KiB of serialized JSON and cannot contain inline media Data URLs. Top-level internal audit fields `screenColorHex`, `mattingProvider`, and `mattingModel` are stripped from client metadata and owner-facing reads; the server can store its own internal audit values. When `assetKind=character-animation`, legacy runtime keys such as `characterAnimation`, `frames`, `previewVideoPath`, `frameCount`, `fps`, and `durationSeconds` are rejected; use `imageSequenceFrames` and `imageSequenceDurationMs` for formal sequence data. Omitting metadata or sending null does not replace required request parameters; empty objects may normalize to null. + +For reuse, read the saved record, recover the relevant requirements, and explicitly construct the next request. Keep the original complete request and idempotency key for retries: stored metadata, especially derived-asset metadata, is not a complete replayable HTTP payload. + ## Art Spec and Image Request Game scenes have a dedicated structured route: `POST /api/external/v1/editor/scenes/generations` with `sceneContent` and `stylePreset` (`customStyle` required when `stylePreset` is `custom`). The server assembles the full provider prompt; a caller-assembled `prompt` is not accepted. `kind: "scene"` and `assetKind: "scene"` remain invalid on generic image generation and return HTTP `400` before any generation job is queued. -When maintaining a reusable art spec, carry it in `generationInputs.artSpec` and reflect important constraints in the prompt. This is an example with both canvas and library destinations, not a requirement for every generation: +`generationInputs.artSpec` is an optional caller-defined metadata convention with no automatic prompt or parameter effect. In the generic image request below, the prompt repeats the desired style, palette, composition, and exclusions, while `aspectRatio` and `imageSize` set the actual format. The saved spec can help a caller construct later requests. This example uses both canvas and library destinations; neither the spec nor both destinations are required for every generation. Do not copy this metadata expectation to the scene route, which rebuilds its own context. ```json { - "prompt": "一张横版幻想森林背景,适合游戏主视觉,无文字", + "prompt": "一张横版幻想森林背景,适合游戏主视觉,手绘游戏概念图风格,翡翠绿与金色光斑,中心留出角色站位,无文字、无 UI 按钮", "aspectRatio": "16:9", "imageSize": "1K", "projectId": "", diff --git a/docs/openapi/genarrative-external-v1.openapi.json b/docs/openapi/genarrative-external-v1.openapi.json index c5a7b82bc..b85e6c217 100644 --- a/docs/openapi/genarrative-external-v1.openapi.json +++ b/docs/openapi/genarrative-external-v1.openapi.json @@ -2306,8 +2306,7 @@ "description": "assetKind=character-animation 时完整图片序列播放一次的毫秒时长;与音频、视频生成参数 durationSeconds 无关。" }, "generationInputs": { - "$ref": "#/components/schemas/JsonValue", - "description": "可重放的生成输入。assetKind=character-animation 时不得包含 characterAnimation、frames、previewVideoPath、frameCount、fps 或 durationSeconds 等旧运行字段;正式媒体数据必须写入 imageSequenceFrames 和 imageSequenceDurationMs。服务端会移除 screenColorHex、mattingProvider、mattingModel 等内部处理审计字段。" + "description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。本接口保存经清理的元数据,删除调用方 references 及顶层 screenColorHex、mattingProvider、mattingModel;不会执行生成配方。序列化后最多 64 KiB,禁止内联媒体 Data URL。assetKind=character-animation 时不得包含 characterAnimation、frames、previewVideoPath、frameCount、fps 或 durationSeconds 等旧运行字段;正式媒体数据使用 imageSequenceFrames 和 imageSequenceDurationMs。可通过素材库读取保存值。" } }, "additionalProperties": false @@ -2404,8 +2403,7 @@ "description": "assetKind=character-animation 时完整图片序列播放一次的毫秒时长;与音频、视频生成参数 durationSeconds 无关。" }, "generationInputs": { - "$ref": "#/components/schemas/JsonValue", - "description": "可重放的生成输入。assetKind=character-animation 时不得包含 characterAnimation、frames、previewVideoPath、frameCount、fps 或 durationSeconds 等旧运行字段;正式媒体数据必须写入 imageSequenceFrames 和 imageSequenceDurationMs。服务端会移除 screenColorHex、mattingProvider、mattingModel 等内部处理审计字段。" + "description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。本接口保存经清理的元数据,删除调用方 references 及顶层 screenColorHex、mattingProvider、mattingModel;不会执行生成配方。序列化后最多 64 KiB,禁止内联媒体 Data URL。assetKind=character-animation 时不得包含 characterAnimation、frames、previewVideoPath、frameCount、fps 或 durationSeconds 等旧运行字段;正式媒体数据使用 imageSequenceFrames 和 imageSequenceDurationMs。可通过项目详情读取保存值。" } }, "additionalProperties": false @@ -2797,7 +2795,7 @@ "description": "assetKind=character-animation 时完整图片序列播放一次的毫秒时长;与音频、视频生成参数 durationSeconds 无关。" }, "generationInputs": { - "$ref": "#/components/schemas/JsonValue" + "description": "实际保存的生成上下文与来源元数据,接受任意 JSON 值,也可能为空;不是完整请求或自动执行指令。已按 owner 读取边界移除内联媒体与顶层内部审计字段;生成接口可能清理、补充或重建请求值,派生记录不保证继承原图自定义字段。fields/references 可供显示,识别的 V2 action/字段 ID 可供支持的面板恢复;参考规范图的“游戏类型”等已知字段也有后续消费者,不能视为全部无业务作用。任意 artSpec 等扩展字段不保证 UI 展示或自动复用。复用时由调用方将所需信息显式转换成新请求参数。通过项目详情的 project.resources 读取。" }, "createdAt": { "type": "string", @@ -2948,7 +2946,7 @@ "description": "权威媒体类别。character-animation 渲染为序列帧,video 渲染为视频,audio/sound-effect/background-music 渲染为音频,其余渲染为图片。" }, "generationInputs": { - "$ref": "#/components/schemas/JsonValue" + "description": "实际保存的生成上下文与来源元数据,接受任意 JSON 值,也可能为空;不是完整请求或自动执行指令。已按 owner 读取边界移除内联媒体与顶层内部审计字段;生成接口可能清理、补充或重建请求值,派生记录不保证继承原图自定义字段。fields/references 可供显示,识别的 V2 action/字段 ID 可供支持的面板恢复;参考规范图的“游戏类型”等已知字段也有后续消费者,不能视为全部无业务作用。任意 artSpec 等扩展字段不保证 UI 展示或自动复用。复用时由调用方将所需信息显式转换成新请求参数。通过素材库的 library.assets 读取。" }, "createdAt": { "type": "string", @@ -3195,8 +3193,7 @@ "type": ["string", "null"] }, "generationInputs": { - "$ref": "#/components/schemas/JsonValue", - "description": "场景配方由服务端重建;仅保留 source 精确等于 ai-game-creator-client 的客户端来源标记,用于选择 AGC 队列结果与幂等命名空间。调用方 fields、action 和引用 provenance 不会覆盖服务端配方。" + "description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。本接口按 sceneContent、stylePreset、customStyle 和规范化尺寸 / 模型参数重建 V2 version/action/fields/references;仅额外保留 source 精确等于 ai-game-creator-client 的标记,用于 AGC 队列结果与幂等命名空间。调用方 fields、action、引用 provenance 和 artSpec 等其它扩展字段不会保留或覆盖服务端配方。" }, "assetFolderId": { "type": ["string", "null"] @@ -3307,7 +3304,7 @@ "description": "生成产物分类。External v1 通用图片接口禁止使用 scene;结构化游戏场景必须使用主站场景专用契约。" }, "generationInputs": { - "$ref": "#/components/schemas/JsonValue" + "description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。内容与构图要求写入 prompt,参考图写入 referenceImageSrcs,尺寸 / 风格使用正式参数。本接口清理内部审计字段并按真实且已鉴权的参考输入重建 references,其余自定义对象字段可保存到生成原图记录。角色透明化等派生记录可另建处理阶段和来源元数据;不保证最终派生图继承原图 artSpec。通过项目 / 素材库查询实际保存值。" }, "assetFolderId": { "type": ["string", "null"], @@ -3405,7 +3402,7 @@ "description": "项目上下文。提供 targetLayerId 时必须同时提供非空 projectId,否则返回 400。" }, "generationInputs": { - "$ref": "#/components/schemas/JsonValue" + "description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。编辑要求写入 prompt,原图使用 sourceReferenceId,辅助参考使用 referenceImageSrcs。本接口保留经清理的自定义字段,references 由已鉴权原图和实际辅助参考重建;不能通过本字段指定或伪造源图身份。通过项目 / 素材库查询实际保存值。" }, "assetFolderId": { "type": ["string", "null"] @@ -3492,7 +3489,7 @@ "x-genarrative-media-family": "static-image" }, "generationInputs": { - "$ref": "#/components/schemas/JsonValue" + "description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。本字段不配置去背景算法或选取源图;来源由 sourceImageSrc/sourceResourceId 等正式参数确定。保留经清理的自定义字段,并从实际且已鉴权来源重建 references。服务端处理审计字段不向普通调用方返回。通过项目 / 素材库查询实际保存值。" }, "assetFolderId": { "type": ["string", "null"] @@ -3701,7 +3698,7 @@ "type": ["string", "null"] }, "generationInputs": { - "$ref": "#/components/schemas/JsonValue" + "description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。需生效的内容、构图和分隔要求写入 iconDescriptions,规范图使用 referenceId。本接口清理内部审计字段并重建 references,自定义字段可保存于生成原图;透明图集和切片另建处理阶段 / 来源元数据,不自动继承 artSpec。后续可按来源链查询原图记录。本接口会读取已保存参考规范图 fields 中标题为“游戏类型”的值来组装类型提示,但不会解析本次请求的任意元数据作为提示词。" }, "assetFolderId": { "type": ["string", "null"] @@ -3764,7 +3761,7 @@ "type": ["string", "null"] }, "generationInputs": { - "$ref": "#/components/schemas/JsonValue" + "description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。提取来源、标注与尺寸使用正式请求参数。本接口清理内部审计字段并按实际已鉴权参考重建 references,自定义字段可保存于生成原图;透明图集和切片另建处理阶段 / 来源元数据,不自动继承全部字段。需原始上下文时按来源链查询项目 / 素材库记录。" }, "assetFolderId": { "type": ["string", "null"] @@ -4065,7 +4062,7 @@ "description": "带项目上下文生成时,服务端据此直接写入画布完成态并返回最新项目快照。" }, "generationInputs": { - "$ref": "#/components/schemas/JsonValue" + "description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。动作、源角色和生成设置由本接口的正式参数决定。经清理的上下文可保存到最终动作序列记录;不得用 characterAnimation、frames、previewVideoPath、frameCount、fps 或 durationSeconds 等旧运行字段承载序列。正式输出帧与时长通过资源 / 素材的 imageSequenceFrames 和 imageSequenceDurationMs 读取。" }, "sourceResourceId": { "type": ["string", "null"] @@ -4317,7 +4314,7 @@ "description": "带项目上下文生成时,服务端据此直接写入画布完成态并返回最新项目快照。" }, "generationInputs": { - "$ref": "#/components/schemas/JsonValue" + "description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。视频内容与参考媒体使用 prompt 及本接口的正式参考参数。本接口保留经清理的上下文;对象元数据可根据规范化生成时长补充或更新 fields 中的时长展示项。通过项目 / 素材库查询实际保存值。" }, "sourceResourceId": { "type": ["string", "null"] @@ -4493,7 +4490,7 @@ "description": "带项目上下文生成时,服务端据此直接写入画布完成态并返回最新项目快照。" }, "generationInputs": { - "$ref": "#/components/schemas/JsonValue" + "description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。音效由 prompt、duration、loop、model 等正式参数决定。本接口根据实际生成重建 fields、空 references 和 soundEffect 元数据;只从调用方对象复制 source、conversationId、toolCallMessageId,artSpec 等其它字段不保留。不能通过本字段覆盖实际时长、Loop 或提示词。" }, "assetFolderId": { "type": ["string", "null"], @@ -4533,7 +4530,7 @@ "description": "带项目上下文生成时,服务端据此直接写入画布完成态并返回最新项目快照。" }, "generationInputs": { - "$ref": "#/components/schemas/JsonValue" + "description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。音乐内容与是否纯音乐由 gptDescriptionPrompt、makeInstrumental 等正式参数决定。本接口保存经清理的上下文,不从自定义字段提取提示词或生成选项。通过项目 / 素材库查询实际保存值。" }, "assetFolderId": { "type": ["string", "null"], diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 444162046..2dc7c60c1 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -2,6 +2,13 @@ 这里只记录对当前开发仍有用的症状、根因、排查方法和风险边界。同一事实保留一个当前口径;退役对象的专属过程与单轮测试结果由 Git 历史追溯。遇到旧路径或版本时,以现行代码和专题文档为准。 +## 2026-10-05 generationInputs 的保存与复用不等于自动生效或完整透传 + +- **现象 / 根因**:调用方把构图等要求只写入 `generationInputs.artSpec`,但实际生图输入没有这些要求;把 `JsonValue` 和“可复用规范”误读为服务端会自动组装提示词、完整保留全部元数据或自动用于下一次生成。 +- **现行边界**:整个 `generationInputs` 承载生成上下文、来源和应用元数据,保存规则取决于接口。场景与音效重建配方;图集原图可保存自定义字段,透明图集与切片另建处理阶段 / 来源元数据。已知字段仍有实际消费者,例如规范图的“游戏类型”、V2 面板恢复字段和客户端来源标记,不能把整个对象描述为无业务作用。 +- **排查 / 使用**:同时核对正式请求字段、当前接口的重建 / 清理逻辑,以及最终资源 / 素材记录;重要要求必须进入 `prompt`、`iconDescriptions` 或场景结构化参数。项目与素材库读取可取得服务端实际保存的上下文,但它不等于完整 HTTP 重放载荷;重试仍保存原始请求和幂等键。 +- **权威说明**:[External v1 OpenAPI](../../openapi/genarrative-external-v1.openapi.json) 与 [API 指南的 Generation Inputs Metadata](../../../.codex/skills/genarrative-external-editor-api/references/requests-and-outputs.md#generation-inputs-metadata)。本条记录现有行为,不引入 API、存储或 UI 行为变更。 + ## 2026-10-03 AGC 随包 plugins 的 feature 档位必须与消费方一致,且门禁会因 build.rs 未重跑而假通过 - **现象**:Windows 本机 `npm run check:generated-bindings`(`npm run lint` 链内,`scripts/check-repository-ci.sh` 的 Repository checks 也走它)在 `build.rs:167:29` panic:`插件随包资源校验失败:随包插件存在未声明文件:.../src-tauri/resources/plugins/agc-godot-editor/native/gdextension/bin/win-x64/agc_godot_editor.dll(目标 x86_64-pc-windows-msvc 与当前 feature 组合不允许;请先执行随包资源准备步骤)`;树上换成 `agc-unity-editor/dotnet/publish/win-x64/Agc.Unity.Attach.exe` 时报同一类错。反向还有更隐蔽的形态:门禁 2 秒就 exit 0 说「通过」,但 tree 上其实带着编辑器产物。 -- 2.52.0 From b116cbc371cd23a48b5a9afc7f98157dd9c787d5 Mon Sep 17 00:00:00 2001 From: lhk Date: Mon, 5 Oct 2026 08:26:17 +0100 Subject: [PATCH 02/10] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E7=BE=8E=E6=9C=AF?= =?UTF-8?q?=E5=8C=85=E5=9B=BE=E9=9B=86=E7=94=9F=E6=88=90=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E7=9A=84=E6=9C=89=E6=95=88=E5=85=A5=E5=8F=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将美术包内容、风格和排布要求写入 iconDescriptions 并复用元数据文案来源 保留普通图标原文透传与旧生成账本的冻结请求和幂等身份 同步工具说明、美术资源技能、主规范和里程碑验收证据 补充真实 HTTP 请求、恢复防重及描述边界测试 --- .../prompts/runtime/texts/direct-tools.json | 2 +- .../prompts/runtime/texts/media.json | 4 +- .../resources/agc-skills/manifest.json | 4 +- .../agc-skills/taonier-art-assets/SKILL.md | 7 + .../references/platform-art-contract.md | 2 + .../src/agent/generation/canvas_generation.rs | 577 +++++++++++++++--- ...计划】AGC美术包生成需求入参修正-2026-10-05.md | 25 + ...¨‹碑】AGC美术包生成需求入参修正-2026-10-05.md | 33 + docs/project-memory/shared-memory/pitfalls.md | 1 + ...¹案】AI游戏创作智能体App实施计划-2026-06-24.md | 3 + 10 files changed, 573 insertions(+), 85 deletions(-) create mode 100644 docs/project-memory/plans/【实施计划】AGC美术包生成需求入参修正-2026-10-05.md create mode 100644 docs/project-memory/plans/【里程碑】AGC美术包生成需求入参修正-2026-10-05.md diff --git a/apps/ai-game-creator-shell/src-tauri/prompts/runtime/texts/direct-tools.json b/apps/ai-game-creator-shell/src-tauri/prompts/runtime/texts/direct-tools.json index 68b49e565..f50343523 100644 --- a/apps/ai-game-creator-shell/src-tauri/prompts/runtime/texts/direct-tools.json +++ b/apps/ai-game-creator-shell/src-tauri/prompts/runtime/texts/direct-tools.json @@ -10,7 +10,7 @@ "agc_write_file.parameters.path": "当前项目根下的相对路径,例如 game/index.html、assets/manifest.json 或 data/gameplay-spec.md", "agc_write_file.parameters.content": "仅填写目标文件的完整原始 UTF-8 正文", "taonier_prepare_game_art.description": "创建或恢复当前 AGC 项目的陶泥儿标准游戏美术包。默认复用有效美术包;根据当前对话需要选择 regenerate 重新生成。授权使用 AGC 客户端当前登录会话;遇到 401/403 时报告客户端登录或权限状态异常并停止。", - "taonier_prepare_game_art.parameters.brief": "面向当前游戏的简洁视觉需求", + "taonier_prepare_game_art.parameters.brief": "面向当前游戏的简洁视觉需求:写明主题、风格、玩家主体及状态、目标或收集物、障碍或场景元素、反馈特效等具体需要的素材。工具会补齐沿用规范图和素材独立排布的通用要求;内容类别不代表切片数量或返回顺序", "taonier_prepare_game_art.parameters.mode": "缺省安全复用有效美术包;Codex 仅在当前对话需要换一套或重新生成时使用 regenerate", "agc_generate_image.description": "生成一张新图片:普通插画、角色立绘、统一视觉规范图、游戏 UI 设计图或透明游戏素材图集。仅在用户明确要求生成新图时调用。", "agc_generate_image.parameters.prompt": "完整图片描述;普通图片、角色、规范图、UI 设计图或透明图集均可。kind=icon-spritesheet 时,去除首尾空白后的描述须为 1 到 200 个 Unicode 字符,保留内部换行并作为单条 iconDescriptions 原样提交;超限拒绝,不截断、不拆条,客户端不追加生图指令", diff --git a/apps/ai-game-creator-shell/src-tauri/prompts/runtime/texts/media.json b/apps/ai-game-creator-shell/src-tauri/prompts/runtime/texts/media.json index bb96a7205..988c2c318 100644 --- a/apps/ai-game-creator-shell/src-tauri/prompts/runtime/texts/media.json +++ b/apps/ai-game-creator-shell/src-tauri/prompts/runtime/texts/media.json @@ -15,8 +15,8 @@ "scene_constraints": "必须是可见的真实图片产物,适合作为 Canvas 或 HTML 游戏场景底图;不得做成素材图集、完整游戏截图、海报或概念板;必须原创,不得复刻现有游戏场景、Logo、贴图、标志性布局或受保护视觉语言", "spritesheet_subject": "{};使用项目原创命名和原创阵营设计", "spritesheet_style": "清晰可切分的原创 Web 游戏素材图集;严格沿用当前规范图的轮廓、材质、色板与光照,素材类别以当前玩法合同为准", - "spritesheet_composition": "{} 游戏素材图集,按玩家主体及状态、目标或收集物、障碍或场景元素、反馈特效分区,留出清楚切分间距", - "spritesheet_constraints": "生成可直接用于游戏的真实透明图片,素材仅包含当前项目玩法合同需要的实体和反馈;角色轮廓、图标排布与配色采用项目原创设计", + "spritesheet_composition": "{} 游戏素材图集,覆盖当前玩法需要的玩家主体及状态、目标或收集物、障碍或场景元素、反馈特效;每类可含多个素材,各素材独立排布并留出清楚切分间距", + "spritesheet_constraints": "素材仅包含当前项目玩法合同需要的实体和反馈,可独立用于游戏;角色轮廓、图标排布与配色采用项目原创设计", "image_generation": "根据用户需求生成一张全新的原创图片。主体、环境、风格、构图、光线和色彩以用户描述为准;不要生成素材图集、规范展板、完整游戏截图或文字说明。不得修改或复述为已有图片编辑。\n\n用户需求:{}", "character_generation": "根据用户需求生成一张全新的原创角色形象或人物立绘。清楚表现角色外貌、服饰、姿势、表情、画风、构图和背景;只生成一张完整图片,不要生成图集、规范展板、完整游戏截图或文字说明。不得复刻现有作品角色或 Logo。\n\n用户需求:{}", "publication_generation": "根据用户需求生成一张全新的原创游戏发布宣传图。突出主体、卖点、氛围、构图、色彩和适合发布展示的画面层次;只生成一张完整图片,不要生成素材图集、规范展板、完整游戏截图或文字说明。不得复刻现有作品角色或 Logo。\n\n用户需求:{}", diff --git a/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/manifest.json b/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/manifest.json index 7457a5a0b..d8e594703 100644 --- a/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/manifest.json +++ b/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/manifest.json @@ -1,6 +1,6 @@ { "schemaVersion": "agc-skill-pack.v1", - "version": "2026-08-26.40", + "version": "2026-08-26.41", "skills": [ { "name": "agc-unity-editor", @@ -101,7 +101,7 @@ "agents/openai.yaml", "references/platform-art-contract.md" ], - "sha256": "dcf243784b7279fc4819140d746e607bde840a53bc419172d615c53364a2ecb4" + "sha256": "d38571ffb5b12b40f23d04b76edbb06cbda06a11590d4dd0f784b524bf30071c" }, { "name": "agc-web-game-development", diff --git a/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/taonier-art-assets/SKILL.md b/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/taonier-art-assets/SKILL.md index 004b18128..9f7aabaf9 100644 --- a/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/taonier-art-assets/SKILL.md +++ b/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/taonier-art-assets/SKILL.md @@ -35,6 +35,13 @@ required; choose it from the brief: Pass `sliceMode` only for `kind="icon-spritesheet"`, and `gridX`/`gridY` only for `sliceMode="grid"`. Read the selected mode from the returned result. +For `taonier_prepare_game_art`, provide `brief` and optionally `mode`. Name the +game's theme, style, and needed player states, targets or collectibles, +obstacles or scene elements, and feedback effects in `brief`. The tool adds +shared requirements for visual-spec consistency and separated asset placement. +These content categories do not specify a slice count or returned ordering; +inspect the actual images before deciding how to use them. + ## Authorization boundary If the tool returns `401` or `403`, stop the operation and report that login or permission state needs attention. diff --git a/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/taonier-art-assets/references/platform-art-contract.md b/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/taonier-art-assets/references/platform-art-contract.md index e305c6b32..974e52f25 100644 --- a/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/taonier-art-assets/references/platform-art-contract.md +++ b/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/taonier-art-assets/references/platform-art-contract.md @@ -2,6 +2,8 @@ `agc_generate_image` and `taonier_prepare_game_art` are the reviewed paid art entries exposed to the AGC Codex thread. The former covers one ordinary/character/spec/UI/publication image; `agc_edit_image` covers edits to an existing registered image; the latter covers the complete game-art package and canonical slices. Call these tools only through the approved AGC session. +- For a package, describe the concrete theme, style, entities, states, and effects in `brief`. The tool includes the package's shared visual-spec and placement requirements in generation. No additional tool fields are required. Ordinary `agc_generate_image` icon descriptions remain the supplied text without package requirements. + - `mode="reuse-or-create"` reuses a complete trusted package and creates only missing assets. It is the safe default for existing games. - `mode="regenerate"` is reserved for an explicit user request to replace or restyle the package. Use it even when a complete package exists, but never bypass an unresolved paid operation. - `mode="regenerate"` requires a trusted, decodable, registered `art-spec.png` and background. An old spritesheet or canonical slices may be absent. If either the spec or background is missing or invalid, use `mode="reuse-or-create"`. diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs index 245491074..fb40540ec 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs @@ -28,6 +28,12 @@ const EXTERNAL_GENERATION_RESULT_UNKNOWN_PREFIX: &str = "platform-generation-res const EXTERNAL_GENERATION_SOURCE_PRESERVED_PREFIX: &str = "platform-generation-source-preserved-no-retry:"; +#[derive(Clone, Copy, PartialEq, Eq)] +enum PlatformArtGenerationPurpose { + Ordinary, + GameArtPackage, +} + /// 设计阶段的功能页面输出路径是否合法:必须落在 `assets/ui-pages/` 下、`.png` 结尾, /// 文件名只含安全字符且不超过 80 字符。 pub(in crate::agent) fn design_foundation_ui_page_output_path_is_valid(path: &str) -> bool { @@ -2297,6 +2303,45 @@ pub(crate) fn validate_platform_art_icon_prompt(prompt: &str) -> Result<(), Stri Ok(()) } +fn validate_platform_art_icon_descriptions(descriptions: &[String]) -> Result<(), String> { + if descriptions.is_empty() || descriptions.len() > 100 { + return Err("图标素材描述数量必须在 1 到 100 条之间".to_string()); + } + for description in descriptions { + validate_platform_art_icon_prompt(description)?; + } + let joined = descriptions + .iter() + .map(|description| description.trim()) + .collect::>() + .join("\n"); + if joined.chars().count() > 2_000 || joined.len() > 6 * 1024 { + return Err("图标素材描述合计不能超过 2000 个字符或 6144 个 UTF-8 字节".to_string()); + } + Ok(()) +} + +fn platform_art_spritesheet_descriptions( + prompt: &str, + options: &PlatformArtAssetGenerationOptions, + purpose: PlatformArtGenerationPurpose, +) -> Result, String> { + let mut descriptions = vec![prompt.trim().to_string()]; + if purpose == PlatformArtGenerationPurpose::GameArtPackage { + // 与描述性 artSpec 复用同一份文案,但只有正式输入会进入服务端提示词。 + descriptions.extend([ + prompt_text!("media.spritesheet_style").to_string(), + format!( + prompt_text!("media.spritesheet_composition"), + options.aspect_ratio + ), + prompt_text!("media.spritesheet_constraints").to_string(), + ]); + } + validate_platform_art_icon_descriptions(&descriptions)?; + Ok(descriptions) +} + fn decode_platform_art_image_with_limits( download: &CanvasResourceDownload, label: &str, @@ -2638,7 +2683,7 @@ pub(in crate::agent) async fn generate_admitted_platform_art_asset_at( false, &context, Some(admission.guard), - false, + PlatformArtGenerationPurpose::Ordinary, ) .await } @@ -2819,7 +2864,7 @@ pub(in crate::agent) async fn generate_platform_art_asset_with_runtime_options_a false, runtime_context, None, - false, + PlatformArtGenerationPurpose::Ordinary, ) .await } @@ -2843,7 +2888,7 @@ pub(in crate::agent) async fn generate_art_package_asset_with_runtime_options_at retain_runtime_state, runtime_context, None, - true, + PlatformArtGenerationPurpose::GameArtPackage, ) .await } @@ -2993,7 +3038,7 @@ async fn generate_platform_art_asset_with_runtime_options_and_retention_at( retain_runtime_state: bool, runtime_context: &PlatformArtGenerationRuntimeContext, admitted_guard: Option, - normalize_package_png: bool, + purpose: PlatformArtGenerationPurpose, ) -> Result { if require_slices && options.asset_kind != GameCreationAppAssetKind::IconSpritesheet { return Err("严格游戏切片生成只允许 icon-spritesheet 资产类型".to_string()); @@ -3063,15 +3108,30 @@ async fn generate_platform_art_asset_with_runtime_options_and_retention_at( acquire_project_write_lock_with_wait(root, "canvas.asset_generate.runtime.recover")?; recover_interrupted_strict_platform_art_transaction_locked_at(root, &recovery_lock)?; } - let mut prepared = request_platform_art_asset_with_runtime_options_at( - root, - prompt, - briefs, - options, - Some(runtime_context), - ) - .await?; - if normalize_package_png { + let mut prepared = match purpose { + PlatformArtGenerationPurpose::Ordinary => { + request_platform_art_asset_with_runtime_options_at( + root, + prompt, + briefs, + options, + Some(runtime_context), + ) + .await? + } + PlatformArtGenerationPurpose::GameArtPackage => { + request_platform_art_asset_for_purpose_at( + root, + prompt, + briefs, + options, + Some(runtime_context), + purpose, + ) + .await? + } + }; + if purpose == PlatformArtGenerationPurpose::GameArtPackage { normalize_art_package_png(&mut prepared.download)?; prepared.extension = "png".to_string(); if options.asset_kind == GameCreationAppAssetKind::IconSpritesheet @@ -3121,6 +3181,25 @@ pub(in crate::agent) async fn request_platform_art_asset_with_runtime_options_at briefs: &[AgentGroupBrief], options: &PlatformArtAssetGenerationOptions, runtime_context: Option<&PlatformArtGenerationRuntimeContext>, +) -> Result { + request_platform_art_asset_for_purpose_at( + root, + prompt, + briefs, + options, + runtime_context, + PlatformArtGenerationPurpose::Ordinary, + ) + .await +} + +async fn request_platform_art_asset_for_purpose_at( + root: &Path, + prompt: &str, + briefs: &[AgentGroupBrief], + options: &PlatformArtAssetGenerationOptions, + runtime_context: Option<&PlatformArtGenerationRuntimeContext>, + purpose: PlatformArtGenerationPurpose, ) -> Result { if options.asset_kind == GameCreationAppAssetKind::IconSpritesheet { validate_platform_art_icon_prompt(prompt)?; @@ -3202,12 +3281,13 @@ pub(in crate::agent) async fn request_platform_art_asset_with_runtime_options_at &context.run_id, )?; } - return Box::pin(request_platform_art_asset_with_runtime_options_at( + return Box::pin(request_platform_art_asset_for_purpose_at( root, prompt, briefs, options, runtime_context, + purpose, )) .await; } @@ -3253,12 +3333,13 @@ pub(in crate::agent) async fn request_platform_art_asset_with_runtime_options_at &context.run_id, )?; } - return Box::pin(request_platform_art_asset_with_runtime_options_at( + return Box::pin(request_platform_art_asset_for_purpose_at( root, prompt, briefs, options, runtime_context, + purpose, )) .await; } @@ -3336,6 +3417,10 @@ pub(in crate::agent) async fn request_platform_art_asset_with_runtime_options_at snapshot.generation_prompt, ) } else { + // 只为新请求组装美术包要求;旧账本继续使用原始请求体、幂等键和 brief 意图。 + let icon_descriptions = (options.asset_kind == GameCreationAppAssetKind::IconSpritesheet) + .then(|| platform_art_spritesheet_descriptions(&generation_prompt, options, purpose)) + .transpose()?; let canvas_context = prepare_external_canvas_generation_context(root, &client, &binding_access).await?; let generation_kind = match options.asset_kind { @@ -3368,7 +3453,7 @@ pub(in crate::agent) async fn request_platform_art_asset_with_runtime_options_at "/api/external/v1/editor/icon-spritesheets/generations", serde_json::json!({ "referenceId": reference_id, - "iconDescriptions": [generation_prompt], + "iconDescriptions": icon_descriptions, "sliceCount": options.slice_count, "sliceMode": options.slice_mode, "gridX": options.grid_x, @@ -13003,6 +13088,334 @@ mod canvas_generation_tests { .expect("BOM is not whitespace under the API Server Rust trim contract"); } + #[test] + fn art_package_descriptions_keep_the_entire_brief_within_api_limits() { + let options = PlatformArtAssetGenerationOptions { + asset_kind: GameCreationAppAssetKind::IconSpritesheet, + aspect_ratio: "16:9".into(), + ..PlatformArtAssetGenerationOptions::default() + }; + let brief = "🪙".repeat(200); + let descriptions = platform_art_spritesheet_descriptions( + &format!(" {brief}\n"), + &options, + PlatformArtGenerationPurpose::GameArtPackage, + ) + .unwrap(); + assert_eq!(descriptions[0], brief); + let metadata = platform_art_asset_art_spec(&options); + for field in ["style", "composition", "constraints"] { + assert!(descriptions + .iter() + .any(|text| Some(text.as_str()) == metadata[field].as_str())); + } + assert_eq!( + platform_art_spritesheet_descriptions( + &brief, + &options, + PlatformArtGenerationPurpose::Ordinary, + ) + .unwrap(), + vec![brief.clone()] + ); + assert!(platform_art_spritesheet_descriptions( + &(brief + "字"), + &options, + PlatformArtGenerationPurpose::GameArtPackage, + ) + .is_err()); + } + + #[test] + fn art_package_description_limits_include_separators_and_utf8_bytes() { + assert!(validate_platform_art_icon_descriptions(&[]).is_err()); + assert!(validate_platform_art_icon_descriptions(&[" ".into()]).is_err()); + assert!(validate_platform_art_icon_descriptions(&vec!["字".into(); 100]).is_ok()); + assert!(validate_platform_art_icon_descriptions(&vec!["字".into(); 101]).is_err()); + let mut chars = vec!["字".repeat(200); 9]; + chars.push("字".repeat(191)); + assert_eq!(chars.join("\n").chars().count(), 2000); + validate_platform_art_icon_descriptions(&chars).unwrap(); + chars.last_mut().unwrap().push('字'); + assert!(validate_platform_art_icon_descriptions(&chars).is_err()); + let mut bytes = vec!["🪙".repeat(200); 7]; + bytes.push("🪙".repeat(134) + "a"); + assert_eq!(bytes.join("\n").len(), 6144); + validate_platform_art_icon_descriptions(&bytes).unwrap(); + bytes.last_mut().unwrap().push('b'); + assert!(validate_platform_art_icon_descriptions(&bytes).is_err()); + } + + #[tokio::test] + async fn art_package_spritesheet_posts_requirements_but_ordinary_icons_keep_original_text() { + assert_art_package_spritesheet_http_request( + PlatformArtGenerationPurpose::GameArtPackage, + None, + ) + .await; + assert_art_package_spritesheet_http_request(PlatformArtGenerationPurpose::Ordinary, None) + .await; + } + + #[tokio::test] + async fn art_package_spritesheet_recovers_old_requests_without_injecting_new_requirements() { + for status in ["prepared", "accepted", "legacy-completed"] { + assert_art_package_spritesheet_http_request( + PlatformArtGenerationPurpose::GameArtPackage, + Some(status), + ) + .await; + } + } + + async fn assert_art_package_spritesheet_http_request( + purpose: PlatformArtGenerationPurpose, + old_status: Option<&str>, + ) { + let temporary = tempfile::tempdir().unwrap(); + let root = temporary.path(); + init_local_game_project_at(root, "art-package-request", "美术包请求测试").unwrap(); + write_project_permission_policy_at( + root, + ProjectPermissionPolicy { + denied_commands: Vec::new(), + confirm_commands: Vec::new(), + agent_policies: BTreeMap::new(), + }, + ) + .unwrap(); + let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); + listener.set_nonblocking(true).unwrap(); + let base_url = format!("http://{}", listener.local_addr().unwrap()); + let api_key = "art-package-fixture-key"; + install_test_external_project_binding(root, &base_url, api_key); + install_test_canonical_spec_binding(root, &base_url, api_key); + let result = serde_json::json!({ + "spritesheetResource": { + "resourceId": "art-package-sheet", "assetObjectId": "art-package-object", + "projectId": "manual-test-canvas", "taskId": "art-package-task", + "objectKey": "art-package.png", + }, + "sliceMode": "connected-components", "iconImageSrcs": [], + "sliceWarning": {"reason": "夹具只提供图集"} + }); + let options = PlatformArtAssetGenerationOptions { + output_path: Some("assets/art-spritesheet.png".into()), + asset_kind: GameCreationAppAssetKind::IconSpritesheet, + asset_label: "森林小游戏素材".into(), + aspect_ratio: "1:1".into(), + image_size: "1K".into(), + slice_mode: Some("connected-components".into()), + ..PlatformArtAssetGenerationOptions::default() + }; + let context = PlatformArtGenerationRuntimeContext { + agent_id: "direct-codex-art".into(), + task_id: "direct-codex-art-art-spritesheet".into(), + session_id: "art-package-session".into(), + run_id: "art-spritesheet".into(), + source: "direct-codex".into(), + action_id: "direct-taonier-art-spritesheet".into(), + action_fingerprint: "direct-taonier-art-v1:icon-spritesheet:art-spritesheet".into(), + }; + let brief = "狐狸奔跑与受击状态\n橡果、树桩、石块和收集反馈"; + let frozen = + old_status.map(|status| { + let access = + ExternalEditorBindingAccess::for_developer(&base_url, api_key).unwrap(); + let (state, _) = prepare_platform_art_generation_runtime_state( + root, &context, "/api/external/v1/editor/icon-spritesheets/generations", + "旧版美术包画布", brief, &serde_json::json!({ + "referenceId": "scene-route-art-spec-resource", "iconDescriptions": [brief], + "projectId": "manual-test-canvas", "assetFolderId": "manual-test-assets", + "sliceMode": "connected-components", "aspectRatio": "1:1", "imageSize": "1K", + }), &access, + ).unwrap(); + let frozen = ( + platform_art_generation_runtime_request_body_json(&state).to_string(), + platform_art_generation_runtime_idempotency_key(&state).to_string(), + ); + match status { + "accepted" => { + mark_platform_art_generation_runtime_accepted( + root, + state, + "art-package-operation", + 0, + ) + .unwrap(); + } + "legacy-completed" => { + mark_platform_art_generation_runtime_legacy_completed(root, state, &result) + .unwrap(); + } + "prepared" => {} + _ => unreachable!(), + } + frozen + }); + let (request_sender, request_receiver) = std::sync::mpsc::channel(); + let (stop_sender, stop_receiver) = std::sync::mpsc::channel(); + let server_url = base_url.clone(); + let server = std::thread::spawn(move || { + let deadline = std::time::Instant::now() + Duration::from_secs(20); + loop { + if stop_receiver.try_recv().is_ok() || std::time::Instant::now() >= deadline { + break; + } + let (mut stream, _) = match listener.accept() { + Ok(connection) => connection, + Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => { + std::thread::sleep(Duration::from_millis(2)); + continue; + } + Err(error) => panic!("accept art package request: {error}"), + }; + let request = read_test_http_request(&mut stream); + request_sender.send(request.clone()).unwrap(); + if request + .starts_with("POST /api/external/v1/editor/icon-spritesheets/generations ") + { + write_test_json_response( + &mut stream, + "202 Accepted", + &serde_json::json!({"data": { + "operationId": "art-package-operation", "status": "queued", "pollAfterMs": 0, + }}), + ); + } else if request + .starts_with("GET /api/external/v1/generations/art-package-operation ") + { + write_test_json_response( + &mut stream, + "200 OK", + &serde_json::json!({"data": { + "operationId": "art-package-operation", "status": "completed", "pollAfterMs": 0, + "result": result, + }}), + ); + } else if request.starts_with("GET /api/external/v1/assets/read-url?") { + write_test_json_response( + &mut stream, + "200 OK", + &serde_json::json!({ + "read": {"signedUrl": format!("{server_url}/art-package.png")} + }), + ); + } else if request.starts_with("GET /art-package.png ") { + let png = rgba_test_png(128).bytes; + write!(stream, "HTTP/1.1 200 OK\r\nContent-Type: image/png\r\nContent-Length: {}\r\nConnection: close\r\n\r\n", png.len()).unwrap(); + stream.write_all(&png).unwrap(); + } else if request.starts_with("GET /api/external/v1/editor/projects?view=summary ") + { + write_test_json_response( + &mut stream, + "200 OK", + &serde_json::json!({"data": {"projects": [{ + "projectId": "manual-test-canvas", "title": "美术包画布" + }]}}), + ); + } else if request + .starts_with("GET /api/external/v1/editor/assets/folders/manual-test-assets ") + { + write_test_json_response( + &mut stream, + "200 OK", + &serde_json::json!({"data": {"folder": { + "folderId": "manual-test-assets", "label": "美术包素材" + }}}), + ); + } else { + write_test_json_response( + &mut stream, + "400 Bad Request", + &serde_json::json!({"error": "unexpected fixture request"}), + ); + } + } + }); + let generated = crate::assets::with_external_editor_api_credentials( + crate::assets::external_editor_api_credentials_for_test(base_url, api_key.into()), + async { + if purpose == PlatformArtGenerationPurpose::GameArtPackage { + let first = generate_art_package_asset_with_runtime_options_at( + root, + brief, + &[], + &options, + false, + true, + &context, + ) + .await?; + let replayed = generate_art_package_asset_with_runtime_options_at( + root, + brief, + &[], + &options, + false, + true, + &context, + ) + .await?; + assert_eq!(first.asset.id, replayed.asset.id); + Ok(first) + } else { + generate_platform_art_asset_with_runtime_options_at( + root, + brief, + &[], + &options, + false, + &context, + ) + .await + } + }, + ) + .await; + stop_sender.send(()).unwrap(); + server.join().unwrap(); + if let Err(error) = generated { + panic!("art package request failed: {error}"); + } + let requests = request_receiver.try_iter().collect::>(); + let posts = requests + .iter() + .filter(|request| request.starts_with("POST ")) + .collect::>(); + let expected_posts = + usize::from(!matches!(old_status, Some("accepted" | "legacy-completed"))); + assert_eq!(posts.len(), expected_posts, "{requests:#?}"); + if let Some(post) = posts.first() { + let body: serde_json::Value = serde_json::from_str(test_request_body(post)).unwrap(); + assert_eq!(body["referenceId"], "scene-route-art-spec-resource"); + assert_eq!(body["aspectRatio"], "1:1"); + assert_eq!(body["imageSize"], "1K"); + if let Some((body, key)) = &frozen { + assert_eq!(test_request_body(post), body); + assert_eq!(test_request_header(post, "idempotency-key"), key); + } else if purpose == PlatformArtGenerationPurpose::Ordinary { + assert_eq!(body["iconDescriptions"], serde_json::json!([brief])); + } else { + let descriptions = body["iconDescriptions"].as_array().unwrap(); + assert_eq!(descriptions[0], brief); + for field in ["style", "composition", "constraints"] { + assert!(descriptions.contains(&body["generationInputs"]["artSpec"][field])); + } + } + } + if let Some((body, key)) = frozen { + let state = read_platform_art_generation_runtime_state(root, &context) + .unwrap() + .unwrap(); + assert_eq!( + platform_art_generation_runtime_request_body_json(&state), + body + ); + assert_eq!(platform_art_generation_runtime_idempotency_key(&state), key); + } + } + #[tokio::test] async fn derived_visual_asset_waits_for_registered_art_spec() { let temporary = tempfile::tempdir().expect("create art dependency project"); @@ -15912,6 +16325,73 @@ mod canvas_generation_tests { ); } + fn install_test_canonical_spec_binding(root: &Path, base_url: &str, api_key: &str) { + // `Scene` 属于「必须带规范图引用」的 kind:先种一张 assets/art-spec.png 并登记成 Canvas 来源。 + fs::write( + root.join(AGENT_RUNTIME_ART_SPEC_PATH), + rgba_test_png(u8::MAX).bytes, + ) + .expect("write canonical art spec"); + register_local_asset_at( + root, + AGENT_RUNTIME_ART_SPEC_PATH, + GameCreationAppAssetKind::IconSpec, + "image/png", + "scene-route-test", + GameCreationAppAssetSource { + kind: GameCreationAppAssetSourceKind::Canvas, + canvas_project_id: Some("manual-test-canvas".to_string()), + resource_id: Some("scene-route-art-spec".to_string()), + asset_object_id: Some("scene-route-art-spec-object".to_string()), + task_id: Some("scene-route-art-spec-task".to_string()), + prompt: None, + model: None, + generation_route: Some("/api/external/v1/editor/images/generations".to_string()), + generation_kind: Some("spec".to_string()), + reference_resource_ids: Vec::new(), + }, + ) + .expect("register canonical art spec"); + + // 规范图的远端引用走 binding 缓存命中:预置绑定后不需要额外的上传夹具端点。 + { + let access = ExternalEditorBindingAccess::new(base_url, api_key, None) + .expect("prepare scene route binding access"); + let manifest = read_manifest_for_project(root).expect("read scene route manifest"); + let source = manifest + .assets + .iter() + .find(|asset| asset.local_path == AGENT_RUNTIME_ART_SPEC_PATH) + .expect("registered canonical art spec"); + let source_bytes = + fs::read(root.join(&source.local_path)).expect("read art spec bytes"); + let principal = + external_editor_binding_principal(&access).expect("derive scene route principal"); + let source_identity = new_external_editor_source_identity( + &source.id, + &format!("{:x}", Sha256::digest(&source_bytes)), + &source.media_type, + &source.kind, + ) + .expect("derive scene route source identity"); + let binding = new_external_editor_resource_binding( + &manifest.project_id, + &principal, + "manual-test-canvas", + &source_identity, + Some("scene-route-art-spec-resource"), + "generated/scene-route-art-spec.png", + "scene-route-art-spec-object", + Some(1), + Some(1), + unix_timestamp(), + ) + .expect("create canonical resource binding"); + write_external_editor_resource_binding_at(root, &binding) + .expect("persist canonical resource binding"); + } + } + /// 场景(背景)阶段的请求夹具:只接受专用场景路由,记录原始请求,并按队列 → 换签 → 下载链路服务完。 fn serve_scene_route_fixture( stream: &mut std::net::TcpStream, @@ -16034,33 +16514,6 @@ mod canvas_generation_tests { }, ) .expect("allow standalone scene generation"); - // `Scene` 属于「必须带规范图引用」的 kind:先种一张 assets/art-spec.png 并登记成 Canvas 来源。 - fs::write( - root.join(AGENT_RUNTIME_ART_SPEC_PATH), - rgba_test_png(u8::MAX).bytes, - ) - .expect("write canonical art spec"); - register_local_asset_at( - root, - AGENT_RUNTIME_ART_SPEC_PATH, - GameCreationAppAssetKind::IconSpec, - "image/png", - "scene-route-test", - GameCreationAppAssetSource { - kind: GameCreationAppAssetSourceKind::Canvas, - canvas_project_id: Some("manual-test-canvas".to_string()), - resource_id: Some("scene-route-art-spec".to_string()), - asset_object_id: Some("scene-route-art-spec-object".to_string()), - task_id: Some("scene-route-art-spec-task".to_string()), - prompt: None, - model: None, - generation_route: Some("/api/external/v1/editor/images/generations".to_string()), - generation_kind: Some("spec".to_string()), - reference_resource_ids: Vec::new(), - }, - ) - .expect("register canonical art spec"); - let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind scene route fixture"); listener @@ -16069,43 +16522,7 @@ mod canvas_generation_tests { let base_url = format!("http://{}", listener.local_addr().expect("fixture address")); let api_key = "scene-route-key"; install_test_external_project_binding(root, &base_url, api_key); - // 规范图的远端引用走 binding 缓存命中:预置绑定后不需要额外的上传夹具端点。 - { - let access = ExternalEditorBindingAccess::new(&base_url, api_key, None) - .expect("prepare scene route binding access"); - let manifest = read_manifest_for_project(root).expect("read scene route manifest"); - let source = manifest - .assets - .iter() - .find(|asset| asset.local_path == AGENT_RUNTIME_ART_SPEC_PATH) - .expect("registered canonical art spec"); - let source_bytes = - fs::read(root.join(&source.local_path)).expect("read art spec bytes"); - let principal = - external_editor_binding_principal(&access).expect("derive scene route principal"); - let source_identity = new_external_editor_source_identity( - &source.id, - &format!("{:x}", Sha256::digest(&source_bytes)), - &source.media_type, - &source.kind, - ) - .expect("derive scene route source identity"); - let binding = new_external_editor_resource_binding( - &manifest.project_id, - &principal, - "manual-test-canvas", - &source_identity, - Some("scene-route-art-spec-resource"), - "generated/scene-route-art-spec.png", - "scene-route-art-spec-object", - Some(1), - Some(1), - unix_timestamp(), - ) - .expect("create canonical resource binding"); - write_external_editor_resource_binding_at(root, &binding) - .expect("persist canonical resource binding"); - } + install_test_canonical_spec_binding(root, &base_url, api_key); let posts = std::sync::Arc::new(std::sync::Mutex::new(Vec::::new())); let others = std::sync::Arc::new(std::sync::Mutex::new(Vec::::new())); diff --git a/docs/project-memory/plans/【实施计划】AGC美术包生成需求入参修正-2026-10-05.md b/docs/project-memory/plans/【实施计划】AGC美术包生成需求入参修正-2026-10-05.md new file mode 100644 index 000000000..ec58f12bb --- /dev/null +++ b/docs/project-memory/plans/【实施计划】AGC美术包生成需求入参修正-2026-10-05.md @@ -0,0 +1,25 @@ +# AGC 美术包生成需求入参修正实施计划 + +- Version: 1 +- Status: Implemented, Awaiting Acceptance +- Date: 2026-10-05 +- Parent Spec: [里程碑](./【里程碑】AGC美术包生成需求入参修正-2026-10-05.md) + +## 修改顺序 + +1. 在 canvas_generation 的既有美术包专用入口显式传递调用用途,独立组装新图集请求的描述数组;复用 media 提示词来源,保留原 brief 意图和冻结请求路径。 +2. 同步必要的工具说明与 taonier-art-assets skill,更新随包摘要及共享排障记忆。 +3. 用 HTTP 夹具验证美术包与普通入口请求和旧任务恢复;定向测试描述边界。 +4. 完成普通 Rust 目标检查、定向 Rust 测试、runtime_prompt_bundle_build、prompt_source_boundaries、skill-pack:check、check:doc-index、check:encoding 和 diff 检查;记录实际证据与限制。 + +## 检查点与风险 + +首轮以 45 分钟完成请求修改与定向验证为检查点;仅测试失败或验收缺口扩展范围。旧请求按持久快照恢复,不能修改原幂等身份或借升级新建付费操作。付费 Provider 验证不在本地夹具中执行。 + +## 回滚 + +回退本次客户端请求组装和随包文案即可;没有服务端、工具参数或持久化 schema 迁移,已冻结的新旧请求继续按原快照恢复。 + +## 验证完成 + +按计划完成美术包入口隔离、描述数组组装、共享文案、skill 摘要与主规范同步。53 项定向 Rust 测试、普通构建检查和文档 / 编码 / 差异门禁通过,逐项证据见里程碑。没有执行真实付费生成;本次不修改服务端、brief 长度合同或切片处理。 diff --git a/docs/project-memory/plans/【里程碑】AGC美术包生成需求入参修正-2026-10-05.md b/docs/project-memory/plans/【里程碑】AGC美术包生成需求入参修正-2026-10-05.md new file mode 100644 index 000000000..843181bb5 --- /dev/null +++ b/docs/project-memory/plans/【里程碑】AGC美术包生成需求入参修正-2026-10-05.md @@ -0,0 +1,33 @@ +# AGC 美术包生成需求入参修正 + +- Version: 1 +- Status: Implemented, Awaiting Acceptance +- Date: 2026-10-05 +- Parent Spec: [AGC 实施计划](../../technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md#2026-08-23-direct-codex-美术包显式重生成与切片投影) + +## 目标与范围 + +让美术包核心图集实际提交的 `iconDescriptions` 包含 brief 与现有内容、风格和排布要求。用户已确认此前方案并指示开工;实现前复核入口隔离、冻结请求恢复和服务端纯色底处理边界,评审通过。 + +必须完成请求组装、Agent 可见说明、定向测试和文档同步。风险项是普通图标入口串用模板、超长描述与重复付费恢复。范围外保留 brief 长度契约调整、固定四片和用途识别、重生成授权措辞及服务端行为修改。 + +## 验收标准 + +1. 新美术包图集的真实 HTTP 请求保留 brief,并在有效字段中包含内容、风格与间距要求;参考与比例/尺寸仍由正式字段承载。 +2. 普通图标入口保持原文单项提交,规范图与背景生成参数不变。 +3. 新增描述满足 API 条数、单条、总字符与字节限制;不截断或拆分用户原文。 +4. 旧 prepared 请求保持原 POST 字节及幂等键;accepted / 已完成请求不新建付费请求,brief 变化仍拒绝冒充同一意图。 +5. 工具参数无新增;工具说明、随包 skill 与主规范一致。提示词、skill 包、编码及差异检查通过;真实 Provider 效果独立标记。 + +## 证据 + +| 验收项 | 证据 | +| --- | --- | +| 新请求与普通入口隔离 | `art_package_spritesheet_posts_requirements_but_ordinary_icons_keep_original_text` 经生产生成入口和本地 HTTP 夹具检查真实请求及重复调用,已通过 | +| 原文与 API 边界 | `art_package_descriptions_keep_the_entire_brief_within_api_limits`、`art_package_description_limits_include_separators_and_utf8_bytes`,覆盖 200/201 码点、100/101 条、2000 字符及 6144 字节边界,已通过 | +| 旧任务恢复 | `art_package_spritesheet_recovers_old_requests_without_injecting_new_requirements` 覆盖 prepared / accepted / legacy-completed 三种旧单条请求;正文与幂等键不变,重复调用不新增生成 POST,已通过 | +| 相关回归 | 美术包过滤组 9 项、普通图标/背景/恢复/skill 包过滤组 20 项,全部通过 | +| 提示词与普通构建 | `runtime_prompt_bundle_build` 18 项、`prompt_source_boundaries` 6 项及 `cargo check --locked --offline --bin genarrative-ai-game-creator-shell` 通过 | +| 文档与资源 | skill-pack:check、check:doc-index、变更文件编码、rustfmt 和差异检查通过 | + +总计 53 项 Rust 测试通过。本地 HTTP 夹具需要监听回环端口,沙箱限制后已在获准的沙箱外环境重跑通过。真实客户端 / 付费 Provider 整包生成未执行;这些证据证明请求传输与恢复,不保证生成图片的视觉质量。固定四片、按序分配用途及 brief 长度不匹配仍属后续工作。本里程碑等待验收,不推进第三项。 diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 2dc7c60c1..4d1c930ef 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -7,6 +7,7 @@ - **现象 / 根因**:调用方把构图等要求只写入 `generationInputs.artSpec`,但实际生图输入没有这些要求;把 `JsonValue` 和“可复用规范”误读为服务端会自动组装提示词、完整保留全部元数据或自动用于下一次生成。 - **现行边界**:整个 `generationInputs` 承载生成上下文、来源和应用元数据,保存规则取决于接口。场景与音效重建配方;图集原图可保存自定义字段,透明图集与切片另建处理阶段 / 来源元数据。已知字段仍有实际消费者,例如规范图的“游戏类型”、V2 面板恢复字段和客户端来源标记,不能把整个对象描述为无业务作用。 - **排查 / 使用**:同时核对正式请求字段、当前接口的重建 / 清理逻辑,以及最终资源 / 素材记录;重要要求必须进入 `prompt`、`iconDescriptions` 或场景结构化参数。项目与素材库读取可取得服务端实际保存的上下文,但它不等于完整 HTTP 重放载荷;重试仍保存原始请求和幂等键。 +- **AGC 美术包**:核心图集新请求将原 brief 与共用的风格、内容和间距要求分别写入 `iconDescriptions`;普通图标入口仍原文单项透传。新增模板只影响新请求,旧请求继续按冻结正文和操作身份恢复,不改原 brief 的意图判据。真实 HTTP 载荷与旧请求恢复必须同时验证,不能只测试 `artSpec` 包含文案。 - **权威说明**:[External v1 OpenAPI](../../openapi/genarrative-external-v1.openapi.json) 与 [API 指南的 Generation Inputs Metadata](../../../.codex/skills/genarrative-external-editor-api/references/requests-and-outputs.md#generation-inputs-metadata)。本条记录现有行为,不引入 API、存储或 UI 行为变更。 ## 2026-10-03 AGC 随包 plugins 的 feature 档位必须与消费方一致,且门禁会因 build.rs 未重跑而假通过 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index d7b725884..338845922 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -1574,6 +1574,9 @@ game-project/ ## 2026-08-23 Direct Codex 美术包显式重生成与切片投影 +- 美术包核心图集的新请求必须把 brief 原文与客户端的素材内容、风格和排布要求共同写入 `iconDescriptions`。内容覆盖当前玩法需要的玩家主体及状态、目标或收集物、障碍或场景元素、反馈特效;各素材独立排布并留出切分间距,沿用规范图的轮廓、材质、色板与光照。描述条数和内容类别不代表切片数量或返回顺序。规范图通过 `referenceId`、比例和尺寸通过正式参数传入;透明结果由服务端纯色底生成与抠图提供,不向模型追加直接生成透明背景的要求。`generationInputs.artSpec` 仅保留描述性上下文,不能作为要求已进入生成提示词的证据。 +- 美术包工具继续只接收 `brief` 与 `mode`,Agent 在 brief 中提供具体主题、风格、实体和反馈需求;客户端补齐通用要求。普通画布及 `agc_generate_image` 图标入口仍将 trim 后原文作为唯一描述项。美术包新增要求使用独立描述项并校验当前 API 的每条 200 字符、合计 2000 字符 / 6144 UTF-8 字节及 100 条上限,不挤占或截断原文;本次不扩展已有 brief 长度合同。只在没有冻结请求的新提交路径组装要求,已有 `prepared / accepted / legacy-completed` 请求按原请求体、幂等键与操作身份恢复,原 brief 的意图比较不受新增模板影响。验证必须覆盖实际 HTTP 请求、普通入口原文、描述边界及旧请求恢复,不以元数据或提示词文本存在代替传输证据。 +- 请求要求的自动化证据由 `art_package_spritesheet_posts_requirements_but_ordinary_icons_keep_original_text`、`art_package_spritesheet_recovers_old_requests_without_injecting_new_requirements` 与描述边界用例提供:生产生成入口经过本地 HTTP 夹具,核对真实 POST、原请求字节、幂等键及重复恢复零新增生成 POST。真实 Provider 的视觉效果不由请求夹具替代。 - 标准美术包在客户端将规范图、背景图和主图集统一保存为 PNG:下载仍校验来源、声明类型与文件签名,随后按真实内容接受 PNG/JPEG/WebP,在已有 20 MiB、4096 像素单边和 64 MiB 解码内存限制内完整解码。有效 PNG 原样保留,JPEG/WebP 编码成 PNG,最终内容也不得超过 20 MiB;本地媒体类型固定为 `image/png`。转码不补造透明度,主图集与独立切片继续执行真实 alpha、可见像素、尺寸和唯一性合同;平台独立切片仍须为 PNG。此行为仅属于美术包,普通图片工具的指定扩展名合同不变。 - 美术包的转码在项目提交锁和本地写入之前完成,文件摘要、已安装结果识别、替换恢复和 manifest 登记均使用最终 PNG。转码失败保留原生成账本与平台身份,同冻结意图重试重新读取已有结果,不提交新的付费生成;不改变请求快照、幂等身份、固定资源路径或旧 PNG 包的复用方式,无数据迁移。验收覆盖 JPEG/WebP 转码、PNG 字节不变、损坏/超限拒绝、真实透明度以及已有结果重复恢复零生成 POST;客户端真实 Provider 的整包验证单独记录。 - 转码自动化验收由 `canvas_generation_tests` 的格式/边界用例和 `retained_runtime_generation_retries_a_completed_stage_without_posting_again` 本地 HTTP 夹具覆盖:PNG/JPEG/WebP 均先下载损坏内容,再从同一已完成账本恢复两次,核对最终 PNG、稳定本地 asset ID、Canvas 来源身份、账本保留/清理及零生成 POST;替换与补偿继续由现有图集事务和 Direct 重生成用例覆盖。真实 Provider 的客户端整包效果不由这些夹具替代。 -- 2.52.0 From ea18cf8f127ee79678ad64db25bde90fa3362ee1 Mon Sep 17 00:00:00 2001 From: lhk Date: Mon, 5 Oct 2026 10:58:08 +0100 Subject: [PATCH 03/10] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF=E5=9B=BE=E9=9B=86=E6=95=B0=E9=87=8F=E7=BA=A6=E6=9D=9F?= =?UTF-8?q?=E5=B9=B6=E6=8C=89=E5=AE=9E=E9=99=85=E4=BA=A7=E7=89=A9=E4=BA=A4?= =?UTF-8?q?=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除美术包和直接生图工具的切片数量参数及按序用途映射 按源图集隔离切片路径并返回完整资源身份与路径标注预览 支持零切片总图交付及动态集合事务和重生成恢复 保留旧数量账本原请求身份并修复完成结果切分声明持久化 同步工具提示词、美术技能、技术规范与定向测试证据 --- .../prompts/runtime/texts/direct-tools.json | 5 +- .../prompts/runtime/texts/execution.json | 2 +- .../prompts/runtime/texts/native-tools.json | 3 +- .../prompts/runtime/texts/runtime.json | 2 +- .../resources/agc-skills/manifest.json | 4 +- .../agc-skills/taonier-art-assets/SKILL.md | 8 +- .../references/platform-art-contract.md | 10 +- .../src-tauri/src/agent/art_manifest.rs | 150 +++- .../src-tauri/src/agent/direct_runtime/mod.rs | 238 ++++- .../src-tauri/src/agent/direct_tool_bridge.rs | 215 ++--- .../src-tauri/src/agent/direct_tools_mcp.rs | 17 +- .../src/agent/generation/canvas_generation.rs | 822 +++++++++++++----- .../generation/external_generation_state.rs | 42 +- .../src/agent/tool/generate_image/error.rs | 16 +- .../src-tauri/src/commands.rs | 2 - ...计划】AGC图集实际产物与语义识别-2026-10-05.md | 27 + ...¨‹碑】AGC图集实际产物与语义识别-2026-10-05.md | 36 + ...¨‹碑】AGC美术包生成需求入参修正-2026-10-05.md | 2 +- docs/project-memory/shared-memory/pitfalls.md | 14 +- ...¹案】AI游戏创作智能体App实施计划-2026-06-24.md | 20 +- 20 files changed, 1141 insertions(+), 494 deletions(-) create mode 100644 docs/project-memory/plans/【实施计划】AGC图集实际产物与语义识别-2026-10-05.md create mode 100644 docs/project-memory/plans/【里程碑】AGC图集实际产物与语义识别-2026-10-05.md diff --git a/apps/ai-game-creator-shell/src-tauri/prompts/runtime/texts/direct-tools.json b/apps/ai-game-creator-shell/src-tauri/prompts/runtime/texts/direct-tools.json index f50343523..0266e5d6e 100644 --- a/apps/ai-game-creator-shell/src-tauri/prompts/runtime/texts/direct-tools.json +++ b/apps/ai-game-creator-shell/src-tauri/prompts/runtime/texts/direct-tools.json @@ -9,7 +9,7 @@ "agc_update_plan.description": "更新当前回合的进度计划,字段与 update_plan 相同:可选 explanation,以及 plan 中的 step/status(pending、in_progress、completed)。它可与其它独立工具并行;同一计划的连续更新按依赖顺序提交。计划完成只表示进度,不代替宿主交付验收。", "agc_write_file.parameters.path": "当前项目根下的相对路径,例如 game/index.html、assets/manifest.json 或 data/gameplay-spec.md", "agc_write_file.parameters.content": "仅填写目标文件的完整原始 UTF-8 正文", - "taonier_prepare_game_art.description": "创建或恢复当前 AGC 项目的陶泥儿标准游戏美术包。默认复用有效美术包;根据当前对话需要选择 regenerate 重新生成。授权使用 AGC 客户端当前登录会话;遇到 401/403 时报告客户端登录或权限状态异常并停止。", + "taonier_prepare_game_art.description": "创建或恢复当前 AGC 项目的陶泥儿标准游戏美术包,返回总图、实际切片与路径标注预览;需看图识别用途,内容要求不保证切片数量或顺序。默认复用有效美术包;根据当前对话需要选择 regenerate 重新生成。授权使用 AGC 客户端当前登录会话;遇到 401/403 时报告客户端登录或权限状态异常并停止。", "taonier_prepare_game_art.parameters.brief": "面向当前游戏的简洁视觉需求:写明主题、风格、玩家主体及状态、目标或收集物、障碍或场景元素、反馈特效等具体需要的素材。工具会补齐沿用规范图和素材独立排布的通用要求;内容类别不代表切片数量或返回顺序", "taonier_prepare_game_art.parameters.mode": "缺省安全复用有效美术包;Codex 仅在当前对话需要换一套或重新生成时使用 regenerate", "agc_generate_image.description": "生成一张新图片:普通插画、角色立绘、统一视觉规范图、游戏 UI 设计图或透明游戏素材图集。仅在用户明确要求生成新图时调用。", @@ -17,10 +17,9 @@ "agc_generate_image.parameters.kind": "image=普通新图(保留生成原图),character=角色图(纯色底生成后自动抠图,产出透明背景立绘,prompt 只描述角色主体),icon-spec=统一视觉规范图,ui-design=完整 UI 设计图,icon-spritesheet=透明游戏素材图集(纯色底生成后自动抠图并切片,项目须已有 icon-spec 规范图),publication-material=发布宣传图", "agc_generate_image.parameters.assetName": "本地素材的人类可读显示名称", "agc_generate_image.parameters.outputPath": "可选项目相对输出路径,必须位于 assets/ 且不能覆盖已有文件", - "agc_generate_image.parameters.sliceMode": "仅适用于 kind=icon-spritesheet,且必填:需求明确要求等分网格、固定槽位或指定行列数时传 grid,并用 gridX/gridY 传入需求中的行列数;自由排布、数量不定或只要求一张图集时传 connected-components,需要约束素材张数时用 sliceCount。", + "agc_generate_image.parameters.sliceMode": "仅适用于 kind=icon-spritesheet,且必填:需求明确要求等分网格、固定槽位或指定行列数时传 grid,并用 gridX/gridY 传入需求中的行列数;自由排布、数量不定或只要求一张图集时传 connected-components,实际切片数量由图像决定,内容需求不保证数量或用途顺序;查看返回图片后识别用途。", "agc_generate_image.parameters.gridX": "grid 模式横向网格数量,只能与 sliceMode=grid 同时提供", "agc_generate_image.parameters.gridY": "grid 模式纵向网格数量,只能与 sliceMode=grid 同时提供", - "agc_generate_image.parameters.sliceCount": "只与 kind=icon-spritesheet 且 sliceMode=connected-components 同时提供,用于约束目标素材张数;省略时按图像内容自动识别", "agc_generate_image.parameters.screenColor": "抠图纯色背景,仅用于 kind=character(角色形象)和 kind=icon-spritesheet(图标素材)。生成时把主体置于该纯色背景上,回图后据此抠除背景。取值为 auto 或下列色板 hex 之一,传值只填 hex 本身:#CFEFFF(浅雾蓝)、#B0C2E0(浅钢蓝)、#FFD6C2(暖浅桃色)、#E6D8FF(淡薰衣草紫)、#F4D8E8(浅粉灰)、#7FB3FF(中度天蓝)、#FFF2A8(浅柠黄)、#CFFFE1(淡薄荷绿)、#D8DEE8(浅中性灰)、#D8D2E8(淡灰紫)、#A8F7F0(高对比浅青)、#A0BBA0(灰竹绿);auto 时由服务端自动选色。手动指定时选择与主体颜色明显不同的背景色", "agc_edit_image.description": "修改一张已登记图片:换装、改色、换背景或局部重绘。sourceLocalAssetId 必须使用 agc_list_registered_assets 返回的当前项目图片 localAssetId。", "agc_edit_image.parameters.sourceLocalAssetId": "当前项目已登记的图片 localAssetId", diff --git a/apps/ai-game-creator-shell/src-tauri/prompts/runtime/texts/execution.json b/apps/ai-game-creator-shell/src-tauri/prompts/runtime/texts/execution.json index bef5c46e7..5e0892573 100644 --- a/apps/ai-game-creator-shell/src-tauri/prompts/runtime/texts/execution.json +++ b/apps/ai-game-creator-shell/src-tauri/prompts/runtime/texts/execution.json @@ -7,7 +7,7 @@ "playtest.laneDefense": "完成合同要求 lane-defense-v1 交互试玩。game/index.html 必须持续更新