Merge pull request 'master' (#43) from master into release
Reviewed-on: #43
1
.cloudbase/container/debug.json
Normal file
@@ -0,0 +1 @@
|
||||
{"containers":[],"config":{}}
|
||||
16
.codegraph/.gitignore
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# CodeGraph data files
|
||||
# These are local to each machine and should not be committed
|
||||
|
||||
# Database
|
||||
*.db
|
||||
*.db-wal
|
||||
*.db-shm
|
||||
|
||||
# Cache
|
||||
cache/
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Hook markers
|
||||
.dirty
|
||||
143
.codegraph/config.json
Normal file
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"version": 1,
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.js",
|
||||
"**/*.jsx",
|
||||
"**/*.py",
|
||||
"**/*.go",
|
||||
"**/*.rs",
|
||||
"**/*.java",
|
||||
"**/*.c",
|
||||
"**/*.h",
|
||||
"**/*.cpp",
|
||||
"**/*.hpp",
|
||||
"**/*.cc",
|
||||
"**/*.cxx",
|
||||
"**/*.cs",
|
||||
"**/*.php",
|
||||
"**/*.rb",
|
||||
"**/*.swift",
|
||||
"**/*.kt",
|
||||
"**/*.kts",
|
||||
"**/*.dart",
|
||||
"**/*.svelte",
|
||||
"**/*.vue",
|
||||
"**/*.liquid",
|
||||
"**/*.pas",
|
||||
"**/*.dpr",
|
||||
"**/*.dpk",
|
||||
"**/*.lpr",
|
||||
"**/*.dfm",
|
||||
"**/*.fmx",
|
||||
"**/*.scala",
|
||||
"**/*.sc"
|
||||
],
|
||||
"exclude": [
|
||||
"**/.git/**",
|
||||
"**/node_modules/**",
|
||||
"**/vendor/**",
|
||||
"**/Pods/**",
|
||||
"**/dist/**",
|
||||
"**/build/**",
|
||||
"**/out/**",
|
||||
"**/bin/**",
|
||||
"**/obj/**",
|
||||
"**/target/**",
|
||||
"**/*.min.js",
|
||||
"**/*.bundle.js",
|
||||
"**/.next/**",
|
||||
"**/.nuxt/**",
|
||||
"**/.svelte-kit/**",
|
||||
"**/.output/**",
|
||||
"**/.turbo/**",
|
||||
"**/.cache/**",
|
||||
"**/.parcel-cache/**",
|
||||
"**/.vite/**",
|
||||
"**/.astro/**",
|
||||
"**/.docusaurus/**",
|
||||
"**/.gatsby/**",
|
||||
"**/.webpack/**",
|
||||
"**/.nx/**",
|
||||
"**/.yarn/cache/**",
|
||||
"**/.pnpm-store/**",
|
||||
"**/storybook-static/**",
|
||||
"**/.expo/**",
|
||||
"**/web-build/**",
|
||||
"**/ios/Pods/**",
|
||||
"**/ios/build/**",
|
||||
"**/android/build/**",
|
||||
"**/android/.gradle/**",
|
||||
"**/__pycache__/**",
|
||||
"**/.venv/**",
|
||||
"**/venv/**",
|
||||
"**/site-packages/**",
|
||||
"**/dist-packages/**",
|
||||
"**/.pytest_cache/**",
|
||||
"**/.mypy_cache/**",
|
||||
"**/.ruff_cache/**",
|
||||
"**/.tox/**",
|
||||
"**/.nox/**",
|
||||
"**/*.egg-info/**",
|
||||
"**/.eggs/**",
|
||||
"**/go/pkg/mod/**",
|
||||
"**/target/debug/**",
|
||||
"**/target/release/**",
|
||||
"**/.gradle/**",
|
||||
"**/.m2/**",
|
||||
"**/generated-sources/**",
|
||||
"**/.kotlin/**",
|
||||
"**/.dart_tool/**",
|
||||
"**/.vs/**",
|
||||
"**/.nuget/**",
|
||||
"**/artifacts/**",
|
||||
"**/publish/**",
|
||||
"**/cmake-build-*/**",
|
||||
"**/CMakeFiles/**",
|
||||
"**/bazel-*/**",
|
||||
"**/vcpkg_installed/**",
|
||||
"**/.conan/**",
|
||||
"**/Debug/**",
|
||||
"**/Release/**",
|
||||
"**/x64/**",
|
||||
"**/.pio/**",
|
||||
"**/release/**",
|
||||
"**/*.app/**",
|
||||
"**/*.asar",
|
||||
"**/DerivedData/**",
|
||||
"**/.build/**",
|
||||
"**/.swiftpm/**",
|
||||
"**/xcuserdata/**",
|
||||
"**/Carthage/Build/**",
|
||||
"**/SourcePackages/**",
|
||||
"**/__history/**",
|
||||
"**/__recovery/**",
|
||||
"**/*.dcu",
|
||||
"**/.composer/**",
|
||||
"**/storage/framework/**",
|
||||
"**/bootstrap/cache/**",
|
||||
"**/.bundle/**",
|
||||
"**/tmp/cache/**",
|
||||
"**/public/assets/**",
|
||||
"**/public/packs/**",
|
||||
"**/.yardoc/**",
|
||||
"**/coverage/**",
|
||||
"**/htmlcov/**",
|
||||
"**/.nyc_output/**",
|
||||
"**/test-results/**",
|
||||
"**/.coverage/**",
|
||||
"**/.idea/**",
|
||||
"**/logs/**",
|
||||
"**/tmp/**",
|
||||
"**/temp/**",
|
||||
"**/_build/**",
|
||||
"**/docs/_build/**",
|
||||
"**/site/**"
|
||||
],
|
||||
"languages": [],
|
||||
"frameworks": [],
|
||||
"maxFileSize": 1048576,
|
||||
"extractDocstrings": true,
|
||||
"trackCallSites": true
|
||||
}
|
||||
23
.codex/config.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
# Genarrative 项目级 Codex 配置。
|
||||
# 这里仅保存可进入仓库的 hook 配置与脚本;个人 token、MCP server、模型路由仍放在个人 ~/.codex/config.toml。
|
||||
|
||||
[features]
|
||||
hooks = true
|
||||
|
||||
# Codex 准备执行 git commit 前检查 TypeScript / admin-web / api-server 编译错误。
|
||||
# 脚本也可手动运行:
|
||||
# node .codex/hooks/pre-submit-compile-check.mjs
|
||||
[[hooks.PreToolUse]]
|
||||
matcher = "Bash|shell_command|functions.shell_command"
|
||||
command = "node .codex/hooks/pre-submit-compile-check.mjs"
|
||||
timeout = 180
|
||||
statusMessage = "提交前检查编译错误"
|
||||
|
||||
# Codex 每次工具修改文件后执行:同步 CodeGraph 索引。
|
||||
# 脚本也可手动运行:
|
||||
# node .codex/hooks/post-edit-codegraph-sync.mjs
|
||||
[[hooks.PostToolUse]]
|
||||
matcher = "Bash|Edit|MultiEdit|Write|apply_patch|shell_command|functions.shell_command|functions.apply_patch"
|
||||
command = "node .codex/hooks/post-edit-codegraph-sync.mjs"
|
||||
timeout = 60
|
||||
statusMessage = "更新 CodeGraph 索引"
|
||||
@@ -3,4 +3,14 @@ version = 1
|
||||
name = "Genarrative"
|
||||
|
||||
[setup]
|
||||
script = ""
|
||||
script = '''
|
||||
cp "$env:CODEX_SOURCE_TREE_PATH\.env.secrets.local" "$env:CODEX_WORKTREE_PATH\.env.secrets.local"
|
||||
npm install
|
||||
npm run codegraph:init
|
||||
npm run codegraph:index
|
||||
'''
|
||||
|
||||
[[actions]]
|
||||
name = "运行"
|
||||
icon = "run"
|
||||
command = "npm run dev"
|
||||
|
||||
51
.codex/hooks/post-edit-codegraph-sync.mjs
Normal file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { existsSync, mkdirSync } from 'node:fs';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = resolve(scriptDir, '..', '..');
|
||||
const logDir = resolve(repoRoot, '.codex', 'logs');
|
||||
const hasCodegraphConfig = existsSync(resolve(repoRoot, '.codegraph', 'config.json'));
|
||||
const npmCommand = process.platform === 'win32' ? 'cmd' : 'npm';
|
||||
|
||||
if (!hasCodegraphConfig) {
|
||||
console.log('[codex-hook] 未发现 .codegraph/config.json,跳过 CodeGraph 同步。');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const result = spawnSync(npmCommand, process.platform === 'win32' ? ['/d', '/s', '/c', 'npm run codegraph:sync'] : ['run', 'codegraph:sync'], {
|
||||
cwd: repoRoot,
|
||||
shell: false,
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
...process.env,
|
||||
NO_COLOR: process.env.NO_COLOR ?? '1',
|
||||
},
|
||||
});
|
||||
|
||||
mkdirSync(logDir, { recursive: true });
|
||||
if (result.stdout) {
|
||||
process.stdout.write(result.stdout);
|
||||
}
|
||||
if (result.stderr) {
|
||||
process.stderr.write(result.stderr);
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
console.error(`[codex-hook] CodeGraph 同步启动失败:${result.error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (result.signal) {
|
||||
console.error(`[codex-hook] CodeGraph 同步被信号终止:${result.signal}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if ((result.status ?? 0) !== 0) {
|
||||
console.error('[codex-hook] CodeGraph 同步失败,请手动运行 npm run codegraph:sync 查看详情。');
|
||||
process.exit(result.status ?? 1);
|
||||
}
|
||||
|
||||
console.log('[codex-hook] CodeGraph 已同步。');
|
||||
122
.codex/hooks/pre-submit-compile-check.mjs
Normal file
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = resolve(scriptDir, '..', '..');
|
||||
const npmCommand = process.platform === 'win32' ? 'cmd' : 'npm';
|
||||
const hookInput = readHookInput();
|
||||
|
||||
if (hookInput && !isGitCommitCommand(extractShellCommand(hookInput))) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const validationSteps = [
|
||||
{
|
||||
label: 'TypeScript typecheck',
|
||||
command: npmCommand,
|
||||
args: process.platform === 'win32' ? ['/d', '/s', '/c', 'npm run typecheck'] : ['run', 'typecheck'],
|
||||
},
|
||||
{
|
||||
label: 'Admin web typecheck',
|
||||
command: npmCommand,
|
||||
args: process.platform === 'win32' ? ['/d', '/s', '/c', 'npm run admin-web:typecheck'] : ['run', 'admin-web:typecheck'],
|
||||
},
|
||||
{
|
||||
label: 'Rust api-server compile check',
|
||||
command: 'cargo',
|
||||
args: ['check', '-p', 'api-server', '--manifest-path', 'server-rs/Cargo.toml'],
|
||||
},
|
||||
];
|
||||
|
||||
for (const step of validationSteps) {
|
||||
const result = runStep(step);
|
||||
if (!result.ok) {
|
||||
const reason = `[codex-hook] 提交前编译检查失败:${step.label}。请修复编译错误后再提交。`;
|
||||
console.error(reason);
|
||||
if (hookInput) {
|
||||
console.log(JSON.stringify({ decision: 'block', reason }));
|
||||
process.exit(0);
|
||||
}
|
||||
process.exit(result.status ?? 1);
|
||||
}
|
||||
}
|
||||
|
||||
console.error('[codex-hook] 提交前编译检查通过。');
|
||||
|
||||
function runStep(step) {
|
||||
console.error(`[codex-hook] ${step.label}`);
|
||||
const result = spawnSync(step.command, step.args, {
|
||||
cwd: repoRoot,
|
||||
shell: false,
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
...process.env,
|
||||
NO_COLOR: process.env.NO_COLOR ?? '1',
|
||||
},
|
||||
});
|
||||
|
||||
if (result.stdout) {
|
||||
process.stderr.write(result.stdout);
|
||||
}
|
||||
if (result.stderr) {
|
||||
process.stderr.write(result.stderr);
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
console.error(`[codex-hook] ${step.label} 启动失败:${result.error.message}`);
|
||||
return { ok: false, status: 1 };
|
||||
}
|
||||
|
||||
if (result.signal) {
|
||||
console.error(`[codex-hook] ${step.label} 被信号终止:${result.signal}`);
|
||||
return { ok: false, status: 1 };
|
||||
}
|
||||
|
||||
return {
|
||||
ok: (result.status ?? 0) === 0,
|
||||
status: result.status ?? 1,
|
||||
};
|
||||
}
|
||||
|
||||
function readHookInput() {
|
||||
try {
|
||||
const rawInput = readFileSync(0, 'utf8').trim();
|
||||
if (!rawInput) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(rawInput);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function extractShellCommand(input) {
|
||||
const candidates = [
|
||||
input?.tool_input?.command,
|
||||
input?.toolInput?.command,
|
||||
input?.tool_args?.command,
|
||||
input?.toolArgs?.command,
|
||||
input?.arguments?.command,
|
||||
input?.params?.command,
|
||||
input?.command,
|
||||
];
|
||||
|
||||
const command = candidates.find(value => typeof value === 'string' && value.trim().length > 0);
|
||||
if (command) {
|
||||
return command;
|
||||
}
|
||||
|
||||
const shellCommand = input?.tool_input?.cmd ?? input?.toolInput?.cmd ?? input?.arguments?.cmd;
|
||||
if (Array.isArray(shellCommand)) {
|
||||
return shellCommand.join(' ');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function isGitCommitCommand(command) {
|
||||
return /(^|[;&|]\s*)git(?:\.exe)?\b[\s\S]{0,200}\bcommit\b/iu.test(command);
|
||||
}
|
||||
@@ -1,389 +1,296 @@
|
||||
---
|
||||
name: genarrative-play-type-integration
|
||||
description: 在 Genarrative 中新增一个创作入口/玩法类型时,按入口配置、前端分流、契约、后端接口、工作台、结果页、可选 runtime 与作品架的顺序接入。
|
||||
description: 在 Genarrative 中新增或补齐一个创作入口/玩法类型时,按入口配置、前端分流、契约、后端接口、工作台、独立生成页、结果页、发布、统一作品详情、正式 runtime、公开 read model、基础统计与作品架/广场的顺序接入。
|
||||
license: MIT
|
||||
metadata:
|
||||
author: Hermes Agent
|
||||
version: "1.0"
|
||||
---
|
||||
|
||||
# Genarrative 新增玩法类型接入流程
|
||||
# Genarrative 新增玩法创作工具平台 SOP
|
||||
|
||||
用于在 Genarrative 中新增一个创作入口/玩法类型,而不是单纯说明用户如何从入口创建作品。
|
||||
把新增玩法当成平台能力接入,不把任何既有玩法当作默认模板。先确定通用模式和契约,再写具体玩法代码。
|
||||
|
||||
## 适用场景
|
||||
## 硬性禁区
|
||||
|
||||
- 新增一个游戏玩法入口
|
||||
- 让某个玩法从“敬请期待”变为可创建
|
||||
- 为新玩法补齐创作工作台、结果页、发布与试玩链路
|
||||
- 将新玩法接入创作中心作品架与广场
|
||||
- 不恢复前端硬编码入口配置;创作入口事实源必须来自 SpacetimeDB 和 `/api/creation-entry/config`。
|
||||
- 不把聊天输入区、流式消息或轻输入 Agent 作为新增玩法默认工作台。
|
||||
- 不在新页面内手写图片上传、参考图、AI 重绘、历史图选择、预览或删除确认逻辑。
|
||||
- 不把通用系列素材建模成任一玩法专属 DTO;玩法只能追加自己的运行态字段。
|
||||
- 不让前端承接正式业务真相;发布、试玩、通关、失败、计分、资产持久化和作品状态以后端投影为准。
|
||||
- 不新建平行入口系统、平行作品架或平行公开列表;优先扩展现有平台壳、现有阶段和现有聚合。
|
||||
- 不在 UI 面板内默认写功能说明、规则说明或开发解释文案。
|
||||
|
||||
## 先判断接入级别
|
||||
## 接入前输入
|
||||
|
||||
### 1. 只做入口占位
|
||||
开始编码前,PRD 或当前玩法文档必须已经明确:
|
||||
|
||||
只需要新增入口配置,不接 session/workspace/result/runtime。
|
||||
- `playId`、对外名称、工程域名、入口 `visible/open` 状态。
|
||||
- 创作链路:入口 -> 工作台 -> 生成页 -> 结果页 -> 试玩 -> 发布 -> 运行态。
|
||||
- 表单字段:字段名、默认值、校验、后端落库位置、生成提示词来源。
|
||||
- 单图资产槽位:`slotId`、`slotType`、`slotName`、提示词来源、读取字段、写回字段、是否允许历史图和 AI 重绘。
|
||||
- 系列素材槽位:`batchId` 语义、`sheetSpec`、`slotSpecs`、切图规则、透明化规则、失败回写、局部重生成策略。
|
||||
- API 命名空间:`/api/creation/<play>/sessions`、`actions`、`works`、`runtime`。
|
||||
- 草稿恢复、生成中恢复、失败重试、登录切换、发布后回读和移动端行为。
|
||||
- 验证命令和例外声明;没有例外时写明“无创作工具模式例外”。
|
||||
|
||||
适合:
|
||||
## 默认模式
|
||||
|
||||
- 敬请期待
|
||||
- 灰度占位
|
||||
新增玩法默认采用表单/图片输入创作工作台:
|
||||
|
||||
### 2. 可进入创作工作台
|
||||
```text
|
||||
创作入口 -> 表单/图片输入工作台 -> 生成页 -> 结果页 -> 试玩 -> 发布 -> 运行态
|
||||
```
|
||||
|
||||
需要补齐前端分流、session、工作台、结果页,至少能生成草稿。
|
||||
工作台只提交结构化表单、图片槽位和配置 payload。确需自然语言对话时,先走“例外流程”,不能把聊天区直接加进默认工作台。
|
||||
|
||||
### 3. 完整玩法闭环
|
||||
## SOP
|
||||
|
||||
需要补齐:
|
||||
### 1. 文档和领域词先行
|
||||
|
||||
- 创作入口
|
||||
- 工作台
|
||||
- 草稿生成
|
||||
- 独立生成页(如果存在自动资产生成)
|
||||
- 结果页
|
||||
- 发布
|
||||
- 统一作品详情页
|
||||
- 试玩 runtime
|
||||
- 作品架 / 广场 / 分享
|
||||
- 公开作品卡 / 作品架 / 广场 / 分享
|
||||
- 正式 runtime 统计
|
||||
|
||||
## 推荐接入顺序
|
||||
## 公开闭环决策点
|
||||
|
||||
### Step 1: 先定玩法 ID 和能力边界
|
||||
新增玩法如果要作为公开作品交付,先按这些决策点对齐,不要直接套某个玩法的具体字段或 UI:
|
||||
|
||||
先明确:
|
||||
```text
|
||||
创作入口 / 工作台
|
||||
-> 草稿保存 / 编译
|
||||
-> 独立生成页(自动素材,可选但推荐)
|
||||
-> 结果页(确认、单槽重试、上传、发布)
|
||||
-> 统一作品详情页 /works/detail?work=<公开作品码>
|
||||
-> 正式 runtime
|
||||
-> 基础统计 / 公开 read model
|
||||
-> 作品架 / 发现流 / 分类推荐 / 今日卡片
|
||||
```
|
||||
|
||||
- `id` 是什么
|
||||
- 入口是否可见
|
||||
- 是否可点击创建
|
||||
- 是否需要对话式创作
|
||||
- 是否需要生成中页面
|
||||
- 是否需要 result/runtime/gallery/share
|
||||
必须先做这些决策:
|
||||
|
||||
不要先随便起临时 ID 再改名。
|
||||
1. **公开作品身份**:是否需要公开作品码;前缀、解析入口、分享 URL 和统一作品详情页如何映射。
|
||||
2. **编辑契约边界**:哪些字段是 v1 公开编辑语义;旧字段是兼容、迁移、只读展示,还是明确不兼容。
|
||||
3. **生成阶段归属**:是否有自动素材生成;生成动作放在工作台、独立 `*-generating` 页,还是结果页手动触发。
|
||||
4. **失败承接策略**:全部失败、部分失败、单槽失败分别进入哪个页面;错误态由生成页还是结果页承接。
|
||||
5. **结果页能力边界**:结果页只做确认 / 单槽重试 / 重新生成 / 上传,还是还允许批量生成、规则编辑或资源配置。
|
||||
6. **发布后去向**:发布成功后默认进入统一作品详情页;只有明确需要时才新增专属详情页。
|
||||
7. **公开卡片资产来源**:封面是复用已有素材合成、使用首图、还是新增独立封面资产。
|
||||
8. **公开读取路径**:广场 / 发现流读取 SpacetimeDB view 或 public read model;api-server 是否需要订阅缓存,避免每请求 procedure 热路径。
|
||||
9. **runtime 模式差异**:`draft` 和 `published` 的输入能力、mock/debug 开关、鉴权、开始条件是否不同。
|
||||
10. **正式统计口径**:哪些 runtime 事件写正式统计;草稿试玩、mock、debug 是否必须排除。
|
||||
11. **规则参数归属**:哪些配置是创作者可编辑;哪些阈值、时长、冷却、计分、反作弊、裁决规则必须留在后端规则集。
|
||||
12. **旧数据策略**:旧草稿、旧发布配置、旧分享码是迁移、降级展示、重新生成,还是明确不兼容。
|
||||
|
||||
### Step 2: 新增入口配置
|
||||
- `AGENTS.md`
|
||||
- `.hermes/shared-memory/`
|
||||
- `CONTEXT.md`
|
||||
- `docs/README.md`
|
||||
- `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
|
||||
- 相关玩法 PRD 或设计文档
|
||||
|
||||
文件:
|
||||
如果文档不能精确指导字段、契约、资产槽位、生成流程和恢复语义,先补文档再编码。新增长期约定时同步 `.hermes/shared-memory/`。
|
||||
|
||||
- `src/config/newWorkEntryConfig.ts`
|
||||
### 2. 定玩法边界
|
||||
|
||||
在 `creationTypes` 中新增:
|
||||
固定 `playId`、对外名称、工程域、入口状态、是否支持结果页、试玩、发布、作品架、广场、分享和 runtime。不要先用临时 ID 接线后再批量改名。
|
||||
|
||||
- `id`
|
||||
- `title`
|
||||
- `subtitle`
|
||||
- `badge`
|
||||
- `visible`
|
||||
- `open`
|
||||
### 3. 接入口配置
|
||||
|
||||
如果只是占位:
|
||||
入口配置事实源是 SpacetimeDB `creation_entry_type_config`。后台通过 `/admin/api/creation-entry/config` 管理,前台通过 `/api/creation-entry/config` 读取。
|
||||
|
||||
- `visible: true`
|
||||
- `open: false`
|
||||
前端只允许在展示层派生:
|
||||
|
||||
### Step 3: 确认类型过滤逻辑
|
||||
- 可见入口卡片。
|
||||
- 锁定或开放状态。
|
||||
- 排序、图标、短标题等展示信息。
|
||||
|
||||
文件:
|
||||
`api-server` 路由熔断必须使用同一份入口配置。禁止新增或恢复前端本地默认入口配置作为事实源。
|
||||
|
||||
- `src/components/platform-entry/platformEntryCreationTypes.ts`
|
||||
### 4. 前端阶段
|
||||
|
||||
检查:
|
||||
按需要扩展 `SelectionStage`:
|
||||
|
||||
- `getVisiblePlatformCreationTypes()` 是否能展示新类型
|
||||
- `isPlatformCreationTypeVisible()` 是否能识别新类型
|
||||
- `locked` / `hidden` 是否正确映射
|
||||
- `<play>-workspace`
|
||||
- `<play>-generating`
|
||||
- `<play>-result`
|
||||
- `<play>-runtime`
|
||||
- `<play>-gallery-detail`
|
||||
|
||||
### Step 4: 扩展页面阶段
|
||||
阶段名可以按玩法命名,UI 形态必须仍是表单/图片创作工作台。进入工作台时只初始化结构化草稿状态,不启动默认聊天会话。
|
||||
|
||||
文件:
|
||||
### 5. 工作台实现
|
||||
|
||||
- `src/components/platform-entry/platformEntryTypes.ts`
|
||||
工作台必须满足:
|
||||
|
||||
为新玩法补充 `SelectionStage`:
|
||||
- 使用表单控件、图片槽位、风格选项、难度选项、开关和提交按钮组织输入。
|
||||
- 单图槽位统一使用 `CreativeImageInputPanel`。
|
||||
- 组件缺少能力时先扩展 `CreativeImageInputPanel` 的受控 props,不在玩法页面复制上传、参考图、AI 重绘、历史图、预览或删除确认。
|
||||
- 主图读取、裁剪、历史素材弹层、计费确认、自动保存和后端请求由外层页面持有;通用面板只表达输入 UI 和短生命周期 UI 状态。
|
||||
- 提交 payload 必须是表单字段与图片槽位结构,不是用户消息文本。
|
||||
|
||||
- `*-agent-workspace`
|
||||
- `*-generating`(可选)
|
||||
- `*-result`
|
||||
- `*-runtime`(可选)
|
||||
- `*-gallery-detail`(可选)
|
||||
### 6. 单图资产槽位
|
||||
|
||||
### Step 5: 在总流程中加类型分流
|
||||
角色形象、UI 背景、容器、封面、分享图、图标等单张图都按单图资产槽位处理。
|
||||
|
||||
文件:
|
||||
统一约定:
|
||||
|
||||
- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
||||
- 槽位用 `slotId` 稳定标识,`slotType` 表达用途,`slotName` 用于 UI 标签。
|
||||
- 上传图、参考图、AI 重绘、历史图选择和删除确认都通过 `CreativeImageInputPanel` 入口表达。
|
||||
- 后端写回 `imageSrc`、`imageObjectKey`、`assetObjectId` 中可用字段;前端展示前通过平台资产读取能力换签。
|
||||
- 单个槽位重生成只禁用该槽位动作,不阻塞结果页其它槽位、系列素材槽位或导航。
|
||||
|
||||
在 `handleCreationHubCreateType(type)` 中新增分支,确保:
|
||||
### 7. 系列素材图集生成
|
||||
|
||||
- 能进入对应工作台
|
||||
- 能设置对应 `selectionStage`
|
||||
- 能关闭类型弹层
|
||||
地块、物品、障碍、装饰、UI 部件等一组同类素材都走通用系列素材图集生成流程:
|
||||
|
||||
同时按玩法补齐:
|
||||
```text
|
||||
批量规划 -> sheet 生图 -> 后端切图 -> 去背景/透明化 -> PNG 输出 -> OSS 持久化 -> 状态回写 -> 局部重生成
|
||||
```
|
||||
|
||||
- `open<Play>AgentWorkspace()`
|
||||
- `leave<Play>Flow()`
|
||||
- `submit<Play>Message()`(对话式玩法)
|
||||
- `execute<Play>Action()`
|
||||
玩法只提供:
|
||||
|
||||
### Step 6: 接入通用 Agent flow controller
|
||||
- `sheetSpec`:画布比例、行列、单格尺寸、输出格式、背景处理策略。
|
||||
- `slotSpecs`:每个素材槽位的 `slotId`、`slotType`、`slotName`、提示词、sheet 单元格映射。
|
||||
- 玩法字段映射:把通用素材结果映射回玩法自己的 draft/profile/runtime 字段。
|
||||
|
||||
文件:
|
||||
通用系列素材结果建议字段:
|
||||
|
||||
- `src/components/platform-entry/usePlatformCreationAgentFlowController.ts`
|
||||
|
||||
如果是 Agent 型玩法,复用通用控制器:
|
||||
|
||||
- `createSession`
|
||||
- `getSession`
|
||||
- `streamMessage`
|
||||
- `executeAction`
|
||||
- `isBusy`
|
||||
- `batchId`
|
||||
- `slotId`
|
||||
- `slotType`
|
||||
- `slotName`
|
||||
- `prompt`
|
||||
- `imageSrc`
|
||||
- `imageObjectKey`
|
||||
- `assetObjectId`
|
||||
- `sourceSheetCell`
|
||||
- `status`
|
||||
- `error`
|
||||
- `streamingReplyText`
|
||||
- `selectionStage` 切换
|
||||
|
||||
### Step 7: 定义 shared contracts
|
||||
玩法可追加运行态字段,例如半径、宽度、视图索引或碰撞参数,但不能依赖任何玩法专属字段作为平台通用模型。新增玩法 compile action 内部调用通用系列素材服务;如果通用服务还缺能力,先补通用服务再接玩法。
|
||||
|
||||
前端:
|
||||
### 8. 契约与 API
|
||||
|
||||
前后端必须同步补契约:
|
||||
|
||||
- `packages/shared/src/contracts/`
|
||||
|
||||
后端:
|
||||
|
||||
- `server-rs/crates/shared-contracts/src/`
|
||||
|
||||
至少补齐:
|
||||
玩法 API 保留独立命名空间:
|
||||
|
||||
- session snapshot
|
||||
- create session request/response
|
||||
- message request/response
|
||||
- action request/response
|
||||
- draft/result 结构
|
||||
- work summary / gallery 结构(如果需要)
|
||||
- runtime 结构(如果需要)
|
||||
- `POST /api/creation/<play>/sessions`
|
||||
- `GET /api/creation/<play>/sessions/{sessionId}`
|
||||
- `POST /api/creation/<play>/sessions/{sessionId}/actions`
|
||||
- `/api/creation/<play>/works`
|
||||
- `/api/creation/<play>/runtime`
|
||||
|
||||
### Step 8: 实现前端 service client
|
||||
契约需要区分:
|
||||
|
||||
目录参考:
|
||||
- 工作台输入。
|
||||
- 草稿 snapshot。
|
||||
- 单图资产槽位。
|
||||
- 系列素材批次与槽位。
|
||||
- 结果页操作。
|
||||
- 发布作品摘要。
|
||||
- runtime snapshot。
|
||||
|
||||
- `src/services/`
|
||||
### 9. 后端分层
|
||||
|
||||
按玩法补:
|
||||
按 DDD 边界落地:
|
||||
|
||||
- creation client
|
||||
- runtime client(可选)
|
||||
- works client(可选)
|
||||
- gallery client(可选)
|
||||
- `module-<play>`:纯领域规则、状态机、draft/runtime 校验。
|
||||
- `shared-contracts`:前后端 DTO。
|
||||
- `spacetime-module`:表、reducer、procedure、事务编排、migration。
|
||||
- `spacetime-client`:typed facade 和 row mapper。
|
||||
- `api-server`:Axum 路由、鉴权、BFF、SSE、生成编排。
|
||||
- `platform-*`:LLM、图片生成、OSS、认证等外部副作用。
|
||||
|
||||
建议保持和现有玩法一致的 API base 与命名风格。
|
||||
涉及 SpacetimeDB schema 时同步 `migration.rs`、表目录和绑定,并运行 `npm run check:spacetime-schema`。
|
||||
|
||||
### Step 9: 接后端 API
|
||||
|
||||
文件参考:
|
||||
|
||||
- `server-rs/crates/api-server/src/puzzle.rs`
|
||||
- `server-rs/crates/api-server/src/puzzle_agent_turn.rs`
|
||||
- `server-rs/crates/api-server/src/match3d.rs`
|
||||
|
||||
通常需要:
|
||||
|
||||
- create session
|
||||
- get session
|
||||
- send message
|
||||
- stream message
|
||||
- execute action
|
||||
- publish / save / delete
|
||||
- runtime start / action(可选)
|
||||
- gallery / detail(可选)
|
||||
|
||||
后端设计优先按 Genarrative 的 DDD 分层拆开,不要把玩法规则、数据库事务、LLM 调用和 HTTP handler 混在一个文件里:
|
||||
|
||||
- `module-<play>`:纯领域规则、状态机、draft/runtime 校验,不依赖 Axum、SpacetimeDB 或外部平台。
|
||||
- `shared-contracts`:前后端 DTO、请求/响应、session snapshot、draft/result/runtime 结构。
|
||||
- `spacetime-module`:表定义、reducer/procedure、事务编排、migration;表结构变化要同步生成绑定。
|
||||
- `spacetime-client`:api-server 到 SpacetimeDB 的 facade,隐藏 reducer 调用细节。
|
||||
- `api-server`:Axum 路由、鉴权、SSE/stream、应用层编排。
|
||||
- `platform-*`:LLM、资产上传、鉴权、第三方服务等副作用。
|
||||
|
||||
建议按四条线设计后端能力:
|
||||
|
||||
- Agent 创作线:session、turn、stream、compile action。
|
||||
- Works 作品线:保存、发布、删除、草稿恢复。
|
||||
- Gallery 广场线:公开列表、详情、like/remix/share。
|
||||
- Runtime 运行态线:开始试玩、提交动作、读取状态。
|
||||
|
||||
### Step 10: 新增工作台组件
|
||||
|
||||
目录建议:
|
||||
|
||||
- `src/components/<play>-creation/<Play>AgentWorkspace.tsx`
|
||||
|
||||
两种形态:
|
||||
|
||||
#### 对话式
|
||||
|
||||
适合设定逐轮补齐。
|
||||
|
||||
参考:
|
||||
|
||||
- `BigFishAgentWorkspace.tsx`
|
||||
- `Match3DAgentWorkspace.tsx`
|
||||
|
||||
#### 表单式
|
||||
|
||||
适合输入结构明确的玩法。
|
||||
|
||||
参考:
|
||||
|
||||
- `PuzzleAgentWorkspace.tsx`
|
||||
|
||||
### Step 11: 在渲染树中挂载新页面
|
||||
|
||||
文件:
|
||||
|
||||
- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
||||
|
||||
补齐:
|
||||
|
||||
- workspace 分支
|
||||
- generating 分支(如需要)
|
||||
- result 分支
|
||||
- runtime 分支(如需要)
|
||||
|
||||
### Step 12: 新增结果页
|
||||
|
||||
目录建议:
|
||||
|
||||
- `src/components/<play>-result/<Play>ResultView.tsx`
|
||||
### 10. 结果页
|
||||
|
||||
结果页至少支持:
|
||||
|
||||
- 展示 draft
|
||||
- 返回编辑
|
||||
- 发布
|
||||
- 试玩
|
||||
- 错误展示
|
||||
- 展示草稿和生成状态。
|
||||
- 返回工作台编辑。
|
||||
- 单图槽位重生成。
|
||||
- 系列素材追加、替换、局部重生成。
|
||||
- 发布。
|
||||
- 试玩。
|
||||
- 错误展示和失败重试。
|
||||
|
||||
### Step 13: 需要试玩就补 runtime
|
||||
单图槽位和系列素材槽位的生成状态互不阻塞。已有可查看结果时,局部重生成不能把作品架草稿重新变成不可打开的全局生成中。
|
||||
|
||||
目录建议:
|
||||
### 11. 运行态、作品架和广场
|
||||
|
||||
- `src/components/<play>-runtime/<Play>RuntimeShell.tsx`
|
||||
需要试玩或发布时补齐:
|
||||
|
||||
如果玩法是游戏类,建议补完整 runtime 闭环。
|
||||
- runtime start/action/finish API。
|
||||
- 作品保存、发布、删除、回读。
|
||||
- 作品架摘要。
|
||||
- 公开列表、详情、分享码。
|
||||
- 公开列表优先消费后端投影或 BFF 缓存,不让前端直接拼源表事实。
|
||||
|
||||
### Step 14: 接入作品架 / 广场 / 分享
|
||||
运行态可以做低延迟表现,但正式胜负、分数、奖励、排行榜和发布状态以后端裁决为准。
|
||||
|
||||
需要改:
|
||||
### 12. 恢复与登录态
|
||||
|
||||
- `src/components/custom-world-home/creationWorkShelf.ts`
|
||||
- `src/components/custom-world-home/CustomWorldCreationHub.tsx`
|
||||
- `src/services/publicWorkCode.ts`
|
||||
必须处理:
|
||||
|
||||
如果玩法支持发布,还要补:
|
||||
- 刷新恢复生成中草稿。
|
||||
- 生成页计时从后端摘要时间恢复。
|
||||
- 失败后回读 session/work detail 再决定是否展示失败。
|
||||
- 退出登录清空私有玩法状态。
|
||||
- 私有生成图展示前换签。
|
||||
- result/runtime 缺必要 draft 时回到可恢复入口,不停在空白页。
|
||||
|
||||
- public work code
|
||||
- public detail
|
||||
- publish share modal
|
||||
- like/remix(可选)
|
||||
### 13. 例外流程
|
||||
|
||||
### Step 15: 处理登录态与草稿恢复
|
||||
任何非表单/图片工作台、对话式 Agent、独立创作系统或特殊资产模型都必须先更新 PRD 和平台文档。例外声明至少写清:
|
||||
|
||||
要考虑:
|
||||
- 为什么默认表单/图片工作台不能满足。
|
||||
- 例外影响哪些输入、契约、后端流程和测试。
|
||||
- 如何保留单图资产槽位和系列素材槽位的通用能力。
|
||||
- 如何回退到平台默认链路。
|
||||
|
||||
- 刷新恢复草稿
|
||||
- 退出登录清空私有状态
|
||||
- result/draft 缺失时回退
|
||||
- busy / generating / runtime 中断恢复
|
||||
没有文档例外,不进入编码。
|
||||
|
||||
### Step 16: 补测试
|
||||
## PRD 检查块
|
||||
|
||||
至少覆盖:
|
||||
在新增玩法 PRD 中保留这一段:
|
||||
|
||||
- 入口展示
|
||||
- 类型分流
|
||||
- 工作台打开
|
||||
- session 创建
|
||||
- compile action
|
||||
- result 页切换
|
||||
- 发布后刷新作品架
|
||||
- runtime 进入与退出
|
||||
```md
|
||||
## 创作工具平台接入声明
|
||||
|
||||
## 最小改动清单
|
||||
|
||||
### 只做占位
|
||||
|
||||
只改:
|
||||
|
||||
- `src/config/newWorkEntryConfig.ts`
|
||||
|
||||
### 做到可进入工作台
|
||||
|
||||
至少改:
|
||||
|
||||
- `newWorkEntryConfig.ts`
|
||||
- `platformEntryTypes.ts`
|
||||
- `PlatformEntryFlowShellImpl.tsx`
|
||||
- 新玩法 service client
|
||||
- 新玩法工作台组件
|
||||
- shared contracts
|
||||
- 后端 API
|
||||
|
||||
### 做到完整闭环
|
||||
|
||||
还要补:
|
||||
|
||||
- result 页
|
||||
- runtime
|
||||
- works / gallery
|
||||
- public code
|
||||
- share
|
||||
- 作品架聚合
|
||||
- 测试
|
||||
|
||||
## 常见坑
|
||||
|
||||
1. 只加入口配置不够,类型分流和页面阶段也要补。
|
||||
2. `SelectionStage` 不扩展,前端无法安全切页。
|
||||
3. 新玩法如果要出现在作品架,必须改聚合逻辑,不只是加入口。
|
||||
4. 发布后不刷新 works/gallery,用户会看不到新作品。
|
||||
5. 如果走 SpacetimeDB,表结构变化要同步 migration 和绑定;`spacetime-client/src/module_bindings/` 通常是生成物,不要为了修编译或格式化而手改,优先改 module 源 schema/reducer/procedure 后重新生成。
|
||||
6. 做 analytics/tracking 这类 runtime 能力时,不要只补 API DTO;先在 `module-runtime` 写纯函数测试(例如 day/week/month/quarter/year bucket 聚合、scope/event 过滤),RED 后再补领域类型与聚合函数。
|
||||
7. 时间粒度聚合建议复用已有 date dimension 逻辑,把 daily stat 映射到 day/week/month/quarter/year bucket;bucket 输出要有稳定排序,并显式携带 `bucketKey`、`bucketStartDateKey`、`bucketEndDateKey`、`value`。
|
||||
8. 后端 shared-contracts 与前端 `packages/shared/src/contracts/runtime.ts` 要同步补 request/response/type union;admin-web 若有独立 `api/adminApiTypes.ts`,也要同步,避免共享包已更新但管理端本地类型缺失。
|
||||
9. 退出登录时要清空新玩法私有状态,避免串用户。
|
||||
10. 移动端入口卡片增多后要检查布局和滚动体验。
|
||||
|
||||
## 验证标准
|
||||
|
||||
一个玩法算真正接入成功,至少要满足:
|
||||
|
||||
- 入口能展示
|
||||
- 能进入对应工作台
|
||||
- 能创建 session
|
||||
- 能生成草稿
|
||||
- 能进入结果页
|
||||
- 能返回编辑
|
||||
- 如果需要,可试玩
|
||||
- 如果需要,可发布
|
||||
- 发布后能回到作品架 / 广场 / 分享链路
|
||||
|
||||
## 建议验证命令
|
||||
|
||||
按改动范围选择:
|
||||
|
||||
```bash
|
||||
# 后端 contracts / module-runtime / api-server
|
||||
cd server-rs
|
||||
cargo test -p shared-contracts
|
||||
cargo test -p module-runtime
|
||||
cargo check -p api-server
|
||||
|
||||
# SpacetimeDB schema/reducer/procedure 改动后,优先在有 CLI 的机器重新生成 bindings
|
||||
npm run spacetime:generate -- --rust-only
|
||||
|
||||
# 前端类型
|
||||
npm run admin-web:typecheck
|
||||
- 工作台模式:表单/图片输入创作工作台
|
||||
- 创作链路:入口 -> 工作台 -> 生成页 -> 结果页 -> 试玩 -> 发布 -> 运行态
|
||||
- 单图资产槽位:
|
||||
- slotId / slotType / slotName / 提示词来源 / 写回字段 / 是否允许历史图 / 是否允许 AI 重绘
|
||||
- 系列素材槽位:
|
||||
- batchId / sheetSpec / slotSpecs / 切图规则 / 透明化规则 / 失败回写 / 局部重生成
|
||||
- API 命名空间:/api/creation/<play>/...
|
||||
- 业务真相:后端裁决字段和前端表现字段边界
|
||||
- 创作工具模式例外:无;如有,先写明例外原因和回退方式
|
||||
- 验证命令:
|
||||
```
|
||||
|
||||
如果新增完整前端玩法闭环,还要按项目实际脚本补充 web typecheck、lint 或 Playwright/单元测试。
|
||||
## 验证门禁
|
||||
|
||||
按改动范围运行:
|
||||
|
||||
- `npm run check:encoding`
|
||||
- `npm run typecheck`
|
||||
- 前端工作台测试:确认没有聊天式 Agent 输入,提交的是表单/图片 payload。
|
||||
- `CreativeImageInputPanel` 测试:覆盖多玩法标签、上传、AI 重绘、参考图上限、历史图入口和删除确认。
|
||||
- 系列素材测试:覆盖 sheet layout、切图、透明化、OSS 持久化、追加、替换、局部重生成和失败回写。
|
||||
- 结果页测试:覆盖单图槽位重生成和系列素材槽位重生成互不阻塞。
|
||||
- 后端定向测试:覆盖 compile action、资产持久化、失败回写、发布和 runtime start。
|
||||
- 涉及 SpacetimeDB schema 时运行 `npm run check:spacetime-schema`。
|
||||
|
||||
@@ -5,7 +5,7 @@ description: Generate or inspect project image assets through this repository's
|
||||
|
||||
# gpt-image-2 VectorEngine
|
||||
|
||||
Use this skill for project-local image asset generation that must match the repository's `server-rs` VectorEngine `gpt-image-2-all` path. The folder still contains `apimart` in its name for compatibility with existing local plugin references.
|
||||
Use this skill for project-local image asset generation that must match the repository's `server-rs` VectorEngine `gpt-image-2` path. The folder still contains `apimart` in its name for compatibility with existing local plugin references.
|
||||
|
||||
## Workflow
|
||||
|
||||
@@ -40,22 +40,14 @@ Default body:
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "gpt-image-2-all",
|
||||
"model": "gpt-image-2",
|
||||
"prompt": "<prompt>",
|
||||
"n": 1,
|
||||
"size": "1024x1024"
|
||||
}
|
||||
```
|
||||
|
||||
For weak visual references in text-to-image generation, add:
|
||||
|
||||
```json
|
||||
{
|
||||
"image": ["data:image/png;base64,..."]
|
||||
}
|
||||
```
|
||||
|
||||
For image-to-image work that must follow a reference image closely, use the VectorEngine edits endpoint instead of the generations `image` array:
|
||||
For visual references, use the edit endpoint instead of the create endpoint:
|
||||
|
||||
```text
|
||||
POST {VECTOR_ENGINE_BASE_URL}/v1/images/edits
|
||||
@@ -73,9 +65,9 @@ size=1024x1024
|
||||
image=@reference.png
|
||||
```
|
||||
|
||||
Prefer edits for workflows where the reference image controls composition, pose, container shape, or layout. In this repository, Match3D container UI generation uses edits with `public/match3d-background-references/pot-fused-reference.png` as the `image` part.
|
||||
In this repository, calls with no reference images use `POST /v1/images/generations`; calls with any reference image use `POST /v1/images/edits` and pass references as one or more `image` form parts. Match3D container UI generation embeds `public/match3d-background-references/pot-fused-reference.png` into the edit request as an `image` part.
|
||||
|
||||
Accept image output from `data[].url`, `data[].b64_json`, or direct nested `url` fields. VectorEngine GPT-image-2-all currently returns synchronously; do not poll APIMart task endpoints.
|
||||
Accept image output from `data[].url`, `data[].b64_json`, or direct nested `url` fields. VectorEngine GPT-image-2 currently returns synchronously; do not poll APIMart task endpoints.
|
||||
|
||||
## Environment
|
||||
|
||||
|
||||
@@ -245,7 +245,7 @@ async function downloadUrl(url, timeoutMs) {
|
||||
|
||||
async function generateOne(env, entry, outDir) {
|
||||
const requestBody = {
|
||||
model: 'gpt-image-2-all',
|
||||
model: 'gpt-image-2',
|
||||
prompt: buildPrompt(entry),
|
||||
n: 1,
|
||||
size: '1024x1024',
|
||||
@@ -305,7 +305,7 @@ if (dryRun) {
|
||||
id: entry.id,
|
||||
title: entry.title,
|
||||
body: {
|
||||
model: 'gpt-image-2-all',
|
||||
model: 'gpt-image-2',
|
||||
prompt: buildPrompt(entry),
|
||||
n: 1,
|
||||
size: '1024x1024',
|
||||
|
||||
@@ -211,7 +211,7 @@ async function downloadUrl(url, timeoutMs) {
|
||||
|
||||
async function generateOne(env, template, outDir) {
|
||||
const requestBody = {
|
||||
model: 'gpt-image-2-all',
|
||||
model: 'gpt-image-2',
|
||||
prompt: buildPrompt(template),
|
||||
n: 1,
|
||||
size: '1024x1024',
|
||||
@@ -275,7 +275,7 @@ if (dryRun) {
|
||||
id: template.id,
|
||||
title: template.title,
|
||||
body: {
|
||||
model: 'gpt-image-2-all',
|
||||
model: 'gpt-image-2',
|
||||
prompt: buildPrompt(template),
|
||||
n: 1,
|
||||
size: '1024x1024',
|
||||
|
||||
1
.gitignore
vendored
@@ -36,6 +36,7 @@ temp*build*/
|
||||
/.codex-temp
|
||||
/target/
|
||||
/logs
|
||||
/server-rs/crates/*/logs/
|
||||
.worktrees/
|
||||
.env.secrets.local
|
||||
spacetime.local.json
|
||||
|
||||
399
.hermes/plans/架构优化计划书.md
Normal file
@@ -0,0 +1,399 @@
|
||||
# Genarrative(陶泥儿)项目架构优化计划书
|
||||
|
||||
**文档版本**:v1.0
|
||||
**编制日期**:2026-05-26
|
||||
**项目名称**:Genarrative(陶泥儿)
|
||||
**文档密级**:内部
|
||||
|
||||
---
|
||||
|
||||
## 一、项目概况
|
||||
|
||||
| 维度 | 详情 |
|
||||
|---|---|
|
||||
| **项目名** | Genarrative(陶泥儿) |
|
||||
| **项目定位** | AI Native 互动视觉 RPG 平台——支持多种玩法模板的 AI 创作、运行与分享("玩法类型平台") |
|
||||
| **核心玩法** | 拼图、视觉小说、Match3D、Bark Battle、Big Fish、Jump-Hop、Square Hole、木鱼、教娱等 10+ 种玩法模板 |
|
||||
| **代码规模** | 前端 ~823 个 TS/TSX 文件,后端 ~1532 个 Rust 文件,属于大型项目 |
|
||||
|
||||
### 1.1 计划目的
|
||||
|
||||
本计划书基于对 Genarrative 项目当前架构的全面分析,识别架构层面的关键问题,并提出分阶段、可落地的优化方案。旨在:
|
||||
|
||||
- 统一前端架构模式,降低团队认知成本和新人上手门槛
|
||||
- 提升模块内聚性,减少不必要的耦合与依赖
|
||||
- 建立自动化契约保障机制,降低跨语言同步出错风险
|
||||
- 优化工程基础设施,提高开发效率和运维可观测性
|
||||
|
||||
---
|
||||
|
||||
## 二、技术栈总览
|
||||
|
||||
| 层级 | 技术选型 | 版本 |
|
||||
|---|---|---|
|
||||
| **前端框架** | React + TypeScript + Vite | React 19 / TS 5.8 / Vite 6 |
|
||||
| **样式** | TailwindCSS | v4 |
|
||||
| **3D / 动画** | Three.js、Motion、cannon-es | - |
|
||||
| **后端 HTTP** | Rust + Axum(BFF 门面) | Axum 0.8 |
|
||||
| **游戏状态 DB** | SpacetimeDB(实时反应式数据库) | v2.2 |
|
||||
| **AI / LLM** | LangChain-Rust + LLM Proxy | - |
|
||||
| **小程序** | 微信小程序(含微信支付) | - |
|
||||
| **容器化** | Docker Compose(Nginx + API Server + OTel Collector) | - |
|
||||
| **运维** | systemd、Nginx、Jenkins CI/CD、k6 压测 | - |
|
||||
| **可观测性** | OpenTelemetry(OTLP → Grafana) | - |
|
||||
|
||||
---
|
||||
|
||||
## 三、当前架构分层图
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 入口层 │
|
||||
│ index.html → main.tsx → resolveAppRoute() → RouteComponent │
|
||||
│ (多入口路由:平台主页 / 拼图 / BigFish / Match3D / ...) │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 前端应用层 (src/) │
|
||||
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐│
|
||||
│ │ components/ │ │ services/ │ │ games/ ││
|
||||
│ │ *-creation │ │ *-creation │ │ bark-battle/ ││
|
||||
│ │ *-result │ │ *-runtime │ │ domain/ ││
|
||||
│ │ *-runtime │ │ *-works │ │ application/ ││
|
||||
│ │ common/ │ │ storyEngine │ │ infrastructure/ ││
|
||||
│ │ auth/ │ │ payment │ │ ui/ ││
|
||||
│ └─────────────┘ └──────────────┘ └──────────────────────┘│
|
||||
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐│
|
||||
│ │ hooks/ │ │ data/ │ │ routing/ ││
|
||||
│ │ persistence/│ │ functionCat. │ │ config/ editor/ ││
|
||||
│ └─────────────┘ └──────────────┘ └──────────────────────┘│
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│ Vite Proxy (/api/*)
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Rust 后端 (server-rs/) │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ api-server (Axum HTTP / SSE / BFF 门面) │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌──────────┐ ┌──────────────────┐ ┌──────────────┐ │
|
||||
│ │ platform │ │ module-* │ │ shared │ │
|
||||
│ │ -auth │ │ -puzzle │ │ -contracts │ │
|
||||
│ │ -llm │ │ -visual-novel │ │ -kernel │ │
|
||||
│ │ -image │ │ -match3d │ │ -logging │ │
|
||||
│ │ -oss │ │ -bark-battle │ └──────────────┘ │
|
||||
│ │ -speech │ │ -big-fish ... │ │
|
||||
│ │ -agent │ │ -runtime │ │
|
||||
│ └──────────┘ │ -combat/npc │ │
|
||||
│ └──────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ spacetime-module + spacetime-client → SpacetimeDB │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 辅助子系统 │
|
||||
│ apps/admin-web (React 后台管理) │
|
||||
│ miniprogram/ (微信小程序) │
|
||||
│ packages/shared (前后端共享契约/LLM工具) │
|
||||
│ deploy/ (Docker/Nginx/systemd/OTel) │
|
||||
│ scripts/ (40+ 构建/部署/检查脚本) │
|
||||
│ jenkins/ (CI/CD Pipeline) │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、架构亮点
|
||||
|
||||
1. **清晰的"玩法模板"平台模式**
|
||||
每种玩法遵循统一的 `creation → result → runtime` 三段式生命周期,前端 `components/` 和 `services/` 均按此模式组织。新增玩法可快速套用模板,极大降低了横向扩展成本。
|
||||
|
||||
2. **后端严格的分层约束**
|
||||
`api-server`(门面)→ `module-*`(领域)→ `spacetime-module`(持久化),`module-*` 不直接依赖 Axum、HTTP、SpacetimeDB table、LLM、文件系统,保证了领域纯净性和可测试性。
|
||||
|
||||
3. **SpacetimeDB 作为游戏状态核心**
|
||||
采用反应式实时数据库替代传统 Redis + PostgreSQL 组合,天然适合多人实时游戏状态同步,减少了中间层复杂度和延迟。
|
||||
|
||||
4. **前端 DDD 探索**
|
||||
`src/games/bark-battle/` 采用 `domain / application / infrastructure / ui` 四层 DDD 结构,为复杂玩法的前端架构提供了良好范本。
|
||||
|
||||
5. **完善的多入口路由体系**
|
||||
通过 `resolveAppRoute()` 按 URL path 分发到不同的 lazy-loaded 组件,实现了按需加载和良好的首屏性能。
|
||||
|
||||
6. **运维体系完备**
|
||||
Docker Compose + systemd + Nginx + OpenTelemetry + k6 压测 + Jenkins CI/CD,覆盖了构建、部署、监控、压测全链路。
|
||||
|
||||
---
|
||||
|
||||
## 五、问题诊断与改进方案
|
||||
|
||||
### 问题 1:前端 services/ 与 components/ 的耦合不统一
|
||||
|
||||
**现状**
|
||||
大部分玩法遵循 `services/` + `components/` 分离模式,但部分玩法运行时直接放在 `components/` 下,services 层职责模糊——有的承担了业务逻辑编排,有的仅作简单 API 调用。
|
||||
|
||||
**影响**
|
||||
- 新人阅读代码时无法预判某个逻辑应位于哪个目录
|
||||
- 单元测试困难:services 与组件耦合的业务逻辑无法独立测试
|
||||
- 跨玩法复用时需要额外的迁移成本
|
||||
|
||||
**改进方案**
|
||||
1. 制定规范:`services/` 仅负责纯 API 调用和数据转换,不包含业务判断逻辑
|
||||
2. 业务逻辑统一归到 `hooks/` 或 `games/<play-type>/application/`
|
||||
3. 以 puzzle 玩法为样板,先行重构并形成迁移指南,再推广至其余玩法
|
||||
4. 在 CI 中增加 ESLint 规则,禁止 `components/` 直接 import `services/` 之外的外部模块
|
||||
|
||||
**预期收益**
|
||||
- 职责边界清晰,降低认知成本约 30%
|
||||
- services 层可独立单测覆盖率达到 80%+
|
||||
- 新玩法开发上手时间从 2 天缩短至 0.5 天
|
||||
|
||||
---
|
||||
|
||||
### 问题 2:前端 DDD 与模板模式并存,架构不统一
|
||||
|
||||
**现状**
|
||||
`bark-battle` 采用 DDD 四层结构(`domain/application/infrastructure/ui`),其余玩法散落在 `components/` + `services/` 下,存在两种截然不同的组织范式。
|
||||
|
||||
**影响**
|
||||
- 团队内部对"正确"的代码组织方式缺乏共识
|
||||
- 代码审查时需要切换判断标准
|
||||
- DDD 玩法的优势无法在全局范围内发挥
|
||||
|
||||
**改进方案**
|
||||
1. 所有玩法统一迁移到 `src/games/<play-type>/` 下,采用 `domain / application / ui` 三层结构(infrastructure 按需保留)
|
||||
2. 以 puzzle 为样板完成首例迁移,产出迁移 Checklist 和模板生成脚本
|
||||
3. 新增玩法脚手架直接生成 DDD 结构目录
|
||||
4. 旧玩法分批次迁移,每批次 2-3 个玩法,在 4 个迭代内完成
|
||||
|
||||
**预期收益**
|
||||
- 架构一致性提升至 100%
|
||||
- 跨玩法逻辑复用变得可能(domain 层可共享)
|
||||
- 为后续 monorepo 改造打下基础
|
||||
|
||||
---
|
||||
|
||||
### 问题 3:后端 module-* 粒度偏细,存在潜在循环依赖风险
|
||||
|
||||
**现状**
|
||||
后端共 35 个 crate。`module-runtime` 被拆分为 3 个独立 crate,`module-combat / npc / inventory` 各自独立。部分紧密协作的模块之间可能存在隐式耦合。
|
||||
|
||||
**影响**
|
||||
- 编译时间增长(35 个 crate 独立编译)
|
||||
- 跨 crate 重构时需要同时修改多处
|
||||
- 循环依赖风险增加,可能在特定组合下触发编译失败
|
||||
|
||||
**改进方案**
|
||||
1. 评估合并方案:
|
||||
- `module-runtime-*` 系列合并为单 crate `module-runtime`,内部用 `mod` 做逻辑隔离
|
||||
- `module-combat / npc / inventory` 评估合并为 `module-combat`,npc 和 inventory 作为子模块
|
||||
2. 保留 trait/interface 抽象层,确保 module 之间不直接依赖具体实现
|
||||
3. 在 CI 中引入 `cargo-deny` 或自定义脚本,自动检测 module 间的依赖方向是否违反分层约束
|
||||
4. 目标:将 35 个 crate 精简至 25 个以内
|
||||
|
||||
**预期收益**
|
||||
- 全量编译时间预计缩短 15%-20%
|
||||
- 循环依赖风险归零
|
||||
- module 内部重构成本降低
|
||||
|
||||
---
|
||||
|
||||
### 问题 4:根目录 env 文件过多且混乱
|
||||
|
||||
**现状**
|
||||
根目录存在 4 个 env 文件(`.env`、`.env.example`、`.env.production` 等),且 `deploy/` 下另有多个 env 文件。配置分散在多处,部分文件之间字段不一致。
|
||||
|
||||
**影响**
|
||||
- 排查配置问题时需要翻阅多个文件
|
||||
- 新人无法快速确定本地开发需要哪些环境变量
|
||||
- 部署时可能遗漏或错误覆盖某项配置
|
||||
|
||||
**改进方案**
|
||||
1. 收敛为三层配置体系:
|
||||
- `.env.example`:包含所有可配置项的说明和默认值(唯一提交到仓库的 env 文件)
|
||||
- `.env.local`:本地开发覆盖(加入 .gitignore)
|
||||
- `deploy/env/<env-name>.env`:各部署环境专用配置
|
||||
2. 引入 config crate,支持层次覆盖(default → local → env-specific),启动时自动校验必填字段
|
||||
3. 在 CI 中加入 env 校验步骤:对比 `.env.example` 与部署环境的 env 文件,标记缺失或多余字段
|
||||
|
||||
**预期收益**
|
||||
- 配置查找时间从分钟级降至秒级
|
||||
- 部署配置遗漏导致的线上事故减少 90%+
|
||||
- 新成员本地环境搭建时间从 30 分钟缩短至 10 分钟
|
||||
|
||||
---
|
||||
|
||||
### 问题 5:scripts/ 目录膨胀为"万能工具箱"
|
||||
|
||||
**现状**
|
||||
`scripts/` 目录共 42 个文件,涵盖构建、部署、检查、迁移、生成、压测等,全部平铺在同一层级,缺乏分类。
|
||||
|
||||
**影响**
|
||||
- 难以快速定位所需脚本
|
||||
- 同类脚本缺乏命名规范
|
||||
- 新增脚本时不知道放在何处
|
||||
|
||||
**改进方案**
|
||||
1. 按功能域分类重组:
|
||||
|
||||
```
|
||||
scripts/
|
||||
├── build/ # 构建相关(vite、cargo、wasm 等)
|
||||
├── deploy/ # 部署相关(docker、systemd、rsync)
|
||||
├── check/ # 检查/校验(lint、format、type-check)
|
||||
├── spacetime/ # SpacetimeDB 相关(migration、seed)
|
||||
├── generate/ # 代码生成(scaffold、proto、types)
|
||||
└── loadtest/ # 压测脚本(k6 配置及辅助)
|
||||
```
|
||||
|
||||
2. 为每个子目录添加 README.md,说明各脚本用途和调用方式
|
||||
3. 将重复逻辑抽取为共享函数库
|
||||
|
||||
**预期收益**
|
||||
- 脚本查找效率提升 60%+
|
||||
- 降低脚本重复概率
|
||||
- 便于 CI Pipeline 直接引用标准化路径
|
||||
|
||||
---
|
||||
|
||||
### 问题 6:前端路由系统缺乏统一的"玩法注册"机制
|
||||
|
||||
**现状**
|
||||
`appRoutes.tsx` 中硬编码 `switch-case` 逻辑,每新增一种玩法需手动修改路由文件、入口组件、资源加载等多个位置。
|
||||
|
||||
**影响**
|
||||
- 新增玩法的接入点分散,容易遗漏
|
||||
- 路由文件随玩法增多持续膨胀
|
||||
- 无法实现"按需注册"——即使某环境不包含某玩法,路由代码仍然存在
|
||||
|
||||
**改进方案**
|
||||
1. 建立 `PlayTypeRegistry` 模式:每个玩法导出一个注册项对象,包含 `path`、`lazyComponent`、`preload` 等字段
|
||||
2. `resolveAppRoute()` 改为动态聚合所有注册项,替代硬编码 switch-case
|
||||
3. 支持环境级玩法开关:通过配置控制某环境启用哪些玩法,路由系统自动忽略未启用的
|
||||
|
||||
**预期收益**
|
||||
- 新增玩法零侵入路由系统,只需在玩法目录内添加注册文件
|
||||
- 路由文件体积与玩法数量解耦
|
||||
- 灰度发布和 A/B 测试变得可能
|
||||
|
||||
---
|
||||
|
||||
### 问题 7:前端缺少统一的状态管理层
|
||||
|
||||
**现状**
|
||||
前端状态管理依赖 React hooks + props drilling + services 层手动管理。未使用任何状态管理库(如 Zustand、Jotai、Redux)。
|
||||
|
||||
**影响**
|
||||
- 全局状态(用户认证、会话、通知)通过多层 props 传递,组件耦合度高
|
||||
- 跨页面状态无法优雅共享
|
||||
- 状态变更难以追踪和调试
|
||||
|
||||
**改进方案**
|
||||
1. 引入 Zustand(轻量、无 boilerplate、TS 友好)管理全局状态:
|
||||
- `useAuthStore`:认证状态
|
||||
- `useSessionStore`:当前会话/游戏状态
|
||||
- `useNotificationStore`:全局通知
|
||||
2. 玩法内状态继续使用 React hooks + `useReducer`,保持局部自治
|
||||
3. 全局 store 与玩法内 state 通过事件总线松耦合通信
|
||||
|
||||
**预期收益**
|
||||
- props drilling 层级从 5+ 层降至 1-2 层
|
||||
- 全局状态可追溯,支持 Redux DevTools 调试
|
||||
- 跨玩法状态共享(如用户余额、道具)变得自然
|
||||
|
||||
---
|
||||
|
||||
### 问题 8:shared-contracts 的实际复用程度待验证
|
||||
|
||||
**现状**
|
||||
前后端通过 `packages/shared` 共享 DTO 类型定义,但依赖手动同步 TypeScript 类型。可能存在前后端契约不一致但编译期无法检出的情况。
|
||||
|
||||
**影响**
|
||||
- 后端修改 DTO 字段后,前端可能遗漏更新导致运行时错误
|
||||
- 手动同步耗时且易出错
|
||||
- Code Review 时难以判断契约一致性
|
||||
|
||||
**改进方案**
|
||||
1. 引入 `ts-rs`:从 Rust 结构体自动生成 TypeScript 类型定义
|
||||
2. 将生成步骤集成到 CI Pipeline:
|
||||
- 每次 Rust PR 触发 `ts-rs` 重新生成 TS 类型
|
||||
- 对比生成的类型与仓库中的类型是否一致,不一致则 CI 失败
|
||||
3. 长期考虑引入 Protobuf / OpenAPI 作为跨语言契约的单一事实来源
|
||||
|
||||
**预期收益**
|
||||
- 前后端契约不一致导致的线上 bug 减少 95%+
|
||||
- 手动同步工作量归零
|
||||
- PR Review 时契约一致性问题自动拦截
|
||||
|
||||
---
|
||||
|
||||
## 六、改进优先级路线图
|
||||
|
||||
| 优先级 | 改进项 | 涉及层 | 建议时间 | 预期收益 |
|
||||
|---|---|---|---|---|
|
||||
| **P0** | 统一前端 services/hooks/components 职责边界 | 前端 | 第 1-2 周 | 降低认知成本,提升可测试性 |
|
||||
| **P1** | 建立 PlayType 注册机制 | 前端 | 第 2-3 周 | 新增玩法零侵入路由 |
|
||||
| **P1** | 评估 module-* 合并方案并执行 | 后端 | 第 3-4 周 | 减少编译时间 15%-20% |
|
||||
| **P1** | 引入 Zustand 全局状态管理 | 前端 | 第 4-5 周 | 改善状态追踪与跨组件共享 |
|
||||
| **P2** | 清理 scripts/ 目录结构 | 工程 | 第 5-6 周 | 提高可发现性 |
|
||||
| **P2** | 前端玩法统一迁移至 DDD 结构 | 前端 | 第 5-8 周 | 架构一致性 100% |
|
||||
| **P2** | env 配置收敛 | 工程 | 第 6-8 周 | 减少部署配置事故 |
|
||||
| **P3** | 前后端共享 DTO 自动化(ts-rs) | 全栈 | 第 6-8 周 | 消除契约不一致风险 |
|
||||
| **P3** | CI 分层约束检查(cargo-deny) | 后端 | 第 8-10 周 | 循环依赖归零 |
|
||||
|
||||
> **说明**:P0 为阻塞项,必须最先完成。P1 项可部分并行推进(PlayType 注册与 module 合并互不依赖)。P2/P3 为优化项,可在日常迭代中穿插推进。
|
||||
|
||||
---
|
||||
|
||||
## 七、架构健康度评分卡
|
||||
|
||||
| 维度 | 评分 | 当前状态 | 目标状态 |
|
||||
|---|---|---|---|
|
||||
| **分层清晰度** | ★★★★☆ | 后端分层严格,前端分层存在不一致 | ★★★★★ 前后端均严格分层 |
|
||||
| **模块化程度** | ★★★★☆ | 后端 35 crate 粒度偏细,前端结构化较好 | ★★★★☆ 后端精简至 25 crate |
|
||||
| **可扩展性** | ★★★★★ | 玩法模板模式使新增玩法成本低 | ★★★★★ 维持 |
|
||||
| **代码复用** | ★★★☆☆ | shared 层作用有限,services 层有重复 | ★★★★☆ DDD 统一后 domain 可复用 |
|
||||
| **DevOps 成熟度** | ★★★★★ | Docker + k6 + OTel + Jenkins 覆盖完整 | ★★★★★ 维持 |
|
||||
| **文档完备性** | ★★★★★ | docs/ 分类清晰,基线文档齐全 | ★★★★★ 维持 |
|
||||
| **技术债务管控** | ★★★★☆ | 有明确的"历史残留"标记和废弃策略 | ★★★★★ 增加自动化检测 |
|
||||
|
||||
**综合评级:A-(优秀,存在可优化空间)**
|
||||
|
||||
---
|
||||
|
||||
## 八、附录:代码规模统计
|
||||
|
||||
| 维度 | 数量 |
|
||||
|---|---|
|
||||
| **前端 TypeScript/TSX 文件** | ~823 个 |
|
||||
| **后端 Rust 源文件** | ~1532 个 |
|
||||
| **后端 Crate 数量** | 35 个 |
|
||||
| **核心玩法类型** | 10+ 种 |
|
||||
| **scripts/ 脚本数量** | 42 个 |
|
||||
| **根目录 env 文件** | 4 个 + deploy 下多个 |
|
||||
|
||||
### 模块规模明细(后端)
|
||||
|
||||
| Crate | 职责 | 建议 |
|
||||
|---|---|---|
|
||||
| `api-server` | Axum HTTP 门面,路由聚合 | 保持 |
|
||||
| `platform-*` (auth/llm/image/oss/speech/agent) | 平台级跨玩法能力 | 保持 |
|
||||
| `module-puzzle` | 拼图玩法 | 作为 DDD 迁移样板 |
|
||||
| `module-visual-novel` | 视觉小说 | 后续迁移 |
|
||||
| `module-match3d` | Match3D 三消 | 后续迁移 |
|
||||
| `module-bark-battle` | 犬吠对战 | 已对接前端 DDD |
|
||||
| `module-big-fish` | Big Fish | 后续迁移 |
|
||||
| `module-runtime*` (3 crates) | 通用运行时 | **建议合并为单 crate** |
|
||||
| `module-combat / npc / inventory` | 战斗系统 | **建议合并** |
|
||||
| `spacetime-module` + `spacetime-client` | SpacetimeDB 接入 | 保持 |
|
||||
| `shared-contracts / kernel / logging` | 共享基础设施 | 保持 |
|
||||
|
||||
---
|
||||
|
||||
> **文档结束**
|
||||
> 本计划书由 Genarrative 架构分析报告衍生,所有改进项均基于对当前项目代码库的实际分析。执行过程中如遇阻力或新发现,应及时更新本计划书并同步相关方。
|
||||
@@ -16,6 +16,250 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-26 平台跨流程错误统一用可复制来源弹窗展示
|
||||
|
||||
- 背景:拼图等生成链路可能同时存在多个草稿或游玩实例,页面内裸错误 banner 容易让用户误以为当前正在看的拼图失败,也不方便复制完整错误给开发排查。
|
||||
- 决策:平台入口、生成页、结果页、作品详情、作品架和运行态的跨流程错误统一收口到 `PlatformErrorDialog`;弹窗必须带错误来源,例如某个草稿、生成会话、作品详情或游玩实例,并提供复制按钮复制来源与错误内容。页面内旧的裸错误 banner、创作入口 modal 错误、生成页错误徽标等不再重复展示;表单校验和发布确认弹窗里的局部业务错误仍可保留在原弹窗内。
|
||||
- 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/platform-entry/PlatformErrorDialog.tsx`、`src/components/CustomWorldGenerationView.tsx`、`src/components/custom-world-home/CustomWorldCreationHub.tsx`、`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`src/components/platform-entry/PlatformWorkDetailView.tsx`、`src/components/platform-entry/PlatformEntryCreationTypeModal.tsx`、`src/components/puzzle-result/PuzzleResultView.tsx`。
|
||||
- 验证方式:`npm run test -- src/components/platform-entry/PlatformErrorDialog.test.tsx src/components/platform-entry/PlatformEntryCreationTypeModal.test.tsx`、`npm run typecheck`、`npm run check:encoding` 通过;手测时异步失败应弹出包含“错误来源”和“错误内容”的弹窗,复制按钮应复制完整诊断文本。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-26 生成任务完成在离开生成页后弹独立完成弹窗
|
||||
|
||||
- 背景:抓大鹅、拼图等生成任务完成时,用户如果已经离开生成页,草稿页的未读红点不足以表达“这次生成已完成”;但如果用户仍停留在生成页,结果页或试玩页本身就是完成反馈,不需要再叠一个成功提示。
|
||||
- 决策:平台壳层在 `markDraftReady(..., viewedImmediately=false)` 时额外弹出 `PlatformTaskCompletionDialog`,完成弹窗必须带来源和复制按钮;如果 `viewedImmediately=true`,只保留结果页 / 试玩页本身的完成反馈和草稿未读态,不重复弹窗。
|
||||
- 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/platform-entry/PlatformTaskCompletionDialog.tsx`、`src/components/platform-entry/PlatformErrorDialog.test.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
- 验证方式:`npm run test -- src/components/platform-entry/PlatformErrorDialog.test.tsx`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "completed match3d draft"` 通过后,离开生成页再完成的草稿应出现“生成完成”弹窗,且复制内容包含来源与状态。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-26 “我的”页任务卡读后端任务摘要并移除常驻填邀请码入口
|
||||
|
||||
- 背景:移动端“我的”页每日任务卡曾硬编码 `0 / 1`,任务领取完成后只刷新弹窗内任务中心,卡片本身不更新;页面底部还保留旧的“填邀请码”次级按钮,和当前五项常用功能宫格口径重复。
|
||||
- 决策:`RpgEntryHomeView` 的每日任务卡以 `/api/profile/tasks` 返回的任务中心为事实源,展示当前可操作任务的奖励、进度和状态;领取成功后同步使用 claim 响应里的 `center` 刷新卡片。移动端“我的”页不再渲染常驻“填邀请码”次级入口,邀请码填写仅保留邀请链接 query 自动打开弹窗和其它明确引导。
|
||||
- 影响范围:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||
- 验证方式:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx` 应断言任务卡显示 `1 / 1`、领取后显示已完成,且新用户账号也没有 `次级入口` / `填邀请码` 常驻按钮;`npm run typecheck`、`npm run check:encoding` 通过。
|
||||
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||
## 2026-05-26 生成页总进度圆弧逆时针回调 5 度
|
||||
|
||||
- 背景:创作生成页的总进度圆弧在 `160deg` 位置仍需轻微向左微调,用户要求向左逆时针回调 `5deg`。
|
||||
- 决策:共用 `GenerationProgressHero` 的 SVG 圆弧起始角从 `160deg` 调整为 `155deg`,track 和 fill 都使用同一个 `rotate(155 200 200)` 变换;仍保持 `270deg` 扫描角和正下方 `90deg` 留空。
|
||||
- 决策:总进度标题与百分比数字在 `GenerationProgressHero` 中显式提升到圆环之上,圆环 SVG 维持背景层级。
|
||||
- 决策:总进度标题与百分比数字的内容区上边距从 `pt-[4%]` 收紧到 `pt-[2%]`,桌面端使用 `sm:pt-[1.5%]`,进一步拉开与圆环弧线的距离。
|
||||
- 影响范围:`src/components/GenerationProgressHero.tsx`、共用 `CustomWorldGenerationView`、汪汪声浪 `BarkBattleGeneratingView` 以及生成页圆环布局文档。
|
||||
- 验证方式:`CustomWorldGenerationView` 和 `BarkBattleGeneratingView` 测试断言 `data-ring-start-degrees=155` 且 track / fill transform 都是 `rotate(155 200 200)`。
|
||||
- 关联文档:`docs/【玩法创作】生成页圆环布局口径-2026-05-23.md`。
|
||||
|
||||
## 2026-05-25 抓大鹅发现页官方 demo 使用静态资源与本地运行态
|
||||
|
||||
- 背景:本轮抓大鹅资源管线曾生成一套官方静态 demo,用于验证生图、切图和运行态资源闭环,但该 demo 已被移除,不再作为发现页入口。
|
||||
- 决策:发现页不再挂载前端固定官方抓大鹅 demo;公开卡片、作品号搜索、详情页和运行态启动全部来自后端真实 profile / gallery 投影,正式作品统一走 server runtime adapter。
|
||||
- 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、原前端 demo 数据文件(已删除)、Match3D 相关测试与原静态资源目录(已删除)。
|
||||
- 验证方式:发现页不再出现原固定 demo;Match3D 公共详情只从真实后端作品数据读取。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-25 抓大鹅运行态 HUD 收敛为拼图同款低遮挡样式
|
||||
|
||||
- 背景:抓大鹅游玩阶段 UI 需要继续对齐拼图运行态的观感,同时移除右上角设置入口、灰白半透底板和显眼锅壳,让棋盘区域更专注。
|
||||
- 决策:抓大鹅运行态只保留左上透明返回按钮,右上不再显示设置入口;顶部关卡名和倒计时直接复用拼图同款的铭牌 + 下挂计时牌结构、同色板、同造型和 `media/logo.png` 产品 logo;底部备选栏和道具图标保持交互边界但不再显示灰白半透底;中央容器图层可以视觉隐藏,但棋盘命中边界和既有交互逻辑保留。
|
||||
- 影响范围:`src/components/match3d-runtime/Match3DRuntimeShell.tsx`、`src/components/match3d-runtime/Match3DRuntimeShell.test.tsx`、`src/index.css`、抓大鹅玩法链路文档。
|
||||
- 验证方式:运行态页面不再渲染“打开抓大鹅设置”,顶部仍显示关卡名和倒计时,底部槽位和道具按钮 class 中不含旧白底视觉;相关测试通过后保持该口径。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-25 平台首页推荐按桌面与移动断点分流
|
||||
|
||||
- 背景:平台首页的推荐页在桌面与移动端之间原先共用同一套推荐运行态逻辑,容易让桌面和移动两套内容同时启动,也让首页的推荐卡与桌面发现壳互相抢状态。
|
||||
- 决策:`RpgEntryHomeView` 只接受同一个 `isDesktopLayout` 断点判断;桌面端首页渲染桌面发现壳(`今日游戏`、`推荐`、`作品分类` 等),不挂移动推荐嵌入运行态;移动端 `home` 才渲染推荐卡与嵌入运行态。平台壳和首页视图都必须共用 `usePlatformDesktopLayout()`,不能在不同文件里各自判断断点。推荐嵌入运行态不是登录门禁:未登录可直达匿名运行态;已登录或已有 access token 时继续使用账号 Bearer,但必须用 local auth impact 防止推荐卡 401 清空全局登录态。
|
||||
- 影响范围:`src/components/platform-entry/platformEntryResponsive.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、首页推荐相关测试与 `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
- 验证方式:桌面宽度下首页应只看到桌面发现壳,窄屏下首页应只看到移动推荐流;`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "recommend"`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation"`、`npm run typecheck`、`npm run check:encoding` 通过。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-25 新增玩法接入必须使用统一 SOP skill
|
||||
|
||||
- 背景:敲木鱼、跳一跳、汪汪声浪等玩法接入过程中,作品架曾经没有被作为强制闭环验收项,导致玩法可以先完成创作、发布、运行态或广场,但用户在草稿 / 已发布作品架中看不到自己的作品。
|
||||
- 决策:凡是新增、补齐、迁移或重构玩法入口、玩法类型、创作工作台、生成页、结果页、发布、运行态、作品架、广场或公开 read model 的任务,开始前必须显式读取并按 `.codex/skills/genarrative-play-type-integration/SKILL.md` 执行。需要发布或试玩的玩法,作品架不是可选项,必须补齐私有 `/works` 列表、作品摘要、pending shelf 兜底、统一作品架 adapter、打开详情 / 草稿恢复、已发布分享入口和草稿 / 已发布可见性测试。
|
||||
- 影响范围:`AGENTS.md`、`.codex/skills/genarrative-play-type-integration/SKILL.md`、玩法 PRD、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、新增玩法前后端接入流程。
|
||||
- 验证方式:玩法接入 PRD 和实现验收必须列出作品架链路;若一个玩法具备发布或试玩能力,但缺少 `/api/creation/<play>/works`、前端 client `listWorks`、`CustomWorldCreationHub` props、`creationWorkShelf` adapter 或草稿 / 已发布作品架测试,则接入不算完成。
|
||||
- 关联文档:`AGENTS.md`、`.codex/skills/genarrative-play-type-integration/SKILL.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-26 统一公开作品主读模型收口
|
||||
|
||||
- 背景:各玩法原有 `*_gallery_card_view` / `*_gallery_view` / `custom_world_gallery_entry` 已经足够承载各自 source 投影,但公开列表 / 详情在 `api-server` 侧分散拼装会继续放大重复逻辑和契约漂移。
|
||||
- 决策:新增跨玩法统一公开主读模型 `public_work_gallery_entry` 与 `public_work_detail_entry`。各玩法旧公开 view 不删除,退为 source / 兼容路径;`api-server` 公开列表与详情主路径统一读 public view cache,再映射回现有 HTTP DTO。前端首期仍不直接订阅 SpacetimeDB,只走 BFF HTTP。
|
||||
- 影响范围:`server-rs/crates/spacetime-module`、`server-rs/crates/spacetime-client`、`server-rs/crates/api-server`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【后端架构】统一公开作品ReadModel设计-2026-05-26.md`。
|
||||
- 验证方式:`SELECT * FROM public_work_gallery_entry` 与 `SELECT * FROM public_work_detail_entry` 可作为 `api-server` 长期订阅目标;`/api/public-works` 与 `/api/public-works/{publicWorkCode}` 走统一 cache;旧 `/api/runtime/<play>/gallery` 响应 shape 保持兼容。
|
||||
- 关联文档:`docs/technical/【后端架构】统一公开作品ReadModel设计-2026-05-26.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||
|
||||
## 2026-05-26 推荐页拼图下一关 pending 时保留当前运行态
|
||||
|
||||
- 背景:推荐页嵌入拼图在点击“下一关”时,`advancePuzzleNextLevel` 的服务端请求会短暂处于 pending。旧逻辑把推荐卡的 `isStartingRecommendEntry` 和拼图局部 busy 混在一起,导致外层直接切回“加载中...”,把当前 `PuzzleRuntimeShell` 一起卸载,视觉上像是切关闪回。
|
||||
- 决策:推荐页嵌入拼图切关 pending 期间必须保留当前运行态与棋盘,只让拼图壳内部 busy 表现承接同步;`isStartingRecommendEntry` 只表示推荐作品尚未真正启动出来,不再把已有嵌入拼图 run 的局部 busy 一并当成整卡加载态。若下一关落到相似作品,前端还必须把新作品写回推荐缓存并同步 `activeRecommendEntryKey`,避免运行态进入新作品但推荐卡元信息、分享 / 点赞 / 改造和后续“下一个”仍锚定旧作品。
|
||||
- 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、推荐页拼图切关测试与平台链路文档。
|
||||
- 验证方式:点击推荐页拼图“下一关”后,在 `advancePuzzleNextLevel` 未返回前,页面仍应保留 `puzzle-board`,且不出现 `加载中...` 占位;返回相似作品后,当前推荐卡的 `作品信息` 应显示新作品标题。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-24 创作 Tab banner 轮播只展示主题赛
|
||||
|
||||
- 背景:创作 Tab banner 曾经把后端入口配置里的默认活动横幅和两个主题赛一起轮播,导致首屏出现 58000 奖池活动卡,和当前只强调拼图 / 抓大鹅主题赛的产品口径不一致。
|
||||
- 决策:创作 Tab 首屏 banner 轮播只展示 `拼图主题创作赛` 与 `抓大鹅主题创作赛` 两张主题卡;后端返回的 `eventBanner` 仅作为开始时间、结束时间等公共字段来源,不再直接作为一张轮播卡渲染。banner 底部顺序固定为开始 / 结束时间条在上、分页点在下,且二者都在封面内容底部。
|
||||
- 影响范围:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`src/components/custom-world-home/CustomWorldCreationHub.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
- 验证方式:`CustomWorldCreationHub.test.tsx` 应断言默认活动标题不出现在 start-only 创作页,且 `creation-event-banner__timebar` 位于 `creation-event-banner__pager` 前。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-24 创作 Tab 首屏字号收敛到普通 UI 档位
|
||||
|
||||
- 背景:创作 Tab 的右上角泥点胶囊、赛事 banner、分类 Tab 和玩法卡标题 / 副标题 / 消耗说明曾经偏向展示级字号,和其它页面的常规 UI 字号不一致。
|
||||
- 决策:创作首屏优先使用 `11px` 到 `14px` 的普通 UI 字号档位;仅在数字本体或强调值上做局部加粗,不使用 `text-lg`、`text-xl` 或更大的展示级字号来撑首屏。
|
||||
- 影响范围:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、创作 Tab 相关测试、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
- 验证方式:`CustomWorldCreationHub.test.tsx` 的字号快照测试和本地浏览器检查都应确认右上组件、banner、分类 Tab、模板卡标题 / 副标题 / 消耗说明没有回到大字号。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-24 草稿页未读点统一使用暖棕色
|
||||
|
||||
- 背景:草稿页底部 Tab 和作品架的未读点之前仍用固定红色和红色 glow,和平台暖白/陶土橙体系不一致,也会让草稿未读态显得像危险告警。
|
||||
- 决策:`platform-nav-unread-dot` 与 `creation-work-card__unread-dot` 统一改用平台暖棕色 token,并把 glow 也切到暖棕色,不再直接写红色 literal 或红色阴影。
|
||||
- 影响范围:`src/index.css`、草稿页底部导航、草稿页作品架、相关 CSS 回归测试。
|
||||
- 验证方式:`src/index.test.ts` 需要断言两个 unread dot block 都不再包含 `#b64a35` 或 `rgba(239, 68, 68, ...)`,并且仍引用 `--platform-unread-dot-fill` / `--platform-unread-dot-glow`。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-24 创作 Tab 模板卡点击直达已有玩法入口表单
|
||||
|
||||
- 背景:创作 Tab 首屏需要对齐参考图,展示赛事 banner、玩法模板分类和两列模板卡;点击模板卡时,空白入口页会让用户多走一层,占位感也会让人误以为功能未接好。
|
||||
- 决策:`/creation/<play>` 直达对应玩法已有的入口创作表单 stage,不再保留空白创作入口页。RPG、拼图、抓大鹅、汪汪声浪、敲木鱼、视觉小说、宝贝识物等都直接进入既有工作台,继续承接草稿恢复和后续编排。创作 Tab 首屏 banner 按参考图拆成右上泥点胶囊、主体宣传封面图文、底部开始/结束时间条和分页点;玩法模板卡使用独立 `creation-template-card` 白底信息区,不复用暗图蒙版 `platform-creation-reference-card`,确保标题、描述和“预计消耗 10-20 泥点”可见。
|
||||
- 影响范围:`src/components/platform-entry/platformEntryTypes.ts`、`src/routing/appPageRoutes.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、创作大厅交互测试与平台入口文档。
|
||||
- 验证方式:`npm test -- src/routing/appPageRoutes.test.ts`、`npm test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t \"create tab opens match3d entry form from the template card|create tab opens puzzle entry form from the template card|create tab opens bark battle entry form from the template card\"`、`npm run typecheck`、`npm run check:encoding` 通过;创作卡片点击后应进入对应工作台,不再出现空白入口页。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-24 创作 Tab 顶栏余额与赛事奖池分离展示
|
||||
|
||||
- 背景:创作页顶部、banner 奖池和玩法卡消耗口径曾经混在一起,容易把活动奖池误认成账号余额,也让横向空间被外部边框和过大的卡片高度挤占。
|
||||
- 决策:移动端创作 Tab 顶栏与 `陶泥儿` 品牌同一行只显示真实账户泥点数,数据直接取 `profileDashboard.walletBalance`;banner 内只展示赛事奖池,新增拼图主题创作赛和抓大鹅主题创作赛,两个主题奖池各 `1000` 泥点数;玩法卡封面右下角固定展示 `10-20泥点数`,列表外框取消,卡片高度和横向间距一起收紧。
|
||||
- 影响范围:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、创作页相关测试和玩法链路文档。
|
||||
- 验证方式:移动端浏览器检查应看到创作顶栏余额、卡内分页点、内嵌横向 banner 和更紧凑的玩法卡;`CustomWorldCreationHub.test.tsx` 与 `RpgEntryHomeView.recharge.test.tsx` 的定向断言应保持通过。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-24 发现 / 创作 / 草稿三页去掉外层全局卡片壳
|
||||
|
||||
- 背景:发现 Tab、创作 Tab 和草稿 Tab 的页面根区原本都套着 `platform-page-stage`,导致全局内容卡片壳把横向空间吃掉,也让创作页和草稿页与发现页的频道标签 / 列表卡风格拉不开。
|
||||
- 决策:这三页的根内容区不再使用 `platform-page-stage` 作为外层全局卡片壳,只保留 `platform-remap-surface` 作为主题与输入框样式钩子;草稿页顶部 `全部 / 草稿 / 已发布` 切换复用发现页的 `platform-mobile-home-channel` 频道标签样式。
|
||||
- 影响范围:`src/components/custom-world-home/CustomWorldCreationHub.tsx`、`src/components/custom-world-home/CustomWorldWorkTabs.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/index.css`、相关创作 / 发现 / 草稿测试。
|
||||
- 验证方式:创作 Hub 和发现页定向测试通过;浏览器里这三页的根区不再出现 `platform-page-stage`,但仍保留 `platform-remap-surface` 命中。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-23 拼图生成页按后端真实进度推进阶段
|
||||
|
||||
- 背景:拼图生成页原先会按本地耗时自动推进步骤,容易在后端真实生成尚未完成时跳到后续阶段,导致页面状态和会话进度脱节。
|
||||
- 决策:拼图生成页的跨步骤推进只认后端会话 `progressPercent` 的真实里程碑,当前步骤内部再用本地耗时假进度平滑展示;总进度初始必须为 `0%`,之后按 `0-88`、`88-94`、`94-96`、`96-98` 的真实里程碑区间平滑推进。只要当前步骤生成内容未完成,就必须停留在当前步骤。页面只展示当前步骤标题和进度,不展示步骤详细描述。`生成拼图首图` 单独按 4 分钟估算,完整 AI 重绘路径约 448 秒;上传图且关闭 AI 重绘路径跳过首图生成,仍约 208 秒。
|
||||
- 影响范围:`src/services/miniGameDraftGenerationProgress.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/CustomWorldGenerationView.tsx`、拼图生成页相关测试与玩法链路文档。
|
||||
- 验证方式:拼图生成页恢复、轮询和测试都应以 `puzzleProgressPercent` 驱动阶段推进;`npm run test -- src/services/miniGameDraftGenerationProgress.test.ts src/components/CustomWorldGenerationView.test.tsx`、`npm run typecheck`、`npm run check:encoding` 通过。
|
||||
- 关联文档:`docs/【玩法创作】拼图生成页进度口径-2026-05-23.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-23 所有玩法生成页统一圆环主视觉
|
||||
|
||||
- 背景:多个玩法生成页分别展示横向总进度条、步骤列表或三槽位列表,和最新参考图里的陶泥儿圆环等待态不一致,也让移动端信息密度偏高。
|
||||
- 决策:`media/create_bg_video.mp4` 作为固定全屏背景层循环静音播放,主进度统一改为居中大圆弧,正下方保留 90 度留空;生成页顶部只保留返回入口和状态胶囊,圆弧左右悬浮半透明“预计等待 / 已耗时”时间卡,下方保留半透明当前步骤单卡和当前作品信息卡。生成页不再列表展示每个步骤块,只显示当前步骤名称和当前步骤进度;圆弧和当前步骤卡不再被独立大面板嵌套出双层卡片感。视频层需要显式触发播放,不能只依赖 `autoPlay/loop/muted`。顶部返回使用 `text-xs-sm`,右上状态使用 `11px-12px`,时间卡标签使用 `9px-10px`,时间值只展示纯时间,不重复拼“预计还需 / 已耗时”前缀;当前步骤标签使用 `10px-11px`,步骤名使用 `14px-15px`,步骤状态使用 `11px-12px`,底部玩法信息标题固定使用 `13px`,避免生成页 UI 字号大于其它页面。`CustomWorldGenerationView` 承接 RPG、拼图、抓大鹅、大鱼吃小鱼、方洞、跳一跳、敲木鱼、宝贝识物、视觉小说等共用生成页;汪汪声浪独立 `BarkBattleGeneratingView` 也对齐同一垂直布局。
|
||||
- 影响范围:`src/components/GenerationProgressHero.tsx`、`src/components/CustomWorldGenerationView.tsx`、`src/components/bark-battle-creation/BarkBattleGeneratingView.tsx` 和玩法链路文档。
|
||||
- 验证方式:执行 `npm run test -- src/components/CustomWorldGenerationView.test.tsx src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx`,并用桌面 / 移动端视口检查生成页只出现圆环和当前步骤卡。
|
||||
- 关联文档:`docs/【玩法创作】生成页圆环布局口径-2026-05-23.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-23 寓教于乐玩法入口收敛为马路街区式横向延展
|
||||
|
||||
- 背景:参考图和视频表明,寓教于乐板块的图形化入口更接近 Toca Life World 式的“中央马路串联主题小建筑群街区”,而不是乐园分区、环形岛屿或世界球体结构。
|
||||
- 决策:后续寓教于乐入口概念图统一采用“横屏 16:9、中央灰蓝色马路贯穿、建筑群沿路两侧聚集、左右边缘持续出画可接下一屏”的结构;马路必须带车道线、斑马线、路口和小汽车,区域通过水果店、画笔工坊、运动馆、音乐剧场、树屋温室等主题小建筑群暗示,不再使用乐园式分区组织。
|
||||
- 影响范围:寓教于乐入口概念图、image2 prompt 生成脚本、设计文档、后续横向世界地图探索稿。
|
||||
- 验证方式:新生成概念图必须满足“马路是主脊线、建筑群成街区聚合、左右边缘可延展、无品牌乐园元素”四项约束;若图面再跑回环形乐园或漂浮岛,需要重新收敛 prompt。
|
||||
- 关联文档:`docs/design/【前端体验】寓教于乐Toca式横向世界地图入口概念图-2026-05-23.md`、`scripts/generate-edutainment-road-town-map-concepts.mjs`、`output/imagegen/edutainment-road-town-map-concepts-20260523/`。
|
||||
|
||||
## 2026-05-22 敲木鱼图片创作采用三图 image2 链路
|
||||
|
||||
- 背景:敲木鱼自定义题材只生成中央敲击物时,运行态缺少与新主题匹配的竖屏背景和主题化返回按钮;若直接让背景 prompt 自由发挥,又容易把敲击物或木槌画进背景里。
|
||||
- 决策:敲木鱼 `compile-draft` / `regenerate-hit-object` 图片链路固定为三步 image2 edits。第一步调用 VectorEngine `/v1/images/edits` + `gpt-image-2`,以默认木鱼图作为结构和画风参考,用户上传参考图只作为同次请求的新主题参考,结合用户题材关键词或参考图主题生成 `1:1` 绿色背景主体图;`api-server` 先对这张绿幕图执行去绿背景处理并写回 `hitObjectAsset`。第二步必须以第一步抠图完成后的透明敲击物图作为参考,结合用户原始题材生成 `9:16` 背景环境图并写回 `backgroundAsset`,避免背景图继承绿幕或纯绿色画布。第三步必须以去绿后的敲击物主体图和背景环境图为参考,生成 `1:1` 绿色背景返回按钮图,服务端去绿后写回 `backButtonAsset`。三步 prompt 使用 PRD 中固定隐藏关键词,不追加额外 negative prompt;返回按钮只允许参考图约束圆形底色和箭头配色,不允许继承复杂造型、花纹、浮雕边、异形外框或装饰图案,主体视觉尺寸比当前模板再放大约 50%,并带主题色外描边;背景图不得包含敲击物本体或木槌互动物品,返回按钮图不得包含文字、数字、水印或额外 UI 面板。
|
||||
- 影响范围:`api-server` 木鱼图片生成编排、`wooden_fish_work_profile.background_asset_json`、`wooden_fish_work_profile.back_button_asset_json`、shared contracts、前端结果页 / 运行态背景与返回按钮展示、敲木鱼 PRD 和平台链路文档。
|
||||
- 验证方式:执行 `cargo test -p api-server wooden_fish --manifest-path server-rs/Cargo.toml`、`cargo test -p spacetime-client wooden_fish --manifest-path server-rs/Cargo.toml`、`npm run spacetime:generate`、`npm run check:spacetime-schema`、`npm run typecheck`。
|
||||
- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-25 通用系列素材图集实现下沉到 platform-image
|
||||
|
||||
- 背景:`generated_asset_sheets` 同时承载 sheet prompt、切图、绿幕去背、边缘 matte 清理和 OSS 持久化准备,长期放在 `api-server` 会把多个玩法的图片 seam 继续绑死在 HTTP crate 上。
|
||||
- 决策:通用系列素材图集的实现真值源下沉到 `platform-image::generated_asset_sheets`,`api-server::generated_asset_sheets` 只保留 `AppState` / `AppError` 适配与调用方兼容导出,不再承载图像处理和 OSS 请求构造细节。
|
||||
- 影响范围:`server-rs/crates/platform-image/src/generated_asset_sheets/`、`server-rs/crates/api-server/src/generated_asset_sheets.rs`、`server-rs/crates/api-server/src/match3d/item_assets.rs`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
- 验证方式:`cargo test -p platform-image --test generated_asset_sheets --manifest-path server-rs/Cargo.toml` 与 `cargo check -p api-server --manifest-path server-rs/Cargo.toml` 通过;调用方继续通过 `api-server` 的薄包装访问同一组能力。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-22 敲木鱼敲击物暂不做服务端抠图后处理
|
||||
|
||||
- 背景:gpt-image-2 偶尔会把木鱼图直接回成带黑底或其它实底背景的 PNG,但服务端抠图后处理在玉米等主题上误伤过主体像素。
|
||||
- 决策:敲木鱼 hit object 落盘前暂不做服务端抠图后处理,当前只通过 prompt 强约束真实透明 alpha PNG、透明底、禁止黑底 / 白底 / 棋盘格 / 实底背景。后续若重启后处理,必须先有可验证的保守策略,只能清理画布边缘连通背景,不能抠掉主体内部深色结构或主题细节。
|
||||
- 影响范围:`server-rs/crates/api-server/src/wooden_fish.rs`、敲木鱼 PRD、平台链路文档、后续同类 image2 单图资产落盘策略。
|
||||
- 验证方式:`cargo test -p api-server wooden_fish --manifest-path server-rs/Cargo.toml`,并在试玩阶段确认主体像素未被后处理误删。
|
||||
- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-22 敲木鱼背景中央禁主体要写成硬约束
|
||||
|
||||
- 背景:苹果等主题在试玩时,背景图中央仍可能残留主题主体,说明“外围设计”这种软描述不够。
|
||||
- 决策:敲木鱼背景 prompt 必须显式要求中央主体预留区保持干净,中央 40% 区域禁止出现主题主体、主体局部特写、轮廓影子、重复元素或主题主体碎片;主题元素只允许出现在外围氛围。
|
||||
- 影响范围:`server-rs/crates/api-server/src/wooden_fish.rs`、敲木鱼 PRD、平台链路文档、后续 image2 背景类玩法 prompt。
|
||||
- 验证方式:背景 prompt 单测应包含中央禁区硬约束,试玩图中央不再出现苹果或其它主题主体。
|
||||
- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-21 外部 API 失败必须 OTLP 上报并落库
|
||||
|
||||
- 背景:图片生成等外部供应商调用失败时,仅返回 502/504 或普通日志无法支持后续按 provider、阶段和重试属性聚合排障。
|
||||
- 决策:外部 API 调用未成功时,`api-server` 必须同时发送 OTLP 失败观测并写入 `tracking_event`。当前通用 VectorEngine `gpt-image-2` 图片生成 / 编辑适配器记录 `external_api_call_failure`,`scope_kind = module`、`scope_id = provider`、`module_key = external-api`,metadata 包含 endpoint、operation、failureStage、statusCode、statusClass、timeout、retryable、errorMessage、latencyMs、promptChars、referenceImageCount、imageModel 和 rawExcerpt。
|
||||
- 落库方式:优先复用 tracking outbox 异步批量写入;outbox 不可写或因保护阈值拒绝时回退同步直写 SpacetimeDB。不新增 SpacetimeDB 表,不让 reducer 做外部 I/O。
|
||||
- 影响范围:`server-rs/crates/api-server/src/external_api_audit.rs`、`server-rs/crates/api-server/src/openai_image_generation.rs`、`server-rs/crates/api-server/src/telemetry.rs`、tracking outbox、后端架构文档和开发运维文档。
|
||||
- 验证方式:执行 `cargo test -p api-server external_api_audit --manifest-path server-rs/Cargo.toml -- --nocapture`、`cargo test -p api-server openai_image_generation --manifest-path server-rs/Cargo.toml -- --nocapture`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`。
|
||||
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 2026-05-25 VectorEngine 图片 provider 收到 platform-image
|
||||
|
||||
- 背景:`api-server` 里原本同时混着 VectorEngine 创建 / 编辑协议、响应解析、远端图片下载、失败日志和审计落库逻辑,Puzzle / Match3D 还各自藏着一份近似实现,导致“provider 协议”和“业务编排”边界不清。
|
||||
- 决策:把 VectorEngine `gpt-image-2` 图片 provider 协议、URL / base64 响应解析、远端图片下载和 provider 侧结构化日志统一收口到 `server-rs/crates/platform-image/src/vector_engine/`,并按 `client.rs`、`transport.rs`、`request.rs`、`payload.rs`、`response.rs`、`image_source.rs` 等小模块拆分,避免把大文件从 `api-server` 平移到平台 crate。`api-server` 只保留配置校验、玩法 prompt 编排、OSS / asset object / binding 持久化、计费和外部 API 失败审计桥接;旧 `openai_image_generation.rs` 只作为兼容转接层,不再承担 provider 实现。
|
||||
- 影响范围:`server-rs/crates/platform-image`、`server-rs/crates/api-server/src/openai_image_generation.rs`、`server-rs/crates/api-server/src/puzzle/vector_engine.rs`、`server-rs/crates/api-server/src/external_api_audit.rs`、后端架构与运维文档。
|
||||
- 验证方式:`cargo test -p platform-image --manifest-path server-rs/Cargo.toml`、`cargo test -p platform-image --test vector_engine --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server openai_image_generation --manifest-path server-rs/Cargo.toml -- --nocapture`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`。
|
||||
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 2026-05-26 音频 provider 协议收口到 platform-audio,Hyper3D 继续保持薄代理
|
||||
|
||||
- 背景:`api-server/src/vector_engine_audio_generation.rs` 和 `api-server/src/hyper3d_generation.rs` 仍然承担太多 provider 细节,容易把外部协议、下载、解析和 BFF 编排混在一起。
|
||||
- 决策:VectorEngine Suno/Vidu 音频协议、任务提交/轮询、下载和 OSS 持久化请求准备收口到 `platform-audio`,并继续按 `client.rs`、`request.rs`、`response.rs`、`download.rs`、`persist.rs`、`error.rs` 拆小模块;`api-server` 只保留路由、配置、计费、asset_object confirm、entity binding 和错误映射。Hyper3D 维持后端安全代理和旧数据兼容,`platform-hyper3d` 承接 Rodin 的协议与解析,`api-server` 仅做薄 wrapper。
|
||||
- 影响范围:`server-rs/crates/platform-audio/`、`server-rs/crates/platform-hyper3d/`、`server-rs/crates/api-server/src/vector_engine_audio_generation.rs`、`server-rs/crates/api-server/src/hyper3d_generation.rs`、相关后端架构文档。
|
||||
- 验证方式:`cargo test -p platform-audio --manifest-path server-rs/Cargo.toml`、`cargo test -p platform-hyper3d --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml` 通过;`api-server` 不再包含音频 provider 协议和 Hyper3D parser 主实现。
|
||||
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【后端架构】复杂媒体资产链路Adapter扩展计划-2026-05-14.md`。
|
||||
|
||||
## 2026-05-21 拼图参考图主链改为 OSS assetObjectId 与只读签名 URL
|
||||
|
||||
- 背景:release 上拼图图生图生成草稿时,旧链路把上传图转成 Data URL/base64 放进创作 action JSON body,容易先触发 Nginx `413 Request Entity Too Large`,也让外部模型调用前的 HTTP body 过大。
|
||||
- 决策:浏览器参考图先通过资产直传票据上传 OSS,并确认 `asset_object`;拼图 action 主链只提交 `referenceImageAssetObjectId(s)`。`api-server` 按当前登录用户校验 asset owner、bucket、kind、图片 MIME 和大小后签发 OSS 只读 URL,传给 VectorEngine 的 generation fallback 使用;需要 edits multipart 时由后端用该签名 URL 拉取字节,不再让前端把图片塞进 JSON body。
|
||||
- 兼容边界:旧 `referenceImageSrc(s)` Data URL 与历史 `/generated-*` 路径仅保留给旧草稿、旧入口和迁移期请求;调大 Nginx `client_max_body_size` 只作为兼容兜底,不是长期创作主链。
|
||||
- 影响范围:拼图创作前端、`packages/shared` / `shared-contracts` action DTO、`api-server` 拼图 VectorEngine 编排、资产确认和 `spacetime-client` 资产读取 facade。
|
||||
- 验证方式:前端 payload 中 AI 重绘优先出现 `referenceImageAssetObjectId(s)` 且 `referenceImageSrc(s)` 不再携带 Data URL;后端 `puzzle_vector_engine_generation_prefers_signed_reference_url`、`puzzle_reference_image_sources_prefer_asset_object_ids`、`puzzle_asset_object_reference_requires_matching_owner` 通过。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 2026-05-21 Nginx 通用 API 入口放行创作参考图请求体
|
||||
|
||||
- 背景:release 上拼图结果页重绘动作携带参考图 Data URL 时,Nginx access log 出现 `413`、`request_time=0.000`、`upstream_status=-`,说明请求被反代层默认 1 MiB 上限拦截,未进入 `api-server`。
|
||||
- 决策:发布、开发服和容器 Nginx 模板的通用 `location ~ ^/api(?:/|$)` 统一设置 `client_max_body_size 64m`。该值只作为反代放行和旧 Data URL 请求兼容兜底,具体业务请求体和图片字节上限继续由 `api-server` 路由 `DefaultBodyLimit`、OSS asset 确认和业务校验控制,不能替代接口级限制;拼图参考图长期主链见同日 `OSS assetObjectId` 决策。
|
||||
- 影响范围:`deploy/nginx/genarrative.conf`、`deploy/nginx/genarrative-dev-http.conf`、`deploy/container/nginx.conf`、Nginx README、生产运维文档和 release 排障口径。
|
||||
- 验证方式:目标机 `nginx -T 2>/dev/null | grep client_max_body_size` 应看到 `client_max_body_size 64m;`;大于 1 MiB 的参考图请求不再在 Nginx 层直接 413,access log 应出现有效 `upstream_status`。
|
||||
- 关联文档:`deploy/nginx/README.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 2026-05-24 跳一跳推荐页允许未登录直达运行态并记录匿名游玩埋点
|
||||
|
||||
- 背景:推荐页的跳一跳作品在未登录时曾被前端登录门禁拦住,导致公开推荐流无法直接游玩;同时游玩埋点如果只接受登录态 userId,会让匿名启动和匿名重开被静默丢失。
|
||||
- 决策:跳一跳推荐页的运行态启动、跳跃和重开路由统一使用可选鉴权;未登录时仍允许进入运行态,并把 `work_play_start` 以匿名语义记录下来,而不是伪造用户身份或直接跳过埋点。
|
||||
- 影响范围:`api-server` 跳一跳 runtime 路由、`work_play_tracking`、推荐页进入运行态逻辑、匿名推荐试玩测试、平台入口 / 玩法链路文档。
|
||||
- 验证:登录态和未登录态都能从推荐页进入运行态;`work_play_start` 事件在匿名时仍产生,metadata 带匿名标记。
|
||||
- 关联:`server-rs/crates/api-server/src/jump_hop.rs`、`server-rs/crates/api-server/src/auth.rs`、`server-rs/crates/api-server/src/work_play_tracking.rs`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。
|
||||
|
||||
## 2026-05-22 抓大鹅素材生成改为关卡整图派生三图
|
||||
|
||||
- 背景:旧抓大鹅素材链路按物品 5x5 sheet、纯背景和独立容器图分开生产,难以保证背景、UI、容器和物品风格一致,也让结果页继续暴露背景 / 容器重生成入口。
|
||||
- 决策:抓大鹅草稿生成先用 `gpt-image-2` 无参考图生成竖屏 `9:16` 完整关卡画面;关卡画面完成后,以它作为参考并发生成三张可运行资产:`1K 1:1` UI spritesheet、`1K 9:16` 关卡背景图、`2K 1:1` 物品 spritesheet。UI 与物品 spritesheet 都固定要求纯绿色绿幕背景,后端上传 OSS 前扣成真实透明 PNG。物品 spritesheet 固定 `10*10`,每行两种物品、每种五个形态。运行态和编辑器都按 alpha 连通域矩形检测解析 UI 和物品图集,不按固定像素坐标切图。
|
||||
- 兼容:新增字段继续存入现有 `generatedItemAssets[].backgroundAsset` / `generatedBackgroundAsset` JSON,不新增 SpacetimeDB schema 字段。历史 `containerImage*` 字段只作兼容;如果它与 `uiSpritesheetImage*` 同源,不得再作为运行态中心容器图。
|
||||
- 影响范围:`server-rs/crates/api-server/src/match3d/*`、`server-rs/crates/shared-contracts/src/match3d_*`、`packages/shared/src/contracts/match3dWorks.ts`、`src/components/match3d-result/Match3DResultView.tsx`、`src/components/match3d-runtime/Match3DRuntimeShell.tsx`、`src/services/match3dSpritesheetParser.ts`。
|
||||
- 验证方式:执行 `cargo test -p api-server match3d --manifest-path server-rs\Cargo.toml`、`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx src/components/match3d-runtime/Match3DRuntimeShell.test.tsx src/services/match3dSpritesheetParser.test.ts src/services/match3dGeneratedModelCache.test.ts`、`npm run typecheck`、`npm run check:encoding`。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
|
||||
## 2026-05-18 Rust 手写模块入口统一不用 mod.rs
|
||||
|
||||
@@ -39,8 +283,9 @@
|
||||
|
||||
- 背景:`server-rs/crates/api-server/src/puzzle.rs` 已膨胀为数千行大文件,混合 Axum handler、草稿编译、图片生成、VectorEngine / OSS 持久化、DTO mapper、标签生成和测试;继续在单文件内迭代会降低定位和评审效率。
|
||||
- 决策:原超大 `puzzle.rs` 改为同名入口 `server-rs/crates/api-server/src/puzzle.rs` 加 `server-rs/crates/api-server/src/puzzle/` 子模块目录。`puzzle.rs` 只保留聚合入口和 handler re-export;`handlers.rs` 放 HTTP handler;`draft.rs` 放表单草稿 / 编译 / snapshot helper;`generation.rs` 放图片与 UI 背景生成编排;`vector_engine.rs` 放 VectorEngine、下载、OSS、asset object / binding 和错误归一;`mappers.rs` / `tags.rs` 保留映射和标签 / 错误 helper;`tests.rs` 承接原 puzzle 单测。
|
||||
- 2026-05-21 追加决策:拼图 HTTP/BFF handler 不再直接提取完整 `AppState`,统一通过 `PuzzleApiState` 暴露拼图能力需要的 SpacetimeDB facade、gallery cache、OSS、作者查询、LLM 和少量配置快照。`modules/puzzle.rs` 仍接收全局 `AppState` 以挂接鉴权和回到全局路由树,但内部路由先 `.with_state(PuzzleApiState::from_ref(&state))`,handler 使用 `State<PuzzleApiState>`。确需复用计费、外部失败审计等仍要求 `AppState` 的横切 helper 时,先经 `PuzzleApiState::root_state()` 显式过渡,后续再继续收窄。
|
||||
- 边界:本次只改变 `api-server` 内部文件组织,不改变 `/api/runtime/puzzle/*` 路由、DTO、error envelope、SpacetimeDB schema、公开 gallery cache 语义或计费语义。领域规则后续仍应逐步沉到 `module-puzzle`,SpacetimeDB 表、reducer、procedure 和 row shape 仍留在 `spacetime-module`。
|
||||
- 影响范围:`server-rs/crates/api-server/src/puzzle/`、`server-rs/crates/api-server/src/modules/puzzle.rs` 的 handler 引用、后端架构文档。
|
||||
- 影响范围:`server-rs/crates/api-server/src/state.rs`、`server-rs/crates/api-server/src/puzzle/`、`server-rs/crates/api-server/src/modules/puzzle.rs` 的 handler 引用、后端架构文档。
|
||||
- 验证方式:执行 `cargo check -p api-server --manifest-path server-rs\Cargo.toml`;后续若改动 puzzle API 行为,再按对应路由补充定向测试和 `npm run dev:api-server` `/healthz` smoke。
|
||||
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||
|
||||
@@ -52,6 +297,7 @@
|
||||
- 影响范围:`jenkins/Jenkinsfile.production-stdb-module-build` 及后续所有同类 Windows 构建流水线。
|
||||
- 验证方式:Jenkins 日志中应能看到 `[jenkins-powershell] user:` 和 `[jenkins-powershell] exe:`,Checkout 阶段会打印当前 `HEAD` 与请求 commit,并在 `COMMIT_HASH` 为空或一致时直接继续;不再停在 `PipelineNodeTreeScanner... Cannot run program "powershell"` 或重复 `git clean` 的退出码 5。
|
||||
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`.hermes/shared-memory/pitfalls.md`。
|
||||
|
||||
## 2026-05-19 tracking outbox 改为 rotate 后异步 flush
|
||||
|
||||
- 背景:普通 route tracking 写入压力上来后,不能让 HTTP 请求线程等待 SpacetimeDB 批量入库。
|
||||
@@ -105,6 +351,70 @@
|
||||
- 验证方式:容器连续 10 轮不重启 SpacetimeDB 压测,`PEAK_RPS=2500` 等价约 5000 HTTP req/s,平均实际吞吐约 `4219 HTTP req/s`,总计 `0` 个 5xx,200 请求平均 `p95=123ms`、`p99=234ms`;同时观察 SpacetimeDB 内存高水位,后续优化先处理连接 / 订阅 / tracking 下游状态。
|
||||
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`deploy/container/README.md`。
|
||||
|
||||
## 2026-05-19 新增玩法创作工具平台 SOP 冻结
|
||||
|
||||
- 背景:新增玩法的创作工具如果默认复制既有玩法的聊天式 Agent、轻输入 Agent 或专属素材模型,平台会不断复制出不可控分支,后续接入、测试和恢复语义都会漂移。
|
||||
- 决策:新增玩法创作工具统一收敛为平台级 SOP:默认使用表单/图片输入创作工作台;单图资产统一通过 `CreativeImageInputPanel`;系列素材统一走批量规划、sheet 生图、后端切图、透明化、OSS 持久化和局部重生成流水线;不把任一玩法专属素材模型当平台通用模型。
|
||||
- 影响范围:`CONTEXT.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`.codex/skills/genarrative-play-type-integration/SKILL.md`、`.hermes/skills/genarrative-play-type-integration/SKILL.md`、后续新增玩法 PRD 和工程实现。
|
||||
- 验证方式:新增玩法 PRD 必须显式声明单图资产槽位和系列素材槽位;新增工作台测试确认没有默认聊天式 Agent 输入;skill 通过 `quick_validate.py`。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`.codex/skills/genarrative-play-type-integration/SKILL.md`、`.hermes/skills/genarrative-play-type-integration/SKILL.md`。
|
||||
|
||||
## 2026-05-20 敲木鱼玩法按完整平台纵切接入
|
||||
|
||||
- 背景:敲木鱼玩法需要对齐拼图 / 跳一跳的创作闭环,不能做成孤立 demo 或前端本地计数工具。
|
||||
- 决策:新增 `wooden-fish` 玩法,采用表单 / 图片输入工作台、单图敲击物资产槽位、敲击音效资产槽位和最多 8 条飘字配置;公开作品号前缀为 `WF-*`;运行态只在单次 run 内累计总敲击次数和词条计数。后端新增独立 `module-wooden-fish`、shared contracts、SpacetimeDB `wooden_fish_*` 表 / public views、`spacetime-client` facade 和 `/api/creation/wooden-fish/*`、`/api/runtime/wooden-fish/*` 路由,前端接入平台入口、生成页、结果页、运行态、公开详情和推荐试玩。
|
||||
- 影响范围:`CONTEXT.md`、`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`packages/shared/src/contracts/woodenFish.ts`、`server-rs/crates/shared-contracts/src/wooden_fish.rs`、`server-rs/crates/module-wooden-fish/`、`server-rs/crates/spacetime-module/src/wooden_fish*`、`server-rs/crates/spacetime-client/src/wooden_fish.rs`、`src/components/wooden-fish-*`。
|
||||
- 验证方式:执行敲木鱼契约 / module / facade / runtime model / platform entry 定向测试、`npm run typecheck`、`npm run check:encoding`、`npm run check:spacetime-schema`、`cargo check -p api-server --manifest-path server-rs\Cargo.toml`,本地 smoke 使用 mock 短信配置后检查 `/healthz`。
|
||||
- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||
|
||||
## 2026-05-21 敲木鱼敲击音效当前只接受上传、录音或默认音
|
||||
|
||||
- 背景:敲木鱼按关键词生成的敲击音效约束不够稳定;当前创作阶段需要先关闭提示词生成音效,避免生成结果不符合敲击体验。
|
||||
- 决策:通用 `/api/creation/audio/sound-effect` 对木鱼 `hit_sound` 目标也返回 `410 Gone`。木鱼工作台只支持上传或麦克风录制音频;若用户未提供音频,`api-server` 写回内置默认木鱼音 `/wooden-fish/default-hit-sound.mp3`。`hitSoundPrompt` 只作为历史兼容字段保留,当前创作流程不使用;`spacetime-client` 不得合成 `/generated-wooden-fish-assets/...` 假路径。
|
||||
- 影响范围:`server-rs/crates/api-server/src/vector_engine_audio_generation.rs`、`server-rs/crates/api-server/src/wooden_fish.rs`、`server-rs/crates/spacetime-client/src/wooden_fish.rs`、`shared-contracts` / `packages/shared` 的 `creationAudio` 契约、敲木鱼 PRD 与平台链路文档。
|
||||
- 验证方式:执行 `cargo test -p shared-contracts creation_audio --manifest-path server-rs\Cargo.toml`、`cargo test -p spacetime-client wooden_fish --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server wooden_fish --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server disabled_creation_audio_targets_return_gone_including_wooden_fish_sound_effects --manifest-path server-rs\Cargo.toml`、`npm run typecheck`、`npm run check:encoding`,本地 smoke 检查 `/healthz`。
|
||||
- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||
|
||||
## 2026-05-21 敲木鱼默认敲击物使用内置透明 PNG
|
||||
|
||||
- 背景:默认敲木鱼图案若继续用“木鱼”关键词临时生成,image2 容易语义化重画并改变用户认可的原始造型。
|
||||
- 决策:默认模板使用内置资源 `/wooden-fish/default-hit-object.png` 写回 `bundled-default` 敲击物资产;仅当用户输入自定义关键词、上传参考图或主动重生成敲击物时,才走 image2 -> OSS -> asset object -> entity binding 链路。创作入口卡片、结果页、运行态和公开列表兜底统一使用该 PNG。
|
||||
- 影响范围:敲木鱼工作台默认提示词、api-server 木鱼默认资产编排、创作入口种子与迁移、平台公开卡片兜底、PRD 与平台链路文档。
|
||||
- 验证方式:默认 `compile-draft` 返回的 `hitObjectAsset.generationProvider` 应为 `bundled-default` 且 `imageSrc=/wooden-fish/default-hit-object.png`;自定义关键词或参考图仍走 image2;前端静态资源可通过 Vite 直接访问。
|
||||
- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-23 敲木鱼创作请求需要独立长超时
|
||||
|
||||
- 背景:敲木鱼 `createSession` 和 `executeAction` 都会串行等待多段 image2 生成、去绿背景处理和 OSS 落库;共享创作工厂默认 15 秒对这条链路太短,容易让前端先报 `请求超时:15000ms`。
|
||||
- 决策:敲木鱼 client 单独配置长等待窗口,同时覆盖会话创建和执行动作请求,不修改共享工厂默认值,避免影响其它轻量创作玩法。
|
||||
- 影响范围:`src/services/wooden-fish/woodenFishClient.ts`、`src/services/creation-agent/creationAgentClientFactory.ts`、敲木鱼工作台与生成页请求行为。
|
||||
- 验证方式:`npm test -- src/services/wooden-fish/woodenFishClient.test.ts`,并在本地敲木鱼创作时不再提前触发 15 秒超时。
|
||||
- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-21 RPG publish_world 设定文本以后端草稿真相派生
|
||||
|
||||
- 背景:RPG 结果页发布动作只保证提交 `{ action: 'publish_world' }`;旧 agent 会话可能没有 `seed_text`,但 `draft_profile_json` 已经通过 `publish_gate` 并可发布。
|
||||
- 决策:发布正式世界时,`spacetime-module` 不再把 `session.seed_text` 当作唯一 `setting_text` 兜底,而是调用 `module-custom-world::resolve_custom_world_publish_setting_text(...)` 从 payload、当前草稿 profile 和 seed 依次派生。
|
||||
- 影响范围:RPG / custom-world agent 发布链路、`custom_world_profile` 编译入库、公开 gallery 投影。
|
||||
- 验证方式:`cargo test -p module-custom-world publish_setting_text --manifest-path server-rs\Cargo.toml`;`cargo check -p spacetime-module --manifest-path server-rs\Cargo.toml`;本地 api-server 重启后检查 `/healthz`。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`.hermes/shared-memory/pitfalls.md`。
|
||||
|
||||
## 2026-05-19 系列素材 n*n 图集抽为 api-server 通用模块
|
||||
|
||||
- 背景:抓大鹅物品 sheet 已包含 prompt 组装、固定网格切图、绿幕 / 近白底透明化、切片 PNG 持久化和 prompt 追踪;继续留在 Match3D 私有模块会让跳一跳、后续地块 / 道具类玩法重复复制同一套算法和 OSS 元数据口径。
|
||||
- 决策:`server-rs/crates/api-server/src/generated_asset_sheets.rs` 作为通用系列素材图集模块,`n` 作为必选 `grid_size` 参数;物品名称 prompt 模板与特殊设定 prompt 作为可选输入;模块负责 sheet prompt、`n*n` 切片、透明化、PNG 输出、OSS private upload 请求构造,以及 sheet / item / special prompt 的 base64 元数据持久化。玩法只负责生图 provider、计费、slot 规划、失败回写和把通用切片结果映射回自身 DTO / 草稿 / runtime 字段。
|
||||
- 影响范围:`api-server` 系列素材生成、Match3D 物品五视角素材、后续新增玩法的地块 / 物品 / 障碍 / 装饰图集生成。
|
||||
- 验证方式:`cargo test -p api-server generated_asset_sheets --manifest-path server-rs\Cargo.toml -- --nocapture` 覆盖通用 prompt、切片、`n` 校验和 prompt 元数据;玩法侧执行对应素材流水线定向测试。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||
|
||||
## 2026-05-19 跳一跳玩法采用正式 scoring DTO 与 public view 投影
|
||||
|
||||
- 背景:跳一跳玩法新增后,前端、shared-contracts、SpacetimeDB 生成绑定和后端 mapper 对 scoring 字段口径不一致,schema guard 也要求 table / view 目录与 `migration.rs` 同步。
|
||||
- 决策:跳一跳的 `JumpHopScoring` 统一采用 `chargeToDistanceRatio/maxChargeMs/hitBonus/perfectBonus`,公开广场优先使用 `jump_hop_gallery_card_view`,详情兼容投影保留 `jump_hop_gallery_view`。`spacetime-module` 新增的 `jump_hop_*` table 必须同步进入 `migration.rs` 和后端架构文档。
|
||||
- 影响范围:`packages/shared/src/contracts/jumpHop.ts`、`server-rs/crates/shared-contracts/src/jump_hop.rs`、`server-rs/crates/spacetime-client/src/mapper/jump_hop.rs`、`server-rs/crates/spacetime-module/src/migration.rs`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||
- 验证方式:`cargo check -p shared-contracts --manifest-path server-rs/Cargo.toml`、`cargo check -p spacetime-client --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:spacetime-schema`。
|
||||
- 关联文档:`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||
|
||||
## 2026-05-16 公开作品列表短期由 BFF 订阅读模型缓存
|
||||
|
||||
- 背景:作品列表压测和实时性讨论中,曾考虑让浏览器前端直接订阅公开作品列表,减少 HTTP 拉取和 BFF 压力。
|
||||
@@ -138,10 +448,10 @@
|
||||
- 验证方式:创作 Tab 中点击汪汪声浪后直接看到内嵌表单,不应再出现单独配置页;发布进入 runtime 后退出应回到创作页的汪汪声浪模板。
|
||||
- 关联文档:`docs/technical/NEW_WORK_ENTRY_CONFIG_2026-05-01.md`。
|
||||
|
||||
## 2026-05-14 拼图与抓大鹅生成页移动端收口为等待与计时双栏
|
||||
## 2026-05-14 拼图与抓大鹅生成页移动端收口为等待与计时双栏(历史)
|
||||
|
||||
- 背景:拼图与抓大鹅的草稿生成页在移动端同时展示“当前批次”“预计等待”“计时”时,模型执行视角过重,信息也显得散。
|
||||
- 决策:这两类轻量玩法的生成页隐藏“当前批次”模块,只保留“预计等待”和“计时”并排展示;生成步骤进入页面时按顺序从左侧滑入,强化推进感。
|
||||
- 决策:这两类轻量玩法的生成页隐藏“当前批次”模块,只保留“预计等待”和“计时”并排展示;生成步骤进入页面时按顺序从左侧滑入,强化推进感。2026-05-23 起已被“所有玩法生成页统一圆环主视觉”取代,步骤列表不再作为当前口径。
|
||||
- 影响范围:`CustomWorldGenerationView`、拼图与抓大鹅创作入口调用处、移动端生成页体验文档。
|
||||
- 验证方式:拼图与抓大鹅生成页在手机竖屏下只显示等待与计时双栏,步骤卡按顺序滑入;其它未传入隐藏参数的生成页继续保留原批次模块。
|
||||
- 关联文档:`docs/experience/MOBILE_UI_DEV_EXPERIENCE.md`。
|
||||
@@ -172,6 +482,7 @@
|
||||
|
||||
## 2026-05-14 抓大鹅物品素材 sheet 改用 VectorEngine Gemini
|
||||
|
||||
- 状态:历史决策,已被 `2026-05-22 抓大鹅素材生成改为关卡整图派生三图` 取代;当前物品 spritesheet 走 `gpt-image-2` 参考关卡整图编辑生成 `2K 1:1`、`10*10` 绿幕图,上传 OSS 前扣成透明 PNG。
|
||||
- 背景:抓大鹅 2D 五视角物品素材仍沿用 5x5 sheet、绿幕去背、切图、OSS 转存和 `generatedItemAssets` 持久化,但用户要求物品素材图片生成步骤改用 VectorEngine Apifox `api-381740608` 对应的 Gemini 原生图片接口。
|
||||
- 决策:抓大鹅物品素材 sheet 生图固定走 VectorEngine `POST {VECTOR_ENGINE_BASE_URL}/v1beta/models/gemini-3-pro-image-preview:generateContent?key={VECTOR_ENGINE_API_KEY}`,请求体使用 `contents[].parts[].text` 与 `generationConfig.responseModalities = ["TEXT", "IMAGE"]`、`imageConfig.aspectRatio = "1:1"`;响应从 `candidates[].content.parts[].inlineData.data` / `inline_data.data` 读取 base64 图片。封面、9:16 纯背景图、1:1 容器 UI 图、切图、OSS、扣费和运行态消费链路保持不变;音频以后续“拼图与抓大鹅音频生成入口临时关闭”决策为准。
|
||||
- 影响范围:`server-rs/crates/api-server/src/match3d.rs`、`server-rs/crates/api-server/src/config.rs`、`deploy/env/api-server.env.example`、抓大鹅素材生成技术文档。
|
||||
@@ -223,7 +534,7 @@
|
||||
## 2026-05-12 抓大鹅入口素材风格改为 2D 常见素材风格
|
||||
|
||||
- 背景:抓大鹅草稿素材生成已经收敛为多视角 2D 图片素材,但入口页和旧参考图仍沿用黏土、低多边形、塑料、木雕、体素、金属等偏 3D 素材语言,容易让后续生成链路和用户预期继续漂移。
|
||||
- 决策:抓大鹅创作入口 `2D素材风格` 固定为 `扁平图标 / 赛璐璐卡通 / 像素复古 / 手绘水彩 / 贴纸描边 / 厚涂图标 / 自定义`;默认风格为 `flat-icon`。入口参考图统一由 `npm run assets:match3d-style-references -- --live` 调用 VectorEngine `gpt-image-2-all` 生成,输出到 `public/match3d-style-references/`。旧 3D 风格参考图不再保留为入口资产。
|
||||
- 决策:抓大鹅创作入口 `2D素材风格` 固定为 `扁平图标 / 赛璐璐卡通 / 像素复古 / 手绘水彩 / 贴纸描边 / 厚涂图标 / 自定义`;默认风格为 `flat-icon`。入口参考图统一由 `npm run assets:match3d-style-references -- --live` 调用 VectorEngine `gpt-image-2` 生成,输出到 `public/match3d-style-references/`。旧 3D 风格参考图不再保留为入口资产。
|
||||
- 影响范围:`Match3DAgentWorkspace`、抓大鹅入口交互测试、Match3D PRD、素材生成流水线技术文档、F1 入口文档和 `public/match3d-style-references/` 静态资产。
|
||||
- 验证方式:执行 `npm run test -- src\components\match3d-creation\Match3DAgentWorkspace.interaction.test.tsx`、`cargo test -p shared-contracts match3d --manifest-path server-rs\Cargo.toml`、`npm run typecheck`、`npm run check:encoding`,并人工抽查 `.tmp/match3d-style-preview.png`。
|
||||
- 关联文档:`docs/prd/AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`、`docs/technical/MATCH3D_F1_CREATION_ENTRY_AND_AGENT_UI_2026-04-30.md`。
|
||||
@@ -239,8 +550,9 @@
|
||||
## 2026-05-12 拼图 UI 背景图复用 levels_json 持久化
|
||||
|
||||
- 背景:拼图草稿结果页需要像抓大鹅一样支持 UI 背景生成,但首版只需要作品级/首关背景,不应为图片生成结果新增 SpacetimeDB 表结构。
|
||||
- 决策:拼图 UI 背景字段存入首关 `levels_json`,字段为 `uiBackgroundPrompt`、`uiBackgroundImageSrc`、`uiBackgroundImageObjectKey`;`compile_puzzle_draft` 草稿编译阶段自动生成首关 UI 背景,自动草稿阶段必须拿到 `uiBackgroundImageSrc` 或 `uiBackgroundImageObjectKey` 才能返回成功;结果页新增 `UI` Tab,可编辑提示词并触发 `generate_puzzle_ui_background`,手动生成失败只展示在当前面板。`api-server` 读取 `public/ui-previews/puzzle-image-compact-ui-2026-05-08.png` 作为非拼图 UI 参考图,调用 VectorEngine `gpt-image-2-all` 生成 9:16 背景并要求中央正方形拼图区与外部 UI 背景边界清晰。SpacetimeDB 只保存结果,不做外部 I/O。
|
||||
- 决策:拼图 UI 背景字段存入首关 `levels_json`,字段为 `uiBackgroundPrompt`、`uiBackgroundImageSrc`、`uiBackgroundImageObjectKey`;`compile_puzzle_draft` 草稿编译阶段自动生成首关 UI 背景,自动草稿阶段必须拿到 `uiBackgroundImageSrc` 或 `uiBackgroundImageObjectKey` 才能返回成功;结果页新增 `UI` Tab,可编辑提示词并触发 `generate_puzzle_ui_background`,手动生成失败只展示在当前面板。`api-server` 读取 `public/ui-previews/puzzle-image-compact-ui-2026-05-08.png` 作为非拼图 UI 参考图,调用 VectorEngine `gpt-image-2` 生成 9:16 背景并要求中央正方形拼图区与外部 UI 背景边界清晰。SpacetimeDB 只保存结果,不做外部 I/O。
|
||||
- 2026-05-18 追加:为缩短首版草稿等待,`compile_puzzle_draft` 在首关命名和 `uiBackgroundPrompt` 稳定后并行启动首关关卡图生成与 UI 背景生成;上传主图且关闭 AI 重绘时,并行执行上传图持久化与 UI 背景生成。生成页预计完成时间按 5 分钟展示。
|
||||
- 2026-05-21 追加:拼图结果页独立“素材配置”Tab 已移除,UI spritesheet 与关卡纯背景收口到每关图片生成资产包。每次 `gpt-image-2` 预计 90 秒;2026-05-24 起草稿首图生成单独按 4 分钟展示,草稿完整 AI 重绘路径约 448 秒,上传图且关闭 AI 重绘路径跳过首图生成约 208 秒。结果页关卡详情继续复用 `CreativeImageInputPanel`,本次上传/历史选择图优先成为主图卡片,正式图只作为无新参考图时的预览;仅有正式图时仍允许在画面描述框上传多张参考图。
|
||||
- 影响范围:拼图结果页、拼图运行态背景渲染、拼图 agent action、`module-puzzle` / `spacetime-module` / `spacetime-client` 的拼图关卡 JSON 映射、拼图流程技术文档。
|
||||
- 验证方式:执行 `npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx`、`cargo test -p api-server puzzle_ui_background --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run typecheck`、`npm run check:encoding`。
|
||||
- 关联文档:`docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`。
|
||||
@@ -248,7 +560,7 @@
|
||||
## 2026-05-12 抓大鹅结果页素材编辑统一走作品级资产面板
|
||||
|
||||
- 背景:抓大鹅结果页需要支持封面图上传 / AI 重绘、物品素材独立预览、单项删除和批量新增,且不能把素材编辑继续做成列表内联展开或前端临时状态。
|
||||
- 决策:结果页 `作品信息` 的封面图点击打开独立面板,封面图面板对齐拼图入口上传卡。已有上传主图时,请求体传 `uploadedImageSrc`,AI 重绘走 VectorEngine `/v1/images/edits`;关闭 AI 重绘时只写回上传图,不调用生图。没有上传主图时,请求体传 `referenceImageSrcs`,可混合本地上传、物品素材和 UI 素材,多参考图作为 `gpt-image-2-all` generations 的 `image` 数组传入。生成结果统一调用 `POST /api/creation/match3d/works/{profileId}/cover-image` 并转存到 `generated-match3d-assets`。`素材配置 > 物品` 列表项点击打开独立预览面板,不再提供单项重新生成按钮;单项删除和批量新增都写回同一份 `generated_item_assets_json`。批量新增调用 `POST /api/creation/match3d/works/{profileId}/item-assets`,复用草稿生成的 2D 素材图、5x5 切图、OSS 上传和可选点击音效链路,仅作用于新增物品,不新增 SpacetimeDB 表。
|
||||
- 决策:结果页 `作品信息` 的封面图点击打开独立面板,封面图面板对齐拼图入口上传卡。已有上传主图时,请求体传 `uploadedImageSrc`,AI 重绘走 VectorEngine `/v1/images/edits`,后端把上传图作为 multipart `image` part 传入 `gpt-image-2`;关闭 AI 重绘时只写回上传图,不调用生图。没有上传主图但存在 `referenceImageSrcs` 时,多参考图同样走 edits 的多个 `image` part;完全无参考图时走 `/v1/images/generations`。生成结果统一调用 `POST /api/creation/match3d/works/{profileId}/cover-image` 并转存到 `generated-match3d-assets`。`素材配置 > 物品` 列表项点击打开独立预览面板,不再提供单项重新生成按钮;单项删除和批量新增都写回同一份 `generated_item_assets_json`。批量新增调用 `POST /api/creation/match3d/works/{profileId}/item-assets`;该接口的物品 spritesheet 生成口径已被 2026-05-22 决策更新为关卡整图参考、`10*10` 绿幕图和上传前透明化。
|
||||
- 影响范围:Match3D 结果页、Match3D works shared contracts、`api-server` Match3D 作品路由、生成资产历史类型和草稿恢复路径。
|
||||
- 验证方式:执行 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`、`npm run typecheck`、`cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`。
|
||||
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
||||
@@ -260,6 +572,7 @@
|
||||
- 影响范围:平台个人页、登录弹窗、法律 Markdown 渲染和前端认证交互测试。
|
||||
- 验证方式:执行 `npm run test -- src/components/auth/AuthGate.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、触碰文件 ESLint、`npm run check:encoding`。
|
||||
- 关联文档:`docs/prd/PROFILE_LEGAL_INFO_AND_AUTH_AGREEMENT_PRD_2026-05-12.md`。
|
||||
|
||||
## 2026-05-12 微信小程序待绑定手机号优先走原生手机号授权
|
||||
|
||||
- 背景:微信小程序 `web-view` 壳登录后若返回 `pending_bind_phone`,H5 仍会展示手输手机号和短信验证码绑定页,体验上多了一步。
|
||||
@@ -268,10 +581,17 @@
|
||||
- 验证方式:执行 `npm run check:encoding`、`node scripts/check-wechat-miniprogram-auth-smoke.mjs`、`cargo test -p shared-contracts wechat_bind_phone_request_accepts_mini_program_phone_code --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server wechat_miniprogram_bind_phone_code_activates_pending_user --manifest-path server-rs/Cargo.toml -- --nocapture`。
|
||||
- 关联文档:`docs/technical/WECHAT_MINIPROGRAM_WEB_VIEW_SHELL_2026-05-03.md`。
|
||||
|
||||
## 2026-05-26 微信小程序进入即开 H5,登录按需走原生手机号授权
|
||||
|
||||
- 背景:当前产品要求微信小程序进入后不再立刻取手机号,而是默认直接进入 `web-view`,登录状态与 Web 端统一;只有 H5 触发受保护操作时才走微信手机号授权。
|
||||
- 决策:小程序壳首次进入只打开 H5,不再把登录态当作启动前置条件;H5 侧在小程序运行态触发登录时,不展示普通登录弹窗,而是跳转到小程序原生手机号授权流程,授权结果再回灌到 H5。未触发登录时保持游客态,与 Web 端一致。
|
||||
- 影响范围:`miniprogram/pages/web-view/index.*`、`src/components/auth/AuthGate.tsx`、`src/components/auth/LoginScreen.tsx`、`src/services/authService.ts`、相关测试与说明文档。
|
||||
- 验证方式:执行 `npm run check:encoding`、`npm run typecheck`、`npx vitest run src/components/auth/AuthGate.test.tsx src/services/authService.test.ts scripts/miniprogram-web-view-auth.test.ts`。
|
||||
|
||||
## 2026-05-13 宝贝爱画先作为寓教于乐独立本地 Demo 落地
|
||||
|
||||
- 背景:第三关 `宝贝爱画` 需要默认出现在“发现 / 寓教于乐”板块下方,但本阶段只验证画板、手部绘制、绘画魔法和本地保存闭环,不进入创作模板、公开作品或正式持久化。
|
||||
- 决策:`baby-love-drawing / 宝贝爱画` 先作为独立运行态接入,入口由发现页寓教于乐默认卡片打开,并支持 `/runtime/baby-love-drawing` 直达;关闭 `VITE_ENABLE_EDUTAINMENT_ENTRY` 时前端不展示频道/卡片且直达路由回落主应用。绘画魔法统一走 `POST /api/creation/edutainment/baby-love-drawing/magic` 后端安全代理,使用 VectorEngine `gpt-image-2-all` 与原始画布 Data URL 参考图生成绘本风图片;保存只写 localStorage,正式持久化后续再设计。
|
||||
- 决策:`baby-love-drawing / 宝贝爱画` 先作为独立运行态接入,入口由发现页寓教于乐默认卡片打开,并支持 `/runtime/baby-love-drawing` 直达;关闭 `VITE_ENABLE_EDUTAINMENT_ENTRY` 时前端不展示频道/卡片且直达路由回落主应用。绘画魔法统一走 `POST /api/creation/edutainment/baby-love-drawing/magic` 后端安全代理,使用 VectorEngine `gpt-image-2` 与原始画布 Data URL 参考图生成绘本风图片;保存只写 localStorage,正式持久化后续再设计。
|
||||
- 影响范围:`packages/shared/src/contracts/edutainmentBabyDrawing.ts`、`src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.tsx`、`src/services/edutainment-baby-drawing/`、`src/routing/appRoutes.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、`server-rs/crates/api-server/src/edutainment_baby_drawing.rs`、`src/index.css`、宝贝爱画 PRD 与技术方案。
|
||||
- 验证方式:执行宝贝爱画 model/runtime/service/route 定向测试、`npm run typecheck`、定向 ESLint、`cargo test -p api-server edutainment_baby_drawing --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server resolves_runtime_paths_to_creation_type_ids --manifest-path server-rs/Cargo.toml` 和编码检查;真实魔法生成需配置 `VECTOR_ENGINE_BASE_URL` 与 `VECTOR_ENGINE_API_KEY`。
|
||||
- 关联文档:`docs/prd/BABY_LOVE_DRAWING_EDUTAINMENT_LEVEL_PRD_2026-05-13.md`、`docs/technical/BABY_LOVE_DRAWING_RUNTIME_DEMO_IMPLEMENTATION_2026-05-13.md`。
|
||||
@@ -304,7 +624,7 @@
|
||||
## 2026-05-10 儿童动作 Demo 视觉资产统一为绘本草地舞台
|
||||
|
||||
- 背景:儿童动作 Demo 需要从暗色科技风切换到更适合儿童互动的卡通绘本草地风格,并且要让背景、地面、UI、地面指示环和用户轮廓使用同一套 image-2 资源口径。
|
||||
- 决策:热身舞台及后续儿童动作 Demo 场景、物品、UI 资源统一采用明亮卡通绘本草地视觉语言。真实资源默认输出到 `public/child-motion-demo/`。背景沿用 `picture-book-grass-stage.png`;地面、指示环、角色指示器和 UI 已拆分为用途专属资源:`picture-book-foreground-grass-v2.png`、`picture-book-ground-ring-v3.png`、`picture-book-character-outline-v4.png`、`picture-book-hud-strip-v2.png`、`picture-book-calibration-strip-v2.png`、`picture-book-start-panel-v2.png` 和 `picture-book-ui-button-v2.png`。其中角色指示器 v4 基于 v2 本地后处理为更细的白色描边样式,内部透明,耳朵、手指、脚趾等细节已弱化,页面显示尺寸相对上一版放大 50%。生成脚本固定为 `scripts/generate-child-motion-demo-assets.mjs`,并通过 `npm run assets:child-motion-demo` 调用 VectorEngine `gpt-image-2-all`;透明资源使用品红底生成后本地去背,中间源图仅保存在 `tmp/child-motion-demo-assets/`。在缺少 `VECTOR_ENGINE_BASE_URL` 或 `VECTOR_ENGINE_API_KEY` 时,只允许 dry-run 和 CSS 兜底,不伪造 live 生图结果。
|
||||
- 决策:热身舞台及后续儿童动作 Demo 场景、物品、UI 资源统一采用明亮卡通绘本草地视觉语言。真实资源默认输出到 `public/child-motion-demo/`。背景沿用 `picture-book-grass-stage.png`;地面、指示环、角色指示器和 UI 已拆分为用途专属资源:`picture-book-foreground-grass-v2.png`、`picture-book-ground-ring-v3.png`、`picture-book-character-outline-v4.png`、`picture-book-hud-strip-v2.png`、`picture-book-calibration-strip-v2.png`、`picture-book-start-panel-v2.png` 和 `picture-book-ui-button-v2.png`。其中角色指示器 v4 基于 v2 本地后处理为更细的白色描边样式,内部透明,耳朵、手指、脚趾等细节已弱化,页面显示尺寸相对上一版放大 50%。生成脚本固定为 `scripts/generate-child-motion-demo-assets.mjs`,并通过 `npm run assets:child-motion-demo` 调用 VectorEngine `gpt-image-2`;透明资源使用品红底生成后本地去背,中间源图仅保存在 `tmp/child-motion-demo-assets/`。在缺少 `VECTOR_ENGINE_BASE_URL` 或 `VECTOR_ENGINE_API_KEY` 时,只允许 dry-run 和 CSS 兜底,不伪造 live 生图结果。
|
||||
- 影响范围:`src/index.css`、`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx` 的舞台视觉层、儿童动作 Demo 技术文档、后续 image-2 资产生成流程。
|
||||
- 验证方式:检查 `/child-motion-demo` 舞台是否在未生成资产时仍有可用草地绘本兜底;补齐 VectorEngine 私密配置后运行 `npm run assets:child-motion-demo -- --live` 或 `--live --only <asset-id>` 应能写出对应 PNG,并确认页面静态资源返回 `image/png`。若只调整透明去背、裁切或品红边缘,可运行 `npm run assets:child-motion-demo -- --live --postprocess-only --force --only <asset-id>` 复用源图后处理。页面接入时必须按资源原始比例等比使用,不得把方形软纸面板拉伸成 HUD、状态条或底部草坪。
|
||||
- 关联文档:`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`、`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`。
|
||||
@@ -325,6 +645,14 @@
|
||||
- 验证方式:执行入口配置、创作 Hub、平台入口交互和 api-server 路由熔断定向测试,确认“视觉小说”不出现在创作页且 `/api/creation/visual-novel/*` 默认被熔断。
|
||||
- 关联文档:`docs/design/PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md`、`docs/technical/ADMIN_CREATION_ENTRY_SWITCH_CONFIG_2026-05-11.md`。
|
||||
|
||||
## 2026-05-20 RPG 创作入口开放
|
||||
|
||||
- 背景:RPG 文字冒险能力已经具备历史 custom-world 创作和运行闭环,但入口默认种子仍 `visible=false`,创作页不展示。
|
||||
- 决策:SpacetimeDB `creation_entry_type_config` 默认种子中 `rpg.visible=true` 且 `open=true`,旧默认隐藏配置只在标题、subtitle、badge、图片、排序和开关完全匹配时迁移为可见可创建。`airp` 仍保持 AI RPG 占位,不接管当前 RPG 链路。结构化创作 / RPG JSON 链路默认关闭 Responses `web_search`,需要联网增强时才通过 `GENARRATIVE_RPG_LLM_WEB_SEARCH_ENABLED=true` 或 `GENARRATIVE_CREATION_AGENT_LLM_WEB_SEARCH_ENABLED=true` 显式启用;未开通工具的上游会返回 `ToolNotOpen`,不能把这类失败暴露成“模型返回结果解析失败”。
|
||||
- 影响范围:创作入口默认种子、旧库入口纠偏、`api-server` 入口熔断、创作页模板 Tab、创作 Hub 测试、玩法链路文档和后端路由文档。
|
||||
- 验证方式:执行入口配置、api-server 路由熔断、创作 Hub 和平台入口交互定向测试,确认“文字冒险”出现在创作入口,`/api/runtime/custom-world*`、`/api/story/*`、`/api/runtime/chat/*` 都按 `rpg` 入口开关熔断。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||
|
||||
## 2026-05-10 运行态输入设备抽象层全项目通用化
|
||||
|
||||
- 背景:拼图运行态接入 mocap 后,鼠标/触控和 mocap 各自维护输入逻辑会导致合并大块、拖拽语义和取消会话行为不一致;后续其他玩法也需要复用体感、摇杆、键盘等设备输入。
|
||||
@@ -347,16 +675,30 @@
|
||||
- 决策:热身关全流程直接接入 `useMocapInput`,通过本地 mocap WebSocket `/stream` 消费 `general.body.center_norm` 身体中心、`actions/action/gesture/gestures/event/name/type` 动作名,以及 `hands[]`、`leftHand/rightHand`、`left_hand/right_hand` 手部坐标;位置步骤由身体中心推进,`wave_greeting`、`wave_left_hand`、`wave_right_hand` 和 `jump_once` 由 mocap 手势/轨迹推进。浏览器摄像头只作为背景层,动作数据源状态优先展示,键鼠仍作为本地调试兜底。
|
||||
- 影响范围:`src/services/useMocapInput.ts`、`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx`、对应单测与热身关技术文档。
|
||||
- 验证方式:执行 `npx vitest run src/services/useMocapInput.test.ts src/components/child-motion-demo/ChildMotionWarmupDemo.test.tsx src/components/child-motion-demo/childMotionWarmupModel.test.ts src/services/child-motion-demo/childMotionDebugInput.test.ts src/routing/appRoutes.test.ts`、`npx eslint ...`、`npm run typecheck`、`npm run check:encoding`,并确认 `http://127.0.0.1:8876/stream` WebSocket 可握手、`http://127.0.0.1:3000/child-motion-demo` 可访问。
|
||||
|
||||
## 2026-05-18 寓教于乐频道补充热身关入口
|
||||
- 背景:用户希望在发现页的寓教于乐板块里直接看到热身关入口,而不是只依赖独立直达路由。
|
||||
- 决策:`child-motion-demo` 作为寓教于乐频道的独立卡片展示,点击后直接进入 `/child-motion-demo`;该入口与 `宝贝爱画` 并列,仍复用现有独立热身关路由,不新增新的创作模板或运行态壳层。
|
||||
- 影响范围:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
- 验证方式:执行入口回归测试、`npm run typecheck`、`npm run check:encoding`,并在发现页的寓教于乐频道确认热身关卡卡片可点击进入 `/child-motion-demo`。
|
||||
- 关联文档:`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`。
|
||||
|
||||
## 2026-05-09 GPT-image-2 图片生成统一迁移到 VectorEngine
|
||||
|
||||
- 背景:仓库内 RPG、拼图、方洞和本地模板脚本的 GPT-image-2 生图此前依赖 APIMart 图片网关;团队要求参考 VectorEngine Apifox `api-448710071`,后续不再使用 APIMart 执行 GPT-image-2 图片生成。
|
||||
- 决策:所有 GPT-image-2 生图请求统一走 VectorEngine `POST /v1/images/generations`,基础配置读取 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` / `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`,上游模型使用 `gpt-image-2-all`,请求体不再携带 `official_fallback`,参考图字段改为 `image`。APIMart 只保留给创意 Agent 的 `gpt-5` Responses 文本/多模态链路。
|
||||
- 决策:所有 GPT-image-2 无参考图生图请求统一走 VectorEngine `POST /v1/images/generations`,有参考图请求走 `POST /v1/images/edits` multipart,基础配置读取 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` / `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`,上游模型使用 `gpt-image-2`,请求体不再携带 `official_fallback`。APIMart 只保留给创意 Agent 的 `gpt-5` Responses 文本/多模态链路。
|
||||
- 影响范围:`api-server` 共享图片 helper、拼图图片生成、角色主图、RPG 场景图、开局 CG 故事板、方洞视觉资产、生产环境示例、gpt-image-2 本地 skill 和相关技术文档。
|
||||
- 验证方式:执行 `npm run check:encoding`、`cargo test -p api-server openai_image --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server puzzle --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server custom_world_ai --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server character_visual --manifest-path server-rs/Cargo.toml`,并用 `npm run dev:api-server` + `/healthz` 做后端 smoke。
|
||||
- 关联文档:`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`、`docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`。
|
||||
|
||||
## 2026-05-21 GPT-image-2 参考图统一走 edits multipart
|
||||
|
||||
- 背景:VectorEngine Apifox 创建 `api-446794806` 与编辑 `api-446794807` 明确区分无参考图创建和有参考图编辑;仓库旧实现曾把参考图塞入 `gpt-image-2` generations 的 `image` 数组,导致与供应商当前契约不一致。
|
||||
- 决策:所有 GPT-image-2 无参考图生成调用 `POST /v1/images/generations`,所有有参考图生成调用 `POST /v1/images/edits`,模型固定 `gpt-image-2`,参考图作为 multipart `image` part 传入;仓库不再调用 `gpt-image-2-all`。
|
||||
- 影响范围:`api-server` 共享图片 helper、拼图图片生成、Match3D 封面重绘和容器 UI 图、gpt-image-2 本地 skill、玩法链路文档和后端架构文档。
|
||||
- 验证方式:搜索仓库不应再出现 VectorEngine 图片编辑路径调用;执行 `cargo test -p api-server openai_image_generation --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server puzzle_vector_engine --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server match3d_background --manifest-path server-rs/Cargo.toml`。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||
|
||||
## 2026-05-08 Hyper3D Rodin Gen-2 只通过后端安全代理接入
|
||||
|
||||
- 背景:需要接入 Hyper3D Rodin Gen-2 的文生 3D 模型与图生 3D 模型,但供应商 API Key 不能进入前端、文档或 Git;本次只是外部副作用代理,不需要新增平台真相表。
|
||||
@@ -530,7 +872,7 @@
|
||||
## 2026-05-10 视觉小说入口收敛为单句创作 + 画风选择
|
||||
|
||||
- 背景:视觉小说入口页要对齐抓大鹅式的线性创作入口,只保留最小可用输入,避免再暴露文档 / 空白 / 对话式工作台。
|
||||
- 决策:入口页只展示一句话创作输入框和横向视觉画风卡片;画风通过 `seedText` 追加 `视觉画风` 和 `画风要求` 两行透传给既有创作链路;点击生成后先进入 `visual-novel-generating` 过程页,再自动进入 `visual-novel-result`。画风卡片主视觉固定消费 `public/visual-novel-style-references/` 下由 VectorEngine `gpt-image-2-all` 生成的静态参考图,不在前端运行时现场调用生图接口。
|
||||
- 决策:入口页只展示一句话创作输入框和横向视觉画风卡片;画风通过 `seedText` 追加 `视觉画风` 和 `画风要求` 两行透传给既有创作链路;点击生成后先进入 `visual-novel-generating` 过程页,再自动进入 `visual-novel-result`。画风卡片主视觉固定消费 `public/visual-novel-style-references/` 下由 VectorEngine `gpt-image-2` 生成的静态参考图,不在前端运行时现场调用生图接口。
|
||||
- 影响范围:`VisualNovelAgentWorkspace`、`visualNovelEntryGeneration`、`PlatformEntryFlowShellImpl`、视觉小说 PRD 和创作 Tab 设计文档;不新增后端字段或数据库结构。
|
||||
- 验证方式:执行 `npm run test -- VisualNovelAgentWorkspace`、视觉小说工作台相关 ESLint、`npx prettier --check` 和 `npm run check:encoding`;`npm run typecheck` 若失败需先区分是否来自无关 Match3D / RPG 既有改动。
|
||||
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`docs/design/PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md`。
|
||||
@@ -554,7 +896,7 @@
|
||||
## 2026-05-12 抓大鹅物品种类从消除次数中拆出并改为 2D 五视角素材
|
||||
|
||||
- 背景:结果页草稿素材已经能生成和预览,但标准 / 硬核难度仍可能按 `clearCount` 误判需要 12 / 20 种素材,且继续生产 GLB 会拉长草稿生成耗时。
|
||||
- 决策:难度配置统一使用 `物品种类`:轻松 3、标准 9、进阶 15、硬核 21;历史硬核 `clearCount=20` 在运行态升为 21 组三消。新草稿和批量新增不再调用 Rodin、不再生成 GLB。每个物品生成 5 个不同 2D 视角,单张 1K 素材图固定按 5x5 切割,最多承载 5 个物品;超过 5 个物品时由 `api-server` 自动分批并行生图。发布必须校验已生成 `image_ready` 且有 `imageViews[]` 或首图引用的素材数量满足当前难度;试玩通过 `itemTypeCountOverride` 自动降到可用 2D 素材数量。历史模型字段只作为旧数据兼容,不再进入新生产链路。
|
||||
- 决策:难度配置统一使用运行态 `物品种类`:轻松 3、标准 9、进阶 15、硬核 20;历史硬核 `clearCount=20` 在运行态仍升为 21 组三消,但类型池最多 20 种。新草稿和批量新增不再调用 Rodin、不再生成 GLB。每次固定从 `2K 1:1`、`10*10` 物品 spritesheet 解析并持久化 20 个物品、每个 5 个不同 2D 形态,物品信息列表全部展示 20 个;持久化行列索引按每行两种物品计算,不能超过 `1..=10`。发布必须校验已生成 `image_ready` 且有 `imageViews[]`、首图引用或可解析的物品 spritesheet 满足当前难度;试玩通过 `itemTypeCountOverride` 自动降到可用 2D 素材数量。历史模型字段只作为旧数据兼容,不再进入新生产链路。
|
||||
- 影响范围:Match3D 结果页、运行态启动契约、`module-match3d` 初始 run 生成、SpacetimeDB start input / restart、发布校验和 Match3D 技术文档。
|
||||
- 验证方式:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`、`cargo test -p module-match3d --manifest-path server-rs\Cargo.toml`、相关后端 check / tests。
|
||||
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
||||
@@ -591,6 +933,14 @@
|
||||
- 验证方式:开发前优先阅读 `CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`;旧 `server-node`、Express、PostgreSQL、Go 方向只允许作为迁移参考。
|
||||
- 关联文档:`docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`、`AGENTS.md`。
|
||||
|
||||
## 2026-05-18 寓教于乐电视端入口概念图采用横屏乐园地图方案
|
||||
|
||||
- 背景:寓教于乐板块需要面向电视端 / 横屏大屏的一组图形化入口概念图,既要像儿童乐园地图,又要和现有绘本插画风一致。
|
||||
- 决策:概念探索优先采用横屏乐园地图结构,推荐顺序为环形乐园岛、展开绘本地图、云朵空中岛、草地舞台地图;生成时优先复用 `public/child-motion-demo/picture-book-grass-stage.png` 作为风格参考,输出仅保留在 `output/imagegen/` 概念目录中,不直接进入正式资源目录。
|
||||
- 影响范围:寓教于乐板块视觉探索、后续前端入口设计、`scripts/generate-edutainment-tv-map-concepts.mjs`、相关设计文档。
|
||||
- 验证方式:概念图需保持无文字、无真实品牌 IP、无暗色科技风,并与现有草地绘本资源在配色和笔触上保持一致。
|
||||
- 关联文档:`docs/design/【前端体验】寓教于乐电视端乐园地图入口概念图-2026-05-18.md`。
|
||||
|
||||
## 2026-04-28/29 server-rs DDD 分层与契约矩阵冻结
|
||||
|
||||
- 背景:server-rs 模块多、上下文多,需防止领域规则、SpacetimeDB 表、HTTP BFF、前端临时逻辑互相污染。
|
||||
@@ -646,3 +996,78 @@
|
||||
- 默认阈值:每批 500 条或 1 秒 flush 一次;outbox 磁盘上限 256 MiB,超过后丢弃低价值 route 事件并记录指标 / 日志。
|
||||
- 影响范围:`api-server` tracking 中间件、SpacetimeDB tracking procedure、部署数据目录、OTLP 指标和运维排障。
|
||||
- 验证方式:数据库不可用时公开 route 请求不失败且 outbox 文件保留;恢复后批量写入成功并删除本地 sealed 文件;关键事件仍立即影响任务 / 统计。
|
||||
|
||||
## 2026-05-19 跳一跳平台公开链路采用独立玩法路由
|
||||
|
||||
- 背景:跳一跳玩法已接入平台入口、推荐、公开详情、试玩和运行态,后续继续扩展公开广场或推荐流时需要避免把它当成拼图兼容分支。
|
||||
- 决策:跳一跳公开路由统一依赖 `sourceType='jump-hop'` 和 `JH-*` public code;平台首页、推荐、公开作品列表/详情、试玩和运行态都按 `jump-hop` 独立玩法分发。后端仍是作品、运行和发布状态的业务真相,前端只做展示、交互和临时 UI 状态,不在页面层补业务规则或权限判断。
|
||||
- 影响范围:平台入口、推荐流、公开详情、试玩启动、跳一跳运行态、`api-server` / SpacetimeDB 公开投影和 shared contracts。
|
||||
- 验证方式:从平台推荐或公开详情进入跳一跳作品时,路由 source type 为 `jump-hop`、public code 为 `JH-*`,运行态启动消费后端返回的完整 profile / run 数据;后端 smoke 统一使用 `npm run dev:api-server` 启动并检查 `/healthz`。
|
||||
- 关联文档:`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 2026-05-26 跳一跳地块图集改为专用 2x3 六格切分
|
||||
|
||||
- 背景:跳一跳创作在地块生图阶段误用了通用系列素材图集 helper,`item_names.len() > grid_size` 的校验会让 6 个地块类型在 `grid_size = 3` 时直接失败;即使绕过校验,通用 helper 仍以“每物品多视图”语义切图,不符合跳一跳地块的一次性六格资产模型。
|
||||
- 决策:跳一跳地块图集固定采用专用 `2行*3列` 六格布局,按 `start / normal / target / finish / bonus / accent` 顺序切分并分别持久化为独立 PNG 资产;图集 prompt 不再调用通用系列素材 `build_generated_asset_sheet_prompt`。
|
||||
- 影响范围:`server-rs/crates/api-server/src/jump_hop.rs`、`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
- 验证方式:`cargo test -p api-server jump_hop_tile_atlas -- --nocapture` 通过;六张切片都应有独立 OSS 对象与 `JumpHopTileAsset` 记录,不再只有 atlas 预览路径。
|
||||
- 关联文档:`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md`。
|
||||
|
||||
# 2026-05-20 陶泥儿主视觉配色回收为暖白/陶土橙
|
||||
|
||||
- 背景:用户要求只替换产品各界面的 UI 颜色,不改布局,并以两张陶泥儿主视觉图作为配色依据。
|
||||
- 决策:平台亮色主题的主色回收到暖白 / 米杏底、陶土橙主按钮、深棕正文与浅杏边框;后台管理也同步切换到同一暖橙体系。主题变量优先通过 `src/index.css` 的 `--platform-*` token 统一控制,零散组件只做必要的局部替换。
|
||||
- 影响范围:主站平台壳层、常用表单 / 按钮 / 卡片 / 背景、后台管理 UI、业务进度条和小游戏结果条的通用强调色。
|
||||
- 验证方式:优先检查 `src/index.css` 与 `apps/admin-web/src/styles/admin.css` 是否还存在旧粉色主色;再用编码检查和可执行的本地 typecheck / build 验证。
|
||||
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||
|
||||
## 2026-05-20 汪汪声浪 v1 公开闭环计划
|
||||
|
||||
- 背景:Bark Battle v1 需要把创作、生成、结果、发布、详情和正式运行态收成一条闭环,避免把草稿试玩、公开广场和正式成绩混在一起。
|
||||
- 决策:`bark-battle` 入口改为 6 字段表单(作品标题、简介、主题 / 竞技背景描述 `themeDescription`、玩家形象描述、对手形象描述、难度);提交后进入 `bark-battle-generating` 独立生成页,自动生成玩家形象、对手形象和竞技背景三图,部分失败也继续进入结果页。旧“角色设定 / 狗狗皮肤预设 / themePreset”统一退场,配置和文档只使用“形象描述 / themeDescription”。结果页只保留单槽重试、重新生成和上传,不再保留一次生成按钮、音频配置入口、皮肤预设入口或排名配置。发布后先跳统一作品详情页 `/works/detail?work=BB-xxxxxxxx`,再由详情页进入正式 `published` runtime;正式 runtime 必须真实麦克风,`draft` 可试玩、可 mock 且不写正式统计。公开广场统一读取 `bark_battle_gallery_view` read model。
|
||||
- 影响范围:`BarkBattleConfigEditor`、`BarkBattleGeneratingView`、`BarkBattleResultView`、`BarkBattleRuntimeShell`、`PlatformEntryFlowShellImpl`、`appPageRoutes`、Bark Battle creation/runtime client、公开广场聚合与相关交互测试。
|
||||
- 验证方式:提交表单后先进入生成页;生成页部分失败仍能落到结果页;结果页只出现单槽重试 / 重新生成 / 上传;发布后先到 `/works/detail?work=BB-xxxxxxxx` 再进正式 runtime;正式 runtime 会要求麦克风并写基础统计,草稿试玩可 mock 且不写正式 run;公开广场读取 `bark_battle_gallery_view`。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-22 汪汪声浪运行态与作品外显信息收口
|
||||
|
||||
- 背景:Bark Battle v1 在正式运行态、图片生成提示词和作品外部卡片上仍存在体验漂移:能量条推满后还要等计时结束、进入正式 runtime 后还要二次点击声控、角色形象 prompt 会默认注入狗主体、草稿 / 已发布卡片外部看不到创作者。
|
||||
- 决策:能量条到玩家或对手边界即结算;正式 `published` runtime 从作品详情启动后立即申请真实麦克风权限,授权成功后立刻进入倒计时,并使用 start run 返回的 `runtimeConfig` 作为本局前端规则参数;结束后弹出独立结算弹窗,运行态固定提供返回按钮。玩家 / 对手形象图提示词保持用户填写的形象描述,只要求单个完整形象、正面和透明背景,不把非狗描述改写成狗;草稿架、已发布作品架、统一作品详情和公开广场列表都展示后端返回的 `authorDisplayName`。Bark Battle 卡片封面按竞技背景、玩家形象、对手形象、入口参考图兜底;works summary 优先读取 `publishedSnapshotJson` 的最终发布素材。拟声词进入配置 JSON,未手动编辑时随主题 / 形象描述重算,手动编辑后保持创作者自定义;触发阈值降到 `0.35`、冷却降到 `150ms`,后端 `BarkBattleRuleset.min_bark_gap_ms` 同步为 `150`,局内有效触发后快速随机展示高能词池。
|
||||
- 影响范围:`BarkBattleSession`、`BarkBattleRuntimeShell`、`BarkBattleConfigEditor`、`BarkBattleConfig`、Bark Battle 生图 prompt、Bark Battle works/gallery summary、创作中心作品架卡片、公开作品码、`module-bark-battle` ruleset 和玩法链路文档。
|
||||
- 验证方式:能量条推到 `100/-100` 的领域测试应提前 finished;发布态 runtime mount 后应自动调用麦克风 sampler、登记正式 run 并使用服务端 runtimeConfig;prompt 单测应覆盖透明背景、正面和非狗描述不强注入狗;作品架测试应覆盖草稿与已发布卡片作者展示和封面兜底;拟声词测试应覆盖主题自动重算、自定义保持和随机展示。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-19 汪汪声浪默认开放并区分草稿试玩与正式运行态
|
||||
|
||||
- 背景:`bark-battle` 已具备草稿结果页、发布链路与运行态 API,继续在入口层标记“敬请期待”会阻断创作闭环;同时草稿试玩不应污染正式成绩统计。
|
||||
- 决策:默认入口改为 `visible=true`、`open=true`、`badge=可创建`,参考图固定为 `/creation-type-references/bark-battle.webp`。系统默认迁移只纠偏未被后台人工改过的汪汪声浪入口。发布后先进入统一作品详情页 `/works/detail?work=BB-xxxxxxxx`;正式 runtime 使用 `runtimeMode=published` 并必须真实麦克风,调用 `startBarkBattleRun` / `finishBarkBattleRun` 写正式 run;草稿结果页试玩仍使用 `runtimeMode=draft`,允许 mock 且不写正式 run。
|
||||
- 验证方式:入口配置响应应返回汪汪声浪可创建和专属参考图;发布后地址应为 `/works/detail?work=BB-xxxxxxxx`;草稿试玩不调用 runtime run API;正式 runtime 无麦克风时不登记正式 run,结算后提交派生指标。
|
||||
|
||||
## 2026-05-20 汪汪声浪生成页负责三图自动生成
|
||||
|
||||
- 背景:结果页承载预览、修补和发布,若继续放“一次生成”按钮会把初始生成和结果修补职责混在一起。
|
||||
- 决策:初始三图生成改由 `bark-battle-generating` 独立生成页自动执行,目标槽位只有玩家形象、对手形象和竞技背景;表单术语统一为 `themeDescription`、玩家形象描述和对手形象描述,不再回退 `themePreset`、狗狗皮肤预设或“角色设定”。部分失败也进入结果页。结果页不再提供一次生成按钮,音频配置和排名配置不进入 v1 公开闭环;结果页只保留单槽重试、重新生成和上传。发布时 SpacetimeDB `bark_battle_published_config.config_json` 使用规范化后的最终 `publishedSnapshot`,`published_snapshot_json` 同步保存同一份快照。
|
||||
- 验证方式:表单提交后进入 `bark-battle-generating`;结果页不会出现一次生成按钮、音频槽、皮肤预设入口或排名配置;Bark Battle 发布后正式 runtime 应读取结果页最终图片素材而不是初始草稿素材。
|
||||
|
||||
## 2026-05-24 敲木鱼结果页先补录作品信息再试玩 / 发布
|
||||
|
||||
- 背景:敲木鱼工作台只应保留生成所需输入,作品标题、简介和主题标签适合放在生成草稿后的补录阶段。
|
||||
- 决策:敲木鱼的 `workTitle`、`workDescription` 和 `themeTags` 从工作台首屏移到结果页;结果页编辑后在试玩或发布前先调用 `update-work-meta` 写回当前作品信息。主题标签编辑样式对齐拼图结果页的胶囊标签编辑器。
|
||||
- 影响范围:`WoodenFishWorkspace`、`WoodenFishResultView`、`PlatformEntryFlowShellImpl`、敲木鱼 PRD 和平台入口链路文档。
|
||||
- 验证方式:工作台首屏不再出现标题 / 简介 / 标签输入;结果页修改后点试玩或发布会先写回当前作品信息。
|
||||
- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-05-26 前端不外露图片模型名
|
||||
|
||||
- 背景:拼图与相关结果页、生成进度和错误提示里直接显示 `gpt-image-2`、`gemini-3.1-flash-image-preview`、`image-2` 等名称,会把内部模型路由暴露给普通用户。
|
||||
- 决策:前端展示层统一改用产品化名称,如“标准模式”“创意模式”,以及“素材”“图片生成模式”等中性文案;内部 `imageModel`、`generationProvider` 和后端契约值保留不变,只改 UI 文案与错误提示。
|
||||
- 影响范围:拼图图片模型选择器、拼图结果页关卡重生成面板、拼图生成进度文案、宝贝识物结果页占位提示和相关错误提示。
|
||||
- 验证方式:前端可见文本中不再出现 `gpt-image-2` / `gemini-3.1-flash-image-preview` / `image-2 资源`;相关交互测试改为断言产品化模式名,但提交 payload 仍保持原有模型 ID。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
## 2026-05-26 敲木鱼发布后作品架与推荐流刷新口径
|
||||
|
||||
- 背景:敲木鱼已具备公开广场投影,但草稿 Tab 的作品架没有当前用户作品列表接口,导致已发布作品在发布后不能立即出现在“已发布”筛选和推荐流里。
|
||||
- 决策:新增 `GET /api/creation/wooden-fish/works` 作为当前用户木鱼作品架事实源,返回 `WoodenFishWorksResponse.items` 摘要;平台壳在发布成功后必须同时刷新作品架和公开广场列表。
|
||||
- 影响范围:`server-rs/crates/api-server/src/wooden_fish.rs`、`server-rs/crates/api-server/src/modules/wooden_fish.rs`、`src/services/wooden-fish/woodenFishClient.ts`、`src/components/custom-world-home/creationWorkShelf.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`。
|
||||
- 验证方式:发布一个木鱼作品后,草稿 Tab 的已发布筛选应立刻出现 `WF-*` 作品卡,推荐 / 最新流也应立即刷新出公开卡片。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`。
|
||||
|
||||
@@ -93,6 +93,8 @@ npm run dev:admin-web
|
||||
|
||||
`npm run dev:api-server` 会保留终端实时输出,并把同一份输出持久化到 `logs/api-server/api-server-<timestamp>.log`。完整联调入口 `npm run dev` 启动的 Rust `api-server` 使用同一套日志规则。如需改写路径,可设置 `GENARRATIVE_API_SERVER_LOG_FILE`;如只改目录,可设置 `GENARRATIVE_API_SERVER_LOG_DIR`。
|
||||
|
||||
开发态 `npm run dev` / `npm run dev:api-server` 默认打开 `GENARRATIVE_DEV_PASSWORD_ENTRY_AUTO_REGISTER_ENABLED=true`,密码入口可以直接注册未知手机号账号;生产默认仍关闭该开关。
|
||||
|
||||
查看本地 Rust/SpacetimeDB 日志:
|
||||
|
||||
```bash
|
||||
@@ -112,6 +114,24 @@ SpacetimeDB bindings 生成:
|
||||
npm run spacetime:generate
|
||||
```
|
||||
|
||||
CodeGraph 本地语义索引:
|
||||
|
||||
```bash
|
||||
npm run codegraph:init
|
||||
npm run codegraph:status
|
||||
npm run codegraph:sync
|
||||
npm run codegraph:index
|
||||
```
|
||||
|
||||
`.codegraph/config.json` 可随仓库共享;`.codegraph/codegraph.db`、缓存和日志为本机生成物,不提交。
|
||||
|
||||
Codex 项目级 hook 保存在 `.codex/config.toml` 与 `.codex/hooks/`:
|
||||
|
||||
- `PreToolUse` hook:`node .codex/hooks/pre-submit-compile-check.mjs`,Codex 准备执行 `git commit` 前检查 `npm run typecheck`、`npm run admin-web:typecheck`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`。
|
||||
- `PostToolUse` hook:`node .codex/hooks/post-edit-codegraph-sync.mjs`,工具修改文件后执行 `npm run codegraph:sync`。
|
||||
|
||||
个人 token、模型路由、MCP server 仍属于个人环境;需要时由成员本机执行 `codegraph install` 或查看 `codegraph install --print-config codex`,不要提交个人全局配置。
|
||||
|
||||
## 常用检查命令
|
||||
|
||||
- 后端通用用户行为埋点统一通过 `record_tracking_event_and_return` procedure、`SpacetimeRuntimeClient::record_tracking_event(...)` 与 api-server `tracking` 中间件写入 `tracking_event` / `tracking_daily_stat`;后台、RPG、大鱼吃小鱼、Visual Novel、Story、Combat 默认排除;作品级游玩埋点统一使用 `work_play_start`,详细事件清单见 `docs/technical/BACKEND_TRACKING_EVENT_COVERAGE_2026-05-09.md`。
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
## 记录格式
|
||||
|
||||
```md
|
||||
|
||||
## 问题标题
|
||||
|
||||
- 现象:看到什么错误或异常行为
|
||||
@@ -14,6 +15,207 @@
|
||||
- 关联:相关文件、文档、提交或 Issue
|
||||
```
|
||||
|
||||
## 平台异步错误必须带来源弹窗,不要只显示裸错误
|
||||
|
||||
- 现象:用户先后触发多个拼图或草稿生成时,旧请求失败后会在当前页面显示“图片生成失败”等裸错误,容易误判为当前正在看的拼图失败;错误文本也不便复制给开发排查。
|
||||
- 原因:不同入口、生成页、结果页、作品详情和运行态各自渲染局部错误,没有统一携带草稿、生成会话、作品或游玩来源。
|
||||
- 处理:跨流程错误统一由 `PlatformEntryFlowShellImpl` 汇总为 `PlatformErrorDialog`,来源使用玩法、草稿 / session / work / run 标识组成;弹窗提供复制按钮。关闭弹窗时只清理可安全清理的错误状态;恢复类错误用 dismiss key 防止反复弹出但不擅自改底层状态。
|
||||
- 验证:触发任一平台级异步失败时,页面应出现包含“错误来源”和“错误内容”的弹窗;复制内容应包含来源和错误正文;旧页面内错误 banner 不再重复出现。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/platform-entry/PlatformErrorDialog.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## “我的”页每日任务卡不要硬编码进度
|
||||
|
||||
- 现象:用户完成或领取每日任务后,任务中心弹窗里的任务状态已经变化,但“我的”页卡片仍显示 `0 / 1` 和“去完成”。
|
||||
- 原因:卡片首版只写了静态展示文案,没有读取 `/api/profile/tasks` 返回的 `ProfileTaskCenterResponse`,领取接口返回的新 `center` 也只用于弹窗。
|
||||
- 处理:进入“我的”页时读取任务中心,卡片用当前可操作任务或已领取任务派生奖励、进度条和操作状态;`claimRpgProfileTaskReward(...)` 成功后用响应里的 `center` 覆盖本地任务中心。
|
||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx` 应覆盖卡片从后端任务摘要显示 `1 / 1`,领取后显示已完成。
|
||||
- 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||
|
||||
## “我的”页不要恢复旧的填邀请码次级按钮
|
||||
|
||||
- 现象:移动端“我的”页在五项常用功能和设置入口下方又出现一个“填邀请码”按钮,看起来像旧入口残留。
|
||||
- 原因:邀请码流程迁移后仍按新用户窗口保留 `canShowReferralRedeemShortcut` 次级入口;但当前页面口径已经固定为五项常用功能宫格,邀请码填写应由邀请链接 query 或明确引导打开弹窗。
|
||||
- 处理:移除常驻 `次级入口` / `填邀请码` 渲染,不删除 `ProfileReferralModal` 的 `redeem` 面板,也不破坏 `?inviteCode=` / `?invite_code=` 自动打开填写弹窗。
|
||||
- 验证:新用户账号打开“我的”页时没有 `次级入口` 和 `填邀请码` 按钮;带 `?inviteCode=spring-2026` 的登录用户仍自动打开邀请码弹窗并预填 `SPRING2026`。
|
||||
- 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`.hermes/skills/genarrative-profile-invite-flow/SKILL.md`。
|
||||
|
||||
## 创作卡片点击要直达已有入口表单,别再保留空白入口页
|
||||
|
||||
- 现象:创作 Tab 模板卡点击后如果仍然停留在创作大厅,或者先进入“X 创作入口”这种空白页,就会让用户多走一层,还可能被错误的 stage 白名单拉回平台。
|
||||
- 原因:`/creation/<play>` 一度被接成空白创作入口页,导致 `SelectionStage`、`appPageRoutes` 和卡片点击分流被旧占位 stage 污染。
|
||||
- 处理:把 `/creation/<play>` 重新指向已有入口表单 stage,例如 `agent-workspace`、`big-fish-agent-workspace`、`match3d-agent-workspace`、`square-hole-agent-workspace`、`jump-hop-workspace`、`wooden-fish-workspace`、`puzzle-agent-workspace`、`bark-battle-workspace`、`visual-novel-agent-workspace`、`baby-object-match-workspace`;平台壳层和测试同步清理空白入口页相关 helper。
|
||||
- 验证:点拼图 / 抓大鹅 / 汪汪声浪卡片后,应看到各自既有工作台内容,例如测试中的 `拼图工作区:missing-session`、`抓大鹅工作区:missing-session` 或 `汪汪声浪配置表单`,并且不再出现“X 创作入口”空白页。
|
||||
- 关联:`src/components/platform-entry/platformEntryTypes.ts`、`src/routing/appPageRoutes.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`。
|
||||
|
||||
## 创作流程刷新恢复必须写私有 query
|
||||
|
||||
- 现象:创作生成页或结果页刷新后回到空白工作区、平台首页,或者从作品详情返回时错误复用了别的玩法草稿。
|
||||
- 原因:部分创作流程只把 `sessionId` / `profileId` / `draftId` / `workId` 放在前端内存里,没有写进 URL;也曾把写 URL 放在 stage 切换前,`writeCreationUrlState` 因为还停在非创作路径而直接跳过。若跨玩法或公开详情继续保留私有 query,还会污染 `/works/detail?work=...`。
|
||||
- 处理:创作页只使用私有 query `sessionId`、`profileId`、`draftId`、`workId` 做刷新恢复,不复用公开 `work` 参数;`pushAppHistoryPath` 只在同一创作流内保留这些 query,离开创作流或切到另一个玩法必须清掉;手动 draft 打开、生成完成和保存回调要在路由已经切到 `/creation/<play>` 后再调用 `writeCreationUrlState`。
|
||||
- 验证:`npm run test -- src/services/creationUrlState.test.ts src/routing/appPageRoutes.test.ts src/components/platform-entry/usePlatformCreationAgentFlowController.test.tsx`;手测生成页 / 结果页刷新仍恢复同一草稿,打开公开作品详情 URL 不带私有恢复参数。
|
||||
- 关联:`src/services/creationUrlState.ts`、`src/routing/appPageRoutes.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 草稿作品架打开结果页返回必须回草稿 Tab
|
||||
|
||||
- 现象:从草稿 Tab 作品架点击已有草稿进入结果页后,点结果页返回会跳回创作 Tab 模板入口,用户需要重新切回草稿页才能继续找原草稿。
|
||||
- 原因:平台壳层只按结果页类型硬编码返回创作入口,没有记录本次创作流是从草稿作品架打开;如果来源标记没有在新建入口时重置,还可能污染下一条创作链路。
|
||||
- 处理:从作品架打开任一玩法草稿时标记返回目标为 `draft-shelf`;从创作 Tab 新建、打开模板或退出非草稿来源工作区时重置为 `create`;结果页返回和工作区退出统一消费这个返回目标,并在消费后复位。
|
||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "puzzle draft result back button returns to draft hub when opened from shelf|agent draft result back button returns to draft hub without syncing result profile"`。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 拼图生成页轮询不要绑展示 phase 或不稳定 setter
|
||||
|
||||
- 现象:拼图创作进入生成中页后,`/api/runtime/puzzle/agent/sessions/{sessionId}` 会在 0.3 到 0.5 秒内被反复 GET,看起来像轮询风暴,而不是 3 秒一次的正常刷新。
|
||||
- 原因:轮询 `useEffect` 同时依赖了拼图展示 phase 和会随父组件渲染变化的 `setSession` 函数,导致 `puzzleGenerationState` 的进度合并或页面重渲染就会重挂 effect;effect 里又会立即先请求一次 session,于是请求被放大成密集循环。
|
||||
- 处理:拼图轮询只绑定 `selectionStage`、`activePuzzleGenerationSessionId` 和“是否仍在生成中”这个布尔条件;`setSession` 通过 ref 保持稳定,不让父组件重新渲染改变轮询器身份。进度 phase 变化只更新展示,不重建轮询。
|
||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating puzzle draft"`,并确认恢复生成中草稿后 `getPuzzleAgentSession` 不会因为进度刷新继续连发。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/platform-entry/usePlatformCreationAgentFlowController.ts`、`src/components/platform-entry/usePlatformCreationAgentFlowController.test.tsx`。
|
||||
|
||||
## 拼图试玩恢复 query 必须先切到运行态路径再写
|
||||
|
||||
- 现象:拼图试玩或正式运行态打开后,刷新会停在“正在进入拼图关卡”,或地址栏只有 `runtimeProfileId`,缺少草稿 `runtimeSessionId`。
|
||||
- 原因:`writePuzzleRuntimeUrlState` 只会在当前路径已经是 `/runtime/puzzle` 时写入;如果先触发阶段切换再写 query,或者草稿作品摘要缺少 `sourceSessionId`,就会把恢复参数写丢。`App.tsx` 的 stage 同步也会改 pathname,所以顺序不对时容易只留下部分 query。
|
||||
- 处理:进入拼图 runtime 时先 `pushAppHistoryPath('/runtime/puzzle')`,再 `setSelectionStage('puzzle-runtime')`,最后写 `runtimeProfileId`、`runtimeSessionId`、`runtimeLevelId`、`work`、`mode`;草稿 runtime URL state 允许从 `profileId` 反推 `puzzle-session-*`,作为 `sourceSessionId` 的兜底。
|
||||
- 验证:`npm test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t \"puzzle draft generation auto starts trial and runtime back opens draft result\"`,确认 `window.location.pathname === '/runtime/puzzle'` 且 `window.location.search` 同时包含 `runtimeProfileId` 和 `runtimeSessionId`。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/services/puzzleRuntimeUrlState.ts`、`src/routing/appPageRoutes.ts`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 首页推荐分流参数不能条件性调用 hook
|
||||
|
||||
- 现象:桌面首页或移动首页在 HMR、断点切换或重新渲染后直接报 React hook 顺序错误,页面停在“正在加载内容”。
|
||||
- 原因:`RpgEntryHomeView` 曾经写成 `const isDesktopLayout = isDesktopLayoutProp ?? usePlatformDesktopLayout();`,当 `isDesktopLayoutProp` 存在时会跳过 hook 调用,导致 hook 顺序在不同渲染之间变化。
|
||||
- 处理:先无条件调用 `usePlatformDesktopLayout()`,再用 `isDesktopLayoutProp ?? detectedDesktopLayout` 合并;不要把 hook 调用藏在条件表达式里。
|
||||
- 验证:桌面与窄屏各刷新一次首页,控制台不再出现 hook 顺序错误;`npm run typecheck` 和首页推荐相关测试通过。
|
||||
- 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/platform-entry/platformEntryResponsive.ts`。
|
||||
|
||||
## 泥点不足提示不要把用户退回创作入口
|
||||
|
||||
- 现象:拼图 / 抓大鹅 / 汪汪声浪等创作表单点击生成时,如果泥点不足,页面直接回到创作 Tab 玩法模板列表,刚填的表单内容随工作台卸载全部丢失。
|
||||
- 原因:`PlatformEntryFlowShellImpl.tsx` 的 `ensureEnoughDraftGenerationPointsFromServer(...)` 曾在余额不足或余额读取失败时调用 `enterCreateTab()` 并 `setSelectionStage('platform')`,把前置校验失败当作离开工作台处理。
|
||||
- 处理:泥点前置校验失败只更新独立 `UnifiedModal` 提示,不切换 stage,不清表单;余额读取失败也走同一弹窗口径。需要提示玩法内错误时可以保留局部错误位,但不得因此退出工作台。
|
||||
- 验证:`npm test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "puzzle form checks mud points before creating a draft|match3d form checks mud points before creating a draft|bark battle form checks mud points before creating image assets"` 应断言弹窗出现、对应工作台仍在、玩法模板分类不再出现。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 玩法入口分类字段缺失要前端兜底
|
||||
|
||||
- 现象:平台创作入口初始化时,`platformEntryCreationTypes.ts` 直接对 `creationTypes[].categoryId` / `categoryLabel` 调 `trim()`,一旦后端旧数据、局部 mock 或异常返回里缺字段,整个创作页会在 `derivePlatformCreationTypes(...)` 里直接炸掉。
|
||||
- 处理:`normalizeCategoryId(...)` 和 `normalizeCategoryLabel(...)` 必须接收可空值,并分别回退到 `recent` / `最近创作`。前端这里是展示派生层,不能要求所有历史配置都先补齐字段。
|
||||
- 验证:`npm test -- src/components/platform-entry/platformEntryCreationTypes.test.ts`,再打开本地创作页确认能正常进入创作 Tab。
|
||||
- 关联:`src/components/platform-entry/platformEntryCreationTypes.ts`、`src/components/platform-entry/platformEntryCreationTypes.test.ts`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 草稿页未读点不要继续用红色 literal
|
||||
|
||||
- 现象:草稿页底部 Tab 和作品架的未读点视觉上仍像红点,或 glow 仍带红色阴影,和平台暖棕体系不一致。
|
||||
- 原因:`platform-nav-unread-dot`、`creation-work-card__unread-dot` 直接写了 `#b64a35` 和 `rgba(239, 68, 68, ...)`,没有收口到统一 token。
|
||||
- 处理:未读点颜色统一走 `--platform-unread-dot-fill` / `--platform-unread-dot-glow`,桌面/移动端共用同一口径;不要把红色 literal 再写回样式。
|
||||
- 验证:`src/index.test.ts` 断言两个 unread dot block 都只引用未读点 token,不再出现红色 literal 或红色 glow。
|
||||
- 关联:`src/index.css`、`src/index.test.ts`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 创作 Tab 模板卡不要复用暗图蒙版参考卡样式
|
||||
|
||||
- 现象:创作 Tab 两列玩法卡上图能看到,但标题、描述或预计消耗泥点在白底信息区里看不见,或只剩泥点小图标。
|
||||
- 原因:旧 `platform-creation-reference-card` 是给暗图蒙版卡用的全局样式,会把卡片及全部子元素强制成白色文字;参考图要求的是“上图 + 下方白底信息区”,继续复用旧类会让白底上的文字消失。
|
||||
- 处理:创作 Tab 首屏模板卡使用独立 `creation-template-card`、`creation-template-card__body`、`creation-template-card__title`、`creation-template-card__subtitle` 和 `creation-template-card__cost` 结构,不挂 `platform-creation-reference-card`;旧弹层如果仍是暗图蒙版卡,可以继续保留旧类。
|
||||
- 验证:浏览器创作 Tab 中每张卡都应显示标题、描述和“预计消耗 10-20 泥点”;`npm test -- src/components/custom-world-home/CustomWorldCreationHub.test.tsx -t "creation start card renders reference-aligned banner and template metadata"` 应通过。
|
||||
- 关联:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`src/index.css`、`src/components/custom-world-home/CustomWorldCreationHub.test.tsx`。
|
||||
|
||||
## 创作首屏开放态卡片不要再显示左上状态标签
|
||||
|
||||
- 现象:创作 Tab 的开放态玩法卡左上角会重复显示“可创建”或“可创作”,视觉上比其它状态更吵,还会和封面图抢注意力。
|
||||
- 原因:卡片渲染层默认把 `badge` 当成所有状态都要展示的左上角标签,没有区分开放态与非开放态。
|
||||
- 处理:开放态卡片不渲染左上标签,仅保留标题、描述和右下角消耗信息;`敬请期待`、`即将开放` 等非开放态标签继续保留。
|
||||
- 验证:创作首屏 HTML 中不应包含 `可创建` / `可创作`,但仍应包含 `即将开放` 等非开放态状态。
|
||||
- 关联:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 发现 / 创作 / 草稿页不要把根内容区再包成全局卡片壳
|
||||
|
||||
- 现象:发现页、创作页或草稿页根区一旦套回 `platform-page-stage`,页面边缘会立刻变得更厚,频道标签、列表和模板卡的横向空间都被挤窄,看起来像回到了旧全局卡片壳。
|
||||
- 原因:`platform-page-stage` 本身是全局内容卡片壳,适合推荐页、我的页和其它页面,但这三页已经有自己的视觉结构;草稿页顶部筛选若继续用旧 `platform-tab`,还会和发现页频道标签不一致。
|
||||
- 处理:这三页的根内容区只保留 `platform-remap-surface`,不要再加 `platform-page-stage`;草稿页顶部筛选复用发现页的 `platform-mobile-home-channel` 与 `platform-mobile-home-channel--active`。
|
||||
- 验证:浏览器里这三页的根区应仍保留 `platform-remap-surface`,但不再出现 `platform-page-stage`;草稿页顶部筛选样式应和发现页频道标签一致。
|
||||
- 关联:`src/components/custom-world-home/CustomWorldCreationHub.tsx`、`src/components/custom-world-home/CustomWorldWorkTabs.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/index.css`。
|
||||
|
||||
## SpacetimeDB 入口迁移 helper 合并时不要只保留调用
|
||||
|
||||
- 现象:`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 或 Jenkins `Genarrative-Stdb-Module-Build` 报 `E0425 cannot find function migrate_rpg_entry_from_old_hidden_default in this scope`,位置在 `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs` 的默认入口配置播种流程。
|
||||
- 原因:分支合并时保留了 `seed_creation_entry_config_if_missing(...)` 中的迁移调用,但漏掉了同文件内的 helper 定义;该 helper 负责把历史默认隐藏的 RPG 入口纠偏为当前开放默认值。
|
||||
- 处理:恢复缺失的迁移 helper,不要直接删除调用。helper 只能匹配历史默认种子(标题、副标题、badge、图片、visible/open、排序都一致)后再更新,避免覆盖后台入口开关的人工配置。
|
||||
- 验证:`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`。
|
||||
- 关联:`server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 抓大鹅新 UI spritesheet 不要回退成中心容器图
|
||||
|
||||
- 现象:新素材流程生成后,运行态棋盘中心可能叠出一整张 UI spritesheet,导致按钮素材、方格和空白图集覆盖容器区域。
|
||||
- 原因:为了兼容旧 DTO,后端可能把 `uiSpritesheetImage*` 同步写入历史 `containerImage*` 字段;旧前端只看 `containerImage*`,会误把 UI 图集当透明中心容器。
|
||||
- 处理:读取中心容器图时先比较归一化后的 `containerImage*` 与 `uiSpritesheetImage*`。两者同源时忽略 `containerImage*`,只把它作为旧数据兼容字段;新流程背景图本身已经保留容器,运行态只需加载背景和解析 UI / 物品 spritesheet。
|
||||
- 验证:`npm run test -- src/components/match3d-runtime/Match3DRuntimeShell.test.tsx` 应覆盖“运行态不把兼容写入的UI spritesheet当中心容器图”。
|
||||
- 关联:`src/components/match3d-runtime/Match3DRuntimeShell.tsx`、`server-rs/crates/api-server/src/match3d/mappers.rs`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 通用系列素材图集先看 platform-image,不要先翻 api-server 大文件
|
||||
|
||||
- 现象:排查跳一跳、抓大鹅或其它玩法的系列素材图集切片 / 去绿 / 持久化时,最容易先打开 `api-server/src/generated_asset_sheets.rs`,结果在一个 60KB+ 大文件里找实现、测试和辅助函数,定位很慢。
|
||||
- 原因:这条通用图片 seam 已经下沉到 `server-rs/crates/platform-image/src/generated_asset_sheets/`,`api-server` 只剩薄包装和调用方兼容;继续把 `api-server` 当真值源会把理解路径拉回旧位置。
|
||||
- 处理:先看 `server-rs/crates/platform-image/src/generated_asset_sheets/mod.rs`、`prompt.rs`、`sheet.rs`、`alpha.rs`、`persist.rs` 和 `error.rs`,再看 `api-server/src/generated_asset_sheets.rs` 的 AppError / AppState 适配和玩法调用点。
|
||||
- 验证:`cargo test -p platform-image --test generated_asset_sheets --manifest-path server-rs/Cargo.toml` 通过,且 `cargo check -p api-server --manifest-path server-rs/Cargo.toml` 保持绿灯。
|
||||
- 关联:`server-rs/crates/platform-image/src/generated_asset_sheets/`、`server-rs/crates/api-server/src/generated_asset_sheets.rs`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## UI spritesheet 不要依赖模型直接生成透明背景
|
||||
|
||||
- 现象:拼图或抓大鹅运行态解析 UI spritesheet 时,把整张背景图、棋盘格、叶子或装饰图也当作 UI 素材区域,按钮映射错乱;截图里常表现为底部按钮区只剩透明棋盘格或素材碎片。
|
||||
- 原因:前端解析依赖 alpha 连通域检测,透明背景是前提;但生图模型收到“透明背景 spritesheet”提示后仍可能输出带实景背景或伪透明棋盘格的普通不透明 PNG,OSS 中保存的图没有真实 alpha。
|
||||
- 处理:UI spritesheet 提示词应要求统一纯绿色绿幕背景,而不是让模型直接产透明背景;后端在上传 OSS 前复用 `generated_asset_sheets::apply_generated_asset_sheet_green_screen_alpha(...)` 把绿幕扣成真实透明 PNG,再把透明图写入 `uiSpritesheetImageSrc/uiSpritesheetImageObjectKey`。
|
||||
- 验证:`cargo test -p api-server puzzle_ui_spritesheet_postprocess_turns_green_screen_transparent --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server puzzle_level_scene_spritesheet_and_background_requests_use_references --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server match3d_derived_asset_prompts_match_three_sheet_pipeline --manifest-path server-rs\Cargo.toml`。
|
||||
- 关联:`server-rs/crates/api-server/src/puzzle/generation.rs`、`server-rs/crates/api-server/src/match3d/works.rs`、`server-rs/crates/api-server/src/generated_asset_sheets.rs`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 敲木鱼 hit object 不要只相信透明底 prompt
|
||||
|
||||
- 现象:苹果等主题试玩时,中央敲击物图带明显黑底;背景图中央还可能出现苹果主体,或背景环境图偶发变成纯绿色底,和“中央只叠加 hitObjectAsset”的运行态设定冲突。
|
||||
- 原因:gpt-image-2 对“透明底”和“背景只做外围氛围”的遵循不稳定。若 hit object 直接入库,黑底会被当成真实像素展示;若背景 prompt 只有软描述,模型会把主题主体画进中央。第一步为了去背刻意要求绿幕图时,如果第二步参考图或 prompt 没有切断绿幕语义,背景图也可能继承纯绿色画布。
|
||||
- 处理:敲木鱼 hit object prompt 固定要求先输出 `1:1` 绿色背景主体图(纯绿色绿幕、单一 `#00FF00` 背景),再由 `api-server` 只对绿幕背景做去绿透明化;不要回到黑底 / 白底 / 透明底 prompt 后再做泛抠图。背景生成必须使用第一步抠图完成后的透明图作为参考图,并在 prompt 中显式禁止继承绿色底色、绿幕底色或纯绿色画布;背景 prompt 还要固定要求中央 40% 主体预留区干净,禁止主题主体、局部特写、轮廓影子、重复元素和主题碎片,只允许外围氛围。
|
||||
- 验证:`cargo test -p api-server wooden_fish --manifest-path server-rs\Cargo.toml`,并用花朵 / 苹果 / 玉米主题跑试玩图确认绿幕被去除、主体未被抠除、背景中央不出现主题主体,背景环境图不再出现纯绿色底。
|
||||
- 关联:`server-rs/crates/api-server/src/wooden_fish.rs`、`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 敲木鱼返回按钮不要让模型自由发挥外圈花纹
|
||||
|
||||
- 现象:返回按钮试玩图有时会被画成徽章、花盘、浮雕圆牌,甚至出现复杂外圈和装饰花纹,左箭头反而不够突出。
|
||||
- 原因:prompt 只说“主题化返回按钮”时,image2 会把参考图里的装饰语言一起学进去;如果没有把形状收束到“标准圆形 + 单个居中左箭头”,模型会优先补造型而不是补图标。
|
||||
- 处理:返回按钮生成 prompt 必须只允许参考图约束圆形底色与箭头配色,明确禁止复杂造型、花纹、浮雕边、异形外框和装饰图案,按钮本体固定为标准圆形,视觉尺寸比当前模板再放大约 50%,圆形外沿需要一圈与主题色搭配的干净外描边。
|
||||
- 验证:`cargo test -p api-server wooden_fish --manifest-path server-rs\Cargo.toml`,并重新试玩确认返回按钮只剩圆形底色和中央左箭头。
|
||||
- 关联:`server-rs/crates/api-server/src/wooden_fish.rs`、`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`.
|
||||
|
||||
## 敲木鱼创作生成不要沿用 15 秒会话超时
|
||||
|
||||
- 现象:敲木鱼工作台点击“生成”后,前端直接提示 `请求超时:15000ms`,但后端和 VectorEngine 未必已经失败。
|
||||
- 原因:`createCreationAgentClient` 的 `createSessionTimeoutMs` 默认是 15 秒;敲木鱼创作链路会继续进入生成页并执行多次 image2 edits、去绿背景处理和 OSS 写入,单次请求窗口如果继承共享默认值,会早于业务生成完成被前端中断。
|
||||
- 处理:敲木鱼 client 必须单独配置长等待窗口,同时覆盖 `createSessionTimeoutMs` 与 `executeActionTimeoutMs`;不要修改共享默认值影响其它轻量创作 Agent。
|
||||
- 验证:`npm run test -- src/services/wooden-fish/woodenFishClient.test.ts`,并在本地触发一次木鱼创作确认不再出现 15 秒前端超时。
|
||||
- 关联:`src/services/wooden-fish/woodenFishClient.ts`、`src/services/creation-agent/creationAgentClientFactory.ts`、`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`。
|
||||
|
||||
## 敲木鱼创作“卡住”先查 2xx 慢请求
|
||||
|
||||
- 现象:敲木鱼工作台点击生成后长时间停留在生成页,看起来像卡住;`api-server` 日志可能出现 `/api/creation/wooden-fish/sessions/{sessionId}/actions` 的 `2xx` 慢请求,耗时可达数分钟,例如 `latency_ms=525473`。
|
||||
- 原因:当前 `compile-draft` 是同步 action,会串行等待敲击物、背景环境图、返回按钮图三次 image2 edits、去绿处理、OSS 写入和 SpacetimeDB 草稿写回;提示词生成音效已关闭,不应作为生成阶段。
|
||||
- 处理:先确认日志中该 action 是不是最终 200;若是 200 慢请求,不要优先排查 WebSocket 或 SpacetimeDB procedure。前端生成页进度必须按“整理草稿 -> 生成敲击物 -> 生成背景环境图 -> 生成返回按钮图 -> 写入正式草稿”展示,并在未收到 action 回包前保持等待态,不宣称完成。
|
||||
- 验证:`npm run test -- src/services/miniGameDraftGenerationProgress.test.ts -t "wooden fish"`,并观察木鱼生成页在 5 分钟以上等待时仍停留在合理阶段。
|
||||
- 关联:`src/services/miniGameDraftGenerationProgress.ts`、`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 敲木鱼点击生成出现 SpacetimeDB procedure 超时先查版本错配
|
||||
|
||||
- 现象:敲木鱼创作时点击“生成”,前端提示 `SpacetimeDB procedure 调用超时`,但服务端日志更早出现 `Failed to BSATN deserialize procedure return value` 或类似反序列化错误。
|
||||
- 原因:本机 `spacetime` CLI / standalone 版本与 `server-rs/Cargo.toml` 锁定的 `spacetimedb` 版本不一致时,procedure 返回值会在宿主侧反序列化失败,api-server 继续等待就表现成调用超时。若旧 standalone 进程还在复用,也会把这个错配继续带进新一轮创作。
|
||||
- 处理:先用 `spacetime --version` 确认 `spacetimedb tool version`,再和 `server-rs/Cargo.toml` 的 `spacetimedb = "..."` 对齐;必要时执行 `spacetime version install <version> && spacetime version use <version>`,然后重启 `npm run dev:spacetime`。当前 dev 脚本会在启动和复用本地 SpacetimeDB 前写入并校验 `dev-spacetime-tool-version`,避免继续复用旧宿主。
|
||||
- 验证:`spacetime --version` 输出与 `server-rs/Cargo.toml` 一致,`http://127.0.0.1:3101/v1/ping` 正常,`npm run test -- scripts/dev.test.ts` 通过,敲木鱼创作点击生成不再卡在 procedure timeout。
|
||||
- 关联:`scripts/dev.mjs`、`scripts/dev.test.ts`、`server-rs/Cargo.toml`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 拼图 UI spritesheet 运行态不要二次包圆底或拉伸比例
|
||||
|
||||
- 现象:拼图运行态左上返回和右上设置按钮外面出现白色圆圈;底部“提示 / 原图 / 冻结”三枚素材被压扁、拉宽或拉成正圆,和图集原始按钮比例不一致。
|
||||
- 原因:UI spritesheet 已经包含按钮视觉本体,但运行态仍给顶部按钮套默认圆形 icon 容器;底部三枚素材用 `h-full w-full rounded-full` 铺满按钮格,覆盖了自动检测矩形的真实宽高比。
|
||||
- 处理:有 `uiSpritesheetImage*` 时,顶部返回 / 设置按钮容器只保留透明点击区和 focus 状态,不再叠加默认圆形底;`buildPuzzleUiSpriteBackgroundStyle(...)` 对检测到的矩形写入 `aspectRatio`,底部三枚素材按原始宽高比和最大尺寸渲染,不强制 `w-full`。
|
||||
- 验证:`npm run test -- src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx`、`npm run test -- src/services/puzzle-runtime/puzzleUiSpritesheetParser.test.ts`。
|
||||
- 关联:`src/components/puzzle-runtime/PuzzleRuntimeShell.tsx`、`src/services/puzzle-runtime/puzzleUiSpritesheetParser.ts`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
2026-05-22 补充:展示矩形和点击热区要分开处理。`puzzleUiSpritesheetParser` 的 `regions` 保留完整视觉裁切矩形,`hitRegions` 用较高 alpha 阈值只包住实心按钮主体;运行态底部 spritesheet 道具按钮启用 `puzzle-runtime-sprite-tool-button--precise-hit`,父按钮不吃整块透明留白,内部 `puzzle-runtime-ui-sprite-hit-zone` 才接收指针事件,避免透明区域成为点击热区。
|
||||
|
||||
## 图像输入组件不要把业务状态藏在页面内联实现里
|
||||
|
||||
- 现象:拼图页把参考图上传、缩略图、主图删除确认和 AI 重绘开关内联实现后,后续想复用到其它创作页时,页面级状态和通用 UI 状态混在一起,容易出现多套上传卡和参考图展示口径。
|
||||
@@ -22,6 +224,54 @@
|
||||
- 验证:拼图入口测试仍可通过,且新组件可通过不同页面复用而不需要复制上传卡实现。
|
||||
- 关联:`src/components/common/CreativeImageInputPanel.tsx`、`src/components/puzzle-agent/PuzzleAgentWorkspace.tsx`。
|
||||
|
||||
## RPG 发布不能只依赖 agent session seed_text
|
||||
|
||||
- 现象:RPG 结果页 `publish_world` 返回 `UPSTREAM_ERROR`,details 为 `custom_world.setting_text 不能为空`;同一 session 的 `result-view` 日志显示 `publish_ready=true`。
|
||||
- 原因:前端发布动作只提交 `{ action: 'publish_world' }`,旧 agent 会话的 `seed_text` 可能为空;如果后端只从 action payload 或 `seed_text` 取 `setting_text`,就会在最终 compile / publish 校验阶段失败。
|
||||
- 处理:`module-custom-world::resolve_custom_world_publish_setting_text(...)` 以当前 `draft_profile_json` 为草稿真相,优先读取 `settingText`、`creatorIntent.rawSettingText`、`creatorIntent.worldHook`、`worldHook`、`anchorContent.worldPromise(.hook)`、`summary`、`name/title`,最后才回退 `seed_text`。
|
||||
- 验证:`cargo test -p module-custom-world publish_setting_text --manifest-path server-rs\Cargo.toml`;`cargo check -p spacetime-module --manifest-path server-rs\Cargo.toml`。
|
||||
- 关联:`server-rs/crates/module-custom-world/src/application.rs`、`server-rs/crates/spacetime-module/src/custom_world.rs`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## RPG 已发布结果页进入世界不能重复 publish_world
|
||||
|
||||
- 现象:RPG 草稿发布成功后,按钮文案已变为“进入世界”,但点击仍请求 `POST /api/runtime/custom-world/agent/sessions/{sessionId}/actions` 且 payload 为 `{"action":"publish_world"}`,后端返回 `publish_world is only available during object_refining, visual_refining, long_tail_review or ready_to_publish`。
|
||||
- 原因:按钮文案依据 agent session `stage === 'published'` 切换,但点击处理仍走发布协调路径;如果前端只依赖草稿同步回包判断是否已发布,回包为空或缺少可进入状态时就会继续重复发送 `publish_world`。
|
||||
- 处理:进入世界协调器接收当前 agent session stage;当 stage 已为 `published` 时,只调用 `result-view` 回读已发布 profile 并启动运行态,不再调用 `sync_result_profile` 或 `publish_world`。
|
||||
- 验证:`npm run test -- src/components/rpg-entry/useRpgCreationEnterWorld.test.tsx`;确认已发布场景下 `syncAgentDraftResultProfile` 与 `executePublishWorld` 均未被调用。
|
||||
- 关联:`src/components/rpg-entry/useRpgCreationEnterWorld.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## RPG 点击启动黑屏 / 默认 profile 先查 profile 归一化和摘要覆盖
|
||||
|
||||
- 现象:作品详情点击“启动”后页面切到 RPG runtime,但用户只看到黑屏、空白,或进入默认角色 / 默认 profile;从作品详情点“作品编辑”后开局 CG、封面、角色图、技能动作预览、初始物品图标或场景背景图丢失;DevTools 里可能同时看到旧自动存档 `/api/runtime/save/snapshot` 被主动 cancel。
|
||||
- 原因:`/custom-world-library` / `/custom-world-gallery` 详情接口可能返回历史或摘要式 `profile`,缺少 `playableNpcs`、`storyNpcs`、`landmarks`、`attributeSchema` 等运行态字段;前端 client 若直接把该对象传给 runtime,角色选择首屏会在 `buildCustomWorldPlayableCharacters(profile)` 或后续属性解析处抛错。另一类常见原因是详情接口已回读完整 profile 后,`savedCustomWorldEntries` 里的列表摘要又把 `selectedDetailEntry` 覆盖回空 profile,导致启动或编辑时只剩卡片摘要。发布 / 回读 result-view 若返回字段更少的旧视图,也可能把当前结果页已编辑资产降级掉。`save/snapshot (canceled)` 通常是切 runtime 或卸载时 `AbortController` 取消旧自动存档,不是黑屏根因。
|
||||
- 处理:RPG 入口作品库 client 在所有返回 `CustomWorldLibraryEntry<CustomWorldProfile>` 的接口边界统一调用 `normalizeCustomWorldProfileRecord`,并用 `profileId/worldName/subtitle/summaryText` 补齐旧数据缺字段;详情页已拿到运行态字段或资产槽位更多的完整 profile 时,不允许列表摘要覆盖当前详情;同一 `profile.id` 下,正式进入世界发布 / 回读不得用字段更少的后端旧视图降级当前结果页 profile。`normalizeCustomWorldProfileRecord` 必须近似无损保留 `cover`、`openingCg`、`camp.narrativeResidues`、`landmark.visualDescription/narrativeResidues`、`skills[].actionPreviewConfig`、`initialItems[].iconSrc`、`attributeSchema`、角色 `attributeProfile` 和 `sceneChapterBlueprints[].acts[]` 的背景与结构字段;只有背景资产的 act 也不能被过滤。角色选择页对角色生成异常或空数组回退默认角色,并保留返回按钮/轻量空态;顶层 runtime 懒加载 fallback 不使用纯 `null`。
|
||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "creation hub published work start uses loaded detail profile instead of library summary|creation hub published work edit keeps loaded detail profile assets instead of library summary"`;`npm run test -- src/data/customWorldLibrary.test.ts -t "保留结果页封面和关键图片资产槽位|近似无损保留编辑态和运行态结构字段|保留只有背景资产的场景幕"`;`npm run test -- src/components/rpg-entry/useRpgEntryAgentDraftRestore.test.tsx -t "默认封面和角色编辑结构差异也不能被列表摘要覆盖"`;`npm run test -- src/components/rpg-entry/useRpgCreationEnterWorld.test.tsx -t "正式进入世界回读结果页字段更少时不降级当前完整 profile"`;`npm run typecheck`。
|
||||
- 关联:`src/components/rpg-entry/useRpgEntryLibraryDetail.ts`、`src/components/rpg-entry/useRpgCreationEnterWorld.ts`、`src/data/customWorldLibrary.ts`、`src/services/rpg-entry/rpgEntryLibraryClient.ts`、`src/components/rpg-entry/RpgEntryCharacterSelectView.tsx`、`src/App.tsx`、`src/components/rpg-runtime-shell/RpgRuntimeShell.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## RPG 战后一轮战斗后卡在观察/试探/调息先查 post-battle finalization
|
||||
|
||||
- 现象:RPG 一轮战斗胜利后,运行态只显示默认 `观察周围迹象 / 主动出声试探 / 原地调息`,这些按钮只有文字反馈;点“继续冒险”后又回到同样选项,点探索只播退场/进场动画,场景和剧情不推进。
|
||||
- 原因:终局战斗 action 如果只走通用 `resolve_story_runtime_action` fallback,而没有在后端调用 `finalize_post_battle_resolution(...)`,就不会持久写入 `story_continue_adventure`、`deferredOptions` 和下一幕 `currentSceneActState`。另外旧 bootstrap 快照可能只有 `connectedSceneIds` / `forwardSceneId`、没有 `connections`,战后选项生成若只读 `connections` 也会退回 `idle_explore_forward` 循环。
|
||||
- 处理:`module-runtime-story` 在 story action 投影后统一调用 post-battle finalization;`idle_explore_forward` 清理战斗态并生成下一段遭遇预览;`idle_travel_next_scene` / `camp_travel_home_scene` 由后端写入新 `currentScenePreset`、场景 act 状态、遭遇预览和 `runtimeStats.scenesTraveled`。前端只负责播放继续、探索和切场景动画,不承接正式剧情推进真相。
|
||||
- 验证:`cargo test -p module-runtime-story --manifest-path server-rs\Cargo.toml battle_tests -- --nocapture` 应覆盖战斗终局持久化 `story_continue_adventure`、`deferredOptions`、下一幕 act,以及 `idle_travel_next_scene` 真正切换场景。
|
||||
- 关联:`server-rs/crates/module-runtime-story/src/session_action.rs`、`server-rs/crates/module-runtime-story/src/post_battle.rs`、`server-rs/crates/module-runtime-story/src/battle_tests.rs`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## RPG 战斗飘字不要只靠低对比红绿文字
|
||||
|
||||
- 现象:暗色或棕黑噪声背景下,战斗伤害飘字看起来像背景纹理,尤其是远端敌人头顶的小号红字几乎不可读。
|
||||
- 原因:旧 `CombatFloatingNumber` 主要依赖 `text-rose-200` / `text-emerald-200` 和 8px 同色 glow;在暗红、棕黑、像素噪声背景上,颜色与背景混在一起,1px 深色描边也不足以形成轮廓。
|
||||
- 处理:飘字本体使用高亮近白文字、小面积半透明深色底、明显深色描边和多层黑色阴影;只增强瞬时反馈,不新增说明面板,不遮挡主要战斗画面。
|
||||
- 验证:`npm run test -- src/components/game-canvas/GameCanvasEntityLayer.test.tsx` 覆盖伤害/治疗飘字样式策略;运行态截图中敌方头顶伤害数字应能在暗场景上辨认。
|
||||
- 关联:`src/components/game-canvas/GameCanvasEntityLayer.tsx`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
|
||||
|
||||
## 弹窗里复用 CreativeImageInputPanel 要保留画面卡高度
|
||||
|
||||
- 现象:拼图草稿结果页的关卡详情弹窗中仍能看到“画面图”标题、画面描述和生成按钮,但实际画面图卡片视觉上消失。
|
||||
- 原因:`CreativeImageInputPanel` 内部依赖 `flex-1`、`h-full` 和 `max-h-full` 撑开正方形画面卡;放进弹窗里的普通 `section` 后,父级没有可计算高度,卡片会被压到不可见。
|
||||
- 处理:通用画面卡 `puzzle-image-upload-card` 保持 `aspect-square` 的同时设置稳定 `min-height`,让入口页和关卡详情弹窗都能显示主图/上传区。
|
||||
- 验证:`npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx -t "opens an independent level detail dialog"` 应断言关卡详情中的 `.puzzle-image-upload-card` 具备最小高度类;`npm run test -- src/components/common/CreativeImageInputPanel.test.tsx` 应继续通过。
|
||||
- 关联:`src/components/common/CreativeImageInputPanel.tsx`、`src/components/puzzle-result/PuzzleResultView.tsx`、`src/components/puzzle-result/PuzzleResultView.test.tsx`。
|
||||
|
||||
## Windows provision 下载截断要断点续传而不是回退目标机下载
|
||||
|
||||
- 现象:`Genarrative-Server-Provision` 在 `Download Provision Tool Archives` 阶段出现 `curl: (18) end of response ... bytes missing`,常见于 `otelcol-contrib_0.151.0_linux_amd64.tar.gz` 等 GitHub release 大文件。
|
||||
@@ -46,6 +296,63 @@
|
||||
- 验证:普通 route 请求在 SpacetimeDB 不可用时仍能返回,恢复后 sealed 文件会继续被清理。
|
||||
- 关联:`server-rs/crates/api-server/src/tracking_outbox.rs`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 跳一跳推荐页匿名直玩要同步放行 runtime 路由和埋点
|
||||
|
||||
- 现象:推荐页能看到跳一跳公开卡片,但未登录点击后会被登录门禁拦住,或者进入运行态后没有 `work_play_start` 记录。
|
||||
- 原因:前端只改了展示层登录门禁,后端 runtime 路由仍要求 bearer auth,或 tracking helper 仍把匿名请求当成无效输入直接丢弃。
|
||||
- 处理:`/api/runtime/jump-hop/runs`、`/jump`、`/restart` 改为可选鉴权;未登录时直接允许启动、跳跃和重开,同时让 `work_play_tracking` 接受 `Option` 用户身份并在 metadata 中标记匿名语义,不要伪造 userId。
|
||||
- 验证:未登录推荐页可以直接进入跳一跳运行态,且 `work_play_start` 事件仍会落库或出现在 outbox 中,metadata 含匿名标记。
|
||||
- 关联:`server-rs/crates/api-server/src/jump_hop.rs`、`server-rs/crates/api-server/src/auth.rs`、`server-rs/crates/api-server/src/work_play_tracking.rs`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。
|
||||
|
||||
## release tracking outbox 权限错误先查 env 缺失
|
||||
|
||||
- 现象:release 机器 `journalctl -u genarrative-api.service` 每秒刷 `tracking outbox 定时封存 active 文件失败 error=Permission denied (os error 13)` 和 `tracking outbox 批量写入 SpacetimeDB 失败`。
|
||||
- 原因:旧 `/etc/genarrative/api-server.env` 没有 `GENARRATIVE_TRACKING_OUTBOX_DIR` 时,api-server 会回退到本地开发默认相对路径 `server-rs/.data/tracking-outbox`;systemd 工作目录是只读发布目录 `/opt/genarrative/releases/<version>`,`genarrative` 用户不能在其中创建 `server-rs`。
|
||||
- 处理:补齐 `GENARRATIVE_TRACKING_OUTBOX_DIR=/var/lib/genarrative/tracking-outbox` 及 batch/flush/max 配置,创建并授权 `/var/lib/genarrative/tracking-outbox` 给 `genarrative:genarrative`,再重启 `genarrative-api.service`。Server-Provision 与 API-Deploy 会保留旧 env 但自动补缺这些运行态路径。
|
||||
- 验证:`tr '\0' '\n' < /proc/$(systemctl show genarrative-api.service -p MainPID --value)/environ | grep GENARRATIVE_TRACKING_OUTBOX_DIR` 应指向 `/var/lib/genarrative/tracking-outbox`;重启后当前 PID 不再出现 `Permission denied (os error 13)`。
|
||||
- 关联:`scripts/deploy/production-api-deploy.sh`、`scripts/jenkins-server-provision.sh`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
|
||||
## 外部 API 失败没法追溯先查 external_api_call_failure
|
||||
|
||||
- 现象:VectorEngine 图片生成 / 编辑接口对前端只表现为 `502` / `504` 或“上游服务请求失败”,但难以区分是请求发送失败、上游 429/5xx、响应解析失败、未返回图片,还是下载图片失败。
|
||||
- 原因:外部 API 失败如果只靠普通日志,不一定能和 OTLP 指标、trace 与 SpacetimeDB 历史查询稳定关联;重启后也容易丢失上下文。
|
||||
- 处理:先查 OTLP 指标 `genarrative.external_api.failures{provider,failure_stage,status_class,retryable}`,再查 `tracking_event` 中 `event_key = 'external_api_call_failure'` 的 `metadata_json`。当前通用 VectorEngine `gpt-image-2-all` 适配器会记录 provider、endpoint、operation、failureStage、statusCode、statusClass、timeout、retryable、errorMessage、latencyMs、promptChars、referenceImageCount、imageModel 和 rawExcerpt。
|
||||
- 验证:`SELECT event_id, scope_id AS provider, metadata_json, occurred_at FROM tracking_event WHERE event_key = 'external_api_call_failure' ORDER BY occurred_at DESC LIMIT 50;`;如果查不到同时看 tracking outbox 目录权限和 sealed 文件是否堆积。
|
||||
- 关联:`server-rs/crates/api-server/src/external_api_audit.rs`、`server-rs/crates/api-server/src/openai_image_generation.rs`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## VectorEngine 图片协议先看 platform-image,不要先翻 puzzle.rs
|
||||
|
||||
- 现象:排查拼图或其它玩法的生图失败时,如果直接在 `api-server` 的大文件里找 `images/generations`、`images/edits`、base64 解码或下载逻辑,会看到很多历史 helper 和测试桥,看起来像每个玩法都自带一份 provider 实现。
|
||||
- 原因:旧实现把 VectorEngine 图片 provider 协议、响应解析、下载和日志混在 `api-server` 里,后来虽然迁出到 `platform-image`,但兼容层和测试 helper 仍会让人误判真相源位置。
|
||||
- 处理:先看 `server-rs/crates/platform-image/src/vector_engine/`:`request.rs` 查路径和请求体,`client.rs` 查生成 / 编辑编排,`transport.rs` 查 HTTP client 与 reqwest 错误归一,`payload.rs` 查响应字段提取,`response.rs` 查上游状态、解析、缺图和下载分流,`image_source.rs` 查参考图和远端图片下载。再看 `server-rs/crates/api-server/src/openai_image_generation.rs` 的兼容桥和 `external_api_audit.rs` 的落库映射;`puzzle/vector_engine.rs` 只保留玩法编排,不再作为 provider 协议真相源。
|
||||
- 验证:`cargo test -p platform-image --manifest-path server-rs/Cargo.toml`、`cargo test -p platform-image --test vector_engine --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server openai_image_generation --manifest-path server-rs/Cargo.toml -- --nocapture` 通过时,排障先按 `platform-image` 的日志字段查 provider / endpoint / failure_stage。
|
||||
- 关联:`server-rs/crates/platform-image/src/vector_engine/`、`server-rs/crates/api-server/src/openai_image_generation.rs`、`server-rs/crates/api-server/src/external_api_audit.rs`、`server-rs/crates/api-server/src/puzzle/vector_engine.rs`。
|
||||
|
||||
## 音频 provider 协议先看 platform-audio,不要先翻 api-server 大文件
|
||||
|
||||
- 现象:排查 Visual Novel 或通用创作音频生成失败时,如果直接打开 `api-server/src/vector_engine_audio_generation.rs`,会同时看到路由、计费、asset binding、下载、解析和 provider 协议,定位时很容易在同一个文件里来回跳。
|
||||
- 原因:音频 provider 已经迁到 `server-rs/crates/platform-audio/`,但 `api-server` 仍保留薄 wrapper;如果把 wrapper 当真值源,就会误判边界。
|
||||
- 处理:先看 `server-rs/crates/platform-audio/src/client.rs`、`request.rs`、`response.rs`、`download.rs`、`persist.rs`、`error.rs`,再看 `api-server/src/vector_engine_audio_generation.rs` 的路由、配置、计费、asset object confirm 和 entity binding 包裹。
|
||||
- 验证:`cargo test -p platform-audio --manifest-path server-rs/Cargo.toml` 通过,且 `cargo check -p api-server --manifest-path server-rs/Cargo.toml` 保持绿灯。
|
||||
- 关联:`server-rs/crates/platform-audio/`、`server-rs/crates/api-server/src/vector_engine_audio_generation.rs`。
|
||||
|
||||
## Hyper3D 现在只剩后端薄代理,不要再把协议解析写回 api-server
|
||||
|
||||
- 现象:排查 Hyper3D/Rodin 时,如果继续在 `api-server/src/hyper3d_generation.rs` 里扩协议解析、请求体构造或下载列表处理,文件会重新变厚。
|
||||
- 原因:`platform-hyper3d` 已经承接 Rodin 的提交、状态和下载协议解析;`api-server` 只是薄 wrapper 和错误 envelope 映射。
|
||||
- 处理:新增或修改 Hyper3D 协议时优先放到 `server-rs/crates/platform-hyper3d/` 的 `client.rs`、`request.rs`、`response.rs`、`transport.rs` 和子模块,`api-server` 只保留鉴权、配置校验和错误映射。
|
||||
- 验证:`cargo test -p platform-hyper3d --manifest-path server-rs/Cargo.toml` 通过后再看 `cargo check -p api-server --manifest-path server-rs/Cargo.toml`。
|
||||
- 关联:`server-rs/crates/platform-hyper3d/`、`server-rs/crates/api-server/src/hyper3d_generation.rs`。
|
||||
|
||||
## release 创作接口 413 先查是否还在提交 Data URL
|
||||
|
||||
- 现象:release 上 `POST /api/runtime/puzzle/agent/sessions/{session_id}/actions` 携带参考图 Data URL 时返回 `413 Request Entity Too Large`,access log 显示 `request_time=0.000`、`upstream_status=-`。
|
||||
- 原因:Nginx 默认 `client_max_body_size` 只有 1 MiB,请求在反代层被拒绝,根本没有到达 `api-server`;即使模板放宽到 `64m`,把图片 base64 放进创作 JSON body 仍会放大请求体并把上限问题推给下一层。
|
||||
- 处理:长期修复不是继续调大 Nginx,而是让浏览器先走 `/api/assets/direct-upload-tickets` 直传 OSS,再 `/api/assets/objects/confirm` 确认 `asset_object`,拼图 action 只提交 `referenceImageAssetObjectId(s)`;后端校验 owner / bucket / kind / MIME / size 后签只读 URL 给 VectorEngine。Nginx `client_max_body_size 64m` 只保留为旧客户端和兼容输入兜底,发布后仍需 `nginx -t && nginx -s reload`。
|
||||
- 验证:前端 action payload 不应再出现大段 `data:image/...;base64`;`nginx -T 2>/dev/null | grep client_max_body_size` 可确认反代兜底;再次提交参考图时 access log 应有正常 `upstream_status`,后端测试 `puzzle_reference_image_sources_prefer_asset_object_ids` / `puzzle_asset_object_reference_requires_matching_owner` 应通过。
|
||||
- 关联:`src/services/puzzle-works/puzzleAssetClient.ts`、`server-rs/crates/api-server/src/puzzle/vector_engine.rs`、`deploy/nginx/genarrative.conf`、`deploy/nginx/genarrative-dev-http.conf`、`deploy/container/nginx.conf`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 汪汪声浪入口不要再回到独立配置阶段
|
||||
|
||||
- 现象:汪汪声浪入口如果继续切换到独立配置阶段,会和拼图、抓大鹅的创作页内嵌结构不一致,用户会感觉入口跳页。
|
||||
@@ -54,6 +361,36 @@
|
||||
- 验证:点击汪汪声浪后直接看到创作页内嵌表单,不再出现独立配置页;测试应覆盖内嵌表单与 runtime 返回路径。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/bark-battle-creation/BarkBattleConfigEditor.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`。
|
||||
|
||||
## 汪汪声浪发布态不要丢失结果页最终素材
|
||||
|
||||
- 现象:结果页上传或批量生成玩家形象、对手形象、UI 背景后,发布进入正式 runtime 仍可能显示初始草稿素材或兜底视觉。
|
||||
- 原因:`publish_bark_battle_work` 如果只把结果页最终状态保存到 `published_snapshot_json`,但正式 runtime 读取的 `config_json` 仍来自草稿行旧值,就会丢失结果页局部替换。
|
||||
- 处理:发布时把最终 `publishedSnapshot` 解析为 `BarkBattleEditorConfigSnapshot`、规范化后同时写入 `bark_battle_published_config.config_json` 和 `published_snapshot_json`;首轮自动生成只由 `bark-battle-generating` 负责,结果页仅覆盖已接入的玩家形象、对手形象和竞技背景图片槽位,不再提供音频配置入口。
|
||||
- 验证:发布后 runtime config 应包含结果页最终 `playerCharacterImageSrc`、`opponentCharacterImageSrc` 和 `uiBackgroundImageSrc`。
|
||||
|
||||
## 汪汪声浪 v1 生成页和正式运行态要分开
|
||||
|
||||
- 现象:如果把初始三图自动生成、结果页修补、公开发布和正式运行态混在一页,创作者容易误以为一次生成和正式运行是同一职责。
|
||||
- 原因:`bark-battle-generating` 才应该承担玩家形象、对手形象和竞技背景的自动生成;结果页只做单槽修补,正式 runtime 又必须切到真实麦克风和正式统计。
|
||||
- 处理:表单提交后先进入独立生成页,部分失败仍进结果页;结果页只保留单槽重试、重新生成和上传,不再保留一次生成按钮、音频配置入口、皮肤预设入口或排名配置。发布后先到统一作品详情页,再进正式 runtime;草稿试玩允许 mock,不写正式 run。
|
||||
- 验证:生成页负责首轮自动产出三图;结果页不出现一次生成按钮、音频配置入口、皮肤预设入口或排名配置;正式 runtime 必须麦克风可用且会写正式 run,草稿试玩不写正式统计。
|
||||
|
||||
## 汪汪声浪生成页不要只停留在前端内存草稿
|
||||
|
||||
- 现象:点击“生成草稿”后生成页一直转圈,或刷新 / 回到草稿架后看不到三图素材。
|
||||
- 原因:生成页只在前端内存里合并玩家形象、对手形象和竞技背景,没有把生成结果写回 `bark_battle_draft_config.config_json`;另外 BFF 若在刚创建草稿后先读 `spacetime-client` 订阅 cache 再保存,cache 可能短暂落后,导致保存失败或返回旧快照。
|
||||
- 处理:生成页三图完成后调用 `POST /api/creation/bark-battle/drafts/{draftId}/config` 持久化;保存接口直接把请求快照交给 SpacetimeDB procedure,由模块事务校验 owner / work,并在 HTTP 回包用本次请求里的三图字段覆盖,避免订阅 cache 滞后;保存请求必须设置前端超时,保存失败也进入结果页并标记部分失败。
|
||||
- 验证:`npm run test -- src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx src/services/bark-battle-creation/barkBattleCreationClient.test.ts src/components/bark-battle-creation/BarkBattleResultView.test.tsx packages/shared/src/contracts/barkBattle.test.ts`;`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "bark battle"`;`cargo check --manifest-path server-rs\Cargo.toml -p api-server`。
|
||||
- 关联:`src/components/bark-battle-creation/BarkBattleGeneratingView.tsx`、`src/services/bark-battle-creation/barkBattleCreationClient.ts`、`server-rs/crates/api-server/src/bark_battle.rs`、`server-rs/crates/spacetime-module/src/bark_battle.rs`。
|
||||
|
||||
## 汪汪声浪三图不要复用 RPG 场景图链路
|
||||
|
||||
- 现象:玩家形象和对手形象看起来走了场景图片 prompt;生成页三个槽位同时转圈,但只有第一个真实生成,首图返回后三个槽位一起停止或只显示首图。
|
||||
- 原因:前端曾复用 `/api/runtime/custom-world/scene-image`,三类素材都被当成 RPG landmark scene image;生成页又只用父级 draft 判断 ready,批量 Promise 结束后才一次性合并结果,缺少逐槽状态。
|
||||
- 处理:Bark Battle 生图统一走 `POST /api/creation/bark-battle/images/generate`,请求体包含 `slot` 和 v1 配置;后端在 `api-server/src/bark_battle.rs` 按 `player-character`、`opponent-character`、`ui-background` 分别拼装正式 prompt,写入 `generated-bark-battle-assets`,并返回 `prompt/actualPrompt`。前端 `generateAllBarkBattleImageAssets` 保持三槽 `Promise.allSettled` 并通过 `onSlotComplete` 逐槽刷新生成页状态。
|
||||
- 验证:`npm run test -- src/services/bark-battle-creation/barkBattleCreationClient.test.ts src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx packages/shared/src/contracts/barkBattle.test.ts`;`cargo test -p shared-contracts bark_battle --manifest-path server-rs\Cargo.toml`;`cargo check --manifest-path server-rs\Cargo.toml -p platform-oss -p api-server`。
|
||||
- 关联:`src/services/bark-battle-creation/barkBattleCreationClient.ts`、`src/components/bark-battle-creation/BarkBattleGeneratingView.tsx`、`server-rs/crates/api-server/src/bark_battle.rs`、`server-rs/crates/platform-oss/src/lib.rs`。
|
||||
|
||||
## 抓大鹅批量重新生成物品不要新增 itemId
|
||||
|
||||
- 现象:结果页批量重新生成物品后,试玩或正式运行态的物品类型和图片对应关系漂移,或者用户输入一个不存在名称后被当作新物品追加。
|
||||
@@ -165,7 +502,7 @@
|
||||
|
||||
## 陶泥儿 logo 生图慢请求先缩短 prompt 并单张串行
|
||||
|
||||
- 现象:使用 VectorEngine `gpt-image-2-all` 生成陶泥儿 logo 概念图时,部分 prompt 会超过 10 分钟仍无响应,或返回 `429` / `当前分组上游负载已饱和`;同一批次里后续图片会被前面的慢请求拖住。
|
||||
- 现象:使用 VectorEngine `gpt-image-2` 生成陶泥儿 logo 概念图时,部分 prompt 会超过 10 分钟仍无响应,或返回 `429` / `当前分组上游负载已饱和`;同一批次里后续图片会被前面的慢请求拖住。
|
||||
- 原因:复杂抽象 logo prompt 同时包含品牌解释、禁用元素、中文结构和多重隐喻时,上游排队与生成时长不稳定;并发或批量运行会放大单条慢请求的影响。
|
||||
- 处理:先 `--dry-run` 看请求体;真实生成时优先短 prompt、单一造型、单张串行或小批量。失败后不要反复重试同一长 prompt,先压缩到“一个主体 + 一个负形 + 颜色 + 禁用文字/播放键/聊天气泡”再跑。联系表中的中文标签不要通过 PowerShell 管道内联 Python 写入,容易因编码链路显示为问号,可改用英文标签或脚本文件方式。
|
||||
- 验证:生成文件落在 `public/branding/taonier-logo-*/`,用 Pillow 检查图片尺寸和非空;执行 `node --check scripts/generate-taonier-logo-concepts.mjs`、`npm run check:encoding`、`git diff --check`。
|
||||
@@ -183,11 +520,11 @@
|
||||
|
||||
- 现象:点击生成抓大鹅草稿后,页面只提示“服务暂不可用”,或者本地 `npm run dev:api-server` 看似启动但生成接口不可用。
|
||||
- 原因:配置缺失类错误通常在后端 `error.details.reason` 中给出具体缺项,前端如果只读 `details.message` 会吞掉原因;本地只配置 `ALIYUN_OSS_BUCKET` / `ALIYUN_OSS_ENDPOINT` 时,旧逻辑还会在启动期构造空 AccessKey 的 OSS 客户端并失败。抓大鹅新链路仍是 2D 生图切割,不需要也不应回退 Rodin/GLB。
|
||||
- 处理:前端 API 错误展示优先读取 `details.reason`,再读取 `details.message`,避免底层 `error sending request` 覆盖真正可操作的配置或网络原因;`api-server` 只有在 OSS 四件套齐全时初始化 OSS 客户端,部分缺失只记 warning 并让具体 generated 上传/换签接口返回 `OSS 未完成环境变量配置`。抓大鹅素材、封面和背景生成在调用 VectorEngine 前先预检 OSS,并通过 `details.missingEnv` 列出缺项;真实生成需补齐 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY` 和完整 `ALIYUN_OSS_*` 四件套。抓大鹅 `5*5` 素材图提示词还必须要求相邻物体主体至少保留 `1/4` 单格宽度空白间距,避免切割后相邻格内容污染。
|
||||
- 处理:前端 API 错误展示优先读取 `details.reason`,再读取 `details.message`,避免底层 `error sending request` 覆盖真正可操作的配置或网络原因;`api-server` 只有在 OSS 四件套齐全时初始化 OSS 客户端,部分缺失只记 warning 并让具体 generated 上传/换签接口返回 `OSS 未完成环境变量配置`。抓大鹅素材、封面和背景生成在调用 VectorEngine 前先预检 OSS,并通过 `details.missingEnv` 列出缺项;真实生成需补齐 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY` 和完整 `ALIYUN_OSS_*` 四件套。抓大鹅 UI spritesheet 和物品 spritesheet 的提示词必须要求纯绿色绿幕背景,后端上传 OSS 前统一扣成透明 PNG,避免运行态 alpha 连通域解析失败。
|
||||
- 验证:`npm run test -- src/services/apiClient.test.ts` 覆盖 `details.reason`;`cargo test -p api-server state --manifest-path server-rs/Cargo.toml` 覆盖半配置 OSS 不阻断启动;`npm run dev:api-server` 后按实际 `GENARRATIVE_API_PORT` 请求 `/healthz`,不要默认打 `3100`。
|
||||
- 关联:`packages/shared/src/http.ts`、`server-rs/crates/api-server/src/state.rs`、`docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`、`docs/technical/AUTH_SNAPSHOT_AND_MATCH3D_LOCAL_DEV_FIX_2026-05-01.md`。
|
||||
|
||||
2026-05-14 补充:抓大鹅“物品素材 sheet”已改用 VectorEngine Gemini `gemini-3-pro-image-preview` 原生 `generateContent`,真实生成读取 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY` 和 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;封面和 `9:16` 背景图走 VectorEngine `/v1/images/generations`,`1:1` 容器 UI 走 VectorEngine `/v1/images/edits` multipart 参考图链路。排查素材 sheet 时看请求路径是否为 `/v1beta/models/gemini-3-pro-image-preview:generateContent?key=...`,响应图片在 `candidates[].content.parts[].inlineData.data` / `inline_data.data`,不要再按 APIMart `/images/generations` 或 `/tasks/{task_id}` 排查。
|
||||
2026-05-22 补充:抓大鹅“物品 spritesheet”不再按旧 Gemini `generateContent` / `5*5` sheet 路径排查;当前链路先用 `gpt-image-2` 无参考图生成 `9:16` 关卡整图,再以该关卡整图作为 multipart `image` 参考并发编辑生成 `1K 1:1` UI spritesheet、`1K 9:16` 背景图和 `2K 1:1` 物品 spritesheet。UI 与物品 spritesheet 都要求纯绿色绿幕背景,上传 OSS 前通过后端透明化处理写入真实 alpha PNG。
|
||||
|
||||
## 抓大鹅发布按钮要先开发布面板,封面编辑收口到发布面板内
|
||||
|
||||
@@ -281,7 +618,7 @@
|
||||
## 儿童动作 Demo 绘本风资源未生成先查 VectorEngine 配置
|
||||
|
||||
- 现象:`/child-motion-demo` 已经呈现绘本草地风格,但 `public/child-motion-demo/picture-book-grass-stage.png`、`picture-book-grass-floor.png`、`picture-book-ground-ring.png`、`picture-book-character-outline.png`、`picture-book-ui-panel.png` 或 `picture-book-ui-button.png` 不存在,Network 里对应图片返回 404,或运行 `npm run assets:child-motion-demo -- --live` 返回缺少 VectorEngine 配置。
|
||||
- 原因:儿童动作 Demo 的真实背景、地面、UI、地面指示环和角色轮廓资源都使用 VectorEngine `gpt-image-2-all` 生成,脚本只读取 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY` 和可选 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;仓库内不能提交真实 key,缺配置时页面只能使用 CSS 草地绘本兜底。
|
||||
- 原因:儿童动作 Demo 的真实背景、地面、UI、地面指示环和角色轮廓资源都使用 VectorEngine `gpt-image-2` 生成,脚本只读取 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY` 和可选 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;仓库内不能提交真实 key,缺配置时页面只能使用 CSS 草地绘本兜底。
|
||||
- 处理:在本地私密环境补齐 `VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai` 与 `VECTOR_ENGINE_API_KEY`,不要把 key 写入 Git;先运行 `npm run assets:child-motion-demo -- --dry-run` 核对 prompt,再运行 `npm run assets:child-motion-demo -- --live` 或 `npm run assets:child-motion-demo -- --live --only ui-panel` 等小批量命令生成资源。透明资源的品红底源图写入 `tmp/child-motion-demo-assets/`,不要把源图或预览图放入 `public/child-motion-demo/` 作为正式资产。
|
||||
- 验证:生成后确认 `public/child-motion-demo/` 只保留页面引用的最终 PNG,重新打开 `/child-motion-demo` 可看到真实绘本草地背景、地面、圆环、角色轮廓和 UI 资源;`npm run check:encoding` 仍通过。
|
||||
- 关联:`scripts/generate-child-motion-demo-assets.mjs`、`src/index.css`、`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`。
|
||||
@@ -305,8 +642,8 @@
|
||||
## GPT-image-2 不再读 APIMart 图片配置
|
||||
|
||||
- 现象:配置了 `APIMART_BASE_URL` / `APIMART_API_KEY` 后,RPG、拼图或方洞的 GPT-image-2 生图仍返回缺配置,或请求体里还出现 `official_fallback` / `image_urls`。
|
||||
- 原因:2026-05-09 后 GPT-image-2 图片生成已切到 VectorEngine `gpt-image-2-all`,APIMart 只保留给创意 Agent 的 `gpt-5` Responses 文本/多模态链路。
|
||||
- 处理:为图片生成配置 `VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai`、`VECTOR_ENGINE_API_KEY`、`VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;排查请求体时确认路径为 `/v1/images/generations`、模型为 `gpt-image-2-all`、参考图字段为 `image`。
|
||||
- 原因:2026-05-21 后 GPT-image-2 图片生成按 VectorEngine 创建/编辑接口分流,APIMart 只保留给创意 Agent 的 `gpt-5` Responses 文本/多模态链路。
|
||||
- 处理:为图片生成配置 `VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai`、`VECTOR_ENGINE_API_KEY`、`VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;排查请求体时确认无参考图路径为 `/v1/images/generations`、有参考图路径为 `/v1/images/edits`,模型为 `gpt-image-2`。
|
||||
- 验证:运行 `cargo test -p api-server openai_image --manifest-path server-rs/Cargo.toml` 和相关玩法图片生成测试;真实联调只在本地私密环境放置 VectorEngine key。
|
||||
- 关联:`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`、`server-rs/crates/api-server/src/openai_image_generation.rs`。
|
||||
|
||||
@@ -326,19 +663,19 @@
|
||||
- 验证:后端单测覆盖 `build_puzzle_levels_with_primary_update` 和 `apply_generated_puzzle_candidates_to_session_snapshot`;结果页重新生成应在未重新上传时继续带入 `level.pictureReference`。
|
||||
- 关联:`server-rs/crates/api-server/src/puzzle.rs`、`src/components/puzzle-result/PuzzleResultView.tsx`。
|
||||
|
||||
## 拼图图生图仍不像参考图时先看是否走了 edits
|
||||
## 拼图参考图不像时先看 edits multipart image
|
||||
|
||||
- 现象:Network payload 已带 `referenceImageSrc`,但 VectorEngine 生成结果仍明显不像上传图。
|
||||
- 原因:`gpt-image-2-all` 的 `/v1/images/generations` 更适合纯文生图;有参考图且需要重绘时应切到 `/v1/images/edits` 的 multipart 图生图接口。
|
||||
- 处理:`referenceImageSrc` 存在且 `aiRedraw = true` 时直接走 edits,prompt 仍保留参考图强约束;入口页关闭 AI 重绘时直接应用上传图,不调用图片生成;前端把参考图压到单边 1024 内,后端解析后拒绝超过 8MB 的参考图字节。
|
||||
- 验证:后端单测应覆盖 `images/edits` 路由、`b64_json` 响应解码和参考图强提示;真实联调先看日志里是否命中 `拼图 VectorEngine 图片编辑 HTTP 返回`。
|
||||
- 原因:参考图只在 `aiRedraw = true` 时由后端解析并传给 `gpt-image-2` `/v1/images/edits` 的 multipart `image` part;若前端没传 `referenceImageSrc`、后端解析失败或 prompt 缺少参考图强约束,生成会退化为纯文生图。
|
||||
- 处理:`referenceImageSrc` 存在且 `aiRedraw = true` 时走 edits multipart,prompt 保留参考图强约束;入口页关闭 AI 重绘时直接应用上传图,不调用图片生成;前端把参考图压到单边 1024 内,后端解析后拒绝超过 8MB 的参考图字节。
|
||||
- 验证:后端单测应覆盖 `/v1/images/edits` 路由、`b64_json` 响应解码和参考图强提示;真实联调看日志里是否命中 `拼图 VectorEngine 图片编辑 HTTP 返回`。
|
||||
- 关联:`server-rs/crates/api-server/src/puzzle.rs`、`src/services/puzzleReferenceImage.ts`、`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`。
|
||||
|
||||
## 拼图 edits 报 error sending request 先看网络分类
|
||||
|
||||
- 现象:拼图有参考图时返回 `拼图图片生成失败:创建拼图 VectorEngine 图片编辑任务失败:error sending request for url (https://api.vectorengine.ai/v1/images/edits)`,后端没有 `拼图 VectorEngine 图片编辑 HTTP 返回` 日志。
|
||||
- 原因:这是 `reqwest` 在 `send()` 阶段失败,尚未收到 VectorEngine HTTP 响应;常见原因是服务器网络 / DNS / 防火墙 / 代理问题,或上游网关中断 multipart 连接。
|
||||
- 处理:查看错误响应 `details.reason/source/connect/body/timeout/endpoint` 和 `拼图 VectorEngine 请求发送失败` 日志。拼图图片客户端已强制 HTTP/1.1,降低 multipart HTTP/2 兼容风险;若 `connect=true` 先查网络出口,若 `body=true` 先查参考图大小和 multipart 发送。
|
||||
- 处理:查看错误响应和 `拼图 VectorEngine 图片编辑` 相关日志;若请求发送阶段失败,先查网络出口、DNS、防火墙、代理、参考图大小和 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`。
|
||||
- 验证:`curl --http1.1 -i -X POST https://api.vectorengine.ai/v1/images/edits -H "Authorization: Bearer invalid" -F "model=gpt-image-2" -F "prompt=test" -F "n=1" -F "size=1024x1024" -F "image=@public/match3d-background-references/pot-fused-reference.png;type=image/png"` 至少应返回 HTTP `401`,说明域名、TLS、路径和 multipart 上传可达;执行 `cargo test -p api-server puzzle_vector_engine --manifest-path server-rs/Cargo.toml`。
|
||||
- 关联:`server-rs/crates/api-server/src/puzzle.rs`、`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`、`docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`。
|
||||
|
||||
@@ -369,17 +706,41 @@
|
||||
## 拼图草稿生成 180 秒后 502/504 先查 VectorEngine 超时与前端重试
|
||||
|
||||
- 现象:点击“生成拼图游戏草稿”后,`POST /api/runtime/puzzle/agent/sessions/{sessionId}/actions` 等待约 180 秒返回 `502 Bad Gateway` 或 `504 Gateway Timeout`;钱包流水里同一 session 可能出现连续两组 `puzzle_initial_image` 扣费后退款。
|
||||
- 原因:首图生成走 VectorEngine `gpt-image-2-all`,默认 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS=1000000`;若上游在该窗口内未返回,后端退款并返回超时错误。旧前端 action 写请求会对 502/503/504 自动重试一次,导致同一次点击重复触发生图与扣退费。
|
||||
- 原因:首图生成走 VectorEngine `gpt-image-2`,默认 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS=1000000`;若上游在该窗口内未返回,后端退款并返回超时错误。旧前端 action 写请求会对 502/503/504 自动重试一次,导致同一次点击重复触发生图与扣退费。
|
||||
- 处理:拼图/创作 Agent 的 `executeAction` 默认不做前端自动重试;后端将 VectorEngine / 图片请求超时映射为 `504 Gateway Timeout`,`error.details.provider=vector-engine` 且 `timeout=true`。真实排障按日志同一 `session_id` 查 `拼图 VectorEngine 图片生成 HTTP 返回` 是否缺失,以及钱包流水扣费到退款的时间差是否接近 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`。
|
||||
- 验证:运行 `npm run test -- src/services/creation-agent/creationAgentClientFactory.test.ts src/services/apiClient.test.ts`、`cargo test -p api-server puzzle_vector_engine --manifest-path server-rs/Cargo.toml`,真实联调重启 `npm run dev:api-server` 后检查 `/healthz`。
|
||||
- 关联:`src/services/creation-agent/creationAgentClientFactory.ts`、`server-rs/crates/api-server/src/puzzle.rs`、`docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`。
|
||||
|
||||
## 开局 CG 故事板生图失败先查 VectorEngine 请求预算和旧进程
|
||||
|
||||
- 现象:RPG 结果页点击开局 CG 后,`POST /api/runtime/custom-world/opening-cg` 在较长等待后返回“开局 CG 故事板生成失败:创建图片生成任务失败:error sending request for url (https://api.vectorengine.ai/v1/images/generations)”。
|
||||
- 原因:该故事板会把角色图和首幕背景图作为参考图一起传给 VectorEngine `gpt-image-2-all`,请求体和上游生成耗时都比普通单图更大;若运行中的 `api-server` 仍沿用旧 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`,或者参考图过大,会在请求发送/等待阶段被 reqwest 截断。日志里 `timeout=false connect=false request=true body=false source=client error (SendRequest)` 表示还没拿到上游 HTTP 响应,通常优先怀疑大 JSON 请求体、上游网关中断或 HTTP 协议兼容,而不是业务响应解析失败。直接请求 VectorEngine 若无效 token 可快速返回 401,不能据此判断真实生图不会超时。
|
||||
- 处理:开局 CG 参考图入参先压到单边 768 的 JPEG;`/v1/images/generations` 保持 reqwest 默认 HTTP 协商,只有 multipart `/v1/images/edits` 单独强制 HTTP/1.1。后端图片 helper 将 `request_body_bytes`、每张参考图 Data URL 长度、`timeout/connect/body/source/rootSource/sourceChain/endpoint` 分类写入日志和 `error.details`,前端优先展示 `details.reason`。修改 `.env.secrets.local` 后必须重启 `api-server`,`npm run dev` 终端用 `rs api-server`,否则旧进程仍按旧超时运行。
|
||||
- 验证:分别运行 `cargo test -p api-server custom_world_ai --manifest-path server-rs/Cargo.toml` 和 `cargo test -p api-server openai_image_generation --manifest-path server-rs/Cargo.toml`;真实联调重启后再触发开局 CG,若仍失败看返回的 `details.reason/source/rootSource/sourceChain/timeout/connect/body/endpoint` 和 `logs/api-server/` 同一 request_id。
|
||||
- 关联:`server-rs/crates/api-server/src/custom_world_ai.rs`、`server-rs/crates/api-server/src/custom_world_ai/opening_cg.rs`、`server-rs/crates/api-server/src/openai_image_generation.rs`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 开局 CG 成功后又变空白要保留 profile.openingCg
|
||||
|
||||
- 现象:RPG 结果页里的开局 CG 成功显示一瞬后,窗口又退回空白占位。
|
||||
- 原因:`openingCg` 只存在于结果页 profile 槽位,如果父层在 `onProfileChange` 后重新同步了 profile,却经过 `normalizeCustomWorldProfileRecord` 或作品库写回时丢掉 `openingCg`,预览就会从视频 / 故事板回退为空白。
|
||||
- 处理:`src/data/customWorldLibrary.ts` 的 profile 归一化必须透传 `openingCg`;结果页和父层后续同步都应把它当作受控资产槽位,而不是临时 UI 状态。
|
||||
- 验证:`npm run test -- src/data/customWorldLibrary.test.ts src/components/CustomWorldResultView.test.tsx`,确认生成后即使父层做一次归一化回写,开局 CG 仍继续显示。
|
||||
- 关联:`src/data/customWorldLibrary.ts`、`src/components/rpg-creation-result/RpgCreationResultViewImpl.tsx`、`src/components/CustomWorldEntityCatalog.tsx`。
|
||||
|
||||
## RPG 发布报 legacy_result_profile_json 非法先查 null 兼容
|
||||
|
||||
- 现象:RPG 结果页发布动作返回 `UPSTREAM_ERROR`,SpacetimeDB details 里是 `custom_world.compile.legacy_result_profile_json 不是合法 JSON object`。
|
||||
- 原因:`publish_world` 前端契约只要求 `{ action: 'publish_world' }`;`ExecuteCustomWorldAgentActionRequest.legacy_result_profile` 是可选字段,经 HTTP / serde / SpacetimeDB payload 传递时可能显式成为 JSON `null`。旧的编译器只接受 object 或缺省,把 `Some("null")` 当成非法 legacy JSON。
|
||||
- 处理:`module-custom-world` 的 optional JSON object 解析要把 `null` 视为未提供,仍拒绝数组、字符串、数字和坏 JSON;正式发布继续以 session `draft_profile_json` 为草稿真相。
|
||||
- 验证:`cargo test -p module-custom-world published_profile_compile --manifest-path server-rs/Cargo.toml`。
|
||||
- 关联:`server-rs/crates/module-custom-world/src/application.rs`、`server-rs/crates/spacetime-module/src/custom_world.rs`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 本地脚本调 VectorEngine 生图卡住先区分 fetch 首部超时
|
||||
|
||||
- 现象:用 Node `fetch` 直接请求 `POST /v1/images/generations`,已经设置较长的 AbortController 超时,但仍在约 180 到 300 秒后抛 `AbortError`、`TypeError: fetch failed` 或 `UND_ERR_HEADERS_TIMEOUT`;同一 prompt 改用原生 `https.request` 可以在较短时间内成功返回图片。
|
||||
- 原因:Node/Undici 的默认 headers timeout 可能早于业务脚本期望的长生图等待窗口触发,表现上容易被误判成 VectorEngine 上游本身超时。
|
||||
- 处理:长期脚本优先复用后端 reqwest 或项目已有生成脚本;临时本地工具若必须用 Node,可改用原生 `http`/`https.request` 并显式设置 socket timeout,或为 Undici 单独配置 headers timeout。仍需隐藏 `VECTOR_ENGINE_API_KEY`,只报告配置是否存在。
|
||||
- 验证:同一 `gpt-image-2-all` 请求体、同一环境变量下,原生 HTTP 请求能返回 `url` / `b64_json` 并落盘;失败时错误里能区分请求发送、首部等待、下载和解码阶段。
|
||||
- 验证:同一 `gpt-image-2` 请求体、同一环境变量下,原生 HTTP 请求能返回 `url` / `b64_json` 并落盘;失败时错误里能区分请求发送、首部等待、下载和解码阶段。
|
||||
- 关联:`.codex/skills/gpt-image-2-apimart/SKILL.md`、`server-rs/crates/api-server/src/openai_image_generation.rs`。
|
||||
|
||||
## 旧后端路线文档造成判断漂移
|
||||
@@ -575,6 +936,7 @@
|
||||
- 现象:前端登录成功后进入推荐页,推荐页自动加载出一个作品,随后瞬间回到未登录;停留在其他页面或推荐页没加载出作品时不复现。
|
||||
- 原因:推荐页 embedded 运行态会自动发起受保护写请求。若这些卡片级后台请求遇到 `401` 或 refresh 失败,默认请求层曾清空 access token 并广播全局 auth 事件,导致 `AuthGate` 重新 hydrate 成未登录态。更隐蔽的是,`refreshAccessToken()` 自身曾在 refresh 失败时静默清 token,即便调用方关闭了 `clearAuthOnUnauthorized`,也可能让后续 hydrate 变成未登录。
|
||||
- 处理:请求层统一使用 `authImpact: 'global' | 'local'` 区分账号权威请求与局部后台请求;推荐页自动运行态、图片换签、公开拼图运行态和平台 bootstrap 私有投影刷新统一使用 `BACKGROUND_AUTH_REQUEST_OPTIONS` / `RUNTIME_BACKGROUND_AUTH_OPTIONS`,并等 `canReadProtectedData` 为 true 后再启动;用户主动点击的账号动作仍保留默认全局鉴权失败处理。
|
||||
- 追加处理:推荐页嵌入运行态要按真实身份分流,已登录或已有 access token 时继续走账号 Bearer + local auth impact,不能误带 runtime guest token;只有匿名访客才申请并透传 runtime guest token。
|
||||
- 追加处理:generated 私有图片换签 `/api/assets/read-url` 也属于展示层后台请求;推荐页拼图运行态挂载后会立即解析封面图,若换签 401 触发全局鉴权事件,也会表现成“进入拼图作品后瞬间未登录”。资源换签失败只应让当前图片为空,不应清 token、广播 auth 事件或主动 refresh。
|
||||
- 追加处理:从推荐页点进公开拼图作品并启动完整运行态后,`startPuzzleRun`、通关自动 `submitPuzzleLeaderboard`、下一关 `advancePuzzleNextLevel` 和重开同样属于当前玩法局部同步;这些请求失败时只应留在拼图错误态,不应清 token 或广播 auth 事件。
|
||||
- 追加处理:通关后 `refreshSaveArchives()`、首屏 bootstrap 的个人看板/作品架/浏览历史读写也只是平台投影刷新,失败应显示局部错误,不能充当全局登录态判定。
|
||||
@@ -591,9 +953,9 @@
|
||||
|
||||
## 推荐页未登录入口误打开公开详情
|
||||
|
||||
- 现象:新用户默认在发现页,但点击推荐页或推荐封面后,如果复用公开作品详情入口,可能绕过推荐页“登录后游玩”的产品门禁。
|
||||
- 现象:新用户默认在发现页,但点击推荐页或推荐封面后,如果复用公开作品详情入口,可能绕过推荐页沉浸运行态,打开普通公开详情页。
|
||||
- 原因:`RpgEntryHomeView` 曾只有 `onOpenGalleryDetail` 一个回调,同时服务发现页公开详情和推荐页作品入口;一旦为发现页保留公开浏览能力,推荐页也会跟着打开详情。
|
||||
- 处理:公开详情与推荐页入口分离为 `onOpenGalleryDetail` 和 `onOpenRecommendGalleryDetail`。发现页、搜索和排行榜保留公开详情;推荐 Tab、推荐封面、推荐运行态错误重试和桌面推荐模块统一走登录门禁。未登录推荐页只显示封面,点击封面只弹登录窗,不携带登录后自动打开详情的回调。
|
||||
- 处理:公开详情与推荐页入口分离为 `onOpenGalleryDetail` 和 `onOpenRecommendGalleryDetail`。发现页、搜索和排行榜保留公开详情;推荐 Tab、推荐封面、推荐运行态错误重试和桌面推荐模块走推荐运行态入口,不再主动弹登录窗。登录门禁只保留给创作、个人作品、删除、发布、Remix 等账号或所有权动作。
|
||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "logged out recommend"`。
|
||||
- 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`docs/technical/AUTH_RESTORE_AND_RECOMMEND_LOADING_FIX_2026-05-09.md`。
|
||||
|
||||
@@ -732,7 +1094,7 @@
|
||||
|
||||
- 现象:生产发布、数据库导入导出、服务器配置、构建或 `Genarrative-Full-Build-And-Deploy` 流水线执行 `GitSCM checkout` 时,如果 Jenkins 生成的 fetch 是 `+refs/heads/*:refs/remotes/origin/*`,公网 Git 链路可能在收包阶段以 `git-remote-https died of signal 15`、`curl 56 GnuTLS recv error (-9)`、`early EOF`、`invalid index-pack output` 失败;发布类流水线还可能先遇到 `http://127.0.0.1:3000/GenarrativeAI/Genarrative.git` 不可达。
|
||||
- 原因:`127.0.0.1` 只代表当前执行阶段的 agent 自身;当 release agent 与 Git 服务不在同一台机器,或本机 Git/Web 服务临时不可用时,固定写死 localhost 会阻断 Jenkinsfile 内部源码/脚本 checkout。即使只使用域名 Git,如果 `GitSCM` 没有显式 refspec 并开启 `CloneOption honorRefspec=true`,Jenkins Git 插件也会拉取所有分支。
|
||||
- 处理:Jenkins Job 的 `Pipeline script from SCM` 由 Windows controller 执行,SCM URL 使用公网域名 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`。运行于 Linux agent 的 Jenkinsfile 首次 `checkout([$class: 'GitSCM', ...])` 层先尝试 `GIT_REMOTE_URL=http://127.0.0.1:3000/GenarrativeAI/Genarrative.git`,失败后直接尝试 `GIT_REMOTE_FALLBACK_URL=https://git.genarrative.world/GenarrativeAI/Genarrative.git`,不再配置内网 IP fallback;所有生产 Jenkinsfile 的首次 checkout 都必须使用目标分支 refspec、`CloneOption shallow=true depth=1 noTags=true honorRefspec=true`。后续统一走 `scripts/jenkins-checkout-source.sh`,该脚本也按主地址、域名备用地址顺序重新 fetch 并把 `origin` 切到实际可用地址;`COMMIT_HASH` 为空时继续 `--depth=1 --no-tags`,只有指定 commit 时才允许加深历史做分支归属校验。
|
||||
- 处理:Jenkins Job 的 `Pipeline script from SCM` 由 Windows controller 执行,SCM URL 使用公网域名 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`。运行于 `linux && genarrative-build` 的 `Genarrative-Full-Build-And-Deploy` 源码解析阶段、`Genarrative-Web-Build` checkout 阶段,以及部署/发布类 Linux agent 的 Jenkinsfile 首次 `checkout([$class: 'GitSCM', ...])` 层先尝试 `GIT_REMOTE_URL=http://127.0.0.1:3000/GenarrativeAI/Genarrative.git`,失败后直接尝试 `GIT_REMOTE_FALLBACK_URL=https://git.genarrative.world/GenarrativeAI/Genarrative.git`,不再配置内网 IP fallback;这些首次 checkout 都必须使用目标分支 refspec、`CloneOption shallow=true depth=1 noTags=true honorRefspec=true`。后续统一走 `scripts/jenkins-checkout-source.sh`,该脚本也按主地址、域名备用地址顺序重新 fetch 并把 `origin` 切到实际可用地址;`COMMIT_HASH` 为空时继续 `--depth=1 --no-tags`,只有指定 commit 时才允许加深历史做分支归属校验。
|
||||
- 验证:扫描本地 Jenkins live job `config.xml`,确认 SCM `<url>` 都是 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`;扫描所有生产 Jenkinsfile 的首次 `GitSCM checkout`,确认 `userRemoteConfigs` 带 `+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}`,`CloneOption` 带 `honorRefspec: true`;运行 `bash -n scripts/jenkins-checkout-source.sh`。
|
||||
- 关联:`jenkins/Jenkinsfile.production-full-build-and-deploy`、`jenkins/Jenkinsfile.production-web-build`、`jenkins/Jenkinsfile.production-api-build`、`jenkins/Jenkinsfile.production-stdb-module-build`、`jenkins/Jenkinsfile.production-web-deploy`、`jenkins/Jenkinsfile.production-api-deploy`、`jenkins/Jenkinsfile.production-stdb-module-publish`、`jenkins/Jenkinsfile.production-server-provision`、`jenkins/Jenkinsfile.production-database-export`、`jenkins/Jenkinsfile.production-database-import`、`scripts/jenkins-checkout-source.sh`、`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。
|
||||
|
||||
@@ -776,6 +1138,22 @@
|
||||
- 验证:`PuzzleResultView` 单测覆盖发布弹窗内展示 `泥点余额不足`。
|
||||
- 关联:`src/components/puzzle-result/PuzzleResultView.tsx`、`docs/technical/PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md`、`docs/technical/ASSET_GENERATION_POINTS_CONSUMPTION_2026-04-27.md`。
|
||||
|
||||
## 拼图发布检查阶段会在事件落库时炸 wasm
|
||||
|
||||
- 现象:拼图发布在“发布检查”环节直接报 `The module instance encountered a fatal error`,wasm backtrace 指向 `spacetime_module::puzzle::publish_puzzle_work`,并停在 `procedure_commit_mut_tx` 的 commit 阶段。
|
||||
- 原因:`publish_puzzle_work_tx` 会无条件调用 `emit_puzzle_work_published_event` 写入 `puzzle_event`;该表的 `event_id` 是主键,而事件 ID 由 `profile_id + published_at_micros` 组成。只要同一发布动作被重复执行、重放,或极端情况下发生时间戳碰撞,commit 时就会因主键冲突触发 fatal error。
|
||||
- 处理:待修复。发布事件写入需要改成幂等,或在重复发布时显式跳过已存在的 `event_id`;发布动作本身也应补一层更明确的幂等键,避免把重复提交直接推到事务提交阶段。
|
||||
- 验证:对同一 `session_id/profile_id/published_at_micros` 重复调用 `publish_puzzle_work` 时,不应再在 commit 阶段炸 wasm;正常发布仍应生成作品、更新 session,并可进入公开详情。
|
||||
- 关联:`server-rs/crates/spacetime-module/src/puzzle.rs`、`server-rs/crates/api-server/src/puzzle/handlers.rs`、`server-rs/crates/spacetime-client/src/module_bindings/puzzle_event_table.rs`。
|
||||
|
||||
## 拼图会过早进入待发布态,结果页可能空图但仍显示可发布
|
||||
|
||||
- 现象:拼图创作有时刚结束就跳到“待发布”结果页,但结果页里的正式图还是空的,发布检查随后又会拦住,用户会感觉“已经完成了却又不能发布”。
|
||||
- 原因:拼图的待发布判定太弱,`build_result_preview` / `validate_publish_requirements` 和 `is_puzzle_session_snapshot_publish_ready` 只检查了作品名、简介、标签、关卡名和 cover 图,没有要求 `level_scene_image_src`、`ui_spritesheet_image_src`、`level_background_image_src` 等完整资产都齐;前端恢复链路里的 `hasRecoverableGeneratedPuzzleDraft` / `normalizeRecoveredPuzzleDraftSession` 也只要有 cover 或候选图就会把草稿当成已完成。
|
||||
- 处理:待修复时要把“待发布”门槛收紧到整套拼图资产包完整,再让恢复逻辑只在完整草稿下抬高为完成态,避免半成品直接进入结果页。
|
||||
- 验证:当某个拼图草稿只补齐首图、但关卡背景或 UI spritesheet 仍缺失时,不应再进入 `ready_to_publish`;结果页也不应把这类草稿误判为已完成。
|
||||
- 关联:`server-rs/crates/module-puzzle/src/application.rs`、`server-rs/crates/api-server/src/puzzle/tags.rs`、`server-rs/crates/api-server/src/puzzle/draft.rs`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/puzzle-result/PuzzleResultView.tsx`。
|
||||
|
||||
## WebGL 画布在高 DPR 移动端放大溢出
|
||||
|
||||
- 现象:抓大鹅试玩入口进入后,3D 锅体和物体从中心圆形区域向右下溢出,顶部状态和底部备选栏也可能看起来被右侧裁切。
|
||||
@@ -796,7 +1174,7 @@
|
||||
|
||||
- 现象:修改抓大鹅素材时容易沿用旧 Rodin/GLB 方案,导致新草稿生成耗时变长、进度停在模型阶段,或运行态等待不存在的 GLB。
|
||||
- 原因:仓库里保留了 Hyper3D 通用代理和历史模型字段,旧文档也曾要求草稿阶段同步生成 GLB。当前产品口径已经改为 2D 多视角素材。
|
||||
- 处理:新 `match3d_compile_draft` 与批量新增只生成 2D 图片:每个物品 5 个视角,单张 1K 素材图固定 5x5,最多承载 5 个物品,一行对应一个物品,不足 5 个物品也补齐到完整 5 行;超过 5 个物品自动分批并行生图。素材图 prompt 固定要求纯绿色绿幕背景,切割前先把绿幕处理为透明 alpha,再做格内内容前景边界校准并带留白,避免固定内缩切掉贴近格线的主体。`generatedItemAssets[].status` 使用 `image_ready`,发布校验看 `imageViews[]` 或首图引用。`generated-models` 仅用于历史外部模型链接转存,不能作为新生产链路。
|
||||
- 处理:新 `match3d_compile_draft` 与批量新增只生成 2D 图片:每个物品 5 个形态,单张 `2K 1:1` 物品 spritesheet 固定 `10*10`,每行承载两种物品、每种五个形态,单张最多承载 20 种物品。素材图 prompt 固定要求纯绿色绿幕背景,上传 OSS 前先把整张 spritesheet 绿幕处理为透明 alpha,再由运行态和编辑器按 alpha 连通域解析;`generatedItemAssets[].status` 使用 `image_ready`,发布校验看 `imageViews[]`、首图引用或可解析的物品 spritesheet。`generated-models` 仅用于历史外部模型链接转存,不能作为新生产链路。
|
||||
- 验证:`cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml`、`npm run test -- src\services\miniGameDraftGenerationProgress.test.ts src\components\match3d-result\Match3DResultView.test.tsx src\components\match3d-runtime\Match3DRuntimeShell.test.tsx`。
|
||||
- 关联:`server-rs/crates/api-server/src/match3d.rs`、`src/components/match3d-runtime/Match3DRuntimeShell.tsx`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
||||
|
||||
@@ -832,11 +1210,11 @@
|
||||
- 验证:执行 `npm run test -- src/components/match3d-runtime/Match3DRuntimeShell.test.tsx` 和 `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "Match3D runtime"`;浏览器 Network 中背景和容器 generated path 应先请求 `/api/assets/read-url` 换签,局内出现 `match3d-background-image` 和 `match3d-container-image` 对应图片。
|
||||
- 关联:`src/components/match3d-runtime/Match3DRuntimeShell.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/rpgEntryWorldPresentation.ts`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
||||
|
||||
## 抓大鹅容器参考图必须走 edits 并接管棋盘外观
|
||||
## 抓大鹅容器参考图必须进入 edits multipart image 并接管棋盘外观
|
||||
|
||||
- 现象:抓大鹅结果页看似有容器生成入口,但真实生成出的局内容器不像 `pot-fused-reference.png`,或进入试玩后仍被默认圆形锅壳、金色边框和径向底色覆盖/裁切。
|
||||
- 原因:`/v1/images/generations` 的 `image` 数组更适合弱参考文生图,难以稳定锁定大尺寸轻俯视容器构图;即使生成了容器图,如果运行态继续保留默认 `rounded-full` 锅壳和 `overflow-hidden`,生成图也会被默认视觉覆盖或裁掉。
|
||||
- 处理:抓大鹅 `1:1` 容器 UI 图必须用 VectorEngine `POST /v1/images/edits` multipart,把 `public/match3d-background-references/pot-fused-reference.png` 作为 `image` part 上传;共享 GPT-image-2 HTTP client 承载 multipart 时强制 HTTP/1.1。`Match3DRuntimeShell` 在容器图换签并成功加载后,把棋盘外壳切为透明和 `overflow-visible`,只在容器缺失或加载失败时使用默认圆形容器。
|
||||
- 原因:容器参考图必须进入 `gpt-image-2` `/v1/images/edits` multipart `image` part,并配合强 prompt 锁定大尺寸轻俯视容器构图;即使生成了容器图,如果运行态继续保留默认 `rounded-full` 锅壳和 `overflow-hidden`,生成图也会被默认视觉覆盖或裁掉。
|
||||
- 处理:抓大鹅 `1:1` 容器 UI 图统一调用 VectorEngine `POST /v1/images/edits`,参考 `public/match3d-background-references/pot-fused-reference.png` 的透明容器图由后端作为 `image` part 上传;该参考图属于后端生图协议输入,需通过 `include_bytes!` 编译进 `api-server`,不能在运行时按当前工作目录读取 `public/`。`Match3DRuntimeShell` 在容器图换签并成功加载后,把棋盘外壳切为透明和 `overflow-visible`,只在容器缺失或加载失败时使用默认圆形容器。
|
||||
- 验证:执行 `cargo test -p api-server vector_engine --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server match3d_background --manifest-path server-rs/Cargo.toml`、`npm run test -- src/components/match3d-runtime/Match3DRuntimeShell.test.tsx src/components/match3d-result/Match3DResultView.test.tsx`;真实联调看容器生成请求是否命中 `/v1/images/edits`,局内 `match3d-container-image` 是否渲染且 `match3d-board` 不再含默认 `rounded-full`。
|
||||
- 关联:`server-rs/crates/api-server/src/openai_image_generation.rs`、`server-rs/crates/api-server/src/match3d.rs`、`src/components/match3d-runtime/Match3DRuntimeShell.tsx`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
||||
|
||||
@@ -863,6 +1241,14 @@
|
||||
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx` 覆盖抓大鹅和拼图生成后自动试玩 / 返回结果页。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
||||
|
||||
## 拼图最后一步到 100% 但不变绿优先看阶段映射
|
||||
|
||||
- 现象:拼图草稿生成跑完所有步骤后,总进度仍停在 98%,最后一步“写入正式草稿”显示 100% 但卡片不变绿,视觉上像还在进行中。
|
||||
- 原因:进度条总进度刻意保留 98% 作为未收到 action 回包前的安全余量,但最后一步的绿色完成态只看步骤状态;如果时间轴已经跑到 `puzzle-select-image` 末尾却还没收到 `ready` 回包,最后一步会一直保持 active。
|
||||
- 处理:`buildMiniGameDraftGenerationProgress` 需要在拼图最后一步时,把“预计写入时长已耗尽”单独判为 completed,避免出现“进行中 100%”。
|
||||
- 验证:`npm test -- src/services/miniGameDraftGenerationProgress.test.ts`。
|
||||
- 关联:`src/services/miniGameDraftGenerationProgress.ts`、`src/services/miniGameDraftGenerationProgress.test.ts`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 微信支付回调验签不要用商户私钥
|
||||
|
||||
- 现象:微信小程序支付下单能返回 `prepay_id`,但真实支付通知验签失败,或者本地实现误把商户 API 私钥当作回调验签 key。
|
||||
@@ -914,9 +1300,9 @@
|
||||
|
||||
## 抓大鹅难度配置的物品种类和消除次数必须分离
|
||||
|
||||
- 现象:历史草稿选择标准 / 硬核难度后,系统可能把 `clearCount` 当成局内物品种类数量,导致标准需要 12 种、硬核需要 20/21 种;素材不足时发布或试玩行为不一致。
|
||||
- 现象:历史草稿选择标准 / 硬核难度后,系统可能把 `clearCount` 当成局内物品种类数量,导致标准需要 12 种、硬核需要 20/21 种;或者把第 11 到 20 个物品持久化为第 11 到 20 行,触发“系列素材图集持久化的行列索引必须落在 n*n 范围内”。
|
||||
- 原因:旧运行态把消除次数和类型数量绑在一起,结果页文案又同时展示“素材图片 / 局内类型”,导致前端、发布校验和 run start 口径不一致。
|
||||
- 处理:统一使用 `物品种类` 口径:轻松 3、标准 9、进阶 15、硬核 21;历史 `clearCount=20` 且难度为硬核的运行态按新硬核升为 21 组三消,避免 20 组却要求 21 种素材。发布前按 `image_ready` 且有 `imageViews[]` 或 `imageSrc/imageObjectKey` 的生成素材数量阻断不足难度;试玩不阻断,但通过 `itemTypeCountOverride` 自动降到已生成 2D 素材数量。重启从已有 run 快照反推实际物品种类,保持同一局重开不变。
|
||||
- 处理:生成和持久化固定使用 20 个物品素材;运行态物品种类口径为轻松 3、标准 9、进阶 15、硬核 20,历史 `clearCount=20` 且难度为硬核的运行态仍可升为 21 组三消,但类型池不超过 20。10*10 sheet 每行两种物品、每种五个形态,持久化行列为 `row = itemIndex / 2 + 1`、`col = itemIndex % 2 * 5 + viewIndex + 1`。发布前按 `image_ready` 且有 `imageViews[]` 或 `imageSrc/imageObjectKey` 的生成素材数量阻断不足难度;试玩不阻断,但通过 `itemTypeCountOverride` 自动降到已生成 2D 素材数量。重启从已有 run 快照反推实际物品种类,保持同一局重开不变。
|
||||
- 验证:`npm run test -- src\components\match3d-result\Match3DResultView.test.tsx`、`cargo test -p module-match3d --manifest-path server-rs\Cargo.toml`,涉及发布 reducer 时补跑 `cargo test -p spacetime-module match3d --manifest-path server-rs\Cargo.toml`。
|
||||
- 关联:`src/components/match3d-result/Match3DResultView.tsx`、`src/services/match3d-runtime/match3dRuntimeClient.ts`、`server-rs/crates/module-match3d/src/application.rs`、`server-rs/crates/spacetime-module/src/match3d.rs`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
||||
|
||||
@@ -933,7 +1319,7 @@
|
||||
- 现象:抓大鹅生成的物品视角图裁剪后仍带白边,或者整块纯绿色绿幕背景没有被透明化,运行态看到绿色方块。
|
||||
- 原因:素材 sheet 可能是“每格内部绿幕、整张图外圈近白底”,内部绿幕不一定连通到 sheet 外边缘;旧 flood fill 只从外边缘找背景会漏掉这种绿幕块。白底抗锯齿如果不纳入抠像和边缘去污染,也会随裁剪输出成一圈白边。即使顺序已是先整张 sheet 去绿再裁剪,较厚的半透明或混色软绿边仍可能低于高置信绿幕阈值,被当作前景带进独立 PNG。
|
||||
- 处理:`api-server` 的 `slice_match3d_material_sheet` 必须先在整张 sheet 上做透明背景后处理:外边缘连通绿幕/近白底清 alpha,非连通但高置信纯绿块也清 alpha,沿整张 sheet 透明背景继续吃掉软绿边,边缘近白和绿幕抗锯齿做透明或去污染;同时保护不够纯的绿色主体像素。不要改成先裁剪单格再去绿。
|
||||
- 验证:`cargo test -p api-server match3d_material_sheet_slicing --manifest-path server-rs\Cargo.toml` 覆盖非连通绿幕、白边、贴边主体保留和固定 5x5 切图。
|
||||
- 验证:`cargo test -p api-server match3d_material_sheet_slicing --manifest-path server-rs\Cargo.toml` 覆盖非连通绿幕、白边、贴边主体保留和固定 `10*10` 切图;`cargo test -p api-server match3d_spritesheet_green_screen_postprocess_turns_background_transparent --manifest-path server-rs\Cargo.toml` 覆盖完整 spritesheet 上传前绿幕透明化。
|
||||
- 关联:`server-rs/crates/api-server/src/match3d.rs`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
|
||||
|
||||
## 抓大鹅物品详情大方格只做单张大图查看
|
||||
@@ -1000,6 +1386,14 @@
|
||||
- 验证:运行 `npm run test -- src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx -t "拖拽合并大块时底层单格不显示选中色块"`,并确认合并块拖拽时底层 `[data-piece-id]` 仍为 `puzzle-runtime-piece--merged`。
|
||||
- 关联:`src/components/puzzle-runtime/PuzzleRuntimeShell.tsx`、`src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx`、`docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`。
|
||||
|
||||
## 推荐页嵌入拼图通关结算不要放在运行态内部 absolute 层
|
||||
|
||||
- 现象:推荐页里玩拼图通关后,结算面板只显示上半部分,排行榜、下一关按钮或相似作品卡被截断。
|
||||
- 原因:推荐页把运行态放在滑动作品卡的视觉区内,`platform-recommend-swipe-page`、`platform-recommend-swipe-card__visual` 和 `platform-recommend-runtime-viewport` 都是 `overflow: hidden`;拼图通关结算如果仍是运行态内部 `absolute inset-0` 弹层,就只能在半屏卡片区域里显示。
|
||||
- 处理:`PuzzleRuntimeShell` 在 `embedded` 模式下把通关结算层通过 portal 挂到 `document.body`,使用 `puzzle-runtime-modal-overlay--fixed` 页面级 fixed 浮层;非嵌入态继续使用运行态内部覆盖层。
|
||||
- 验证:运行 `npm run test -- src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx -t "推荐页嵌入拼图通关结算使用页面级浮层避免卡片裁剪"`,确认弹层不再位于 `.platform-recommend-runtime-viewport` 内。
|
||||
- 关联:`src/components/puzzle-runtime/PuzzleRuntimeShell.tsx`、`src/index.css`、`src/components/rpg-entry/RpgEntryHomeView.tsx`。
|
||||
|
||||
## 拼图历史图片列表不要把账号归属当图片名
|
||||
|
||||
- 现象:拼图创作页或结果页打开“选择历史图片”后,历史列表显示 `账号 user-1` 之类归属文案而不是图片名;`1713686400.000000Z` 这类时间显示为未知;选中后预览或生成参考图可能被怀疑不可用。
|
||||
@@ -1018,12 +1412,22 @@
|
||||
|
||||
## 拼图结果页局部生图不要污染草稿生成态
|
||||
|
||||
- 现象:拼图草稿已经生成完成后,在结果页重新生成 UI 背景或追加关卡生成图片,草稿页仍显示整卡“生成中”,点击草稿会回到生成过程页,无法查看已有结果;UI 背景生成中还会禁用“新增关卡”和关卡图生成。
|
||||
- 现象:拼图草稿已经生成完成后,在结果页重新生成关卡图片或追加关卡生成图片,草稿页仍显示整卡“生成中”,点击草稿会回到生成过程页,无法查看已有结果;关卡图片生成中还会禁用“新增关卡”和其它关卡详情编辑。
|
||||
- 原因:结果页局部 action 复用了全局 `isPuzzleBusy` / 持久化 `generationStatus=generating` 语义,作品架没有区分“初始草稿不可查看”和“已有结果上的局部关卡生成”。
|
||||
- 处理:作品架只在拼图没有可用封面、首关候选图或任一可查看关卡时才把 `generationStatus=generating` 解释为初始草稿生成;结果页 UI 背景和关卡图走 background action,不设置全局 busy,UI 背景只禁用自己的按钮;SpacetimeDB/API mapper 读写时把已有图片但状态仍是 `generating` 的历史关卡归一为 `ready`。
|
||||
- 处理:作品架只在拼图没有可用封面、首关候选图或任一可查看关卡时才把 `generationStatus=generating` 解释为初始草稿生成;结果页关卡图走 background action,不设置全局 busy,只标记对应关卡局部生成进度;SpacetimeDB/API mapper 读写时把已有图片但状态仍是 `generating` 的历史关卡归一为 `ready`。
|
||||
- 验证:`npm run test -- src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`、`cargo test -p api-server puzzle --manifest-path server-rs\Cargo.toml`。
|
||||
- 关联:`src/components/custom-world-home/creationWorkShelf.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/puzzle-result/PuzzleResultView.tsx`、`server-rs/crates/api-server/src/puzzle/mappers.rs`、`server-rs/crates/spacetime-module/src/puzzle.rs`。
|
||||
|
||||
2026-05-22 补充:结果页关卡详情的“关卡测试”不能把单关 `draft` 传给父级再调用 `updatePuzzleWork`。`updatePuzzleWork` 会同步 `puzzle_work_profile.levels_json` 和 source session 草稿,单关快照会把整份多关卡草稿覆盖成一个关卡,退出重进后只剩最后测试的关卡且序号表现为第一关。修复口径是 `PuzzleResultView` 始终传完整 `syncedDraft`,额外用 `{ levelId }` 指定起始关卡;父级持久化完整 levels 后调用 `startLocalPuzzleRun(item, levelId)`。
|
||||
|
||||
## 拼图上传图关闭 AI 重绘不要走首图生图
|
||||
|
||||
- 现象:用户在拼图入口页或结果页关卡详情上传图片并关闭 AI 重绘后,生成页仍显示“生成拼图首图”,或者后端仍调用 `generate_puzzle_image_candidates` 生成第一张 1:1 候选图。
|
||||
- 原因:上传图直用路径应把 Data URL 或 `/generated-*` 历史图解析后持久化为 `sourceType=uploaded` 的正式候选,再继续生成 9:16 关卡画面、UI spritesheet 和纯背景;如果只把 `aiRedraw=false` 当作“不参考图片生成”,就会误走首图生成。
|
||||
- 处理:入口页用 payload 的 `aiRedraw` 写入生成页 metadata,`puzzleAiRedraw=false` 时进度跳过 `生成拼图首图`;后端 `compile_puzzle_draft` 和结果页 `generate_puzzle_images` 都在 `aiRedraw=false && referenceImageSrc 非空` 时走上传图直用候选。结果页关卡详情必须复用 `CreativeImageInputPanel`,不要把正式图当成可重绘参考图;本次上传或历史选择的图才显示 AI 重绘开关并可删除。
|
||||
- 验证:`npm run test -- src/services/miniGameDraftGenerationProgress.test.ts src/components/puzzle-result/PuzzleResultView.test.tsx`、`cargo test -p api-server puzzle_result_level_direct_upload_skips_cover_image_generation --manifest-path server-rs\Cargo.toml`。
|
||||
- 关联:`src/services/miniGameDraftGenerationProgress.ts`、`src/components/puzzle-agent/PuzzleAgentWorkspace.tsx`、`src/components/puzzle-result/PuzzleResultView.tsx`、`server-rs/crates/api-server/src/puzzle/draft.rs`、`server-rs/crates/api-server/src/puzzle/generation.rs`。
|
||||
|
||||
## Jenkins 数据库导入导出脚本先补 Node 工具链 PATH
|
||||
|
||||
- 现象:`Genarrative-Database-Import` 或 `Genarrative-Database-Export` 运行到迁移脚本时,`bash` 报 `node: command not found`,常见在日志里表现为某个 `sh` 块内第 61 行直接调用 `node` 失败。
|
||||
@@ -1056,6 +1460,14 @@
|
||||
- 验证:Jenkins 目标机日志不再出现 `unexpected argument '-y'`、`unknown command name for spacetimedb-update multicall binary`,后续应继续检查 `bin/current/spacetimedb-cli` 和 `bin/current/spacetimedb-standalone` 是否生成。
|
||||
- 关联:`scripts/prepare-server-provision-tools.sh`、`jenkins/Jenkinsfile.production-server-provision`。
|
||||
|
||||
## 清库重建后先查 schema 兼容再重启
|
||||
|
||||
- 现象:`npm run dev -- --clear-database --no-interactive` 之后,api-server 仍在 `GET /api/creation-entry/config` 或订阅恢复阶段报 `No such procedure` / schema guard 失败。
|
||||
- 原因:本地重建只会重发当前 `spacetime-module`,不会自动修正旧迁移 JSON 的字段兼容;如果 `migration.rs` 没把新字段补成 `None` / 默认值,清库后重建仍会卡在 schema 同步。
|
||||
- 处理:先让 `server-rs/crates/spacetime-module/src/migration.rs`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` 和生成绑定对齐,再执行清库重建。
|
||||
- 验证:`npm run check:spacetime-schema` 先通过,再重启 `npm run dev -- --clear-database --no-interactive`,最后检查 `/v1/ping`、`/healthz` 和 `GET /api/creation-entry/config`。
|
||||
- 关联:`server-rs/crates/spacetime-module/src/migration.rs`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`scripts/dev.mjs`。
|
||||
|
||||
## QQ 浏览器发现页推荐封面全不显示先查 aspect-ratio 兜底
|
||||
|
||||
- 现象:发现页的“推荐”子频道作品卡标题、作者和数据正常,但所有封面图不显示,常见于 QQ 浏览器 / X5 等旧移动内核。
|
||||
@@ -1064,10 +1476,134 @@
|
||||
- 验证:`npm run test -- src/components/rpg-entry/rpgEntryWorldPresentation.test.ts src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`npm run typecheck`、`npm run check:encoding`。
|
||||
- 关联:`src/index.css`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/rpg-entry/rpgEntryWorldPresentation.ts`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 生成中草稿刷新后不要只恢复作品架遮罩
|
||||
## 生成中草稿刷新后不要复用旧 updatedAt 当展示起点
|
||||
|
||||
- 现象:拼图或抓大鹅草稿生成中刷新网页后,作品架卡片能显示等待遮罩,但点击卡片会走普通草稿恢复,可能进入空白结果页或未完成工作区。
|
||||
- 原因:前端只把内存 notice 当作“生成中点击恢复”的判断条件,没有把后端摘要里的 `generationStatus=generating` 纳入同一路径。
|
||||
- 处理:打开草稿时把持久化 `generationStatus=generating` 等同于生成中 notice,恢复对应玩法生成进度页;恢复计时使用作品摘要 `updatedAt` 推导 `startedAtMs`。
|
||||
- 验证:`npm test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating"`。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
- 现象:拼图或抓大鹅草稿生成中刷新网页后,作品架卡片能显示等待遮罩,但进入生成页时总进度首帧直接跳到 80%+,看起来像已经跑了一大半。
|
||||
- 原因:前端只把持久化 `generationStatus=generating` 当作恢复生成页的条件,但恢复展示时仍沿用了作品摘要 `updatedAt` 作为伪 `startedAtMs`;同时拼图总进度又把后端 `progressPercent` 直接当作 floor,导致 `86%` 之类未到首个里程碑的会话一进页就抬到 80%+。
|
||||
- 处理:恢复生成中的草稿时,展示起点改用“进入生成页的当前时间”;`updatedAt` 只保留给作品架排序和摘要,不再参与生成页假进度起算。拼图总进度还要忽略 `88` 以下的后端进度 floor,拼图保留后端里程碑推进,抓大鹅等非拼图玩法则从 `0%` 平滑起步,避免刚进页就看到 4% / 88% / 80%+。
|
||||
- 验证:`npm test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating"`、`npm run test -- src/services/miniGameDraftGenerationProgress.test.ts -t "match3d draft generation starts total progress from zero"`。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`src/services/miniGameDraftGenerationProgress.ts`、`docs/【玩法创作】拼图生成页进度口径-2026-05-23.md`。
|
||||
|
||||
## 汪汪声浪草稿试玩不要写正式 run
|
||||
|
||||
- 现象:如果草稿结果页试玩和发布后 runtime 共用同一写成绩路径,未发布或未确认资源的草稿试玩会污染正式单局、排行榜和作品统计。
|
||||
- 原因:`BarkBattleRuntimeShell` 同时承担草稿预览和发布后运行态,需要由调用方显式传入 `runtimeMode` 区分是否写正式 run。
|
||||
- 处理:草稿结果页试玩保持 `runtimeMode=draft`,只做本地预览;发布成功后先进入 `/works/detail?work=BB-xxxxxxxx`,再从详情页以 `runtimeMode=published` 进入正式 runtime,并在开始/结算时分别调用 `startBarkBattleRun` 与 `finishBarkBattleRun`。
|
||||
- 验证:草稿试玩不触发 start / finish run;正式 runtime 必须先通过麦克风授权,再写 start run 和结算派生指标。
|
||||
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx`、`src/services/bark-battle-runtime/barkBattleRuntimeClient.ts`。
|
||||
|
||||
## 汪汪声浪移动端创作表单不要再套一层纵向滚动
|
||||
|
||||
- 现象:移动端创作 Tab 里进入汪汪声浪表单后,页面右侧出现不自然的内层滚动条,最后的形象描述输入框容易被“生成草稿”按钮、键盘或底部 TabBar 挤压 / 遮挡;顶部玩法卡首尾也可能贴边显得被裁。
|
||||
- 原因:外层 `.platform-tab-panel` 已经是纵向滚动容器,创作页中间又有多层 `overflow-hidden`,旧的 `BarkBattleConfigEditor` 根节点再加 `overflow-y-auto`,形成外层 Tab 面板 + 内层表单的套滚动;底部按钮只预留 safe-area,不预留真实操作区距离;顶部玩法卡横向滚动条隐藏且首尾没有 scroll padding。
|
||||
- 处理:移动端让 Bark Battle 表单跟随父级滚动,`lg` 以上才恢复表单内滚动;创作页容器移动端使用 `overflow-visible` 和 safe-area 底部 padding;顶部模板 tablist 加 `scroll-px-3` / 横向 padding,移动端卡片宽度收窄,避免首尾 ring 和圆角贴边裁切。
|
||||
- 验证:`npm run test -- src/components/bark-battle-creation/BarkBattleConfigEditor.test.tsx`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "create tab shows template tabs"`、移动端视口检查最后一个输入框与“生成草稿”按钮不重叠。
|
||||
- 关联:`src/components/bark-battle-creation/BarkBattleConfigEditor.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 汪汪声浪拟声词不要被默认狗主题锁死
|
||||
|
||||
- 现象:创作者把主题或形象改成机甲、猫、骑士等非狗主题后,局内仍播放 `轰汪!`、`汪爆!` 这类狗叫词,表现像系统强行把主题带回狗。
|
||||
- 原因:拟声词 textarea 如果一开始就填入默认小狗词池,并且始终作为自定义 `onomatopoeia` 提交,runtime 会优先使用该字段,无法再根据新的 `themeDescription` / `playerImageDescription` / `opponentImageDescription` 走主题 fallback。
|
||||
- 处理:`BarkBattleConfigEditor` 需要区分“系统默认词池”和“创作者已手动编辑”。未手动编辑时随主题 / 形象描述自动重算;手动编辑后才冻结为自定义词池。默认词池只在命中狗相关关键词时加入狗叫词,非狗主题使用科技、幻想或通用高能词。
|
||||
- 验证:`npm run test -- src/components/bark-battle-creation/BarkBattleConfigEditor.test.tsx src/games/bark-battle/ui/__tests__/BarkBattleRuntimeShell.test.tsx`,并确认非狗主题的拟声词不含 `汪`。
|
||||
- 关联:`src/components/bark-battle-creation/BarkBattleConfigEditor.tsx`、`src/games/bark-battle/application/BarkBattleConfig.ts`、`src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx`。
|
||||
|
||||
## Jenkins Web 构建公开作品号导出缺失优先补 publicWorkCode
|
||||
|
||||
- 现象:`Genarrative-Web-Build` 在 `npm run build:production-release -- --component web` 阶段失败,Rollup 报 `"buildJumpHopPublicWorkCode" is not exported by "src/services/publicWorkCode.ts"`,但导入方 `rpgEntryWorldPresentation.ts` 或 `PlatformEntryFlowShellImpl.tsx` 已经引用该玩法公开码函数。
|
||||
- 原因:玩法分支合并时容易只带入新玩法的 `publicWorkCode.ts` 导出,覆盖或遗漏另一个玩法的公开码 builder / matcher,Vite 构建会在静态导出检查阶段直接失败。
|
||||
- 处理:在 `src/services/publicWorkCode.ts` 中保持每个玩法的 `build<Play>PublicWorkCode` 与 `isSame<Play>PublicWorkCode` 成对导出;跳一跳使用 `JH-` 前缀和 profileId 后 8 位规范化后缀。补 `src/services/publicWorkCode.test.ts` 覆盖 builder 和 matcher,避免后续合并再次丢失导出。
|
||||
- 验证:`npm test -- src/services/publicWorkCode.test.ts`,并用 `npm run build:production-release -- --component web --name <临时名>` 复现 Jenkins web 构建路径。若 `npm run typecheck` 仍报 JumpHop 阶段或状态变量缺口,那是远端当前 JumpHop 接线未收齐的独立问题,不等同于该 Rollup 导出失败。
|
||||
- 关联:`src/services/publicWorkCode.ts`、`src/components/rpg-entry/rpgEntryWorldPresentation.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 跳一跳前端壳层接线不要只合渲染分支
|
||||
|
||||
- 现象:`npm run typecheck` 大量报 `setJumpHopSession`、`jumpHopRun`、`jumpHopGalleryEntries`、`mapJumpHopWorkToPublicWorkDetail` 不存在,以及 `"jump-hop-runtime" is not assignable to SelectionStage`;即使 typecheck 过了,分享或刷新 `/runtime/jump-hop?work=...` 仍可能掉回首页。
|
||||
- 原因:跳一跳工作台、生成页、结果页、runtime 和推荐流渲染分支已经合入 `PlatformEntryFlowShellImpl.tsx`,但平台壳层状态、public detail mapper、`SelectionStage` union 与 `appPageRoutes.ts` 阶段路由映射没有一并合入;发现页卡片分类也没有先判断 `isJumpHopGalleryEntry`,导致 fallback 访问 RPG `themeMode`。
|
||||
- 处理:`platformEntryTypes.ts` 必须注册 `jump-hop-workspace/generating/result/runtime/gallery-detail`;`appPageRoutes.ts` 必须补 `/creation/jump-hop/workspace`、`/creation/jump-hop/generating`、`/creation/jump-hop/result`、`/gallery/jump-hop/detail`、`/runtime/jump-hop`;`PlatformEntryFlowShellImpl.tsx` 必须持有 JumpHop session/work/run/gallery/runtimeReturnStage/generationState/error/busy,并提供 `mapJumpHopWorkToPublicWorkDetail`;`RpgEntryHomeView.tsx` 的公开卡片类型描述要给 JumpHop 单独返回 `跳一跳`。
|
||||
- 验证:`npm run typecheck`,并跑 `npm test -- src/routing/appPageRoutes.test.ts` 覆盖 JumpHop 阶段路径。
|
||||
- 关联:`src/components/platform-entry/platformEntryTypes.ts`、`src/routing/appPageRoutes.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 跳一跳地块图集不要套通用系列素材 n 行模型
|
||||
|
||||
- 现象:跳一跳初始草稿生成时报 `系列素材图集的物品行数不能超过 n。`,或者即使绕过报错也只生成了 atlas 预览路径,地块切片没有真正落盘。
|
||||
- 原因:跳一跳地块只有 6 个固定 tileType,但旧实现把它塞进通用系列素材 helper,并使用 `grid_size = 3` / `item_names = 6` 的语义冲突模型;随后又只保留 atlas 资产与模拟路径,没把六个切片逐一上传并确认到 `JumpHopTileAsset`。
|
||||
- 处理:跳一跳地块改用专用 `2行*3列` 图集 prompt,按 `start / normal / target / finish / bonus / accent` 顺序切 6 张 PNG,并对每张切片各自走 OSS 上传、asset_object 确认和 entity bind。
|
||||
- 验证:`cargo test -p api-server jump_hop_tile_atlas -- --nocapture` 通过后,再看 `jump_hop.rs` 不应再调用 `build_generated_asset_sheet_prompt` 处理地块图集;公开结果里应能拿到 6 个独立 `JumpHopTileAsset`。
|
||||
- 关联:`server-rs/crates/api-server/src/jump_hop.rs`、`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## image2 dry-run 带参考图时不要直接打印 data URL
|
||||
|
||||
- 现象:使用 VectorEngine `gpt-image-2-all` 生成带参考图的概念图时,如果 dry-run 直接打印完整请求体,参考图会被转成超长 `data:image/png;base64,...`,终端日志会被数百万字符淹没。
|
||||
- 原因:生成请求支持 `image` 数组传入 data URL 参考图;dry-run 如果复用 live 请求体输出,就会把参考图内容完整打印。
|
||||
- 处理:dry-run 输出摘要,只保留 `imageReferenceCount`、尺寸、模型和 prompt,不输出完整 base64。live 请求仍按实际需要传 `image` 数组。
|
||||
- 验证:执行 `node scripts/generate-edutainment-tv-map-concepts.mjs --dry-run`,输出应只显示 `imageReferenceCount: 1`,不出现完整 base64。
|
||||
- 关联:`scripts/generate-edutainment-tv-map-concepts.mjs`、`docs/design/【前端体验】寓教于乐电视端乐园地图入口概念图-2026-05-18.md`。
|
||||
|
||||
## 生成图资产不能只拼 generated legacy path
|
||||
|
||||
- 现象:结果页或运行态拿到 `/generated-*-assets/.../image.png` 后图片不显示;前端 `ResolvedAssetImage` 会先调用 `/api/assets/read-url?legacyPublicPath=...`,但换签后的 OSS URL 仍指向不存在对象。
|
||||
- 原因:后端只写了看起来像生成图的 legacy path,没有真正调用 image2、上传 OSS、登记 `asset_object` 并绑定实体。`/api/assets/read-url` 只负责签名读取,不会凭空生成或补写对象。
|
||||
- 处理:玩法生成链路必须在 `api-server` 完成外部副作用:调用 VectorEngine `gpt-image-2-all`,用 `GeneratedImageAssetAdapter` 准备 `PutObject`,上传 OSS 私有对象,调用 `confirm_asset_object` 和 `bind_asset_object_to_entity`,再把返回的 `legacyPublicPath` 写入玩法 profile。
|
||||
- 验证:`cargo check -p api-server --manifest-path server-rs/Cargo.toml`;契约测试应断言前端 JSON 自带的 `hitObjectAsset` 会被忽略,spacetime-client 定向测试应断言缺少服务端注入的真实 `hitObjectAsset` 时不能编译;浏览器 Network 中 generated 图片应先换签,签名 URL 指向已存在对象。
|
||||
- 关联:`server-rs/crates/api-server/src/wooden_fish.rs`、`server-rs/crates/spacetime-client/src/wooden_fish.rs`、`src/components/ResolvedAssetImage.tsx`、`src/services/assetReadUrlService.ts`。
|
||||
|
||||
## 生成页背景视频要固定全屏并显式触发播放
|
||||
|
||||
- 现象:生成页明明带了 `media/create_bg_video.mp4`,但移动端或某些内核里只看到静态首帧,或视频层跟着局部容器滚动,被白色面板压住后看起来像没加载。
|
||||
- 原因:仅靠 `autoPlay/loop/muted/playsInline` 并不稳定;视频如果仍挂在局部容器里,还会被页面面板和遮罩吞掉。某些浏览器初始化后也会停在 `paused=true`。
|
||||
- 处理:背景视频必须放到 `fixed inset-0` 的全屏底层容器里,外层页面用 `isolate` / 透明底控制叠层;挂载后显式尝试 `play()`,并在 `loadeddata`、`canplay` 和页面聚焦时再次触发,避免只停首帧。
|
||||
- 验证:移动端视口检查视频 `rect` 应覆盖整个视口,`paused` 应最终变为 `false`,`currentTime` 应持续前进。
|
||||
- 关联:`src/components/GenerationProgressHero.tsx`、`docs/【玩法创作】生成页圆环布局口径-2026-05-23.md`。
|
||||
|
||||
2026-05-24 补充:`GenerationPageBackdrop` 不要通过 portal 挂到 `document.body`。body 级 fixed 背景会逃离生成页自己的 stacking context,即使业务内容有局部 `z-10`,真实浏览器里也可能把整页 UI 压住。背景视频应作为生成页根容器子节点保留 `fixed inset-0 z-0`,生成页内容保持 `relative z-10`;相关测试应同时断言背景容器低层级、生成页根容器高层级,以及视频节点仍在生成页 DOM 内部。视觉调整时还要记住:空心圆环的中心块要抽掉,时间卡与总进度标题都应缩小,不要让生成页再回到“纯色底 + 大字号说明卡”的状态。顶部返回和右上状态也不能沿用 `text-lg` / `sm:text-2xl` 这类展示级字号;当前步骤名、步骤状态和底部玩法信息标题要维持普通 UI 字号档位,优先保持 `text-xs` 到 `text-sm` 区间。
|
||||
|
||||
2026-05-24 补充:生成页“预计等待 / 已耗时”卡片本身已经有标签,传给 `GenerationProgressHero` 的值只能是纯时间,例如 `4 分钟`、`1 分 15 秒`,不要再拼接“预计还需”或“已耗时”;两张时间卡也要和当前步骤卡一样保持半透明。拼图总进度初始帧必须允许显示 `0%`,不要再用 `Math.max(1, nextProgress)` 之类的保护把启动态抬到 `1%`。
|
||||
|
||||
## `dev:spacetime` 启动后 3101 又断开先查 publish 是否被 spacetime.json 干扰
|
||||
|
||||
- 现象:浏览器报 `Failed to initiate WebSocket connection`,目标为 `ws://127.0.0.1:3101/v1/database/<db>/subscribe`,端口检查发现 `3101` 没有长期监听;手动运行 `npm run dev:spacetime` 可看到 standalone 短暂启动后退出,发布阶段报 `No database target matches '<db>'`。
|
||||
- 原因:SpacetimeDB CLI 会读取仓库根目录 `spacetime.json`。如果本地发布命令没有显式 `--no-config`,CLI 可能按配置文件里的 target 解析数据库,覆盖脚本已传入的 `.env.local` 数据库名和 `--server`,导致 publish 失败;`dev.mjs` 捕获错误后会清理刚启动的 standalone,于是浏览器看到 3101 被拒绝连接。
|
||||
- 处理:`scripts/dev.mjs` 的本地 publish 固定追加 `--no-config`,只使用脚本解析出的数据库名、module path 和实际 SpacetimeDB server。排查时前台运行 `npm run dev:spacetime -- --no-interactive`,若看到该错误,先确认脚本是否仍带 `--no-config`,再查 `.env.local` / `spacetime.local.json` 的数据库名。
|
||||
- 验证:`npm run test -- scripts/dev.test.ts` 覆盖 publish 参数包含 `--no-config`;`npm run dev:spacetime -- --no-interactive` 后 `http://127.0.0.1:3101/v1/ping` 应保持 200。
|
||||
- 关联:`scripts/dev.mjs`、`scripts/dev.test.ts`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 本地 api-server 启动订阅 401 先查 Web identity token 注入
|
||||
|
||||
- 现象:`npm run dev` 启动到 api-server 恢复认证快照时,日志出现 `Failed to initiate WebSocket connection ... /v1/database/<db>/subscribe?compression=Brotli: HTTP error: 401 Unauthorized`。
|
||||
- 原因:SpacetimeDB SDK 订阅需要 Web API identity token;本地 `.env.local` 常把 `GENARRATIVE_SPACETIME_TOKEN` 留空,只靠 CLI 登录态 publish 成功并不能让 api-server 的 WebSocket subscribe 获得权限。
|
||||
- 处理:`scripts/dev.mjs` 在 SpacetimeDB 就绪后调用 `/v1/identity` 创建当前进程专用 Web API identity token,并只注入本次 `api-server` 环境;不要把临时 token 写进 `.env.local` 或日志。若仍报 401,先确认是否使用了项目脚本启动、日志是否出现 `已创建本地 Web identity`,以及 `GENARRATIVE_SPACETIME_SERVER_URL` / 数据库名是否指向本次启动的实例。
|
||||
- 验证:`npm run test -- scripts/dev.test.ts`;重新运行 `npm run dev` 后 api-server 启动日志不再出现上述 subscribe 401,`/healthz` 返回 200。
|
||||
- 关联:`scripts/dev.mjs`、`scripts/dev.test.ts`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 创作作品架或公开列表异常先查本地 SpacetimeDB schema 漂移
|
||||
|
||||
- 现象:本地 `http://127.0.0.1:3000/` 启动后,`api-server` 日志反复出现 `Host returned error when processing subscription query: no such table: puzzle_gallery_card_view`;或创作中心草稿 / 已发布作品整块消失,`GET /api/creation-entry/config` 返回 `502` 且 details 为 `No such procedure`。
|
||||
- 原因:本地 `.env.local` 或 `spacetime.local.json` 指向的 SpacetimeDB 库没有发布当前 `spacetime-module`,或当前 CLI 身份无权发布该库;例如旧 `xushi-p4wfr` 库缺 `get_creation_entry_config` / `puzzle_gallery_card_view`,但当前代码的 `spacetime-client` 启动时会长期订阅这些公开 read model。
|
||||
- 处理:先用 `spacetime sql <database> "SELECT * FROM puzzle_gallery_card_view LIMIT 1" --server http://127.0.0.1:3101` 确认目标库是否有当前 view;若只是本地验证,可用 gitignored 的 `spacetime.local.json` 指向可发布且已包含当前 schema 的库,例如 `{"database":"genarrative-dev-codex"}`。该 JSON 必须无 UTF-8 BOM,否则 `scripts/dev.mjs` 会忽略它。修改后用 `npm run dev:api-server -- --database <database> --spacetime-port 3101 --api-port 8082 --no-interactive` 重启。
|
||||
- 验证:`curl.exe -i http://127.0.0.1:8082/healthz` 返回 `200`;`curl.exe -i http://127.0.0.1:8082/api/runtime/puzzle/gallery` 返回 `200`;浏览器打开 `http://127.0.0.1:3000/` 无 `puzzle_gallery_card_view` 控制台或后端日志错误。
|
||||
- 关联:`scripts/dev.mjs`、`server-rs/crates/spacetime-client/src/lib.rs`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 创作作品架消失先查入口配置 procedure 与本地库权限
|
||||
|
||||
- 现象:寓教于乐或创作中心下草稿 / 已发布作品突然整块消失,`GET /api/creation-entry/config` 返回 `502`,details 中为 `No such procedure`。
|
||||
- 原因:本地 `.env.local` 或 `spacetime.local.json` 指向的 SpacetimeDB 库没有发布当前 `spacetime-module`,或当前 CLI 身份无权发布该库;例如旧 `xushi-p4wfr` 库缺 `get_creation_entry_config` 时,前端拿不到入口配置就不会渲染作品架。
|
||||
- 处理:优先切换到拥有目标库权限的 SpacetimeDB 身份后重新运行 `npm run dev` 完成发布;若只是本地验证,可用 gitignored 的 `spacetime.local.json` 指向可发布的本地库。debug 构建的 `api-server` 对入口配置缺 procedure 会使用后端默认入口配置兜底,避免作品架因本地库漂移整块空白。
|
||||
- 验证:`curl.exe -i http://127.0.0.1:8082/api/creation-entry/config` 返回 `200` 且包含 `baby-object-match`;前端草稿页作品架重新渲染。
|
||||
- 关联:`server-rs/crates/api-server/src/state.rs`、`server-rs/crates/api-server/src/creation_entry_config.rs`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 抓大鹅物品 spritesheet 偏移先查 alpha 连通域切片是否启用
|
||||
|
||||
- 现象:抓大鹅物品图集里大多数素材显示不全、被裁碎、位置整体偏移,甚至切出来像拼贴块。
|
||||
- 原因:旧链路只按 `10x10` 固定格线裁切,遇到模型输出的透明图集稍有偏移、跨格或留白不均时就会把主体切坏。现在后端优先按透明 alpha 连通域识别真实素材矩形,再按原图从上到下、从左到右排序;只有识别数量不足时才回退旧网格切法。
|
||||
- 处理:优先检查 `generated_asset_sheets.rs` 的 alpha 连通域切片是否生效,再查 `item_assets.rs` 是否还在透传旧的固定格线语义。不要只改前端显示比例。
|
||||
- 验证:定向测试 `cargo test -p api-server generated_asset_sheet_two_items_per_row --manifest-path server-rs/Cargo.toml -- --nocapture` 应通过,且错位透明样本应按连通域切出完整视图。
|
||||
- 关联:`server-rs/crates/api-server/src/generated_asset_sheets.rs`、`server-rs/crates/api-server/src/match3d/item_assets.rs`。
|
||||
|
||||
## 个人中心不再保留直达“存档”按钮入口
|
||||
|
||||
- 现象:2026-05-25 起,移动端“我的”页顶部改为品牌行 + 扫码 / 设置按钮,设置区和次级入口不再提供独立的 `存档` 按钮;用户仍可在“玩过”弹窗里查看可继续存档。
|
||||
- 原因:产品布局收口后,个人中心只保留设置、扫码、常用功能和条件性次级入口,存档恢复继续以后端 `/api/profile/save-archives` 真相为准,但不再作为页面直达入口。
|
||||
- 处理:后续如果需要重新暴露存档入口,优先评估是否应回到“玩过”或别的独立弹窗流程,不要默认把存档再塞回常用功能宫格或设置列表。
|
||||
- 验证:`npm test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "mobile profile page matches the reference layout sections|profile scan action opens camera scanner instead of recharge panel"`。
|
||||
- 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
@@ -10,6 +10,7 @@ Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台,把 A
|
||||
|
||||
- RPG / 自定义世界创作与运行时。
|
||||
- 拼图玩法创作、草稿、发布、运行态和排行榜。
|
||||
- 敲木鱼玩法创作、草稿、发布、运行态、公开详情和分享码。
|
||||
- 抓大鹅 Match3D 创作、2D 多视角素材生成、发布和运行态。
|
||||
- 大鱼吃小鱼、方洞挑战、视觉小说、汪汪声浪和儿童向寓教于乐玩法。
|
||||
- 账号、短信 / 密码 / 微信登录、个人资料、任务、钱包、邀请码、充值、反馈、法律信息和后台管理。
|
||||
|
||||
@@ -1,348 +1,253 @@
|
||||
---
|
||||
name: genarrative-play-type-integration
|
||||
description: 在 Genarrative 中新增一个创作入口/玩法类型时,按入口配置、前端分流、契约、后端接口、工作台、结果页、可选 runtime 与作品架的顺序接入。
|
||||
version: 1.0.0
|
||||
author: Hermes Agent
|
||||
license: MIT
|
||||
metadata:
|
||||
hermes:
|
||||
tags: [Genarrative, 玩法接入, 创作入口, 前端, 后端, contracts, runtime]
|
||||
related_skills: []
|
||||
description: 在 Genarrative 新增、开放或重构玩法创作工具时,按平台级强约束 SOP 接入入口配置、表单/图片输入创作工作台、单图资产槽位、系列素材图集生成、独立契约、后端 DDD、结果页、运行态、作品架、广场与验证;用于避免复制既有玩法、默认对话式 Agent、页面内手写图片输入或复用玩法专属素材模型。
|
||||
---
|
||||
|
||||
# Genarrative 新增玩法类型接入流程
|
||||
# Genarrative 新增玩法创作工具平台 SOP
|
||||
|
||||
用于在 Genarrative 中新增一个创作入口/玩法类型,而不是单纯说明用户如何从入口创建作品。
|
||||
把新增玩法当成平台能力接入,不把任何既有玩法当作默认模板。先确定通用模式和契约,再写具体玩法代码。
|
||||
|
||||
## 适用场景
|
||||
## 硬性禁区
|
||||
|
||||
- 新增一个游戏玩法入口
|
||||
- 让某个玩法从“敬请期待”变为可创建
|
||||
- 为新玩法补齐创作工作台、结果页、发布与试玩链路
|
||||
- 将新玩法接入创作中心作品架与广场
|
||||
- 不恢复前端硬编码入口配置;创作入口事实源必须来自 SpacetimeDB 和 `/api/creation-entry/config`。
|
||||
- 不把聊天输入区、流式消息或轻输入 Agent 作为新增玩法默认工作台。
|
||||
- 不在新页面内手写图片上传、参考图、AI 重绘、历史图选择、预览或删除确认逻辑。
|
||||
- 不把通用系列素材建模成任一玩法专属 DTO;玩法只能追加自己的运行态字段。
|
||||
- 不让前端承接正式业务真相;发布、试玩、通关、失败、计分、资产持久化和作品状态以后端投影为准。
|
||||
- 不新建平行入口系统、平行作品架或平行公开列表;优先扩展现有平台壳、现有阶段和现有聚合。
|
||||
- 不在 UI 面板内默认写功能说明、规则说明或开发解释文案。
|
||||
|
||||
## 先判断接入级别
|
||||
## 接入前输入
|
||||
|
||||
### 1. 只做入口占位
|
||||
开始编码前,PRD 或当前玩法文档必须已经明确:
|
||||
|
||||
只需要新增入口配置,不接 session/workspace/result/runtime。
|
||||
- `playId`、对外名称、工程域名、入口 `visible/open` 状态。
|
||||
- 创作链路:入口 -> 工作台 -> 生成页 -> 结果页 -> 试玩 -> 发布 -> 运行态。
|
||||
- 表单字段:字段名、默认值、校验、后端落库位置、生成提示词来源。
|
||||
- 单图资产槽位:`slotId`、`slotType`、`slotName`、提示词来源、读取字段、写回字段、是否允许历史图和 AI 重绘。
|
||||
- 系列素材槽位:`batchId` 语义、`sheetSpec`、`slotSpecs`、切图规则、透明化规则、失败回写、局部重生成策略。
|
||||
- API 命名空间:`/api/creation/<play>/sessions`、`actions`、`works`、`runtime`。
|
||||
- 草稿恢复、生成中恢复、失败重试、登录切换、发布后回读和移动端行为。
|
||||
- 验证命令和例外声明;没有例外时写明“无创作工具模式例外”。
|
||||
|
||||
适合:
|
||||
- 敬请期待
|
||||
- 灰度占位
|
||||
## 默认模式
|
||||
|
||||
### 2. 可进入创作工作台
|
||||
新增玩法默认采用表单/图片输入创作工作台:
|
||||
|
||||
需要补齐前端分流、session、工作台、结果页,至少能生成草稿。
|
||||
```text
|
||||
创作入口 -> 表单/图片输入工作台 -> 生成页 -> 结果页 -> 试玩 -> 发布 -> 运行态
|
||||
```
|
||||
|
||||
### 3. 完整玩法闭环
|
||||
工作台只提交结构化表单、图片槽位和配置 payload。确需自然语言对话时,先走“例外流程”,不能把聊天区直接加进默认工作台。
|
||||
|
||||
需要补齐:
|
||||
- 创作入口
|
||||
- 工作台
|
||||
- 草稿生成
|
||||
- 结果页
|
||||
- 发布
|
||||
- 试玩 runtime
|
||||
- 作品架 / 广场 / 分享
|
||||
## SOP
|
||||
|
||||
## 推荐接入顺序
|
||||
### 1. 文档和领域词先行
|
||||
|
||||
### Step 1: 先定玩法 ID 和能力边界
|
||||
先读:
|
||||
|
||||
先明确:
|
||||
- `id` 是什么
|
||||
- 入口是否可见
|
||||
- 是否可点击创建
|
||||
- 是否需要对话式创作
|
||||
- 是否需要生成中页面
|
||||
- 是否需要 result/runtime/gallery/share
|
||||
- `AGENTS.md`
|
||||
- `.hermes/shared-memory/`
|
||||
- `CONTEXT.md`
|
||||
- `docs/README.md`
|
||||
- `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
|
||||
- 相关玩法 PRD 或设计文档
|
||||
|
||||
不要先随便起临时 ID 再改名。
|
||||
如果文档不能精确指导字段、契约、资产槽位、生成流程和恢复语义,先补文档再编码。新增长期约定时同步 `.hermes/shared-memory/`。
|
||||
|
||||
### Step 2: 新增入口配置
|
||||
### 2. 定玩法边界
|
||||
|
||||
文件:
|
||||
- `src/config/newWorkEntryConfig.ts`
|
||||
固定 `playId`、对外名称、工程域、入口状态、是否支持结果页、试玩、发布、作品架、广场、分享和 runtime。不要先用临时 ID 接线后再批量改名。
|
||||
|
||||
在 `NEW_WORK_ENTRY_CONFIG.creationTypes` 中新增或调整:
|
||||
- `id`
|
||||
- `title`
|
||||
- `subtitle`
|
||||
- `badge`
|
||||
- `visible`
|
||||
- `open`
|
||||
### 3. 接入口配置
|
||||
|
||||
字段语义:
|
||||
- `visible: true`:在创作页签 / 新建作品入口中展示。
|
||||
- `visible: false`:不在平台入口展示,但不删除既有玩法路由和能力。
|
||||
- `open: true`:可点击进入创作流程。
|
||||
- `open: false`:展示为锁定 / 敬请期待,不应进入创建流程。
|
||||
入口配置事实源是 SpacetimeDB `creation_entry_type_config`。后台通过 `/admin/api/creation-entry/config` 管理,前台通过 `/api/creation-entry/config` 读取。
|
||||
|
||||
如果只是占位:
|
||||
- `visible: true`
|
||||
- `open: false`
|
||||
前端只允许在展示层派生:
|
||||
|
||||
相关渲染与过滤位置:
|
||||
- `src/components/platform-entry/platformEntryCreationTypes.ts`:将 `NEW_WORK_ENTRY_CONFIG.creationTypes` 映射为平台入口卡片,`getVisiblePlatformCreationTypes()` 会过滤隐藏项,并把可创建模板排在敬请期待模板前面。
|
||||
- `src/components/custom-world-home/CustomWorldCreationStartCard.tsx`:创作页签首屏模板入口卡片的实际渲染位置。
|
||||
- `src/components/platform-entry/PlatformEntryCreationTypeModal.tsx`:选择创作类型弹层的渲染位置。
|
||||
- 可见入口卡片。
|
||||
- 锁定或开放状态。
|
||||
- 排序、图标、短标题等展示信息。
|
||||
|
||||
注意:当前项目工作区通常已经是 `<repo-root>`,路径不要再额外拼接 `./Genarrative/`。
|
||||
`api-server` 路由熔断必须使用同一份入口配置。禁止新增或恢复前端本地默认入口配置作为事实源。
|
||||
|
||||
### Step 3: 确认类型过滤逻辑
|
||||
### 4. 前端阶段
|
||||
|
||||
文件:
|
||||
- `./Genarrative/src/components/platform-entry/platformEntryCreationTypes.ts`
|
||||
按需要扩展 `SelectionStage`:
|
||||
|
||||
检查:
|
||||
- `getVisiblePlatformCreationTypes()` 是否能展示新类型
|
||||
- `isPlatformCreationTypeVisible()` 是否能识别新类型
|
||||
- `locked` / `hidden` 是否正确映射
|
||||
- `<play>-workspace`
|
||||
- `<play>-generating`
|
||||
- `<play>-result`
|
||||
- `<play>-runtime`
|
||||
- `<play>-gallery-detail`
|
||||
|
||||
### Step 4: 扩展页面阶段
|
||||
阶段名可以按玩法命名,UI 形态必须仍是表单/图片创作工作台。进入工作台时只初始化结构化草稿状态,不启动默认聊天会话。
|
||||
|
||||
文件:
|
||||
- `./Genarrative/src/components/platform-entry/platformEntryTypes.ts`
|
||||
### 5. 工作台实现
|
||||
|
||||
为新玩法补充 `SelectionStage`:
|
||||
- `*-agent-workspace`
|
||||
- `*-generating`(可选)
|
||||
- `*-result`
|
||||
- `*-runtime`(可选)
|
||||
- `*-gallery-detail`(可选)
|
||||
工作台必须满足:
|
||||
|
||||
### Step 5: 在总流程中加类型分流
|
||||
- 使用表单控件、图片槽位、风格选项、难度选项、开关和提交按钮组织输入。
|
||||
- 单图槽位统一使用 `CreativeImageInputPanel`。
|
||||
- 组件缺少能力时先扩展 `CreativeImageInputPanel` 的受控 props,不在玩法页面复制上传、参考图、AI 重绘、历史图、预览或删除确认。
|
||||
- 主图读取、裁剪、历史素材弹层、计费确认、自动保存和后端请求由外层页面持有;通用面板只表达输入 UI 和短生命周期 UI 状态。
|
||||
- 提交 payload 必须是表单字段与图片槽位结构,不是用户消息文本。
|
||||
|
||||
文件:
|
||||
- `./Genarrative/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
||||
### 6. 单图资产槽位
|
||||
|
||||
在 `handleCreationHubCreateType(type)` 中新增分支,确保:
|
||||
- 能进入对应工作台
|
||||
- 能设置对应 `selectionStage`
|
||||
- 能关闭类型弹层
|
||||
角色形象、UI 背景、容器、封面、分享图、图标等单张图都按单图资产槽位处理。
|
||||
|
||||
同时补:
|
||||
- `open<Play>AgentWorkspace()`
|
||||
- `leave<Play>Flow()`
|
||||
- `submit<Play>Message()`(对话式玩法)
|
||||
- `execute<Play>Action()`
|
||||
统一约定:
|
||||
|
||||
### Step 6: 接入通用 Agent flow controller
|
||||
- 槽位用 `slotId` 稳定标识,`slotType` 表达用途,`slotName` 用于 UI 标签。
|
||||
- 上传图、参考图、AI 重绘、历史图选择和删除确认都通过 `CreativeImageInputPanel` 入口表达。
|
||||
- 后端写回 `imageSrc`、`imageObjectKey`、`assetObjectId` 中可用字段;前端展示前通过平台资产读取能力换签。
|
||||
- 单个槽位重生成只禁用该槽位动作,不阻塞结果页其它槽位、系列素材槽位或导航。
|
||||
|
||||
文件:
|
||||
- `./Genarrative/src/components/platform-entry/usePlatformCreationAgentFlowController.ts`
|
||||
### 7. 系列素材图集生成
|
||||
|
||||
如果是 Agent 型玩法,复用通用控制器:
|
||||
- `createSession`
|
||||
- `getSession`
|
||||
- `streamMessage`
|
||||
- `executeAction`
|
||||
- `isBusy`
|
||||
地块、物品、障碍、装饰、UI 部件等一组同类素材都走通用系列素材图集生成流程:
|
||||
|
||||
```text
|
||||
批量规划 -> sheet 生图 -> 后端切图 -> 去背景/透明化 -> PNG 输出 -> OSS 持久化 -> 状态回写 -> 局部重生成
|
||||
```
|
||||
|
||||
玩法只提供:
|
||||
|
||||
- `sheetSpec`:画布比例、行列、单格尺寸、输出格式、背景处理策略。
|
||||
- `slotSpecs`:每个素材槽位的 `slotId`、`slotType`、`slotName`、提示词、sheet 单元格映射。
|
||||
- 玩法字段映射:把通用素材结果映射回玩法自己的 draft/profile/runtime 字段。
|
||||
|
||||
通用系列素材结果建议字段:
|
||||
|
||||
- `batchId`
|
||||
- `slotId`
|
||||
- `slotType`
|
||||
- `slotName`
|
||||
- `prompt`
|
||||
- `imageSrc`
|
||||
- `imageObjectKey`
|
||||
- `assetObjectId`
|
||||
- `sourceSheetCell`
|
||||
- `status`
|
||||
- `error`
|
||||
- `streamingReplyText`
|
||||
- `selectionStage` 切换
|
||||
|
||||
### Step 7: 定义 shared contracts
|
||||
玩法可追加运行态字段,例如半径、宽度、视图索引或碰撞参数,但不能依赖任何玩法专属字段作为平台通用模型。新增玩法 compile action 内部调用通用系列素材服务;如果通用服务还缺能力,先补通用服务再接玩法。
|
||||
|
||||
前端:
|
||||
- `./Genarrative/packages/shared/src/contracts/`
|
||||
### 8. 契约与 API
|
||||
|
||||
后端:
|
||||
- `./Genarrative/server-rs/crates/shared-contracts/src/`
|
||||
前后端必须同步补契约:
|
||||
|
||||
至少补齐:
|
||||
- session snapshot
|
||||
- create session request/response
|
||||
- message request/response
|
||||
- action request/response
|
||||
- draft/result 结构
|
||||
- work summary / gallery 结构(如果需要)
|
||||
- runtime 结构(如果需要)
|
||||
- `packages/shared/src/contracts/`
|
||||
- `server-rs/crates/shared-contracts/src/`
|
||||
|
||||
### Step 8: 实现前端 service client
|
||||
玩法 API 保留独立命名空间:
|
||||
|
||||
目录参考:
|
||||
- `./Genarrative/src/services/`
|
||||
- `POST /api/creation/<play>/sessions`
|
||||
- `GET /api/creation/<play>/sessions/{sessionId}`
|
||||
- `POST /api/creation/<play>/sessions/{sessionId}/actions`
|
||||
- `/api/creation/<play>/works`
|
||||
- `/api/creation/<play>/runtime`
|
||||
|
||||
按玩法补:
|
||||
- creation client
|
||||
- runtime client(可选)
|
||||
- works client(可选)
|
||||
- gallery client(可选)
|
||||
契约需要区分:
|
||||
|
||||
建议保持和现有玩法一致的 API base 与命名风格。
|
||||
- 工作台输入。
|
||||
- 草稿 snapshot。
|
||||
- 单图资产槽位。
|
||||
- 系列素材批次与槽位。
|
||||
- 结果页操作。
|
||||
- 发布作品摘要。
|
||||
- runtime snapshot。
|
||||
|
||||
### Step 9: 接后端 API
|
||||
### 9. 后端分层
|
||||
|
||||
文件参考:
|
||||
- `./Genarrative/server-rs/crates/api-server/src/puzzle.rs`
|
||||
- `./Genarrative/server-rs/crates/api-server/src/puzzle_agent_turn.rs`
|
||||
- `./Genarrative/server-rs/crates/api-server/src/match3d.rs`
|
||||
按 DDD 边界落地:
|
||||
|
||||
通常需要:
|
||||
- create session
|
||||
- get session
|
||||
- send message
|
||||
- stream message
|
||||
- execute action
|
||||
- publish / save / delete
|
||||
- runtime start / action(可选)
|
||||
- gallery / detail(可选)
|
||||
- `module-<play>`:纯领域规则、状态机、draft/runtime 校验。
|
||||
- `shared-contracts`:前后端 DTO。
|
||||
- `spacetime-module`:表、reducer、procedure、事务编排、migration。
|
||||
- `spacetime-client`:typed facade 和 row mapper。
|
||||
- `api-server`:Axum 路由、鉴权、BFF、SSE、生成编排。
|
||||
- `platform-*`:LLM、图片生成、OSS、认证等外部副作用。
|
||||
|
||||
后端设计优先按 Genarrative 的 DDD 分层拆开,不要把玩法规则、数据库事务、LLM 调用和 HTTP handler 混在一个文件里:
|
||||
- `module-<play>`:纯领域规则、状态机、draft/runtime 校验,不依赖 Axum、SpacetimeDB 或外部平台。
|
||||
- `shared-contracts`:前后端 DTO、请求/响应、session snapshot、draft/result/runtime 结构。
|
||||
- `spacetime-module`:表定义、reducer/procedure、事务编排、migration;表结构变化要同步生成绑定。
|
||||
- `spacetime-client`:api-server 到 SpacetimeDB 的 facade,隐藏 reducer 调用细节。
|
||||
- `api-server`:Axum 路由、鉴权、SSE/stream、应用层编排。
|
||||
- `platform-*`:LLM、资产上传、鉴权、第三方服务等副作用。
|
||||
涉及 SpacetimeDB schema 时同步 `migration.rs`、表目录和绑定,并运行 `npm run check:spacetime-schema`。
|
||||
|
||||
建议按四条线设计后端能力:
|
||||
- Agent 创作线:session、turn、stream、compile action。
|
||||
- Works 作品线:保存、发布、删除、草稿恢复。
|
||||
- Gallery 广场线:公开列表、详情、like/remix/share。
|
||||
- Runtime 运行态线:开始试玩、提交动作、读取状态。
|
||||
|
||||
### Step 10: 新增工作台组件
|
||||
|
||||
目录建议:
|
||||
- `./Genarrative/src/components/<play>-creation/<Play>AgentWorkspace.tsx`
|
||||
|
||||
两种形态:
|
||||
|
||||
#### 对话式
|
||||
适合设定逐轮补齐。
|
||||
|
||||
参考:
|
||||
- `BigFishAgentWorkspace.tsx`
|
||||
- `Match3DAgentWorkspace.tsx`
|
||||
|
||||
#### 表单式
|
||||
适合输入结构明确的玩法。
|
||||
|
||||
参考:
|
||||
- `PuzzleAgentWorkspace.tsx`
|
||||
|
||||
### Step 11: 在渲染树中挂载新页面
|
||||
|
||||
文件:
|
||||
- `./Genarrative/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
||||
|
||||
补齐:
|
||||
- workspace 分支
|
||||
- generating 分支(如需要)
|
||||
- result 分支
|
||||
- runtime 分支(如需要)
|
||||
|
||||
### Step 12: 新增结果页
|
||||
|
||||
目录建议:
|
||||
- `./Genarrative/src/components/<play>-result/<Play>ResultView.tsx`
|
||||
### 10. 结果页
|
||||
|
||||
结果页至少支持:
|
||||
- 展示 draft
|
||||
- 返回编辑
|
||||
- 发布
|
||||
- 试玩
|
||||
- 错误展示
|
||||
|
||||
### Step 13: 需要试玩就补 runtime
|
||||
- 展示草稿和生成状态。
|
||||
- 返回工作台编辑。
|
||||
- 单图槽位重生成。
|
||||
- 系列素材追加、替换、局部重生成。
|
||||
- 发布。
|
||||
- 试玩。
|
||||
- 错误展示和失败重试。
|
||||
|
||||
目录建议:
|
||||
- `./Genarrative/src/components/<play>-runtime/<Play>RuntimeShell.tsx`
|
||||
单图槽位和系列素材槽位的生成状态互不阻塞。已有可查看结果时,局部重生成不能把作品架草稿重新变成不可打开的全局生成中。
|
||||
|
||||
如果玩法是游戏类,建议补完整 runtime 闭环。
|
||||
### 11. 运行态、作品架和广场
|
||||
|
||||
### Step 14: 接入作品架 / 广场 / 分享
|
||||
需要试玩或发布时补齐:
|
||||
|
||||
需要改:
|
||||
- `./Genarrative/src/components/custom-world-home/creationWorkShelf.ts`
|
||||
- `./Genarrative/src/components/custom-world-home/CustomWorldCreationHub.tsx`
|
||||
- `./Genarrative/src/services/publicWorkCode.ts`
|
||||
- runtime start/action/finish API。
|
||||
- 作品保存、发布、删除、回读。
|
||||
- 作品架摘要。
|
||||
- 公开列表、详情、分享码。
|
||||
- 公开列表优先消费后端投影或 BFF 缓存,不让前端直接拼源表事实。
|
||||
|
||||
如果玩法支持发布,还要补:
|
||||
- public work code
|
||||
- public detail
|
||||
- publish share modal
|
||||
- like/remix(可选)
|
||||
运行态可以做低延迟表现,但正式胜负、分数、奖励、排行榜和发布状态以后端裁决为准。
|
||||
|
||||
### Step 15: 处理登录态与草稿恢复
|
||||
### 12. 恢复与登录态
|
||||
|
||||
要考虑:
|
||||
- 刷新恢复草稿
|
||||
- 退出登录清空私有状态
|
||||
- result/draft 缺失时回退
|
||||
- busy / generating / runtime 中断恢复
|
||||
必须处理:
|
||||
|
||||
### Step 16: 补测试
|
||||
- 刷新恢复生成中草稿。
|
||||
- 生成页计时从后端摘要时间恢复。
|
||||
- 失败后回读 session/work detail 再决定是否展示失败。
|
||||
- 退出登录清空私有玩法状态。
|
||||
- 私有生成图展示前换签。
|
||||
- result/runtime 缺必要 draft 时回到可恢复入口,不停在空白页。
|
||||
|
||||
至少覆盖:
|
||||
- 入口展示
|
||||
- 类型分流
|
||||
- 工作台打开
|
||||
- session 创建
|
||||
- compile action
|
||||
- result 页切换
|
||||
- 发布后刷新作品架
|
||||
- runtime 进入与退出
|
||||
### 13. 例外流程
|
||||
|
||||
## 最小改动清单
|
||||
任何非表单/图片工作台、对话式 Agent、独立创作系统或特殊资产模型都必须先更新 PRD 和平台文档。例外声明至少写清:
|
||||
|
||||
### 只做占位
|
||||
- 为什么默认表单/图片工作台不能满足。
|
||||
- 例外影响哪些输入、契约、后端流程和测试。
|
||||
- 如何保留单图资产槽位和系列素材槽位的通用能力。
|
||||
- 如何回退到平台默认链路。
|
||||
|
||||
只改:
|
||||
- `./Genarrative/src/config/newWorkEntryConfig.ts`
|
||||
没有文档例外,不进入编码。
|
||||
|
||||
### 做到可进入工作台
|
||||
## PRD 检查块
|
||||
|
||||
至少改:
|
||||
- `newWorkEntryConfig.ts`
|
||||
- `platformEntryTypes.ts`
|
||||
- `PlatformEntryFlowShellImpl.tsx`
|
||||
- 新玩法 service client
|
||||
- 新玩法工作台组件
|
||||
- shared contracts
|
||||
- 后端 API
|
||||
在新增玩法 PRD 中保留这一段:
|
||||
|
||||
### 做到完整闭环
|
||||
```md
|
||||
## 创作工具平台接入声明
|
||||
|
||||
还要补:
|
||||
- result 页
|
||||
- runtime
|
||||
- works / gallery
|
||||
- public code
|
||||
- share
|
||||
- 作品架聚合
|
||||
- 测试
|
||||
- 工作台模式:表单/图片输入创作工作台
|
||||
- 创作链路:入口 -> 工作台 -> 生成页 -> 结果页 -> 试玩 -> 发布 -> 运行态
|
||||
- 单图资产槽位:
|
||||
- slotId / slotType / slotName / 提示词来源 / 写回字段 / 是否允许历史图 / 是否允许 AI 重绘
|
||||
- 系列素材槽位:
|
||||
- batchId / sheetSpec / slotSpecs / 切图规则 / 透明化规则 / 失败回写 / 局部重生成
|
||||
- API 命名空间:/api/creation/<play>/...
|
||||
- 业务真相:后端裁决字段和前端表现字段边界
|
||||
- 创作工具模式例外:无;如有,先写明例外原因和回退方式
|
||||
- 验证命令:
|
||||
```
|
||||
|
||||
## 常见坑
|
||||
## 验证门禁
|
||||
|
||||
1. 只加入口配置不够,类型分流和页面阶段也要补。
|
||||
2. `SelectionStage` 不扩展,前端无法安全切页。
|
||||
3. 新玩法如果要出现在作品架,必须改聚合逻辑,不只是加入口。
|
||||
4. 发布后不刷新 works/gallery,用户会看不到新作品。
|
||||
5. 如果走 SpacetimeDB,表结构变化要同步 migration 和绑定;`spacetime-client/src/module_bindings/` 通常是生成物,不要为了修编译或格式化而手改,优先改 module 源 schema/reducer/procedure 后重新生成。
|
||||
6. 做 analytics/tracking 这类 runtime 能力时,不要只补 API DTO;先在 `module-runtime` 写纯函数测试(例如 day/week/month/quarter/year bucket 聚合、scope/event 过滤),RED 后再补领域类型与聚合函数。
|
||||
7. 时间粒度聚合建议复用已有 date dimension 逻辑,把 daily stat 映射到 day/week/month/quarter/year bucket;bucket 输出要有稳定排序,并显式携带 `bucketKey`、`bucketStartDateKey`、`bucketEndDateKey`、`value`。
|
||||
8. 后端 shared-contracts 与前端 `packages/shared/src/contracts/runtime.ts` 要同步补 request/response/type union;admin-web 若有独立 `api/adminApiTypes.ts`,也要同步,避免共享包已更新但管理端本地类型缺失。
|
||||
9. 退出登录时要清空新玩法私有状态,避免串用户。
|
||||
10. 移动端入口卡片增多后要检查布局和滚动体验。
|
||||
按改动范围运行:
|
||||
|
||||
## 参考资料
|
||||
|
||||
- `references/genarrative-analytics-tracking-runtime.md`:analytics/tracking runtime 粒度聚合、contracts 同步与 SpacetimeDB 生成物注意事项。
|
||||
|
||||
## 验证标准
|
||||
|
||||
一个玩法算真正接入成功,至少要满足:
|
||||
|
||||
- 入口能展示
|
||||
- 能进入对应工作台
|
||||
- 能创建 session
|
||||
- 能生成草稿
|
||||
- 能进入结果页
|
||||
- 能返回编辑
|
||||
- 如果需要,可试玩
|
||||
- 如果需要,可发布
|
||||
- 发布后能回到作品架 / 广场 / 分享链路
|
||||
- `npm run check:encoding`
|
||||
- `npm run typecheck`
|
||||
- 前端工作台测试:确认没有聊天式 Agent 输入,提交的是表单/图片 payload。
|
||||
- `CreativeImageInputPanel` 测试:覆盖多玩法标签、上传、AI 重绘、参考图上限、历史图入口和删除确认。
|
||||
- 系列素材测试:覆盖 sheet layout、切图、透明化、OSS 持久化、追加、替换、局部重生成和失败回写。
|
||||
- 结果页测试:覆盖单图槽位重生成和系列素材槽位重生成互不阻塞。
|
||||
- 后端定向测试:覆盖 compile action、资产持久化、失败回写、发布和 runtime start。
|
||||
- 涉及 SpacetimeDB schema 时运行 `npm run check:spacetime-schema`。
|
||||
|
||||
@@ -26,6 +26,10 @@ Use the default canonical triage labels: `needs-triage`, `needs-info`, `ready-fo
|
||||
|
||||
Single-context layout: read root `CONTEXT.md` when present. Current architecture and product constraints are consolidated under `docs/`.
|
||||
|
||||
### 新增玩法接入
|
||||
|
||||
- 凡是新增、补齐、迁移或重构任何玩法入口、玩法类型、创作工作台、生成页、结果页、发布、运行态、作品架、广场或公开 read model 的任务,开始前必须显式读取并按 [$genarrative-play-type-integration](.codex\skills\genarrative-play-type-integration\SKILL.md) 执行;未先使用该 skill 的,不允许进入编码。
|
||||
|
||||
## 项目约束
|
||||
- 代码需要有完善的中文注释
|
||||
- 在落地工程修改前检查是否有详细指导本次落地的文档,若没有文档或文档的完善程度仍有落地过程中编码级别的歧义优先优化文档后落地工程迭代。
|
||||
|
||||
40
CONTEXT.md
@@ -2,8 +2,48 @@
|
||||
|
||||
Genarrative 是一个 AI 原生互动内容与小游戏平台,当前上下文记录团队在玩法、作品、运行态和平台闭环中使用的领域语言。
|
||||
|
||||
## 平台创作工具
|
||||
|
||||
**表单/图片输入创作工作台**:
|
||||
新增玩法默认采用的创作工具模式,用户通过结构化表单、图片槽位和配置控件提交创作输入,链路覆盖入口、工作台、生成页、结果页、试玩、发布和运行态闭环。
|
||||
_Avoid_: 默认对话式 Agent 工作台、默认轻输入 Agent 工作台、复制既有玩法工作台
|
||||
|
||||
**单图资产编辑**:
|
||||
角色形象、UI 背景、容器、封面、分享图等单张图资产的统一输入与重生成方式,统一通过 `CreativeImageInputPanel` 表达上传、AI 重绘、参考图、历史图和删除确认。
|
||||
_Avoid_: 在玩法页面内手写上传、参考图、重绘、预览、删除确认
|
||||
|
||||
**系列素材图集生成**:
|
||||
一组同类素材的统一批量生成方式,采用批量规划、sheet 生图、后端切图、透明化、OSS 持久化和局部重生成的通用流水线。
|
||||
_Avoid_: 为每个玩法单独发明素材流水线、把系列素材建模成任一玩法专属 DTO
|
||||
|
||||
## Language
|
||||
|
||||
### Wooden Fish
|
||||
|
||||
**敲木鱼**:
|
||||
轻量点击型互动玩法,玩家在单次运行中点击非功能区敲击中央物品,触发敲击音效、敲击动画、随机飘字和本次运行内的词条计数。
|
||||
_Avoid_: 长期功德账本、排行榜玩法、全局账户累计
|
||||
|
||||
**敲击物图案**:
|
||||
敲木鱼作品中被玩家点击敲击的单张物品图案;默认模板使用内置透明 PNG `/wooden-fish/default-hit-object.png`,用户自定义关键词或上传图时再使用 image2 生成最终资产,上传图只作为 image2 参考。
|
||||
_Avoid_: 直接把上传图作为运行态素材、系列素材图集
|
||||
|
||||
**敲木鱼背景环境图**:
|
||||
敲木鱼作品中的竖屏 9:16 背景资产;由后端在敲击物图案生成后,以新敲击物图案作为主题和画风参考,再结合用户原始题材关键词或参考图主题调用 image2 生成。背景只适配敲击物主题和画风,不包含敲击物本体或木槌互动物品。
|
||||
_Avoid_: 把背景当封面图、在背景里重复绘制敲击物、让前端临时拼背景
|
||||
|
||||
**敲击音效**:
|
||||
敲木鱼作品中每次有效敲击播放的短音频资产,可由描述生成、文件上传或麦克风录制产生,最终统一写回作品的敲击音效资产槽位。
|
||||
_Avoid_: 背景音乐、长音频轨道、运行态实时录音
|
||||
|
||||
**飘字**:
|
||||
每次有效敲击后从作品配置中等概率抽取词条,并在敲击物上方以“词条+1”短暂漂浮显示的文本;配置里只保存幸运、健康、财富、姻缘、幸福、事业、成功、功德等词条名本身。
|
||||
_Avoid_: 带权重奖励、账户属性、可结算货币
|
||||
|
||||
**单次 run 计数**:
|
||||
敲木鱼运行态只在当前 run 内累计总敲击次数和已出现飘字词条计数,run 结束后作为摘要保存,不形成账号级长期账本。
|
||||
_Avoid_: 用户永久功德值、跨作品累计值、排行榜积分
|
||||
|
||||
### Bark Battle
|
||||
|
||||
**汪汪声浪大作战**:
|
||||
|
||||
@@ -157,6 +157,9 @@ export interface AdminCreationEntryTypeConfigPayload {
|
||||
visible: boolean;
|
||||
open: boolean;
|
||||
sortOrder: number;
|
||||
categoryId: string;
|
||||
categoryLabel: string;
|
||||
categorySortOrder: number;
|
||||
updatedAtMicros: number;
|
||||
}
|
||||
|
||||
@@ -169,6 +172,9 @@ export interface AdminUpsertCreationEntryTypeConfigRequest {
|
||||
visible: boolean;
|
||||
open: boolean;
|
||||
sortOrder: number;
|
||||
categoryId: string;
|
||||
categoryLabel: string;
|
||||
categorySortOrder: number;
|
||||
}
|
||||
|
||||
export interface AdminUpsertProfileRedeemCodeRequest {
|
||||
|
||||
@@ -27,6 +27,9 @@ export function AdminCreationEntrySwitchPage({
|
||||
const [visible, setVisible] = useState(true);
|
||||
const [open, setOpen] = useState(true);
|
||||
const [sortOrder, setSortOrder] = useState('30');
|
||||
const [categoryId, setCategoryId] = useState('recent');
|
||||
const [categoryLabel, setCategoryLabel] = useState('最近创作');
|
||||
const [categorySortOrder, setCategorySortOrder] = useState('10');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [listErrorMessage, setListErrorMessage] = useState('');
|
||||
@@ -82,6 +85,9 @@ export function AdminCreationEntrySwitchPage({
|
||||
visible,
|
||||
open,
|
||||
sortOrder: parseInteger(sortOrder),
|
||||
categoryId: categoryId.trim(),
|
||||
categoryLabel: categoryLabel.trim(),
|
||||
categorySortOrder: parseInteger(categorySortOrder),
|
||||
});
|
||||
const nextEntries = sortEntries(response.entries);
|
||||
setEntries(nextEntries);
|
||||
@@ -105,6 +111,9 @@ export function AdminCreationEntrySwitchPage({
|
||||
setVisible(entry.visible);
|
||||
setOpen(entry.open);
|
||||
setSortOrder(String(entry.sortOrder));
|
||||
setCategoryId(entry.categoryId);
|
||||
setCategoryLabel(entry.categoryLabel);
|
||||
setCategorySortOrder(String(entry.categorySortOrder));
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -189,6 +198,32 @@ export function AdminCreationEntrySwitchPage({
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className="admin-form-row">
|
||||
<label className="admin-field">
|
||||
<span>分类 ID</span>
|
||||
<input
|
||||
value={categoryId}
|
||||
onChange={(event) => setCategoryId(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<label className="admin-field">
|
||||
<span>分类名称</span>
|
||||
<input
|
||||
value={categoryLabel}
|
||||
onChange={(event) => setCategoryLabel(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label className="admin-field">
|
||||
<span>分类排序</span>
|
||||
<input
|
||||
inputMode="numeric"
|
||||
value={categorySortOrder}
|
||||
onChange={(event) => setCategorySortOrder(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
|
||||
{errorMessage ? (
|
||||
<div className="admin-alert" role="status">
|
||||
{errorMessage}
|
||||
@@ -211,6 +246,7 @@ export function AdminCreationEntrySwitchPage({
|
||||
<th>入口</th>
|
||||
<th>展示</th>
|
||||
<th>开放</th>
|
||||
<th>分类</th>
|
||||
<th>排序</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -228,6 +264,7 @@ export function AdminCreationEntrySwitchPage({
|
||||
</td>
|
||||
<td>{entry.visible ? '是' : '否'}</td>
|
||||
<td>{entry.open ? '是' : '否'}</td>
|
||||
<td>{entry.categoryLabel || entry.categoryId}</td>
|
||||
<td>{entry.sortOrder}</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
:root {
|
||||
color: #17212b;
|
||||
background: #eef3f6;
|
||||
color: #3d1f10;
|
||||
background: #f8efe7;
|
||||
font-family:
|
||||
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||
"Segoe UI", sans-serif;
|
||||
@@ -16,7 +16,7 @@ body {
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
background: #eef3f6;
|
||||
background: #f8efe7;
|
||||
}
|
||||
|
||||
button,
|
||||
@@ -49,31 +49,31 @@ button:disabled {
|
||||
|
||||
.admin-loading-screen {
|
||||
gap: 12px;
|
||||
color: #5c6b77;
|
||||
color: #7b6150;
|
||||
}
|
||||
|
||||
.admin-loading-mark {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 3px solid #d1dde6;
|
||||
border-top-color: #126e82;
|
||||
border: 3px solid #e1ccbb;
|
||||
border-top-color: #b6623f;
|
||||
border-radius: 50%;
|
||||
animation: admin-spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
.admin-login-screen {
|
||||
background:
|
||||
linear-gradient(145deg, rgba(18, 110, 130, 0.12), transparent 36%),
|
||||
linear-gradient(315deg, rgba(165, 94, 54, 0.12), transparent 34%),
|
||||
#eef3f6;
|
||||
linear-gradient(145deg, rgba(204, 117, 76, 0.14), transparent 36%),
|
||||
linear-gradient(315deg, rgba(226, 171, 134, 0.16), transparent 34%),
|
||||
#f8efe7;
|
||||
}
|
||||
|
||||
.admin-login-panel,
|
||||
.admin-panel {
|
||||
border: 1px solid #d8e2e8;
|
||||
border: 1px solid #e1ccbb;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 12px 36px rgba(23, 33, 43, 0.08);
|
||||
box-shadow: 0 12px 36px rgba(112, 57, 30, 0.08);
|
||||
}
|
||||
|
||||
.admin-login-panel {
|
||||
@@ -92,14 +92,14 @@ button:disabled {
|
||||
|
||||
.admin-login-brand h1 {
|
||||
margin: 0;
|
||||
color: #17212b;
|
||||
color: #3d1f10;
|
||||
font-size: 26px;
|
||||
line-height: 1.16;
|
||||
}
|
||||
|
||||
.admin-login-brand span,
|
||||
.admin-brand span {
|
||||
color: #667682;
|
||||
color: #8f7868;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@@ -108,10 +108,10 @@ button:disabled {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
place-items: center;
|
||||
border: 1px solid #bcd2db;
|
||||
border: 1px solid #dfc8b7;
|
||||
border-radius: 8px;
|
||||
color: #126e82;
|
||||
background: #e7f3f5;
|
||||
color: #b6623f;
|
||||
background: #f4e5d7;
|
||||
}
|
||||
|
||||
.admin-brand-icon-large {
|
||||
@@ -129,7 +129,7 @@ button:disabled {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
border-right: 1px solid #d8e2e8;
|
||||
border-right: 1px solid #e1ccbb;
|
||||
background: #ffffff;
|
||||
padding: 22px 18px;
|
||||
}
|
||||
@@ -164,13 +164,13 @@ button:disabled {
|
||||
gap: 10px;
|
||||
min-height: 42px;
|
||||
padding: 0 12px;
|
||||
color: #52616d;
|
||||
color: #755a49;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.admin-nav-button[data-active="true"] {
|
||||
color: #0f5666;
|
||||
background: #e7f3f5;
|
||||
color: #8f3f27;
|
||||
background: #f4e5d7;
|
||||
}
|
||||
|
||||
.admin-main {
|
||||
@@ -184,7 +184,7 @@ button:disabled {
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
border-bottom: 1px solid #d8e2e8;
|
||||
border-bottom: 1px solid #e1ccbb;
|
||||
background: rgba(255, 255, 255, 0.86);
|
||||
padding: 0 24px;
|
||||
}
|
||||
@@ -200,7 +200,7 @@ button:disabled {
|
||||
}
|
||||
|
||||
.admin-user small {
|
||||
color: #667682;
|
||||
color: #8f7868;
|
||||
}
|
||||
|
||||
.admin-content {
|
||||
@@ -232,7 +232,7 @@ button:disabled {
|
||||
.admin-page-heading h2,
|
||||
.admin-panel-heading h3 {
|
||||
margin: 0;
|
||||
color: #17212b;
|
||||
color: #3d1f10;
|
||||
}
|
||||
|
||||
.admin-page-heading h2 {
|
||||
@@ -241,7 +241,7 @@ button:disabled {
|
||||
|
||||
.admin-page-heading p {
|
||||
margin: 3px 0 0;
|
||||
color: #667682;
|
||||
color: #8f7868;
|
||||
}
|
||||
|
||||
.admin-panel {
|
||||
@@ -256,7 +256,7 @@ button:disabled {
|
||||
}
|
||||
|
||||
.admin-panel-heading span {
|
||||
color: #667682;
|
||||
color: #8f7868;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@@ -314,12 +314,12 @@ button:disabled {
|
||||
}
|
||||
|
||||
.admin-table tbody tr[data-clickable="true"]:hover {
|
||||
background: #f5fafb;
|
||||
background: #fff7f0;
|
||||
}
|
||||
|
||||
.admin-text-button:hover,
|
||||
.admin-text-button:focus-visible {
|
||||
color: #126e82;
|
||||
color: #b6623f;
|
||||
text-decoration: underline;
|
||||
outline: none;
|
||||
}
|
||||
@@ -345,7 +345,7 @@ button:disabled {
|
||||
}
|
||||
|
||||
.admin-query-summary {
|
||||
color: #667682;
|
||||
color: #8f7868;
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
}
|
||||
@@ -354,7 +354,7 @@ button:disabled {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 7px;
|
||||
color: #4c5c68;
|
||||
color: #6f5848;
|
||||
font-size: 13px;
|
||||
font-weight: 650;
|
||||
}
|
||||
@@ -372,16 +372,16 @@ button:disabled {
|
||||
.admin-field textarea {
|
||||
width: 100%;
|
||||
min-height: 42px;
|
||||
border: 1px solid #cbd8e0;
|
||||
border: 1px solid #dfc8b7;
|
||||
border-radius: 8px;
|
||||
color: #17212b;
|
||||
background: #fbfdfe;
|
||||
color: #3d1f10;
|
||||
background: #fffdf9;
|
||||
padding: 9px 11px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.admin-field-note {
|
||||
color: #667682;
|
||||
color: #8f7868;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 1.45;
|
||||
@@ -395,8 +395,8 @@ button:disabled {
|
||||
.admin-field input:focus,
|
||||
.admin-field select:focus,
|
||||
.admin-field textarea:focus {
|
||||
border-color: #126e82;
|
||||
box-shadow: 0 0 0 3px rgba(18, 110, 130, 0.16);
|
||||
border-color: #b6623f;
|
||||
box-shadow: 0 0 0 3px rgba(204, 117, 76, 0.16);
|
||||
}
|
||||
|
||||
.admin-combobox {
|
||||
@@ -419,16 +419,16 @@ button:disabled {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 42px;
|
||||
border: 1px solid #cbd8e0;
|
||||
border: 1px solid #dfc8b7;
|
||||
border-left: 0;
|
||||
border-radius: 0 8px 8px 0;
|
||||
color: #52616d;
|
||||
background: #fbfdfe;
|
||||
color: #755a49;
|
||||
background: #fffdf9;
|
||||
}
|
||||
|
||||
.admin-combobox:focus-within .admin-combobox-toggle {
|
||||
border-color: #126e82;
|
||||
box-shadow: 0 0 0 3px rgba(18, 110, 130, 0.16);
|
||||
border-color: #b6623f;
|
||||
box-shadow: 0 0 0 3px rgba(204, 117, 76, 0.16);
|
||||
}
|
||||
|
||||
.admin-combobox-menu {
|
||||
@@ -440,10 +440,10 @@ button:disabled {
|
||||
display: grid;
|
||||
max-height: 260px;
|
||||
overflow: auto;
|
||||
border: 1px solid #cbd8e0;
|
||||
border: 1px solid #dfc8b7;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 16px 40px rgba(23, 33, 43, 0.14);
|
||||
box-shadow: 0 16px 40px rgba(112, 57, 30, 0.14);
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
@@ -453,7 +453,7 @@ button:disabled {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
border-radius: 7px;
|
||||
color: #17212b;
|
||||
color: #3d1f10;
|
||||
background: transparent;
|
||||
padding: 9px 10px;
|
||||
text-align: left;
|
||||
@@ -461,12 +461,12 @@ button:disabled {
|
||||
|
||||
.admin-combobox-option:hover,
|
||||
.admin-combobox-option:focus-visible {
|
||||
background: #e7f3f5;
|
||||
background: #f4e5d7;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.admin-combobox-option span {
|
||||
color: #0f5666;
|
||||
color: #8f3f27;
|
||||
font-family:
|
||||
"SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-size: 12px;
|
||||
@@ -475,7 +475,7 @@ button:disabled {
|
||||
|
||||
.admin-combobox-option small,
|
||||
.admin-combobox-empty {
|
||||
color: #667682;
|
||||
color: #8f7868;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 1.45;
|
||||
@@ -495,7 +495,7 @@ button:disabled {
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
min-height: 42px;
|
||||
color: #4c5c68;
|
||||
color: #6f5848;
|
||||
font-size: 13px;
|
||||
font-weight: 650;
|
||||
}
|
||||
@@ -503,7 +503,7 @@ button:disabled {
|
||||
.admin-switch-field input {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
accent-color: #126e82;
|
||||
accent-color: #b6623f;
|
||||
}
|
||||
|
||||
.admin-primary-button,
|
||||
@@ -522,7 +522,7 @@ button:disabled {
|
||||
z-index: 80;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: rgba(23, 33, 43, 0.42);
|
||||
background: rgba(61, 31, 16, 0.34);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
@@ -530,10 +530,10 @@ button:disabled {
|
||||
display: grid;
|
||||
width: min(100%, 420px);
|
||||
gap: 16px;
|
||||
border: 1px solid #d8e2e8;
|
||||
border: 1px solid #e1ccbb;
|
||||
border-radius: 10px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 22px 60px rgba(23, 33, 43, 0.24);
|
||||
box-shadow: 0 22px 60px rgba(112, 57, 30, 0.24);
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
@@ -543,10 +543,10 @@ button:disabled {
|
||||
max-height: min(90dvh, 760px);
|
||||
gap: 16px;
|
||||
overflow: auto;
|
||||
border: 1px solid #d8e2e8;
|
||||
border: 1px solid #e1ccbb;
|
||||
border-radius: 10px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 22px 60px rgba(23, 33, 43, 0.24);
|
||||
box-shadow: 0 22px 60px rgba(112, 57, 30, 0.24);
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
@@ -557,7 +557,7 @@ button:disabled {
|
||||
.admin-confirm-warning {
|
||||
border: 1px solid #efc894;
|
||||
border-radius: 8px;
|
||||
color: #8a5a1b;
|
||||
color: #8f4b26;
|
||||
background: #fffaf3;
|
||||
padding: 10px 12px;
|
||||
font-size: 13px;
|
||||
@@ -576,26 +576,26 @@ button:disabled {
|
||||
|
||||
.admin-primary-button {
|
||||
color: #ffffff;
|
||||
background: #126e82;
|
||||
background: #b6623f;
|
||||
}
|
||||
|
||||
.admin-secondary-button,
|
||||
.admin-icon-button {
|
||||
border: 1px solid #cbd8e0;
|
||||
color: #2f4550;
|
||||
border: 1px solid #dfc8b7;
|
||||
color: #4b2412;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.admin-danger-button {
|
||||
color: #ffffff;
|
||||
background: #a44242;
|
||||
background: #a6402f;
|
||||
}
|
||||
|
||||
.admin-ghost-button {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
color: #52616d;
|
||||
background: #eef3f6;
|
||||
color: #755a49;
|
||||
background: #f8efe7;
|
||||
}
|
||||
|
||||
.admin-ghost-button.admin-query-reset-button {
|
||||
@@ -608,7 +608,7 @@ button:disabled {
|
||||
.admin-text-button {
|
||||
display: inline;
|
||||
border: 0;
|
||||
color: #0f5666;
|
||||
color: #8f3f27;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
@@ -616,9 +616,9 @@ button:disabled {
|
||||
}
|
||||
|
||||
.admin-alert {
|
||||
border: 1px solid #efc0bd;
|
||||
border: 1px solid #e2b9a4;
|
||||
border-radius: 8px;
|
||||
color: #8a2f2f;
|
||||
color: #8f3f27;
|
||||
background: #fff4f3;
|
||||
padding: 10px 12px;
|
||||
font-size: 13px;
|
||||
@@ -638,7 +638,7 @@ button:disabled {
|
||||
}
|
||||
|
||||
.admin-info-list dt {
|
||||
color: #667682;
|
||||
color: #8f7868;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@@ -646,7 +646,7 @@ button:disabled {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
overflow-wrap: anywhere;
|
||||
color: #17212b;
|
||||
color: #3d1f10;
|
||||
font-size: 13px;
|
||||
font-weight: 650;
|
||||
}
|
||||
@@ -666,26 +666,26 @@ button:disabled {
|
||||
|
||||
.admin-table th,
|
||||
.admin-table td {
|
||||
border-bottom: 1px solid #e4edf2;
|
||||
border-bottom: 1px solid #eaded2;
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.admin-table th {
|
||||
color: #667682;
|
||||
color: #8f7868;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.admin-table td small {
|
||||
display: block;
|
||||
margin-top: 3px;
|
||||
color: #667682;
|
||||
color: #8f7868;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.admin-muted-text {
|
||||
color: #86939c;
|
||||
color: #a38f80;
|
||||
}
|
||||
|
||||
.admin-tag-list {
|
||||
@@ -699,10 +699,10 @@ button:disabled {
|
||||
display: inline-flex;
|
||||
max-width: 100%;
|
||||
align-items: center;
|
||||
border: 1px solid #cbdfe6;
|
||||
border: 1px solid #dfc8b7;
|
||||
border-radius: 999px;
|
||||
background: #eef7f8;
|
||||
color: #0f5666;
|
||||
background: #f7eadf;
|
||||
color: #8f3f27;
|
||||
padding: 3px 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 750;
|
||||
@@ -744,7 +744,7 @@ button:disabled {
|
||||
gap: 6px;
|
||||
max-width: 100%;
|
||||
border: 0;
|
||||
color: #667682;
|
||||
color: #8f7868;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
@@ -773,7 +773,7 @@ button:disabled {
|
||||
.admin-table-sort-button:hover,
|
||||
.admin-table-sort-button:focus-visible,
|
||||
.admin-table-sort-button[data-active="true"] {
|
||||
color: #0f5666;
|
||||
color: #8f3f27;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -782,7 +782,7 @@ button:disabled {
|
||||
max-height: 160px;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
color: #2f4550;
|
||||
color: #4b2412;
|
||||
font-family:
|
||||
"SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-size: 12px;
|
||||
@@ -801,18 +801,18 @@ button:disabled {
|
||||
}
|
||||
|
||||
.admin-status-ok {
|
||||
color: #17623c;
|
||||
background: #e6f5ed;
|
||||
color: #2f7b46;
|
||||
background: #edf8ef;
|
||||
}
|
||||
|
||||
.admin-status-pending {
|
||||
color: #8a5a1b;
|
||||
background: #fff4df;
|
||||
color: #8f4b26;
|
||||
background: #fdf1e5;
|
||||
}
|
||||
|
||||
.admin-status-error {
|
||||
color: #8a2f2f;
|
||||
background: #fff1ef;
|
||||
color: #8f3f27;
|
||||
background: #fff0e9;
|
||||
}
|
||||
|
||||
.admin-error-list {
|
||||
@@ -820,7 +820,7 @@ button:disabled {
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
color: #8a5a1b;
|
||||
color: #8f4b26;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
@@ -830,7 +830,7 @@ button:disabled {
|
||||
}
|
||||
|
||||
.admin-subsection-heading {
|
||||
color: #4c5c68;
|
||||
color: #6f5848;
|
||||
font-size: 13px;
|
||||
font-weight: 650;
|
||||
}
|
||||
@@ -850,7 +850,7 @@ button:disabled {
|
||||
.admin-header-row input {
|
||||
min-width: 0;
|
||||
min-height: 38px;
|
||||
border: 1px solid #cbd8e0;
|
||||
border: 1px solid #dfc8b7;
|
||||
border-radius: 8px;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
@@ -863,10 +863,10 @@ button:disabled {
|
||||
max-height: 520px;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
border: 1px solid #dce6ec;
|
||||
border: 1px solid #eaded2;
|
||||
border-radius: 8px;
|
||||
background: #17212b;
|
||||
color: #e9f2f4;
|
||||
background: #3d1f10;
|
||||
color: #fdf9f5;
|
||||
padding: 14px;
|
||||
font-family:
|
||||
"SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
@@ -880,19 +880,19 @@ button:disabled {
|
||||
display: grid;
|
||||
min-height: 140px;
|
||||
place-items: center;
|
||||
border: 1px dashed #cbd8e0;
|
||||
border: 1px dashed #dfc8b7;
|
||||
border-radius: 8px;
|
||||
color: #667682;
|
||||
background: #fbfdfe;
|
||||
color: #8f7868;
|
||||
background: #fffdf9;
|
||||
}
|
||||
|
||||
.admin-segmented-control {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 6px;
|
||||
border: 1px solid #d8e2e8;
|
||||
border: 1px solid #e1ccbb;
|
||||
border-radius: 8px;
|
||||
background: #eef3f6;
|
||||
background: #f8efe7;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
@@ -900,15 +900,15 @@ button:disabled {
|
||||
min-height: 36px;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
color: #52616d;
|
||||
color: #755a49;
|
||||
background: transparent;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.admin-segmented-control button[data-active="true"] {
|
||||
color: #0f5666;
|
||||
color: #8f3f27;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 2px 8px rgba(23, 33, 43, 0.08);
|
||||
box-shadow: 0 2px 8px rgba(112, 57, 30, 0.08);
|
||||
}
|
||||
|
||||
.admin-bottom-nav {
|
||||
@@ -964,7 +964,7 @@ button:disabled {
|
||||
z-index: 20;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(64px, 1fr));
|
||||
border-top: 1px solid #d8e2e8;
|
||||
border-top: 1px solid #e1ccbb;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
padding: 8px 10px calc(8px + env(safe-area-inset-bottom));
|
||||
backdrop-filter: blur(10px);
|
||||
@@ -974,15 +974,15 @@ button:disabled {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
min-height: 48px;
|
||||
color: #667682;
|
||||
color: #8f7868;
|
||||
background: transparent;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.admin-bottom-nav-button[data-active="true"] {
|
||||
color: #0f5666;
|
||||
background: #e7f3f5;
|
||||
color: #8f3f27;
|
||||
background: #f4e5d7;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -170,6 +170,8 @@ http {
|
||||
|
||||
location ~ ^/api(?:/|$) {
|
||||
default_type application/json;
|
||||
# 中文注释:创作接口会携带参考图 Data URL,Nginx 只放行到 api-server;真实大小限制仍由路由 DefaultBodyLimit 和业务字节校验负责。
|
||||
client_max_body_size 64m;
|
||||
limit_conn genarrative_api_conn 64;
|
||||
limit_req zone=genarrative_api_rps burst=64 nodelay;
|
||||
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
本配置片段由 `scripts/jenkins-server-provision.sh` 在安装 Nginx 站点配置时展开。
|
||||
|
||||
## 请求体大小
|
||||
|
||||
- 生产、开发服和容器模板都在通用 `location ~ ^/api(?:/|$)` 内设置 `client_max_body_size 64m`。
|
||||
- 该值只用于让携带参考图 Data URL 的创作接口抵达 `api-server`;不要把它当作业务上传上限。Rust 路由仍通过 `DefaultBodyLimit` 和解码后字节校验限制具体接口,例如拼图参考图路由只放宽到 12 MiB 请求体,图片字节继续按业务规则拒绝。
|
||||
- 若线上看到 `413 Request Entity Too Large`,并且 access log 里 `request_time=0.000 upstream_status=-`,通常是 Nginx 没有加载该模板或未 reload;先执行 `nginx -T | grep client_max_body_size` 和 `nginx -t` 再检查 `api-server`。
|
||||
|
||||
## gzip
|
||||
|
||||
- `deploy/nginx/genarrative.conf` 与 `deploy/nginx/genarrative-dev-http.conf` 默认开启 gzip。
|
||||
|
||||
@@ -190,6 +190,8 @@ server {
|
||||
# 临时兼容主站仍在使用的 /api/* HTTP facade;前端完成 SpacetimeDB SDK 迁移后删除。
|
||||
location ~ ^/api(?:/|$) {
|
||||
default_type application/json;
|
||||
# 中文注释:创作接口会携带参考图 Data URL,Nginx 只放行到 api-server;真实大小限制仍由路由 DefaultBodyLimit 和业务字节校验负责。
|
||||
client_max_body_size 64m;
|
||||
limit_conn genarrative_api_conn 64;
|
||||
limit_req zone=genarrative_api_rps burst=64 nodelay;
|
||||
|
||||
|
||||
@@ -210,6 +210,8 @@ server {
|
||||
# 临时兼容主站仍在使用的 /api/* HTTP facade;前端完成 SpacetimeDB SDK 迁移后删除。
|
||||
location ~ ^/api(?:/|$) {
|
||||
default_type application/json;
|
||||
# 中文注释:创作接口会携带参考图 Data URL,Nginx 只放行到 api-server;真实大小限制仍由路由 DefaultBodyLimit 和业务字节校验负责。
|
||||
client_max_body_size 64m;
|
||||
limit_conn genarrative_api_conn 64;
|
||||
limit_req zone=genarrative_api_rps burst=64 nodelay;
|
||||
|
||||
|
||||
@@ -13,7 +13,11 @@
|
||||
重点补充:RPG 创作与运行时脚本职责地图见 [RPG_CREATION_AND_RUNTIME_SCRIPT_RESPONSIBILITY_MAP_2026-04-28.md](./reference/RPG_CREATION_AND_RUNTIME_SCRIPT_RESPONSIBILITY_MAP_2026-04-28.md)。
|
||||
- [埋点查询](./tracking/README.md):埋点原始事件与聚合投影的本地 SQL 查询。
|
||||
- [运营查询](./operations/README.md):任务、领奖、钱包对账等后台核查查询。
|
||||
- [PRD](./prd/README.md):产品需求与阶段计划;参考 MOKU / 幕间类 AI 文游的陶泥儿 `text-game` 模板口径见 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md](./prd/AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md),视觉小说模板 TXT 玩法平台化接入口径见 [AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md](./prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md),创意互动内容 Agent Phase 1 的 LangChain-Rust PoC、拼图闭环和并行任务拆分见 [CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md](./prd/CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md),幸存者类模板闭环见 [AI_NATIVE_SURVIVOR_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-05.md](./prd/AI_NATIVE_SURVIVOR_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-05.md),后台管理独立前端工程见 [ADMIN_WEB_CONSOLE_PRD_2026-04-30.md](./prd/ADMIN_WEB_CONSOLE_PRD_2026-04-30.md),新增 RPG 开场动画方案见 [AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md](./prd/AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md),新增抓大鹅 Match3D 玩法方案见 [AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md](./prd/AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md),方洞挑战创作、发布与试玩闭环见 [AI_NATIVE_SQUARE_HOLE_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-04.md](./prd/AI_NATIVE_SQUARE_HOLE_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-04.md)。
|
||||
- [PRD](./prd/README.md):产品需求与阶段计划;参考 MOKU / 幕间类 AI 文游的陶泥儿 `text-game` 模板口径见 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md](./prd/AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md),视觉小说模板 TXT 玩法平台化接入口径见 [AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md](./prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md),创意互动内容 Agent Phase 1 的 LangChain-Rust PoC、拼图闭环和并行任务拆分见 [CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md](./prd/CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md),幸存者类模板闭环见 [AI_NATIVE_SURVIVOR_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-05.md](./prd/AI_NATIVE_SURVIVOR_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-05.md),后台管理独立前端工程见 [ADMIN_WEB_CONSOLE_PRD_2026-04-30.md](./prd/ADMIN_WEB_CONSOLE_PRD_2026-04-30.md),新增 RPG 开场动画方案见 [AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md](./prd/AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md),新增抓大鹅 Match3D 玩法方案见 [AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md](./prd/AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md),方洞挑战创作、发布与试玩闭环见 [AI_NATIVE_SQUARE_HOLE_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-04.md](./prd/AI_NATIVE_SQUARE_HOLE_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-04.md),跳一跳俯视角玩法模板 PRD 见 [【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md](./prd/%E3%80%90%E7%8E%A9%E6%B3%95%E5%88%9B%E4%BD%9C%E3%80%91%E8%B7%B3%E4%B8%80%E8%B7%B3%E4%BF%AF%E8%A7%86%E8%A7%92%E7%8E%A9%E6%B3%95%E6%A8%A1%E6%9D%BFPRD-2026-05-19.md)。
|
||||
|
||||
拼图生成页步骤真进度、步骤内假进度和精简展示口径见 [【玩法创作】拼图生成页进度口径-2026-05-23.md](./%E3%80%90%E7%8E%A9%E6%B3%95%E5%88%9B%E4%BD%9C%E3%80%91%E6%8B%BC%E5%9B%BE%E7%94%9F%E6%88%90%E9%A1%B5%E8%BF%9B%E5%BA%A6%E5%8F%A3%E5%BE%84-2026-05-23.md)。
|
||||
|
||||
从文字需求生成高一致性美术素材流程抽象出的发明专利交底稿见 [【专利交底】一种极低成本快速生成高质量2D小游戏高一致性美术素材的解决方案-2026-05-25.md](./%E3%80%90%E4%B8%93%E5%88%A9%E4%BA%A4%E5%BA%95%E3%80%91%E4%B8%80%E7%A7%8D%E6%9E%81%E4%BD%8E%E6%88%90%E6%9C%AC%E5%BF%AB%E9%80%9F%E7%94%9F%E6%88%90%E9%AB%98%E8%B4%A8%E9%87%8F2D%E5%B0%8F%E6%B8%B8%E6%88%8F%E9%AB%98%E4%B8%80%E8%87%B4%E6%80%A7%E7%BE%8E%E6%9C%AF%E7%B4%A0%E6%9D%90%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88-2026-05-25.md)。
|
||||
|
||||
生产部署切换到 systemd + Nginx + SpacetimeDB 自托管的总方案见 [PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md](./technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md),该文档也是当前生产 Jenkinsfile 的唯一入口。SpacetimeDB 表结构变更、自动迁移边界和保留旧数据的分阶段迁移流程见 [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md);private 表迁移 JSON 导入导出、HTTP 413 分片导入和旧数据库迁移流水线经验见 [SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md](./technical/SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md) 与 [JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md](./technical/JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md);后台管理独立前端工程技术方案见 [ADMIN_WEB_CONSOLE_TECHNICAL_SOLUTION_2026-04-30.md](./technical/ADMIN_WEB_CONSOLE_TECHNICAL_SOLUTION_2026-04-30.md)。
|
||||
|
||||
|
||||
48
docs/design/【前端体验】寓教于乐Toca式横向世界地图入口概念图-2026-05-23.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# 寓教于乐 马路街区式横向世界入口概念图
|
||||
|
||||
更新时间:`2026-05-23`
|
||||
|
||||
## 背景
|
||||
|
||||
寓教于乐板块需要继续探索图形化玩法入口。前一轮“乐园地图 / 世界地图”方向已被证明不够贴近参考图,本轮进一步收敛为“中央马路串联主题小建筑群街区”的结构,让每一屏都像一个可横向滑动的儿童小镇街区。
|
||||
|
||||
## 参考结论
|
||||
|
||||
从参考图和视频里提炼出的关键结构是:
|
||||
|
||||
1. 每一屏都是一组主题小建筑群,不是稀疏乐园点位。
|
||||
2. 中央马路是主路径,汽车沿路通过,边缘继续延伸到下一屏。
|
||||
3. 建筑群贴着道路两侧聚合,形成清晰街区,而不是围绕中央广场或环形路径展开。
|
||||
4. 左右两边都要有明确“可继续探索”的出画感,方便后续做横向滑动世界。
|
||||
|
||||
## 目标
|
||||
|
||||
- 视觉上像可滑动的儿童街区地图。
|
||||
- 每一屏都能作为独立探索单元,并且能自然接到前后相邻屏。
|
||||
- 保持寓教于乐既有的卡通绘本风,不借用真实品牌乐园或现成 IP。
|
||||
- 适合后续叠加入口按钮、焦点框和中文标题。
|
||||
|
||||
## 本次概念方向
|
||||
|
||||
1. 识物认知主街
|
||||
2. 绘画创作工坊街
|
||||
3. 运动音乐街区
|
||||
4. 自然探索实验大道
|
||||
|
||||
## 推荐方向
|
||||
|
||||
优先推荐 `绘画创作工坊街` 与 `运动音乐街区`。这两条在当前批次里最接近“中央马路 + 两侧主题小建筑群 + 左右可延展”的参考结构。
|
||||
|
||||
## 生图脚本
|
||||
|
||||
- 生成脚本:`scripts/generate-edutainment-road-town-map-concepts.mjs`
|
||||
- 输出目录:`output/imagegen/edutainment-road-town-map-concepts-20260523/`
|
||||
- 风格参考:`public/child-motion-demo/picture-book-grass-stage.png`
|
||||
|
||||
## 说明
|
||||
|
||||
本次产物是设计概念稿,不直接进入正式资源目录。后续如果继续收敛,可以以 `城市公园脊` 为母版,向左、向右补相邻屏幕的区域内容。
|
||||
|
||||
## 当前结论
|
||||
|
||||
乐园分区结构可以抛弃,后续所有概念都优先按“马路街区式一屏一屏延展”的结构继续迭代。
|
||||
36
docs/design/【前端体验】寓教于乐电视端乐园地图入口概念图-2026-05-18.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# 寓教于乐电视端乐园地图入口概念图
|
||||
|
||||
更新时间:`2026-05-18`
|
||||
|
||||
## 背景
|
||||
|
||||
寓教于乐板块需要一个面向电视端 / 横屏大屏的图形化入口,整体感觉接近主题乐园地图,但必须保留 Genarrative 现有的明亮卡通绘本插画风,不借用任何真实品牌乐园或版权角色。
|
||||
|
||||
## 目标
|
||||
|
||||
- 远看像一个完整乐园地图,近看能分辨每个玩法入口。
|
||||
- 入口区域清晰分区,后续可以叠加焦点框、按钮和中文标题。
|
||||
- 中心和下方保留足够留白,适合遥控器焦点、儿童角色或主推荐位。
|
||||
- 风格保持和寓教于乐现有草地舞台资源一致。
|
||||
|
||||
## 本次概念方向
|
||||
|
||||
1. 环形乐园岛:中央草地广场 + 外圈入口环路。
|
||||
2. 展开绘本地图:横向展开的大绘本页,左右页自然衔接。
|
||||
3. 云朵空中岛:多个浮岛通过彩虹桥和云朵步道连接。
|
||||
4. 草地舞台地图:更接近实际运行态的横屏草地入口首屏。
|
||||
|
||||
## 推荐方向
|
||||
|
||||
优先推荐 `草地舞台地图` 作为后续落地主方向,因为它与现有寓教于乐草地舞台最接近,且中央下方留白最适合后续叠加交互焦点。
|
||||
|
||||
## 生图脚本
|
||||
|
||||
- 生成脚本:`scripts/generate-edutainment-tv-map-concepts.mjs`
|
||||
- 默认尺寸:`2048x1152`
|
||||
- 风格参考:`public/child-motion-demo/picture-book-grass-stage.png`
|
||||
- 输出目录:`output/imagegen/edutainment-tv-map-entry-concepts-20260518/`
|
||||
|
||||
## 说明
|
||||
|
||||
本次结果是设计概念稿,不直接进入 `public/` 正式资源目录。后续若要继续细化,可在同一脚本里增加新的横屏变体,并保持“不写文字、不露品牌 IP、绘本插画风”这三条底线。
|
||||
@@ -1,4 +1,4 @@
|
||||
# 宝贝识物寓教于乐模板 PRD 2026-05-11
|
||||
# 宝贝识物寓教于乐模板 PRD 2026-05-11
|
||||
|
||||
## 1. 目标
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
5. 游戏视觉主题包;
|
||||
6. 作品标签。
|
||||
|
||||
素材使用 VectorEngine `gpt-image-2-all` / image-2 生成。图片生成只能走后端接口,前端不得读取、拼接或暴露 `VECTOR_ENGINE_API_KEY`。
|
||||
素材使用 VectorEngine `gpt-image-2` / image-2 生成。图片生成只能走后端接口,前端不得读取、拼接或暴露 `VECTOR_ENGINE_API_KEY`。
|
||||
|
||||
为降低生成成本,创作提交后只生成两张原始图片:一张 `2x2` 素材 sheet 和一张单独场景背景图。`2x2` 素材 sheet 固定包含左上物品 A、右上物品 B、左下篮子、右下礼物盒。服务端必须按固定格切图,并把物品、篮子和礼物盒转成透明 PNG。只有透明抠图后的两个物品素材才允许写入草稿 `itemAssets` 并进入游戏运行态。左右手位置指示器属于运行态默认规则,使用项目内置静态素材,不在每次创作时生成。
|
||||
|
||||
|
||||
406
docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md
Normal file
@@ -0,0 +1,406 @@
|
||||
# 敲木鱼玩法模板 PRD 2026-05-20
|
||||
|
||||
## 1. 目标
|
||||
|
||||
新增一个可创作、可试玩、可发布的轻量休闲玩法模板:
|
||||
|
||||
```text
|
||||
敲木鱼
|
||||
```
|
||||
|
||||
模板按平台新增玩法 SOP 接入完整闭环:
|
||||
|
||||
```text
|
||||
创作入口 -> 工作台 -> 生成页 -> 结果页 -> 试玩 -> 发布 -> 运行态 -> 公开详情/分享
|
||||
```
|
||||
|
||||
首版默认屏幕中央展示内置卡通透明敲击物图案 `/wooden-fish/default-hit-object.png`。玩家点击运行态非功能区时触发一次敲击:播放敲击音效、敲击物图案执行被敲击动画,并在敲击物上方随机飘出一条祝福词。顶部只展示总数记录;子项计数收纳到总数卡片下方的折叠面板中,总数卡片点击后展开各子项计数,词条在面板中预置显示,未出现时初始值为 0,点击面板外收起。计数仅属于当前单次 run,不进入账号长期账本。
|
||||
|
||||
## 2. 模板定位
|
||||
|
||||
模板 ID:
|
||||
|
||||
```text
|
||||
wooden-fish
|
||||
```
|
||||
|
||||
用户展示名:
|
||||
|
||||
```text
|
||||
敲木鱼
|
||||
```
|
||||
|
||||
公开作品号前缀:
|
||||
|
||||
```text
|
||||
WF-*
|
||||
```
|
||||
|
||||
体验关键词:
|
||||
|
||||
1. 单屏点击;
|
||||
2. 轻量解压;
|
||||
3. 飘字反馈;
|
||||
4. 单局累计;
|
||||
5. 可自定义敲击物、敲击音效和祝福词。
|
||||
|
||||
## 3. 与拼图创作流程的复用边界
|
||||
|
||||
可以复用:
|
||||
|
||||
1. 创作入口配置、入口开关和作品架;
|
||||
2. 表单/图片输入工作台;
|
||||
3. 生成过程页和生成中恢复;
|
||||
4. 结果页的返回编辑、局部重生成、试玩、发布;
|
||||
5. 公开列表、公开详情、分享码和推荐流分发;
|
||||
6. 平台资产对象、OSS 私有读取换签和音频资产持久化能力。
|
||||
|
||||
不复用:
|
||||
|
||||
1. 拼图关卡、棋盘、拼块、排行榜和关卡推进语义;
|
||||
2. 跳一跳地块图集和蓄力判定语义;
|
||||
3. 抓大鹅物品消除、五视角图集和容器语义;
|
||||
4. 任何长期功德账本、账号维度排行榜或全局累计。
|
||||
|
||||
## 4. 创作工具平台接入声明
|
||||
|
||||
- 工作台模式:表单/图片输入创作工作台
|
||||
- 创作链路:入口 -> 工作台 -> 生成页 -> 结果页 -> 试玩 -> 发布 -> 运行态
|
||||
- 单图资产槽位:
|
||||
- `slotId=hit-object`
|
||||
- `slotType=hit-object-image`
|
||||
- `slotName=敲击物图案`
|
||||
- 提示词来源:`hitObjectPrompt` 与可选 `hitObjectReferenceImageSrc`
|
||||
- 写回字段:`hitObjectAsset`
|
||||
- 是否允许历史图:允许
|
||||
- 是否允许 AI 重绘:允许;上传图只作为 image2 参考,最终运行态只消费 image2 生成图
|
||||
- `slotId=background`
|
||||
- `slotType=background-image`
|
||||
- `slotName=背景环境图`
|
||||
- 提示词来源:第一步生成的敲击物图案与用户原始题材关键词 / 参考图主题
|
||||
- 写回字段:`backgroundAsset`
|
||||
- 是否允许历史图:不单独选择;由敲击物图案生成链路派生
|
||||
- 是否允许 AI 重绘:允许;随敲击物图案一起重生成
|
||||
- 系列素材槽位:无;首版只有敲击物图案与背景环境图两个单图资产,不生成图集
|
||||
- 音频资产槽位:
|
||||
- `slotId=hit-sound`
|
||||
- `slotType=hit-sound-audio`
|
||||
- `slotName=敲击音效`
|
||||
- 来源:用户上传/麦克风录制音频,或使用默认木鱼音
|
||||
- 写回字段:`hitSoundAsset`
|
||||
- 默认兜底:`/wooden-fish/default-hit-sound.mp3`
|
||||
- API 命名空间:
|
||||
- `/api/creation/wooden-fish/...`
|
||||
- `/api/runtime/wooden-fish/...`
|
||||
- 业务真相:
|
||||
- 后端裁决并持久化 session、work profile、发布状态、run 摘要和公开投影;
|
||||
- 前端只负责点击低延迟表现、音频播放、动画、飘字渲染和定期 checkpoint。
|
||||
- 创作工具模式例外:无
|
||||
- 验证命令:
|
||||
- `npm run check:encoding`
|
||||
- `npm run typecheck`
|
||||
- `cargo test -p shared-contracts wooden_fish --manifest-path server-rs/Cargo.toml`
|
||||
- `cargo test -p module-wooden-fish --manifest-path server-rs/Cargo.toml`
|
||||
- `cargo check -p api-server --manifest-path server-rs/Cargo.toml`
|
||||
- `npm run spacetime:generate`
|
||||
- `npm run check:spacetime-schema`
|
||||
- `npm run dev:api-server` 后检查 `/healthz`
|
||||
|
||||
## 5. 创作输入
|
||||
|
||||
工作台提交结构化 payload,不提交聊天消息。
|
||||
|
||||
必填字段:
|
||||
|
||||
1. `templateId = "wooden-fish"`;
|
||||
2. `hitObjectPrompt`:用户想敲的对象关键词或描述,默认“默认敲击物图案,圆润木质质感,透明背景”;
|
||||
3. `floatingWords[]`:祝福词,最多 8 条,不填或清空时使用默认祝福词。
|
||||
|
||||
可选字段:
|
||||
|
||||
1. `hitObjectReferenceImageSrc`:上传或历史图引用,只能作为 image2 参考,不可直接进入运行态;
|
||||
2. `hitSoundPrompt`:历史兼容字段,当前创作流程不再使用;
|
||||
3. `hitSoundAsset`:用户上传、录音或默认音频资产。
|
||||
|
||||
结果页补录字段:
|
||||
|
||||
1. `workTitle`:作品标题,默认值在结果页可编辑;
|
||||
2. `workDescription`:作品简介;
|
||||
3. `themeTags[]`:最多 6 个标签,样式对齐拼图结果页标签编辑器。
|
||||
|
||||
创作界面默认祝福词:
|
||||
|
||||
```text
|
||||
幸运
|
||||
```
|
||||
|
||||
用户可通过加号继续新增 7 个词条,总数最多 8 条。新增词条右侧提供减号 / 删除小按钮;默认的第一个词条保留为普通输入格。
|
||||
|
||||
`floatingWords[]` 保存词条名本身,不保存 `+1` 后缀;运行态每次敲击时再把飘字展示为“词条+1”。
|
||||
|
||||
## 6. 生成规则
|
||||
|
||||
### 6.1 敲击物图案、背景环境图与返回按钮图
|
||||
|
||||
默认模板在用户未自定义关键词且未上传参考图时,`compile-draft` 使用内置透明 PNG `/wooden-fish/default-hit-object.png` 写回 `hitObjectAsset`,`generationProvider="bundled-default"`。这张图来自 image2 对原始参考图的卡通风格化重绘,固定为模板默认资源,避免默认关键词在每次生成时改变造型。即使使用内置默认敲击物,首版仍需要生成 `backgroundAsset` 与 `backButtonAsset`,背景环境图和主题返回按钮图都使用默认敲击物作为主题和画风参考。
|
||||
|
||||
用户输入自定义关键词、上传参考图,或在结果页主动重生成敲击物时,`compile-draft` 与 `regenerate-hit-object` 必须先为敲击物图案生成 image2 单图资产,再基于新敲击物图案生成背景环境图,最后基于去绿后的敲击物主体和背景环境图生成主题返回按钮图,并由 `api-server` 注入写回 `hitObjectAsset`、`backgroundAsset` 与 `backButtonAsset`。前端 action 请求不得自带 `hitObjectAsset`、`backgroundAsset` 或 `backButtonAsset` 短路生成。如果用户上传参考图,后端只能把该图作为 image2 参考图或主题参考;运行态不得直接使用上传图。
|
||||
|
||||
敲击物图案生成流程固定为:
|
||||
|
||||
1. 调用 VectorEngine `/v1/images/edits`,模型固定为 `gpt-image-2`;
|
||||
2. multipart 参考图固定包含默认木鱼图 `/wooden-fish/default-hit-object.png`,作为基础结构和画风参考;
|
||||
3. 若用户上传参考图,该图只作为新主题参考追加到同一次 image2 edits 请求,不直接进入运行态;
|
||||
4. 尺寸固定 `1:1`,必须输出绿色背景主体图(纯绿色绿幕),背景为单一纯绿色 `#00FF00`,并显式禁止黑底、白底、棋盘格、纸板底或任何其它实底背景;
|
||||
5. 提示词严格使用:
|
||||
|
||||
```text
|
||||
生成敲木鱼新样式,要求结构,画风与参考图保持高度一致,新样式颜色搭配使用新主题对应的颜色。尺寸1:1,先输出绿色背景主体图(纯绿色绿幕),背景必须是单一纯绿色 #00FF00 且平整无纹理、无渐变、无阴影、无道具,主体完整居中,主体边缘必须干净,不要直接输出透明底。随后由服务端对绿色背景主体图做抠图去除绿色背景。最终结果只保留单个敲击物图案,禁止黑底、白底、棋盘格、纸板底或任何实底背景;主体本身不要使用与绿幕接近的纯绿色,若新主题天然包含绿色,请改用偏深、偏黄或偏蓝的绿色并与绿幕清晰区分。
|
||||
新主题为:(用户提供参考图或用户输入关键词)
|
||||
```
|
||||
|
||||
敲击物图案落盘前,`api-server` 必须只对第一步生成的纯绿色绿幕背景执行去绿处理,把绿色背景转成真实透明 alpha PNG;不得对黑底、白底或其它未知实底执行泛抠图,避免误伤玉米等主体像素。去绿处理必须保留主体内部深色结构和主题细节。
|
||||
|
||||
背景环境图生成流程固定为:
|
||||
|
||||
1. 调用 VectorEngine `/v1/images/edits`,模型固定为 `gpt-image-2`;
|
||||
2. multipart 参考图固定为第一步敲击物图案抠图完成后的透明图;默认未生成新敲击物时使用内置默认敲击物图案的透明兜底图;
|
||||
3. 尺寸固定竖屏 `9:16`;
|
||||
4. 背景环境图只适配新敲击物主题和画风,背景中不得包含新敲击物本体,也不得增加木槌互动物品;中央主体预留区必须保持干净,画面中央 40% 区域禁止出现主题主体、主体局部特写、主体轮廓影子、重复元素或主题主体的局部碎片;
|
||||
5. 提示词严格使用:
|
||||
|
||||
```text
|
||||
生成敲木鱼背景,要求主题,画风与参考图保持高度一致,背景元素和颜色搭配与主题对应,木鱼预设在屏幕中央位置,木鱼主体周围元素保持干净,背景氛围围绕外围设计,背景环境图中不包含新木鱼物品,背景氛围中不增加木槌互动物品。尺寸竖屏9:16。参考图必须是第一步敲击物抠图完成后的透明图,不继承任何绿色底色、绿幕底色或纯绿色画布,并要求最终输出完整不透明的背景环境图。中央主体预留区必须保持干净,画面中央 40% 区域禁止出现主题主体、主体局部特写、主体轮廓影子、重复元素或主题主体的局部碎片;主题元素只允许出现在外围氛围,不得把主题物品画在画面中央,也不要把主题物品作为背景中心装饰。
|
||||
主题为:(用户提供参考图或用户输入关键词)
|
||||
```
|
||||
|
||||
返回按钮图生成流程固定为:
|
||||
|
||||
1. 调用 VectorEngine `/v1/images/edits`,模型固定为 `gpt-image-2`;
|
||||
2. multipart 参考图固定包含第一步去除绿色背景后的敲击物主体图,以及第二步生成的背景环境图;
|
||||
3. 尺寸固定 `1:1`,必须输出绿色背景主体图(纯绿色绿幕),后端落库前执行同一套去绿背景处理;
|
||||
4. 按主题、画风、材质和配色生成左上角返回按钮图,但参考图只用于约束圆形底色和中央左箭头的颜色搭配,不得借鉴复杂造型、花纹、浮雕边、异形外框或装饰图案;按钮必须始终是标准圆形,主体视觉尺寸比当前模板再放大约 50%,圆形外沿必须有与主题色搭配的干净外描边,中央只保留单个清晰左箭头或返回箭头,不得包含文字、数字、水印、额外 UI 面板、木槌或敲击道具;
|
||||
5. 提示词严格使用:
|
||||
|
||||
```text
|
||||
生成敲木鱼左上角返回按钮图。要求以参考图-去除绿色背景后的敲击物主体和背景环境图为主题、画风、材质和配色参考,但参考图只用来约束圆形底色和中央左箭头的颜色搭配,不要继承复杂造型、花纹、浮雕边、异形外框或装饰图案。按钮必须始终是标准圆形,整体像单个圆形图标,按钮主体在画布中的视觉尺寸比当前模板再放大约 50%,圆心居中,圆形外沿加一圈和主题色搭配的干净外描边,让它更像一个按钮,但仍然只保留一个清晰、简洁、居中的向左返回箭头,不要出现文字、数字、水印、按钮外标签、额外 UI 面板、木槌或敲击道具。尺寸1:1,输出绿色背景主体图(纯绿色绿幕),背景必须是单一纯绿色 #00FF00 且平整无纹理、无渐变、无阴影。按钮主体边缘干净,后续由服务端扣除绿色背景;按钮底色不要使用与绿幕接近的纯绿色,若主题天然包含绿色,请仅在圆形底色上使用偏深、偏黄或偏蓝的主题绿色,并用更高对比的箭头颜色区分。
|
||||
主题为:(用户提供参考图或用户输入关键词)
|
||||
```
|
||||
|
||||
落库链路固定为:`api-server` 调用 VectorEngine `/v1/images/edits` -> 服务端上传 OSS 私有对象 -> `confirm_asset_object` 登记资产对象 -> `bind_asset_object_to_entity` 绑定到 `entityKind='wooden_fish_work'`。敲击物绑定 `slot='hit_object'`、`assetKind='wooden_fish_hit_object'`,背景绑定 `slot='background'`、`assetKind='wooden_fish_background'`,返回按钮绑定 `slot='back_button'`、`assetKind='wooden_fish_back_button'`。写回时把 `legacyPublicPath` 分别写入 `hitObjectAsset.imageSrc`、`backgroundAsset.imageSrc` 与 `backButtonAsset.imageSrc`。不得只拼 `/generated-wooden-fish-assets/...` 占位路径;前端会对 generated legacy path 走 `/api/assets/read-url` 换签,OSS 中没有真实对象时图片无法显示。
|
||||
|
||||
默认图案要求:
|
||||
|
||||
1. 中央主体使用 `/wooden-fish/default-hit-object.png`;
|
||||
2. 透明背景;
|
||||
3. 适合移动端居中展示;
|
||||
4. 不包含 UI、按钮、说明文字、水印或品牌标识;
|
||||
5. 图片主体需留出敲击动画缩放空间。
|
||||
|
||||
### 6.2 敲击音效
|
||||
|
||||
音效统一写回 `hitSoundAsset`。
|
||||
|
||||
写回规则:
|
||||
|
||||
1. 若 payload 已包含上传/录音音频资产,`compile-draft` 跳过音效生成,直接持久化该资产;
|
||||
2. 若 payload 已上传或录制音频,则直接写回 `hitSoundAsset`;
|
||||
3. 若两者都没有,后端写回默认木鱼音 `/wooden-fish/default-hit-sound.mp3`;
|
||||
4. 音效资产必须包含可播放地址、对象键、asset object id、来源和可选时长;
|
||||
5. 通用创作音频接口当前对 `wooden_fish` 的 `hit_sound` 目标返回 `410 Gone`,不得在创作流程中按提示词生成音效;
|
||||
6. `spacetime-client` 不得自行合成 `/generated-wooden-fish-assets/...` 音效占位路径;缺少真实 `hitSoundAsset` 时应使用默认木鱼音兜底展示与播放。
|
||||
|
||||
### 6.3 封面
|
||||
|
||||
首版封面使用 `hitObjectAsset.imageSrc` 作为 `coverImageSrc`。背景环境图与返回按钮图不作为封面图。
|
||||
|
||||
## 7. 契约草案
|
||||
|
||||
`WoodenFishDraft` 至少包含:
|
||||
|
||||
1. `templateId = "wooden-fish"`;
|
||||
2. `templateName = "敲木鱼"`;
|
||||
3. `profileId`;
|
||||
4. `workTitle`;
|
||||
5. `workDescription`;
|
||||
6. `themeTags[]`;
|
||||
7. `hitObjectPrompt`;
|
||||
8. `hitObjectReferenceImageSrc`;
|
||||
9. `hitSoundPrompt`,历史兼容字段,当前创作流程恒为 `null`;
|
||||
10. `floatingWords[]`;
|
||||
11. `hitObjectAsset`;
|
||||
12. `backgroundAsset`;
|
||||
13. `backButtonAsset`;
|
||||
14. `hitSoundAsset`;
|
||||
15. `coverImageSrc`;
|
||||
16. `generationStatus`。
|
||||
|
||||
`WoodenFishImageAsset` 至少包含:
|
||||
|
||||
1. `assetId`;
|
||||
2. `imageSrc`;
|
||||
3. `imageObjectKey`;
|
||||
4. `assetObjectId`;
|
||||
5. `generationProvider`;
|
||||
6. `prompt`;
|
||||
7. `width`;
|
||||
8. `height`。
|
||||
|
||||
`WoodenFishAudioAsset` 至少包含:
|
||||
|
||||
1. `assetId`;
|
||||
2. `audioSrc`;
|
||||
3. `audioObjectKey`;
|
||||
4. `assetObjectId`;
|
||||
5. `source = uploaded | recorded | bundled-default`;
|
||||
6. `prompt`;
|
||||
7. `durationMs`。
|
||||
|
||||
`WoodenFishRunSnapshot` 至少包含:
|
||||
|
||||
1. `runId`;
|
||||
2. `profileId`;
|
||||
3. `ownerUserId`;
|
||||
4. `status = playing | finished`;
|
||||
5. `totalTapCount`;
|
||||
6. `wordCounters[]`;
|
||||
7. `startedAtMs`;
|
||||
8. `updatedAtMs`;
|
||||
9. `finishedAtMs`。
|
||||
|
||||
## 8. API 草案
|
||||
|
||||
HTTP 路由:
|
||||
|
||||
```text
|
||||
POST /api/creation/wooden-fish/sessions
|
||||
GET /api/creation/wooden-fish/sessions/{sessionId}
|
||||
POST /api/creation/wooden-fish/sessions/{sessionId}/actions
|
||||
GET /api/creation/wooden-fish/works
|
||||
GET /api/creation/wooden-fish/works/{profileId}
|
||||
POST /api/creation/wooden-fish/works/{profileId}/publish
|
||||
GET /api/runtime/wooden-fish/works/{profileId}
|
||||
POST /api/runtime/wooden-fish/runs
|
||||
POST /api/runtime/wooden-fish/runs/{runId}/checkpoint
|
||||
POST /api/runtime/wooden-fish/runs/{runId}/finish
|
||||
GET /api/runtime/wooden-fish/gallery
|
||||
GET /api/runtime/wooden-fish/gallery/{publicWorkCode}
|
||||
```
|
||||
|
||||
动作类型:
|
||||
|
||||
```text
|
||||
compile-draft
|
||||
regenerate-hit-object
|
||||
replace-hit-sound
|
||||
update-work-meta
|
||||
update-floating-words
|
||||
publish
|
||||
start-run
|
||||
checkpoint
|
||||
finish
|
||||
```
|
||||
|
||||
`compile-draft` 是长耗时动作。前端进入生成页后应展示可恢复进度;如果请求失败,标记失败前必须复读 session,确认后端是否已经生成并写回草稿。
|
||||
|
||||
敲木鱼创作请求在前端必须使用长等待窗口,避免 `createSession` 或 `executeAction` 仍沿用共享创作工厂默认的 15 秒超时。因为 `compile-draft` 会串行等待敲击物、背景、返回按钮三次 image2 和 OSS 落库,木鱼 client 需要单独配置与整条 image2 链路匹配的超时。本地测试中该 action 可能达到数分钟级;生成页进度必须按“整理草稿 -> 生成敲击物 -> 生成背景环境图 -> 生成返回按钮图 -> 写入正式草稿”展示,不展示“提示词生成音效”阶段,因为当前木鱼音效只支持上传、录音或默认音。
|
||||
|
||||
作品架使用 `GET /api/creation/wooden-fish/works` 读取当前用户草稿和已发布摘要,前端发布成功后必须刷新该列表和 `GET /api/runtime/wooden-fish/gallery` 公开列表,使刚发布作品立即出现在草稿 Tab 的已发布筛选和推荐 / 最新流中。
|
||||
|
||||
## 9. SpacetimeDB 表和 view
|
||||
|
||||
新增表:
|
||||
|
||||
1. `wooden_fish_agent_session`;
|
||||
2. `wooden_fish_work_profile`,其中 `background_asset_json` 保存背景环境图资产快照,`back_button_asset_json` 保存主题返回按钮图资产快照;
|
||||
3. `wooden_fish_runtime_run`;
|
||||
4. `wooden_fish_event`。
|
||||
|
||||
新增 view:
|
||||
|
||||
1. `wooden_fish_gallery_card_view`:公开列表卡片投影,只暴露已发布作品;
|
||||
2. `wooden_fish_gallery_view`:公开详情兼容投影,包含图案、背景、返回按钮、音效和祝福词配置。
|
||||
|
||||
新增或调整表、procedure、view 后必须同步 `migration.rs`、后端表目录、生成 bindings,并执行 `npm run check:spacetime-schema`。
|
||||
|
||||
## 10. 结果页能力
|
||||
|
||||
结果页必须展示:
|
||||
|
||||
1. 作品标题和简介;
|
||||
2. 竖屏背景环境图预览;
|
||||
3. 敲击物图案;
|
||||
4. 敲击音效试听;
|
||||
5. 祝福词配置;
|
||||
6. 标签;
|
||||
7. 试玩;
|
||||
8. 发布;
|
||||
9. 返回编辑。
|
||||
|
||||
结果页必须支持:
|
||||
|
||||
1. 重生成敲击物图案;
|
||||
2. 上传、录制或替换敲击音效;未提供时使用默认木鱼音;
|
||||
3. 修改标题、简介和标签,并在试玩或发布前写回当前作品信息;
|
||||
4. 修改祝福词,最多 8 条。
|
||||
|
||||
图案重生成是独立局部生成态,不得把已有可查看结果重新变成不可打开的全局生成中。音效替换只接受上传或录音资产,不触发提示词音效生成。
|
||||
|
||||
## 11. 运行态规则
|
||||
|
||||
运行态采用全屏单击模型。
|
||||
|
||||
功能区:
|
||||
|
||||
1. 顶部总数记录卡和其下拉的子项计数器面板;
|
||||
2. 设置、暂停、返回、发布分享等按钮;
|
||||
3. 结果弹层和音频授权提示。
|
||||
|
||||
点击规则:
|
||||
|
||||
1. 点击非功能区才算一次敲击;
|
||||
2. 每次敲击立即本地累加 `totalTapCount`;
|
||||
3. 随机等概率从 `floatingWords[]` 中取一个词条;
|
||||
4. 子项计数面板中预置展示所有词条,未出现词条初始值为 0;
|
||||
5. 后续同词条出现时对应计数器 +1;
|
||||
6. 播放敲击音效;
|
||||
7. 敲击物图案执行压缩、回弹或轻微震动动画;
|
||||
8. 木鱼上方飘出“词条+1”并淡出,飘字只显示文字本体,不加底板、胶囊背景或说明面板。
|
||||
|
||||
运行态左上角返回按钮必须优先使用 `backButtonAsset` 渲染主题化按钮图;缺失时才回退通用图标按钮。运行态不提供右上角重开按钮。
|
||||
|
||||
音频播放:
|
||||
|
||||
1. 前端使用小复音池;
|
||||
2. 设置最小播放间隔,避免极端连点导致浏览器抖动;
|
||||
3. 点击计数不能因为音频节流而丢失;
|
||||
4. 签名 URL 未就绪时先静音表现,不请求裸 generated 私有路径。
|
||||
|
||||
后端只保存 run 摘要,不保存每次点击的完整明细;`checkpoint` 和 `finish` 都写入总敲击次数与词条计数快照。
|
||||
|
||||
## 12. 公开链路
|
||||
|
||||
平台首页推荐、发现、公开详情、搜索、已玩作品和公开试玩统一按 `sourceType='wooden-fish'` 与 `WF-*` 公开作品号识别敲木鱼作品。
|
||||
|
||||
公开列表优先消费 `wooden_fish_gallery_card_view` 订阅缓存。公开详情如果卡片摘要不足以进入运行态,必须补读完整 work profile。
|
||||
|
||||
## 13. 验收
|
||||
|
||||
1. 创作入口能看到 `敲木鱼` 模板;
|
||||
2. 工作台可以填写敲击物描述、上传参考图、上传或录制音效、配置祝福词;
|
||||
3. 提交后按默认木鱼参考图生成 image2 敲击物图案;
|
||||
4. 提交后按新敲击物图案参考图生成 9:16 背景环境图;
|
||||
5. 提交后按去绿后的敲击物主体和背景环境图生成主题返回按钮图;
|
||||
6. 上传图不会直接进入运行态;
|
||||
7. 用户上传或录制音效时直接持久化该资产,未提供时使用默认木鱼音;
|
||||
8. 结果页能看到背景、图案、试听音效、编辑祝福词并试玩;
|
||||
9. 运行态功能区点击不触发敲击;
|
||||
10. 运行态左上角使用主题返回按钮图,右上角不出现重开按钮;
|
||||
11. 非功能区点击会计数、播放音效、播放敲击动画并飘出无底板大号文字;
|
||||
12. 顶部总数卡点击后展开子项计数器面板,面板内预置全部词条且未出现词条初始值为 0,面板外点击可收起;
|
||||
13. 连点不丢计数;
|
||||
14. `checkpoint` 和 `finish` 只保存单次 run 摘要;
|
||||
15. 作品可以发布、进入公开列表和公开详情;
|
||||
16. `WF-*` 公开作品号能进入分享和运行态;
|
||||
17. `npm run check:encoding` 通过;
|
||||
18. schema 变更后 `npm run check:spacetime-schema` 通过。
|
||||
492
docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md
Normal file
@@ -0,0 +1,492 @@
|
||||
# 跳一跳俯视角玩法模板 PRD 2026-05-19
|
||||
|
||||
## 1. 目标
|
||||
|
||||
新增一个可创作、可试玩、可发布的玩法模板:
|
||||
|
||||
```text
|
||||
跳一跳
|
||||
```
|
||||
|
||||
本模板参考拼图模板的创作闭环,沿用“创作入口 -> 生成过程页 -> 结果页 -> 试玩 -> 发布”的平台链路,但玩法本体改为俯视角 / 等距视角的跳跃闯关。
|
||||
|
||||
首版要求:
|
||||
|
||||
1. 初始草稿生成时,角色形象单独调用一次生图;
|
||||
2. 初始草稿生成时,地块只调用一次生图,输出 3D 视图的 2D 图片图集;
|
||||
3. 运行态不接真实 3D 网格,不生成 GLB / glTF;
|
||||
4. 作品可以直接进入试玩和发布。
|
||||
|
||||
## 2. 模板定位
|
||||
|
||||
模板 ID:
|
||||
|
||||
```text
|
||||
jump-hop
|
||||
```
|
||||
|
||||
用户展示名:
|
||||
|
||||
```text
|
||||
跳一跳
|
||||
```
|
||||
|
||||
体验关键词:
|
||||
|
||||
1. 俯视角;
|
||||
2. 等距感地块;
|
||||
3. 单局闯关;
|
||||
4. 长按蓄力,松手起跳;
|
||||
5. 轻量休闲。
|
||||
|
||||
首版采用竖屏优先的移动端体验,桌面端保持居中展示,画面比例以 `9:16` 为主。参考图的核心视觉要点是:
|
||||
|
||||
1. 大面积留白或浅色渐变背景;
|
||||
2. 角色站在单个地块上;
|
||||
3. 地块有明显顶面、侧面和投影;
|
||||
4. 整体是俯视角 / 等距视角,而不是横版平台跳跃;
|
||||
5. UI 克制,只保留必要控制,不堆说明文案。
|
||||
|
||||
## 3. 与拼图模板的复用边界
|
||||
|
||||
可以复用:
|
||||
|
||||
1. 创作入口和模板分流;
|
||||
2. 生成过程页;
|
||||
3. 结果页的草稿保存、返回编辑、试玩、发布、分享链路;
|
||||
4. 作品架展示和草稿恢复口径;
|
||||
5. 平台统一的发布与公开展示流程。
|
||||
|
||||
不复用:
|
||||
|
||||
1. 拼图关卡切片逻辑;
|
||||
2. 拼图拖拽拼块逻辑;
|
||||
3. 拼图 UI 背景和多关卡编辑结构;
|
||||
4. 任何方格拼合语义。
|
||||
|
||||
## 4. 工程接入范围
|
||||
|
||||
首版需要做到完整玩法闭环,不只做入口占位。
|
||||
|
||||
新增前端阶段:
|
||||
|
||||
```text
|
||||
jump-hop-workspace
|
||||
jump-hop-generating
|
||||
jump-hop-result
|
||||
jump-hop-runtime
|
||||
jump-hop-gallery-detail
|
||||
```
|
||||
|
||||
新增前端组件建议:
|
||||
|
||||
1. `src/components/jump-hop-creation/JumpHopWorkspace.tsx`;
|
||||
2. `src/components/jump-hop-result/JumpHopResultView.tsx`;
|
||||
3. `src/components/jump-hop-runtime/JumpHopRuntimeShell.tsx`;
|
||||
4. `src/services/jump-hop/jumpHopClient.ts`。
|
||||
|
||||
新增共享契约建议:
|
||||
|
||||
1. `packages/shared/src/contracts/jumpHop.ts`;
|
||||
2. `server-rs/crates/shared-contracts/src/jump_hop.rs`。
|
||||
|
||||
新增后端模块建议:
|
||||
|
||||
1. `server-rs/crates/module-jump-hop`:纯领域规则,包含路径生成、蓄力换算、落点判定、通关 / 失败状态机;
|
||||
2. `server-rs/crates/api-server/src/jump_hop.rs` 和 `src/jump_hop/` 子模块:HTTP handler、生成编排、资产保存和 DTO 映射;
|
||||
3. `server-rs/crates/spacetime-module/src/jump_hop.rs`:session、work profile、runtime run、公开 view 和 reducer / procedure;
|
||||
4. `server-rs/crates/spacetime-client/src/jump_hop.rs`:api-server 访问 SpacetimeDB 的 facade;
|
||||
5. `server-rs/crates/api-server/src/modules/jump_hop.rs`:路由挂载。
|
||||
|
||||
入口配置事实源必须走 SpacetimeDB `creation_entry_type_config` 默认种子和后台配置接口,不新增前端硬编码入口配置。
|
||||
|
||||
## 5. 创作输入
|
||||
|
||||
创作者需要填写以下内容:
|
||||
|
||||
1. 作品主题描述,必填;
|
||||
2. 角色形象描述,必填;
|
||||
3. 地块风格卡,必选;
|
||||
4. 难度,必选;
|
||||
5. 可选的终点氛围或节奏偏好。
|
||||
|
||||
推荐的最小输入形态是:
|
||||
|
||||
1. 一句话主题;
|
||||
2. 角色一句话描述;
|
||||
3. 风格卡;
|
||||
4. 难度卡。
|
||||
|
||||
不在首版开放手工拖拽平台编辑器。平台路径、地块间距和终点位置由系统自动生成,创作者只负责风格与难度选择。
|
||||
|
||||
### 5.1 地块风格卡
|
||||
|
||||
建议提供以下风格:
|
||||
|
||||
1. 极简积木;
|
||||
2. 纸模玩具;
|
||||
3. 霓虹玻璃;
|
||||
4. 森林石块;
|
||||
5. 未来金属;
|
||||
6. 自定义。
|
||||
|
||||
### 5.2 难度
|
||||
|
||||
建议提供以下离散档位:
|
||||
|
||||
1. 轻松;
|
||||
2. 标准;
|
||||
3. 进阶;
|
||||
4. 挑战。
|
||||
|
||||
难度主要影响:
|
||||
|
||||
1. 平台路径长度;
|
||||
2. 平台间距;
|
||||
3. 可落点容差;
|
||||
4. 完美落点窗口;
|
||||
5. 终点前的节奏变化。
|
||||
|
||||
## 6. 生成规则
|
||||
|
||||
本模板必须把生图责任拆成两条独立链路:
|
||||
|
||||
### 6.1 角色形象只生一次
|
||||
|
||||
角色形象必须只调用一次生图,输出一张可直接进入运行态的主角色图。
|
||||
|
||||
角色图要求:
|
||||
|
||||
1. 单人主角;
|
||||
2. 全身可见;
|
||||
3. 透明背景;
|
||||
4. 角色站姿或轻微前倾姿态;
|
||||
5. 镜头和透视必须匹配俯视角场景;
|
||||
6. 不要求多视角,不要求多帧动画图集。
|
||||
|
||||
角色图生成后作为作品级锚点资产使用,结果页、封面合成、试玩和发布都复用同一张图。后续如果只修改标题、标签、难度或路径,不应默认重新生角色。只有用户在结果页明确点击“重生成角色”时,才允许再调用一次角色生图。
|
||||
|
||||
### 6.2 地块只生一次图集
|
||||
|
||||
地块必须只调用一次生图,输出一张 3D 视图的 2D 图片图集,再由后端切成运行态可用的地块资产。该图集使用跳一跳专用 `2行*3列` 六格布局,不套用通用“每个物品一行、每行 n 个不同视图”的系列素材模型。
|
||||
|
||||
地块图集要求:
|
||||
|
||||
1. 统一使用等距 / 俯视角;
|
||||
2. 必须表现出顶面、侧面和投影;
|
||||
3. 必须与角色图保持同一光向;
|
||||
4. 必须有清晰的立体层次,但仍然是 2D 图片;
|
||||
5. 六格必须按固定顺序包含以下地块类型:
|
||||
- 起点地块;
|
||||
- 普通地块;
|
||||
- 目标地块;
|
||||
- 终点地块;
|
||||
- 奖励地块;
|
||||
- 视觉强调地块。
|
||||
|
||||
固定格位为:
|
||||
|
||||
| 格位 | tileType | 语义 |
|
||||
| --- | --- | --- |
|
||||
| 第 1 行第 1 列 | `start` | 起点地块 |
|
||||
| 第 1 行第 2 列 | `normal` | 普通地块 |
|
||||
| 第 1 行第 3 列 | `target` | 目标地块 |
|
||||
| 第 2 行第 1 列 | `finish` | 终点地块 |
|
||||
| 第 2 行第 2 列 | `bonus` | 奖励地块 |
|
||||
| 第 2 行第 3 列 | `accent` | 视觉强调地块 |
|
||||
|
||||
图集生成后按地块类型切分并去掉背景,运行态直接消费切好的 PNG,不在前端做复杂拼接。只有用户在结果页明确点击“重生成地块”时,才允许再调用一次地块图集生图。
|
||||
|
||||
### 6.3 不新增第三次生成
|
||||
|
||||
首版不把封面、分享海报、路径预览再拆成第三次图像生成。封面和分享图必须由角色图 + 地块图集在本地或后端轻量合成,不额外增加新的角色生图次数。
|
||||
|
||||
### 6.4 路径元数据
|
||||
|
||||
除图片资产外,系统还必须生成跳跃路径元数据:
|
||||
|
||||
1. 平台序列;
|
||||
2. 平台中心点;
|
||||
3. 平台宽度;
|
||||
4. 平台间距;
|
||||
5. 终点索引;
|
||||
6. 评分和容差参数。
|
||||
|
||||
路径由领域规则自动生成,创作者不直接编辑坐标。路径元数据不依赖 LLM 或图片生成。
|
||||
|
||||
### 6.5 推荐的难度区间
|
||||
|
||||
| 难度 | 平台数量 | 平台间距 | 节奏 |
|
||||
| --- | ---: | --- | --- |
|
||||
| 轻松 | 12 - 14 | 短 | 宽容 |
|
||||
| 标准 | 16 - 18 | 中 | 稳定 |
|
||||
| 进阶 | 20 - 24 | 中长 | 紧凑 |
|
||||
| 挑战 | 26 - 32 | 长 | 高压 |
|
||||
|
||||
平台宽度和容差由系统按难度自动缩放,不要求创作者手工填写。
|
||||
|
||||
## 7. 契约草案
|
||||
|
||||
### 7.1 草稿结构
|
||||
|
||||
`JumpHopDraft` 至少包含:
|
||||
|
||||
1. `templateId = "jump-hop"`;
|
||||
2. `templateName = "跳一跳"`;
|
||||
3. `profileId`;
|
||||
4. `workTitle`;
|
||||
5. `workDescription`;
|
||||
6. `themeTags`;
|
||||
7. `difficulty`;
|
||||
8. `stylePreset`;
|
||||
9. `characterPrompt`;
|
||||
10. `tilePrompt`;
|
||||
11. `characterAsset`;
|
||||
12. `tileAtlasAsset`;
|
||||
13. `tileAssets[]`;
|
||||
14. `path`;
|
||||
15. `coverComposite`;
|
||||
16. `generationStatus`。
|
||||
|
||||
### 7.2 资产结构
|
||||
|
||||
`JumpHopCharacterAsset` 至少包含:
|
||||
|
||||
1. `assetId`;
|
||||
2. `imageSrc`;
|
||||
3. `imageObjectKey`;
|
||||
4. `assetObjectId`;
|
||||
5. `generationProvider`;
|
||||
6. `prompt`;
|
||||
7. `width`;
|
||||
8. `height`。
|
||||
|
||||
`JumpHopTileAsset` 至少包含:
|
||||
|
||||
1. `tileType`;
|
||||
2. `imageSrc`;
|
||||
3. `imageObjectKey`;
|
||||
4. `assetObjectId`;
|
||||
5. `sourceAtlasCell`;
|
||||
6. `visualWidth`;
|
||||
7. `visualHeight`;
|
||||
8. `topSurfaceRadius`;
|
||||
9. `landingRadius`。
|
||||
|
||||
`tileType` 首版限定:
|
||||
|
||||
```text
|
||||
start | normal | target | finish | bonus | accent
|
||||
```
|
||||
|
||||
### 7.3 路径结构
|
||||
|
||||
`JumpHopPath` 至少包含:
|
||||
|
||||
1. `seed`;
|
||||
2. `difficulty`;
|
||||
3. `platforms[]`;
|
||||
4. `finishIndex`;
|
||||
5. `cameraPreset`;
|
||||
6. `scoring`。
|
||||
|
||||
`JumpHopPlatform` 至少包含:
|
||||
|
||||
1. `platformId`;
|
||||
2. `tileType`;
|
||||
3. `x`;
|
||||
4. `y`;
|
||||
5. `width`;
|
||||
6. `height`;
|
||||
7. `landingRadius`;
|
||||
8. `perfectRadius`;
|
||||
9. `scoreValue`。
|
||||
|
||||
### 7.4 运行态快照
|
||||
|
||||
`JumpHopRunSnapshot` 至少包含:
|
||||
|
||||
1. `runId`;
|
||||
2. `profileId`;
|
||||
3. `status = playing | failed | cleared`;
|
||||
4. `currentPlatformIndex`;
|
||||
5. `score`;
|
||||
6. `combo`;
|
||||
7. `lastJump`;
|
||||
8. `startedAtMs`;
|
||||
9. `finishedAtMs`。
|
||||
|
||||
`lastJump` 至少包含:
|
||||
|
||||
1. `chargeMs`;
|
||||
2. `jumpDistance`;
|
||||
3. `targetPlatformIndex`;
|
||||
4. `landedX`;
|
||||
5. `landedY`;
|
||||
6. `result = miss | hit | perfect | finish`。
|
||||
|
||||
## 8. API 草案
|
||||
|
||||
HTTP 路由建议:
|
||||
|
||||
```text
|
||||
POST /api/creation/jump-hop/sessions
|
||||
GET /api/creation/jump-hop/sessions/{sessionId}
|
||||
POST /api/creation/jump-hop/sessions/{sessionId}/actions
|
||||
POST /api/creation/jump-hop/works/{profileId}/publish
|
||||
GET /api/runtime/jump-hop/works/{profileId}
|
||||
POST /api/runtime/jump-hop/runs
|
||||
POST /api/runtime/jump-hop/runs/{runId}/jump
|
||||
POST /api/runtime/jump-hop/runs/{runId}/restart
|
||||
GET /api/runtime/jump-hop/gallery
|
||||
GET /api/runtime/jump-hop/gallery/{publicWorkCode}
|
||||
```
|
||||
|
||||
动作类型建议:
|
||||
|
||||
```text
|
||||
compile-draft
|
||||
regenerate-character
|
||||
regenerate-tiles
|
||||
update-work-meta
|
||||
update-difficulty
|
||||
```
|
||||
|
||||
`compile-draft` 是长耗时动作。前端进入生成页后必须持久化 `generationStatus=generating`,刷新后能从作品架恢复生成页。失败前需要复读 session;如果后端已经完成草稿并写回资产,前端按成功收尾。
|
||||
|
||||
## 9. SpacetimeDB 表和 view
|
||||
|
||||
建议新增表:
|
||||
|
||||
1. `jump_hop_agent_session`;
|
||||
2. `jump_hop_work_profile`;
|
||||
3. `jump_hop_runtime_run`;
|
||||
4. `jump_hop_event`;
|
||||
5. `jump_hop_leaderboard_entry`,首版可暂不对外展示;
|
||||
6. `jump_hop_gallery_view`;
|
||||
7. `jump_hop_gallery_card_view`。
|
||||
|
||||
表结构新增字段必须按 SpacetimeDB 迁移规则放在结构体末尾并设置明确默认值。新增或调整表、reducer、procedure、view 后必须同步 `migration.rs`、表目录、生成 bindings,并执行 `npm run check:spacetime-schema`。
|
||||
|
||||
公开列表主路径应优先订阅 `jump_hop_gallery_card_view` 后在 `api-server` 本地 cache 构造列表响应,不要让每个 HTTP 请求都调用 SpacetimeDB procedure 组装全量列表。
|
||||
|
||||
## 10. 结果页能力
|
||||
|
||||
结果页必须展示:
|
||||
|
||||
1. 作品标题;
|
||||
2. 作品简介;
|
||||
3. 角色形象;
|
||||
4. 地块图集;
|
||||
5. 路径预览;
|
||||
6. 标签;
|
||||
7. 试玩;
|
||||
8. 发布;
|
||||
9. 返回编辑。
|
||||
|
||||
结果页还必须支持:
|
||||
|
||||
1. 单独重生成角色;
|
||||
2. 单独重生成地块图集;
|
||||
3. 单独修改标题和简介;
|
||||
4. 单独调整标签和难度。
|
||||
|
||||
结果页不应强制再走一次封面生图。封面只做合成,不新增图像生成调用。
|
||||
|
||||
## 11. 运行态规则
|
||||
|
||||
运行态采用 2D 表现,但画面视觉上必须保留参考图那种俯视角 / 等距感。
|
||||
|
||||
### 11.1 核心玩法
|
||||
|
||||
1. 玩家长按蓄力;
|
||||
2. 松手后角色按蓄力长度起跳;
|
||||
3. 跳跃距离决定是否落到下一个地块;
|
||||
4. 落在目标区域内判定成功;
|
||||
5. 落在地块外或越界判定失败;
|
||||
6. 到达终点地块判定通关。
|
||||
|
||||
### 11.2 判定规则
|
||||
|
||||
1. 只做一个当前局面的起跳判定;
|
||||
2. 不做复杂连招动作树;
|
||||
3. 不新增生命数、体力、回合数;
|
||||
4. 不新增计时赛作为首版核心规则;
|
||||
5. 不把前端动画结果当成最终真相,通关与失败必须能回写运行态状态。
|
||||
|
||||
### 11.3 角色动画
|
||||
|
||||
角色不需要多帧生图,运行态只通过位移、缩放、轻微旋转和投影变化表达:
|
||||
|
||||
1. 蓄力时轻微压缩;
|
||||
2. 起跳时向上抬升;
|
||||
3. 空中保持可读轮廓;
|
||||
4. 落地时轻微弹性回弹;
|
||||
5. 失败时从地块边缘跌落。
|
||||
|
||||
### 11.4 摄像机与构图
|
||||
|
||||
1. 相机以当前角色和下一地块为中心;
|
||||
2. 至少保证下一个落点一直可见;
|
||||
3. 画面要留出顶部和底部的 UI 安全区;
|
||||
4. 不要把地块做得太满,保留参考图那种疏朗感。
|
||||
|
||||
### 11.5 UI
|
||||
|
||||
运行态 UI 只保留必要元素:
|
||||
|
||||
1. 分数;
|
||||
2. 暂停;
|
||||
3. 重新开始;
|
||||
4. 分享;
|
||||
5. 结算按钮。
|
||||
|
||||
不默认展示大段规则说明。首进如果需要引导,只能用一次轻量提示,不允许常驻一屏的说明文案。
|
||||
|
||||
## 12. 视觉规范
|
||||
|
||||
本模板的视觉目标是“像 3D,但仍是 2D 图片”。
|
||||
|
||||
必须遵守:
|
||||
|
||||
1. 平台有明确厚度;
|
||||
2. 侧面可见分层或材质变化;
|
||||
3. 投影统一且方向一致;
|
||||
4. 背景干净,颜色克制;
|
||||
5. 角色尺寸在小屏上依然可读;
|
||||
6. 地块不能出现过多文字、按钮或装饰信息;
|
||||
7. 不能把运行态做成重 UI 面板。
|
||||
|
||||
建议的背景策略:
|
||||
|
||||
1. 以静态浅色渐变或纯色背景为主;
|
||||
2. 不把背景也做成每次都生成的重资产;
|
||||
3. 让地块和角色成为画面的第一视觉焦点。
|
||||
|
||||
## 13. 发布后体验
|
||||
|
||||
发布后的作品必须支持:
|
||||
|
||||
1. 进入作品架和公开展示;
|
||||
2. 分享;
|
||||
3. 试玩;
|
||||
4. 重新进入结果页编辑。
|
||||
|
||||
发布后的卡片封面应优先由角色图和地块图合成,不要求单独再生成封面图。
|
||||
|
||||
首版不新增排行榜、回放和对局对抗。后续如要扩展排行,可另起版本,不要塞进首版模板范围。
|
||||
|
||||
## 14. 验收
|
||||
|
||||
1. 创作入口能看到 `跳一跳` 模板;
|
||||
2. 创作者可以填写主题、角色描述、风格和难度;
|
||||
3. 提交后只生成一次角色图和一次地块图集;
|
||||
4. 结果页能看到角色图、地块图集和路径预览;
|
||||
5. 结果页可单独重生成角色或地块;
|
||||
6. 试玩进入跳一跳运行态;
|
||||
7. 长按蓄力、松手起跳、落点判定、失败和通关都可用;
|
||||
8. 作品可以保存、发布和分享;
|
||||
9. 前端不直接读取或暴露生图密钥;
|
||||
10. 发布后的封面不依赖第三次额外生图。
|
||||
11. `npm run check:spacetime-schema` 在 schema 变更后通过;
|
||||
12. `npm run check:encoding` 通过。
|
||||
@@ -0,0 +1,33 @@
|
||||
# 创作 Tab 入口表单回切记录
|
||||
|
||||
> 说明:本文件原本记录“空白入口页”实施计划。2026-05-24 已按产品反馈回切为点击玩法模板卡后直达既有入口创作表单,保留此文件作为历史回顾,避免后续误按旧计划继续实现空白页。
|
||||
|
||||
**Goal:** 创作 Tab 只展示赛事 banner、玩法模板分类和两列玩法卡;点击卡片后直接进入对应玩法已有的入口创作表单,而不是继续展示空白占位页。
|
||||
|
||||
**Architecture:** 保留现有创作大厅和入口配置事实源。创作大厅负责参考图 banner、分类 tabs 和入口卡片;平台壳层负责把卡片点击路由到对应玩法的既有入口表单 stage。入口表单继续承接各玩法自己的表单、草稿恢复和后续编排,不再多套一层空白入口页。
|
||||
|
||||
**Current Route Mapping:**
|
||||
|
||||
- `/creation/rpg` -> `agent-workspace`
|
||||
- `/creation/big-fish` -> `big-fish-agent-workspace`
|
||||
- `/creation/match3d` -> `match3d-agent-workspace`
|
||||
- `/creation/square-hole` -> `square-hole-agent-workspace`
|
||||
- `/creation/jump-hop` -> `jump-hop-workspace`
|
||||
- `/creation/wooden-fish` -> `wooden-fish-workspace`
|
||||
- `/creation/puzzle` -> `puzzle-agent-workspace`
|
||||
- `/creation/bark-battle` -> `bark-battle-workspace`
|
||||
- `/creation/visual-novel` -> `visual-novel-agent-workspace`
|
||||
- `/creation/baby-object-match` -> `baby-object-match-workspace`
|
||||
|
||||
**Verification:**
|
||||
|
||||
- `npm test -- src/routing/appPageRoutes.test.ts`
|
||||
- `npm test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "create tab opens match3d entry form from the template card|create tab opens puzzle entry form from the template card|create tab opens bark battle entry form from the template card"`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
|
||||
**Notes:**
|
||||
|
||||
- 不再新增或保留 `*-workspace-entry` 空白 stage。
|
||||
- `/creation/<play>` 是用户可直达的入口表单 URL。
|
||||
- 旧的空白入口页方案已废弃;如需重新引入,必须先更新平台入口文档和本记录。
|
||||
@@ -0,0 +1,59 @@
|
||||
# Profile Tab Mobile Layout Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Tighten the mobile `我的` Tab layout so typography, spacing, and fixed-height controls align with the rest of the platform UI and do not overlap the bottom dock.
|
||||
|
||||
**Architecture:** Keep the existing `RpgEntryHomeView` structure and update only page-specific class names plus CSS rules. Treat `src/index.css` as the platform token surface; document the mobile profile Tab acceptance criteria in the existing product / play-flow docs instead of creating a parallel doc.
|
||||
|
||||
**Tech Stack:** React, TypeScript, Tailwind utility classes, project CSS in `src/index.css`, Vitest, Vite local browser smoke.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add Mobile Profile Layout Assertions
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`
|
||||
|
||||
- [ ] Add expectations in `mobile profile page matches the reference layout sections` that the profile page, header, stats grid, daily task card, shortcut grid, and bottom dock expose the stable class hooks used by the mobile CSS.
|
||||
|
||||
- [ ] Run `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "mobile profile page matches the reference layout sections"` and verify the test covers the hooks before CSS edits.
|
||||
|
||||
### Task 2: Tighten Profile Tab Markup Hooks
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/components/rpg-entry/RpgEntryHomeView.tsx`
|
||||
|
||||
- [ ] Add focused class hooks for profile identity text, stat values, shortcut labels, membership copy, and daily task copy without changing user-facing Chinese text.
|
||||
|
||||
- [ ] Keep the current page order: profile header, membership card, three stats, daily task, five shortcut buttons, settings, secondary shortcuts, legal strip.
|
||||
|
||||
### Task 3: Implement Mobile CSS
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/index.css`
|
||||
|
||||
- [ ] Under the existing `@media (max-width: 639px)` block, reduce `我的` Tab fixed sizes to ordinary UI scale: profile title 16-17px, body copy 11-13px, stats values 13-14px, shortcut labels 11-12px.
|
||||
|
||||
- [ ] Make narrow screens robust: stats grid uses three min-width-safe columns, shortcut grid becomes `repeat(5, minmax(0, 1fr))` above 360px and a stable `repeat(3, minmax(0, 1fr))` below 360px, identity text wraps safely, legal links wrap when needed.
|
||||
|
||||
- [ ] Keep the bottom dock fixed but add enough profile page bottom padding so the final legal / secondary section can scroll above it.
|
||||
|
||||
### Task 4: Update Docs
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/【项目基线】当前产品与工程约束-2026-05-15.md`
|
||||
- Modify: `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
|
||||
|
||||
- [ ] Add the acceptance rule that mobile `我的` Tab typography stays within normal UI sizes and all functional blocks must scroll clear of the bottom dock on narrow screens.
|
||||
|
||||
### Task 5: Verify
|
||||
|
||||
**Files:**
|
||||
- No new files.
|
||||
|
||||
- [ ] Run `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "mobile profile page matches the reference layout sections"`.
|
||||
|
||||
- [ ] Run `npm run check:encoding`.
|
||||
|
||||
- [ ] Run a local mobile viewport smoke check for the profile tab if the dev server starts cleanly.
|
||||
@@ -1,4 +1,4 @@
|
||||
# 宝贝识物创作发布实现方案 2026-05-11
|
||||
# 宝贝识物创作发布实现方案 2026-05-11
|
||||
|
||||
## 1. 范围
|
||||
|
||||
@@ -144,7 +144,7 @@ PUT /api/creation/edutainment/baby-object-match/drafts/{draftId}
|
||||
POST /api/creation/edutainment/baby-object-match/drafts/{draftId}/publish
|
||||
```
|
||||
|
||||
图片生成必须在后端调用 VectorEngine `gpt-image-2-all`,不得从前端直接调用外部图片接口。
|
||||
图片生成必须在后端调用 VectorEngine `gpt-image-2`,不得从前端直接调用外部图片接口。
|
||||
|
||||
后端 `2x2` 素材 sheet prompt 约束:
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 儿童动作识别互动玩法 Demo 热身关开发规格文档
|
||||
# 儿童动作识别互动玩法 Demo 热身关开发规格文档
|
||||
|
||||
> 日期:2026-05-09
|
||||
> 关联设计文档:[CHILD_MOTION_DEMO_WARMUP_LEVEL_DESIGN_2026-05-09.md](../design/CHILD_MOTION_DEMO_WARMUP_LEVEL_DESIGN_2026-05-09.md)
|
||||
@@ -49,6 +49,8 @@
|
||||
|
||||
热身结束后展示“开始游戏”按钮,用户点击后进入宝贝识物首关本地 Demo。该入口只用于热身关后的本地体验验证;正式平台体验仍必须通过“宝贝识物”创作模板发布后,在寓教于乐板块进入。
|
||||
|
||||
发现页的寓教于乐频道同时提供独立热身关入口,用户可直接进入 `/child-motion-demo`。
|
||||
|
||||
### 3.3 固定流程顺序
|
||||
|
||||
热身关必须按照以下顺序执行:
|
||||
@@ -684,7 +686,7 @@
|
||||
1. 舞台主环境采用卡通绘本风格、明亮草地、天空、小山坡和树木的组合,默认背景环境需要保证中心与下方前景留空,便于角色轮廓和地面指示环叠加。
|
||||
2. 该卡通绘本草地风格是儿童动作 Demo 后续场景、物品、UI 资源的全局风格要求;新增资源不得切回暗色科技风、真实照片风或后台面板风。
|
||||
3. `src/index.css` 中的热身舞台、摄像头背景层、地面、角色轮廓、地面圆环、开始按钮和横屏提示均按绘本草地风格接入真实资源;资源加载失败时保留 CSS 兜底。
|
||||
4. 生成脚本固定为 `scripts/generate-child-motion-demo-assets.mjs`,并通过 `npm run assets:child-motion-demo` 触发;脚本使用 `gpt-image-2-all` 调用 VectorEngine `POST /v1/images/generations`,透明资源先生成品红底源图,再在本地移除色键,源图写入 `tmp/child-motion-demo-assets/`。
|
||||
4. 生成脚本固定为 `scripts/generate-child-motion-demo-assets.mjs`,并通过 `npm run assets:child-motion-demo` 触发;脚本使用 `gpt-image-2` 调用 VectorEngine `POST /v1/images/generations`,透明资源先生成品红底源图,再在本地移除色键,源图写入 `tmp/child-motion-demo-assets/`。
|
||||
5. 当前已生成并接入以下正式 Demo 资源:
|
||||
- `public/child-motion-demo/picture-book-grass-stage.png`:默认草地舞台背景。
|
||||
- `public/child-motion-demo/picture-book-foreground-grass-v2.png`:底部前景草坪条,只覆盖舞台下沿,不作为整块地板拉伸。
|
||||
|
||||
@@ -214,7 +214,7 @@ Handler 主要在 `story.rs`、`combat.rs`、`runtime_inventory.rs`:
|
||||
| Big Fish 正式图 | DashScope `wan2.2-t2i-flash` | 轮询 task 后 HTTP GET 图片 URL | `LegacyAssetPrefix::BigFishAssets` | 由 assetKind 映射主图/动作图/舞台背景等 | `big_fish_session` + session/entity id + slot | `big_fish.rs` 调用方 `execute_billable_asset_operation` | 配置缺失/上游失败直接错误;gallery 对部分 Spacetime 运行错误软降级 |
|
||||
| Square Hole 图片重生成 | OpenAI/VectorEngine GPT image helper | URL 下载或 base64/data URL 解码 | `LegacyAssetPrefix::SquareHoleAssets` | 方洞作品图片槽位相关 kind | profile/work + image slot | 调用方包裹 | 生成成功但入库失败保留 Data URL 回包 |
|
||||
| Custom World 场景/封面 | VectorEngine GPT image 2 / OpenAI helper | URL 下载或 base64 解码 | `LegacyAssetPrefix::CustomWorldScenes` 等 | scene/cover/opening storyboard | `custom_world_profile` 或 profile/landmark/scene slot | `custom_world_ai.rs` 调用方包裹 | entity/scene 生成存在 LLM fallback;资产持久化失败按当前错误口径返回 |
|
||||
| Puzzle 图片 | GPT image 2 generations/edits | multipart/base64/URL 结果归一 | `LegacyAssetPrefix::PuzzleAssets` | puzzle level/background/generated image,另有 `puzzle_background_music` | puzzle profile/run/level slot | `puzzle.rs` 调用方包裹 | connectivity 可按既有规则跳过部分计费;运行态 fallback 保持原逻辑 |
|
||||
| Puzzle 图片 | GPT image 2 generations/edits | 无参考图 JSON 创建;有参考图 multipart 编辑;base64/URL 结果归一 | `LegacyAssetPrefix::PuzzleAssets` | puzzle level/background/generated image,另有 `puzzle_background_music` | puzzle profile/run/level slot | `puzzle.rs` 调用方包裹 | connectivity 可按既有规则跳过部分计费;运行态 fallback 保持原逻辑 |
|
||||
| Match3D 图片 | APIMart/VectorEngine/OpenAI image helper | 下载、切图、透明化、校准后入库 | `LegacyAssetPrefix::Match3DAssets` | cover/background/item material sheet,音频 kind 另列 | match3d profile/session slot | `match3d.rs` 调用方包裹 | 新草稿不回退 Rodin/GLB;部分连接错误按现有计费跳过规则处理 |
|
||||
| Visual Novel 音频 | VectorEngine Suno/Vidu | 任务提交后按 task publish 下载音频 | 视觉小说/creation audio scope | `visual_novel_music`、`visual_novel_ambient_sound` | `visual_novel_scene` + scene id + `music`/`ambient_sound` | `vector_engine_audio_generation.rs` 调用方包裹 | 上游/下载失败显式错误,不混入图片 Adapter |
|
||||
| 通用音频 | VectorEngine Suno/Vidu | 同上 | creation audio scope | background_music/sound_effect 由调用方目标指定 | creation target entity/slot | 调用方包裹 | 不与 VN 场景语义混用 |
|
||||
|
||||
@@ -88,6 +88,10 @@ Adapter 只负责媒体持久化和资产绑定,不负责:
|
||||
| Custom World opening CG video | `custom_world_ai.rs` 或后续分层文件 | video | Ark/火山视频 task 结果 URL | 视频下载、OSS、confirm、binding | storyboard->video 顺序、固定点数计费、超时错误 | storyboard 图片仍走图片 Adapter,最终视频走 media persist |
|
||||
| Character visual reference/workflow | `character_visual_assets.rs` | image/cache metadata | GPT image helper、workflow cache | 可复用 media persist 的 source/OSS/confirm/binding;图片生成 provider 不迁入复杂媒体 | 角色 workflow cache 可空继续生成 | 角色视觉发布链路回包字段不变 |
|
||||
| Character animation publish/import | `character_animation_assets.rs` | video / image sequence | data:video base64、remote video、阶段占位 | data URL/base64 解码、视频 OSS、confirm、binding | stage1 placeholder 语义、import-video contract | 导入视频和发布视频都不再复制 OSS/confirm 代码 |
|
||||
|
||||
2026-05-26 补充:图片生成 provider 不再作为复杂媒体 Adapter 的实现细节散落在 `api-server`。VectorEngine `gpt-image-2` 创建 / 编辑协议、响应解析、URL / base64 图片归一、远端下载和 provider 侧结构化失败日志已经收口到 `server-rs/crates/platform-image/src/vector_engine/`;`api-server/src/openai_image_generation.rs` 只保留配置、兼容调用面和外部失败审计桥接。后续扩展视频、音频或 Hyper3D 时,可以复用“platform crate 承接 provider 协议,api-server 承接 HTTP/BFF、计费、OSS 绑定和失败审计桥接”的分层方式,但不得把新的 provider 协议塞回 `api-server` 大文件。
|
||||
2026-05-26 补充:音频生成 provider 协议也不应继续挤在 `api-server/src/vector_engine_audio_generation.rs`。VectorEngine Suno/Vidu 的任务提交、轮询、下载、MIME/extension 归一和 OSS 持久化请求准备已经收口到 `server-rs/crates/platform-audio/`,并继续按 `client.rs`、`request.rs`、`response.rs`、`download.rs`、`persist.rs`、`error.rs` 拆成小模块;`api-server` 只保留路由、配置、计费、asset object confirm、entity binding 和错误 envelope 映射,不再承担 provider 协议和下载细节。后续若再增加音频子能力,也必须优先放进平台 crate,而不是扩张 `api-server`。
|
||||
2026-05-26 补充:Hyper3D 只保留后端安全代理和旧数据兼容,`api-server/src/hyper3d_generation.rs` 应保持薄 wrapper,`platform-hyper3d` 承接 Rodin 的提交、状态和下载协议解析。若未来要继续压缩这一条线,应优先继续下沉协议解析与 transport helper,而不是把 provider 逻辑回流到 `api-server`。
|
||||
| Match3D 背景音乐 | `match3d.rs` | audio | 现有生成/上传链路 | 仅复用音频持久化 | 不恢复 Rodin/GLB 新草稿回退 | 图片素材仍按图片 Adapter 计划处理 |
|
||||
| Puzzle 背景音乐 | `puzzle.rs` | audio | 现有生成/上传链路 | 仅复用音频持久化 | puzzle 运行态和排行榜语义不变 | `puzzle_background_music` kind/binding 不变 |
|
||||
| Hyper3D/GLB 历史代理 | `hyper3d.rs` | model/glb | Hyper3D Rodin status/download | 如存在转存需求,仅复用 media persist | Match3D 新草稿禁止回退 Rodin/GLB | 历史代理 route contract 不变 |
|
||||
|
||||
92
docs/technical/【后端架构】统一公开作品ReadModel设计-2026-05-26.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# 统一公开作品 ReadModel 设计
|
||||
|
||||
更新时间:`2026-05-26`
|
||||
|
||||
## 背景
|
||||
|
||||
各玩法原本各自维护 `*_gallery_card_view` / `*_gallery_view` / `custom_world_gallery_entry` 等公开投影。它们继续保留为 source view 和兼容路径,但公开列表与公开详情的主读模型需要跨玩法统一,避免 `api-server` 在 HTTP 热路径里为每个玩法各写一套拼装逻辑。
|
||||
|
||||
## 统一契约
|
||||
|
||||
公开列表主读模型:
|
||||
|
||||
- `public_work_gallery_entry`
|
||||
|
||||
公开详情摘要主读模型:
|
||||
|
||||
- `public_work_detail_entry`
|
||||
|
||||
统一字段只保留公开层契约所需内容:
|
||||
|
||||
- `source_type`
|
||||
- `work_id`
|
||||
- `profile_id`
|
||||
- `source_session_id`
|
||||
- `public_work_code`
|
||||
- `owner_user_id`
|
||||
- `author_display_name`
|
||||
- `world_name`
|
||||
- `subtitle`
|
||||
- `summary_text`
|
||||
- `cover_image_src`
|
||||
- `cover_asset_id`
|
||||
- `theme_tags`
|
||||
- `play_count`
|
||||
- `remix_count`
|
||||
- `like_count`
|
||||
- `published_at_micros`
|
||||
- `updated_at_micros`
|
||||
- `sort_time_micros`
|
||||
- `detail_payload_json`
|
||||
|
||||
其中 `detail_payload_json` 只承载平台详情页展示扩展,不承载正式 runtime 配置、玩法规则或草稿真相。
|
||||
|
||||
## 来源与兼容
|
||||
|
||||
统一 public view 由现有玩法 source view 组装:
|
||||
|
||||
- `puzzle_gallery_card_view`
|
||||
- `puzzle_gallery_view`
|
||||
- `custom_world_gallery_entry`
|
||||
- `jump_hop_gallery_card_view`
|
||||
- `jump_hop_gallery_view`
|
||||
- `wooden_fish_gallery_card_view`
|
||||
- `wooden_fish_gallery_view`
|
||||
- `match_3_d_gallery_view`
|
||||
- `square_hole_gallery_view`
|
||||
- `visual_novel_gallery_view`
|
||||
- `big_fish_gallery_view`
|
||||
- `bark_battle_gallery_view`
|
||||
|
||||
规则是:
|
||||
|
||||
- 旧 view 保留,不删除。
|
||||
- 旧 view 退到底层 source / 兼容职责。
|
||||
- 新 `public_work_*` view 是 `api-server` 公开列表 / 详情的统一主读模型。
|
||||
- 旧 `/api/runtime/<play>/gallery` 响应 shape 保持兼容,由 BFF mapper 把统一 cache 再映射回当前 DTO。
|
||||
- 旧详情 / runtime / 点赞 / 游玩 / Remix 仍走玩法专用路径。
|
||||
|
||||
## 订阅与路由
|
||||
|
||||
`spacetime-client` 当前长期订阅:
|
||||
|
||||
- `SELECT * FROM public_work_gallery_entry`
|
||||
- `SELECT * FROM public_work_detail_entry`
|
||||
- `SELECT * FROM public_work_play_daily_stat`
|
||||
- 各玩法 source view 作为兼容缓存和旧路径支撑
|
||||
|
||||
`api-server` 当前新增统一公开路由:
|
||||
|
||||
- `GET /api/public-works`
|
||||
- `GET /api/public-works/{publicWorkCode}`
|
||||
|
||||
旧 route 继续保留,由 BFF 从统一 cache 映射回旧 DTO 形状。
|
||||
|
||||
## 验证
|
||||
|
||||
- `npm run spacetime:generate`
|
||||
- `npm run check:spacetime-schema`
|
||||
- `cargo check -p spacetime-client --manifest-path server-rs/Cargo.toml`
|
||||
- `cargo check -p api-server --manifest-path server-rs/Cargo.toml`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
184
docs/【专利交底】一种极低成本快速生成高质量2D小游戏高一致性美术素材的解决方案-2026-05-25.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# 一种极低成本快速生成高质量2D小游戏高一致性美术素材的解决方案
|
||||
|
||||
更新时间:`2026-05-25`
|
||||
|
||||
> 本文为内部发明专利交底稿,目标是把“文字需求 -> 画面图 -> 透明 spritesheet -> 自动边界检测 -> 元素绑定”这一条高一致性美术素材生成链路抽象为可申请的通用技术方案。
|
||||
|
||||
## 摘要
|
||||
|
||||
本发明涉及一种极低成本快速生成高质量 2D 小游戏高一致性美术素材的解决方案。该方案接收文字形态的需求描述,调用图片生成模型生成一张用于表达整体视觉关系的游戏画面图;再以所述游戏画面图作为参考,继续调用图片生成模型生成一张透明背景的 spritesheet 图片,所述 spritesheet 图片承载需要随不同设备分辨率自适应调整位置的素材;随后基于自动边界检测算法对所述 spritesheet 图片中的素材进行逐一解析,按照从上到下、从左到右的顺序,将解析出的素材与文字形态需求描述中的画面元素一一对应,并将代码中的元素标识与对应素材绑定。该方案通过单次文字输入驱动画面图生成、基于画面图派生透明 spritesheet、自动边界检测替代人工切图、顺序映射替代手工命名和手工对图,从而在较少人工干预和较低重复生成成本下,快速得到风格统一、可直接绑定代码的高一致性美术素材。
|
||||
|
||||
## 技术领域
|
||||
|
||||
本发明属于人工智能图像生成、2D 小游戏美术素材生产、图像分割解析和元素绑定技术领域,具体涉及一种根据文字需求描述自动生成游戏画面图、spritesheet 美术素材和元素映射关系的方法及系统。
|
||||
|
||||
## 背景技术
|
||||
|
||||
现有 2D 小游戏的美术素材生产通常包含以下步骤:先由设计人员撰写文字需求,再由美术人员分别绘制画面图、按钮图、状态图和装饰图,之后由前端或游戏程序员进行切图、命名、排布和代码绑定。该流程存在如下问题:
|
||||
|
||||
1. 素材往往分散生成,整体风格不统一。
|
||||
2. 多分辨率适配时,需要人工调整大量元素位置,维护成本高。
|
||||
3. 切图和命名依赖人工,容易出现遗漏、错位和绑定错误。
|
||||
4. 文字需求与最终代码元素之间缺少稳定映射,后续修改代价大。
|
||||
5. 若每个素材分别生成,会增加生成次数和等待成本。
|
||||
|
||||
因此,需要一种能够把文字需求直接转化为成套美术素材,并且能够自动解析、自动映射、自动绑定到代码中的方法。
|
||||
|
||||
## 发明内容
|
||||
|
||||
### 要解决的技术问题
|
||||
|
||||
本发明主要解决以下技术问题:
|
||||
|
||||
1. 如何根据文字形态的需求描述快速生成一张完整游戏画面图。
|
||||
2. 如何基于该画面图进一步生成透明背景的 spritesheet 图片。
|
||||
3. 如何基于自动边界检测算法逐一解析 spritesheet 中的素材。
|
||||
4. 如何按照从上到下、从左到右的顺序将素材与文字描述中的画面元素一一对应。
|
||||
5. 如何将映射结果稳定绑定到代码,减少人工切图和手工配置成本。
|
||||
|
||||
### 技术方案
|
||||
|
||||
本发明提供一种高一致性美术素材生成方法,包括如下步骤:
|
||||
|
||||
```text
|
||||
文字形态需求描述
|
||||
-> 游戏画面图生成
|
||||
-> 透明背景 spritesheet 生成
|
||||
-> 自动边界检测解析素材
|
||||
-> 顺序映射文字元素
|
||||
-> 代码绑定
|
||||
```
|
||||
|
||||
其中,所述文字形态需求描述至少包含画面元素名称、语义说明、布局意图和顺序信息。所述游戏画面图用于表达整体视觉风格和元素关系;所述 spritesheet 图片用于承载需要随不同设备分辨率自适应调整位置的素材;所述自动边界检测算法用于把 spritesheet 中的独立素材一一切分出来;所述顺序映射用于将解析结果与文字描述中的元素一一对应;所述代码绑定用于将元素标识、资源地址、边界框或布局参数写入代码配置或元素表。
|
||||
|
||||
### 有益效果
|
||||
|
||||
与现有技术相比,本发明至少具有以下效果:
|
||||
|
||||
1. 降低人工切图成本。
|
||||
2. 降低人工命名和代码绑定成本。
|
||||
3. 提升整体美术素材一致性。
|
||||
4. 提升多分辨率适配效率。
|
||||
5. 减少重复生成和重复调整次数。
|
||||
6. 让文字需求到代码元素的映射更稳定、更可维护。
|
||||
|
||||
## 附图说明
|
||||
|
||||
图 1 为本发明从文字需求描述到元素绑定的总体流程图。
|
||||
|
||||
图 2 为游戏画面图生成与 spritesheet 生成的派生关系示意图。
|
||||
|
||||
图 3 为自动边界检测算法解析透明背景 spritesheet 的流程图。
|
||||
|
||||
图 4 为素材顺序与文字形态需求描述中的画面元素一一对应的映射关系示意图。
|
||||
|
||||
## 具体实施方式
|
||||
|
||||
### 一、系统组成
|
||||
|
||||
本发明的系统可以包括如下模块:
|
||||
|
||||
1. 输入采集模块:用于接收文字形态需求描述。
|
||||
2. 图像生成模块:用于根据文字形态需求描述生成游戏画面图。
|
||||
3. 图集生成模块:用于根据游戏画面图生成透明背景 spritesheet 图片。
|
||||
4. 边界检测模块:用于对 spritesheet 图片执行自动边界检测算法。
|
||||
5. 顺序映射模块:用于将解析出的素材按照从上到下、从左到右的顺序与文字描述中的画面元素对应。
|
||||
6. 代码绑定模块:用于将元素标识与对应素材绑定。
|
||||
|
||||
### 二、方法步骤
|
||||
|
||||
#### S100:接收文字形态需求描述
|
||||
|
||||
系统接收用户输入的文字形态需求描述。该需求描述可写成一段自然语言,也可写成按元素顺序排列的结构化文本。需求描述中应至少能够识别出画面元素名称、语义含义和布局顺序。
|
||||
|
||||
#### S200:生成游戏画面图
|
||||
|
||||
图像生成模块调用图片生成模型,根据所述文字形态需求描述生成一张完整游戏画面图。所述游戏画面图用于表达整体视觉关系、主次层级和风格基调,为后续 spritesheet 生成提供统一参考。
|
||||
|
||||
#### S300:生成透明背景 spritesheet 图片
|
||||
|
||||
图集生成模块以所述游戏画面图为参考,再次调用图片生成模型,生成一张透明背景的 spritesheet 图片。所述 spritesheet 图片中包含需要随不同设备分辨率自适应调整位置的素材,例如按钮、状态条、提示气泡、装饰元素或其他需要由代码控制位置的元素。
|
||||
|
||||
#### S400:自动边界检测解析素材
|
||||
|
||||
边界检测模块对所述 spritesheet 图片执行自动边界检测算法,对透明背景中的每个独立素材进行逐一解析,输出素材边界框、素材索引和必要的资源属性。所述自动边界检测算法优选采用 alpha 通道连通域检测、边界矩形检测或二者组合;在一个优选实施方式中,可复用拼图场景中已验证的自动边界检测思路,以提高解析稳定性。
|
||||
|
||||
#### S500:按照顺序映射文字元素
|
||||
|
||||
顺序映射模块将解析出的素材按照从上到下、从左到右的顺序进行排列,并与文字形态需求描述中的画面元素内容一一对应。若需求描述中已显式给出元素顺序,则优先按该顺序映射;若仅给出自然语言描述,则可先抽取元素列表,再按布局顺序排序。由此形成元素索引与语义名称之间的稳定映射关系。
|
||||
|
||||
#### S600:代码绑定
|
||||
|
||||
代码绑定模块将所述映射关系写入代码配置、元素表或资源清单中。代码侧只需读取元素标识,即可找到对应素材的资源地址、边界框和布局参数,从而完成美术素材与程序逻辑之间的直接绑定。
|
||||
|
||||
#### S700:输出美术素材包
|
||||
|
||||
系统最终输出至少包括游戏画面图、透明背景 spritesheet 图片、素材映射表和代码绑定结果。由于 spritesheet 图片与游戏画面图来自同一视觉链路,且素材顺序与文字描述顺序一一对应,因此可得到风格统一、可直接绑定、可适配多分辨率的高一致性美术素材包。
|
||||
|
||||
### 三、核心机制
|
||||
|
||||
1. 文字驱动:一次文字描述即可驱动画面图和 spritesheet 生成。
|
||||
2. 单图派生:spritesheet 以游戏画面图为参考生成,减少风格漂移。
|
||||
3. 自动解析:边界检测算法替代人工切图。
|
||||
4. 顺序对应:素材顺序与文字元素顺序一致,减少命名和对图错误。
|
||||
5. 代码绑定:映射结果可直接进入代码配置或资源表。
|
||||
|
||||
### 四、实施例
|
||||
|
||||
#### 实施例一:界面型 2D 小游戏素材生成
|
||||
|
||||
用户输入“科技实验室界面,顶部标题栏,中部主角色,底部三个操作按钮,右侧状态提示”。系统先生成一张完整游戏画面图,再生成一张透明背景 spritesheet 图片。边界检测模块解析出标题栏、主角色、操作按钮和状态提示等素材,顺序映射模块按从上到下、从左到右的顺序将其与文字描述对应,代码绑定模块将这些元素写入代码配置,最终形成可直接用于界面装配的素材包。
|
||||
|
||||
#### 实施例二:需要自适应位置的素材生成
|
||||
|
||||
用户输入“横版战斗界面,血条、技能按钮、提示气泡、道具栏”。系统将这些需要随设备分辨率自适应调整位置的元素集中生成到同一张 spritesheet 图片中。运行时,代码根据元素绑定结果对血条、按钮和提示元素进行位置调整,而不改变它们对应的语义关系。
|
||||
|
||||
#### 实施例三:代码与元素一一绑定
|
||||
|
||||
系统为解析出的每个素材分配唯一元素标识,例如 `top_title_bar`、`center_character`、`bottom_actions`、`right_status_hint`。代码侧通过元素标识直接读取对应素材的边界框和资源路径,从而消除人工对图和人工命名的步骤。
|
||||
|
||||
## 权利要求书草案
|
||||
|
||||
1. 一种极低成本快速生成高质量 2D 小游戏高一致性美术素材的方法,其特征在于,包括:接收文字形态需求描述;根据所述文字形态需求描述调用图片生成模型生成游戏画面图;以所述游戏画面图为参考图再次调用图片生成模型生成透明背景的 spritesheet 图片;对所述 spritesheet 图片执行自动边界检测算法,逐一解析素材边界;按照从上到下、从左到右的顺序将解析出的素材与所述文字形态需求描述中的画面元素一一对应;将代码中的元素标识与对应素材绑定。
|
||||
|
||||
2. 根据权利要求 1 所述的方法,其特征在于,所述文字形态需求描述至少包括画面元素名称、语义说明和布局顺序。
|
||||
|
||||
3. 根据权利要求 1 所述的方法,其特征在于,所述游戏画面图用于表达整体视觉风格和元素关系,所述 spritesheet 图片用于承载需要随不同设备分辨率自适应调整位置的素材。
|
||||
|
||||
4. 根据权利要求 1 所述的方法,其特征在于,所述 spritesheet 图片具有透明背景,且素材之间通过透明区域分隔。
|
||||
|
||||
5. 根据权利要求 1 所述的方法,其特征在于,所述自动边界检测算法包括基于 alpha 通道的连通域检测、边界矩形检测或二者组合。
|
||||
|
||||
6. 根据权利要求 5 所述的方法,其特征在于,所述自动边界检测算法复用拼图场景中已验证的素材边界解析思路。
|
||||
|
||||
7. 根据权利要求 1 所述的方法,其特征在于,所述从上到下、从左到右的顺序用于建立元素索引与语义名称之间的映射表。
|
||||
|
||||
8. 根据权利要求 1 所述的方法,其特征在于,所述代码绑定包括为每一素材写入唯一元素标识,并在代码中通过所述元素标识读取对应素材的资源地址、边界框或布局参数。
|
||||
|
||||
9. 根据权利要求 1 所述的方法,其特征在于,所述 spritesheet 图片中的素材包括按钮、状态条、提示元素、装饰元素或其他需要自适应布局的画面元素。
|
||||
|
||||
10. 根据权利要求 1 所述的方法,其特征在于,所述游戏画面图与所述 spritesheet 图片由同一视觉链路生成,以保持美术素材的一致性。
|
||||
|
||||
11. 根据权利要求 1 所述的方法,其特征在于,所述元素绑定结果用于在不同设备分辨率下动态调整素材位置,而不改变元素语义对应关系。
|
||||
|
||||
12. 一种极低成本快速生成高质量 2D 小游戏高一致性美术素材的系统,其特征在于,包括输入采集模块、图像生成模块、图集生成模块、边界检测模块、顺序映射模块和代码绑定模块;所述各模块被配置为执行权利要求 1 至 11 任一项所述的方法。
|
||||
|
||||
13. 一种电子设备,包括处理器和存储器,所述存储器中存储有计算机程序,其特征在于,所述计算机程序被所述处理器执行时实现权利要求 1 至 11 任一项所述的方法。
|
||||
|
||||
14. 一种计算机可读存储介质,其上存储有计算机程序,其特征在于,所述计算机程序被处理器执行时实现权利要求 1 至 11 任一项所述的方法。
|
||||
|
||||
## 可重点保护的创新点
|
||||
|
||||
1. 文字需求直接驱动一张游戏画面图。
|
||||
2. 基于该画面图再生成透明背景 spritesheet。
|
||||
3. 自动边界检测替代人工切图。
|
||||
4. 按从上到下、从左到右的顺序把素材与文字元素一一对应。
|
||||
5. 通过元素标识直接绑定代码与素材,减少人工命名和对图成本。
|
||||
|
||||
## 正式申请前建议
|
||||
|
||||
1. 检索是否已有“文字生成画面图 + spritesheet 自动解析 + 元素绑定”的相近专利,再确定独立权利要求的保护重心。
|
||||
2. 将“极低成本”“高质量”等效果性表述尽量放在说明书效果部分,权利要求中改写为“减少人工切图”“减少重复生成”“提高一致性”等技术特征。
|
||||
3. 避免在权利要求中绑定特定供应商或模型名称;模型名称可保留在实施例中。
|
||||
4. 如需扩大保护范围,可将“2D 小游戏”进一步上位为“交互式图像驱动应用”的美术素材生成方法。
|
||||
5. 如需增强授权稳定性,可将“文字驱动生成 + 透明 spritesheet + 自动边界检测 + 顺序映射 + 代码绑定”组合为主权利要求的必要技术特征。
|
||||
@@ -20,7 +20,7 @@ server-rs + Axum + SpacetimeDB
|
||||
|
||||
- HTTP 服务:`api-server`。
|
||||
- 领域模块:`module-ai`、`module-assets`、`module-auth`、`module-bark-battle`、`module-big-fish`、`module-combat`、`module-creative-agent`、`module-custom-world`、`module-inventory`、`module-match3d`、`module-npc`、`module-progression`、`module-puzzle`、`module-quest`、`module-runtime`、`module-runtime-item`、`module-runtime-story`、`module-square-hole`、`module-story`、`module-visual-novel`。
|
||||
- 平台副作用:`platform-agent`、`platform-auth`、`platform-llm`、`platform-oss`、`platform-speech`。
|
||||
- 平台副作用:`platform-agent`、`platform-auth`、`platform-image`、`platform-llm`、`platform-oss`、`platform-speech`。
|
||||
- 共享层:`shared-contracts`、`shared-kernel`、`shared-logging`。
|
||||
- SpacetimeDB:`spacetime-client`、`spacetime-module`。
|
||||
- 测试支撑:`tests-support`。
|
||||
@@ -44,7 +44,7 @@ npm run check:server-rs-ddd
|
||||
|
||||
`server-rs/crates/spacetime-client/src/mapper.rs` 只作为聚合入口,负责声明 `src/mapper/` 下的领域子模块并 re-export 原有 record / mapper 能力;不要在该文件继续堆叠大段映射实现。
|
||||
|
||||
当前子模块按调用领域拆分:`assets.rs`、`auth.rs`、`runtime.rs`、`runtime_profile.rs`、`custom_world.rs`、`puzzle.rs`、`match3d.rs`、`square_hole.rs`、`visual_novel.rs`、`big_fish.rs`、`story.rs`、`ai.rs`、`bark_battle.rs`、`combat.rs`、`inventory.rs`、`npc.rs`,跨领域轻量 helper 和共享 record 统一放在 `common.rs`。该拆分只改变 `spacetime-client` 文件组织,不改变 SpacetimeDB schema、生成绑定、procedure result 契约或外部 DTO;后续新增 mapper 时优先落到对应领域子模块,不得重新引入跨层 JSON 字符串兼容结构。
|
||||
当前子模块按调用领域拆分:`assets.rs`、`auth.rs`、`runtime.rs`、`runtime_profile.rs`、`custom_world.rs`、`puzzle.rs`、`match3d.rs`、`jump_hop.rs`、`wooden_fish.rs`、`square_hole.rs`、`visual_novel.rs`、`big_fish.rs`、`story.rs`、`ai.rs`、`bark_battle.rs`、`combat.rs`、`inventory.rs`、`npc.rs`,跨领域轻量 helper 和共享 record 统一放在 `common.rs`。该拆分只改变 `spacetime-client` 文件组织,不改变 SpacetimeDB schema、生成绑定、procedure result 契约或外部 DTO;后续新增 mapper 时优先落到对应领域子模块,不得重新引入跨层 JSON 字符串兼容结构。
|
||||
|
||||
## API 路由分组
|
||||
|
||||
@@ -60,6 +60,7 @@ npm run check:server-rs-ddd
|
||||
- 自定义世界 / RPG:`/api/runtime/custom-world*`、`/api/story/*`、`/api/runtime/chat/*`。
|
||||
- 拼图:`/api/runtime/puzzle/*`。
|
||||
- 抓大鹅 Match3D:`/api/creation/match3d/*`、`/api/runtime/match3d/*`。
|
||||
- 敲木鱼:`/api/creation/wooden-fish/*`、`/api/runtime/wooden-fish/*`。
|
||||
- 方洞挑战:`/api/creation/square-hole/*`、`/api/runtime/square-hole/*`。
|
||||
- 视觉小说:`/api/creation/visual-novel/*`、`/api/runtime/visual-novel/*`。
|
||||
- 大鱼吃小鱼:`/api/runtime/big-fish/*`。
|
||||
@@ -77,13 +78,16 @@ npm run check:server-rs-ddd
|
||||
|
||||
1. 每个能力 Module 只暴露 `router(state) -> Router<AppState>`,由 `app.rs` 统一 `.merge(...)`。
|
||||
2. `app.rs` 只保留全局 middleware、TraceLayer、request context、tracking middleware、入口开关和少量顶层 glue。
|
||||
3. 路由迁移和业务重构分阶段处理;先移动路由装配,再拆 handler 内部实现。
|
||||
4. 大 handler 拆分时优先按 `router.rs`、`handlers.rs`、`application.rs`、`assets.rs`、`mapper.rs`、`errors.rs` 分层。`handlers.rs` 只做 Axum extract、鉴权和 request/response,业务规则继续下沉到 `module-*`。
|
||||
5. 手写 Rust 模块入口统一使用同名 `.rs` 文件,例如 `puzzle.rs` + `puzzle/*.rs`、`match3d.rs` + `match3d/*.rs`;不要再新增 `mod.rs` 入口。生成的 SpacetimeDB Rust bindings 也由生成脚本同步为 `module_bindings.rs` + `module_bindings/*.rs` 布局。
|
||||
3. 能力 Module 可在路由内部用 `FromRef<AppState>` 派生自己的 Feature State,例如 `PuzzleApiState`。全局 `AppState` 仍作为进程组合根、鉴权层和全局中间件状态,但业务 handler 优先只提取对应 Feature State,不直接暴露完整 `AppState`。
|
||||
4. Feature State 只暴露该能力实际需要的 facade / adapter / 配置快照;若必须复用仍要求 `AppState` 的横切 helper(例如计费、外部失败审计或通用 tracking),应通过 Feature State 的窄方法或显式 `root_state()` 过渡,并在后续继续收窄。
|
||||
5. 路由迁移和业务重构分阶段处理;先移动路由装配,再拆 handler 内部实现,再收窄 handler 可见状态。
|
||||
6. 大 handler 拆分时优先按 `router.rs`、`handlers.rs`、`application.rs`、`assets.rs`、`mapper.rs`、`errors.rs` 分层。`handlers.rs` 只做 Axum extract、鉴权和 request/response,业务规则继续下沉到 `module-*`。
|
||||
7. 手写 Rust 模块入口统一使用同名 `.rs` 文件,例如 `puzzle.rs` + `puzzle/*.rs`、`match3d.rs` + `match3d/*.rs`;不要再新增 `mod.rs` 入口。生成的 SpacetimeDB Rust bindings 也由生成脚本同步为 `module_bindings.rs` + `module_bindings/*.rs` 布局。
|
||||
|
||||
拼图 `api-server` 内部拆分:
|
||||
|
||||
- `server-rs/crates/api-server/src/modules/puzzle.rs` 只负责路由装配、鉴权层和参考图 body limit;对外继续引用同一批 handler 名称。
|
||||
- `server-rs/crates/api-server/src/state.rs` 中的 `PuzzleApiState` 是拼图 HTTP/BFF 的 Feature State,集中暴露 `SpacetimeClient`、`PuzzleGalleryCache`、OSS client、作者查询所需认证服务、拼图 LLM client 和少量 VectorEngine / Agent 配置快照。拼图 handler 只提取 `State<PuzzleApiState>`,不得重新改回 `State<AppState>`。
|
||||
- `server-rs/crates/api-server/src/puzzle.rs` 只作为聚合入口,保留共享 import / 常量、内部模块声明和 handler re-export,不继续承载大段实现。
|
||||
- `server-rs/crates/api-server/src/puzzle/handlers.rs` 承接 Axum handler,负责 extract、鉴权上下文、调用 SpacetimeDB facade / 编排 helper,并返回 HTTP/SSE 响应。
|
||||
- `server-rs/crates/api-server/src/puzzle/draft.rs` 承接表单草稿保存、草稿编译、首关命名、UI 背景 prompt、降级 snapshot 和初始资产就绪校验。
|
||||
@@ -94,6 +98,8 @@ npm run check:server-rs-ddd
|
||||
|
||||
该拆分只改变 `api-server` 文件组织,不改变 `/api/runtime/puzzle/*` route、DTO、error envelope、SpacetimeDB schema、公开 gallery cache 语义或计费语义;后续继续细分时也必须先保持行为不变,再单独讨论领域规则下沉。
|
||||
|
||||
`/api/runtime/puzzle/runs*` 当前接受 `RuntimePrincipal`,可同时识别登录用户 Bearer 和 runtime guest token。推荐页嵌入运行态的正式开局、交换、拖拽、下一关、暂停、道具与排行榜请求,应由前端在登录态下继续携带账号 access token;匿名游客仅在确认为未登录时走 runtime guest token。不要再把拼图 runtime 当成只认普通 Bearer 的纯账号接口。
|
||||
|
||||
抓大鹅 Match3D `api-server` 内部拆分:
|
||||
|
||||
- `server-rs/crates/api-server/src/modules/match3d.rs` 继续负责路由装配和 body limit;对外 handler 名称保持不变。
|
||||
@@ -101,8 +107,8 @@ npm run check:server-rs-ddd
|
||||
- `server-rs/crates/api-server/src/match3d/handlers.rs` 承接 Axum handler,负责 extract、鉴权上下文、调用 SpacetimeDB facade / 编排 helper,并返回 HTTP 响应。
|
||||
- `server-rs/crates/api-server/src/match3d/draft.rs` 承接 Agent session、草稿编译、题材 / 难度 / 物品计划和草稿持久化编排。
|
||||
- `server-rs/crates/api-server/src/match3d/works.rs` 承接作品 CRUD、封面 / 背景 / 容器资产生成入口、发布 / Remix / 点赞 / 游玩记录和作品级 helper。
|
||||
- `server-rs/crates/api-server/src/match3d/item_assets.rs` 承接物品 sheet 生成、绿幕 / 近白底透明化、切图、append / replace / delete / sort / merge 和素材持久化。
|
||||
- `server-rs/crates/api-server/src/match3d/vector_engine_gemini.rs` 承接 VectorEngine Gemini 请求体、响应解析、base64 图片下载和上游错误归一。
|
||||
- `server-rs/crates/api-server/src/match3d/item_assets.rs` 承接物品生成批次编排、append / replace / delete / sort / merge、计费外层和草稿素材映射;sheet prompt、绿幕 / 近白底透明化、切图和切片持久化复用 `generated_asset_sheets` 通用模块。
|
||||
- `server-rs/crates/api-server/src/match3d/vector_engine_gemini.rs` 仅保留历史 VectorEngine Gemini 物品 sheet helper;当前草稿物品 spritesheet 以关卡整图为参考走 `gpt-image-2` 编辑链路,提示词、绿幕透明化和 OSS 持久化由 `item_assets.rs` / `works.rs` 约束。
|
||||
- `server-rs/crates/api-server/src/match3d/runtime.rs` 保留运行态轻量归一 helper;`mappers.rs` / `tags.rs` / `tests.rs` 分别承接 DTO 映射、标签 / 通用错误 helper 和原有单测。
|
||||
|
||||
该拆分只改变 `api-server` 文件组织,不改变 `/api/creation/match3d/*`、`/api/runtime/match3d/*` route、DTO、error envelope、SpacetimeDB schema、公开 gallery cache 语义、VectorEngine / OSS 副作用边界或计费语义;后续继续细分时也必须先保持行为不变,再单独讨论领域规则下沉到 `module-match3d`。
|
||||
@@ -113,7 +119,10 @@ npm run check:server-rs-ddd
|
||||
2. Adapter 输入应显式包含 provider、prompt、reference images、OSS prefix/path/file name、asset kind、entity kind/id、slot、owner/profile/source job、metadata 和可选透明背景后处理。
|
||||
3. Adapter 输出应保留 legacy public path、object key、asset object id、MIME、extension、task id 和实际 prompt。
|
||||
4. Adapter 不负责扣费、退款或钱包读取;计费仍由调用方显式包裹。
|
||||
5. Puzzle、Match3D、音频、GLB、视频等复杂媒体可以复用 OSS + asset object + binding 的底层持久化能力,但玩法专属处理规则留在各自编排层,不塞进公共接口。
|
||||
5. 图片 provider 协议不再放在玩法模块里实现。VectorEngine `gpt-image-2` 创建 / 编辑协议、URL / base64 图片解析、远端图片下载、请求超时 / 上游状态 / 响应解析 / 缺图 / 下载失败的结构化日志统一在 `server-rs/crates/platform-image/src/vector_engine/`;其中 `client.rs` 只保留 provider 调用编排,`transport.rs` 负责 HTTP client 与 reqwest 错误归一,`request.rs` 负责请求体和路径,`payload.rs` 负责响应 JSON 字段提取,`response.rs` 负责响应状态分流和图片结果归一。`api-server` 只负责配置校验、玩法 prompt 编排、OSS / asset object / binding 持久化、计费和外部 API 失败审计落库。
|
||||
6. Puzzle、Match3D、音频、GLB、视频等复杂媒体可以复用 OSS + asset object + binding 的底层持久化能力,但玩法专属处理规则留在各自编排层,不塞进公共接口。
|
||||
7. 拼图入口页与结果页新增关卡的本地参考图不走浏览器直传 OSS,前端读取为 Data URL 后随创作 action 提交,并在读取前限制 6MB、显示“图片≤6MB”。`api-server` 必须对 Data URL 实际字节数再次校验;历史图片才提交 `referenceImageAssetObjectId(s)`,后端校验 `asset_object` 的 bucket、kind、图片 MIME、大小和 owner 后签发只读 URL 给 VectorEngine 读取。
|
||||
8. 系列素材图集实现真相源在 `server-rs/crates/platform-image/src/generated_asset_sheets/`:调用方必须传入 `grid_size` 作为 `n*n` 的 `n`,可选传入物品名称 prompt 模板和特殊设定 prompt;模块负责 sheet prompt 组装、按 `n*n` 切片、透明化、PNG 输出、OSS private upload 请求构造和 sheet / item / special prompt 元数据持久化。`server-rs/crates/api-server/src/generated_asset_sheets.rs` 只保留 `AppState` / `AppError` 适配和兼容导出。玩法只负责规划 slot、调用具体生图 provider、计费、失败回写,以及把通用切片结果映射回自己的 DTO / 草稿 / runtime 字段。
|
||||
|
||||
## SpacetimeDB schema 变更规则
|
||||
|
||||
@@ -122,7 +131,7 @@ npm run check:server-rs-ddd
|
||||
3. 删除字段、改名、重排字段、改类型或修改字段属性前,必须先询问用户并确认迁移计划。
|
||||
4. Vec 字段不要直接写无法 const 求值的 default;需要默认空集合时优先使用 `Option<Vec<T>>` 加 `#[default(None::<Vec<T>>)]`,业务层归一为空数组。
|
||||
5. 运行态读表必须按已声明索引访问。只要 table 上存在覆盖查询前缀的 `#[index(...)]` 或主键 / unique accessor,列表、详情、快照组装和计数都先用对应 accessor `.filter(...)` / `.find(...)`,再在内存中处理索引无法覆盖的残余条件;不得用 `.iter().filter(...)` 扫整表替代现成索引。
|
||||
6. 面向公开列表的只读投影优先做成 public view / public 读模型表,并由 `api-server` 的 `spacetime-client` 长期订阅后读本地 cache。短期不把作品列表整体交给浏览器前端直接订阅;不要让 HTTP 列表接口每次请求都调用 procedure 重新组装全量列表。需要请求时间窗口的轻量统计可订阅公开统计表后在 `api-server` 本地聚合,需要写入副作用的详情、点赞、游玩记录仍可走 procedure / reducer。中期如要让前端可选直连订阅,只能新增或统一稳定的专用 public read model,例如 `public_work_gallery_entry`,并保持字段、排序键、公开权限和降级语义由后端投影定义;前端不得直接订阅 `puzzle_work_profile`、`custom_world_profile` 等领域源表,也不得自己做 join、聚合或权限逻辑。首屏、排序、字段归一、权限降级和 HTTP fallback 仍由 `api-server` BFF 维持。
|
||||
6. 面向公开列表的只读投影优先做成 public view / public 读模型表,并由 `api-server` 的 `spacetime-client` 长期订阅后读本地 cache。跨玩法公开作品统一主读模型是 `public_work_gallery_entry` 和 `public_work_detail_entry`;各玩法既有 `*_gallery_card_view` / `*_gallery_view` / `custom_world_gallery_entry` 保留为 source view 和兼容路径。短期不把作品列表整体交给浏览器前端直接订阅;不要让 HTTP 列表接口每次请求都调用 procedure 重新组装全量列表。需要请求时间窗口的轻量统计可订阅 `public_work_play_daily_stat` 后在 `api-server` 本地聚合,需要写入副作用的详情、点赞、游玩记录仍走玩法 procedure / reducer。前端不得直接订阅 `puzzle_work_profile`、`custom_world_profile` 等领域源表,也不得自己做 join、聚合或权限逻辑。首屏、排序、字段归一、权限降级和 HTTP fallback 由 `api-server` BFF 维持。
|
||||
7. 多列索引按 SpacetimeDB 绑定生成的元组参数直接传入,例如 `.filter((source_type, profile_id, played_day))`;前缀查询只传前缀元组,例如 `.filter((scope_kind, scope_id.as_str()))`。不要为了绕过类型问题退回整表遍历。
|
||||
8. procedure result 必须返回 typed snapshot / typed value。`spacetime-client` mapper 不得再通过 `row_json/session_json/work_json/items_json/run_json/event_json/feedback_json: Option<String>` 做跨层 JSON 字符串传输,也不得在 mapper 里反序列化旧 `*JsonRecord` 兼容结构。业务内部持久化字段如 `profile_payload_json`、`levels_json` 等不属于 procedure result 载荷例外,仍按各自表契约处理。
|
||||
9. 修改后运行:
|
||||
@@ -150,13 +159,15 @@ npm run check:server-rs-ddd
|
||||
## 外部服务与资产
|
||||
|
||||
- LLM:`GENARRATIVE_LLM_*`,创意 Agent 另用 `APIMART_BASE_URL` / `APIMART_API_KEY`。
|
||||
- 图片生成:VectorEngine / APIMart / DashScope,密钥只在后端环境变量中。
|
||||
- Match3D 物品 sheet:VectorEngine Gemini `gemini-3-pro-image-preview` 原生 `generateContent`。
|
||||
- Match3D 封面和 9:16 纯背景:VectorEngine `/v1/images/generations`。
|
||||
- Match3D 1:1 容器 UI:VectorEngine `/v1/images/edits` multipart 参考图。
|
||||
- Hyper3D / Rodin:只保留后端安全代理和旧数据兼容;新 Match3D 草稿和批量新增不再生成 GLB。
|
||||
- 音频:视觉小说专用音频路由保留;拼图和抓大鹅生成入口暂时关闭,通用 `/api/creation/audio/*` 对相关目标返回 `410 Gone`。
|
||||
- 图片生成:VectorEngine `gpt-image-2` 图片 provider 归属 `platform-image`,密钥只在后端环境变量中;`api-server` 内的 `openai_image_generation.rs` 只是兼容调用面和外部失败审计桥接,不再承载 provider 协议实现。APIMart 只保留给创意 Agent `gpt-5` Responses 文本 / 多模态链路;DashScope 只按仍在使用的历史能力单独处理,不作为 GPT-image-2 兜底。
|
||||
- Match3D 物品 sheet:关卡整图完成后走 VectorEngine `/v1/images/edits` multipart `image`,模型为 `gpt-image-2`,`2K 1:1` 输出 `10*10` spritesheet;物品 sheet prompt 固定要求纯绿色绿幕背景,后端上传 OSS 前必须把绿幕扣成透明 PNG,并把透明整图写入 `itemSpritesheetImageSrc/itemSpritesheetImageObjectKey`。后端优先按透明 alpha 连通域从该 sheet 识别真实素材矩形并持久化 20 个物品、每个 5 个形态;识别数量不足时才回退 `10*10` 固定网格。通用系列素材图集的行列索引按每行 2 个物品计算,必须落在 `1..=10`,难度只决定运行态加载 3 / 9 / 15 / 20 种。
|
||||
- Match3D UI spritesheet 和背景派生图:关卡整图作为参考图并发生成 `1K 1:1` UI spritesheet 与 `1K 9:16` 背景图,模型均为 `gpt-image-2`。UI spritesheet prompt 固定要求纯绿色绿幕背景,后端上传 OSS 前必须把绿幕扣成透明 PNG;背景图必须合成为全画幅不透明 PNG。
|
||||
- Match3D 1:1 容器 UI:VectorEngine `/v1/images/edits` multipart 参考图。该容器参考图是后端生图协议输入,必须通过 `include_bytes!` 随 `api-server` 编译进二进制,避免 API 单独发布或运行目录缺少 `public/` 时生成失败。
|
||||
- 敲木鱼敲击物和背景环境图:VectorEngine `/v1/images/edits`,模型固定 `gpt-image-2`。敲击物支持 multipart 多参考图,第一张固定为后端内嵌默认木鱼图,用户上传图只作为新主题参考;prompt 必须要求 `1:1` 真实透明 alpha PNG 并禁止黑底、白底、棋盘格和任何实底背景。当前敲击物上传 OSS 前不做服务端抠图后处理,避免误伤玉米等主体像素。背景环境图只使用第一步抠图完成后的透明敲击物图作为参考,prompt 必须要求中央主体预留区保持干净,中央 40% 区域禁止出现主题主体、主体局部特写、轮廓影子或重复元素,主题元素只能作为外围氛围,且必须显式声明不继承任何绿色底色、绿幕底色或纯绿色画布。
|
||||
- Hyper3D / Rodin:只保留后端安全代理和旧数据兼容;Rodin 提交、状态、下载和响应解析归属 `platform-hyper3d`,`api-server/src/hyper3d_generation.rs` 只做路由、配置和错误 envelope 映射;新 Match3D 草稿和批量新增不再生成 GLB。
|
||||
- 音频:视觉小说专用音频路由保留;VectorEngine Suno/Vidu provider 协议、任务提交/查询、音频 URL 提取、下载、MIME/extension 归一和 OSS put 请求准备归属 `platform-audio`。`api-server/src/vector_engine_audio_generation.rs` 只做路由、配置、计费、asset object confirm、entity binding 和错误 envelope 映射;拼图、抓大鹅和敲木鱼提示词生成音效入口暂时关闭,通用 `/api/creation/audio/*` 对这些目标返回 `410 Gone`。敲木鱼创作只接收上传 / 录音音频资产;未提供时由 `api-server` 写回内置默认木鱼音 `/wooden-fish/default-hit-sound.mp3`。
|
||||
- OSS:私有 generated legacy path 进入浏览器前必须通过 `/api/assets/read-url` 换签;不要裸请求 `/generated-*`。
|
||||
- 外部 API 失败审计:外部供应商调用未成功时,`api-server` 必须发送 OTLP 失败事件并写入 `tracking_event`。VectorEngine 图片 provider 在 `platform-image` 内输出结构化日志和 `PlatformImageFailureAudit`,覆盖 `request_send`、`response_body`、`upstream_status`、`response_parse`、`missing_image` 和 `image_download` 阶段;`api-server` 只把该 audit 映射成 `external_api_call_failure`,`scope_kind = module`、`scope_id = provider`、`module_key = external-api`。metadata 固定包含 provider、endpoint、operation、failureStage、statusCode、statusClass、timeout、retryable、errorMessage、latencyMs、promptChars、referenceImageCount、imageModel 和 rawExcerpt。入库优先复用 tracking outbox,outbox 不可写或保护阈值拒绝时回退同步写 SpacetimeDB;不得新增前端兜底或在 SpacetimeDB reducer 内做外部 I/O。
|
||||
|
||||
## SpacetimeDB 表目录
|
||||
|
||||
@@ -293,7 +304,7 @@ npm run check:server-rs-ddd
|
||||
- Rust view:`big_fish_gallery_view`
|
||||
- 返回类型:`Vec<BigFishWorkSummarySnapshot>`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/big_fish/session.rs`
|
||||
- 说明:大鱼吃小鱼公开广场列表投影,只从 `Published` creation session 组装公开卡片字段;`api-server` 的 `spacetime-client` 长期订阅 `SELECT * FROM big_fish_gallery_view` 与 `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'big-fish'` 后,从本地 cache 构造 `/api/runtime/big-fish/gallery` 响应。公开列表不再每个 HTTP 请求调用 `list_big_fish_works` procedure;个人作品列表、详情、点赞、游玩记录和 Remix 仍按原有 procedure / reducer 路径处理。
|
||||
- 说明:大鱼吃小鱼公开 source 投影,只从 `Published` creation session 组装公开卡片字段;统一公开列表 / 详情主路径通过 `public_work_gallery_entry` / `public_work_detail_entry` 消费该 view 并映射成跨玩法契约。玩法旧 gallery 路径保留兼容 shape;个人作品列表、详情、点赞、游玩记录和 Remix 仍按原有 procedure / reducer 路径处理。
|
||||
|
||||
### `chapter_progression`
|
||||
|
||||
@@ -304,11 +315,15 @@ npm run check:server-rs-ddd
|
||||
|
||||
- Rust 结构体:`CreationEntryConfig`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs`
|
||||
- 字段:`config_id`、`start_title`、`start_description`、`start_idle_badge`、`start_busy_badge`、`modal_title`、`modal_description`、`updated_at`、`event_title`、`event_description`、`event_cover_image_src`、`event_prize_pool_mud_points`、`event_starts_at_text`、`event_ends_at_text`。
|
||||
- 迁移兼容:旧迁移包缺少活动横幅字段时,由 `migration.rs` 写入 `None` / `58000` 默认值;运行态读取层再按 `module-runtime` 默认横幅归一,不覆盖后台已保存配置。
|
||||
|
||||
### `creation_entry_type_config`
|
||||
|
||||
- Rust 结构体:`CreationEntryTypeConfig`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs`
|
||||
- 字段:`id`、`title`、`subtitle`、`badge`、`image_src`、`visible`、`open`、`sort_order`、`updated_at`、`category_id`、`category_label`、`category_sort_order`。
|
||||
- 迁移兼容:旧迁移包缺少入口分类字段时,由 `migration.rs` 写入 `None` / `0` 默认值;入口分组展示由 `module-runtime` 和前端展示派生消费。
|
||||
|
||||
### `custom_world_agent_message`
|
||||
|
||||
@@ -324,6 +339,7 @@ npm run check:server-rs-ddd
|
||||
|
||||
- Rust 结构体:`CustomWorldAgentSession`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/custom_world.rs`
|
||||
- 发布约束:`publish_world` 的 action payload 不要求携带 `settingText`;`spacetime-module` 调用 `module-custom-world::resolve_custom_world_publish_setting_text(...)`,优先从当前 `draft_profile_json` 草稿真相派生正式 `setting_text`,避免旧会话 `seed_text` 为空时在最终 compile / publish 阶段触发 `custom_world.setting_text 不能为空`。
|
||||
|
||||
### `custom_world_draft_card`
|
||||
|
||||
@@ -334,7 +350,7 @@ npm run check:server-rs-ddd
|
||||
|
||||
- Rust 结构体:`CustomWorldGalleryEntry`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/custom_world.rs`
|
||||
- 作用:自定义世界公开作品列表读模型。`api-server` 的 `spacetime-client` 长期订阅 `SELECT * FROM custom_world_gallery_entry` 与 `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'custom-world'`,`/api/runtime/custom-world-gallery` 从本地 cache 排序并聚合 `recentPlayCount7d`,不再每个 HTTP 请求调用 `list_custom_world_gallery_entries` procedure。旧 procedure 只用于兼容旧库缺少 gallery 读模型行时的一次性同步兜底。
|
||||
- 作用:自定义世界公开 source 读模型。统一公开列表 / 详情主路径通过 `public_work_gallery_entry` / `public_work_detail_entry` 消费该投影并映射成跨玩法契约;`/api/runtime/custom-world-gallery` 保留旧 HTTP shape,并从统一 public cache 映射回旧 DTO。旧 procedure 只用于兼容旧库缺少 gallery 读模型行时的一次性同步兜底。
|
||||
|
||||
### `custom_world_profile`
|
||||
|
||||
@@ -361,6 +377,75 @@ npm run check:server-rs-ddd
|
||||
- Rust 结构体:`InventorySlot`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/gameplay.rs`
|
||||
|
||||
### `jump_hop_agent_session`
|
||||
|
||||
- Rust 结构体:`JumpHopAgentSessionRow`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/jump_hop/tables.rs`
|
||||
|
||||
### `jump_hop_event`
|
||||
|
||||
- Rust 结构体:`JumpHopEventRow`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/jump_hop/tables.rs`
|
||||
|
||||
### `jump_hop_runtime_run`
|
||||
|
||||
- Rust 结构体:`JumpHopRuntimeRunRow`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/jump_hop/tables.rs`
|
||||
|
||||
### `jump_hop_work_profile`
|
||||
|
||||
- Rust 结构体:`JumpHopWorkProfileRow`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/jump_hop/tables.rs`
|
||||
|
||||
### SpacetimeDB view:`jump_hop_gallery_card_view`
|
||||
|
||||
- Rust view:`jump_hop_gallery_card_view`
|
||||
- 返回类型:`Vec<JumpHopGalleryCardViewRow>`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/jump_hop.rs`
|
||||
- 说明:跳一跳公开列表 source 投影,只暴露 `publication_status = Published` 的作品卡片字段;统一公开列表主路径通过 `public_work_gallery_entry` 消费该 view 并映射成跨玩法契约。个人作品列表、详情、发布和运行态仍按 procedure 路径处理。
|
||||
|
||||
### SpacetimeDB view:`jump_hop_gallery_view`
|
||||
|
||||
- Rust view:`jump_hop_gallery_view`
|
||||
- 返回类型:`Vec<JumpHopGalleryViewRow>`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/jump_hop.rs`
|
||||
- 说明:跳一跳公开详情兼容投影,包含作品、路径和素材字段;统一公开详情主路径通过 `public_work_detail_entry` 消费该 view,只保留平台详情页展示摘要。
|
||||
|
||||
### `wooden_fish_agent_session`
|
||||
|
||||
- Rust 结构体:`WoodenFishAgentSessionRow`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/wooden_fish/tables.rs`
|
||||
|
||||
### `wooden_fish_event`
|
||||
|
||||
- Rust 结构体:`WoodenFishEventRow`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/wooden_fish/tables.rs`
|
||||
|
||||
### `wooden_fish_runtime_run`
|
||||
|
||||
- Rust 结构体:`WoodenFishRuntimeRunRow`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/wooden_fish/tables.rs`
|
||||
|
||||
### `wooden_fish_work_profile`
|
||||
|
||||
- Rust 结构体:`WoodenFishWorkProfileRow`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/wooden_fish/tables.rs`
|
||||
- 说明:敲木鱼作品 profile 真相,包含敲击物图案、背景环境图、主题返回按钮图、敲击音效、飘字配置、发布状态和公开计数;`background_asset_json` 保存 image2 生成的 9:16 背景环境图资产快照,`back_button_asset_json` 保存 image2 生成并去绿后的 1:1 返回按钮图资产快照,旧迁移数据按 `None` 兼容。
|
||||
|
||||
### SpacetimeDB view:`wooden_fish_gallery_card_view`
|
||||
|
||||
- Rust view:`wooden_fish_gallery_card_view`
|
||||
- 返回类型:`Vec<WoodenFishGalleryCardViewRow>`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/wooden_fish.rs`
|
||||
- 说明:敲木鱼公开列表 source 投影,只暴露 `publication_status = published` 的作品卡片字段;统一公开列表主路径通过 `public_work_gallery_entry` 消费该 view 并映射成跨玩法契约。个人作品列表、详情、发布和运行态仍按 procedure 路径处理。
|
||||
|
||||
### SpacetimeDB view:`wooden_fish_gallery_view`
|
||||
|
||||
- Rust view:`wooden_fish_gallery_view`
|
||||
- 返回类型:`Vec<WoodenFishGalleryViewRow>`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/wooden_fish.rs`
|
||||
- 说明:敲木鱼公开详情兼容投影,包含敲击物图案、背景环境图、主题返回按钮图、敲击音效和飘字配置;统一公开详情主路径通过 `public_work_detail_entry` 消费该 view,只保留平台详情页展示摘要。
|
||||
|
||||
### `match3d_agent_message`
|
||||
|
||||
- Rust 结构体:`Match3DAgentMessageRow`
|
||||
@@ -386,7 +471,7 @@ npm run check:server-rs-ddd
|
||||
- Rust view:`match3d_gallery_view`
|
||||
- 返回类型:`Vec<Match3DGalleryViewRow>`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/match3d.rs`
|
||||
- 说明:抓大鹅公开广场列表投影,只暴露 `publication_status = published` 的作品卡片字段;`api-server` 的 `spacetime-client` 长期订阅 `SELECT * FROM match_3_d_gallery_view` 与 `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'match3d'` 后,从本地 cache 构造 `/api/runtime/match3d/gallery` 响应。公开列表不再每个 HTTP 请求调用 `list_match3d_works` procedure;个人作品列表、详情、发布、点赞、游玩记录和 Remix 仍按原有 procedure / reducer 路径处理。
|
||||
- 说明:抓大鹅公开 source 投影,只暴露 `publication_status = published` 的作品卡片字段;统一公开列表 / 详情主路径通过 `public_work_gallery_entry` / `public_work_detail_entry` 消费该 view 并映射成跨玩法契约。个人作品列表、详情、发布、点赞、游玩记录和 Remix 仍按原有 procedure / reducer 路径处理。
|
||||
|
||||
### `npc_state`
|
||||
|
||||
@@ -519,14 +604,14 @@ npm run check:server-rs-ddd
|
||||
- Rust view:`puzzle_gallery_view`
|
||||
- 返回类型:`Vec<PuzzleWorkProfile>`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/puzzle.rs`
|
||||
- 说明:拼图广场公开详情兼容投影,只暴露 `publication_status = Published` 的作品,但返回完整 `PuzzleWorkProfile`,包含 levels / anchor_pack 等详情级字段;公开列表主路径不再订阅该 view。
|
||||
- 说明:拼图广场公开详情 source / 兼容投影,只暴露 `publication_status = Published` 的作品,但返回完整 `PuzzleWorkProfile`,包含 levels / anchor_pack 等详情级字段;统一公开详情主路径通过 `public_work_detail_entry` 消费该 view,只保留平台详情页展示摘要。
|
||||
|
||||
### SpacetimeDB view:`puzzle_gallery_card_view`
|
||||
|
||||
- Rust view:`puzzle_gallery_card_view`
|
||||
- 返回类型:`Vec<PuzzleGalleryCardViewRow>`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/puzzle.rs`
|
||||
- 说明:拼图广场公开列表卡片投影,只暴露前端列表卡片需要的公开字段,不携带 levels / anchor_pack 等详情级载荷;`api-server` 的 `spacetime-client` 长期订阅 `SELECT * FROM puzzle_gallery_card_view` 与 `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'puzzle'` 后,从本地 cache 构造 `/api/runtime/puzzle/gallery` 响应,并在本地按当前请求时间聚合 `recentPlayCount7d`,不再每个 HTTP 请求调用 `list_puzzle_gallery` procedure。
|
||||
- 说明:拼图公开列表 source 投影,只暴露前端列表卡片需要的公开字段,不携带 levels / anchor_pack 等详情级载荷;统一公开列表主路径通过 `public_work_gallery_entry` 消费该 view,`/api/runtime/puzzle/gallery` 保留旧 HTTP shape,并从统一 public cache 映射回 `PuzzleGalleryResponse`。
|
||||
|
||||
### 拼图公开列表 HTTP 窗口缓存
|
||||
|
||||
@@ -539,29 +624,40 @@ npm run check:server-rs-ddd
|
||||
|
||||
`spacetime-client` 建立每个池连接时会等待下列订阅初始同步:
|
||||
|
||||
- `SELECT * FROM public_work_gallery_entry`
|
||||
- `SELECT * FROM public_work_detail_entry`
|
||||
- `SELECT * FROM bark_battle_gallery_view`
|
||||
- `SELECT * FROM puzzle_gallery_card_view`
|
||||
- `SELECT * FROM jump_hop_gallery_card_view`
|
||||
- `SELECT * FROM wooden_fish_gallery_card_view`
|
||||
- `SELECT * FROM custom_world_gallery_entry`
|
||||
- `SELECT * FROM match_3_d_gallery_view`
|
||||
- `SELECT * FROM square_hole_gallery_view`
|
||||
- `SELECT * FROM visual_novel_gallery_view`
|
||||
- `SELECT * FROM big_fish_gallery_view`
|
||||
|
||||
下列订阅用于统计或配置缓存,订阅失败不会让公开列表连接整体不可用,调用方保留兼容兜底:
|
||||
|
||||
- `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'puzzle'`
|
||||
- `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'jump-hop'`
|
||||
- `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'wooden-fish'`
|
||||
- `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'custom-world'`
|
||||
- `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'match3d'`
|
||||
- `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'square-hole'`
|
||||
- `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'visual-novel'`
|
||||
- `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'big-fish'`
|
||||
- `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'bark-battle'`
|
||||
- `SELECT * FROM creation_entry_config`
|
||||
- `SELECT * FROM creation_entry_type_config`
|
||||
- `SELECT * FROM asset_object`
|
||||
|
||||
拼图、自定义世界、抓大鹅、方洞挑战、视觉小说和大鱼吃小鱼的公开列表 HTTP 路由都从订阅 cache 读取公开 read model / view。各玩法的个人作品列表、详情、发布、点赞、游玩记录、Remix 和其它需要鉴权或写入副作用的路径继续走 procedure / reducer;不要为了公开列表性能把这些 owner-specific 或 mutation 语义混进 public view。
|
||||
跨玩法公开作品列表 / 详情主读模型是 `public_work_gallery_entry` 与 `public_work_detail_entry`。拼图、自定义世界等旧玩法公开列表 HTTP 路由保留原响应 shape,由 BFF mapper 从统一 public cache 映射回当前 DTO;旧 `*_gallery_card_view` / `*_gallery_view` / `custom_world_gallery_entry` 继续作为 source view 和兼容缓存。各玩法的个人作品列表、详情、发布、点赞、游玩记录、Remix 和其它需要鉴权或写入副作用的路径继续走 procedure / reducer;不要为了公开列表性能把这些 owner-specific 或 mutation 语义混进 public view。
|
||||
|
||||
`GET /api/creation-entry/config` 和入口熔断优先从订阅 cache 读取创作入口配置;cache 缺失时使用最近一次成功读取的内存快照,再兜底调用 `get_creation_entry_config` procedure 完成空库种子或旧库兼容。
|
||||
入口配置快照包含 start card、类型弹窗、活动横幅和入口类型列表;入口类型列表新增 `category_id`、`category_label`、`category_sort_order` 后,后台 upsert、`shared-contracts`、`module-runtime` 和 `spacetime-client` binding 必须同步,旧迁移 JSON 通过 `migration.rs` 默认值兼容。
|
||||
|
||||
未来可选:若发现页、推荐流和各玩法广场需要统一给浏览器前端直接订阅公开作品列表,只新增 / 统一专用 public read model,例如 `public_work_gallery_entry`。该 read model 必须是后端投影后的公开作品卡片契约,覆盖作品类型、公开作品号、标题、摘要、封面、作者展示名、排序键、公开统计和入口开关后的可见性,不暴露玩法领域源表 row shape。前端可选择订阅这个稳定投影来减少 HTTP 拉取,但不能订阅 `puzzle_work_profile`、`custom_world_profile` 等源表后自行拼装列表;BFF 仍保留首屏、SEO / 分享、旧客户端、订阅失败和灰度期间的 HTTP fallback。
|
||||
RPG 创作入口的配置 ID 是 `rpg`,当前 `visible=true`、`open=true`;历史 `custom-world` 路由仍是 RPG 的工程域与运行态源类型。入口熔断把 `/api/runtime/custom-world*`、`/api/story/*` 和 `/api/runtime/chat/*` 统一映射到 `rpg`,不要新增平行 `airp` 路由或用 `airp` 接管当前文字冒险链路。
|
||||
|
||||
结构化创作和 RPG 的 LLM JSON 链路默认不启用 Responses `web_search`;只有在明确需要联网增强时,才通过 `GENARRATIVE_RPG_LLM_WEB_SEARCH_ENABLED` 或 `GENARRATIVE_CREATION_AGENT_LLM_WEB_SEARCH_ENABLED` 显式打开。否则未开通工具的上游会先吐自然语言再返回 `ToolNotOpen`,这类失败要按上游工具不可用处理,不要误判成模型返回结果解析失败。
|
||||
|
||||
统一公开作品 BFF 路由是 `GET /api/public-works` 与 `GET /api/public-works/{publicWorkCode}`,响应契约由 `shared-contracts::public_work` 和 `packages/shared/src/contracts/publicWork.ts` 共同维护。前端首期仍走 BFF HTTP,不直接订阅 SpacetimeDB;后续若允许浏览器直连订阅,也只能订阅 `public_work_gallery_entry` / `public_work_detail_entry` 这类稳定公开 read model,不能订阅 `puzzle_work_profile`、`custom_world_profile` 等源表后自行拼装列表。设计细节见 `docs/technical/【后端架构】统一公开作品ReadModel设计-2026-05-26.md`。
|
||||
|
||||
### `quest_log`
|
||||
|
||||
@@ -613,7 +709,7 @@ npm run check:server-rs-ddd
|
||||
- Rust view:`square_hole_gallery_view`
|
||||
- 返回类型:`Vec<SquareHoleGalleryViewRow>`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/square_hole.rs`
|
||||
- 说明:方洞挑战公开广场列表投影,只暴露 `publication_status = published` 的作品卡片字段;`api-server` 的 `spacetime-client` 长期订阅 `SELECT * FROM square_hole_gallery_view` 与 `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'square-hole'` 后,从本地 cache 构造 `/api/runtime/square-hole/gallery` 响应。公开列表不再每个 HTTP 请求调用 `list_square_hole_works` procedure;个人作品列表、详情、发布、点赞、游玩记录和 Remix 仍按原有 procedure / reducer 路径处理。
|
||||
- 说明:方洞挑战公开 source 投影,只暴露 `publication_status = published` 的作品卡片字段;统一公开列表 / 详情主路径通过 `public_work_gallery_entry` / `public_work_detail_entry` 消费该 view 并映射成跨玩法契约。个人作品列表、详情、发布、点赞、游玩记录和 Remix 仍按原有 procedure / reducer 路径处理。
|
||||
|
||||
### `story_event`
|
||||
|
||||
@@ -636,6 +732,7 @@ npm run check:server-rs-ddd
|
||||
- Rust 结构体:`TrackingEvent`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/runtime/profile.rs`
|
||||
- 写入:关键业务埋点同步调用单条 procedure;普通 HTTP route tracking 由 `api-server` 本机 outbox 批量调用 `record_tracking_events_and_return`。outbox 到达批量阈值时先封存 active 文件并切新 active,后台 worker 异步 flush sealed 文件,HTTP 请求线程不等待 SpacetimeDB。`FLUSH_INTERVAL_MS` 只负责兜底封存长时间未满批的 active 文件,`MAX_BYTES` 只做磁盘保护阈值。`event_id` 必须稳定且全局唯一,批量重试时用唯一索引做幂等跳过。
|
||||
- 外部 API 失败:`event_key = external_api_call_failure` 使用同一张表落库;它是供应商失败审计事实,不新增 SpacetimeDB 表,查询时按 `module_key = 'external-api'` 或 `scope_kind = module AND scope_id = '<provider>'` 过滤。
|
||||
|
||||
### `treasure_record`
|
||||
|
||||
@@ -687,4 +784,4 @@ npm run check:server-rs-ddd
|
||||
- Rust view:`visual_novel_gallery_view`
|
||||
- 返回类型:`Vec<VisualNovelGalleryViewRow>`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/visual_novel.rs`
|
||||
- 说明:视觉小说公开广场列表投影,只暴露 `publication_status = published` 的作品卡片字段,不把完整 `draft` 暴露给公开列表订阅;`api-server` 的 `spacetime-client` 长期订阅 `SELECT * FROM visual_novel_gallery_view` 与 `SELECT * FROM public_work_play_daily_stat WHERE source_type = 'visual-novel'` 后,从本地 cache 构造 `/api/runtime/visual-novel/gallery` 响应。公开列表不再每个 HTTP 请求调用 `list_visual_novel_works` procedure;个人历史、详情、运行态和发布仍按原有 procedure / reducer 路径处理。
|
||||
- 说明:视觉小说公开 source 投影,只暴露 `publication_status = published` 的作品卡片字段,不把完整 `draft` 暴露给公开列表订阅;统一公开列表 / 详情主路径通过 `public_work_gallery_entry` / `public_work_detail_entry` 消费该 view 并映射成跨玩法契约。个人历史、详情、运行态和发布仍按原有 procedure / reducer 路径处理。
|
||||
|
||||
@@ -40,10 +40,28 @@ npm run dev:web
|
||||
单独启动 Rust API server:
|
||||
|
||||
```bash
|
||||
npm run api-server
|
||||
npm run dev:api-server
|
||||
```
|
||||
|
||||
后端日志默认写入 `logs/api-server/`。后端 API smoke 使用 `npm run api-server` 并检查 `/healthz`;不要使用旧 `api-server:maincloud` 或任何 `GENARRATIVE_SPACETIME_MAINCLOUD_*` 口径。
|
||||
后端日志默认写入 `logs/api-server/`。后端 API smoke 使用 `npm run dev:api-server` 并检查 `/healthz`;不要使用旧 `api-server:maincloud` 或任何 `GENARRATIVE_SPACETIME_MAINCLOUD_*` 口径。
|
||||
|
||||
开发态 `npm run dev` 与 `npm run dev:api-server` 会默认注入 `GENARRATIVE_DEV_PASSWORD_ENTRY_AUTO_REGISTER_ENABLED=true`,因此密码登录在本地开发环境可直接注册未知手机号账号;生产环境仍按 `api-server` 配置默认关闭该开关。
|
||||
|
||||
如果本地 `GET /api/creation-entry/config` 返回 `No such procedure`,或 `api-server` 日志出现 `no such table: puzzle_gallery_card_view` / `no such table: wooden_fish_gallery_card_view` 这类公开 view 缺失,通常是 `.env.local` 指向的 SpacetimeDB 库还没有发布当前 `spacetime-module`,或当前 CLI 身份无权发布该库。debug 构建的 `api-server` 会临时使用后端默认入口配置兜底,避免创作作品架整块消失;正式修复仍应切换到拥有目标库权限的 SpacetimeDB 身份后重新运行 `npm run dev` 完成发布,或用 gitignored 的 `spacetime.local.json` 指向可发布的本地库。
|
||||
|
||||
本地排查 schema 漂移时,先用当前 dev server 显式查询目标库,例如:
|
||||
|
||||
```bash
|
||||
spacetime sql <database> "SELECT * FROM puzzle_gallery_card_view LIMIT 1" --server http://127.0.0.1:3101
|
||||
```
|
||||
|
||||
如果旧 `.env.local` 仍指向缺少当前 view 的库,例如 `xushi-p4wfr`,而当前可发布库已经包含这些 view,可在 gitignored 的 `spacetime.local.json` 写入 `{"database":"genarrative-dev-codex"}` 作为本机覆盖;写入时不要带 UTF-8 BOM,否则 `scripts/dev.mjs` 会忽略该文件。修改后重启 `api-server`,再检查 `/healthz` 和 `/api/runtime/puzzle/gallery`。
|
||||
|
||||
本地 `npm run dev:spacetime` 发布模块时必须显式忽略仓库根目录的 `spacetime.json`,由脚本固定追加 `--no-config` 并使用命令参数里传入的数据库名和 `--server http://127.0.0.1:3101`。否则 CLI 可能把发布目标改写到配置文件里的其他数据库,导致 `dev:spacetime` 启动后又因发布失败自动退出,浏览器随后会在 `ws://127.0.0.1:3101/v1/database/.../subscribe` 看到连接拒绝。
|
||||
|
||||
本地 `spacetime` CLI / standalone 版本必须和 `server-rs/Cargo.toml` 里锁定的 `spacetimedb` 版本一致。若版本错配,procedure 返回值可能在宿主侧触发 `Failed to BSATN deserialize procedure return value`,api-server 最终表现为敲木鱼等创作动作的 `SpacetimeDB procedure 调用超时`。排障时先运行 `spacetime --version`,再对照 `server-rs/Cargo.toml` 的 `spacetimedb = "..."`;需要切版本时执行 `spacetime version install <version> && spacetime version use <version>`,然后重新启动 `npm run dev:spacetime`。当前 `scripts/dev.mjs` 会在启动和复用本地 SpacetimeDB 前写入并校验 `dev-spacetime-tool-version`,避免把旧 standalone 继续带进新一轮创作。
|
||||
|
||||
本地 `.env`、`.env.local` 或 `.env.secrets.local` 修改后必须重启 `api-server` 才会生效;若已经通过 `npm run dev` 启动完整联调,可在该终端输入 `rs api-server`。排查 RPG / 拼图 / 抓大鹅等 VectorEngine 生图链路时,确认 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY` 和 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 只在本地或服务器密钥文件中配置,不能写入 Git。VectorEngine `gpt-image-2` 图片协议、URL / base64 响应解析、远端图片下载和 provider 侧结构化日志在 `server-rs/crates/platform-image`;`api-server` 只做配置、玩法编排、OSS / asset 持久化、计费和失败审计落库。开局 CG 故事板、首图、背景和图集都属于长耗时图片请求;后端默认会把 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 下限收口到 `1000000`,旧进程仍可能沿用重启前的短超时。若开局 CG 故事板在 `send()` 阶段失败且日志显示 `SendRequest`,先看同一 request_id 的 `request_body_bytes`、`reference_data_url_bytes`、`sourceChain` 和 `rootSource`;当前开局 CG 会把角色图与首幕背景图压到单边 768 的 JPEG 后再作为 generations `image` 数组发送,`/v1/images/generations` 使用默认 HTTP 协商,只有 multipart `/v1/images/edits` 单独强制 HTTP/1.1。
|
||||
|
||||
查看本地 Rust / SpacetimeDB 日志:
|
||||
|
||||
@@ -94,16 +112,51 @@ SpacetimeDB bindings:
|
||||
npm run spacetime:generate
|
||||
```
|
||||
|
||||
## CodeGraph 本地代码索引
|
||||
|
||||
项目已安装 `@colbymchenry/codegraph` 作为开发期依赖,用于在本地生成语义代码索引,辅助 AI / IDE 做符号搜索、调用关系和影响范围分析。索引目录为 `.codegraph/`,其中 `config.json` 可提交,数据库、缓存和日志由 `.codegraph/.gitignore` 保持本机私有。
|
||||
|
||||
首次拉取或需要重建索引时:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run codegraph:init
|
||||
```
|
||||
|
||||
日常使用:
|
||||
|
||||
```bash
|
||||
npm run codegraph:status
|
||||
npm run codegraph:sync
|
||||
npm run codegraph:index
|
||||
```
|
||||
|
||||
Codex 项目级 hook 已放在 `.codex/config.toml` 与 `.codex/hooks/`:
|
||||
|
||||
- `PreToolUse` hook 会在 Codex 准备执行 `git commit` 前运行 `node .codex/hooks/pre-submit-compile-check.mjs`,依次执行 `npm run typecheck`、`npm run admin-web:typecheck`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`,发现编译错误会阻止本次提交。
|
||||
- `PostToolUse` hook 会在 Codex 工具修改文件后运行 `node .codex/hooks/post-edit-codegraph-sync.mjs`,执行 `npm run codegraph:sync` 刷新本地语义索引。
|
||||
- 如果某个 Codex 客户端版本尚未自动加载项目级 hook,可先手动运行 `node .codex/hooks/pre-submit-compile-check.mjs` 与 `node .codex/hooks/post-edit-codegraph-sync.mjs`;个人模型、token、MCP server 仍放在个人 `~/.codex/config.toml`,不要提交。
|
||||
|
||||
若要把 CodeGraph 接到 Codex CLI / Cursor / Claude Code 等 MCP 客户端,按本机 agent 配置执行 `codegraph install` 或参考 `codegraph install --print-config codex` 输出;不要把个人全局 agent 配置、token 或本机绝对路径提交到仓库。
|
||||
|
||||
## 后端改动验收
|
||||
|
||||
后端代码修改后,按变更范围选择:
|
||||
|
||||
- `cargo test -p <crate> --manifest-path server-rs/Cargo.toml`
|
||||
- `cargo test -p platform-image --manifest-path server-rs/Cargo.toml`
|
||||
- `cargo check -p api-server --manifest-path server-rs/Cargo.toml`
|
||||
- `cargo check -p spacetime-client --manifest-path server-rs/Cargo.toml`
|
||||
- `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`
|
||||
- `npm run check:server-rs-ddd`
|
||||
- `npm run api-server` 后请求 `/healthz`
|
||||
- `npm run dev:api-server` 后请求 `/healthz`
|
||||
|
||||
其中推荐页匿名游玩与 `work_play_start` 相关改动,至少要补跑:
|
||||
|
||||
```bash
|
||||
cargo check -p api-server --manifest-path server-rs/Cargo.toml
|
||||
npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "logged out recommend tab enters runtime without login modal|logged out desktop recommend page renders runtime directly|logged out desktop recommend rail enters runtime without login modal"
|
||||
```
|
||||
|
||||
涉及 SpacetimeDB schema 时必须补:
|
||||
|
||||
@@ -138,6 +191,7 @@ UI 相关修改要重点验证:
|
||||
4. 身份问题先查 `spacetime login show`、`spacetime server list` 和目标库权限,不通过切回旧 Node / PostgreSQL 绕过。
|
||||
5. 旧库迁移或 private 表数据保留走 `migration.rs` 的 JSON 导入导出和分片导入思路。
|
||||
6. Jenkins 数据库导入 / 导出流水线会先加载 `scripts/jenkins-prepare-toolchain-env.sh`,显式补齐 Jenkins 用户的 Node、Cargo、SpacetimeDB 工具链目录;如果目标机器安装路径不同,用 `GENARRATIVE_JENKINS_TOOL_PATHS` 传入额外 `bin` 目录。
|
||||
7. 本地 `npm run dev` / `npm run dev:api-server` 若没有显式 `GENARRATIVE_SPACETIME_TOKEN`,会在 SpacetimeDB 就绪后调用 `/v1/identity` 创建当前进程专用 Web API identity token,并只注入本次 `api-server` 环境,不写回 `.env.local`。启动日志只打印 identity 前缀,禁止打印 token 明文;若仍出现 `subscribe ... 401 Unauthorized`,先确认是否绕过了项目 dev 脚本或是否连接到非本次启动的 SpacetimeDB server。
|
||||
|
||||
## 生产运维
|
||||
|
||||
@@ -149,11 +203,17 @@ Nginx 负责站点和反向代理
|
||||
Jenkins 按 web / api / Spacetime module / build / deploy / publish 拆分
|
||||
```
|
||||
|
||||
`Genarrative-Web-Build` 的主站构建失败若出现 Rollup 报错 `"xxx" is not exported by "src/services/publicWorkCode.ts"`,优先按前端公开作品号工具缺失处理,而不是排查 Jenkins 节点环境。修复时要让 `publicWorkCode.ts` 的 `build<Play>PublicWorkCode` 与 `isSame<Play>PublicWorkCode` 成对导出,并补 `src/services/publicWorkCode.test.ts` 覆盖对应玩法前缀;随后用 `npm run build:production-release -- --component web --name <临时名>` 复现 Jenkins web 构建路径。
|
||||
|
||||
生产 Jenkins 的 `Pipeline script from SCM` 由 Windows controller 读取 Jenkinsfile,SCM URL 继续使用 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`。运行在 `linux && genarrative-build` 构建机上的 `Genarrative-Full-Build-And-Deploy` 源码解析阶段和 `Genarrative-Web-Build` checkout 阶段,优先使用 `http://127.0.0.1:3000/GenarrativeAI/Genarrative.git`,失败后回退到 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`;两层 checkout 都必须保留单分支 refspec、`shallow=true`、`depth=1`、`noTags=true` 与 `honorRefspec=true`,后续二次源码确认继续走 `scripts/jenkins-checkout-source.sh`。
|
||||
|
||||
`Genarrative-Stdb-Module-Build` 或 SpacetimeDB module 构建失败若出现 Rust `E0425 cannot find function migrate_*`,优先排查 `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs` 等同文件内默认种子迁移 helper 是否在分支合并时只保留了调用、漏掉了函数定义。修复时不要直接删除迁移调用;应恢复只纠偏历史默认种子且不覆盖后台手动配置的 helper,并用 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 复现 Jenkins module 编译路径。
|
||||
|
||||
Windows Stdb module 构建流水线运行在 Jenkins `windows` 节点上。该流水线需要执行 PowerShell 逻辑时,统一通过 `bat` 显式调用 `%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe`,不要直接使用 Jenkins `powershell` step;本地 Jenkins durable-task 曾在 `Genarrative-Stdb-Module-Build` workspace 中启动裸 `powershell` 时触发 `CreateProcess error=5, 拒绝访问`。临时 `.ps1` 由 Jenkins `writeFile` 写出后要先转成 UTF-8 with BOM 再交给 Windows PowerShell 5.1 `-File` 解析,避免中文错误消息在无 BOM UTF-8 下被当成本地 ANSI 误解码。Checkout 阶段要优先复用 Jenkins GitSCM 已经完成的结果:`COMMIT_HASH` 为空或与当前 `HEAD` 一致时,不要再额外 `git clean` / `git checkout`,只在确实需要切到别的指定 commit 时才补 fetch、校验和切换。排查时先看对应 build log、`@tmp/durable-*` 下的 `powershellWrapper.ps1`,以及日志中的 `[jenkins-powershell] user/exe`。
|
||||
|
||||
生产环境变量模板:`deploy/env/api-server.env.example`。真实密钥只放服务器,不提交 Git,不写入文档示例。
|
||||
|
||||
`Genarrative-Server-Provision` 会安装基础构建依赖、systemd 模板和 Nginx 站点模板。Ubuntu / apt 目标机会额外安装 `libnginx-mod-http-brotli-filter` 与 `libnginx-mod-http-brotli-static`,随后由 `scripts/jenkins-server-provision.sh` 通过临时 `nginx -t` 配置探测 Brotli 指令是否可用;该临时配置必须先 `include /etc/nginx/modules-enabled/*.conf`,因为 apt 安装的 Brotli 是动态模块,不会出现在普通 `nginx -V` 编译参数里。探测成功才在渲染后的 `deploy/nginx/genarrative.conf` / `genarrative-dev-http.conf` 中启用 Brotli,避免未安装模块的机器直接写入无效配置。
|
||||
`Genarrative-Server-Provision` 会安装基础构建依赖、systemd 模板和 Nginx 站点模板。Ubuntu / apt 目标机会额外安装 `libnginx-mod-http-brotli-filter` 与 `libnginx-mod-http-brotli-static`,随后由 `scripts/jenkins-server-provision.sh` 通过临时 `nginx -t` 配置探测 Brotli 指令是否可用;该临时配置必须先 `include /etc/nginx/modules-enabled/*.conf`,因为 apt 安装的 Brotli 是动态模块,不会出现在普通 `nginx -V` 编译参数里。探测成功才在渲染后的 `deploy/nginx/genarrative.conf` / `genarrative-dev-http.conf` 中启用 Brotli,避免未安装模块的机器直接写入无效配置。Provision 写入 Genarrative Nginx 站点时会把 `/etc/nginx/sites-enabled/default*` 移到 `/etc/nginx/sites-disabled/`,避免 Debian / Certbot 默认站点继续占用 `genarrative.world` / `www.genarrative.world` 并在 `nginx -T` 中出现 `conflicting server name ... ignored`。如果 `nginx -t` 失败,脚本会恢复写入前的 Genarrative 配置和被移动的默认站点。
|
||||
|
||||
50 HTTP req/s 首版压测优化口径:
|
||||
|
||||
@@ -164,7 +224,7 @@ Windows Stdb module 构建流水线运行在 Jenkins `windows` 节点上。该
|
||||
- Windows 下载阶段如果出现 `curl: (18)` 或响应体截断,流水线会保留同名 `.download` 临时文件并用 `curl -C -` 断点续传;只有完整返回但 SHA256 digest 仍不匹配时才删除临时文件后重新下载。目标 Linux 节点仍只接收 `stash/unstash` 带过去的本地下载件,不回退外网下载。
|
||||
- Windows 下载阶段如果走代理,在 `Genarrative-Server-Provision` 参数 `PROVISION_DOWNLOAD_PROXY` 填写 Windows Jenkins 节点可访问的 HTTP 代理,例如 `http://127.0.0.1:7890`;不要填写目标 release 机器视角的 `127.0.0.1`,除非代理确实运行在该 Windows 节点本机。Linux 目标机阶段会强制要求使用本地下载件,缺少文件直接失败,不再回退到外网下载。
|
||||
- `otelcol-contrib.service` 作为可选系统服务加入 provision,默认监听 `127.0.0.1:4317/4318` 并使用 `deploy/otelcol/genarrative-debug.yaml`。api-server 是否发送 OTLP 仍由 `GENARRATIVE_OTEL_ENABLED` 控制,服务 unit 见 `deploy/systemd/otelcol-contrib.service`。
|
||||
- Nginx `/api/` 与 `/admin/api/` 通过 `genarrative_api` upstream 代理到 `127.0.0.1:8082`,upstream keepalive 为 64;`limit_conn` 负责连接 / 并发保护,`limit_req` 负责入口 RPS 快拒绝。当前模板把公开 gallery list 单独放到 `genarrative_gallery_rps`,默认 `rate=5000r/s`、`burst=4096`、`limit_conn=320`;公开详情和普通 API 放到 `genarrative_api_rps`,后台 API 放到 `genarrative_admin_rps`。压测时看 `/var/log/nginx/genarrative.access.log` 中的 `request_time`、`upstream_connect_time`、`upstream_header_time`、`upstream_response_time`、`upstream_status`、`request_id`。
|
||||
- Nginx `/api/` 与 `/admin/api/` 通过 `genarrative_api` upstream 代理到 `127.0.0.1:8082`,upstream keepalive 为 64;`limit_conn` 负责连接 / 并发保护,`limit_req` 负责入口 RPS 快拒绝。当前模板把公开 gallery list 单独放到 `genarrative_gallery_rps`,默认 `rate=5000r/s`、`burst=4096`、`limit_conn=320`;公开详情和普通 API 放到 `genarrative_api_rps`,后台 API 放到 `genarrative_admin_rps`。通用 `/api` location 设置 `client_max_body_size 64m` 是反代兜底,防止拼图入口页 / 新增关卡本地参考图 Data URL 或旧兼容请求在到达 `api-server` 前被默认 1 MiB 上限拦截;拼图本地参考图前后端统一限制 6MB,历史图片仍提交 `referenceImageAssetObjectId(s)`。若线上出现 `413 Request Entity Too Large` 且 access log 中 `request_time=0.000`、`upstream_status=-`,说明请求在 Nginx 层被拦截,先用 `nginx -T | grep client_max_body_size` 检查 release 模板是否已渲染并 reload,同时检查前端是否超出 6MB 或错误提交了未压缩大图。`limit_conn_status 429` 和 `limit_req_status 429` 必须在 HTTP 与 HTTPS server 中同时生效;若线上压测看到 `limiting connections by zone "genarrative_api_conn"` 却返回 503,优先检查 `nginx -T` 里 HTTPS server 是否缺少这些状态码,以及 `/api/runtime/puzzle/gallery` 是否误落到通用 `location ~ ^/api` 的 `limit_conn=64`。压测时看 `/var/log/nginx/genarrative.access.log` 中的 `request_time`、`upstream_connect_time`、`upstream_header_time`、`upstream_response_time`、`upstream_status`、`request_id`。
|
||||
- 作品列表 K6 脚本一次 iteration 默认请求两个公开接口,因此约 50 HTTP req/s 的目标命令使用 `SCENARIO=spike START_RPS=5 PEAK_RPS=25 HOLD=60s END_RPS=5 DETAIL_RATIO=0 npm run loadtest:k6:works`。
|
||||
- 作品列表短期继续由 `api-server` / BFF 订阅 SpacetimeDB 公开 read model 后读本地 cache,不让浏览器前端直接订阅完整列表;未来如新增 `public_work_gallery_entry` 等专用公开作品列表 read model,前端只可订阅稳定、低基数、公开的专用投影,禁止订阅 `puzzle_work_profile`、`custom_world_profile` 等玩法源表后自行 join、聚合或判断权限。前端直订阅落地前必须先补齐权限、字段契约、排序 / 分页、埋点和 BFF 回退策略。
|
||||
- 50 HTTP req/s 验收目标为 `http_req_failed < 1%`、`p95 < 2s`、`dropped_iterations = 0`,同时压测窗口内 Nginx 无新增 502。2026-05-19 容器 2C / 2G 连续 10 轮不重启 SpacetimeDB 压测:`PEAK_RPS=2500` 等价约 5000 HTTP req/s,平均实际吞吐约 `4219 HTTP req/s`,10 轮总计 `1,897,357` 个 200、`212,542` 个 429、`0` 个 5xx,200 请求平均 `p95=123ms`、`p99=234ms`;该档会把 SpacetimeDB 容器内存从约 `366MiB` 推到约 `885MiB / 896MiB`,因此当前不要继续抬公开 gallery 入口并发,应优先处理 SpacetimeDB 侧连接 / 订阅 / tracking 写入后的内存高水位。
|
||||
@@ -193,6 +253,7 @@ OpenTelemetry 现阶段默认开启 OTLP traces / metrics / logs,但本地日
|
||||
- debug exporter / Rider 转发都会同时接收 traces、metrics 和 logs。
|
||||
- api-server 会随 metrics 发送进程级指标:`process.memory.usage`、`process.memory.virtual`、`process.cpu.time`、`genarrative.process.cpu.usage_percent`、`process.thread.count`、`genarrative.process.memory.private`;Windows 额外发送 `process.windows.handle.count`,Linux 额外发送 `process.unix.file_descriptor.count`。这些指标只描述当前进程,不携带请求、用户或作品 label。
|
||||
- HTTP 运行态补充发送 `genarrative.http.server.response_bodies.in_flight` 与 `genarrative.http.server.request_permits.available`,后者带低基数 `pool=default|gallery|detail|admin` label,用于区分业务 handler / 背压 permit 是否仍被占用;拼图广场热点缓存补充发送 `genarrative.puzzle_gallery.cache.*` 指标,记录 fresh hit、stale hit、未命中、后台刷新开始 / 失败、重建耗时和预序列化 data JSON 字节数。
|
||||
- 外部 API 失败统一发送 OTLP 并落库。当前 VectorEngine `gpt-image-2` 图片生成 / 编辑失败由 `platform-image` provider 输出低基数字段结构化日志,字段包括 provider、endpoint、failure_stage、status、status_class、timeout、retryable、latency_ms、prompt_chars、reference_image_count、image_model 和 raw_excerpt;`api-server` 再记录指标 `genarrative.external_api.failures{provider,failure_stage,status_class,retryable}`,并写入 `tracking_event`,`event_key = external_api_call_failure`、`module_key = external-api`、`scope_kind = module`、`scope_id = provider`。排障时先按 provider / failureStage 聚合,再结合 request 日志和上游响应 excerpt 判断是限流、超时、解析失败还是未返回图片。
|
||||
- SpacetimeDB 观测分为两类:procedure / reducer 调用继续用 `genarrative.spacetime.procedure.*`,订阅本地 cache 读使用 `genarrative.spacetime.read.*`。`read=list_puzzle_gallery` 表示拼图广场当前从 `puzzle_gallery_card_view` 本地 cache 读取,不再每个 HTTP 请求调用 `list_puzzle_gallery` procedure。
|
||||
- 本地 Windows 直连压测的内存高水位要结合 K6 VU / 连接数解释。250 RPS 下过高 `PREALLOCATED_VUS` 可能让 300 个本地 Established 连接把 `api-server` private memory 瞬时推到 GB 级,且 `/healthz` 小响应也能复现;若压测结束后回落、`response_bodies.in_flight` 和背压 permit 未显示业务积压,应优先按连接 / 发送链路高水位处理,而不是判断为 SpacetimeDB 或 JSON 缓存泄漏。
|
||||
- Rider 的 Logs 面板只展示 log event 自身字段,不会自动展开父 span 的全部 attributes;请求完成日志会直接带 `request_id`、`http.request.method`、`http.route`、`url.scheme`、`url.path`、`http.response.status_code`、`status_class`、`latency_ms` 和 `slow_request`,完整链路继续到 Traces 面板按 trace/span 查看。
|
||||
@@ -213,6 +274,8 @@ OpenTelemetry 现阶段默认开启 OTLP traces / metrics / logs,但本地日
|
||||
- `WECHAT_*`
|
||||
- `ALIYUN_OSS_*`
|
||||
|
||||
结构化创作 / RPG 的 Responses JSON 链路默认不打开 `web_search`;本地和生产如需联网增强,必须显式配置 `GENARRATIVE_RPG_LLM_WEB_SEARCH_ENABLED=true` 或 `GENARRATIVE_CREATION_AGENT_LLM_WEB_SEARCH_ENABLED=true`。如果上游未开通工具,Responses 可能先吐自然语言再返回 `ToolNotOpen`,这类报错应按工具不可用排查,不要先当成 JSON 解析 bug。
|
||||
|
||||
### 手机验证码短信
|
||||
|
||||
手机验证码发送走阿里云普通短信 `SendSms`,验证码由 `module-auth` 在当前 `api-server` 进程内生成、哈希存储和校验,不再调用阿里云托管验证码的 `SendSmsVerifyCode` / `CheckSmsVerifyCode`。因此 `api-server` 重启后,已发送但未校验的验证码会失效。
|
||||
@@ -248,6 +311,16 @@ cargo test -p platform-auth --manifest-path server-rs/Cargo.toml aliyun_send_sms
|
||||
|
||||
个人任务首版 scope 仅支持 `user`。后台、RPG、大鱼吃小鱼、Visual Novel、Story、Combat 等特定链路按 tracking 中间件排除规则处理;作品游玩统一使用 `work_play_start`。
|
||||
|
||||
外部 API 失败审计复用 `tracking_event`,不新增表。失败事件优先写入本机 tracking outbox,再由后台 worker 批量落库;如果 outbox 因权限、磁盘或保护阈值不可写,会回退同步直写 SpacetimeDB。`metadata_json` 包含 endpoint、operation、failureStage、statusCode、statusClass、timeout、retryable、errorMessage、latencyMs、promptChars、referenceImageCount、imageModel 和 rawExcerpt。常用查询:
|
||||
|
||||
```sql
|
||||
SELECT event_id, scope_id AS provider, metadata_json, occurred_at
|
||||
FROM tracking_event
|
||||
WHERE event_key = 'external_api_call_failure'
|
||||
ORDER BY occurred_at DESC
|
||||
LIMIT 50;
|
||||
```
|
||||
|
||||
tracking outbox 默认配置:
|
||||
|
||||
```env
|
||||
@@ -260,6 +333,17 @@ GENARRATIVE_TRACKING_OUTBOX_MAX_BYTES=268435456
|
||||
|
||||
outbox 采用 NDJSON 文件保存原始事件。达到 `BATCH_SIZE` 时会立刻把当前 active 文件原子封存为 sealed 文件,并马上切到新的 active 继续写入;后台 worker 异步 flush sealed 文件,HTTP 请求线程不等待 SpacetimeDB。`FLUSH_INTERVAL_MS` 只负责兜底封存长时间未满批的 active 文件。SpacetimeDB 批量 procedure 返回成功后删除 sealed 文件,失败则保留文件并重试。`MAX_BYTES` 是磁盘保护阈值,不是 flush 阈值;超过后低价值 route tracking 可以被丢弃并记录日志 / 指标,关键同步事件不进入该丢弃路径。sealed 文件若出现无法解析的坏行,会重命名为 `corrupt-*` 隔离并记录 `genarrative.tracking_outbox.files.corrupt` 指标,避免一个坏文件阻塞后续批量入库。该机制提供至少一次投递语义,依赖 `tracking_event.event_id` 幂等跳过重复事件。
|
||||
|
||||
release 机器如果日志每秒刷 `tracking outbox ... Permission denied (os error 13)`,先检查 `/etc/genarrative/api-server.env` 是否缺少 `GENARRATIVE_TRACKING_OUTBOX_DIR`。缺少时 `api-server` 会回退到本地开发默认相对路径 `server-rs/.data/tracking-outbox`,而 systemd 的工作目录是只读发布目录 `/opt/genarrative/releases/<version>`,`genarrative` 用户无法在其中创建 `server-rs`。修复顺序:
|
||||
|
||||
```bash
|
||||
install -d -o genarrative -g genarrative -m 0750 /var/lib/genarrative/tracking-outbox
|
||||
grep -n '^GENARRATIVE_TRACKING_OUTBOX' /etc/genarrative/api-server.env
|
||||
systemctl restart genarrative-api.service
|
||||
journalctl -u genarrative-api.service --since '30 seconds ago' --no-pager | grep -E 'tracking outbox|Permission denied|os error 13'
|
||||
```
|
||||
|
||||
`Genarrative-Server-Provision` 和 `Genarrative-Api-Deploy` 会在保留旧 `/etc/genarrative/api-server.env` 的前提下补齐缺失的 tracking outbox 与 auth-store 运行态路径,并确保 `/var/lib/genarrative/tracking-outbox`、`/var/lib/genarrative/auth` 归属 `genarrative:genarrative`。
|
||||
|
||||
常用检查思路:
|
||||
|
||||
```sql
|
||||
|
||||
@@ -6,19 +6,79 @@
|
||||
|
||||
创作入口配置事实源在 SpacetimeDB,通过 `GET /api/creation-entry/config` 下发;后台通过 `/admin/api/creation-entry/config` 管理。前端只在展示层派生可见卡片和入口状态,`api-server` 路由熔断也使用同一份配置。不要恢复前端硬编码入口配置文件。
|
||||
|
||||
当前创作 Tab 固定为智能创作首页与模板入口,草稿 Tab 承接作品架。点击独立入口后应切换到对应内嵌创作表单或生成页;不要额外做一张平行配置页,除非玩法本身需要完整独立工作台。
|
||||
当前创作 Tab 只承载赛事 banner、玩法模板分类和两列模板卡;点击模板卡后直接进入对应玩法已有的入口创作表单 stage,不再经过空白占位页,也不把旧表单嵌进创作 Tab 首屏。移动端创作 Tab 顶栏在 `陶泥儿` 品牌同一行显示真实账户泥点数,数据来自 `profileDashboard.walletBalance`,不得再把活动奖池当作账号余额展示。首屏 banner 结构按参考图拆成横向可滑动赛事卡、主体宣传图文区、奖池胶囊、开始 / 结束时间条和卡片内分页点;轮播只保留 `拼图主题创作赛` 和 `抓大鹅主题创作赛`,两个主题赛事奖池均为 `1000` 泥点数。玩法列表不再套外部边框卡片,移动端需要压缩横向边距和两列间距;玩法卡统一按“上图、左上状态标签(仅非开放态显示)、封面右下 `10-20泥点数`、下方白底标题/描述”结构展示,卡片高度保持紧凑但标题、描述和预估消耗点数都必须可见。创作 Tab 根容器不再使用 `platform-page-stage` 这类全局内容卡片壳,但继续保留 `platform-remap-surface` 作为主题和输入框样式命中钩子。创作首屏字号需要对齐平台普通 UI 档位:顶栏泥点组件、banner 正文、分类 Tab 和玩法卡标题 / 副标题 / 消耗说明优先使用 `11px` 到 `14px`,不使用 `text-lg`、`text-xl` 或更大的展示级字号。草稿 Tab 继续承接作品架。RPG、RPG 之外的各玩法入口分别落到既有的 `agent-workspace`、`big-fish-agent-workspace`、`match3d-agent-workspace`、`square-hole-agent-workspace`、`jump-hop-workspace`、`wooden-fish-workspace`、`puzzle-agent-workspace`、`bark-battle-workspace`、`visual-novel-agent-workspace`、`baby-object-match-workspace`,这些入口继续承接各玩法自己的表单、草稿恢复和后续编排,不作为创作 Tab 首屏内容。
|
||||
|
||||
创作恢复参数只保留 `sessionId`、`profileId`、`draftId`、`workId` 这四个私有 query。它们只允许在同一条创作链路的结果页、生成页、工作台之间保留;切到首页、公开作品详情、runtime 或另一条玩法链路时必须清掉。生成页恢复时只认当前进入页的时间作为新的 `startedAtMs`,作品摘要里的 `updatedAt` 只用于排序与摘要展示,不再作为生成进度起点。
|
||||
|
||||
创作表单提交前的泥点余额前置校验只允许用独立弹窗提示失败原因,不得把用户退回创作入口或玩法模板列表,也不得清空当前表单状态。当前适用拼图、抓大鹅和汪汪声浪等会在前端提交前校验泥点的生成入口;余额不足、余额读取失败都应停留在当前工作台,由用户关闭提示后继续编辑或自行补足泥点。
|
||||
|
||||
平台入口、生成页、结果页、作品详情、作品架和运行态的跨流程错误统一收口到 `PlatformErrorDialog`。弹窗必须带明确错误来源,例如某个草稿、某次生成、作品详情或某个游玩实例,并提供复制按钮复制“错误来源 + 错误内容”。页面内不再重复渲染裸错误 banner;表单校验、发布确认弹窗里的局部业务错误可以保留在原弹窗内。
|
||||
|
||||
生成任务在用户离开生成页后异步完成时,平台壳层必须弹出 `PlatformTaskCompletionDialog`。完成弹窗同样要带来源,例如某个草稿或生成会话,并提供复制按钮复制“来源 + 状态”;如果用户仍停留在生成页并被自动带入结果页或试玩页,生成页 / 结果页本身即为完成反馈,不再额外叠加完成弹窗。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 仍是平台入口编排壳,后续维护时应优先把独立 UI 片段、公开作品映射、草稿生成 notice 和运行态状态 helper 拆到 `src/components/platform-entry/PlatformEntryFlowShellImpl/` 或同目录紧邻 helper 文件。拆分只允许改变文件组织,不改变入口配置事实源、默认导出、props、页面阶段、UI 文案或现有交互;其中拼图首访 onboarding 已拆为 `PlatformEntryFlowShellImpl/PuzzleOnboardingView.tsx`。
|
||||
|
||||
`platformEntryCreationTypes.ts` 只做前端展示派生,分组时必须把后端 `creationTypes` 里的 `categoryId` / `categoryLabel` 当作可缺失字段处理,空值统一回退到 `recent` / `最近创作`,避免旧数据、局部 mock 或异常返回把创作入口初始化直接打崩。
|
||||
|
||||
移动端底部一级导航是全局平台样式,不按单一玩法分叉。当前视觉统一为米白浮动胶囊底座、浅棕分隔线、棕色线性图标、橘色选中态和底部短下划线;中间 `创作` 入口保持凸起圆形主按钮,但凸起位移只能作用在按钮内容层,不能移动承载分隔线的 Tab 按钮容器,确保创作左右分隔线与其他分隔线垂直位置一致。Tab 名称和可见性仍由现有 `PlatformHomeTab` / 登录态规则决定,样式调整不得改写 Tab 文案或导航状态。
|
||||
|
||||
## 新增玩法创作工具平台 SOP
|
||||
|
||||
新增玩法默认采用表单/图片输入创作工作台,链路为:
|
||||
|
||||
```text
|
||||
创作入口 -> 工作台 -> 生成页 -> 结果页 -> 试玩 -> 发布 -> 运行态
|
||||
```
|
||||
|
||||
默认工作台只提交结构化表单、图片槽位和配置 payload,不默认增加聊天输入区、流式消息区或轻输入 Agent。确需偏离该模式时,必须先在 PRD 和本文档写明例外原因、影响范围和回退方式,再进入编码。
|
||||
|
||||
单图资产编辑统一通过 `CreativeImageInputPanel` 承载上传、AI 重绘、参考图、历史图和删除确认;新玩法页面不得重复手写这些交互。系列素材图集生成统一走“批量规划 -> sheet 生图 -> 后端切图 -> 透明化 -> OSS 持久化 -> 状态回写 -> 局部重生成”流程,玩法只提供 `sheetSpec`、`slotSpecs`、提示词和字段映射,不把任一玩法专属素材 DTO 当作平台通用模型。
|
||||
|
||||
通用系列素材图集能力的实现真相源在 `platform-image::generated_asset_sheets`:`n` 是必选参数,模块负责组装 `n*n` sheet prompt、按 `n*n` 切片、绿幕 / 近白底透明化、导出 PNG 和 OSS 持久化请求。`api-server::generated_asset_sheets` 只保留 `AppError` / `AppState` 适配,不再承载图像处理和 OSS 请求构造细节。物品名称 prompt 和特殊设定 prompt 是可选输入;调用方可传入类似“每个物品生成五个不同视图”的视角约束,通用模块会把 sheet prompt、物品行 prompt、特殊设定 prompt 编码写入 OSS 元数据。玩法仍负责计费、物品规划、slot 映射、失败回写和把通用切片结果映射回自己的草稿 / profile / runtime 字段。
|
||||
|
||||
当前所有玩法生成页 UI 统一收敛为圆环主视觉:`media/create_bg_video.mp4` 作为生成页固定全屏背景层循环静音播放,主进度圆环居中覆盖在背景之上,围绕陶泥儿视觉展示;页面只保留当前步骤名称和当前步骤进度,不再渲染步骤列表块。视频层需要显式触发播放,不能只依赖 `autoPlay/loop/muted` 属性。共用生成页 `CustomWorldGenerationView` 和汪汪声浪生成页都必须遵循这一口径。
|
||||
|
||||
## 草稿与作品架
|
||||
|
||||
1. 草稿页作品卡对齐发现页列表卡风格:左侧信息,右侧封面图,移动端单列,桌面两到三列。
|
||||
2. 草稿 / 已发布状态尽量图标化,不使用大段状态文案。
|
||||
3. 草稿卡常态不外露低频动作;已发布作品卡右上角可直接显示无边框分享 icon,删除等破坏性动作继续收口到左滑或长按操作层。
|
||||
2. 草稿页顶部 `全部 / 草稿 / 已发布` 筛选与发现页 `推荐 / 今日 / 分类 / 排行` 频道标签复用同一选中 / 未选中视觉,即 `platform-mobile-home-channel` 与 `platform-mobile-home-channel--active`,不再使用旧 `platform-tab` 胶囊样式。
|
||||
3. 草稿页与底部导航的未读提示点统一使用平台暖棕色点和暖棕光晕,不再使用红点或红色 glow;草稿 Tab 作品架卡片无论草稿 / 已发布都不外露作者信息;已发布作品卡右上角直接显示无边框分享 icon。删除等破坏性动作继续收口到左滑或长按操作层。
|
||||
4. 生成中作品在整卡上加等待遮罩,但不移除作品基础信息。
|
||||
5. 生成中状态不能只存在前端内存 notice。后端作品摘要必须下发可恢复的 `generationStatus`;前端刷新或退出产品后,作品架优先用摘要状态恢复等待遮罩,本轮内存 notice 只作为即时反馈。
|
||||
6. 点击 `generationStatus=generating` 的草稿卡必须恢复对应玩法的生成进度页,不能进入空白结果页或普通工作区;恢复生成页的 `startedAtMs` 使用作品摘要 `updatedAt` 推导。
|
||||
7. 私有 generated 图片必须通过 `ResolvedAssetImage` / `/api/assets/read-url` 换签读取。
|
||||
6. 点击 `generationStatus=generating` 的草稿卡必须恢复对应玩法的生成进度页,不能进入空白结果页或普通工作区;恢复生成页的 `startedAtMs` 使用进入生成页的当前时间,作品摘要 `updatedAt` 只用于排序和摘要展示,不参与假进度起算。
|
||||
7. 从草稿 Tab 作品架打开草稿工作区、生成页或结果页时,返回按钮必须回到草稿 Tab 的同一作品架语境;从创作 Tab 新建或直接进入创作链路时才回到创作 Tab。平台壳层需要显式记录本次创作流的返回来源,不能让结果页返回动作固定跳到创作入口。
|
||||
8. 私有 generated 图片必须通过 `ResolvedAssetImage` / `/api/assets/read-url` 换签读取。
|
||||
9. 敲木鱼作品架读取当前用户作品列表时走 `GET /api/creation/wooden-fish/works`;发布成功后平台壳必须同时刷新作品架与公开广场,避免作品刚发布时仍停留在旧列表。
|
||||
|
||||
发现 Tab、创作 Tab 与草稿 Tab 的页面根内容区不再套 `platform-page-stage` 外层全局卡片壳,让列表、筛选和玩法卡获得更宽的横向空间;推荐页和我的页仍按各自页面设计保留原有全局卡片口径。移动端“我的”页仍按顶部头像 / 昵称 / 陶泥号、会员横幅、三张统计卡、每日任务、五项常用功能宫格、设置入口和法律信息组织,不保留旧的底部“填邀请码”次级入口;每日任务卡必须读取 `/api/profile/tasks` 的当前任务摘要并在领取后同步刷新卡片进度。字号必须维持平台普通 UI 档位,不能因为窄屏把卡片标题、功能 label 或法律信息撑成展示级字号;最后一屏内容必须能在底部 dock 上方完整滚动露出,不得被固定底部导航遮挡。
|
||||
|
||||
## RPG / 自定义世界
|
||||
|
||||
当前 RPG 创作入口使用 `playId = rpg`,工程域和运行态源类型沿用历史 `custom-world`。默认入口状态为 `visible=true`、`open=true`,对外展示为“文字冒险”;`airp` 仍是独立的“AI RPG”占位入口,保持 `open=false`,不要把它当作当前 RPG 创作链路开放。
|
||||
|
||||
当前链路为:
|
||||
|
||||
```text
|
||||
创作入口 -> RPG Agent 共创工作台 -> 生成过程页 -> 结果页 -> 进入世界/试玩 -> 发布 -> RPG 运行态
|
||||
```
|
||||
|
||||
RPG 是历史既有链路例外:当前仍使用对话式 Agent 共创工作台和 RPG 资产编辑器体系,不作为新增玩法默认模板复制。新增玩法继续遵循本文默认的表单/图片输入工作台、`CreativeImageInputPanel` 单图槽位和通用系列素材图集生成流程;如果要把 RPG 逐步迁回默认模式,应先补 PRD 和迁移方案,再改代码。
|
||||
|
||||
RPG API 仍沿用历史命名空间:`/api/runtime/custom-world*`、`/api/story/*`、`/api/runtime/chat/*`。这些路由在 `api-server` 入口熔断中统一映射到 `rpg`,只按 `open` 判断是否允许调用;`visible` 只控制创作页入口展示和作品架可见性。
|
||||
|
||||
RPG Agent 结果页点击发布或发布并进入世界时,必须先把结果页当前 profile 通过 `sync_result_profile` 保存回 `custom_world_agent_session.draft_profile_json`,再发送发布动作;发布动作前端契约只允许提交 `{ action: 'publish_world' }`,`api-server` 只补作者公开信息,不转发 `profile`、`draftProfile`、`legacyResultProfile` 或 `settingText`。`spacetime-module` 发布时只读取当前 session 的 `draft_profile_json` 作为草稿真相,从 `settingText`、`creatorIntent.rawSettingText`、`creatorIntent.worldHook`、`worldHook`、`anchorContent.worldPromise(.hook)`、`summary`、`name/title` 依次派生正式 `setting_text`,最后才回退 `seed_text`。不要把 `seed_text` 当作唯一设定来源,旧会话可能为空。
|
||||
|
||||
Agent session 已进入 `published` 后,结果页按钮只能执行“进入世界”:前端需先通过 `result-view` 回读已发布 profile 并启动运行态,不得再次调用 `sync_result_profile` 或发送 `{ action: 'publish_world' }`。`publish_world` 只允许在 `object_refining`、`visual_refining`、`long_tail_review`、`ready_to_publish` 等发布前阶段触发;否则会被后端阶段门槛拒绝。
|
||||
|
||||
`legacyResultProfile` 只作为历史结果页 profile 兼容兜底;编译正式 profile 时,session 草稿内已保存字段优先于 legacy 字段,legacy 只能补缺失字段。`publish_world` 不再接受前端临时传入的 legacy 载荷;历史兼容路径中 legacy 缺省或显式为 `null` 时等价于未提供,不得因此报 `custom_world.compile.legacy_result_profile_json 不是合法 JSON object`。真正的数组、字符串、数字等非 object legacy 载荷仍应拒绝。
|
||||
|
||||
RPG 结果页开局 CG 是 `profile.openingCg` 资产槽位:`api-server` 负责 VectorEngine / OSS 副作用并返回故事板和视频引用,前端只把结果写回当前 profile;`sync_result_profile`、作品库保存和 `normalizeCustomWorldProfileRecord` 都必须保留该槽位。封面是 `profile.cover` 资产槽位,默认封面也要保留 `sourceType='default'` 和 `characterRoleIds`,不能因为没有 `imageSrc` 就当作空封面。若生成成功后画面短暂显示又变回空白,优先检查父层重新同步或 profile 归一化是否把 `openingCg` / `cover` 丢掉,而不是先怀疑已生成资源本身失效。
|
||||
|
||||
RPG 从作品架、广场详情或作品号搜索点击“启动”前,入口 client 必须把后端返回的完整 `profile` 先经过 `normalizeCustomWorldProfileRecord`,并用作品条目的 `profileId/worldName/subtitle/summaryText` 补齐旧数据缺失字段;运行态和详情页不得直接消费未归一化的旧 profile。作品架列表或 `savedCustomWorldEntries` 中的摘要 profile 只可用于卡片展示,不可在详情接口已回读完整 profile 后覆盖 `selectedDetailEntry`;若摘要缺少 `playableNpcs`、`storyNpcs`、`landmarks`、`items`、`sceneChapterBlueprints`、`cover`、`openingCg`、`skills[].actionPreviewConfig`、`initialItems[].iconSrc`、`attributeSchema`、角色 `attributeProfile`、场景残留或场景幕背景资产,启动和编辑必须继续使用详情 profile,否则会进入默认角色 / 默认 profile,或在编辑页丢 CG、封面、技能预览和初始物品图标。正式“进入世界”发布 / 回读结果页时,同一 `profile.id` 下也不得用字段更少的后端旧视图降级当前结果页完整 profile。角色选择页还需要在角色数组异常或为空时回退默认角色,并显示可返回的轻量空态,不能 `return null` 造成黑屏。运行态懒加载 fallback 必须可见,不能用纯 `null` 让用户误判为黑屏。
|
||||
|
||||
RPG 运行态的战斗终局、继续冒险、继续探索和切场景都属于服务端 runtime 快照真相:`module-runtime-story` 必须在终局战斗 action 后调用 post-battle finalization,持久写入 `story_continue_adventure`、`deferredOptions`、`deferredRuntimeState.storyEngineMemory.currentSceneActState` 和清理后的战斗状态;`idle_travel_next_scene` / `camp_travel_home_scene` 必须由后端写入新的 `currentScenePreset`、`currentSceneActState`、`currentEncounter` 和 `runtimeStats.scenesTraveled`。前端只播放退场、进场和继续按钮表现,不能用默认 `观察/试探/调息` fallback 或本地动画假装推进剧情。旧 bootstrap 快照可能只有 `connectedSceneIds` / `forwardSceneId` 而没有 `connections`,后端生成战后旅行选项时必须兼容这些字段。
|
||||
|
||||
RPG / 拼图等运行态存档仍以 `/api/profile/save-archives` 的后端列表为真相,恢复动作继续走对应恢复接口,但移动端“我的”页已经不再提供独立的 `次级入口 > 存档` 和设置入口存档按钮;“玩过”弹窗可以继续合并展示可继续存档,个人中心只保留设置、扫码和五项常用功能。移动端“我的”页的五项常用功能宫格只放泥点充值、邀请好友、兑换码、玩家社区、反馈与建议,避免把存档或填邀请码挤入主宫格破坏参考图布局。前端只展示 `/api/profile/save-archives` 返回的列表并在用户选择后调用对应恢复接口,不能本地拼装或筛选正式存档真相。
|
||||
|
||||
## 拼图
|
||||
|
||||
@@ -31,25 +91,86 @@
|
||||
当前口径:
|
||||
|
||||
- 图像输入复用 `CreativeImageInputPanel`。
|
||||
- 支持画面描述生图、多参考图生图、上传或历史生成主图后 AI 重绘、上传或历史生成主图后不重绘;关闭 AI 重绘时,前端可提交本地上传 Data URL 或历史 `/generated-*` 图片路径,后端统一解析为首关正式图后再持久化。
|
||||
- 草稿生成会先持久化 `generationStatus=generating` 的作品摘要,生成完成并回写关卡图、UI 背景后再变为 `ready`;首关关卡图和 UI 背景在命名稳定后并行启动,当前不自动生成背景音乐。
|
||||
- 结果页每关画面编辑复用 `CreativeImageInputPanel`;入口页和关卡画面只共享受控 UI 模块,不共享数据源、状态、action 或存储位置:入口页继续写 `formDraft` 与草稿编译 payload,关卡画面写 `levels[].pictureReference/pictureDescription` 并触发 `generate_puzzle_images`。结果页删除独立“素材配置”Tab,不再提供单独 UI 背景生成入口。通用图片面板的展示图和 AI 重绘参考图能力必须分开控制:结果页正式关卡图只作为预览图,不因存在正式图自动暴露 AI 重绘开关;只有本地上传、历史选择或已保存 `pictureReference` 可作为重绘参考图时,才显示 AI 重绘开关并把状态带入 `generate_puzzle_images`。用户在本次编辑中上传或选择历史图后,该图优先占据主图卡片,可删除、切换 AI 重绘,也可关闭 AI 重绘直用;仅有正式图预览时,画面描述框仍可上传多张参考图。关卡详情弹窗应使用加宽面板,关卡名称、画面图和画面描述合并在同一个纵向列表中,名称输入和画面编辑模块外层不再包独立 `platform-subpanel`;画面图卡仍必须保留稳定最小高度,避免弹窗内 `flex-1` 布局坍缩后只剩标题、描述输入和操作按钮。
|
||||
- 支持画面描述生图、多参考图生图、上传或历史生成主图后 AI 重绘、上传或历史生成主图后不重绘;主链要求浏览器先经 `/api/assets/direct-upload-tickets` 直传 OSS 并确认 `asset_object`,创作 action 只提交 `referenceImageAssetObjectId(s)`,由后端校验 owner / bucket / kind / MIME / size 后签发 OSS 只读 URL 并下载为 VectorEngine `/v1/images/edits` 的 multipart `image` part。本地上传 Data URL 与历史 `/generated-*` 图片路径仅保留为旧草稿、旧入口或未迁移客户端的兼容输入;关闭 AI 重绘时,后端统一解析为首关或当前关卡正式图后再持久化,不调用第一段拼图首图生成。
|
||||
- 草稿生成会先持久化 `generationStatus=generating` 的作品摘要,生成完成并回写关卡拼图画面、关卡画面参考图、UI spritesheet 和关卡背景图后再变为 `ready`;当前不自动生成背景音乐。生成页步骤推进必须跟随后端 session `progressPercent` 的真实里程碑:`88` 表示草稿编译完成并进入出图步骤,`94` 表示生成图已保存并进入 UI / 背景步骤,`96` 表示正式图与 UI 背景已确认并进入写入步骤,最终 action 成功或发布才进入完成态;每个步骤内部可以按实际等待时间使用假进度平滑推进,总进度按 `0-88`、`88-94`、`94-96`、`96-98` 的真实里程碑区间平滑推进。任一同步 action 回包到达时立即以真实完成/失败结果冻结进度。
|
||||
- 作品架拼图草稿的“生成中”遮罩只表示初始草稿还没有可查看结果;只要作品摘要、首关封面或任一关卡候选图已经可用,后续 UI 背景重生成和追加关卡生图都必须作为结果页局部生成态处理,不能阻止打开草稿结果页。
|
||||
- 拼图草稿编译是长耗时 action,前端 action 请求默认等待 `1_000_000ms` 且不自动重试,生成页预计完成时间按 `5` 分钟展示;生成页恢复时必须沿用作品摘要 `updatedAt` 作为原始 `startedAtMs`,失败/完成态用 `finishedAtMs` 冻结耗时,不能在锁屏或返回草稿页后重新从 0 计时。
|
||||
- 拼图草稿编译是长耗时 action,前端 action 请求默认等待 `1_800_000ms`(30 分钟)且不自动重试。每次图片生成调用的预期用时按 90 秒计算,但 `生成拼图首图` 单独按 4 分钟展示;完整 AI 重绘路径为 `编译首关草稿` 8 秒、`生成关卡名称` 10 秒、`生成拼图首图` 4 分钟、`生成关卡画面` 90 秒、`生成UI与背景` 90 秒、`写入正式草稿` 10 秒,合计约 448 秒。上传图且关闭 AI 重绘时必须跳过 `生成拼图首图`,直接进入 `生成关卡画面` 和 `生成UI与背景`,合计约 208 秒。生成页恢复时必须使用进入生成页的当前时间作为原始 `startedAtMs`;失败/完成态用 `finishedAtMs` 冻结耗时。未收到对应后端里程碑前,后续步骤保持待处理;即使当前步骤预计时长耗尽,也只能让当前步骤内部进度停在 `98%` 内,不能自动完成当前步骤或跳到后续步骤。生成页每个步骤只展示标题和进度,不展示步骤详细描述。
|
||||
- 前端创作、结果页、生成页和错误提示不展示 GPT / Gemini 等具体模型名称;如需在内部保留模型路由,UI 只使用“标准模式”“创意模式”等产品化名称。
|
||||
- 若浏览器锁屏、息屏或网络切换导致 compile 请求失败,前端在标记失败前必须先复读 `getPuzzleAgentSession(sessionId)`;只有最新 session 仍缺 `draft.coverImageSrc`、首关 `coverImageSrc` 或候选图时才展示失败,复读到已生成草稿时按成功收尾、刷新作品架并继续自动试玩/结果页链路。
|
||||
- 拼图参考图 AI 重绘优先走 VectorEngine `/v1/images/edits`;若编辑接口超时,`api-server` 会降级为 `/v1/images/generations`,并把同一参考图塞进 `image` 数组继续生成,避免参考图草稿整单失败。
|
||||
- 结果页素材配置当前只保留 UI 相关能力;旧背景音乐入口隐藏。
|
||||
- 拼图参考图 AI 重绘走 VectorEngine `/v1/images/edits`;无参考图时走 `/v1/images/generations`。两者模型都使用 `gpt-image-2`,参考图由后端作为 multipart `image` part 传入编辑接口。
|
||||
- 每次新建关卡生成或重新生成关卡图都必须由 `api-server` 串起当前关卡资产包:AI 重绘开启时第一段沿用草稿生成第一关的拼图主图提示词配置和模型 / 尺寸 / 参考图规则生成 `coverImageSrc/coverAssetId` 作为关卡拼图画面和结果页预览图,提示词来源同样按显式画面描述、关卡画面描述、草稿摘要顺序回退,且固定要求输出画面比例为 `1:1`;上传图且关闭 AI 重绘时跳过这一段,把上传图或历史图持久化为 `sourceType=uploaded` 的正式候选。随后用正式候选图作为参考,`9:16` 生成完整拼图游戏关卡画面并写入 `levelSceneImageSrc/levelSceneImageObjectKey`,提示词必须要求道具按钮上不要显示次数标注,且返回按钮和设置按钮旁禁止标注文字;UI spritesheet 与关卡纯背景在关卡画面完成后并发生成,spritesheet 用 `1:1`、`1k` 先生成纯绿色绿幕背景图,后端上传 OSS 前必须把绿幕扣成透明 PNG,再写入 `uiSpritesheetImageSrc/uiSpritesheetImageObjectKey`,按钮顺序固定为返回、设置、下一关、提示、原图、冻结,按钮素材自身保留对应中文文字,返回和设置按钮不得额外生成白色外圈、白底圆环或浮雕外框;纯背景用 `9:16`、`1k` 写入 `levelBackgroundImageSrc/levelBackgroundImageObjectKey`,提示词必须包含“禁止在背景中出现人像或和拼图画面中主体一致的内容”。运行态不直接使用第二段完整关卡画面,但必须持久化它用于追踪和后续再生成。结果页局部关卡生成进度按 AI 重绘开启约 270 秒、关闭 AI 重绘约 180 秒展示。
|
||||
- 结果页允许多关卡并行编辑和生成;某一关卡图片生成完成回包只静默更新该关卡素材与生成态,不得自动打开或切换关卡详情面板,避免打断用户正在编辑的其它关卡。
|
||||
- 结果页 UI 背景重生成只禁用 UI 背景自己的按钮和确认动作,不禁用“新增关卡”、关卡图片生成、关卡详情编辑和结果页导航;关卡图片生成也只标记对应关卡的局部生成进度。
|
||||
- 结果页关卡图片生成只标记对应关卡的局部生成进度,不禁用“新增关卡”、其它关卡详情编辑和结果页导航。
|
||||
- 结果页单关测试只能把完整草稿持久化,并通过 `levelId` 指定运行态起始关卡;不得把单关快照作为整份草稿调用 `updatePuzzleWork`,否则 source session 和作品 profile 的 `levels` 会被覆盖成单关,退出重进后其它关卡会丢失。
|
||||
- 拼图试玩和正式运行态刷新恢复不复用创作私有 query。进入 `/runtime/puzzle` 时必须写入 `runtimeProfileId`、草稿 `runtimeSessionId`、可选 `runtimeLevelId`、公开作品 `work` 和 `mode=draft|published`;进入运行态的导航顺序必须先切到 `/runtime/puzzle`,再写这些 runtime query,避免被阶段导航清掉后刷新停在“正在进入拼图关卡”。
|
||||
- 结果页生成关卡图时若关卡名为空,前端必须传 `shouldAutoNameLevel=true`,后端复用首关命名契约先按画面描述生成关卡名,再在图片生成后用视觉命名结果精修,并把生成名和 UI 背景提示词随本次关卡快照写回。
|
||||
- 拼图 UI 背景是作品运行态背景,不只属于第一关;本地试玩、直达指定关卡和正式 `next-level` 推进时,目标关卡缺 `uiBackgroundImageSrc/uiBackgroundImageObjectKey` 必须继承同作品首个可用 UI 背景,仍缺失时才沿用当前运行态快照背景或默认 UI。
|
||||
- 拼图运行态背景优先读取当前关卡 `levelBackgroundImageSrc/levelBackgroundImageObjectKey`,旧数据才兼容 `uiBackgroundImageSrc/uiBackgroundImageObjectKey`;本地试玩、直达指定关卡和正式 `next-level` 推进时,目标关卡缺关卡背景时必须继承同作品首个可用关卡背景,仍缺失时才沿用当前运行态快照背景或默认 UI。运行态按钮视觉优先读取当前关卡 `uiSpritesheetImageSrc/uiSpritesheetImageObjectKey`,先按透明 alpha 自动边界检测识别 spritesheet 中的独立按钮展示矩形,再按原图位置从左到右、从上到下映射到返回、设置、下一关、提示、原图、冻结;同一组件还要按较高 alpha 阈值派生紧致点击热区,透明留白和柔边低 alpha 区域尽量不响应点击。检测失败时回退旧固定六格裁切,缺失时才用现有图标按钮兜底。有 spritesheet 时,返回和设置按钮的点击容器只提供透明点击区,不再叠加默认白色圆形底;底部提示、原图、冻结三枚素材按检测矩形的原始宽高比显示,不能强行拉伸成正圆或铺满整列。底部道具区不再使用连片胶囊背景,提示、原图、冻结三个按钮均匀分布;运行态只展示按钮素材本身,不额外叠加“提示 / 原图 / 冻结”文字。
|
||||
- 推荐页本身不是登录门禁入口,未登录用户点击底部或侧边栏的推荐 Tab 应直接进入嵌入运行态,不主动打开登录弹窗。推荐页嵌入运行态必须按真实身份分流:已登录用户或本地已有 access token 时,启动拼图和后续排行榜 / 下一关等正式请求继续走账号 Bearer;只有确认为匿名访客时才申请并透传 runtime guest token。`/api/runtime/puzzle/runs*` 后端统一接受 `RuntimePrincipal`,可识别账号用户和匿名 runtime guest;推荐卡片的后台读写请求仍使用 local auth impact,避免单卡 401 清空整站登录态。创作、个人作品、删除、发布、Remix 等账号或所有权动作仍保持普通用户鉴权。
|
||||
- 拼图运行态棋盘不叠加分块蒙版、描边、阴影、选中底色或合并块 SVG 轮廓;拼图片本体需要裁切为圆角形状,单块使用独立圆角裁切,合并块使用 SVG 原生 `clipPath` 裁切整体外轮廓,外凸角和内凹角分别计算半径,内凹角半径要比外凸角更明显以避免手机 WebView 中看起来仍是直角。原图道具只在用户主动确认后打开独立原图查看层,不在当前拼图棋盘上叠加原图。
|
||||
- 拼图运行态拖拽必须完全跟随手指或鼠标位置,`pointermove` 期间即时写入可见拼块的 transform,不依赖等待后端回包、React 重渲染或下一帧动画队列;进入拖动后不展示拼块选中态或“已选择”提示,松手后再提交目标格同步规则真相。
|
||||
- 拼图运行态的提示、设置等点击弹层跟随当前运行态主色主题,使用普通圆角主题面板,不复用像素九宫格素材框。
|
||||
- 拼图运行态壳层自身要补齐 `platform-ui-shell` / `platform-theme` / `platform-theme--light|dark`,不能依赖外层平台壳来提供主题变量;`/puzzle` 直达页和平台内嵌页都必须渲染同一套主题语义类。
|
||||
- 拼图运行态顶部关卡信息采用游戏化铭牌样式:橘棕横向关卡名牌承载 `第 N 关` 和关卡名,左侧固定使用 `media/logo.png` 卡通形象;倒计时作为下挂米白小牌独立显示,紧贴铭牌但不遮挡棋盘。该样式只改变运行态 HUD 视觉,不改变计时、暂停、失败同步或关卡推进规则。
|
||||
- 拼图运行态进行中关卡的 `elapsedMs` 仍是结算字段,设置面板的“当前用时”必须按 `startedAtMs`、暂停累计和冻结累计实时派生;不要直接把进行中的 `currentLevel.elapsedMs` 当作展示值。
|
||||
- 推荐页嵌入拼图运行态时,通关结算弹层必须挂到页面级 fixed 浮层,不能留在推荐卡片视觉区内的 absolute 覆盖层;推荐页滑动卡片和运行态视口都使用 `overflow: hidden`,半屏内容区会裁剪排行榜、下一关按钮和相似作品卡。
|
||||
- 推荐页嵌入拼图运行态时,“下一关”应优先切到相似作品;如果当前推荐候选为空,才回退到同作品下一关,避免匿名推荐流在多关卡作品上持续停留在同一作品内。下一关请求 pending 期间必须保留当前 `PuzzleRuntimeShell` 和棋盘,不得把推荐卡整体切回 `加载中...` 占位态;局部同步状态由拼图运行态自己的 busy 表现承接。后端返回的新关卡属于其它作品时,前端必须同步 `selectedPuzzleDetail`、推荐页 `puzzleGalleryEntries` 缓存和 `activeRecommendEntryKey`,让底部作品信息、分享 / 点赞 / 改造和下一次“下一个”基准都指向新作品。
|
||||
- 推荐页里的拼图作品如果从运行态进入“改造”结果页,返回平台后要清掉推荐嵌入态的 `activeRecommendEntryKey` / `activeRecommendRuntimeKind` / `isStartingRecommendEntry`,再重新按推荐页自动启动逻辑进入作品,不能复用已经被清空的旧 `puzzleRun`。
|
||||
- 拼图运行态允许前端低延迟交互表现,但通关、排行榜、奖励和作品状态仍以后端确认为准。
|
||||
|
||||
## 跳一跳
|
||||
|
||||
对外名称:`跳一跳`。工程域:`jump-hop`。PRD 见 `docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md`。
|
||||
|
||||
首版定位为俯视角 / 等距视角 2D 休闲跳跃模板,链路对齐拼图的创作闭环:
|
||||
|
||||
```text
|
||||
创作入口 -> 模板输入 -> 生成过程页 -> 结果页 -> 试玩 -> 发布 -> 运行态
|
||||
```
|
||||
|
||||
素材生成规则固定为:
|
||||
|
||||
1. 初始草稿生成时,角色形象单独调用一次生图;
|
||||
2. 初始草稿生成时,地块单独调用一次生图,输出 3D 视图的 2D 图片图集;
|
||||
3. 跳一跳地块图集使用专用 `2行*3列` 六格布局,后端按 `start / normal / target / finish / bonus / accent` 顺序切分为透明 PNG;
|
||||
4. 封面和分享图由角色图与地块图轻量合成,不再额外调用第三次生图;
|
||||
5. 显式重生成角色或地块时,只重生成对应资产槽位。
|
||||
|
||||
运行态规则真相必须沉到 `module-jump-hop`,前端只做蓄力表现、角色位移、投影和落地反馈。通关、失败、分数、combo、运行态快照和发布作品状态以后端为准。公开列表应走 `jump_hop_gallery_card_view` 订阅缓存,不要每次 HTTP 请求调用 procedure 组装全量列表。
|
||||
|
||||
平台首页推荐、精选、最新、公开详情、搜索、已玩作品和公开试玩统一按 `sourceType='jump-hop'` 与 `JH-*` 公开作品号识别跳一跳作品;从公开详情或推荐流启动运行态时,若卡片摘要不足以携带角色图、地块图集和路径配置,必须先补读完整 work profile 再传入运行态。平台壳层必须同步注册 `jump-hop-workspace`、`jump-hop-generating`、`jump-hop-result`、`jump-hop-runtime`、`jump-hop-gallery-detail` 阶段,并在 `appPageRoutes.ts` 映射 `/creation/jump-hop/workspace`、`/creation/jump-hop/generating`、`/creation/jump-hop/result`、`/gallery/jump-hop/detail`、`/runtime/jump-hop`,同时持有 session、work、run、gallery、busy/error 与生成进度状态,避免只合入渲染分支但遗漏状态源或分享路径导致 typecheck 失败、刷新回首页。
|
||||
|
||||
跳一跳作品架走创作中心的统一作品列表:前端通过 `/api/creation/jump-hop/works` 拉取作品摘要,草稿态会与 pending notice 合并后显示在作品架里,已发布作品点击后会先按 profileId 读取完整详情再进入详情或运行态。生成中作品仍以后端摘要里的 `generationStatus` 为准,刷新后应能恢复等待遮罩,不能只依赖内存 notice。
|
||||
|
||||
删除等破坏性动作当前未接入 jump-hop 删除 API;如果后续要在作品架提供删除入口,必须先补齐后端/SpacetimeDB/前端整条删除链路,再开放按钮。
|
||||
|
||||
推荐页匿名游玩不再限定为跳一跳。移动端一级 `推荐` Tab 是内嵌运行态刷卡流,会自动选择推荐作品并启动对应玩法;桌面端首页不启动这套移动推荐运行态,而是渲染桌面发现壳,展示 `今日游戏`、`推荐`、`作品分类` 等桌面内容。断点事实统一走 `platformEntryResponsive.ts` 的 `usePlatformDesktopLayout()`,平台壳和首页视图必须共用同一个判断,避免桌面发现页与移动推荐页同时挂载、重复触发请求或启动运行态。推荐页嵌入运行态启动时按真实身份分流:已登录用户或本地已有 access token 时继续使用账号 Bearer,但请求选项必须是 local auth impact,避免单卡 401 清空整站登录态;只有确认为匿名访客时才申请短期 Runtime Guest Token,并只把它作为局部请求头传给运行态客户端,不写入全局登录态、不触发 refresh,也不把匿名流量伪装成普通用户。当前覆盖矩阵为:跳一跳、视觉小说、抓大鹅 Match3D、方洞挑战、拼图、敲木鱼、大鱼吃小鱼、汪汪声浪。每个模板的启动请求、推荐页内后续运行态动作以及需要上报的 play/finish/leaderboard/next-level 类请求,都必须继续按该身份分流;公开读取入口仍可匿名读取,创作、个人作品、删除、发布、Remix 等账号/所有权动作仍保持普通用户鉴权。
|
||||
|
||||
## 敲木鱼
|
||||
|
||||
对外名称:`敲木鱼`。工程域:`wooden-fish`。PRD 见 `docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`。
|
||||
|
||||
首版定位为单屏点击解压模板,链路对齐拼图的创作闭环:
|
||||
|
||||
```text
|
||||
创作入口 -> 工作台 -> 生成过程页 -> 结果页 -> 试玩 -> 发布 -> 运行态
|
||||
```
|
||||
|
||||
创作输入固定为:
|
||||
|
||||
1. `敲什么`:敲击物单图资产槽位。默认模板使用内置透明 PNG `/wooden-fish/default-hit-object.png` 作为 `bundled-default` 敲击物资产,避免默认关键词被重新语义化改形;用户输入自定义关键词或上传参考图时,后端必须以默认木鱼图作为基础结构和画风参考,使用 image2 生成最终敲击物图案,上传图只作为新主题参考,不直接进入运行态。自定义 `compile-draft` / `regenerate-hit-object` 必须完成 image2 -> OSS 私有对象 -> asset object 登记和绑定后,再由 `api-server` 注入真实 `hitObjectAsset.imageSrc`,不能只写 `/generated-wooden-fish-assets/...` 占位路径,也不能接受前端请求自带的 `hitObjectAsset` 短路生成。
|
||||
2. `敲击音效`:音频资产槽位,当前创作阶段只支持用户上传或麦克风录制;未提供音频时统一写回内置默认木鱼音 `/wooden-fish/default-hit-sound.mp3`。提示词生成音效入口临时关闭,通用 `/api/creation/audio/sound-effect` 对木鱼 `hit_sound` 目标也返回 `410 Gone`;`hitSoundPrompt` 只作为历史兼容字段保留,不参与当前创作流程,也不得由 `spacetime-client` 合成假音频路径。
|
||||
3. `功德有什么`:最多 8 条飘字,创作态首屏只保留一个默认词条 `幸运`,其下提供加号格继续追加词条;创作态只保存词条名,运行态飘字展示时再追加 `+1`。运行态顶部总数卡采用品牌化徽标样式,子项计数器预置展示在可展开面板中,未出现词条初始值为 0。
|
||||
4. `作品标题 / 作品简介 / 主题标签`:不再放在创作工作台首屏,改为生成草稿后的结果页补录区,提交试玩或发布前必须先写回当前作品信息。主题标签编辑样式对齐拼图结果页的胶囊标签编辑器。
|
||||
|
||||
图片生成链路固定为三图 image2 流程:第一步用默认木鱼图作为结构和画风参考,按用户题材关键词或参考图主题生成 `1:1` 绿色背景主体图(纯绿色绿幕),prompt 必须显式要求背景为单一纯绿色 `#00FF00` 且平整无纹理、无渐变、无阴影、无道具,主体完整居中,且禁止黑底、白底、棋盘格和任何实底背景;后端在落库前只对这张绿幕主体图执行去绿背景处理,不做泛抠图,避免误伤玉米等主体像素。第二步必须使用第一步抠图完成后的透明图作为参考图,再用新敲击物作为主题和画风参考生成 `9:16` 背景环境图,背景图只适配主题和画风,不能包含新敲击物本体,也不能增加木槌互动物品;画面中央主体预留区必须干净,中央 40% 区域禁止出现主题主体、主体局部特写、轮廓影子或重复元素,主题元素只能作为外围氛围。第三步必须使用去绿后的敲击物主体图和背景环境图作为参考图生成 `1:1` 返回按钮图,返回按钮必须始终是标准圆形,主体视觉尺寸比当前模板再放大约 50%,圆形外沿必须有与主题色搭配的干净外描边,中央只保留单个左箭头,参考图只约束圆形底色和箭头配色,不得延伸到复杂造型和花纹;按钮不得出现文字、数字、水印、额外 UI 面板或木槌物品。三个资产分别写回 `hitObjectAsset`、`backgroundAsset` 与 `backButtonAsset`,并绑定到 `wooden_fish_work` 的 `hit_object` / `background` / `back_button` 槽位。运行态和结果页消费 `backgroundAsset` 做竖屏背景,中央再叠加 `hitObjectAsset`,左上角返回按钮消费 `backButtonAsset`。
|
||||
|
||||
木鱼初始 `compile-draft` 是长耗时同步 action,生成页必须按上述三图 image2 链路展示进度:整理草稿、生成敲击物、生成背景环境图、生成返回按钮图、写入正式草稿。本地或供应商慢时一次 action 可能持续数分钟;前端不得把已关闭的提示词生成音效当成进度阶段,也不得在未收到 action 回包前宣称生成完成。
|
||||
|
||||
运行态规则真相以后端 run 摘要为准,前端只做点击低延迟表现、敲击动画、音频播放和飘字渲染。每次非功能区点击在当前 run 内累计 `totalTapCount` 和 `wordCounters`;计数不进入账号长期账本,不做排行榜。顶部总数卡点击后展开子项计数器面板,子项计数在面板中按词条纵列预置展示,未出现词条初始值为 0,后续同词条继续累加;运行态左上角使用主题化返回按钮图,不提供右上角重开按钮。
|
||||
|
||||
平台首页推荐、精选、最新、公开详情、搜索、已玩作品和公开试玩统一按 `sourceType='wooden-fish'` 与 `WF-*` 公开作品号识别敲木鱼作品;公开列表应走 `wooden_fish_gallery_card_view` 订阅缓存,公开详情或运行态启动时卡片摘要不足则补读完整 work profile。
|
||||
|
||||
## 抓大鹅 Match3D
|
||||
|
||||
对外名称:`抓大鹅`。工程域:`match3d`。
|
||||
@@ -57,9 +178,10 @@
|
||||
入口表单只展示:
|
||||
|
||||
- 题材主题。
|
||||
- `2D素材风格` 横向风格卡:扁平图标、赛璐璐卡通、像素复古、手绘水彩、贴纸描边、厚涂图标、自定义。
|
||||
- 难度:轻松、标准、进阶、硬核。
|
||||
|
||||
入口不再要求用户选择素材风格;历史草稿和旧接口中的 `assetStyleId` / `assetStyleLabel` / `assetStylePrompt` 仅作为兼容字段保留,新入口提交不再写入这些字段。
|
||||
|
||||
难度映射:
|
||||
|
||||
| 难度 | clearCount | difficulty | 总物品数 | 物品种类 |
|
||||
@@ -67,28 +189,27 @@
|
||||
| 轻松 | 8 | 2 | 24 | 3 |
|
||||
| 标准 | 12 | 4 | 36 | 9 |
|
||||
| 进阶 | 16 | 6 | 48 | 15 |
|
||||
| 硬核 | 21 | 8 | 63 | 21 |
|
||||
| 硬核 | 21 | 8 | 63 | 20 |
|
||||
|
||||
当前素材生成流水线:
|
||||
|
||||
1. 点击生成前弹出泥点确认,草稿生成固定消耗 `10` 泥点。
|
||||
2. 先写入可恢复草稿 profile,再执行文本计划、图片生成、切图、OSS 上传、背景和容器生成;作品摘要在素材或背景未完整时下发 `generationStatus=generating`,素材和背景完整后下发 `ready`,草稿完成条件不包含 `backgroundMusic`。
|
||||
3. 物品素材不再调用 Hyper3D Rodin,不再生成 GLB。新草稿和批量新增固定生成 2D 五视角素材。
|
||||
4. 物品 sheet 走 VectorEngine Gemini `gemini-3-pro-image-preview` 原生 `generateContent`,单张 `1:1` 图固定 `5*5`,每张承载 `5` 个物品、每个物品 `5` 个视角。
|
||||
5. 切图前先在整张 sheet 上做绿幕 / 近白底透明化和边缘去污染,再按格子导出独立 PNG;每个视角图再以扩大的 PNG 边界带为种子,把连通的浅绿 / 近白抗锯齿边直接改为透明,并对贴透明背景的弱绿 / 暗绿轮廓像素做去绿污染处理,最后按剩余可见主体二次收紧;不要先裁剪单格再各自去绿。
|
||||
6. `generatedItemAssets[].imageViews[]` 是新素材主字段,`imageSrc/imageObjectKey` 只兼容首张视角。
|
||||
7. 文本生成物品名称时必须同时生成 `itemSize`,只允许 `大`、`中`、`小`。该字段随 `generatedItemAssets[].itemSize` 持久化并下发;历史缺失字段的素材按 `大` 兼容,模型缺失或非法值按物品名本地推断。
|
||||
8. 局内 `9:16` 纯背景图和 `1:1` 中心容器 UI 图分开生成。纯背景不得包含锅、盘、托盘、HUD、按钮、文字或物品,且入库前必须合成为全画幅不透明图片,不允许出现透明区域;容器图走 `/v1/images/edits` 参考透明容器图。
|
||||
2. 先写入可恢复草稿 profile,再执行文本计划、关卡整图生成、三张派生图生成、OSS 上传和素材解析;作品摘要在背景、UI spritesheet 或物品 spritesheet 未完整时下发 `generationStatus=generating`,完整后下发 `ready`,草稿完成条件不包含 `backgroundMusic`。
|
||||
3. 首次调用 VectorEngine `gpt-image-2`,无参考图,竖屏 `9:16`,生成完整抓大鹅关卡画面并持久化到 `generatedBackgroundAsset.levelSceneImageSrc/levelSceneImageObjectKey`。提示词必须包含用户主题描述、顶部返回 / 标题倒计时 / 设置按钮、中间与主题匹配且贴横向边缘的容器,以及底部“移出 / 凑齐 / 打乱”三个道具按钮。
|
||||
4. 关卡整图完成后并发发起三次 `gpt-image-2` 编辑请求,三者都以关卡整图作为参考图:`1K`、`1:1` 的 UI spritesheet 写入 `uiSpritesheetImageSrc/uiSpritesheetImageObjectKey`;`1K`、`9:16` 的背景图写入 `imageSrc/imageObjectKey`;`2K`、`1:1` 的物品 spritesheet 写入 `itemSpritesheetImageSrc/itemSpritesheetImageObjectKey`。
|
||||
5. UI spritesheet 提示词固定要求按从上到下、从左到右整理纯绿色绿幕背景素材:返回按钮、设置按钮、方格素材(不含边框,仅保留一个)、移出按钮、凑齐按钮、打乱按钮;后端上传 OSS 前必须把绿幕扣成透明 PNG。背景图提示词固定要求移除全部 UI 组件和容器内含物,完整保留容器和背景,并补全被 UI 覆盖的背景内容。
|
||||
6. 物品 spritesheet 固定 `10行*10列`、统一纯绿色绿幕背景,后端上传 OSS 前必须把绿幕扣成透明 PNG;素材间距严格均匀分布,每一行包含两种物品,每种物品五个不同形态,物品来自参考图中心容器中的 2D 素材,严禁高相似度物品。新流程每次解析并持久化 `20` 种物品,物品信息列表全部展示这 `20` 种;后端切 `generatedItemAssets[].imageViews[]` 时优先按透明 alpha 连通域识别真实素材矩形,再按原图从上到下、从左到右排序,每 `5` 个区域组成一个物品的五个形态;只有识别出的区域数量不足时才回退 `10*10` 固定网格。持久化单格映射元数据仍按 `row = itemIndex / 2 + 1`、`col = itemIndex % 2 * 5 + viewIndex + 1` 写入通用系列素材图集,不能再用 `row = itemIndex + 1`。`generatedItemAssets[].imageViews[]` 仍兼容已切好的五视角图,缺失时运行态和编辑器按 spritesheet 自动解析结果回退。
|
||||
7. 前端和运行态统一使用 alpha 连通域矩形检测解析 spritesheet:UI 图先把识别出的透明素材矩形按行聚类,再在每一行内按横向 `x` 坐标排序,最后按返回、设置、方格、移出、凑齐、打乱顺序映射回原 UI 位置;不能只按全局 `y` 坐标排序,否则同一行素材上下略有错位时会把方格和底部道具按钮顺序打乱。物品图按检测顺序每 `5` 个区域组成一个物品的五个形态,最多 `20` 个物品。透明背景是解析前提,不能在前端按固定像素坐标写死切片。
|
||||
8. 文本生成物品名称时必须同时生成 `itemSize`,只允许 `大`、`中`、`小`。该字段随 `generatedItemAssets[].itemSize` 持久化并下发;历史缺失字段的素材按 `大` 兼容,模型缺失或非法值按物品名本地推断。
|
||||
9. 当前抓大鹅音频生成关闭:入口无 `生成音效`,草稿不生成背景音乐或点击音效,结果页不展示背景音乐 Tab 或点击音效生成入口。历史 `backgroundMusic` / `clickSound` 字段继续兼容传递。
|
||||
10. UI 背景和容器资产的持久化真相仍在 `generatedItemAssets[].backgroundAsset`;Agent session、work summary/detail、结果页和运行态入口都必须把该字段提升为 `backgroundImageSrc/backgroundImageObjectKey/generatedBackgroundAsset` 读取。草稿编译后的 `draftJson` 自身也必须携带 `generatedItemAssets` 快照;HTTP facade 不能只依赖 work detail 回读补齐 UI 资产,外部回读为空时也不得清空草稿内已有的背景 / 容器图。平台壳层从作品架、广场、生成完成回调、结果页保存 / 发布 / 试玩回调进入 Match3D profile 时也要先归一化并提升,避免首次试玩、手动试玩、推荐流或公开详情运行态退回默认背景 / 默认容器。
|
||||
10. 背景、UI spritesheet、物品 spritesheet 和历史容器兼容字段的持久化真相仍在 `generatedItemAssets[].backgroundAsset` 与提升后的 `generatedBackgroundAsset`;Agent session、work summary/detail、结果页和运行态入口都必须把该字段提升为 `backgroundImageSrc/backgroundImageObjectKey/generatedBackgroundAsset` 读取。草稿编译后的 `draftJson` 自身也必须携带 `generatedItemAssets` 快照;HTTP facade 不能只依赖 work detail 回读补齐 UI 资产,外部回读为空时也不得清空草稿内已有的背景 / 图集。平台壳层从作品架、广场、生成完成回调、结果页保存 / 发布 / 试玩回调进入 Match3D profile 时也要先归一化并提升,避免首次试玩、手动试玩、推荐流或公开详情运行态退回默认背景。
|
||||
|
||||
结果页当前结构:
|
||||
|
||||
- `作品信息`:名称、描述、标签;封面编辑收口到发布面板。
|
||||
- `难度配置`:四档离散拖动条,显示需要消除、总物品数、物品种类、已生成物品种类。
|
||||
- `素材配置 > 物品`:两列素材卡,点击打开独立五视角预览面板;支持删除、批量新增和批量重新生成。替换模式必须保留原 `itemId` 和列表顺序。
|
||||
- `素材配置 > UI`:纯背景图与运行态 UI 预览,重生成消耗 `2` 泥点;UI 预览必须复用运行态顶部 HUD、中央容器棋盘、容器图定位和底部槽位样式,不单独维护一套简化预览 UI。
|
||||
- `素材配置 > 容器形象`:单独预览和重生成中心容器,消耗 `2` 泥点。
|
||||
- `素材配置 > 物品`:两列素材卡固定展示 20 个物品,点击打开独立五视角预览面板;支持删除、批量新增和批量重新生成。替换模式必须保留原 `itemId` 和列表顺序。
|
||||
- `素材配置 > UI素材`:预览背景图、UI spritesheet 原图、物品 spritesheet 原图和物品 spritesheet 自动解析缩略图;背景图只支持预览,不提供重新生成入口。UI 预览必须复用运行态顶部 HUD、中央容器棋盘和底部槽位样式,不单独维护一套简化预览 UI。
|
||||
|
||||
运行态当前口径:
|
||||
|
||||
@@ -98,14 +219,18 @@
|
||||
- 初始物品坐标围绕容器口中心生成,并保留内缩安全距离,避免贴边和局部角落聚集。
|
||||
- 本地试玩与 Rust `module-match3d` 后端领域生成使用同一套中心铺开口径;生成点覆盖四象限且均值接近中心。
|
||||
- 运行态优先消费 2D 生成图;默认积木 / 程序化 3D 表现只作为视觉分支和兜底,不改变规则真相。
|
||||
- 运行态启动前要预加载 `generatedItemAssets[].imageViews[]`、顶层 `generatedBackgroundAsset`、物品挂载 `backgroundAsset` 中的背景和容器图;首次生成自动试玩、结果页手动试玩、推荐流和公开详情启动都必须传入提升后的 profile。卡片摘要缺 UI 背景或容器字段时,进入运行态前必须补读 work detail。补读后的 profile 也要再次提升 `generatedItemAssets[].backgroundAsset`,确保背景和容器字段传给 `Match3DRuntimeShell`。
|
||||
- 局内容器图在移动端宽度大于屏幕宽度并略向下压,当前运行态使用 `w-[min(116vw,42rem)]` 与 `top-[54%]` 放大和下移容器图本体,保持原图比例不拉伸且不改变后端物品布局、点击半径或消除规则;生成容器图加载成功后棋盘外壳透明且 `overflow-visible`,只有生成图缺失或加载失败时才显示透明参考容器兜底。
|
||||
- 难度只决定本局加载的物品种类数量:轻松 3、标准 9、进阶 15、硬核 20。硬核仍保留 21 次消除和 63 件总物品,运行态按 20 种素材循环复用,不要求生成第 21 种素材。
|
||||
- 运行态启动前要预加载 `generatedItemAssets[].imageViews[]`、顶层 `generatedBackgroundAsset`、物品挂载 `backgroundAsset` 中的背景、UI spritesheet 和物品 spritesheet;首次生成自动试玩、结果页手动试玩、推荐流和公开详情启动都必须传入提升后的 profile。卡片摘要缺图集字段时,进入运行态前必须补读 work detail。补读后的 profile 也要再次提升 `generatedItemAssets[].backgroundAsset`,确保背景和图集字段传给 `Match3DRuntimeShell`。
|
||||
- 背景图作为运行态全屏背景,图内已经保留容器;旧 `containerImage*` 只作为历史透明容器兼容字段。若 `containerImage*` 与 `uiSpritesheetImage*` 同源,运行态不得把 UI spritesheet 当中心容器图叠到棋盘上。
|
||||
- 抓大鹅运行态 HUD 需贴近拼图顶部信息条的视觉口径:左上只保留透明返回按钮;右上不再暴露设置入口;顶部关卡名和倒计时直接复用拼图同款的铭牌 + 下挂计时牌结构、同色板和同造型,并在牌面左侧挂上 `media/logo.png` 产品 logo;下方备选栏和道具图标只保留内容与交互边界,不再显示灰白半透底板;中央容器图层视觉可隐藏,但棋盘命中边界仍保留。
|
||||
- generated 私有图换签未完成时,局内物品先隐藏等待,不得短暂显示默认积木;同一批资源在重启 run 时保留已解析签名 URL,只有资源源列表变化或换签失败后才允许进入兜底视觉。
|
||||
- `itemSize` 只缩放生成 2D 图片本体:`大`、`中`、`小` 均按相对尺寸缩放,其中 `大` 也比原始图片略小,`中` 和 `小` 进一步缩小;不改变后端下发的布局半径、点击半径或三消规则。
|
||||
- 物品进入底部物品栏时按同类型插入:如果物品栏已有同类物品,新物品插到该类型最后一个物品后面,后续物品整体后移;没有同类时追加到当前末尾。达到三件同类时,在飞入物品栏动画结束后,左侧和右侧同类物品向中间合成,三件一起消失,播放合成音效,不展示星星图标,后面的物品再向前补位。该动效只是前端表现层,后端和本地试玩仍负责权威插入、指定点击类型清除与补位后的槽位快照。
|
||||
- 抓大鹅运行态右上角常驻设置入口,不直接暴露重新开始按钮;重新开始收口到设置面板内,结算弹层仍保留结果态的再来一局动作。
|
||||
- 抓大鹅运行态不渲染右上角设置入口,也不在局内直接暴露重新开始按钮;结算弹层仍保留结果态的再来一局动作。
|
||||
- 高 DPR 移动端 WebGL canvas 必须锁定 CSS 尺寸,避免右下溢出。
|
||||
|
||||
发现页不再挂载前端固定官方抓大鹅静态 demo;公开卡片、作品号搜索、详情页和运行态启动只能来自后端真实 profile / gallery 投影。正式公开作品统一走 server runtime adapter,前端不得再用本地 demo profile 绕过后端统计和运行态链路。
|
||||
|
||||
## 视觉小说
|
||||
|
||||
当前视觉小说只吸收外部 TXT 玩法的创作与运行经验,不迁入外部平台社区、支付、榜单、私有存档或回放。
|
||||
@@ -133,11 +258,38 @@
|
||||
当前领域语言:
|
||||
|
||||
- 有效声浪触发:麦克风归一化响度在冷却结束后达到阈值的一次计分输入。
|
||||
- 能量条:玩家与对手当前声浪优势的连续对抗刻度。
|
||||
- 能量条:玩家与对手当前声浪优势的连续对抗刻度,推到玩家或对手一侧边界时本局立即结算。
|
||||
- 主题 / 竞技背景描述:配置字段为 `themeDescription`,用于生成竞技背景并表达整体场景,不再使用 `themePreset` 或狗狗皮肤预设。
|
||||
- 玩家 / 对手形象描述:配置字段为 `playerImageDescription` / `opponentImageDescription`,对外统一称“形象描述”,不再称“角色设定”。
|
||||
- 后端裁决结果:后端根据 start run 与 finish 派生指标校验后的正式单局结果。
|
||||
- 排行榜分榜:按 `workId + difficultyPreset + rulesetVersion` 拆分,只收录后端裁决玩家胜利的成绩。
|
||||
- 基础统计:只记录正式 `published` run 的开始、结算和派生指标,草稿试玩不写正式统计。
|
||||
- 公开广场:统一读取 `bark_battle_gallery_view` 这类 read model,不再由前端自己拼公开列表。
|
||||
- 创作者信息:统一作品详情和公开广场都必须展示后端返回的 `authorDisplayName`,不得只在详情页内层可见;草稿 Tab 作品架遵循平台作品架统一口径,无论草稿 / 已发布都不外露作者信息。
|
||||
- 拟声词:配置字段为 `onomatopoeia`。创作者未手动编辑时,前端根据主题 / 竞技背景描述、玩家形象描述和对手形象描述生成高能词池;创作者手动编辑后按自定义词池发布。默认词池只在命中狗相关主题时加入狗叫词,不能把非狗主题强行带回狗语义。
|
||||
|
||||
当前入口状态为 `visible=true`、`open=false`,创作 Tab 展示为“敬请期待”,不进入轻配置表单或 runtime。后续重新开放时仍沿用创作 Tab 内嵌轻配置表单,不再切到独立 `bark-battle-config` 阶段;runtime 退出后回到创作页并恢复汪汪声浪模板选中态。
|
||||
当前入口默认开放:`visible=true`、`open=true`、`badge=可创建`,入口参考图使用 `/creation-type-references/bark-battle.webp`。创作入口使用 7 字段表单(作品标题、简介、主题 / 竞技背景描述 `themeDescription`、玩家形象描述、对手形象描述、拟声词、难度);提交后先进入 `bark-battle-generating` 独立生成页,自动生成玩家形象、对手形象和竞技背景三图。生成页即使部分槽位失败也要继续落到结果页,失败槽位保留错误态和单槽重试入口,不在生成页停留。结果页只保留单槽重试、重新生成和上传,不再展示一次生成按钮、音频配置入口、皮肤预设入口或排名配置。发布成功后先跳统一作品详情页 `/works/detail?work=BB-xxxxxxxx`,正式 `published` runtime 从作品详情页进入并必须使用真实麦克风;`draft` 可试玩,可使用 mock 输入,且不写正式统计。统一作品详情和广场列表展示创作者名称;草稿 Tab 作品架不外露创作者名称,已发布作品只在右上角常驻分享入口。
|
||||
|
||||
移动端创作 Tab 内嵌 Bark Battle 表单时,只保留外层 Tab 面板承担纵向滚动;表单自身移动端不再创建独立纵向滚动容器,底部“生成草稿”按钮作为普通表单尾部并保留 safe-area 底部间距,避免与最后一组输入框、移动端键盘或底部 TabBar 形成套滚动 / 遮挡。
|
||||
|
||||
创作流程为:
|
||||
|
||||
- 创作 Tab 表单:填写作品标题、简介、主题 / 竞技背景描述、玩家形象描述、对手形象描述、拟声词和难度。拟声词支持换行、逗号、顿号、斜杠或竖线分隔;未手动编辑时随主题 / 形象描述自动重算,手动编辑后保持创作者自定义。
|
||||
- 草稿编译:`POST /api/creation/bark-battle/drafts` 写入配置 JSON,返回包含 `draftId`、稳定 `workId`、`configVersion` 和 `rulesetVersion` 的草稿结果。
|
||||
- 生成页:`bark-battle-generating` 自动并行产出玩家形象、对手形象和竞技背景三图;前端生成页 UI 和其它玩法保持同一圆环主视觉,`media/create_bg_video.mp4` 作为固定全屏页面背景层循环静音播放,主进度圆环居中展示总进度,只保留当前步骤名称和当前步骤进度,不再渲染三行槽位列表。视频层需要显式触发播放。三图都走 Bark Battle 专用后端生图接口 `POST /api/creation/bark-battle/images/generate`,由后端按 `player-character`、`opponent-character`、`ui-background` 分别拼装正式提示词、写入 `generated-bark-battle-assets` 私有资产前缀并返回实际 prompt。玩家 / 对手形象提示词必须保持用户形象描述,不强行注入狗相关主体,并要求正面、单个完整形象和透明背景。部分失败也继续进入结果页。
|
||||
- 结果页:围绕三图槽位展示错误态与已生成结果,只保留单槽重试、重新生成和上传,不再提供一次生成按钮、音频配置入口或排名配置。
|
||||
- 手动上传:结果页通过平台资产直传 `/api/assets/direct-upload-tickets` 与 `/api/assets/objects/confirm` 写入私有资产,再把返回的历史 generated 路径写回草稿配置。
|
||||
- 发布:结果页确认后必须携带草稿返回的同一个 `workId` 和结果页最终 `publishedSnapshot` 调用 `POST /api/creation/bark-battle/works/publish`;SpacetimeDB 发布态的 `config_json` 必须使用该最终快照,works summary 若拿到 `publishedSnapshotJson` 也优先使用最终快照映射封面三图。发布成功后先进入统一作品详情页,再由详情页进入正式 runtime;缺少 `workId` 的旧草稿状态需要重新生成草稿。
|
||||
- 作品架:Bark Battle 草稿 / 已发布列表优先读取后端 `/works`,但创建、生成完成、保存或发布后的本地摘要必须在后端 read model 尚未回读到同 `workId` 前继续保留;创作中心作品架同时接入 pending shelf 兜底,避免 ready 且三图齐全的草稿在刷新窗口期从“我的草稿 / 已发布”中消失。
|
||||
- 试玩与正式 runtime:草稿试玩使用 `runtimeMode=draft` 和 mock 输入,不写正式 run;正式 runtime 使用 `runtimeMode=published`,进入运行态后直接申请真实麦克风权限,授权成功后立刻进入倒计时,启动对局时调用 `POST /api/runtime/bark-battle/works/{workId}/runs` 登记 start run,并以返回的 `runtimeConfig` 作为本局前端规则参数;结算时调用 `POST /api/runtime/bark-battle/runs/{runId}/finish` 写入基础统计派生指标;对局会在能量条推到任一侧边界时提前结算并弹出独立结算弹窗,运行态内固定提供返回按钮。
|
||||
|
||||
支持的创作者可替换内容:
|
||||
|
||||
- 基础信息:作品标题、简介、主题 / 竞技背景描述(`themeDescription`)、玩家形象描述、对手形象描述和难度。
|
||||
- 生成素材:玩家形象、对手形象和竞技背景三个槽位可单槽重试、重新生成或上传;形象图保持正面和透明背景,不把非狗形象描述改写成狗。
|
||||
- 拟声词:最多保留前 `24` 个有效词;默认池按狗、机甲 / 科技、幻想 / 骑士等主题补充高能短词,并叠加通用“炸场 / 破阵 / 声浪拉满”等基础词。局内只要有效声浪触发就随机快速展示,避免连续重复。
|
||||
- 运行态输入:正式 runtime 必须真实麦克风;草稿试玩允许 mock,不写正式统计。
|
||||
|
||||
这些创作字段写入 Bark Battle 配置 JSON,发布后由 runtime 和基础统计链路读取;对局时长、反作弊校验和后端裁决仍由规则集与后端控制,不能通过前端替换项改变。当前声浪触发口径为前端默认阈值 `0.35`、有效触发冷却 `150ms`,后端 `BarkBattleRuleset` 的 `min_bark_gap_ms` 也保持 `150ms`,用于正式成绩校验的物理触发上限。历史排名相关后端字段暂保留兼容,但 v1 公开闭环不展示音频、皮肤预设或排名配置入口。
|
||||
|
||||
## 方洞挑战
|
||||
|
||||
@@ -151,9 +303,9 @@
|
||||
|
||||
当前包含:
|
||||
|
||||
- `baby-object-match`:宝贝识物当前入口状态为 `visible=true`、`open=false`,创作 Tab 展示为“敬请期待”,不进入创作与发布链路;历史资产接口为 `/api/creation/edutainment/baby-object-match/assets`,后续重新开放时继续复用该资产链路。
|
||||
- `baby-object-match`:宝贝识物当前入口状态为 `visible=true`、`open=true`,创作 Tab 展示为“可创建”,进入宝贝识物创作、生成、结果页、试玩和发布链路;资产生成接口为 `/api/creation/edutainment/baby-object-match/assets`。入口关闭只允许通过 SpacetimeDB / 后台入口配置显式调整,默认种子和 debug 兜底都必须保持可创建。
|
||||
- `baby-love-drawing`:宝贝爱画本地 demo,魔法生成接口为 `/api/creation/edutainment/baby-love-drawing/magic`。
|
||||
- `child-motion-demo`:儿童动作识别热身关。真实动作数据来自 mocap WebSocket,不要把浏览器摄像头视频流当作主动作数据源。
|
||||
- `child-motion-demo`:儿童动作识别热身关。真实动作数据来自 mocap WebSocket,不要把浏览器摄像头视频流当作主动作数据源;发现页的寓教于乐频道同时提供独立热身关入口,点击后进入 `/child-motion-demo`。
|
||||
|
||||
## 创意互动 Agent
|
||||
|
||||
|
||||
29
docs/【玩法创作】拼图生成页进度口径-2026-05-23.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# 拼图生成页进度口径
|
||||
|
||||
更新时间:`2026-05-24`
|
||||
|
||||
## 目标
|
||||
|
||||
拼图草稿生成页的步骤推进只跟随真实生成结果;步骤内部允许使用本地假进度增强等待反馈。未收到当前步骤完成信号前,生成页必须停留在当前步骤,不得按预计耗时自动跳到后续步骤。
|
||||
|
||||
## 落地口径
|
||||
|
||||
- 总进度和当前步骤内百分比可以按已耗时平滑增长,但进入生成页的初始帧必须从 `0%` 开始,非完成态最多停在 `98%`。
|
||||
- 未收到首个真实里程碑前,页面仍停留在当前步骤,总进度在 `0-88` 区间内平滑推进;收到 `88/94/96` 里程碑后,分别在 `88-94`、`94-96`、`96-98` 区间内推进,避免步骤不跳时总进度也停死。
|
||||
- 后端 `progressPercent` 低于 `88` 只作为当前会话状态记录,不得把生成页阶段推到首个图片里程碑;低于首个里程碑时页面仍按当前视图进入时间从 `0%` 平滑展示。
|
||||
- 步骤状态以真实阶段为准:`phase` / 后端会话进度 / 最终完成或失败回包才允许跨步。
|
||||
- 拼图生成页恢复持久化 `generationStatus=generating` 草稿时,展示进度使用“进入生成页的当前时间”作为 `startedAtMs`;不得再用作品摘要 `updatedAt` 推导展示起点,避免刷新后首帧直接跳到 `80%+`。
|
||||
- 拼图和抓大鹅等生成页从作品架 / 刷新恢复进入时,前端应把展示态生成状态重基准到进入页面的当前时间;后台 session 的 `progressPercent` 与历史里程碑只保留为状态事实,不得直接作为首帧总进度。
|
||||
- 当前步骤未完成时,后续步骤保持待处理;即使预计时间耗尽,也只能让当前步骤内部进度接近或达到上限,不能自动完成后续步骤。
|
||||
- 抓大鹅等非拼图小游戏的生成页也遵守初始帧 `0%`:没有后端资产计数时,当前步骤内假进度按玩法预计等待总时长从 `0` 平滑推进,不使用固定 `0.5` 这类常量起步。
|
||||
- 步骤卡片只展示标题和进度,不展示详细描述。
|
||||
- 生成拼图首图步骤按 4 分钟预估;完整 AI 重绘路径总预计时长为 448 秒,上传图且关闭 AI 重绘时跳过首图生成,仍为 208 秒。
|
||||
|
||||
## 验收
|
||||
|
||||
- `src/services/miniGameDraftGenerationProgress.test.ts` 覆盖拼图步骤不会单纯按时间推进。
|
||||
- `src/services/miniGameDraftGenerationProgress.test.ts` 覆盖后端 `progressPercent < 88` 时不会抬高进入生成页的初始总进度。
|
||||
- `src/services/miniGameDraftGenerationProgress.test.ts` 覆盖抓大鹅等非拼图生成页初始总进度为 `0%`。
|
||||
- `src/components/CustomWorldGenerationView.test.tsx` 覆盖步骤详情不在生成页渲染。
|
||||
- `src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating"` 覆盖刷新后继续生成中拼图 / 抓大鹅草稿不会继承旧 `updatedAt` 导致总进度首帧过高。
|
||||
- 文档主图谱的拼图章节同步保留该口径。
|
||||
31
docs/【玩法创作】生成页圆环布局口径-2026-05-23.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# 生成页圆环布局口径
|
||||
|
||||
更新时间:`2026-05-24`
|
||||
|
||||
## 目标
|
||||
|
||||
所有玩法的生成页统一收敛为参考图同款等待态:顶部只保留返回入口和生成状态胶囊,页面中段用大圆环展示总进度,圆环左右悬浮“预计等待 / 已耗时”,下方只保留当前步骤单卡和当前作品信息卡,不再渲染步骤列表块。
|
||||
|
||||
## 落地口径
|
||||
|
||||
- 共用生成页 `CustomWorldGenerationView` 的主进度条使用居中 SVG 大圆弧,默认保留正下方 90 度留空,`media/create_bg_video.mp4` 作为固定全屏背景层循环静音播放;圆弧覆盖在背景之上展示总进度。视频层需要显式触发播放,不能只依赖 `autoPlay/loop/muted` 属性。
|
||||
- 生成页背景视频必须留在生成页容器内部,直接作为 `fixed inset-0` 的底层背景,不要再通过 portal 挂到 `document.body`;页面根容器使用 `z-[1]`、背景容器使用 `z-0`,确保顶部导航、圆环和当前步骤卡都稳定覆盖在视频之上。
|
||||
- 预计等待 / 已耗时信息卡要压缩为更轻的半透明窄卡,标签使用 `9px-10px`,数值使用 `12px-13px`,字号对齐其他生成页 UI 的小字号,不再使用偏大的提示文本;卡片标题和时间值都居中显示,两个数值只展示时间本身,调用侧不要再拼接“预计还需”或“已耗时”前缀。圆环中心不再保留独立白底块,空心圆环只保留条状进度,圆弧半径继续加大,进度数字与“总进度”标题整体上移,靠近圆环上半区。
|
||||
- 顶部导航区采用“返回创作中心 / 状态胶囊”结构,返回按钮使用左箭头图标,字号使用 `text-xs-sm`,状态胶囊使用 `11px-12px`,展示 `素材生成中`、`草稿生成中` 等调用侧传入文案。
|
||||
- 圆弧区域不再包独立大卡片,左右悬浮信息卡只展示“预计等待”和“已耗时”;总进度数值放在圆弧内侧偏上的位置并保持更小字号。当前圆环外径以 `w-[min(35rem,94vw)] sm:w-[52rem]` 为基准,圆弧使用 `r=166`、`strokeWidth=18` 的 SVG 描边,不再使用 `conic-gradient + mask`,避免进度条边缘模糊。
|
||||
- 圆弧描边以圆心为中心整体按 `155deg` 起始;在当前 SVG 坐标系下,这相对 `160deg` 会向左逆时针回调 `5deg`。track 和 fill 都必须共用同一个 `rotate(155 200 200)` 变换,避免只改视觉起点却让填充和轨道错位。
|
||||
- 总进度标题和百分比数字必须显式高于 SVG 圆环层级渲染,避免被圆环边缘压住;圆环本身只做背景层,不抢文字层。
|
||||
- 总进度标题和百分比数字要比圆环再上移一点,当前内容区上边距以 `pt-[2%]` 为准,桌面端可进一步微调到 `sm:pt-[1.5%]`,确保数字不与进度条弧线重合。
|
||||
- 从作品架或刷新后的持久化生成中草稿进入生成页时,前端必须重置“展示态 startedAtMs”为进入生成页的当前时间;后端 `progressPercent` 只用于后续真实步骤推进,不得参与首帧总进度展示,避免恢复生成页首帧直接显示 `80%+`。
|
||||
- 生成页只展示半透明“当前步骤”单卡,卡片内只保留步骤名称、步骤状态、步骤进度条和轻量加载指示;“当前步骤”标签使用 `10px-11px`,步骤名称使用 `14px-15px`,状态使用 `11px-12px`,不再渲染步骤列表或步骤详情。
|
||||
- 当前作品信息放在圆角信息卡中,标题固定使用 `13px`;有结构化字段时以两列信息块展示,例如“题材 / 素材数量”,无结构化字段时才展示纯文本设定。
|
||||
- 汪汪声浪生成页 `BarkBattleGeneratingView` 也必须对齐同一垂直布局,不再继续展示三行槽位列表或左右分栏抢占主视觉。
|
||||
- 汪汪声浪的总进度按三槽位已完成数量换算;当前步骤只显示第一个未完成槽位的名称与进度。
|
||||
|
||||
## 验收
|
||||
|
||||
- `src/components/CustomWorldGenerationView.test.tsx` 覆盖圆环主视觉和单步卡片。
|
||||
- `src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx` 覆盖汪汪声浪生成页对齐后的圆环布局。
|
||||
- 两个生成页都应在测试里断言页面根容器层级高于背景视频容器,且背景视频确实是页面子节点,避免 portal 背景把业务 UI 压住。
|
||||
- 还应断言圆弧正下方留空、圆环中心没有独立底色块,时间卡和总进度字号缩小后仍能在桌面与移动端正常排版;同时断言时间卡 `text-center`、标题行 `justify-center`、总进度内容区上移到 `pt-[4%]`,圆弧 DOM 为 SVG,包含清晰的 track/fill circle 描边。
|
||||
- 页面在桌面和移动端都不应再出现生成步骤列表块,圆环和当前步骤卡不能被外层卡片嵌套出双层面板感。
|
||||
@@ -44,6 +44,8 @@ Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台。当
|
||||
1. 主站登录弹窗必须稳定展示 `短信登录` 与 `密码登录` 两个核心入口;`GET /api/auth/login-options` 只能补充微信等环境相关入口,不能决定是否隐藏短信或密码登录。
|
||||
2. `login-options` 为空、失败、只返回 `phone` 或只返回 `password` 时,前端仍要同时展示验证码登录页签和密码登录页签;短信能力真实可用性由发送验证码接口返回结果表达。
|
||||
3. 登录弹窗继续复用现有独立 modal 和页签结构,不在页面中新增功能说明类文案,也不把邀请码输入放回登录面板。
|
||||
4. 微信小程序 `web-view` 外壳默认不预登录,首次进入直接打开 H5,并保持与 Web 端一致的未登录状态;只有 H5 触发 `openLoginModal` / `requireAuth` 等受保护入口时,才跳转小程序原生授权态。
|
||||
5. 小程序内需要登录时不展示 H5 登录弹窗,也不走手输手机号 / 短信验证码流程;统一通过原生 `button open-type="getPhoneNumber"` 获取微信手机号授权,再调用 `/api/auth/wechat/miniprogram-login` 与 `/api/auth/wechat/bind-phone` 换取系统登录态。
|
||||
|
||||
## 账户与充值
|
||||
|
||||
@@ -93,7 +95,12 @@ server-rs + Axum + SpacetimeDB
|
||||
7. 主站入口已锁定移动端页面级缩放;单个游戏页面不要再重复实现整页缩放锁定。
|
||||
8. 图像输入通用 UI 统一走 `src/components/common/CreativeImageInputPanel.tsx`。外层页面持有业务状态,组件只承担上传卡、预览、参考图缩略图、AI 重绘开关、错误展示和提交按钮。
|
||||
9. 发现页 `分类` 子频道的筛选必须打开独立 dialog / drawer / modal,至少支持玩法类型过滤与排序切换;筛选结果为空时显示空状态,不把筛选内容展开在当前列表下方。
|
||||
10. “我的”页泥点、游戏时长、玩过三张统计卡只展示各自标签和值,内容居中且不换行,不在统计区底部展示“更新于”时间。
|
||||
10. 移动端“我的”页顶部品牌行承载扫码和设置入口,正文按参考图顺序组织为头像 / 昵称 / 陶泥号、会员横幅、三张统计卡、每日任务、五项常用功能宫格、设置入口和法律信息;`media/profile/` 中的陶泥素材作为该页图形资产。常用功能宫格固定承载泥点充值、邀请好友、兑换码、玩家社区、反馈与建议;页面不再提供独立存档按钮入口,也不在底部保留旧的填邀请码次级入口。填邀请码只由邀请链接 query 或其它明确引导打开独立弹窗,不作为“我的”页常驻按钮。
|
||||
11. “我的”页每日任务卡必须展示后端 `/api/profile/tasks` 返回的当前任务摘要,包括奖励泥点数、进度和领取 / 去完成 / 已完成状态;任务领取成功后,卡片摘要必须跟随返回的任务中心数据同步刷新,不能继续硬编码 `0 / 1` 或只更新弹窗内任务列表。
|
||||
12. “我的”页泥点、游戏时长、已玩游戏数量三张统计卡只展示各自标签和值,三个统计 icon 使用小尺寸普通 UI 档位,内容不换行,不在统计区底部展示“更新于”时间;移动端昵称、会员卡、每日任务、常用功能和法律信息也应保持 `10px` 到 `14px` 的普通 UI 字号区间,避免展示级字号挤压内容。
|
||||
13. 移动端“我的”页需要兼容窄屏:头像 / 昵称 / 陶泥号、三张统计卡、每日任务、五项常用功能和法律信息都必须能在底部固定 TabBar 上方完整滚动露出,不得与底部 dock、刘海 safe-area 或相邻 UI 元素遮挡重叠。
|
||||
14. RPG 等运行态的战斗飘字、血量变化和即时反馈必须在暗色、噪声高的场景背景上保持可读:使用高亮文字、深色描边、强阴影或小面积半透明底,不只依赖红/绿文字本身表达伤害或治疗。
|
||||
15. 平台亮色 UI 配色以陶泥儿主视觉为准:暖白 / 米杏底、陶土橙主按钮、深棕正文与浅杏边框;新增界面优先复用 `src/index.css` 的 `--platform-*` 主题变量和 `apps/admin-web/src/styles/admin.css` 的同系色值,不再引入粉红、蓝绿等独立主色方案。
|
||||
|
||||
## 文案与编码
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ pipeline {
|
||||
}
|
||||
|
||||
environment {
|
||||
GIT_REMOTE_URL = 'https://git.genarrative.world/GenarrativeAI/Genarrative.git'
|
||||
GIT_REMOTE_URL = 'http://127.0.0.1:3000/GenarrativeAI/Genarrative.git'
|
||||
GIT_REMOTE_FALLBACK_URL = 'https://git.genarrative.world/GenarrativeAI/Genarrative.git'
|
||||
}
|
||||
|
||||
parameters {
|
||||
@@ -42,23 +43,36 @@ pipeline {
|
||||
label 'linux && genarrative-build'
|
||||
}
|
||||
steps {
|
||||
checkout([
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: "*/${params.SOURCE_BRANCH}"]],
|
||||
doGenerateSubmoduleConfigurations: false,
|
||||
extensions: [
|
||||
[$class: 'CleanBeforeCheckout'],
|
||||
[$class: 'CloneOption', shallow: true, depth: 1, noTags: true, timeout: 30, honorRefspec: true],
|
||||
],
|
||||
userRemoteConfigs: [[url: "${GIT_REMOTE_URL}", refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"]],
|
||||
])
|
||||
script {
|
||||
def checkoutFromRemote = { String remoteUrl ->
|
||||
checkout([
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: "*/${params.SOURCE_BRANCH}"]],
|
||||
doGenerateSubmoduleConfigurations: false,
|
||||
extensions: [
|
||||
[$class: 'CleanBeforeCheckout'],
|
||||
[$class: 'CloneOption', shallow: true, depth: 1, noTags: true, timeout: 30, honorRefspec: true],
|
||||
],
|
||||
userRemoteConfigs: [[url: remoteUrl, refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"]],
|
||||
])
|
||||
}
|
||||
try {
|
||||
checkoutFromRemote(env.GIT_REMOTE_URL)
|
||||
env.EFFECTIVE_GIT_REMOTE_URL = env.GIT_REMOTE_URL
|
||||
} catch (error) {
|
||||
echo "Git 主地址拉取失败: ${env.GIT_REMOTE_URL},改用备用地址: ${env.GIT_REMOTE_FALLBACK_URL}"
|
||||
checkoutFromRemote(env.GIT_REMOTE_FALLBACK_URL)
|
||||
env.EFFECTIVE_GIT_REMOTE_URL = env.GIT_REMOTE_FALLBACK_URL
|
||||
}
|
||||
}
|
||||
sh '''
|
||||
bash -lc '
|
||||
set -euo pipefail
|
||||
chmod +x scripts/jenkins-checkout-source.sh
|
||||
SOURCE_BRANCH="${SOURCE_BRANCH:-master}" \
|
||||
COMMIT_HASH="${COMMIT_HASH:-}" \
|
||||
GIT_REMOTE_URL="${GIT_REMOTE_URL}" \
|
||||
GIT_REMOTE_URL="${EFFECTIVE_GIT_REMOTE_URL:-${GIT_REMOTE_URL}}" \
|
||||
GIT_REMOTE_FALLBACK_URL="${GIT_REMOTE_FALLBACK_URL:-}" \
|
||||
SOURCE_COMMIT_FILE=".jenkins-source-commit" \
|
||||
scripts/jenkins-checkout-source.sh
|
||||
'
|
||||
|
||||
@@ -10,7 +10,8 @@ pipeline {
|
||||
}
|
||||
|
||||
environment {
|
||||
GIT_REMOTE_URL = 'https://git.genarrative.world/GenarrativeAI/Genarrative.git'
|
||||
GIT_REMOTE_URL = 'http://127.0.0.1:3000/GenarrativeAI/Genarrative.git'
|
||||
GIT_REMOTE_FALLBACK_URL = 'https://git.genarrative.world/GenarrativeAI/Genarrative.git'
|
||||
WEB_ARTIFACT_ROOT = '/var/cache/genarrative-build/web-artifacts'
|
||||
}
|
||||
|
||||
@@ -29,23 +30,36 @@ pipeline {
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout([
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: "*/${params.SOURCE_BRANCH}"]],
|
||||
doGenerateSubmoduleConfigurations: false,
|
||||
extensions: [
|
||||
[$class: 'CleanBeforeCheckout'],
|
||||
[$class: 'CloneOption', shallow: true, depth: 1, noTags: true, timeout: 30, honorRefspec: true],
|
||||
],
|
||||
userRemoteConfigs: [[url: "${GIT_REMOTE_URL}", refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"]],
|
||||
])
|
||||
script {
|
||||
def checkoutFromRemote = { String remoteUrl ->
|
||||
checkout([
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: "*/${params.SOURCE_BRANCH}"]],
|
||||
doGenerateSubmoduleConfigurations: false,
|
||||
extensions: [
|
||||
[$class: 'CleanBeforeCheckout'],
|
||||
[$class: 'CloneOption', shallow: true, depth: 1, noTags: true, timeout: 30, honorRefspec: true],
|
||||
],
|
||||
userRemoteConfigs: [[url: remoteUrl, refspec: "+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}"]],
|
||||
])
|
||||
}
|
||||
try {
|
||||
checkoutFromRemote(env.GIT_REMOTE_URL)
|
||||
env.EFFECTIVE_GIT_REMOTE_URL = env.GIT_REMOTE_URL
|
||||
} catch (error) {
|
||||
echo "Git 主地址拉取失败: ${env.GIT_REMOTE_URL},改用备用地址: ${env.GIT_REMOTE_FALLBACK_URL}"
|
||||
checkoutFromRemote(env.GIT_REMOTE_FALLBACK_URL)
|
||||
env.EFFECTIVE_GIT_REMOTE_URL = env.GIT_REMOTE_FALLBACK_URL
|
||||
}
|
||||
}
|
||||
sh '''
|
||||
bash -lc '
|
||||
set -euo pipefail
|
||||
chmod +x scripts/jenkins-checkout-source.sh
|
||||
SOURCE_BRANCH="${SOURCE_BRANCH:-master}" \
|
||||
COMMIT_HASH="${COMMIT_HASH:-}" \
|
||||
GIT_REMOTE_URL="${GIT_REMOTE_URL}" \
|
||||
GIT_REMOTE_URL="${EFFECTIVE_GIT_REMOTE_URL:-${GIT_REMOTE_URL}}" \
|
||||
GIT_REMOTE_FALLBACK_URL="${GIT_REMOTE_FALLBACK_URL:-}" \
|
||||
SOURCE_COMMIT_FILE=".jenkins-source-commit" \
|
||||
scripts/jenkins-checkout-source.sh
|
||||
'
|
||||
|
||||
BIN
media/create_bg_video.mp4
Normal file
BIN
media/logo.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
media/profile/_Image (1).png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
media/profile/_Image (2).png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
media/profile/_Image (3).png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
media/profile/_Image (4).png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
media/profile/_Image (5).png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
media/profile/_Image (6).png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
media/profile/_Image (7).png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
media/profile/_Image (8).png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
media/profile/_Image (9).png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
media/profile/_Image.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
@@ -1,17 +1,17 @@
|
||||
// 中文注释:这里填写已经在“小程序后台-开发-开发设置-业务域名”配置过的 H5 入口。
|
||||
// 示例:https://game.example.com/
|
||||
// 注意:必须是 https 域名,不能是 localhost、IP 地址或未备案域名。
|
||||
const WEB_VIEW_ENTRY_URL = 'https://dev.genarrative.world/';
|
||||
const WEB_VIEW_ENTRY_URL = 'https://www.genarrative.world/';
|
||||
|
||||
// 中文注释:这里填写 Rust api-server 的公网 HTTPS 域名,必须在“小程序后台-开发设置-request 合法域名”中配置。
|
||||
// 如果 H5 和 API 同域,可保持和 WEB_VIEW_ENTRY_URL 同一个域名;请求路径会固定走 /api/auth/wechat/miniprogram-login。
|
||||
const API_BASE_URL = 'https://dev.genarrative.world/';
|
||||
const API_BASE_URL = 'https://www.genarrative.world/';
|
||||
|
||||
// 中文注释:这里填写微信小程序 AppID,用于后端记录会话来源;project.config.json 里的 appid 也要保持一致。
|
||||
const MINI_PROGRAM_APP_ID = 'wx3da23ea14ca66b65';
|
||||
|
||||
// 中文注释:按当前上传版本填写 develop / trial / release,后端会写入会话来源快照。
|
||||
const MINI_PROGRAM_ENV = 'develop';
|
||||
const MINI_PROGRAM_ENV = 'release';
|
||||
|
||||
// 中文注释:给 H5 加一个来源标记,便于后续前端或后端识别这是微信小程序 web-view 宿主。
|
||||
const WEB_VIEW_SOURCE_QUERY = {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/* global Page, wx */
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const {
|
||||
API_BASE_URL,
|
||||
MINI_PROGRAM_APP_ID,
|
||||
@@ -10,6 +13,8 @@ const MINI_PROGRAM_CLIENT_TYPE = 'mini_program';
|
||||
const MINI_PROGRAM_CLIENT_RUNTIME = 'wechat_mini_program';
|
||||
const CLIENT_INSTANCE_STORAGE_KEY = 'genarrative:mini-program-client-instance-id';
|
||||
const PAY_RESULT_STORAGE_KEY = 'genarrative:wechat-pay-result';
|
||||
const AUTH_RESULT_STORAGE_KEY = 'genarrative:mini-program-auth-result';
|
||||
const AUTH_ACTION_LOGIN = 'login';
|
||||
|
||||
function isConfiguredEntryUrl(value) {
|
||||
const trimmed = String(value || '').trim();
|
||||
@@ -57,6 +62,18 @@ function appendHashParams(url, params) {
|
||||
return `${baseUrl}#${rawHash}${separator}${pairs.join('&')}`;
|
||||
}
|
||||
|
||||
function parseBooleanQueryFlag(value) {
|
||||
return value === true || value === '1' || value === 'true' || value === 'yes';
|
||||
}
|
||||
|
||||
function shouldStartAuthFromQuery(query) {
|
||||
return String((query && query.authAction) || '').trim() === AUTH_ACTION_LOGIN;
|
||||
}
|
||||
|
||||
function shouldReturnToPreviousPage(query) {
|
||||
return String((query && query.returnTo) || '').trim() === 'previous';
|
||||
}
|
||||
|
||||
function resolveWebViewUrl(authResult) {
|
||||
const entryUrl = String(WEB_VIEW_ENTRY_URL || '').trim();
|
||||
if (!isConfiguredEntryUrl(entryUrl)) {
|
||||
@@ -75,6 +92,38 @@ function resolveWebViewUrl(authResult) {
|
||||
});
|
||||
}
|
||||
|
||||
function persistAuthResult(authResult) {
|
||||
wx.setStorageSync(AUTH_RESULT_STORAGE_KEY, JSON.stringify(authResult));
|
||||
}
|
||||
|
||||
function consumeAuthResult() {
|
||||
const rawValue = wx.getStorageSync(AUTH_RESULT_STORAGE_KEY);
|
||||
if (!rawValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
wx.removeStorageSync(AUTH_RESULT_STORAGE_KEY);
|
||||
try {
|
||||
const parsed = JSON.parse(String(rawValue));
|
||||
if (!parsed || typeof parsed !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const token = String(parsed.token || '').trim();
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
token,
|
||||
bindingStatus: String(parsed.bindingStatus || 'pending_bind_phone'),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[web-view] parse auth result failed', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getClientInstanceId() {
|
||||
const stored = wx.getStorageSync(CLIENT_INSTANCE_STORAGE_KEY);
|
||||
if (stored) {
|
||||
@@ -217,10 +266,12 @@ Page({
|
||||
errorMessage: '',
|
||||
loading: true,
|
||||
phoneBindingRequired: false,
|
||||
returnToPreviousPage: false,
|
||||
webViewUrl: '',
|
||||
},
|
||||
|
||||
async onLoad() {
|
||||
async onLoad(query = {}) {
|
||||
this._lastLaunchQuery = query;
|
||||
// 中文注释:web-view 只能打开已配置业务域名;未配置时展示本地提示,避免空白页误判。
|
||||
if (!isConfiguredEntryUrl(WEB_VIEW_ENTRY_URL)) {
|
||||
this.setData({
|
||||
@@ -231,6 +282,20 @@ Page({
|
||||
return;
|
||||
}
|
||||
|
||||
const forcedPhoneBinding = parseBooleanQueryFlag(query.phoneBindingRequired);
|
||||
const returnToPreviousPage = shouldReturnToPreviousPage(query);
|
||||
if (!shouldStartAuthFromQuery(query) && !forcedPhoneBinding) {
|
||||
this.setData({
|
||||
authResult: null,
|
||||
errorMessage: '',
|
||||
loading: false,
|
||||
phoneBindingRequired: false,
|
||||
returnToPreviousPage: false,
|
||||
webViewUrl: resolveWebViewUrl(null),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isConfiguredApiBaseUrl(API_BASE_URL)) {
|
||||
this.setData({
|
||||
errorMessage: '请先在 miniprogram/config.js 填写 API_BASE_URL。',
|
||||
@@ -240,6 +305,14 @@ Page({
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData({
|
||||
loading: true,
|
||||
phoneBindingRequired: false,
|
||||
returnToPreviousPage,
|
||||
errorMessage: '',
|
||||
webViewUrl: '',
|
||||
});
|
||||
|
||||
try {
|
||||
const authResult = await resolveAuthResult();
|
||||
if (authResult.bindingStatus === 'pending_bind_phone') {
|
||||
@@ -248,44 +321,56 @@ Page({
|
||||
errorMessage: '',
|
||||
loading: false,
|
||||
phoneBindingRequired: true,
|
||||
returnToPreviousPage,
|
||||
webViewUrl: '',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (returnToPreviousPage) {
|
||||
persistAuthResult(authResult);
|
||||
wx.navigateBack();
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData({
|
||||
authResult,
|
||||
errorMessage: '',
|
||||
loading: false,
|
||||
phoneBindingRequired: false,
|
||||
returnToPreviousPage,
|
||||
webViewUrl: resolveWebViewUrl(authResult),
|
||||
});
|
||||
} catch (error) {
|
||||
this.setData({
|
||||
authResult: null,
|
||||
errorMessage:
|
||||
error && error.message
|
||||
? error.message
|
||||
: '微信登录失败,请稍后重试。',
|
||||
error && error.message ? error.message : '微信登录失败,请稍后重试。',
|
||||
loading: false,
|
||||
phoneBindingRequired: false,
|
||||
returnToPreviousPage,
|
||||
webViewUrl: '',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
const result = wx.getStorageSync(PAY_RESULT_STORAGE_KEY);
|
||||
if (!result || !this.data.webViewUrl) {
|
||||
return;
|
||||
const authResult = consumeAuthResult();
|
||||
if (authResult) {
|
||||
this.setData({
|
||||
webViewUrl: resolveWebViewUrl(authResult),
|
||||
});
|
||||
}
|
||||
|
||||
wx.removeStorageSync(PAY_RESULT_STORAGE_KEY);
|
||||
this.setData({
|
||||
webViewUrl: appendHashParams(this.data.webViewUrl, {
|
||||
wx_pay_result: result,
|
||||
}),
|
||||
});
|
||||
const result = wx.getStorageSync(PAY_RESULT_STORAGE_KEY);
|
||||
if (result && this.data.webViewUrl) {
|
||||
wx.removeStorageSync(PAY_RESULT_STORAGE_KEY);
|
||||
this.setData({
|
||||
webViewUrl: appendHashParams(this.data.webViewUrl, {
|
||||
wx_pay_result: result,
|
||||
}),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async handleGetPhoneNumber(event) {
|
||||
@@ -318,6 +403,17 @@ Page({
|
||||
token: response.token,
|
||||
bindingStatus: 'active',
|
||||
};
|
||||
if (this.data.returnToPreviousPage) {
|
||||
persistAuthResult(nextAuthResult);
|
||||
this.setData({
|
||||
bindingPhone: false,
|
||||
errorMessage: '',
|
||||
loading: false,
|
||||
phoneBindingRequired: false,
|
||||
});
|
||||
wx.navigateBack();
|
||||
return;
|
||||
}
|
||||
this.setData({
|
||||
authResult: nextAuthResult,
|
||||
bindingPhone: false,
|
||||
@@ -344,9 +440,10 @@ Page({
|
||||
errorMessage: '',
|
||||
loading: true,
|
||||
phoneBindingRequired: false,
|
||||
returnToPreviousPage: false,
|
||||
webViewUrl: '',
|
||||
});
|
||||
this.onLoad();
|
||||
this.onLoad(this._lastLaunchQuery || { authAction: AUTH_ACTION_LOGIN });
|
||||
},
|
||||
|
||||
handleWebViewLoad(event) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<block wx:if="{{webViewUrl}}">
|
||||
<web-view
|
||||
id="genarrative-web-view"
|
||||
src="{{webViewUrl}}"
|
||||
bindload="handleWebViewLoad"
|
||||
binderror="handleWebViewError"
|
||||
@@ -15,10 +16,13 @@
|
||||
|
||||
<view wx:elif="{{phoneBindingRequired}}" class="setup-screen">
|
||||
<view class="setup-card">
|
||||
<view class="setup-title">绑定手机号</view>
|
||||
<view class="setup-title">登录</view>
|
||||
<view wx:if="{{errorMessage}}" class="setup-text setup-text--danger">
|
||||
{{errorMessage}}
|
||||
</view>
|
||||
<view wx:if="{{returnToPreviousPage}}" class="setup-text">
|
||||
登录完成后将自动返回。
|
||||
</view>
|
||||
<button
|
||||
class="retry-button"
|
||||
open-type="getPhoneNumber"
|
||||
@@ -26,7 +30,7 @@
|
||||
loading="{{bindingPhone}}"
|
||||
disabled="{{bindingPhone}}"
|
||||
>
|
||||
{{bindingPhone ? '正在绑定' : '微信授权手机号'}}
|
||||
{{bindingPhone ? '正在绑定' : '手机号快捷登录'}}
|
||||
</button>
|
||||
<button
|
||||
class="ghost-button"
|
||||
|
||||
BIN
output/generation-page-mobile-check.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
652
output/rpg-profile-test-report.json
Normal file
1061
package-lock.json
generated
@@ -57,7 +57,11 @@
|
||||
"check:data": "node scripts/run-tsx.cjs scripts/validate-content.ts",
|
||||
"check:overrides": "node scripts/run-tsx.cjs scripts/validate-overrides.ts",
|
||||
"check:smoke": "node scripts/run-tsx.cjs scripts/smoke-content.ts",
|
||||
"check:content": "npm run check:data && npm run check:overrides && npm run check:smoke"
|
||||
"check:content": "npm run check:data && npm run check:overrides && npm run check:smoke",
|
||||
"codegraph:init": "codegraph init -i .",
|
||||
"codegraph:index": "codegraph index .",
|
||||
"codegraph:sync": "codegraph sync .",
|
||||
"codegraph:status": "codegraph status ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
@@ -73,6 +77,7 @@
|
||||
"vite": "^6.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@colbymchenry/codegraph": "^0.8.0",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/node": "^22.14.0",
|
||||
|
||||
@@ -25,6 +25,13 @@ export type PublicUserSearchResponse = {
|
||||
user: PublicUserSummary;
|
||||
};
|
||||
|
||||
export type RuntimeGuestTokenResponse = {
|
||||
token: string;
|
||||
expiresAt: string;
|
||||
subject: string;
|
||||
scope: string;
|
||||
};
|
||||
|
||||
export type AuthEntryRequest = {
|
||||
phone: string;
|
||||
password: string;
|
||||
|
||||
@@ -1,24 +1,34 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import {
|
||||
BARK_BATTLE_ASSET_SLOTS,
|
||||
BARK_BATTLE_DIFFICULTY_PRESETS,
|
||||
type BarkBattleDraftConfig,
|
||||
type BarkBattleDraftConfigUpdateRequest,
|
||||
type BarkBattleFinishResponse,
|
||||
type BarkBattleGeneratedImageAsset,
|
||||
type BarkBattleImageAssetGenerateRequest,
|
||||
type BarkBattlePersonalBestSummary,
|
||||
type BarkBattleWorkStats,
|
||||
} from './barkBattle';
|
||||
|
||||
describe('Bark Battle shared contracts', () => {
|
||||
test('default draft config fixture uses normal difficulty and camelCase fields', () => {
|
||||
test('default draft config fixture uses normal difficulty and v1 description fields', () => {
|
||||
const draft: BarkBattleDraftConfig = {
|
||||
draftId: 'draft-bark-1',
|
||||
workId: 'work-bark-1',
|
||||
configVersion: 2,
|
||||
rulesetVersion: 'bark-battle-ruleset-v1',
|
||||
title: '汪汪声浪挑战',
|
||||
description: '轻配置草稿',
|
||||
themePreset: 'city-park',
|
||||
playerDogSkinPreset: 'corgi',
|
||||
opponentDogSkinPreset: 'husky',
|
||||
themeDescription: '傍晚城市公园里的声浪擂台',
|
||||
playerImageDescription: '戴红围巾的柯基主角',
|
||||
opponentImageDescription: '蓝色运动头带的哈士奇对手',
|
||||
onomatopoeia: ['轰汪!', '嗷呜!', '咚咚!'],
|
||||
playerCharacterImageSrc: '/generated-bark-battle/player/image.png',
|
||||
opponentCharacterImageSrc: 'https://example.test/opponent.png',
|
||||
uiBackgroundImageSrc: '/generated-bark-battle/ui/background.png',
|
||||
difficultyPreset: 'normal',
|
||||
leaderboardEnabled: true,
|
||||
updatedAt: '2026-05-13T03:00:00.000Z',
|
||||
};
|
||||
|
||||
@@ -26,15 +36,105 @@ describe('Bark Battle shared contracts', () => {
|
||||
expect(draft.difficultyPreset).toBe('normal');
|
||||
expect(Object.keys(draft)).toEqual([
|
||||
'draftId',
|
||||
'workId',
|
||||
'configVersion',
|
||||
'rulesetVersion',
|
||||
'title',
|
||||
'description',
|
||||
'themePreset',
|
||||
'playerDogSkinPreset',
|
||||
'opponentDogSkinPreset',
|
||||
'themeDescription',
|
||||
'playerImageDescription',
|
||||
'opponentImageDescription',
|
||||
'onomatopoeia',
|
||||
'playerCharacterImageSrc',
|
||||
'opponentCharacterImageSrc',
|
||||
'uiBackgroundImageSrc',
|
||||
'difficultyPreset',
|
||||
'leaderboardEnabled',
|
||||
'updatedAt',
|
||||
]);
|
||||
expect(draft.playerCharacterImageSrc).toContain('/generated-bark-battle/');
|
||||
expect('barkSoundSrc' in draft).toBe(false);
|
||||
expect('leaderboardEnabled' in draft).toBe(false);
|
||||
});
|
||||
|
||||
test('draft config update contract persists generated image slots only', () => {
|
||||
const update: BarkBattleDraftConfigUpdateRequest = {
|
||||
draftId: 'draft-bark-1',
|
||||
workId: 'BB-12345678',
|
||||
configVersion: 2,
|
||||
rulesetVersion: 'bark-battle-ruleset-v1',
|
||||
title: '汪汪声浪挑战',
|
||||
description: '轻配置草稿',
|
||||
themeDescription: '傍晚城市公园里的声浪擂台',
|
||||
playerImageDescription: '戴红围巾的柯基主角',
|
||||
opponentImageDescription: '蓝色运动头带的哈士奇对手',
|
||||
onomatopoeia: ['轰!', '燃起来!', '破阵!'],
|
||||
playerCharacterImageSrc: '/generated-bark-battle/player/image.png',
|
||||
opponentCharacterImageSrc: '/generated-bark-battle/opponent/image.png',
|
||||
uiBackgroundImageSrc: '/generated-bark-battle/ui/background.png',
|
||||
difficultyPreset: 'normal',
|
||||
};
|
||||
|
||||
expect(Object.keys(update)).toEqual([
|
||||
'draftId',
|
||||
'workId',
|
||||
'configVersion',
|
||||
'rulesetVersion',
|
||||
'title',
|
||||
'description',
|
||||
'themeDescription',
|
||||
'playerImageDescription',
|
||||
'opponentImageDescription',
|
||||
'onomatopoeia',
|
||||
'playerCharacterImageSrc',
|
||||
'opponentCharacterImageSrc',
|
||||
'uiBackgroundImageSrc',
|
||||
'difficultyPreset',
|
||||
]);
|
||||
expect('barkSoundSrc' in update).toBe(false);
|
||||
expect('leaderboardEnabled' in update).toBe(false);
|
||||
});
|
||||
|
||||
test('image generation contract uses dedicated Bark Battle slots and backend prompt result', () => {
|
||||
expect(BARK_BATTLE_ASSET_SLOTS).toEqual([
|
||||
'player-character',
|
||||
'opponent-character',
|
||||
'ui-background',
|
||||
]);
|
||||
|
||||
const request: BarkBattleImageAssetGenerateRequest = {
|
||||
slot: 'opponent-character',
|
||||
draftId: 'bark-battle-draft-1',
|
||||
config: {
|
||||
title: '汪汪冠军杯',
|
||||
description: '',
|
||||
themeDescription: '霓虹公园擂台',
|
||||
playerImageDescription: '红围巾柴犬',
|
||||
opponentImageDescription: '蓝头带哈士奇',
|
||||
onomatopoeia: ['轰汪!', '炸场!', '冲啊!'],
|
||||
difficultyPreset: 'normal',
|
||||
},
|
||||
};
|
||||
const response: BarkBattleGeneratedImageAsset = {
|
||||
imageSrc: '/generated-bark-battle-assets/draft/opponent/image.webp',
|
||||
assetId: 'asset-1',
|
||||
sourceType: 'generated',
|
||||
model: 'gpt-image-2',
|
||||
size: '1024*1024',
|
||||
taskId: 'task-1',
|
||||
prompt: '后端拼装后的对手形象 prompt',
|
||||
};
|
||||
|
||||
expect(JSON.parse(JSON.stringify(request))).toMatchObject({
|
||||
slot: 'opponent-character',
|
||||
config: {
|
||||
opponentImageDescription: '蓝头带哈士奇',
|
||||
onomatopoeia: ['轰汪!', '炸场!', '冲啊!'],
|
||||
},
|
||||
});
|
||||
expect(JSON.parse(JSON.stringify(response))).toMatchObject({
|
||||
imageSrc: '/generated-bark-battle-assets/draft/opponent/image.webp',
|
||||
prompt: '后端拼装后的对手形象 prompt',
|
||||
});
|
||||
});
|
||||
|
||||
test('finish accepted player_win fixture exposes backend adjudication result', () => {
|
||||
|
||||
@@ -16,24 +16,65 @@ export type BarkBattleFinishStatus =
|
||||
|
||||
export type BarkBattlePlayTypeId = 'bark-battle';
|
||||
|
||||
export interface BarkBattleConfigEditorPayload {
|
||||
export const BARK_BATTLE_ASSET_SLOTS = [
|
||||
'player-character',
|
||||
'opponent-character',
|
||||
'ui-background',
|
||||
] as const;
|
||||
|
||||
export type BarkBattleAssetSlot = (typeof BARK_BATTLE_ASSET_SLOTS)[number];
|
||||
|
||||
export interface BarkBattleReplacementConfig {
|
||||
playerCharacterImageSrc?: string;
|
||||
opponentCharacterImageSrc?: string;
|
||||
uiBackgroundImageSrc?: string;
|
||||
}
|
||||
|
||||
export type BarkBattleOnomatopoeia = string[];
|
||||
|
||||
export interface BarkBattleConfigEditorPayload extends BarkBattleReplacementConfig {
|
||||
title: string;
|
||||
description?: string;
|
||||
themePreset: string;
|
||||
playerDogSkinPreset: string;
|
||||
opponentDogSkinPreset: string;
|
||||
themeDescription: string;
|
||||
playerImageDescription: string;
|
||||
opponentImageDescription: string;
|
||||
onomatopoeia?: BarkBattleOnomatopoeia;
|
||||
difficultyPreset: BarkBattleDifficultyPreset;
|
||||
leaderboardEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface BarkBattleDraftCreateRequest extends BarkBattleConfigEditorPayload {}
|
||||
|
||||
export interface BarkBattleDraftConfigUpdateRequest
|
||||
extends BarkBattleConfigEditorPayload {
|
||||
draftId: string;
|
||||
workId?: string | null;
|
||||
configVersion?: number;
|
||||
rulesetVersion?: string;
|
||||
}
|
||||
|
||||
export interface BarkBattleWorkPublishRequest {
|
||||
draftId: string;
|
||||
workId?: string;
|
||||
workId: string;
|
||||
publishedSnapshot?: BarkBattleConfigEditorPayload;
|
||||
}
|
||||
|
||||
export interface BarkBattleImageAssetGenerateRequest {
|
||||
slot: BarkBattleAssetSlot;
|
||||
draftId?: string | null;
|
||||
config: BarkBattleConfigEditorPayload;
|
||||
}
|
||||
|
||||
export interface BarkBattleGeneratedImageAsset {
|
||||
imageSrc: string;
|
||||
assetId: string;
|
||||
sourceType?: 'generated' | string;
|
||||
model: string;
|
||||
size: string;
|
||||
taskId: string;
|
||||
prompt: string;
|
||||
actualPrompt?: string;
|
||||
}
|
||||
|
||||
export interface BarkBattleDraftConfig extends BarkBattleConfigEditorPayload {
|
||||
draftId: string;
|
||||
workId?: string;
|
||||
@@ -50,15 +91,62 @@ export interface BarkBattlePublishedConfig {
|
||||
playTypeId: BarkBattlePlayTypeId;
|
||||
title: string;
|
||||
description?: string;
|
||||
themePreset: string;
|
||||
playerDogSkinPreset: string;
|
||||
opponentDogSkinPreset: string;
|
||||
themeDescription: string;
|
||||
playerImageDescription: string;
|
||||
opponentImageDescription: string;
|
||||
onomatopoeia?: BarkBattleOnomatopoeia;
|
||||
playerCharacterImageSrc?: string;
|
||||
opponentCharacterImageSrc?: string;
|
||||
uiBackgroundImageSrc?: string;
|
||||
difficultyPreset: BarkBattleDifficultyPreset;
|
||||
leaderboardEnabled: boolean;
|
||||
updatedAt: string;
|
||||
publishedAt: string;
|
||||
}
|
||||
|
||||
export type BarkBattleWorkStatus = 'draft' | 'published';
|
||||
|
||||
export type BarkBattleGenerationStatus =
|
||||
| 'pending_assets'
|
||||
| 'ready'
|
||||
| 'partial_failed'
|
||||
| string;
|
||||
|
||||
export interface BarkBattleWorkSummary {
|
||||
workId: string;
|
||||
draftId?: string | null;
|
||||
ownerUserId: string;
|
||||
authorDisplayName: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
themeDescription: string;
|
||||
playerImageDescription: string;
|
||||
opponentImageDescription: string;
|
||||
onomatopoeia?: BarkBattleOnomatopoeia;
|
||||
playerCharacterImageSrc?: string | null;
|
||||
opponentCharacterImageSrc?: string | null;
|
||||
uiBackgroundImageSrc?: string | null;
|
||||
difficultyPreset: BarkBattleDifficultyPreset;
|
||||
status: BarkBattleWorkStatus;
|
||||
generationStatus?: BarkBattleGenerationStatus | null;
|
||||
publishReady: boolean;
|
||||
playCount: number;
|
||||
finishCount?: number;
|
||||
winCount?: number;
|
||||
drawCount?: number;
|
||||
lossCount?: number;
|
||||
recentPlayCount7d?: number;
|
||||
updatedAt: string;
|
||||
publishedAt?: string | null;
|
||||
}
|
||||
|
||||
export interface BarkBattleWorksResponse {
|
||||
items: BarkBattleWorkSummary[];
|
||||
}
|
||||
|
||||
export interface BarkBattleWorkDetailResponse {
|
||||
item: BarkBattleWorkSummary;
|
||||
}
|
||||
|
||||
export interface BarkBattleRuntimeConfig {
|
||||
workId: string;
|
||||
configVersion: number;
|
||||
@@ -70,10 +158,13 @@ export interface BarkBattleRuntimeConfig {
|
||||
drawThreshold: number;
|
||||
minBarkGapMs: number;
|
||||
difficultyPreset: BarkBattleDifficultyPreset;
|
||||
themePreset: string;
|
||||
playerDogSkinPreset: string;
|
||||
opponentDogSkinPreset: string;
|
||||
leaderboardEnabled: boolean;
|
||||
themeDescription: string;
|
||||
playerImageDescription: string;
|
||||
opponentImageDescription: string;
|
||||
onomatopoeia?: BarkBattleOnomatopoeia;
|
||||
playerCharacterImageSrc?: string;
|
||||
opponentCharacterImageSrc?: string;
|
||||
uiBackgroundImageSrc?: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,12 @@ export interface PublishGeneratedAudioAssetRequest {
|
||||
slot: string;
|
||||
assetKind: string;
|
||||
profileId?: string | null;
|
||||
storagePrefix?: 'puzzle_assets' | 'match3d_assets' | 'custom_world_scenes' | null;
|
||||
storagePrefix?:
|
||||
| 'puzzle_assets'
|
||||
| 'match3d_assets'
|
||||
| 'wooden_fish_assets'
|
||||
| 'custom_world_scenes'
|
||||
| null;
|
||||
}
|
||||
|
||||
export interface GeneratedAudioAssetResponse {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
export type * from './creativeAgent';
|
||||
export type * from './creationAudio';
|
||||
export type * from './hyper3d';
|
||||
export type * from './jumpHop';
|
||||
export type * from './puzzleCreativeTemplate';
|
||||
export type * from './publicWork';
|
||||
export type * from './visualNovel';
|
||||
export type * from './barkBattle';
|
||||
export type * from './woodenFish';
|
||||
|
||||
267
packages/shared/src/contracts/jumpHop.ts
Normal file
@@ -0,0 +1,267 @@
|
||||
export type JumpHopDifficulty = 'easy' | 'standard' | 'advanced' | 'challenge';
|
||||
|
||||
export type JumpHopStylePreset =
|
||||
| 'minimal-blocks'
|
||||
| 'paper-toy'
|
||||
| 'neon-glass'
|
||||
| 'forest-stone'
|
||||
| 'future-metal'
|
||||
| 'custom';
|
||||
|
||||
export type JumpHopGenerationStatus =
|
||||
| 'draft'
|
||||
| 'generating'
|
||||
| 'ready'
|
||||
| 'failed';
|
||||
|
||||
export type JumpHopTileType =
|
||||
| 'start'
|
||||
| 'normal'
|
||||
| 'target'
|
||||
| 'finish'
|
||||
| 'bonus'
|
||||
| 'accent';
|
||||
|
||||
export type JumpHopActionType =
|
||||
| 'compile-draft'
|
||||
| 'regenerate-character'
|
||||
| 'regenerate-tiles'
|
||||
| 'update-work-meta'
|
||||
| 'update-difficulty';
|
||||
|
||||
export type JumpHopRunStatus = 'playing' | 'failed' | 'cleared';
|
||||
|
||||
export type JumpHopJumpResult = 'miss' | 'hit' | 'perfect' | 'finish';
|
||||
|
||||
export interface JumpHopWorkspaceCreateRequest {
|
||||
templateId: string;
|
||||
workTitle: string;
|
||||
workDescription: string;
|
||||
themeTags: string[];
|
||||
difficulty: JumpHopDifficulty;
|
||||
stylePreset: JumpHopStylePreset;
|
||||
characterPrompt: string;
|
||||
tilePrompt: string;
|
||||
endMoodPrompt?: string | null;
|
||||
}
|
||||
|
||||
export interface JumpHopActionRequest {
|
||||
actionType: JumpHopActionType;
|
||||
profileId?: string | null;
|
||||
workTitle?: string | null;
|
||||
workDescription?: string | null;
|
||||
themeTags?: string[] | null;
|
||||
difficulty?: JumpHopDifficulty | null;
|
||||
stylePreset?: JumpHopStylePreset | null;
|
||||
characterPrompt?: string | null;
|
||||
tilePrompt?: string | null;
|
||||
endMoodPrompt?: string | null;
|
||||
characterAsset?: JumpHopCharacterAsset | null;
|
||||
tileAtlasAsset?: JumpHopCharacterAsset | null;
|
||||
tileAssets?: JumpHopTileAsset[] | null;
|
||||
coverComposite?: string | null;
|
||||
}
|
||||
|
||||
export interface JumpHopCharacterAsset {
|
||||
assetId: string;
|
||||
imageSrc: string;
|
||||
imageObjectKey: string;
|
||||
assetObjectId: string;
|
||||
generationProvider: string;
|
||||
prompt: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface JumpHopTileAsset {
|
||||
tileType: JumpHopTileType;
|
||||
imageSrc: string;
|
||||
imageObjectKey: string;
|
||||
assetObjectId: string;
|
||||
sourceAtlasCell: string;
|
||||
visualWidth: number;
|
||||
visualHeight: number;
|
||||
topSurfaceRadius: number;
|
||||
landingRadius: number;
|
||||
}
|
||||
|
||||
export interface JumpHopScoring {
|
||||
chargeToDistanceRatio: number;
|
||||
maxChargeMs: number;
|
||||
hitBonus: number;
|
||||
perfectBonus: number;
|
||||
}
|
||||
|
||||
export interface JumpHopPlatform {
|
||||
platformId: string;
|
||||
tileType: JumpHopTileType;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
landingRadius: number;
|
||||
perfectRadius: number;
|
||||
scoreValue: number;
|
||||
}
|
||||
|
||||
export interface JumpHopPath {
|
||||
seed: string;
|
||||
difficulty: JumpHopDifficulty;
|
||||
platforms: JumpHopPlatform[];
|
||||
finishIndex: number;
|
||||
cameraPreset: string;
|
||||
scoring: JumpHopScoring;
|
||||
}
|
||||
|
||||
export interface JumpHopLastJump {
|
||||
chargeMs: number;
|
||||
jumpDistance: number;
|
||||
targetPlatformIndex: number;
|
||||
landedX: number;
|
||||
landedY: number;
|
||||
result: JumpHopJumpResult;
|
||||
}
|
||||
|
||||
export interface JumpHopDraftResponse {
|
||||
templateId: string;
|
||||
templateName: string;
|
||||
profileId: string | null;
|
||||
workTitle: string;
|
||||
workDescription: string;
|
||||
themeTags: string[];
|
||||
difficulty: JumpHopDifficulty;
|
||||
stylePreset: JumpHopStylePreset;
|
||||
characterPrompt: string;
|
||||
tilePrompt: string;
|
||||
endMoodPrompt: string | null;
|
||||
characterAsset: JumpHopCharacterAsset | null;
|
||||
tileAtlasAsset: JumpHopCharacterAsset | null;
|
||||
tileAssets: JumpHopTileAsset[];
|
||||
path: JumpHopPath | null;
|
||||
coverComposite: string | null;
|
||||
generationStatus: JumpHopGenerationStatus;
|
||||
}
|
||||
|
||||
export interface JumpHopSessionSnapshotResponse {
|
||||
sessionId: string;
|
||||
ownerUserId: string;
|
||||
status: JumpHopGenerationStatus;
|
||||
draft: JumpHopDraftResponse | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface JumpHopSessionResponse {
|
||||
session: JumpHopSessionSnapshotResponse;
|
||||
}
|
||||
|
||||
export interface JumpHopActionResponse {
|
||||
actionType: JumpHopActionType;
|
||||
session: JumpHopSessionSnapshotResponse;
|
||||
work: JumpHopWorkProfileResponse | null;
|
||||
}
|
||||
|
||||
export interface JumpHopWorkSummaryResponse {
|
||||
runtimeKind: 'jump-hop';
|
||||
workId: string;
|
||||
profileId: string;
|
||||
ownerUserId: string;
|
||||
sourceSessionId: string | null;
|
||||
workTitle: string;
|
||||
workDescription: string;
|
||||
themeTags: string[];
|
||||
difficulty: JumpHopDifficulty;
|
||||
stylePreset: JumpHopStylePreset;
|
||||
coverImageSrc: string | null;
|
||||
publicationStatus: string;
|
||||
playCount: number;
|
||||
updatedAt: string;
|
||||
publishedAt: string | null;
|
||||
publishReady: boolean;
|
||||
generationStatus: JumpHopGenerationStatus;
|
||||
}
|
||||
|
||||
export interface JumpHopWorkProfileResponse {
|
||||
summary: JumpHopWorkSummaryResponse;
|
||||
draft: JumpHopDraftResponse;
|
||||
path: JumpHopPath;
|
||||
characterAsset: JumpHopCharacterAsset;
|
||||
tileAtlasAsset: JumpHopCharacterAsset;
|
||||
tileAssets: JumpHopTileAsset[];
|
||||
}
|
||||
|
||||
export interface JumpHopWorksResponse {
|
||||
items: JumpHopWorkSummaryResponse[];
|
||||
}
|
||||
|
||||
export interface JumpHopWorkDetailResponse {
|
||||
item: JumpHopWorkProfileResponse;
|
||||
}
|
||||
|
||||
export interface JumpHopWorkMutationResponse {
|
||||
item: JumpHopWorkProfileResponse;
|
||||
}
|
||||
|
||||
export interface JumpHopGalleryCardResponse {
|
||||
publicWorkCode: string;
|
||||
workId: string;
|
||||
profileId: string;
|
||||
ownerUserId: string;
|
||||
authorDisplayName: string;
|
||||
workTitle: string;
|
||||
workDescription: string;
|
||||
coverImageSrc: string | null;
|
||||
themeTags: string[];
|
||||
difficulty: JumpHopDifficulty;
|
||||
stylePreset: JumpHopStylePreset;
|
||||
publicationStatus: string;
|
||||
playCount: number;
|
||||
updatedAt: string;
|
||||
publishedAt: string | null;
|
||||
generationStatus: JumpHopGenerationStatus;
|
||||
}
|
||||
|
||||
export interface JumpHopGalleryResponse {
|
||||
items: JumpHopGalleryCardResponse[];
|
||||
hasMore: boolean;
|
||||
nextCursor: string | null;
|
||||
}
|
||||
|
||||
export interface JumpHopGalleryDetailResponse {
|
||||
item: JumpHopWorkProfileResponse;
|
||||
}
|
||||
|
||||
export interface JumpHopRuntimeRunSnapshotResponse {
|
||||
runId: string;
|
||||
profileId: string;
|
||||
ownerUserId: string;
|
||||
status: JumpHopRunStatus;
|
||||
currentPlatformIndex: number;
|
||||
score: number;
|
||||
combo: number;
|
||||
path: JumpHopPath;
|
||||
lastJump: JumpHopLastJump | null;
|
||||
startedAtMs: number;
|
||||
finishedAtMs: number | null;
|
||||
}
|
||||
|
||||
export interface JumpHopRunResponse {
|
||||
run: JumpHopRuntimeRunSnapshotResponse;
|
||||
}
|
||||
|
||||
export interface JumpHopStartRunRequest {
|
||||
profileId: string;
|
||||
}
|
||||
|
||||
export interface JumpHopJumpRequest {
|
||||
chargeMs: number;
|
||||
clientEventId: string;
|
||||
}
|
||||
|
||||
export interface JumpHopRestartRunRequest {
|
||||
clientActionId: string;
|
||||
}
|
||||
|
||||
export interface JumpHopJumpResponse {
|
||||
run: JumpHopRuntimeRunSnapshotResponse;
|
||||
}
|
||||
@@ -19,8 +19,17 @@ export type Match3DGeneratedItemSize = '大' | '中' | '小' | string;
|
||||
|
||||
export interface Match3DGeneratedBackgroundAsset {
|
||||
prompt: string;
|
||||
levelScenePrompt?: string | null;
|
||||
levelSceneImageSrc?: string | null;
|
||||
levelSceneImageObjectKey?: string | null;
|
||||
imageSrc?: string | null;
|
||||
imageObjectKey?: string | null;
|
||||
uiSpritesheetPrompt?: string | null;
|
||||
uiSpritesheetImageSrc?: string | null;
|
||||
uiSpritesheetImageObjectKey?: string | null;
|
||||
itemSpritesheetPrompt?: string | null;
|
||||
itemSpritesheetImageSrc?: string | null;
|
||||
itemSpritesheetImageObjectKey?: string | null;
|
||||
containerPrompt?: string | null;
|
||||
containerImageSrc?: string | null;
|
||||
containerImageObjectKey?: string | null;
|
||||
|
||||
38
packages/shared/src/contracts/publicWork.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
export interface PublicWorkGalleryEntryResponse {
|
||||
sourceType: string;
|
||||
workId: string;
|
||||
profileId: string;
|
||||
sourceSessionId?: string | null;
|
||||
publicWorkCode: string;
|
||||
ownerUserId: string;
|
||||
authorDisplayName: string;
|
||||
worldName: string;
|
||||
subtitle: string;
|
||||
summaryText: string;
|
||||
coverImageSrc?: string | null;
|
||||
coverAssetId?: string | null;
|
||||
themeTags: string[];
|
||||
playCount: number;
|
||||
remixCount: number;
|
||||
likeCount: number;
|
||||
recentPlayCount7d?: number;
|
||||
publishedAt?: string | null;
|
||||
updatedAt: string;
|
||||
sortTimeMicros: number;
|
||||
}
|
||||
|
||||
export interface PublicWorkDetailEntryResponse
|
||||
extends PublicWorkGalleryEntryResponse {
|
||||
detailPayloadJson: string;
|
||||
}
|
||||
|
||||
export interface PublicWorkGalleryResponse {
|
||||
items: PublicWorkGalleryEntryResponse[];
|
||||
hasMore?: boolean;
|
||||
nextCursor?: string | null;
|
||||
totalCount?: number;
|
||||
}
|
||||
|
||||
export interface PublicWorkDetailResponse {
|
||||
item: PublicWorkDetailEntryResponse;
|
||||
}
|
||||
@@ -52,6 +52,8 @@ export type PuzzleAgentActionRequest =
|
||||
pictureDescription?: string;
|
||||
referenceImageSrc?: string | null;
|
||||
referenceImageSrcs?: string[];
|
||||
referenceImageAssetObjectId?: string | null;
|
||||
referenceImageAssetObjectIds?: string[];
|
||||
imageModel?: string | null;
|
||||
aiRedraw?: boolean;
|
||||
}
|
||||
@@ -63,6 +65,8 @@ export type PuzzleAgentActionRequest =
|
||||
pictureDescription?: string;
|
||||
referenceImageSrc?: string | null;
|
||||
referenceImageSrcs?: string[];
|
||||
referenceImageAssetObjectId?: string | null;
|
||||
referenceImageAssetObjectIds?: string[];
|
||||
imageModel?: string | null;
|
||||
aiRedraw?: boolean;
|
||||
candidateCount?: number;
|
||||
@@ -73,6 +77,8 @@ export type PuzzleAgentActionRequest =
|
||||
promptText?: string | null;
|
||||
referenceImageSrc?: string | null;
|
||||
referenceImageSrcs?: string[];
|
||||
referenceImageAssetObjectId?: string | null;
|
||||
referenceImageAssetObjectIds?: string[];
|
||||
imageModel?: string | null;
|
||||
aiRedraw?: boolean;
|
||||
candidateCount?: number;
|
||||
|
||||
@@ -51,6 +51,12 @@ export interface PuzzleDraftLevel {
|
||||
uiBackgroundPrompt?: string | null;
|
||||
uiBackgroundImageSrc?: string | null;
|
||||
uiBackgroundImageObjectKey?: string | null;
|
||||
levelSceneImageSrc?: string | null;
|
||||
levelSceneImageObjectKey?: string | null;
|
||||
uiSpritesheetImageSrc?: string | null;
|
||||
uiSpritesheetImageObjectKey?: string | null;
|
||||
levelBackgroundImageSrc?: string | null;
|
||||
levelBackgroundImageObjectKey?: string | null;
|
||||
backgroundMusic?: CreationAudioAsset | null;
|
||||
candidates: PuzzleGeneratedImageCandidate[];
|
||||
selectedCandidateId: string | null;
|
||||
|
||||
@@ -51,6 +51,8 @@ export interface CreatePuzzleAgentSessionRequest {
|
||||
pictureDescription?: string;
|
||||
referenceImageSrc?: string | null;
|
||||
referenceImageSrcs?: string[];
|
||||
referenceImageAssetObjectId?: string | null;
|
||||
referenceImageAssetObjectIds?: string[];
|
||||
imageModel?: string | null;
|
||||
aiRedraw?: boolean;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,10 @@ export interface PuzzleRuntimeLevelSnapshot {
|
||||
coverImageSrc: string | null;
|
||||
uiBackgroundImageSrc?: string | null;
|
||||
uiBackgroundImageObjectKey?: string | null;
|
||||
levelBackgroundImageSrc?: string | null;
|
||||
levelBackgroundImageObjectKey?: string | null;
|
||||
uiSpritesheetImageSrc?: string | null;
|
||||
uiSpritesheetImageObjectKey?: string | null;
|
||||
backgroundMusic?: CreationAudioAsset | null;
|
||||
board: PuzzleBoardSnapshot;
|
||||
status: PuzzleRuntimeLevelStatus;
|
||||
@@ -132,6 +136,7 @@ export interface DragPuzzlePieceRequest {
|
||||
|
||||
export interface AdvancePuzzleNextLevelRequest {
|
||||
targetProfileId?: string | null;
|
||||
preferSimilarWork?: boolean;
|
||||
}
|
||||
|
||||
export interface UsePuzzleRuntimePropRequest {
|
||||
|
||||
206
packages/shared/src/contracts/woodenFish.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
export type WoodenFishGenerationStatus =
|
||||
| 'draft'
|
||||
| 'generating'
|
||||
| 'ready'
|
||||
| 'failed';
|
||||
|
||||
export type WoodenFishActionType =
|
||||
| 'compile-draft'
|
||||
| 'regenerate-hit-object'
|
||||
| 'generate-hit-sound'
|
||||
| 'replace-hit-sound'
|
||||
| 'update-work-meta'
|
||||
| 'update-floating-words';
|
||||
|
||||
export type WoodenFishRunStatus = 'playing' | 'finished';
|
||||
|
||||
export interface WoodenFishImageAsset {
|
||||
assetId: string;
|
||||
imageSrc: string;
|
||||
imageObjectKey: string;
|
||||
assetObjectId: string;
|
||||
generationProvider: string;
|
||||
prompt: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface WoodenFishAudioAsset {
|
||||
assetId: string;
|
||||
audioSrc: string;
|
||||
audioObjectKey: string;
|
||||
assetObjectId: string;
|
||||
source: string;
|
||||
prompt?: string | null;
|
||||
durationMs?: number | null;
|
||||
}
|
||||
|
||||
export interface WoodenFishWorkspaceCreateRequest {
|
||||
templateId: string;
|
||||
workTitle: string;
|
||||
workDescription: string;
|
||||
themeTags: string[];
|
||||
hitObjectPrompt: string;
|
||||
hitObjectReferenceImageSrc?: string | null;
|
||||
hitSoundPrompt?: string | null;
|
||||
hitSoundAsset?: WoodenFishAudioAsset | null;
|
||||
floatingWords: string[];
|
||||
}
|
||||
|
||||
export interface WoodenFishActionRequest {
|
||||
actionType: WoodenFishActionType;
|
||||
profileId?: string | null;
|
||||
workTitle?: string | null;
|
||||
workDescription?: string | null;
|
||||
themeTags?: string[] | null;
|
||||
hitObjectPrompt?: string | null;
|
||||
hitObjectReferenceImageSrc?: string | null;
|
||||
hitObjectAsset?: WoodenFishImageAsset | null;
|
||||
backgroundAsset?: WoodenFishImageAsset | null;
|
||||
backButtonAsset?: WoodenFishImageAsset | null;
|
||||
hitSoundPrompt?: string | null;
|
||||
hitSoundAsset?: WoodenFishAudioAsset | null;
|
||||
floatingWords?: string[] | null;
|
||||
}
|
||||
|
||||
export interface WoodenFishWordCounter {
|
||||
text: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface WoodenFishDraftResponse {
|
||||
templateId: string;
|
||||
templateName: string;
|
||||
profileId: string | null;
|
||||
workTitle: string;
|
||||
workDescription: string;
|
||||
themeTags: string[];
|
||||
hitObjectPrompt: string;
|
||||
hitObjectReferenceImageSrc: string | null;
|
||||
hitSoundPrompt: string | null;
|
||||
floatingWords: string[];
|
||||
hitObjectAsset: WoodenFishImageAsset | null;
|
||||
backgroundAsset: WoodenFishImageAsset | null;
|
||||
backButtonAsset?: WoodenFishImageAsset | null;
|
||||
hitSoundAsset: WoodenFishAudioAsset | null;
|
||||
coverImageSrc: string | null;
|
||||
generationStatus: WoodenFishGenerationStatus;
|
||||
}
|
||||
|
||||
export interface WoodenFishSessionSnapshotResponse {
|
||||
sessionId: string;
|
||||
ownerUserId: string;
|
||||
status: WoodenFishGenerationStatus;
|
||||
draft: WoodenFishDraftResponse | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface WoodenFishSessionResponse {
|
||||
session: WoodenFishSessionSnapshotResponse;
|
||||
}
|
||||
|
||||
export interface WoodenFishActionResponse {
|
||||
actionType: WoodenFishActionType;
|
||||
session: WoodenFishSessionSnapshotResponse;
|
||||
work: WoodenFishWorkProfileResponse | null;
|
||||
}
|
||||
|
||||
export interface WoodenFishWorkSummaryResponse {
|
||||
runtimeKind: 'wooden-fish';
|
||||
workId: string;
|
||||
profileId: string;
|
||||
ownerUserId: string;
|
||||
sourceSessionId: string | null;
|
||||
workTitle: string;
|
||||
workDescription: string;
|
||||
themeTags: string[];
|
||||
coverImageSrc: string | null;
|
||||
publicationStatus: string;
|
||||
playCount: number;
|
||||
updatedAt: string;
|
||||
publishedAt: string | null;
|
||||
publishReady: boolean;
|
||||
generationStatus: WoodenFishGenerationStatus;
|
||||
}
|
||||
|
||||
export interface WoodenFishWorkProfileResponse {
|
||||
summary: WoodenFishWorkSummaryResponse;
|
||||
draft: WoodenFishDraftResponse;
|
||||
hitObjectAsset: WoodenFishImageAsset;
|
||||
backgroundAsset: WoodenFishImageAsset | null;
|
||||
backButtonAsset: WoodenFishImageAsset | null;
|
||||
hitSoundAsset: WoodenFishAudioAsset;
|
||||
floatingWords: string[];
|
||||
}
|
||||
|
||||
export interface WoodenFishWorksResponse {
|
||||
items: WoodenFishWorkSummaryResponse[];
|
||||
}
|
||||
|
||||
export interface WoodenFishWorkDetailResponse {
|
||||
item: WoodenFishWorkProfileResponse;
|
||||
}
|
||||
|
||||
export interface WoodenFishWorkMutationResponse {
|
||||
item: WoodenFishWorkProfileResponse;
|
||||
}
|
||||
|
||||
export interface WoodenFishGalleryCardResponse {
|
||||
publicWorkCode: string;
|
||||
workId: string;
|
||||
profileId: string;
|
||||
ownerUserId: string;
|
||||
authorDisplayName: string;
|
||||
workTitle: string;
|
||||
workDescription: string;
|
||||
coverImageSrc: string | null;
|
||||
themeTags: string[];
|
||||
publicationStatus: string;
|
||||
playCount: number;
|
||||
updatedAt: string;
|
||||
publishedAt: string | null;
|
||||
generationStatus: WoodenFishGenerationStatus;
|
||||
}
|
||||
|
||||
export interface WoodenFishGalleryResponse {
|
||||
items: WoodenFishGalleryCardResponse[];
|
||||
hasMore: boolean;
|
||||
nextCursor: string | null;
|
||||
}
|
||||
|
||||
export interface WoodenFishGalleryDetailResponse {
|
||||
item: WoodenFishWorkProfileResponse;
|
||||
}
|
||||
|
||||
export interface WoodenFishRuntimeRunSnapshotResponse {
|
||||
runId: string;
|
||||
profileId: string;
|
||||
ownerUserId: string;
|
||||
status: WoodenFishRunStatus;
|
||||
totalTapCount: number;
|
||||
wordCounters: WoodenFishWordCounter[];
|
||||
startedAtMs: number;
|
||||
updatedAtMs: number;
|
||||
finishedAtMs: number | null;
|
||||
}
|
||||
|
||||
export interface WoodenFishRunResponse {
|
||||
run: WoodenFishRuntimeRunSnapshotResponse;
|
||||
}
|
||||
|
||||
export interface WoodenFishStartRunRequest {
|
||||
profileId: string;
|
||||
}
|
||||
|
||||
export interface WoodenFishCheckpointRunRequest {
|
||||
totalTapCount: number;
|
||||
wordCounters: WoodenFishWordCounter[];
|
||||
clientEventId: string;
|
||||
}
|
||||
|
||||
export interface WoodenFishFinishRunRequest {
|
||||
totalTapCount: number;
|
||||
wordCounters: WoodenFishWordCounter[];
|
||||
clientEventId: string;
|
||||
}
|
||||
|
After Width: | Height: | Size: 791 KiB |
|
After Width: | Height: | Size: 764 KiB |
|
After Width: | Height: | Size: 774 KiB |
|
After Width: | Height: | Size: 812 KiB |
|
After Width: | Height: | Size: 770 KiB |
|
After Width: | Height: | Size: 978 KiB |
|
After Width: | Height: | Size: 734 KiB |
|
After Width: | Height: | Size: 801 KiB |
|
After Width: | Height: | Size: 520 KiB |
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"model": "gpt-image-2",
|
||||
"endpoint": "/v1/images/edits",
|
||||
"size": "1024x1024",
|
||||
"referenceImage": "public\\branding\\taonier-logo-peeking-head-jar-new-animals-concepts\\taonier-peeking-head-jar-new-animals-01-capybara.png",
|
||||
"generatedAt": "2026-05-18T06:24:38.693Z",
|
||||
"logoSkillSummary": {
|
||||
"requiredReview": "visual inspection, 32px readability, black-white viability",
|
||||
"outputStatus": "AI concept only; final logo needs vector cleanup"
|
||||
},
|
||||
"brief": {
|
||||
"brand": "陶泥儿",
|
||||
"source": "基于 peeking-head-jar-new-animals 批次 01 水豚头参考图继续收敛",
|
||||
"logoType": "symbol/icon-only mascot mark, no wordmark",
|
||||
"product": "AI UGC 轻休闲小游戏创作与传播平台,用户把脑洞、梗和灵感塑造成可分享的作品",
|
||||
"keep": [
|
||||
"陶罐容器为主形体",
|
||||
"水豚式半圆脑袋只露到眼睛位置",
|
||||
"两只纯黑点眼,无高光",
|
||||
"小圆耳朵与平静亲和感",
|
||||
"中心构图与 32px 可读性"
|
||||
],
|
||||
"explore": [
|
||||
"不同罐子颜色",
|
||||
"不同动物头色彩浓度",
|
||||
"更扁平、更抽象、更商标化",
|
||||
"更强黑白轮廓",
|
||||
"减少插画感、渐变感和材质细节"
|
||||
],
|
||||
"avoid": [
|
||||
"中文或英文字",
|
||||
"鼻子、嘴巴、腮红、表情高光",
|
||||
"罐子表情",
|
||||
"星星、闪光、手、陶艺工具",
|
||||
"甜点、面包、巧克力、糖果、布丁、餐具感",
|
||||
"完整动物身体、爪子、复杂场景",
|
||||
"贴纸感、儿童玩具感、写实陶瓷质感"
|
||||
]
|
||||
},
|
||||
"variants": [
|
||||
{
|
||||
"id": "01-flat-terracotta",
|
||||
"title": "扁平陶橙",
|
||||
"file": "taonier-capybara-jar-ref01-logo-refine-01-flat-terracotta.png",
|
||||
"prompt": "Use the uploaded reference image as the composition and pose reference. Keep the same core idea: a ceramic jar container with a capybara-like small animal peeking out only to eye level.\nCreate a refined icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nPreserve the reference structure: the jar is the main brand shape; the animal shows only small rounded ears, the upper half-dome head, and two matte black dot eyes above the jar rim.\nDo not show nose, mouth, cheek blush, teeth, paws, body, lower face, or a full animal head. The eyes must be pure black dots with no highlights, no white sparkle, and no glossy pupils.\nThe jar itself must have no face, no expression, no eyes, no smile, and no decorative emoji marks.\nMake the result more like a trademark and logo than an illustration: simplified silhouette, clean vector curves, broad flat color fields, fewer gradients, fewer soft shadows, no realistic ceramic rendering.\nKeep a warm ceramic-container feeling through color and silhouette only. The jar should not look like a bowl of food, cup, dessert package, pudding cup, bread, bun, chocolate, candy, jelly, or pastry.\nStyle target: premium friendly mascot-symbol logo, all-age and women-friendly, calm but memorable, suitable for app icon, social avatar, and trademark review.\nComposition: centered on a clean light background with generous safe area. Strong readable silhouette first. It must still read at 32px and in black and white.\nAvoid star, spark, halo, magic wand, hand, pottery tool, UI, border, sticker outline, background scene, and watermark.\nVariant focus: the most direct logo refinement. Use flat terracotta jar, warm caramel capybara head, minimal rim shadow, almost no gradients. Make the jar silhouette slightly more iconic and compact."
|
||||
},
|
||||
{
|
||||
"id": "02-cream-cocoa",
|
||||
"title": "奶白可可",
|
||||
"file": "taonier-capybara-jar-ref01-logo-refine-02-cream-cocoa.png",
|
||||
"prompt": "Use the uploaded reference image as the composition and pose reference. Keep the same core idea: a ceramic jar container with a capybara-like small animal peeking out only to eye level.\nCreate a refined icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nPreserve the reference structure: the jar is the main brand shape; the animal shows only small rounded ears, the upper half-dome head, and two matte black dot eyes above the jar rim.\nDo not show nose, mouth, cheek blush, teeth, paws, body, lower face, or a full animal head. The eyes must be pure black dots with no highlights, no white sparkle, and no glossy pupils.\nThe jar itself must have no face, no expression, no eyes, no smile, and no decorative emoji marks.\nMake the result more like a trademark and logo than an illustration: simplified silhouette, clean vector curves, broad flat color fields, fewer gradients, fewer soft shadows, no realistic ceramic rendering.\nKeep a warm ceramic-container feeling through color and silhouette only. The jar should not look like a bowl of food, cup, dessert package, pudding cup, bread, bun, chocolate, candy, jelly, or pastry.\nStyle target: premium friendly mascot-symbol logo, all-age and women-friendly, calm but memorable, suitable for app icon, social avatar, and trademark review.\nComposition: centered on a clean light background with generous safe area. Strong readable silhouette first. It must still read at 32px and in black and white.\nAvoid star, spark, halo, magic wand, hand, pottery tool, UI, border, sticker outline, background scene, and watermark.\nVariant focus: cream ceramic jar with a cocoa-brown capybara head. Keep the palette soft but not edible; use graphic flat fills and a crisp rim shape to avoid dessert feeling."
|
||||
},
|
||||
{
|
||||
"id": "03-sage-clay",
|
||||
"title": "鼠尾草陶",
|
||||
"file": "taonier-capybara-jar-ref01-logo-refine-03-sage-clay.png",
|
||||
"prompt": "Use the uploaded reference image as the composition and pose reference. Keep the same core idea: a ceramic jar container with a capybara-like small animal peeking out only to eye level.\nCreate a refined icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nPreserve the reference structure: the jar is the main brand shape; the animal shows only small rounded ears, the upper half-dome head, and two matte black dot eyes above the jar rim.\nDo not show nose, mouth, cheek blush, teeth, paws, body, lower face, or a full animal head. The eyes must be pure black dots with no highlights, no white sparkle, and no glossy pupils.\nThe jar itself must have no face, no expression, no eyes, no smile, and no decorative emoji marks.\nMake the result more like a trademark and logo than an illustration: simplified silhouette, clean vector curves, broad flat color fields, fewer gradients, fewer soft shadows, no realistic ceramic rendering.\nKeep a warm ceramic-container feeling through color and silhouette only. The jar should not look like a bowl of food, cup, dessert package, pudding cup, bread, bun, chocolate, candy, jelly, or pastry.\nStyle target: premium friendly mascot-symbol logo, all-age and women-friendly, calm but memorable, suitable for app icon, social avatar, and trademark review.\nComposition: centered on a clean light background with generous safe area. Strong readable silhouette first. It must still read at 32px and in black and white.\nAvoid star, spark, halo, magic wand, hand, pottery tool, UI, border, sticker outline, background scene, and watermark.\nVariant focus: muted sage green ceramic jar paired with a warm ochre capybara head. More mature and boutique. Keep the silhouette simple and logo-like, with only two or three main color regions."
|
||||
},
|
||||
{
|
||||
"id": "04-outline-emblem",
|
||||
"title": "线面徽记",
|
||||
"file": "taonier-capybara-jar-ref01-logo-refine-04-outline-emblem.png",
|
||||
"prompt": "Use the uploaded reference image as the composition and pose reference. Keep the same core idea: a ceramic jar container with a capybara-like small animal peeking out only to eye level.\nCreate a refined icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nPreserve the reference structure: the jar is the main brand shape; the animal shows only small rounded ears, the upper half-dome head, and two matte black dot eyes above the jar rim.\nDo not show nose, mouth, cheek blush, teeth, paws, body, lower face, or a full animal head. The eyes must be pure black dots with no highlights, no white sparkle, and no glossy pupils.\nThe jar itself must have no face, no expression, no eyes, no smile, and no decorative emoji marks.\nMake the result more like a trademark and logo than an illustration: simplified silhouette, clean vector curves, broad flat color fields, fewer gradients, fewer soft shadows, no realistic ceramic rendering.\nKeep a warm ceramic-container feeling through color and silhouette only. The jar should not look like a bowl of food, cup, dessert package, pudding cup, bread, bun, chocolate, candy, jelly, or pastry.\nStyle target: premium friendly mascot-symbol logo, all-age and women-friendly, calm but memorable, suitable for app icon, social avatar, and trademark review.\nComposition: centered on a clean light background with generous safe area. Strong readable silhouette first. It must still read at 32px and in black and white.\nAvoid star, spark, halo, magic wand, hand, pottery tool, UI, border, sticker outline, background scene, and watermark.\nVariant focus: bolder trademark mark with clean outline plus flat fills. Use a dark warm-brown contour line around the jar and animal, but keep it soft and modern, not sticker-like."
|
||||
},
|
||||
{
|
||||
"id": "05-abstract-geometric",
|
||||
"title": "抽象几何",
|
||||
"file": "taonier-capybara-jar-ref01-logo-refine-05-abstract-geometric.png",
|
||||
"prompt": "Use the uploaded reference image as the composition and pose reference. Keep the same core idea: a ceramic jar container with a capybara-like small animal peeking out only to eye level.\nCreate a refined icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nPreserve the reference structure: the jar is the main brand shape; the animal shows only small rounded ears, the upper half-dome head, and two matte black dot eyes above the jar rim.\nDo not show nose, mouth, cheek blush, teeth, paws, body, lower face, or a full animal head. The eyes must be pure black dots with no highlights, no white sparkle, and no glossy pupils.\nThe jar itself must have no face, no expression, no eyes, no smile, and no decorative emoji marks.\nMake the result more like a trademark and logo than an illustration: simplified silhouette, clean vector curves, broad flat color fields, fewer gradients, fewer soft shadows, no realistic ceramic rendering.\nKeep a warm ceramic-container feeling through color and silhouette only. The jar should not look like a bowl of food, cup, dessert package, pudding cup, bread, bun, chocolate, candy, jelly, or pastry.\nStyle target: premium friendly mascot-symbol logo, all-age and women-friendly, calm but memorable, suitable for app icon, social avatar, and trademark review.\nComposition: centered on a clean light background with generous safe area. Strong readable silhouette first. It must still read at 32px and in black and white.\nAvoid star, spark, halo, magic wand, hand, pottery tool, UI, border, sticker outline, background scene, and watermark.\nVariant focus: higher abstraction. Reduce the capybara head to a clean half-dome with two round ears and two black dots; reduce the jar to a distinct pot silhouette with a single rim band. Very vector-ready."
|
||||
},
|
||||
{
|
||||
"id": "06-monochrome-first",
|
||||
"title": "黑白优先",
|
||||
"file": "taonier-capybara-jar-ref01-logo-refine-06-monochrome-first.png",
|
||||
"prompt": "Use the uploaded reference image as the composition and pose reference. Keep the same core idea: a ceramic jar container with a capybara-like small animal peeking out only to eye level.\nCreate a refined icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nPreserve the reference structure: the jar is the main brand shape; the animal shows only small rounded ears, the upper half-dome head, and two matte black dot eyes above the jar rim.\nDo not show nose, mouth, cheek blush, teeth, paws, body, lower face, or a full animal head. The eyes must be pure black dots with no highlights, no white sparkle, and no glossy pupils.\nThe jar itself must have no face, no expression, no eyes, no smile, and no decorative emoji marks.\nMake the result more like a trademark and logo than an illustration: simplified silhouette, clean vector curves, broad flat color fields, fewer gradients, fewer soft shadows, no realistic ceramic rendering.\nKeep a warm ceramic-container feeling through color and silhouette only. The jar should not look like a bowl of food, cup, dessert package, pudding cup, bread, bun, chocolate, candy, jelly, or pastry.\nStyle target: premium friendly mascot-symbol logo, all-age and women-friendly, calm but memorable, suitable for app icon, social avatar, and trademark review.\nComposition: centered on a clean light background with generous safe area. Strong readable silhouette first. It must still read at 32px and in black and white.\nAvoid star, spark, halo, magic wand, hand, pottery tool, UI, border, sticker outline, background scene, and watermark.\nVariant focus: black-and-white survival first. Design with strong positive and negative shapes; color is secondary. Use warm clay and dark umber, but the mark must remain clear if converted to pure black and white."
|
||||
},
|
||||
{
|
||||
"id": "07-soft-gradient-logo",
|
||||
"title": "轻渐变商标",
|
||||
"file": "taonier-capybara-jar-ref01-logo-refine-07-soft-gradient-logo.png",
|
||||
"prompt": "Use the uploaded reference image as the composition and pose reference. Keep the same core idea: a ceramic jar container with a capybara-like small animal peeking out only to eye level.\nCreate a refined icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nPreserve the reference structure: the jar is the main brand shape; the animal shows only small rounded ears, the upper half-dome head, and two matte black dot eyes above the jar rim.\nDo not show nose, mouth, cheek blush, teeth, paws, body, lower face, or a full animal head. The eyes must be pure black dots with no highlights, no white sparkle, and no glossy pupils.\nThe jar itself must have no face, no expression, no eyes, no smile, and no decorative emoji marks.\nMake the result more like a trademark and logo than an illustration: simplified silhouette, clean vector curves, broad flat color fields, fewer gradients, fewer soft shadows, no realistic ceramic rendering.\nKeep a warm ceramic-container feeling through color and silhouette only. The jar should not look like a bowl of food, cup, dessert package, pudding cup, bread, bun, chocolate, candy, jelly, or pastry.\nStyle target: premium friendly mascot-symbol logo, all-age and women-friendly, calm but memorable, suitable for app icon, social avatar, and trademark review.\nComposition: centered on a clean light background with generous safe area. Strong readable silhouette first. It must still read at 32px and in black and white.\nAvoid star, spark, halo, magic wand, hand, pottery tool, UI, border, sticker outline, background scene, and watermark.\nVariant focus: allow only a very subtle premium gradient on broad shapes, like a polished app logo. Keep it much flatter than the reference and remove painterly shadows or texture."
|
||||
},
|
||||
{
|
||||
"id": "08-bold-avatar",
|
||||
"title": "头像强识别",
|
||||
"file": "taonier-capybara-jar-ref01-logo-refine-08-bold-avatar.png",
|
||||
"prompt": "Use the uploaded reference image as the composition and pose reference. Keep the same core idea: a ceramic jar container with a capybara-like small animal peeking out only to eye level.\nCreate a refined icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nPreserve the reference structure: the jar is the main brand shape; the animal shows only small rounded ears, the upper half-dome head, and two matte black dot eyes above the jar rim.\nDo not show nose, mouth, cheek blush, teeth, paws, body, lower face, or a full animal head. The eyes must be pure black dots with no highlights, no white sparkle, and no glossy pupils.\nThe jar itself must have no face, no expression, no eyes, no smile, and no decorative emoji marks.\nMake the result more like a trademark and logo than an illustration: simplified silhouette, clean vector curves, broad flat color fields, fewer gradients, fewer soft shadows, no realistic ceramic rendering.\nKeep a warm ceramic-container feeling through color and silhouette only. The jar should not look like a bowl of food, cup, dessert package, pudding cup, bread, bun, chocolate, candy, jelly, or pastry.\nStyle target: premium friendly mascot-symbol logo, all-age and women-friendly, calm but memorable, suitable for app icon, social avatar, and trademark review.\nComposition: centered on a clean light background with generous safe area. Strong readable silhouette first. It must still read at 32px and in black and white.\nAvoid star, spark, halo, magic wand, hand, pottery tool, UI, border, sticker outline, background scene, and watermark.\nVariant focus: compact social-avatar readability. Make the jar a fuller rounded vessel and enlarge the peeking capybara head slightly, while preserving the hidden half-head rhythm and black dot eyes."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 699 KiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 846 KiB |