This commit is contained in:
2026-05-14 21:33:34 +08:00
193 changed files with 17051 additions and 1203 deletions
@@ -0,0 +1,351 @@
import { Buffer } from 'node:buffer';
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const skillRoot = path.resolve(__dirname, '..');
const repoRoot = path.resolve(skillRoot, '..', '..', '..');
const defaultOutDir = path.join(repoRoot, 'public', 'anthro-cat-illustrations');
const defaultTimeoutMs = 180000;
const prompts = [
{
id: 'cat-barista',
title: '咖啡师猫咪',
subject:
'一只奶油色猫咪像人一样双足站立,穿深绿色围裙,在温暖咖啡馆吧台前专注拉花,爪子扶着咖啡杯,蓬松尾巴自然弯起,童书级精致插画,柔和自然光,主体清晰。',
},
{
id: 'cat-detective',
title: '侦探猫咪',
subject:
'一只黑白猫咪像侦探一样双足站在雨后街角,穿短风衣和小帽子,单爪拿放大镜,另一只爪插兜,路灯和湿润石板路反光,电影感但可爱,插画风格。',
},
{
id: 'cat-dancer',
title: '舞者猫咪',
subject:
'一只橘猫以拟人舞者姿态单脚旋转,穿轻盈舞台披肩,前爪展开,尾巴形成优雅弧线,背景是暖色小剧场灯光,动作灵动,精致插画。',
},
{
id: 'cat-knight',
title: '骑士猫咪',
subject:
'一只银灰猫咪像小骑士一样站在苔藓石台上,披短斗篷,双爪握着细剑指向地面,姿态勇敢但可亲,远处森林微光,奇幻插画风格。',
},
{
id: 'cat-painter',
title: '画家猫咪',
subject:
'一只三花猫咪双足站在画架前,穿宽松蓝色工作衫,一爪拿画笔一爪托调色盘,鼻尖有颜料点,窗边画室阳光明亮,温柔手绘插画。',
},
{
id: 'cat-astronaut',
title: '宇航员猫咪',
subject:
'一只白猫咪以拟人宇航员姿态站在月面,透明头盔内露出猫脸,尾巴在宇航服后轻轻翘起,爪子向远处蓝色星球敬礼,梦幻插画风格。',
},
];
const args = new Map();
for (let index = 2; index < process.argv.length; index += 1) {
const raw = process.argv[index];
if (raw.startsWith('--')) {
const next = process.argv[index + 1];
if (next && !next.startsWith('--')) {
args.set(raw, next);
index += 1;
} else {
args.set(raw, true);
}
}
}
function readDotenv(fileName) {
const filePath = path.join(repoRoot, fileName);
if (!existsSync(filePath)) {
return {};
}
const values = {};
for (const line of readFileSync(filePath, 'utf8').split(/\r?\n/u)) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) {
continue;
}
const match = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/u.exec(trimmed);
if (!match) {
continue;
}
let value = match[2].trim();
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
values[match[1]] = value;
}
return values;
}
function resolveEnv() {
const loaded = {
...readDotenv('.env.example'),
...readDotenv('.env.local'),
...readDotenv('.env.secrets.local'),
...process.env,
};
return {
baseUrl: String(loaded.VECTOR_ENGINE_BASE_URL || '')
.trim()
.replace(/\/+$/u, ''),
apiKey: String(loaded.VECTOR_ENGINE_API_KEY || '').trim(),
timeoutMs: Number.parseInt(
String(loaded.VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS || defaultTimeoutMs),
10,
),
};
}
function buildVectorEngineImagesGenerationUrl(baseUrl) {
return baseUrl.endsWith('/v1')
? `${baseUrl}/images/generations`
: `${baseUrl}/v1/images/generations`;
}
function buildPrompt(entry) {
return [
'请生成一张高清 1:1 方形插画。',
`画面主体:${entry.subject}`,
'要求:猫咪保留清晰猫脸、猫耳、猫尾和毛发质感,但身体姿态像人一样自然;构图完整,角色占画面主体,适合作为项目插画素材。',
'避免:文字、水印、边框、按钮、UI 元素、低清晰度、过度写实恐怖感、畸形肢体、多余手指。',
].join('');
}
function collectStringsByKey(value, targetKey, output) {
if (Array.isArray(value)) {
value.forEach((entry) => collectStringsByKey(entry, targetKey, output));
return;
}
if (!value || typeof value !== 'object') {
return;
}
for (const [key, nested] of Object.entries(value)) {
if (key === targetKey) {
if (typeof nested === 'string' && nested.trim()) {
output.push(nested.trim());
}
if (Array.isArray(nested)) {
nested.forEach((entry) => {
if (typeof entry === 'string' && entry.trim()) {
output.push(entry.trim());
}
});
}
}
collectStringsByKey(nested, targetKey, output);
}
}
function extractImageUrls(payload) {
const urls = [];
collectStringsByKey(payload, 'url', urls);
collectStringsByKey(payload, 'image', urls);
collectStringsByKey(payload, 'image_url', urls);
return [...new Set(urls)].filter((url) => /^https?:\/\//u.test(url));
}
function extractBase64Images(payload) {
const values = [];
collectStringsByKey(payload, 'b64_json', values);
return values;
}
function inferExtensionFromContentType(contentType) {
const normalized = contentType.split(';')[0]?.trim().toLowerCase();
if (normalized === 'image/png') {
return 'png';
}
if (normalized === 'image/webp') {
return 'webp';
}
if (normalized === 'image/gif') {
return 'gif';
}
return 'jpg';
}
function inferExtensionFromBytes(bytes) {
if (bytes.subarray(0, 8).equals(Buffer.from('\x89PNG\r\n\x1A\n', 'binary'))) {
return 'png';
}
if (bytes.subarray(0, 3).equals(Buffer.from([0xff, 0xd8, 0xff]))) {
return 'jpg';
}
if (
bytes.subarray(0, 4).toString('ascii') === 'RIFF' &&
bytes.subarray(8, 12).toString('ascii') === 'WEBP'
) {
return 'webp';
}
return 'png';
}
async function fetchJson(url, options, timeoutMs) {
const abortController = new AbortController();
const timer = setTimeout(() => abortController.abort(), timeoutMs);
try {
const response = await fetch(url, {
...options,
signal: abortController.signal,
});
const text = await response.text();
if (!response.ok) {
throw new Error(`VectorEngine ${response.status}: ${text.slice(0, 600)}`);
}
return JSON.parse(text);
} catch (error) {
if (error?.name === 'AbortError') {
throw new Error(`VectorEngine request timed out after ${timeoutMs}ms`);
}
throw error;
} finally {
clearTimeout(timer);
}
}
async function downloadUrl(url, timeoutMs) {
const abortController = new AbortController();
const timer = setTimeout(() => abortController.abort(), timeoutMs);
try {
const response = await fetch(url, { signal: abortController.signal });
if (!response.ok) {
throw new Error(`download ${response.status}`);
}
const bytes = Buffer.from(await response.arrayBuffer());
return {
bytes,
extension: inferExtensionFromContentType(
response.headers.get('content-type') || 'image/jpeg',
),
};
} catch (error) {
if (error?.name === 'AbortError') {
throw new Error(`Generated image download timed out after ${timeoutMs}ms`);
}
throw error;
} finally {
clearTimeout(timer);
}
}
async function generateOne(env, entry, outDir) {
const requestBody = {
model: 'gpt-image-2-all',
prompt: buildPrompt(entry),
n: 1,
size: '1024x1024',
};
const payload = await fetchJson(
buildVectorEngineImagesGenerationUrl(env.baseUrl),
{
method: 'POST',
headers: {
Authorization: `Bearer ${env.apiKey}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
},
env.timeoutMs,
);
const urls = extractImageUrls(payload);
const b64Images = extractBase64Images(payload);
let image;
if (urls[0]) {
image = await downloadUrl(urls[0], env.timeoutMs);
} else if (b64Images[0]) {
const bytes = Buffer.from(b64Images[0], 'base64');
image = {
bytes,
extension: inferExtensionFromBytes(bytes),
};
} else {
throw new Error(`VectorEngine returned no image for ${entry.id}`);
}
mkdirSync(outDir, { recursive: true });
const outputPath = path.join(outDir, `${entry.id}.${image.extension}`);
writeFileSync(outputPath, image.bytes);
return outputPath;
}
const dryRun = args.has('--dry-run') || !args.has('--live');
const outDir = path.resolve(String(args.get('--out-dir') || defaultOutDir));
const limit = Number.parseInt(String(args.get('--limit') || '0'), 10);
const selectedPrompts = limit > 0 ? prompts.slice(0, limit) : prompts;
if (dryRun) {
const env = resolveEnv();
console.log(
JSON.stringify(
{
mode: 'dry-run',
outDir,
count: selectedPrompts.length,
hasBaseUrl: Boolean(env.baseUrl),
hasApiKey: Boolean(env.apiKey),
requests: selectedPrompts.map((entry) => ({
id: entry.id,
title: entry.title,
body: {
model: 'gpt-image-2-all',
prompt: buildPrompt(entry),
n: 1,
size: '1024x1024',
},
})),
},
null,
2,
),
);
process.exit(0);
}
const env = resolveEnv();
if (!env.baseUrl || !env.apiKey) {
console.error(
JSON.stringify({
ok: false,
error: 'Missing VECTOR_ENGINE_BASE_URL or VECTOR_ENGINE_API_KEY',
hasBaseUrl: Boolean(env.baseUrl),
hasApiKey: Boolean(env.apiKey),
}),
);
process.exit(1);
}
const generated = [];
for (const entry of selectedPrompts) {
console.log(`Generating ${entry.id}...`);
generated.push(await generateOne(env, entry, outDir));
}
console.log(
JSON.stringify(
{
ok: true,
count: generated.length,
files: generated,
},
null,
2,
),
);
+2 -1
View File
@@ -8,6 +8,7 @@
- 不提交个人配置、API Key、会话转录、模型密钥、本地路径密钥等敏感内容。
- 个人 Hermes 的 `~/.hermes/config.yaml`、`~/.hermes/.env`、`~/.hermes/sessions/` 不应复制到本仓库。
- 开发前先阅读本目录下与任务相关的记忆文件;开发后如产生稳定知识,更新对应文档。
- 后续新增的 Markdown 文档文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`,便于团队跨目录检索。
- 若本目录内容与 `docs/` 或代码事实冲突,以当前代码和最新 `docs/` 为准,并同步修正过期记忆。
## 目录结构
@@ -24,6 +25,7 @@
│ ├─ pitfalls.md # 踩坑与排障记录
│ └─ handoff-template.md # 任务交接模板
├─ plans/ # 阶段性计划与实施方案
├─ todos/ # 已定稿但尚未执行的共享 TODO 计划
├─ skills/ # 仓库级 Hermes skills
└─ plugins/ # 仓库级 Hermes plugins(需显式启用项目 plugin)
```
@@ -89,4 +91,3 @@ HERMES_ENABLE_PROJECT_PLUGINS=1 HERMES_PLUGINS_DEBUG=1 hermes chat -q "请读取
- 大段临时聊天记录
- 尚未确认的一次性猜测
- 构建产物、日志、缓存、数据库 dump
File diff suppressed because it is too large Load Diff
+16
View File
@@ -127,6 +127,22 @@
- 验证方式:执行 `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-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,正式持久化后续再设计。
- 影响范围:`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`。
## 2026-05-12 宝贝识物创作同时生成玩法视觉主题包
- 背景:`宝贝识物` 创作原本只根据两个关键词生成物品透明图,运行态背景、UI、礼物盒和篮子仍使用固定 CSS 绘本风,无法根据“小猪佩琪 / 奥特曼”或“苹果 / 橘子”等创作者提示词做主题化包装。
- 决策:`POST /api/creation/edutainment/baby-object-match/assets` 同一次 image-2 / VectorEngine 调用链返回两个物品图和 `visualPackage`。视觉包包含 `background`、`ui-frame`、`gift-box`、`basket`、`smoke-puff` 五类资源;总风格保持寓教于乐明亮卡通绘本插画风,主题按两个物品关键词匹配,水果偏果园自然,动漫角色 / 玩具偏动漫玩具。物品图和礼物盒 / 篮子 / UI / 烟雾特效资源走透明 PNG 后处理,背景为清爽不遮挡玩法区的环境图;运行态中礼物盒按约 2 倍视觉尺寸展示、篮子按约 1.5 倍展示,礼物盒打开时使用 `smoke-puff` 弹出中央物品并移除礼盒。前端草稿保存该包,运行态消费该包;旧草稿以 `visualPackage = null` 继续使用 CSS 兜底。
- 影响范围:`packages/shared/src/contracts/edutainmentBabyObject.ts`、`server-rs/crates/api-server/src/edutainment_baby_object.rs`、`src/services/edutainment-baby-object/babyObjectMatchClient.ts`、`src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx`、`src/index.css`、宝贝识物 PRD 与技术方案。
- 验证方式:执行宝贝识物 service / runtime 定向测试、`cargo test -p api-server edutainment_baby_object --manifest-path server-rs/Cargo.toml`、相关 ESLint 与编码检查;真实生图需配置 `VECTOR_ENGINE_BASE_URL` 与 `VECTOR_ENGINE_API_KEY`。
- 关联文档:`docs/prd/BABY_OBJECT_MATCH_EDUTAINMENT_TEMPLATE_PRD_2026-05-11.md`、`docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`。
## 2026-05-11 拼图与抓大鹅结果页音频资产复用通用创作音频链路
- 背景:拼图和抓大鹅结果页需要接入 Suno 背景音乐,抓大鹅还需要物体点击音效,但当前两类作品没有独立的作品级音频表或 metadata 字段。
+3
View File
@@ -30,6 +30,8 @@
- `operations/`:后台运营核查、对账和排障查询。
- `prd/`:产品需求与阶段计划。
新增 Markdown 文档文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`。标签用于跨目录检索,不替代 `docs/` 的目录分类;历史文档不要求批量重命名。
## 推荐阅读顺序
通用复杂任务:
@@ -96,5 +98,6 @@ RPG 创作与运行时链路:
- 新增工程实现时,如果已有对应文档,必须同步更新。
- 如果没有对应文档,新文档放入 `docs/` 下合适分类。
- 新文档文件名必须使用 `【标签名】` 前缀,标题尽量保留中文语义,日期使用 `YYYY-MM-DD`。
- `.hermes/shared-memory/` 只保留跨任务、跨成员、高频使用的摘要和索引。
- 如果文档与代码冲突,先确认代码事实,再更新过期文档和共享记忆。
+51 -3
View File
@@ -93,15 +93,55 @@
- 验证:运行 `npx vitest run src\services\useMocapInput.test.ts src\components\child-motion-demo\ChildMotionWarmupDemo.test.tsx`,并在本地硬件服务启动后进入 `/child-motion-demo` 实测站位、招手、左右手挥动和跳跃阶段。
- 关联:`src/services/useMocapInput.ts`、`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx`、`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`。
## 儿童动作 Demo 左右手阶段误通过先查身体侧映射和手臂展开阈值
- 现象:热身关“挥动左手 / 挥动右手”阶段,用户只是手自然下垂、横向小幅抖动,或挥了相反侧手,也可能被判定通过。
- 原因:本地 mocap 的 handedness 当前按摄像头视角输出,不能直接当作用户身体左/右;同时左右手阶段的目标是确认现实空间安全,需要验证手臂向外打开和上下摆动角度,不能只看手部 `x` 轨迹范围。
- 处理:热身关中用户左手应消费 camera-right,用户右手应消费 camera-left;左右手阶段只在同侧肩肘腕外展、手腕非自然下垂、连续有效帧、横向范围、上下摆动范围、肩腕角度范围和上下方向变化全部达标时完成,并记录轨迹空间包络、角度范围和最大外展距离。
- 验证:运行 `npx vitest run src\components\child-motion-demo\ChildMotionWarmupDemo.test.tsx src\components\child-motion-demo\childMotionWarmupModel.test.ts`,确认相反侧手、自然下垂、单纯横向轨迹不会完成,真实展开上下摆动可以完成。
- 关联:`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx`、`src/components/child-motion-demo/childMotionWarmupModel.ts`、`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`。
## 儿童动作 Demo 角色轮廓抽搐先查 mocap 坐标防抖和渲染分层
- 现象:`/child-motion-demo` 中间半透明小人在真实硬件驱动下左右轻微来回摆,移动过程中看起来忽大忽小,用户很难稳定停在目标圆环内。
- 原因:`general.body.center_norm.x` 原始值逐包直接写入 `avatarX` 时,硬件坐标小噪声会直接驱动位置保持判定和 CSS 动画;如果角色外层同时承担横向定位和跳跃 `transform`,半透明 PNG 在移动时也更容易出现重采样抖动观感。
- 处理:mocap 身体中心进入角色位置前必须先 clamp,再经过小幅死区、低通阻尼和单包最大步长限制;键盘 A/D 调试输入仍保持即时。角色 DOM 外层只负责横向定位,内层 sprite 负责轮廓图和跳跃位移,避免同一层 `transform` 同时表达多种运动。
- 验证:运行 `npx vitest run src\components\child-motion-demo\ChildMotionWarmupDemo.test.tsx src\components\child-motion-demo\childMotionWarmupModel.test.ts src\services\useMocapInput.test.ts src\services\child-motion-demo\childMotionDebugInput.test.ts`,并用真实硬件进入站位阶段观察小幅身体晃动不会导致角色频繁左右跳动。
- 关联:`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx`、`src/index.css`、`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`。
## 宝贝识物选篮误触发先查多套判定和残余轨迹
- 现象:`宝贝识物` 运行态打开礼物盒或反馈结束后,当前物品被连续送入左侧或右侧篮子,或硬件动作名偶发命中导致未做明确横移动作也触发选篮。
- 原因:选篮如果同时消费 `wave_left_hand` / `wave_right_hand` / `wave` 动作名和手部轨迹,或在 `correct` / `wrong` 反馈阶段继续累计手部路径,会把抓握、反馈期间残留移动或未知侧别手部误算成下一次选篮。
- 处理:宝贝识物选篮只使用明确 `leftHand` / `rightHand` 的连续横向轨迹阈值;侧别为 `unknown` 的手部轨迹不参与选篮;礼物盒打开和反馈阶段清空轨迹,不在非 `active` 阶段累计路径。礼物盒激活仍使用 `open_palm -> grab` 抓握序列。
- 处理:宝贝识物选篮只使用明确 `leftHand` / `rightHand` 的连续横向轨迹阈值;侧别为 `unknown` 的手部轨迹不参与选篮;反馈阶段清空轨迹,不在非 `active` 阶段累计路径。进入关卡和每次正确反馈结束后自动弹出物品,不再用 `open_palm -> grab` 抓握序列激活礼物盒。
- 补充:当前本地 mocap 的 handedness 是摄像头视角,宝贝识物选篮前需要换算为用户身体视角;`rightHand` 轨迹代表玩家左手并进入左篮,`leftHand` 轨迹代表玩家右手并进入右篮。键鼠调试不走该换算,仍保持鼠标左键=左篮、右键=右篮。
- 验证:运行 `npm run test -- src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.test.tsx src/services/useMocapInput.test.ts`,确认动作名负向测试、未知侧别负向测试和左右手横向轨迹测试通过。
- 关联:`src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx`、`docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`。
## 宝贝爱画左右手反了先查 mocap 摄像头视角换算
- 现象:`宝贝爱画` 中真实硬件下左手指示器和右手画笔表现反向,用户抬右手却出现左手选色指示器,或抬左手却驱动画笔 / 橡皮。
- 原因:本地 mocap 的 handedness 当前按摄像头视角输出,不能直接当成用户身体左 / 右;宝贝爱画初版直接消费 `latestCommand.leftHand/rightHand`,漏做摄像头视角到用户身体视角的换算。
- 处理:宝贝爱画运行态消费 mocap 前先换算:`rightHand` 作为用户左手,用于颜色悬停和左手指示器;`leftHand` 作为用户右手,用于画笔 / 橡皮光标、绘制、擦除和工具切换。键鼠调试输入不做该换算,继续保持鼠标左键为左手、右键为右手。
- 验证:运行 `npm run test -- src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.test.tsx src/components/edutainment-runtime/babyLoveDrawingModel.test.ts`,确认 camera-left 驱动用户右手画笔、camera-right 渲染用户左手选色指示器。
- 关联:`src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.tsx`、`docs/technical/BABY_LOVE_DRAWING_RUNTIME_DEMO_IMPLEMENTATION_2026-05-13.md`。
## 宝贝识物创作卡在准备结果页先查长耗时 image-2 请求
- 现象:`/creation/baby-object-match` 创作生成停在“准备结果页”,约 3 分钟后显示“生成失败 / 请求超时”;后端日志可能出现同一路由 `status=502 latency_ms=231291`,或前端已失败但后端稍后返回 200。
- 原因:宝贝识物一次创作会生成 2 张物品图和 `background`、`ui-frame`、`gift-box`、`basket`、`smoke-puff` 5 张视觉包装图。旧前端只等待 180 秒并对长耗时 POST 自动重试,容易在 VectorEngine 仍在生成时先 abort,再重复发起第二次生成;上游某张图超过后端 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 或返回 5xx 时会表现为 502。
- 处理:`babyObjectMatchClient` 对 `/api/creation/edutainment/baby-object-match/assets` 使用 10 分钟超时并取消自动重试;后端并发启动物品图和视觉主题包生成,并把该路由的 VectorEngine 单图请求等待预算提升到至少 8 分钟,按资源类别输出开始、完成和耗时日志。
- 验证:运行 `npm run test -- src/services/edutainment-baby-object/babyObjectMatchClient.test.ts src/services/miniGameDraftGenerationProgress.test.ts`、`cargo test -p api-server edutainment_baby_object --manifest-path server-rs/Cargo.toml` 和编码检查;真实联调时查看 `宝贝识物 image-2 资源生成完成` 耗时是否小于前端超时,若仍 502 再看 `VectorEngine 图片生成上游错误` 的 `upstreamStatus/raw_excerpt`。
- 关联:`src/services/edutainment-baby-object/babyObjectMatchClient.ts`、`src/services/miniGameDraftGenerationProgress.ts`、`server-rs/crates/api-server/src/edutainment_baby_object.rs`、`docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`。
## 寓教于乐作品和宝贝识物模板同时消失先查入口种子
- 现象:发现页“寓教于乐”分类下已发布的宝贝识物作品突然消失,同时创作界面模板选项中也看不到或无法正常展示 `宝贝识物`。
- 原因:创作入口配置事实源已迁到 SpacetimeDB `creation_entry_type_config`;前端用 `baby-object-match` 入口可见性同时控制创作模板展示和发现页宝贝识物公开作品合入。若默认种子或后台配置缺少 `baby-object-match` 行,两条链路会一起被判定为不可见。
- 处理:确认 `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs` 默认种子包含 `id=baby-object-match`、`title=宝贝识物`、`visible=true`、`open=true`、`sort_order=90`;api-server 测试降级配置也要同步包含该类型。入口图片路径需指向真实存在资源,避免卡片图片 404。
- 验证:运行 `cargo test -p module-runtime default_creation_entry_types_include_baby_object_match --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server test_creation_entry_config_response_keeps_baby_object_match_visible --manifest-path server-rs/Cargo.toml`、`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 和 `npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts`。
- 关联:`server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs`、`server-rs/crates/api-server/src/creation_entry_config.rs`、`docs/technical/NEW_WORK_ENTRY_CONFIG_2026-05-01.md`。
## 儿童动作 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 配置。
@@ -250,8 +290,8 @@
- 现象:本地 `npm run dev` 因 `3101` 已占用、重复发布 SpacetimeDB wasm 编译太慢,或只想检查 `spacetime-module` 语法而被完整联调链路拖慢。
- 原因:`npm run dev` 默认同时启动 SpacetimeDB standalone、发布 `server-rs/crates/spacetime-module`、启动 Rust `api-server`、主站 Vite 与后台 Vite;并非每个阶段都需要完整重启和重新发布。
- 处理:`npm run dev` 启动后会把实际 SpacetimeDB URL 记录到 `server-rs/.spacetimedb/local/data/dev-rust-spacetime-url`。下次启动即使没有传 `--skip-spacetime`,脚本也会先检查 `spacetime.pid` 对应进程和该 URL 是否在线;在线则直接复用现有宿主。确认需要新启动 SpacetimeDB 时,脚本先检测 `3101`,被占用则输出占用进程并选择最近可用端口,保证 publish 与 `api-server` 都连接同一个实际 SpacetimeDB URL。`api-server` 启动前也会检测 `8082` 并选择最近可用端口。Windows / Git Bash 下不要用 `tr/head/xargs` 管道读取 `spacetime.pid` 或 URL 记录,脚本应使用 Node 读取并短重试,避免 `tr: read error: Device or resource busy`;未修改 `spacetime-module` 时使用 `npm run dev -- --skip-publish`;只查模块语法时执行 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`。`npm run dev` 会在启动前检查 SpacetimeDB、api-server、主站 Vite、后台 Vite 端口,不可用时自动寻找后续可用端口,并把实际端口传给 publish、后端环境变量和前端代理目标。
- 验证:`--skip-spacetime` 后脚本复用现有 `http://127.0.0.1:3101`;`3101` 或 `8082` 被其他进程占用时,脚本输出占用进程并使用最近可用端口;`--skip-publish` 后不再进入 publish 阶段;`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 能完成 Rust 语法和类型检查。端口漂移时控制台会打印 `[dev:ports] ... 不可用,改用 ...`,后续 `[dev:rust] web/admin web/rust api/spacetime` 地址应与实际端口一致。
- 处理:`npm run dev` 启动后会把实际 SpacetimeDB URL 记录到 `server-rs/.spacetimedb/local/data/dev-rust-spacetime-url`。下次启动即使没有传 `--skip-spacetime`,脚本也会先检查 `spacetime.pid` 对应进程和该 URL 是否在线;在线则直接复用现有宿主。确认需要新启动 SpacetimeDB 时,脚本先检测 `3101`,被占用则输出占用进程并选择最近可用端口,保证 publish 与 `api-server` 都连接同一个实际 SpacetimeDB URL。显式传 `--skip-spacetime` 时表示复用既有宿主,脚本不再对 SpacetimeDB 端口做可用性漂移;`--spacetime-port 3101` 就是后端要连接的实际端口,避免被误改到空闲但未启动的 `3102`。`api-server` 启动前也会检测 `8082` 并选择最近可用端口。Windows / Git Bash 下不要用 `tr/head/xargs` 管道读取 `spacetime.pid` 或 URL 记录,脚本应使用 Node 读取并短重试,避免 `tr: read error: Device or resource busy`;未修改 `spacetime-module` 时使用 `npm run dev -- --skip-publish`;只查模块语法时执行 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`。`npm run dev` 会在启动前检查 SpacetimeDB、api-server、主站 Vite、后台 Vite 端口,不可用时自动寻找后续可用端口,并把实际端口传给 publish、后端环境变量和前端代理目标。
- 验证:`--skip-spacetime` 后脚本复用现有 `http://127.0.0.1:3101`;日志中的 `[dev:rust] spacetime:` 不应漂移到没有服务的 `3102`;`GET /api/creation-entry/config` 不应返回连接空端口导致的 `502`。`3101` 或 `8082` 被其他进程占用时,脚本输出占用进程并使用最近可用端口;`--skip-publish` 后不再进入 publish 阶段;`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 能完成 Rust 语法和类型检查。端口漂移时控制台会打印 `[dev:ports] ... 不可用,改用 ...`,后续 `[dev:rust] web/admin web/rust api/spacetime` 地址应与实际端口一致。
- 关联:`docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md`、`scripts/dev-rust-stack.sh`。
## 本地 SpacetimeDB publish 401 可清本地库重发
@@ -654,6 +694,14 @@
- 验证:mock 通知测试只能覆盖本地回调推进;真实环境还需用微信支付平台公钥、真实通知头和 API v3 密钥验证签名与解密链路。
- 关联:`server-rs/crates/api-server/src/wechat_pay.rs`、`docs/technical/MY_TAB_ACCOUNT_RECHARGE_IMPLEMENTATION_2026-04-25.md`。
## 后台表查询展示 SpacetimeDB 枚举时不要套用 Option 解码
- 现象:后台“表查询”查看 `profile_recharge_order` 时,`kind` 和 `status` 显示为空数组 `[]`,例如充值订单原始行里 `points_60` 的类型和状态都不可读。
- 原因:SpacetimeDB HTTP SQL 对无载荷枚举会返回 SATS 形态 `[variant_index, []]`;后台通用 normalizer 曾把任何 `[0, value]` 都当作 `Option::Some(value)` 展开,导致 `[0, []]` 最终只剩 `[]`。
- 处理:通用表查询解析应先按表名和列名识别已知业务枚举,再落回 Option / Timestamp 通用展开;例如 `profile_recharge_order.kind` 映射为 `points` / `membership`,`profile_recharge_order.status` 映射为 `pending` / `paid` / `failed` / `closed` / `refunded`。
- 验证:执行 `cargo test -p api-server admin_database -- --nocapture`,并确认后台详情弹层的 `raw` 与表格 `cells` 都显示业务字符串。
- 关联:`server-rs/crates/api-server/src/admin.rs`、`docs/technical/ADMIN_DATABASE_TABLE_QUERY_2026-05-08.md`。
## 抓大鹅历史草稿外部 Rodin GLB 链接必须转存后再试玩或发布
- 现象:草稿页预览模型失败并报 `GL_INVALID_ENUM: Invalid cap.`,或结果页能看到历史生成记录但试玩、发布和正式运行态仍显示默认积木。
+6 -3
View File
@@ -15,6 +15,7 @@
- `.hermes/shared-memory/` 团队级长期记忆
- `.hermes/plans/` 阶段性实施计划
- `.hermes/todos/` 已确定需要执行、但尚未进入实施的共享 TODO 计划
- `.hermes/skills/` 未来可复用仓库级 skills
- `docs/` 中 PRD、设计、技术、经验、审计、查询手册
- `AGENTS.md` 项目级 Agent 约束
@@ -41,6 +42,7 @@
- 保持修改范围聚焦,不做无关重构。
- 复用、修改、扩展现有系统优先,避免新建重复系统或页面。
- 新增 Markdown 文档时,文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`;只在任务需要时重命名历史文档,避免无关大 diff。
- 涉及中文文本时注意 UTF-8 编码和乱码排查。
- 涉及后端时遵循 DDD 分层,不把业务真相下沉到前端或临时兼容层。
- `maincloud` / `Maincloud` / `MAINCLOUD` 相关代码、脚本、测试、环境变量、命令和文档要求均视为历史残留,禁止新增、运行或引用;API smoke 统一使用 `npm run api-server` 与 `/healthz`。
@@ -51,9 +53,10 @@
1. 运行与修改范围匹配的测试或验证命令。
2. 更新相关 `docs/` 文档。
3. 若产生长期有效知识,更新 `.hermes/shared-memory/`。
4. 若形成可复用流程,考虑沉淀到 `.hermes/skills/`。
5. 在提交信息中区分代码变更与文档/记忆变更。
3. 新增或沉淀 Markdown 文档时,确认文件名已使用 `【标签名】` 前缀。
4. 若产生长期有效知识,更新 `.hermes/shared-memory/`。
5. 若形成可复用流程,考虑沉淀到 `.hermes/skills/`。
6. 在提交信息中区分代码变更与文档/记忆变更。
## 文档阅读顺序
+15
View File
@@ -0,0 +1,15 @@
# 项目共享 TODO 计划
本目录用于存放已经讨论定稿、需要后续执行,但当前尚未实施的项目级计划文档。
## 使用规则
- 每个 TODO 计划单独一个 Markdown 文件,文件名优先使用可检索的中文标题与日期。
- 每个 TODO 计划文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`。
- 计划内容必须足够明确,后续开发者可以直接据此开始实施。
- 已执行完成的计划应迁移到对应 `docs/` 技术/规划文档,或在本文档中标记完成并移出 TODO 队列。
- 不在这里保存个人私密路径、密钥、临时聊天记录或未确认猜测。
## 当前待执行
- [【后端架构】api-server 能力模块化与图片资产 Adapter 收口计划](./【后端架构】api-server能力模块化与图片资产Adapter收口计划-2026-05-14.md)
@@ -0,0 +1,108 @@
# api-server 能力模块化与生成资产 Adapter 完整收口计划
状态:待执行
## Summary
目标是把 `api-server` 从“超大 `app.rs` + 多个超大 handler 文件 + 多处生成资产重复链路”收成可长期维护的能力 Module 结构。
本计划完整覆盖:路由能力模块化、生成图片资产 Adapter、复杂媒体链路扩展、大 handler 瘦身、文档与验收。全程不改变 HTTP contract、DTO、SpacetimeDB schema、前端行为和计费语义,除非某阶段文档先明确提出并单独批准。
## 文档交付
先新增一个总纲,再按阶段新增单独执行文档:
- 总纲:`docs/technical/【后端架构】api-server能力模块化与生成资产Adapter总纲-2026-05-14.md`
- 阶段 1:`docs/technical/【后端架构】api-server路由能力模块化执行计划-2026-05-14.md`
- 阶段 2:`docs/technical/【后端架构】生成图片资产Adapter收口执行计划-2026-05-14.md`
- 阶段 3:`docs/technical/【后端架构】复杂媒体资产链路Adapter扩展计划-2026-05-14.md`
- 阶段 4:`docs/technical/【后端架构】api-server大Handler瘦身执行计划-2026-05-14.md`
- 同步更新 `server-rs/crates/api-server/README.md`、`docs/technical/README.md`、本 TODO 文档。
## 阶段 0:基线盘点与总纲冻结
- 盘点 `app.rs` 当前全部 route,按 `admin/auth/assets/profile/creation/runtime/story/platform/internal` 分类形成 route inventory。
- 盘点 Big Fish、Square Hole、Custom World、Puzzle、Match3D、Visual Novel、音频、视频、GLB 的生成资产链路,标出 provider、下载方式、OSS prefix、asset kind、entity binding、计费位置、降级行为。
- 在总纲文档中冻结边界:`api-server` 只做 HTTP/SSE/BFF、鉴权、DTO 映射、平台服务编排;领域规则仍归 `module-*`,SpacetimeDB 真相仍归 `spacetime-module`。
退出条件:总纲和 inventory 能指导后续编码,不再只是一段方向描述。
## 阶段 1:路由能力模块化完整收口
- 新增 `server-rs/crates/api-server/src/modules/`,每个能力 Module 暴露 `router(state) -> Router<AppState>`。
- 第一批迁移低风险路由:`admin`、`auth`、`assets`、`profile`、`internal`、`health`。
- 第二批迁移平台编排路由:`ai_tasks`、`llm`、`speech`、`wechat`、`creative_agent`、`visual_novel`、通用 audio。
- 第三批迁移玩法路由:`big_fish`、`square_hole`、`puzzle`、`match3d`、`custom_world`、`story`、runtime save/settings/chat/inventory。
- `app.rs` 最终只保留全局 middleware、TraceLayer、request context、tracking middleware、`.merge(modules::*::router(...))` 和少量顶层 glue。
- handler 实现第一阶段可以继续留在原文件;本阶段只改变路由装配位置,不混入业务重构。
验收:route inventory 中所有原 route 仍存在;旧明确下线 route 继续 404;`cargo test -p api-server app --manifest-path server-rs/Cargo.toml` 或等价 route 回归通过。
## 阶段 2:稳定单图生成资产 Adapter 收口
- 新增 `modules/assets/generated_image_assets` 内部 Module,Interface 覆盖“provider 生成 -> 下载/base64 解码 -> MIME/extension 归一 -> OSS private upload -> HEAD -> asset_object confirm -> entity binding”。
- Adapter 输入包含 provider、prompt、negative prompt、size、reference images、OSS prefix/path/file name、asset kind、entity kind/id、slot、owner/profile/source_job_id、metadata、可选透明背景后处理。
- Adapter 输出包含 `legacy_public_path`、`object_key`、`asset_object_id`、`mime_type`、`extension`、`task_id`、`actual_prompt`。
- 首批迁移:
- Big Fish 正式图片:主图、动作图、舞台背景。
- Square Hole 图片:保留生成成功但入库失败时回退 Data URL。
- Custom World 场景图、自动草稿场景图、生成封面图。
- `asset_billing.rs` 仍由调用方显式包裹;Adapter 不扣费、不退款、不读钱包。
验收:三类调用方都经过同一 Adapter;删除旧重复 persist 函数后行为不变;Big Fish、Square Hole、Custom World 定向测试通过。
## 阶段 3:复杂媒体资产链路扩展
- 扩展 Adapter,但不把玩法图像处理规则塞进公共 Interface。
- Puzzle:
- 收口普通 generations、edits/multipart 生成结果的下载、OSS、asset object、binding。
- 拼图关卡 JSON 更新、参考图策略、UI 背景落位仍留在 Puzzle 编排层。
- Match3D:
- APIMart material sheet、VectorEngine 背景/容器/封面生成接入统一入库能力。
- 5x5 切图、绿幕透明化、格内校准、批量新增补齐规则仍留在 Match3D 专属处理器。
- 音频:
- 评估是否抽 `generated_media_assets`,但背景音乐、点击音效的计费和落位语义不与图片 Adapter 混用。
- GLB/视频:
- 仅历史转存链路复用“OSS + asset_object + binding”底层持久化能力,不恢复新草稿 GLB 生产。
验收:Puzzle 与 Match3D 的 generated 私有资产仍通过 `/api/assets/read-url` 换签读取;Match3D 不回退 Rodin/GLB;音频试听和运行态仍可播放。
## 阶段 4:超大 handler 能力内瘦身
- 在路由已模块化、资产 Adapter 已稳定后,再拆大文件,避免同时改 route 和业务实现。
- 对 `match3d.rs`、`puzzle.rs`、`custom_world.rs`、`custom_world_ai.rs`、`big_fish.rs`、`square_hole.rs` 分别按能力拆:
- `router.rs` 只挂路由。
- `handlers.rs` 只做 Axum extract、鉴权、request/response。
- `application.rs` 做 api-server 层编排。
- `assets.rs` 只放玩法专属生成资产策略。
- `mapper.rs` 只做 DTO/record 映射。
- `errors.rs` 只做该能力错误映射。
- 不把领域规则留在 handler;发现领域规则时只登记迁出候选,不在本阶段直接扩大到 `module-*` 重构。
验收:每个原超大文件显著缩小;新文件按能力可读;定向玩法测试和全量 `api-server` 测试通过。
## 阶段 5:清理、文档和最终验收
- 删除旧重复 helper、过时注释和已迁移的私有函数。
- 更新 `api-server` README:目录规则、Router 暴露规则、Adapter 边界、禁止事项。
- 更新 `.hermes/shared-memory` 中长期有效的架构约定和排障经验。
- 最终验收命令:
- `cargo check -p api-server --manifest-path server-rs/Cargo.toml`
- `cargo test -p api-server --manifest-path server-rs/Cargo.toml`
- `npm run check:server-rs-ddd`
- `npm run check:encoding`
- `git diff --check`
- `npm run api-server` 后检查 `/healthz`
- 禁止使用 `api-server:maincloud` 作为本轮 smoke。
## Public Interfaces
- HTTP route、DTO、error envelope、SpacetimeDB schema、前端调用方式默认不变。
- 新增的都是 `api-server` 内部 Rust Interface,不进入 `shared-contracts`。
- 若后续任何阶段发现必须改 contract,先更新对应阶段文档和 G1 route/contract 矩阵,再单独实施。
## Assumptions
- 本计划目标是完整收口,不是只完成第一阶段。
- 可以分阶段提交,但每个阶段都必须有文档、测试和明确退出条件。
- `generated_image_assets` 首版必须至少被三个真实调用方使用,否则不算形成有效 Module。
+1
View File
@@ -30,6 +30,7 @@ Single-context layout: read root `CONTEXT.md` when present and architecture deci
- 代码需要有完善的中文注释
- 在落地工程修改前检查是否有详细指导本次落地的文档,若没有文档或文档的完善程度仍有落地过程中编码级别的歧义优先优化文档后落地工程迭代。
- 对工程的修改不仅要落地到代码更面,还要更改对应文档,若没有生成新的文档,文档统一存在doc目录中
- 后续新增的 Markdown 文档文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`;例如 `【后端架构】api-server能力模块化与图片资产Adapter收口计划-2026-05-14.md`。不要求批量重命名历史文档,除非本次任务明确涉及该文档。
- 不要擅自把现有中文文案、注释、剧情、文档改写成英文,除非用户明确要求翻译。
- 看到中文乱码时,不要直接沿用乱码文本,也不要用英文替换;先确认文件真实编码,再决定是否修改。
- 在 PowerShell 5.1 中读取或写入文本时,必须显式使用 UTF-8;如果终端输出疑似乱码,要用 `Get-Content -Encoding UTF8`、Python 或 Node 再次核对原文。
+110
View File
@@ -0,0 +1,110 @@
# Genarrative Domain Context
Genarrative 是一个 AI 原生互动内容与小游戏平台,当前上下文记录团队在玩法、作品、运行态和平台闭环中使用的领域语言。
## Language
### Bark Battle
**汪汪声浪大作战**:
浏览器 2D 声控对战玩法,玩家通过麦克风响度触发声浪并推动能量条。
_Avoid_: 狗叫识别游戏、声纹识别玩法
**有效声浪触发**:
玩家麦克风采样点的归一化响度在冷却结束后达到或超过有效阈值时产生的一次计分输入。
_Avoid_: 有效叫声持续时长、狗叫识别结果、等待回落后的叫声
**有效阈值**:
用于判定麦克风采样是否产生有效声浪触发的响度门槛。
_Avoid_: 狗叫识别阈值、语义识别阈值
**声浪冷却**:
两次有效声浪触发之间必须满足的最小时间间隔。
_Avoid_: 叫声持续时长、回落等待时间
**能量条**:
表示玩家与对手当前声浪优势的连续对抗刻度。
_Avoid_: 血条、分数条
**单局结果**:
一局 Bark Battle 结束后形成的胜负、平局和派生统计摘要。
_Avoid_: 原始音频记录、语音内容
**Bark Battle 平台作品闭环**:
Bark Battle 从创作配置、发布作品、正式运行态到单局结果记录和作品统计的完整平台流程。
_Avoid_: 孤立 demo、只做表现深化
**轻创作配置作品**:
创作者只配置展示与难度预设字段、但不能直接配置公平性敏感规则的 Bark Battle 作品。
_Avoid_: 完整规则编辑器、固定官方关卡
**难度预设**:
Bark Battle 作品中用于选择 AI 对手行为强度的发布态配置值。
_Avoid_: 阈值配置、分数公式配置
**排行榜分榜**:
排行榜按作品、难度预设和规则集版本拆分后的独立排名空间,只收录后端裁决为玩家胜利的单局结果。
_Avoid_: 全难度混排、跨规则版本混排、失败刷分榜
**后端裁决结果**:
后端根据 start run 记录和 finish 派生指标校验后生成的正式单局结果。
_Avoid_: 前端最终分数、客户端胜负裁决
**派生指标**:
前端从本地 runtime 汇总出的不可还原原始音频的单局统计值。
_Avoid_: 原始音频、可还原语音内容
**作品统计投影**:
按作品聚合的 Bark Battle 游玩开始、完成结果、反作弊标记和最佳/平均表现摘要。
_Avoid_: 只从排行榜反推、原始音频分析、留存分析
**个人历史成绩**:
玩家本人可查看的 Bark Battle 最近完成记录和个人最佳摘要。
_Avoid_: 公开失败记录、完整无限历史、好友对比
**正式作品入口闭环**:
Bark Battle 作品从创作入口、作品详情、广场/作品卡片、我的作品到正式 runtime 路由的可发现、可进入流程。
_Avoid_: 内部试玩入口、独立活动专区
**轻配置编辑流程**:
Bark Battle 创作者用单页轻配置表单和预览卡片完成草稿保存与发布的创作流程。
_Avoid_: 多步骤向导、完整规则编辑器、拖拽编辑器
**Phase 2 实施顺序**:
Bark Battle 平台作品闭环按契约与领域规则、后端存储/API、最小前端纵切、投影体验、收口验证的顺序推进。
_Avoid_: mock 先行堆积、前后端各自发散、先做排行榜 UI
## Relationships
- 一个 **汪汪声浪大作战** 单局包含多个 **有效声浪触发**。
- 每个 **有效声浪触发** 必须达到 **有效阈值** 并满足 **声浪冷却**。
- **有效声浪触发** 推动 **能量条**。
- **能量条** 在倒计时结束时产生一个 **单局结果**。
- **单局结果** 可以被后端记录为派生摘要,但不包含原始麦克风音频。
- **Bark Battle 平台作品闭环** 包含发布态作品配置、**正式作品入口闭环**、run start / finish、个人历史成绩、**作品统计投影** 和最小排行榜。
- Phase 2 的 Bark Battle 作品是 **轻创作配置作品**,通过 **轻配置编辑流程** 创建;配置范围限制为标题、描述、主题/背景预设、狗狗皮肤预设和排行榜开关,其中 **难度预设** 只影响 AI 对手行为。
- **排行榜分榜** 由 `workId + difficultyPreset + rulesetVersion` 唯一确定,只收录 `serverResult = player_win` 的单局结果。
- **单局结果** 的正式胜负、分数与排行榜成绩来自 **后端裁决结果**;前端只提交 **派生指标**,客户端结果仅用于 debug/对账。
- **作品统计投影** 计入成功 start run 的 playStartCount、后端接受 finish 的 finishCount、胜/平/负、flagged、leaderboard 以及最佳/平均能量表现。
- **个人历史成绩** 由最近记录列表和个人最佳摘要组成,只允许本人查看;排行榜只公开入榜胜利成绩。
- **正式作品入口闭环** 必须覆盖创作入口、作品详情 CTA、广场/作品卡片、我的作品/个人作品架、稳定作品 ID runtime 路由和 `work_play_start` 埋点。
- **Phase 2 实施顺序** 固定为:契约与领域规则 → SpacetimeDB 表/reducer 与 api-server BFF → 最小前端纵切 → 投影与列表体验 → 收口验证。
## Example dialogue
> **Dev:** “第二阶段排行榜要按玩家狗叫持续时间排序吗?”
> **Domain expert:** “不按持续时间;Bark Battle 的计分输入是有效声浪触发,排行榜只能基于触发次数、峰值、能量条结果等派生摘要。”
## Flagged ambiguities
- “有效叫声”曾同时指代持续时长合规的声音片段和瞬时响度触发;已解析为 **有效声浪触发**,不再要求 `minBarkDurationMs` / `maxBarkDurationMs`,也不等待响度回落。
- “第二阶段”曾可能指玩法表现深化或平台接入;已解析为 **Bark Battle 平台作品闭环**,优先补正式 play type、作品配置、发布、正式 runtime、结果持久化、历史成绩、作品统计和最小排行榜。
- “创作者可配置作品”曾可能指完整规则编辑器;已解析为 **轻创作配置作品**,Phase 2 不允许创作者直接配置单局时长、有效阈值、声浪冷却、AI 细粒度参数、分数公式或反作弊阈值。
- “难度预设”曾可能影响阈值、冷却或计分;已解析为只影响 AI 对手行为,排行榜按 `workId + difficultyPreset + rulesetVersion` 分榜。
- “单局结果”曾可能由前端直接决定;已解析为必须由 **后端裁决结果** 决定,前端只提交触发次数、音量、能量、连击、时长等 **派生指标**。
- “排行榜成绩”曾可能收录胜/平/负或按触发次数排序;已解析为只收录玩家胜利局,并以 `finalEnergy` 优先、`triggerCount` / `maxVolume` / 标准局时长接近度 / `finishedAt` 作为后续排序因子。
- “作品统计”曾可能只从排行榜反推;已解析为独立 **作品统计投影**,失败、平局和 flagged finish 都可进入统计,但 rejected finish 不进入完成统计。
- “个人历史成绩”曾可能指完整无限历史或公开记录;已解析为仅本人可见的最近记录列表 + 个人最佳摘要,不公开失败、平局或 flagged 历史。
- “入口闭环”曾可能只指内部 demo 或单个详情 CTA;已解析为 **正式作品入口闭环**,不新增独立专区或活动页。
- “创作编辑”曾可能指多步骤向导或完整编辑器;已解析为 **轻配置编辑流程**,使用单页表单 + 预览卡片完成保存草稿、发布和发布后跳转作品详情。
- “实施顺序”曾可能按 UI 或功能并行发散;已解析为契约/领域规则先行,再做后端存储/API,随后打通最小前端纵切,最后补投影体验与收口验证。
+4
View File
@@ -49,3 +49,7 @@ AI 文字游戏模板接入以 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_
- `reference/`:偏目录、速查、检索辅助。
- `tracking/`:偏埋点原始事实和聚合投影查询,不放任务进度或钱包对账。
- `operations/`:偏后台运营核查、对账和排障查询。
## 文档命名规则
后续新增的 Markdown 文档文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`。标签用于跨目录检索,不替代上面的目录分类;历史文档不要求批量重命名,除非本次任务明确涉及该文档。
+1
View File
@@ -5,6 +5,7 @@
## 文档列表
- [CHILD_MOTION_DEMO_WARMUP_LEVEL_DESIGN_2026-05-09.md](./CHILD_MOTION_DEMO_WARMUP_LEVEL_DESIGN_2026-05-09.md):4-8 岁儿童动作识别互动玩法 Demo 固定热身关的横屏体验流程、识别目标、表现需求与待确认事项。
- [TAONIER_BRAND_LOGO_CONCEPTS_2026-05-13.md](./TAONIER_BRAND_LOGO_CONCEPTS_2026-05-13.md):候选产品名“陶泥儿”的品牌定位归纳、gpt-image-2 Logo 概念图和主标选择建议。
- [CUSTOM_WORLD_CREATOR_INPUT_AND_AI_BOUNDARY_DESIGN_2026-04-06.md](./CUSTOM_WORLD_CREATOR_INPUT_AND_AI_BOUNDARY_DESIGN_2026-04-06.md):自定义世界里陶泥儿主输入与 AI 分工边界设计。
- [CUSTOM_WORLD_CREATOR_MANUAL_AI_SYSTEM_BALANCE_DESIGN_2026-04-12.md](./CUSTOM_WORLD_CREATOR_MANUAL_AI_SYSTEM_BALANCE_DESIGN_2026-04-12.md):自定义世界创作里“手填锚点 / AI 可改初稿 / 系统托管层”的平衡设计。
- [CUSTOM_WORLD_CREATOR_PURE_AGENT_COMPARISON_AND_CONVERSION_DESIGN_2026-04-12.md](./CUSTOM_WORLD_CREATOR_PURE_AGENT_COMPARISON_AND_CONVERSION_DESIGN_2026-04-12.md):纯 Agent 式创作工具与结构化工作台方案的优缺点对比,以及转型设计。
@@ -0,0 +1,423 @@
# 陶泥儿品牌 Logo 概念稿
> 本稿是围绕候选产品名“陶泥儿”的品牌视觉探索,不替代当前已冻结的“百梦”正式命名口径。若后续确认更名,需要另起产品命名、前后端文案和商标检索落地方案。
## 1. 品牌定位归纳
“陶泥儿”适合承接的不是传统陶艺或儿童黏土,而是“把灵感塑形成可玩内容”的 AI 创作平台隐喻。
核心关键词:
- 精品:作品不是随手糊出来,而是经过 AI 辅助打磨、可被消费和传播的轻精品内容。
- UGC:用户是主要造物者,平台降低创作门槛。
- 创作:从一句脑洞、一个梗、一张图,生成小游戏、互动作品或可分享内容。
- 裂变与梗:名字要支持“开捏”“捏个梗”“捏个小游戏”这类用户语言。
- 轻度休闲:体验应松弛、即时、好玩,不走硬核生产工具气质。
- AI:AI 是塑形能力,不是冷冰冰的技术标签。
推荐品牌主张:
```text
把脑洞捏成小游戏
```
备选表达:
```text
捏个脑洞,马上开玩
AI 开捏,人人会创作
随手造梗,随心开玩
```
## 2. 生成原则
本轮使用仓库 GPT-image-2 / VectorEngine 工作流生成 Logo 概念图,生成时刻意要求“无文字 Logo 图标”。
原因:
- AI 生图直接生成中文品牌字容易出现笔画错误,不适合作为正式字标。
- 当前阶段更适合先确定图形符号方向,再由设计师或前端继续做矢量化、字标搭配和多尺寸适配。
- 图标需要优先服务 App icon、平台左上角品牌、分享卡片和加载页,而不是一次性海报图。
生成文件:
```text
public/branding/taonier-logo-v3-concepts/
├─ taonier-logo-v3-contact-sheet.png
├─ taonier-v3-finger-spark.png
├─ taonier-v3-seed-pop.png
├─ taonier-v3-magic-dot.png
├─ taonier-v3-work-gem.png
└─ taonier-v3-soft-t.png
public/branding/taonier-logo-magic-dot-concepts/
├─ taonier-logo-magic-dot-contact-sheet.png
├─ taonier-magic-dot-orbit.png
├─ taonier-magic-dot-seal.png
├─ taonier-magic-dot-squish.png
├─ taonier-magic-dot-mold.png
└─ taonier-magic-dot-bloom.png
public/branding/taonier-logo-flat-concepts/
├─ taonier-logo-flat-contact-sheet.png
├─ taonier-flat-play-clay.png
├─ taonier-flat-spark-clay.png
├─ taonier-flat-meme-smile.png
├─ taonier-flat-loop-mold.png
└─ taonier-flat-seal-blocks.png
public/branding/taonier-logo-concepts/
├─ taonier-logo-contact-sheet.png
├─ taonier-clay-spark.png
├─ taonier-play-mold.png
├─ taonier-meme-bubble.png
├─ taonier-creation-loop.png
└─ taonier-premium-seal.png
```
生成脚本:
```text
scripts/generate-taonier-logo-concepts.mjs
```
## 3. V3-03 一捏成型延展
这一组专门沿 V3 “一捏成型”继续打磨。目标是保留“两个软形触点 + 中央作品核”的成型瞬间,同时降低括号感、碰撞特效感和功能按钮感。
![陶泥儿 Logo 一捏成型延展总览](../../public/branding/taonier-logo-magic-dot-concepts/taonier-logo-magic-dot-contact-sheet.png)
### 3.1 捏合星核
![捏合星核](../../public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-orbit.png)
定位:一捏成型方向的主标首选。
这个方向最稳地保留了“左右合拢、中央成型”的核心动作,中心青绿色星核形成了明确焦点,整体比原 V3-03 更完整,也没有明显播放器、聊天或表情联想。
优点:
- 结构清楚,第一眼能看出“合拢生成”。
- 元素少,小尺寸适配潜力好。
- 中央星核可以做加载、生成成功、发布完成等动效延展。
风险:
- 左右软形仍有一点括号感,后续矢量化可把外轮廓做得更不对称、更像被捏塑的软泥。
建议用途:主 Logo 备选首选、AI 生成按钮、启动动效核心符号。
### 3.2 成型印记
![成型印记](../../public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-seal.png)
定位:完整主标感最强的延展方向。
这个方向把左右触点收成一个更完整的软形图腾,减少了“两个括号”的割裂感。视觉上更像独立品牌符号,但也因此少了一点“捏合动作”的即时感。
建议用途:主 Logo 强备选;若选择它,后续应去掉背景底色并强化中心负形星点。
### 3.3 软泥合拍
![软泥合拍](../../public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-squish.png)
定位:轻松、年轻、动效友好。
这个方向的上下软形更活泼,适合表达“啪嗒一下成型”。但静态 Logo 中的黄色星点和短线略像特效贴纸,主标使用前需要继续简化。
建议用途:生成中动效、运营图、互动反馈,不建议直接定为主 Logo。
### 3.4 灵感模口
![灵感模口](../../public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-mold.png)
定位:最有“模口 / 造物容器”意味。
这个方向图形独特,和“从软泥模口里生成作品”的隐喻贴合。但外形复杂度比 01、02 更高,边缘细节在小尺寸下可能损失。
建议用途:主 Logo 备选探索,适合继续做专业矢量简化。
### 3.5 捏开灵感
![捏开灵感](../../public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-bloom.png)
定位:温和、包裹、生成容器。
这个方向亲和、平衡,但整体像眼睛 / 容器 / 开合结构,陶泥儿的“捏”动作弱一些。
建议用途:AI 生成入口、等待态、创作容器辅助图形。
## 4. V3 抽象主标候选
V3 根据评审反馈重新避开了五个问题:播放三角、褐色陶土主色、聊天气泡 / 表情包、循环符号,以及过多碎元素。方向转为更抽象、更亮眼、更像长期主 Logo 的符号。
![陶泥儿 Logo V3 概念总览](../../public/branding/taonier-logo-v3-concepts/taonier-logo-v3-contact-sheet.png)
### 4.1 灵感捏痕
![灵感捏痕](../../public/branding/taonier-logo-v3-concepts/taonier-v3-finger-spark.png)
定位:主 Logo 首选。
这个方向用醒目的珊瑚红软形、指纹捏痕和星点负形建立记忆点。它不再依赖“陶泥的褐色”,而是用“被捏过的痕迹”表达陶泥儿的核心动作:用户把脑洞捏成作品。
优点:
- 第一眼足够醒目,远离旧版褐色和播放器感。
- 指纹捏痕有独特性,能承接“人人创作”和“亲手塑形”。
- 元素少,适合继续矢量化和小尺寸适配。
风险:
- 指纹弧线后续需要进一步简化,避免在 24px 以下变糊。
- 星点比例要克制,避免变成普通灵感图标。
建议用途:主 Logo、App icon、平台顶栏、启动页、生成按钮。
### 4.2 脑洞种子
![脑洞种子](../../public/branding/taonier-logo-v3-concepts/taonier-v3-seed-pop.png)
定位:创意生长与新手友好。
这个方向从“灵感发芽”切入,比陶泥更偏创造生命力。它亲和、可爱,但容易让用户联想到教育、植物、儿童启蒙或种植类产品。
建议用途:新手引导、创作孵化、儿童 / 寓教于乐支线,不建议作为主 Logo。
### 4.3 一捏成型
![一捏成型](../../public/branding/taonier-logo-v3-concepts/taonier-v3-magic-dot.png)
定位:AI 把灵感合成为作品的瞬间。
这个方向很简洁,用左右两个软形触点和中心星点表达“捏合”。它避开了播放器和聊天气泡,也能做动效,但静态图形目前稍像碰撞特效或括号,需要继续重绘增强独特轮廓。
建议用途:生成按钮、AI 施法动效、主 Logo 备选微调方向。
### 4.4 作品胶囊
![作品胶囊](../../public/branding/taonier-logo-v3-concepts/taonier-v3-work-gem.png)
定位:精品内容和作品沉淀。
这个方向更稳、更精品,青绿色也比褐色更吸睛。但整体像水滴、宝石或通用内容图标,和“捏”这个动作的关系弱。
建议用途:精选作品、作品库、创作者中心,不建议优先做主 Logo。
### 4.5 软体 T 形
![软体 T 形](../../public/branding/taonier-logo-v3-concepts/taonier-v3-soft-t.png)
定位:英文辅助名 / Taonier 的抽象首字母。
这个方向试图做更品牌化的抽象符号,但当前形体还不够自然,也未形成足够强的“陶泥儿”心智。若未来英文名确定为 `Taonier` 或类似形式,可以继续沿这个方向做专业字母标重绘。
建议用途:英文标识探索,不作为当前主 Logo 首选。
## 5. V2 扁平矢量候选
第一批图形偏 3D 和拟物,更适合作为吉祥物、运营图或启动页气氛图,不适合作为长期主 Logo。V2 已把约束收紧为扁平、矢量、少元素、强轮廓和小尺寸可识别。
![陶泥儿 Logo 扁平概念总览](../../public/branding/taonier-logo-flat-concepts/taonier-logo-flat-contact-sheet.png)
### 5.1 扁平开捏
![扁平开捏](../../public/branding/taonier-logo-flat-concepts/taonier-flat-play-clay.png)
定位:最直接的主 Logo 候选。
这个方向用一团柔软陶泥承载播放符号,用户一眼能理解“点开玩 / 马上玩”,同时外形保留“捏出来”的不规则软泥感。
优点:
- 识别速度最快,移动端小尺寸也成立。
- 符合主流 App Logo 语言,亲和、不重、不技术冷。
- 和“把脑洞捏成小游戏”的主张绑定最强。
风险:
- 播放符号是常见母题,后续矢量化时要通过不规则软泥外轮廓、颜色和字标形成独特资产。
建议用途:主 Logo 首选、App icon、平台顶栏、分享卡片角标。
### 5.2 灵感泥星
![灵感泥星](../../public/branding/taonier-logo-flat-concepts/taonier-flat-spark-clay.png)
定位:AI 创作与灵感生成。
这个方向比“扁平开捏”更品牌化,中心负形星点表达灵感、AI 生成和创意爆发。它没有播放符号那么直白,但更容易和“陶泥儿”的创作平台气质绑定。
优点:
- 图形更简洁,品牌记忆点强。
- 陶泥心智、AI 灵感和精品感比较平衡。
- 适合未来扩成字标、启动页和生成态动效。
风险:
- 对“小游戏/马上玩”的表达弱于播放符号。
建议用途:主 Logo 强备选、创作首页、AI 生成按钮和品牌主视觉。
### 5.3 造梗笑泥
![造梗笑泥](../../public/branding/taonier-logo-flat-concepts/taonier-flat-meme-smile.png)
定位:社交传播和玩梗亲和力。
这个方向的气泡与笑脸非常亲和,适合表达“分享快乐”和“造梗”。但它和聊天、社区类产品的通用图形过近,作为主 Logo 可能会让用户误判产品品类。
建议用途:社区、评论、分享、活动贴纸,不建议做主 Logo。
### 5.4 共创泥环
![共创泥环](../../public/branding/taonier-logo-flat-concepts/taonier-flat-loop-mold.png)
定位:AI 与用户共创闭环。
这个方向表达共创与循环,但生成结果带有偏柔和彩虹渐变的视觉倾向,与“陶泥儿”的软泥名称关联不够直观,也不如 01/02 容易记住。
建议用途:创作流程、共创能力、生成进度辅助图形。
### 5.5 精品泥印
![精品泥印](../../public/branding/taonier-logo-flat-concepts/taonier-flat-seal-blocks.png)
定位:精品作品和内容集合。
这个方向像内容平台或作品库入口,能表达图片、用户、游戏等多形态内容。但图形元素较多,主标识别不如 01/02 凝练。
建议用途:精选作品、作品集、创作者中心、内容品质标识。
## 6. V1 立体探索
### 6.1 灵感陶团
![灵感陶团](../../public/branding/taonier-logo-concepts/taonier-clay-spark.png)
定位:AI 共创与灵感造物。
这个方向把“陶泥”作为主视觉,内部用发光火花和节点表达 AI 赋能。它最贴“陶泥儿”名字本身,也能说明平台不是普通小游戏集合,而是从灵感生成作品的创作容器。
优点:
- 与“陶泥儿”的名称绑定最强。
- 有 AI、创作、造物的综合含义。
- 适合启动页、品牌介绍、创作首页空状态。
风险:
- 小尺寸下细节偏多,需要后续矢量化时压缩节点和纹理。
- 如果色彩处理不当,会回到手工陶艺联想。
建议用途:品牌主视觉备选、官网/启动页、创作入口图形。
### 6.2 开玩模具
![开玩模具](../../public/branding/taonier-logo-concepts/taonier-play-mold.png)
定位:把脑洞捏成小游戏。
这个方向用软陶捏出播放符号,最直接地连接“创作”和“马上玩”。它比单纯陶泥团更有产品动作,也更适合轻休闲、小游戏、短内容传播。
优点:
- 识别强,小尺寸也清楚。
- 与轻度休闲小游戏的关系最直接。
- 适合作为 App icon 和主 Logo 图形。
风险:
- 播放符号相对常见,需要后续在外轮廓、捏痕和色彩上做独特性。
- 如果三角形过硬,会削弱“陶泥儿”的柔软感。
建议用途:主 Logo 首选、App icon、分享卡片角标、加载态图形。
### 6.3 造梗气泡
![造梗气泡](../../public/branding/taonier-logo-concepts/taonier-meme-bubble.png)
定位:社交传播、玩梗、裂变。
这个方向把陶泥变形成聊天气泡和表情,强调“梗”和“传播”。它最有社交平台感,也适合表情包、活动贴纸和运营视觉。
优点:
- 传播感强,年轻、轻松、容易做 IP 化。
- 能承接社区、评论、分享和玩梗场景。
- 比较容易延展成贴纸和表情包。
风险:
- 偏软萌,可能削弱“精品 AI 创作平台”的质感。
- 作为主 Logo 容易显得像聊天或表情产品。
建议用途:社区模块、活动运营、IP 辅助形象,不建议作为唯一主 Logo。
### 6.4 共创回路
![共创回路](../../public/branding/taonier-logo-concepts/taonier-creation-loop.png)
定位:AI 与用户共同迭代生成。
这个方向用软陶带形成循环和造物轨迹,表达“灵感 -> AI 塑形 -> 用户修改 -> 作品传播”的闭环。它比其他方向更抽象,也更有平台级和工具级气质。
优点:
- 高级、简洁,避免儿童化。
- 适合表达 AI 共创、迭代和作品循环。
- 可用于创作者工作台或生成进度标识。
风险:
- 与“陶泥儿”名称的直观关联较弱。
- 缺少小游戏和玩梗的即时识别。
建议用途:创作流程标识、AI 共创能力图标、品牌辅助图形。
### 6.5 精品泥印
![精品泥印](../../public/branding/taonier-logo-concepts/taonier-premium-seal.png)
定位:精品内容、作品认证、创作者成果。
这个方向像一个被压印的软陶徽章,中间有方块和火花,比较适合表达“作品被打磨成型”。它的内容平台感强于游戏入口感。
优点:
- 精品感和作品库气质较强。
- 适合作品认证、精选、创作者徽章。
- 与“陶泥压印”隐喻相对自然。
风险:
- 细节较多,主 Logo 小尺寸可读性不如“开玩模具”。
- 徽章感偏静态,轻休闲的即时性稍弱。
建议用途:精选作品标识、创作者荣誉、内容品质标签。
## 7. 推荐结论
优先级建议:
```text
主 Logo 首选:V3 01 灵感捏痕
一捏成型首选:V3-03 延展 01 捏合星核
完整主标备选:V3-03 延展 02 成型印记
英文标识探索:V3 05 软体 T 形
精品内容辅助:V3 04 作品胶囊
新手 / 寓教于乐辅助:V3 02 脑洞种子
```
若要兼顾主流、亲和、醒目和“陶泥儿”的动作隐喻,优先继续打磨 V3 “灵感捏痕”。
若想把 Logo 做得更抽象、更像 AI 生成瞬间,可以继续打磨 V3-03 延展中的“捏合星核”或“成型印记”。
V1 的 3D 图标不建议直接作为主 Logo,只适合做运营图、吉祥物探索或风格参考;V2 的播放、气泡、碎元素方向本轮已降级为历史探索。
## 8. 后续落地建议
1. 先围绕 V3 “灵感捏痕”做 3 到 5 个专业矢量微调版:减少指纹线条、强化软形轮廓、测试深色 / 浅色底。
2. 同步对 V3-03 “捏合星核”做一版更独特的轮廓重绘,弱化括号感,保留中央成型星核。
3. 字标不要直接使用生图结果,应单独设计“陶泥儿”中文字标,并准备英文辅助名。
4. 正式应用前做商标近似检索,重点覆盖第 9、35、38、41、42 类。
5. 若确认替换“百梦”,再更新现有命名规范文档、前端品牌组件、HTML metadata、后台和后端默认文案。
@@ -0,0 +1,101 @@
# 宝贝爱画寓教于乐独立关卡 PRD 2026-05-13
## 1. 目标
新增寓教于乐内容线独立关卡:
```text
宝贝爱画
```
工程 ID 固定为:
```text
baby-love-drawing
```
该关卡默认出现在“发现 / 寓教于乐”板块下方。当前阶段只做本地 Demo 保存,验证完成后再补正式持久化。
## 2. 关卡结构
启动宝贝爱画后进入绘画运行态:
1. 屏幕中央是带边框的空白画板;
2. 画板边框内是可绘画区域;
3. 画板外左侧展示彩虹 7 色;
4. 画板外右侧中下方展示画笔和橡皮;
5. 用户进入内容后默认手持画笔;
6. 手持画笔或橡皮时,屏幕上实时显示跟随右手位置的工具图案标识。
## 3. 输入规则
颜色选择:
1. 仅检测左手;
2. 左手悬停在某个颜色区域 1.5 秒后,选中该颜色;
3. 颜色固定为彩虹 7 色。
工具切换:
1. 右手移动到画笔或橡皮工具区域;
2. 右手握拳后,将手里的工具切换为对应工具。
绘画与擦除:
1. 右手在画布区域握拳时,当前工具生效;
2. 当前工具为画笔时留下轨迹;
3. 当前工具为橡皮时擦除轨迹;
4. 右手张开时,画笔或橡皮抬起,不在画布上生效。
按钮选择:
1. 完成;
2. 使用绘画魔法;
3. 保存;
4. 再画一张;
5. 返回。
以上按钮都使用任一手悬停 2 秒完成选中。
## 4. 绘画魔法
用户完成绘画后,可使用“绘画魔法”。
绘画魔法使用 image-2,以用户绘画内容和笔触轨迹生成对应绘本风格图片内容。
前端不得直接读取、拼接或暴露图片生成密钥。image-2 调用必须通过后端代理。
## 5. 保存规则
当前只做本地 Demo 保存。
保存规则:
1. 魔法生成前保存,只保存一张原图;
2. 若用户未保存原图,直接点击魔法生成,则魔法生成后保存时同时保存原图和魔法图;
3. 保存完毕后,可继续“再画一张”或“返回”。
## 6. 展示与开关
宝贝爱画只属于寓教于乐内容线。
`VITE_ENABLE_EDUTAINMENT_ENTRY` 关闭时:
1. 不展示“发现 / 寓教于乐”频道;
2. 不展示宝贝爱画默认卡片;
3. 不允许通过 `/runtime/baby-love-drawing` 直达运行态。
## 7. 验收
1. 寓教于乐开启时,“发现 / 寓教于乐”下方展示“宝贝爱画”默认关卡卡片;
2. 寓教于乐关闭时,不展示宝贝爱画,也不能直达运行态;
3. 进入后展示空白画板、彩虹 7 色、画笔和橡皮;
4. 默认工具为画笔;
5. 左手悬停颜色 1.5 秒后选中颜色;
6. 右手移动到工具区并握拳后切换画笔或橡皮;
7. 右手握拳在画布内绘制或擦除,张开时不生效;
8. 任一手悬停按钮 2 秒后触发按钮;
9. 完成后可保存原图;
10. 完成后可使用绘画魔法生成绘本风格图片;
11. 未保存原图直接使用绘画魔法后,保存会同时保存原图和魔法图;
12. 保存后可再画一张或返回。
@@ -29,11 +29,24 @@
2. 模板名称:`宝贝识物`;
3. 两个物品;
4. 两个物品图;
5. 作品标签。
5. 游戏视觉主题包;
6. 作品标签。
物品图使用 VectorEngine `gpt-image-2-all` / image-2 生成。图片生成只能走后端或后续后端预留接口,前端不得泄露 `VECTOR_ENGINE_API_KEY`。
物品图使用 VectorEngine `gpt-image-2-all` / image-2 生成。图片生成只能走后端接口,前端不得读取、拼接或暴露 `VECTOR_ENGINE_API_KEY`。
本地 Demo 阶段若真实生图接口未接入完成,允许前端 service 返回明确标记为 `placeholder` 的占位图形,用于打通创作到结果页的交互链路;该占位结果不得伪装成正式 image-2 资产。
每个关键词只生成一张围绕该关键词的单一物品形象。生成 prompt 必须锁定寓教于乐板块统一的卡通绘本草地舞台插画风,但最终画面不生成背景、场景、氛围渲染、人物、手、篮子、礼物盒、文字、水印或 UI。服务端必须把生成结果转成透明 PNG,并执行透明抠图后处理;只有透明抠图后的素材才允许写入草稿 `itemAssets` 并进入游戏运行态。
同一次创作还必须使用 image-2 生成游戏视觉主题包,包含背景环境、UI 装饰框、礼物盒、篮子和烟雾弹出特效资源。主题包必须继续保持寓教于乐插画风,并根据用户填写的两个物品关键词匹配主题:例如关键词偏动漫角色或玩具时,背景环境和元素可使用动漫、玩具主题;关键词偏水果时,背景环境和元素可匹配果园、自然主题;其它关键词按其语义匹配合适主题。主题包不得改变关卡玩法规则,不新增文字说明、额外按钮或额外判定规则。
视觉主题包的资源边界:
1. 背景环境图不做透明抠图,但必须保证屏幕中间、中下方和底部左右篮子区域清爽,不遮挡放大后的物品、礼物盒和篮子;
2. UI 装饰框用于字幕条和计数器风格化包装,只生成装饰边框和主题点缀,不生成文字、数字或按钮;
3. 礼物盒资源输出为透明 PNG,运行态按当前礼盒视觉的 2 倍尺寸展示,素材主体必须饱满清晰;
4. 篮子资源输出为透明 PNG,运行态按当前篮子视觉的 1.5 倍尺寸展示,左右篮子仍固定为两个物品对应选项,篮子造型资源可以复用同一张主题篮子图;
5. 烟雾弹出特效资源输出为透明 PNG,用于礼物盒打开瞬间覆盖开盒区域并承接中央物品弹出,不生成物品、篮子、礼物盒或文字。
当前本地 Demo 阶段已接入真实 image-2 资源链路。创作提交必须成功获得 `generationProvider = "vector-engine-gpt-image-2"` 的两个物品透明 PNG 和完整视觉主题包后,才能进入结果页、试玩或发布;若后端接口、登录态、VectorEngine 配置或上游生成失败,前端必须停留在生成失败状态并展示错误,不得静默回退为占位图。历史草稿中若仍存在 `generationProvider = "placeholder"` 的占位资源,结果页必须提示重新生成,试玩和发布前必须先补齐 image-2 资源。
## 4. 标签规则
@@ -63,6 +76,8 @@
试玩按钮进入宝贝识物首关运行态,运行态消费当前草稿中的两个物品名称和两张物品图,不重新生成或改写物品内容。
若草稿包含视觉主题包,运行态还必须消费该主题包中的背景环境、UI 装饰、礼物盒、篮子和烟雾弹出特效资源;旧草稿或接口失败时允许回退到当前 CSS 绘本风兜底。
## 6. 发布后体验
发布完成后作品应进入寓教于乐内容线,并在寓教于乐入口开启时可被板块消费。
@@ -73,27 +88,30 @@
本 PRD 同步约束首关运行态,已确认规则包括:
1. 礼物盒打开在本地调试绑定 `F` 键;
1. 进入关卡后礼物盒自动打开并弹出首个随机物品;
2. 每轮仅中间礼物盒跳出的物品随机;左右两侧篮子固定为当前草稿两个物品的顺序;
3. 下一关按钮当前占位;
4. 不新增用户未确认的计时、失败次数、分数、体力或难度递增。
5. 屏幕中上方字幕固定为“将物品放入对应的篮子里”。
6. 礼物盒位于屏幕中下方,任意手抬起后打开并跳出下一个随机物品。
6. 礼物盒位于屏幕中下方并按当前视觉放大一倍,首次进入关卡和每次正确反馈结束后的新轮次都从上方落下后自动打开。
7. 屏幕下方左侧和右侧分别展示两个固定篮子,左侧固定使用草稿第一个物品图,右侧固定使用草稿第二个物品图。
8. 明确左手连续横向移动达到阈值时将当前物品送入左侧篮子,明确右手连续横向移动达到阈值时将当前物品送入右侧篮子;选篮不使用动作名判定,侧别未知的手部轨迹不参与选篮。
9. 正确时展示“真棒”字幕和正确特效;错误时展示“再想一想吧”字幕和错误特效,物品回到中央。
10. 成功 20 次后展示“恭喜你!小朋友!”字幕和特效,并展示“再来一次”和“下一关”按钮。
11. 当前本地 Demo 阶段音效与语音播报接口只预留调用点,不在前端写死外部硬件或服务接口。
8. 左右篮子按当前视觉放大 50%。
9. 礼物盒打开时播放烟雾特效,中央物品从烟雾特效中弹出;物品弹出后礼物盒从舞台移除。
10. 明确左手连续横向移动达到阈值时将当前物品送入左侧篮子,明确右手连续横向移动达到阈值时将当前物品送入右侧篮子;选篮不使用动作名判定,侧别未知的手部轨迹不参与选篮。
11. 正确时展示“真棒”字幕和正确特效;错误时展示“再想一想吧”字幕和错误特效,物品回到中央。
12. 成功 20 次后展示“恭喜你!小朋友!”字幕和特效,并展示“再来一次”和“下一关”按钮。
13. 当前本地 Demo 阶段音效与语音播报接口只预留调用点,不在前端写死外部硬件或服务接口。
## 8. 验收
1. 创作入口显示 `宝贝识物` 并可进入模板表单。
2. 未填写任一物品名称时不能生成草稿。
3. 生成草稿后进入结果页,展示两个物品名称和物品图。
4. 草稿标签中始终包含精确 `寓教于乐`。
5. 发布 payload 始终包含精确 `寓教于乐`。
6. 发布完成后出现分享弹窗或发布完成状态。
7. 前端不读取或暴露 VectorEngine 密钥。
8. 结果页试玩进入宝贝识物运行态,不再显示“试玩关卡正在接入中”。
9. 运行态可通过 `F` 打开礼物盒,通过鼠标左键拖动映射左手横向移动,通过鼠标右键拖动映射右手横向移动。
10. 成功 20 次后出现“再来一次”和“下一关”按钮。
4. 生成草稿后包含视觉主题包,主题包含背景环境、UI 装饰框、礼物盒、篮子和烟雾弹出特效资源。
5. 草稿标签中始终包含精确 `寓教于乐`。
6. 发布 payload 始终包含精确 `寓教于乐`。
7. 发布完成后出现分享弹窗或发布完成状态。
8. 前端不读取或暴露 VectorEngine 密钥。
9. 结果页试玩进入宝贝识物运行态,不再显示“试玩关卡正在接入中”。
10. 运行态通过鼠标左键拖动映射左手横向移动,通过鼠标右键拖动映射右手横向移动。
11. 成功 20 次后出现“再来一次”和“下一关”按钮。
+61 -62
View File
@@ -4,7 +4,7 @@
- 需求来源:用户提供的视频 `C:\Users\DSK\Videos\一款双方比狗叫的游戏 - 1.一款双方比狗叫的游戏(Av116504192360177,P1).mp4`,并已在 `.hermes/plans/2026-05-11_144229-bark-battle-2d-game-bdd-ddd-tdd-plan.md` 中完成抽帧分析和玩法方案整理。
- 玩法定位:浏览器 2D 声控狗叫对战小游戏,暂定中文名 `汪汪声浪大作战`,英文代号与 play type ID 建议为 `bark-battle`。
- 核心玩法:双方狗狗在 30 秒限时内通过麦克风输入“狗叫声”进行声浪拔河;系统依据声音强度、有效叫声次数和叫声节奏计算推动力,实时推动顶部红蓝能量条;倒计时结束后按能量条位置判定胜负或平局。
- 核心玩法:双方狗狗在 30 秒限时内通过麦克风输入“狗叫声”进行声浪拔河;系统依据声音强度、有效声浪触发次数和声浪节奏计算推动力,实时推动顶部红蓝能量条;倒计时结束后按能量条位置判定胜负或平局。
- 文档目的:为产品、测试、前端、后端在编码前统一可验证验收口径;本文只定义 PRD/BDD 级行为与测试映射,不实现工程代码。
## 角色与目标
@@ -20,14 +20,14 @@
### 用户目标
- 玩家可以在开局前完成麦克风授权和环境噪音校准。
- 玩家发出有效狗叫时,能看到叫声计数、狗狗动画、拟声词/冲击波以及能量条变化。
- 低于阈值的背景噪音不会被误计为有效叫声。
- 玩家产生有效声浪触发时,能看到声浪计数、狗狗动画、拟声词/冲击波以及能量条变化。
- 低于阈值的背景噪音不会被误计为有效声浪触发。
- 单局在 30 秒后给出明确胜负、平局和关键数据。
- 移动端和不支持麦克风的环境不会进入不可操作状态。
### 非目标
- MVP 不要求识别“是否真实狗叫”,不引入机器学习声纹/物种分类;有效输入以音量阈值、峰值间隔、持续时间和校准结果为准。
- MVP 不要求识别“是否真实狗叫”,不引入机器学习声纹/物种分类;有效输入以音量阈值、峰值冷却间隔和校准结果为准。
- MVP 不要求实时联机对战;可先按“玩家 vs AI 对手”完成单机浏览器 runtime。
- MVP 不要求成绩持久化、作品发布、作品架、广场和排行榜;若后续接入 Genarrative 作品闭环,需要另补玩法类型集成 PRD/技术文档。
- MVP 不要求在 UI 中长期展示大段规则说明;游戏界面应保持倒计时、能量条、狗狗、麦克风状态和结算信息为主。
@@ -38,10 +38,10 @@
- 单局时长:默认 30 秒,从正式进入 `playing` 阶段开始计时。
- 能量条:使用 `-100` 到 `100` 的连续值表示,负数偏对手侧,正数偏玩家侧,`0` 为中线。
- 平局阈值:倒计时结束时,若能量条绝对值小于或等于 `drawThreshold`,判定平局;具体数值由实现配置,但测试需可注入固定阈值。
- 有效叫声:一次有效叫声至少满足:音量超过校准后的有效阈值、与上一次有效峰值间隔不小于 `minBarkGapMs`、持续时长在 `minBarkDurationMs` 到 `maxBarkDurationMs` 之间。
- 背景噪音:校准阶段采集到的环境声用于计算动态阈值;低于阈值的输入不得增加叫声次数,也不得让能量条出现可见推进。
- 推动力:玩家推动力由音量分数、有效叫声频率和连击加成组成;能量条按玩家推动力与对手推动力差值移动,并被限制在 `-100` 到 `100`。
- UI 反馈:有效叫声应触发可观察反馈,包括玩家侧狗狗张嘴/吠叫动画、拟声词或冲击波;反馈不应遮挡倒计时和顶部能量条。
- 有效声浪触发:一次有效声浪触发满足:当前麦克风采样点的归一化响度达到或超过校准后的有效阈值,且与上一次有效声浪触发间隔不小于 `minBarkGapMs`;不再要求持续高响度时长达标,也不等待响度回落。
- 背景噪音:校准阶段采集到的环境声用于计算动态阈值;低于阈值的输入不得增加声浪触发次数,也不得让能量条出现可见推进。
- 推动力:玩家推动力由音量分数、有效声浪触发频率和连击加成组成;能量条按玩家推动力与对手推动力差值移动,并被限制在 `-100` 到 `100`。
- UI 反馈:有效声浪触发应触发可观察反馈,包括玩家侧狗狗张嘴/吠叫动画、拟声词或冲击波;反馈不应遮挡倒计时和顶部能量条。
## 中文 Gherkin 场景
@@ -66,7 +66,7 @@
场景: 校准完成后进入开局倒计时
假如玩家已允许麦克风权限
而且系统已采集足够的环境噪音样本
当校准计算出有效叫声阈值
当校准计算出有效声浪阈值
那么系统应进入开局倒计时阶段
而且倒计时结束后应进入 30 秒对战阶段
而且初始能量条应位于中线
@@ -84,19 +84,19 @@
功能: 环境噪音校准
为了减少背景噪音误触发
作为浏览器玩家
我希望游戏在开局前根据当前环境设置有效叫声阈值
我希望游戏在开局前根据当前环境设置有效声浪阈值
场景: 安静环境生成低但非零的有效阈值
假如校准阶段采集到的环境噪音 RMS 稳定低于默认噪音基线
当系统完成校准
那么有效叫声阈值应高于环境噪音平均值
那么有效声浪阈值应高于环境噪音平均值
而且阈值不应低于系统配置的最小阈值
场景: 嘈杂环境生成更高的有效阈值
假如校准阶段采集到的环境噪音 RMS 高于默认噪音基线
当系统完成校准
那么有效叫声阈值应随环境噪音上调
而且低于该阈值的后续输入不应计为有效叫声
那么有效声浪阈值应随环境噪音上调
而且低于该阈值的后续输入不应计为有效声浪触发
场景: 校准期间无法获得有效音频样本
假如麦克风授权成功但音频样本持续为空或不可读
@@ -106,36 +106,36 @@
而且不应直接开始对战
```
### 功能: 有效叫声计数
### 功能: 有效声浪触发计数
```gherkin
功能: 有效叫声计数
为了把玩家的狗叫行为转换为可计分输入
功能: 有效声浪触发计数
为了把玩家的声控行为转换为可计分输入
作为玩家
我希望每次符合规则的短促叫声只被计数一次
我希望每次超过阈值且满足冷却的声浪触发只被计数一次
背景:
假如游戏处于 30 秒 playing 阶段
而且系统已完成环境噪音校准
场景: 单次超过阈值且间隔足够的叫声计数加一
假如玩家当前叫声次数为 0
而且上一次有效叫声时间早于 minBarkGapMs
当麦克风输入出现一次超过有效阈值且持续时长合规的峰值
那么玩家叫声次数应变为 1
场景: 单次超过阈值且间隔足够的声浪触发计数加一
假如玩家当前声浪触发次数为 0
而且上一次有效声浪触发时间早于 minBarkGapMs
当某个麦克风采样点达到或超过有效阈值且满足声浪冷却
那么玩家声浪触发次数应变为 1
而且玩家侧应出现一次吠叫动画反馈
而且画面应出现一次拟声词或冲击波反馈
场景: 持续噪音不会被无限计数
假如玩家当前叫声次数为 1
当麦克风输入持续超过阈值但没有新的峰值间隔
那么玩家叫声次数不应在每个 tick 中持续增加
而且系统最多只应记录当前连续声音段内的一次有效叫声
场景: 持续高响度输入只按冷却节奏计数
假如玩家当前声浪触发次数为 1
当麦克风输入持续超过阈值但仍处于声浪冷却内
那么玩家声浪触发次数不应在每个 tick 中持续增加
而且系统只应在冷却结束后的采样点再次达阈值时记录下一次有效声浪触发
场景: 间隔过短的连续峰值不重复计数
假如玩家刚刚产生一次有效叫声
当麦克风输入在 minBarkGapMs 内再次出现峰值
那么玩家叫声次数不应增加
假如玩家刚刚产生一次有效声浪触发
当麦克风输入在 minBarkGapMs 内再次达到有效阈值
那么玩家声浪触发次数不应增加
而且连击或推动力不应因该峰值重复加成
```
@@ -145,23 +145,23 @@
功能: 声浪推动能量条
为了复刻双方比狗叫的核心体验
作为玩家
我希望更响、更连续的有效叫声能把顶部能量条推向自己一侧
我希望更响、更高频的有效声浪触发能把顶部能量条推向自己一侧
背景:
假如游戏处于 30 秒 playing 阶段
而且能量条当前位于中线
场景: 玩家推动力高于对手时能量条向玩家侧移动
假如玩家在短时间窗口内产生多次有效叫声
假如玩家在短时间窗口内产生多次有效声浪触发
而且玩家推动力高于对手推动力
当系统推进一个 simulation tick
那么能量条数值应向玩家侧增加
而且顶部红蓝能量条的玩家侧占比应变大
场景: 连续大声叫声触发更强反馈
假如玩家连续产生多次高于强叫声阈值的有效叫声
场景: 连续强声浪触发触发更强反馈
假如玩家连续产生多次高于强声浪阈值的有效声浪触发
当系统计算玩家连击加成
那么玩家侧推动力应高于单次普通叫声推动力
那么玩家侧推动力应高于单次普通声浪触发推动力
而且玩家侧声浪或冲击波反馈应比普通叫声更明显
但是反馈不应遮挡倒计时和能量条
@@ -185,31 +185,30 @@
功能: 背景噪音过滤
为了避免环境声替玩家自动得分
作为玩家
我希望低于阈值或不合规的声音不会被当作有效狗叫
我希望低于阈值或处于冷却内的声音不会被当作有效声浪触发
背景:
假如游戏处于 30 秒 playing 阶段
而且系统已完成环境噪音校准
场景: 低于阈值的背景噪音不计数
当麦克风只接收到低于有效叫声阈值的背景噪音
那么玩家叫声次数不应增加
当麦克风只接收到低于有效声浪阈值的背景噪音
那么玩家声浪触发次数不应增加
而且玩家侧不应播放吠叫动画
而且能量条不应因为该背景噪音出现可见推进
场景: 过短脉冲不计为有效叫声
假如麦克风输入峰值超过有效阈值
但是持续时长短于 minBarkDurationMs
当系统完成该声音段判定
那么玩家叫声次数不应增加
场景: 冷却内重复达阈值不计数
假如玩家刚刚产生一次有效声浪触发
当麦克风输入在 minBarkGapMs 内再次达到有效声浪阈值
那么玩家声浪触发次数不应增加
而且不应触发连击加成
场景: 过长持续声被削弱为单段输入
假如麦克风输入持续超过有效阈值
但是持续时长长于 maxBarkDurationMs
当系统完成该声音段判定
那么系统不应按多个叫声重复计数
而且该声音段的推动力应按持续噪音削弱规则处理
场景: 持续高响度输入只按冷却节奏产生触发
假如麦克风输入持续达到或超过有效声浪阈值
当声浪冷却尚未结束
那么系统不应在每个 tick 中重复计数
当声浪冷却结束且当前采样仍达到有效声浪阈值
那么系统可以记录下一次有效声浪触发
```
### 功能: 倒计时与胜负结算
@@ -227,20 +226,20 @@
当系统时间从 30 秒推进到 0 秒
那么界面应显示倒计时归零
而且系统应进入 finished 结算阶段
而且归零后的麦克风输入不应再改变本局能量条和叫声次数
而且归零后的麦克风输入不应再改变本局能量条和声浪触发次数
场景: 玩家侧占优时判定玩家胜利
假如倒计时归零时能量条数值大于 drawThreshold
当系统进入结算阶段
那么系统应判定玩家胜利
而且结算面板应展示玩家叫声次数、最大音量和声浪评分
而且结算面板应展示玩家声浪触发次数、最大音量和声浪评分
而且应提供再来一局入口
场景: 对手侧占优时判定玩家失败
假如倒计时归零时能量条数值小于 -drawThreshold
当系统进入结算阶段
那么系统应判定对手胜利
而且结算面板应展示玩家叫声次数、最大音量和声浪评分
而且结算面板应展示玩家声浪触发次数、最大音量和声浪评分
而且应提供再来一局入口
场景: 能量条接近平衡时判定平局
@@ -265,7 +264,7 @@
当玩家选择再来一局
那么系统应重置剩余时间为 30 秒
而且能量条应回到中线
而且玩家叫声次数、最大音量、连击和胜负结果应清零
而且玩家声浪触发次数、最大音量、连击和胜负结果应清零
而且系统应重新进入校准或开局倒计时流程
场景: 结算后返回玩法入口
@@ -353,7 +352,7 @@
假如玩家在 playing 阶段刷新页面
当页面重新加载 bark-battle
那么系统应重新进入权限检查或授权准备状态
而且不应沿用刷新前的剩余时间、能量条和叫声次数作为新局结果
而且不应沿用刷新前的剩余时间、能量条和声浪触发次数作为新局结果
```
## 测试映射
@@ -367,15 +366,15 @@
| 嘈杂环境生成更高的有效阈值 | unit | `src/games/bark-battle/domain/BarkNoiseCalibration.test.ts` | planned |
| 校准期间无法获得有效音频样本 | application/component | `src/games/bark-battle/application/BarkBattleController.test.ts`, `src/games/bark-battle/ui/BarkBattlePermissionPanel.test.tsx` | planned |
| 单次超过阈值且间隔足够的叫声计数加一 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
| 持续噪音不会被无限计数 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
| 持续高响度输入只按冷却节奏计数 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
| 间隔过短的连续峰值不重复计数 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
| 玩家推动力高于对手时能量条向玩家侧移动 | unit | `src/games/bark-battle/domain/EnergyTugOfWar.test.ts` | planned |
| 连续大声叫声触发更强反馈 | unit/integration/component | `src/games/bark-battle/domain/BarkBattleScoring.test.ts`, `src/games/bark-battle/ui/BarkBattleHud.test.tsx` | planned |
| 连续强声浪触发触发更强反馈 | unit/integration/component | `src/games/bark-battle/domain/BarkBattleScoring.test.ts`, `src/games/bark-battle/ui/BarkBattleHud.test.tsx` | planned |
| 能量条到达边界后不会越界 | unit | `src/games/bark-battle/domain/EnergyTugOfWar.test.ts` | planned |
| 对手推动力高于玩家时能量条向对手侧移动 | unit | `src/games/bark-battle/domain/EnergyTugOfWar.test.ts` | planned |
| 低于阈值的背景噪音不计数 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
| 过短脉冲不计为有效叫声 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
| 过长持续声被削弱为单段输入 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
| 冷却内重复达阈值不计数 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
| 持续高响度输入只按冷却节奏产生触发 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
| 倒计时每秒递减并在归零时停止对战输入 | unit/application | `src/games/bark-battle/domain/BarkBattleSession.test.ts`, `src/games/bark-battle/application/BarkBattleController.test.ts` | planned |
| 玩家侧占优时判定玩家胜利 | unit/component | `src/games/bark-battle/domain/BarkBattleSession.test.ts`, `src/games/bark-battle/ui/BarkBattleResultPanel.test.tsx` | planned |
| 对手侧占优时判定玩家失败 | unit/component | `src/games/bark-battle/domain/BarkBattleSession.test.ts`, `src/games/bark-battle/ui/BarkBattleResultPanel.test.tsx` | planned |
@@ -394,8 +393,8 @@
## 验收清单
- [ ] 权限允许、拒绝、非安全上下文、API 不支持、麦克风未找到/不可读、AudioContext 被拦截、校准超时或样本不可读均有明确状态,且不会误进入 playing。
- [ ] 校准阶段会影响有效叫声阈值,低噪音不会增加叫声计数。
- [ ] 有效叫声计数具备阈值、峰值间隔、持续时长约束。
- [ ] 校准阶段会影响有效声浪阈值,低噪音不会增加叫声计数。
- [ ] 有效声浪触发计数具备阈值与声浪冷却约束。
- [ ] 能量条根据双方推动力差值双向移动,并限制在 `-100` 到 `100`。
- [ ] 30 秒归零后停止本局输入影响,并按玩家胜利、对手胜利、平局三类结果结算。
- [ ] 移动端核心元素可见,非关键设置收起,不在主画面堆叠长规则说明。
@@ -405,8 +404,8 @@
## 开放问题
1. MVP 是否确认只做“玩家 vs AI”,还是第一版需要双人同屏或联机对战?
2. `drawThreshold`、`minBarkGapMs`、`minBarkDurationMs`、`maxBarkDurationMs` 的首版默认值由产品/调参阶段确认,还是先采用开发可配置默认值?
2. `drawThreshold`、`minBarkGapMs`、有效声浪阈值 的首版默认值由产品/调参阶段确认,还是先采用开发可配置默认值?
3. 是否允许无麦克风设备提供键盘/点击备用输入?若允许,需要另补非声控模式场景;若不允许,当前降级只提供返回入口。
4. 是否需要在结算中记录或上报成绩、最高音量、叫声次数和声浪评分?若需要,需补埋点/后端持久化场景。
4. 是否需要在结算中记录或上报成绩、最高音量、声浪触发次数和声浪评分?若需要,需补埋点/后端持久化场景。
5. bark-battle 是否作为 Genarrative 正式 play type 接入创作入口、作品发布和广场,还是先作为独立 runtime 原型验证?
6. 狗狗、背景、拟声词和冲击波素材来源是临时占位、AI 生成,还是复用项目现有素材管线?
+1
View File
@@ -4,6 +4,7 @@
## 重点入口
- [宝贝爱画寓教于乐独立关卡 PRD](./BABY_LOVE_DRAWING_EDUTAINMENT_LEVEL_PRD_2026-05-13.md):定义寓教于乐内容线的 `宝贝爱画` 独立本地 Demo 关卡,覆盖画板、七色选择、画笔/橡皮、手部绘画、完成、image-2 绘画魔法、本地保存和关闭入口隐藏边界。
- [宝贝识物寓教于乐模板 PRD](./BABY_OBJECT_MATCH_EDUTAINMENT_TEMPLATE_PRD_2026-05-11.md):定义寓教于乐内容线的 `宝贝识物` 创作模板,覆盖两个物品名称输入、image-2 物品图生成、精确 `寓教于乐` 标签、结果页和发布边界。
- [AI 原生幕间文字游戏模板 PRD:参考 MOKU 的剧本模拟器闭环](./AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md):参考 MOKU / 幕间类 AI 文游的剧本游乐场、自由行动、AI GM、记忆和模拟器强反馈经验,但只落为陶泥儿 `text-game` 模板,复用平台接口,不迁入外部社区、支付、私有存档或回放。
- [AI 原生视觉小说模板 PRD:TXT 玩法平台化接入](./AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md):参考 `Interactive-fiction-backend` / `Interactive-fiction-frontend` 的 TXT 玩法经验,但只保留视觉小说模板创作与运行闭环,完全使用 Genarrative 平台接口,并明确删除回放和外部平台功能。
@@ -70,7 +70,7 @@ Query:
- SQL 固定为 `SELECT * FROM {tableName} LIMIT {limit}`;SpacetimeDB 2.2 HTTP SQL 不拼 `ORDER BY`。
- 用户输入不直接拼入 SQL;关键词和条件在 API Server 内存中过滤。
- private 表或 token 不可见时返回后台可读错误信息。
- SpacetimeDB SQL 行和 SATS 值统一转成人可读 JSON:Option None 为 null,Some 展开为内部值,Timestamp 单元素数组展开为内部值,enum 可保留 tag/name 或原始数组文本。
- SpacetimeDB SQL 行和 SATS 值统一转成人可读 JSON:Option None 为 null,Some 展开为内部值,Timestamp 单元素数组展开为内部值;已知业务枚举列应在 API Server 按表名和列名转换为业务字符串,例如 `profile_recharge_order.kind` 转为 `points` / `membership`,`profile_recharge_order.status` 转为 `pending` / `paid` / `failed` / `closed` / `refunded`。
## 前端页面
@@ -0,0 +1,202 @@
# 宝贝爱画本地 Demo 运行态实现方案 2026-05-13
## 1. 范围
本方案落地寓教于乐独立关卡:
```text
baby-love-drawing / 宝贝爱画
```
当前范围只做本地 Demo 闭环:
1. 寓教于乐频道默认关卡卡片;
2. 独立运行态;
3. mocap 与开发者调试输入;
4. Canvas 绘制和擦除;
5. image-2 绘画魔法后端代理;
6. localStorage 本地保存;
7. 直达路由开关保护。
本阶段不接正式持久化表,不新增作品发布、作品号、公开详情或搜索入口。
## 2. 前端接入点
已新增页面阶段:
```text
baby-love-drawing-runtime
```
已新增路由:
```text
/runtime/baby-love-drawing
```
已新增文件:
```text
packages/shared/src/contracts/edutainmentBabyDrawing.ts
src/services/edutainment-baby-drawing/babyDrawingClient.ts
src/components/edutainment-runtime/babyLoveDrawingModel.ts
src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.tsx
server-rs/crates/api-server/src/edutainment_baby_drawing.rs
```
已接入:
1. `src/components/rpg-entry/RpgEntryHomeView.tsx`:寓教于乐频道默认展示宝贝爱画卡片;
2. `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`:启动宝贝爱画运行态;
3. `src/components/platform-entry/platformEntryTypes.ts`:扩展 `SelectionStage`;
4. `src/routing/appPageRoutes.ts`:扩展路由;
5. `src/routing/appRoutes.tsx`:直达路由开关保护;
6. `src/index.css`:补齐寓教于乐默认关卡卡片和宝贝爱画运行态样式;
7. `server-rs/crates/api-server/src/app.rs`:挂载绘画魔法后端路由。
## 3. 契约
契约放在:
```text
packages/shared/src/contracts/edutainmentBabyDrawing.ts
```
核心字段:
1. `templateId = "baby-love-drawing"`;
2. `templateName = "宝贝爱画"`;
3. `originalImageSrc` 保存原始画布图;
4. `magicImageSrc` 保存 image-2 魔法图,可为 `null`;
5. `strokeTrace` 保存画笔和橡皮轨迹;
6. `saveMode = "original-only" | "original-and-magic"` 记录保存结果。
## 4. 运行态模型
运行态状态:
```text
drawing
finished
magicPending
magicReady
saved
```
工具:
```text
brush
eraser
```
颜色:
```text
红、橙、黄、绿、青、蓝、紫
```
按钮悬停:
1. 颜色选择只接受左手悬停,阈值 1500ms;
2. 按钮选择接受任一手悬停,阈值 2000ms;
3. 工具切换只接受右手在工具区域握拳。
4. 画笔 / 橡皮光标位置只接受右手坐标;左手缺帧或左手移动不得重置、替换或驱动画笔位置。
5. 左手需要显示独立位置指示器,帮助用户确认当前是否悬停在目标颜色上;该指示器只表达左手位置,不参与画笔 / 橡皮操作。
6. 本地 mocap handedness 当前按摄像头视角输出,宝贝爱画运行态消费前需要换算为用户身体视角:`rightHand` 作为用户左手,`leftHand` 作为用户右手。键鼠调试输入不做该换算。
7. 真实硬件短暂缺失某只手时,显示层保留上一帧位置约 320ms 并做轻微坐标平滑;绘制层仍只在当前帧确认用户右手存在时生效。
8. 为避免左手抢画笔,本关不做动态 handedness 换手纠正;`rightHand` 永远只进入用户左手选色通道,`leftHand` 永远只进入用户右手画笔通道。若硬件侧 handedness 继续抖动,宁可右手画笔短暂停住,也不允许左手驱动画笔。
9. 右手画笔通道增加单帧最大位移门禁;若 camera-left 候选点相对上一帧右手位置出现不合理大跳,判定为不可信帧,只保留上一帧光标并停止绘制。
## 5. Canvas 绘制
画板使用 DOM Canvas。
绘制规则:
1. 右手在画板内且状态为 `grab` 时生效;
2. 工具为 `brush` 时,以当前颜色绘制连续线段;
3. 工具为 `eraser` 时,以 `destination-out` 擦除;
4. 右手状态为 `open_palm` 或离开画板时结束当前笔画;
5. 当前帧没有右手坐标时只结束当前笔画,不把左手坐标用于绘制、擦除或光标定位;
6. 每条笔画记录工具、颜色、点位和时间。
## 6. 绘画魔法
前端 service:
```text
createBabyDrawingMagicImage(payload)
```
后端接口:
```text
POST /api/creation/edutainment/baby-love-drawing/magic
```
请求体:
```json
{
"originalImageSrc": "data:image/png;base64,...",
"strokeTrace": []
}
```
响应体:
```json
{
"magicImageSrc": "data:image/png;base64,...",
"generationProvider": "vector-engine-gpt-image-2",
"prompt": "..."
}
```
后端使用 VectorEngine `gpt-image-2-all`,把原始画布图作为参考图,生成绘本风格图片。
本地未配置 VectorEngine 或接口失败时,前端允许提示错误并保留原图保存能力;不得把失败伪装成正式魔法图。
后端接入约束:
1. 接口需要 Bearer 鉴权;
2. 请求体限制为 8MB;
3. `originalImageSrc` 只接受图片 Data URL;
4. 笔触数量上限为 600 条;
5. 上游参考图字段使用 VectorEngine 统一契约 `image`;
6. 关闭入口时,`creation_entry_config` 路由熔断可识别 `baby-love-drawing`。
## 7. 本地保存
本地保存使用:
```text
localStorage key = genarrative.edutainmentBabyDrawing.localDrawings.v1
```
保存策略:
1. 魔法生成前保存:`saveMode = "original-only"`,只保存 `originalImageSrc`;
2. 未保存原图直接生成魔法后保存:`saveMode = "original-and-magic"`,保存 `originalImageSrc` 和 `magicImageSrc`;
3. 保存后展示“再画一张”和“返回”。
## 8. 验收命令
```bash
npm run test -- src/components/edutainment-runtime/babyLoveDrawingModel.test.ts src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.test.tsx src/services/edutainment-baby-drawing/babyDrawingClient.test.ts src/routing/appRoutes.test.ts
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
npx eslint src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.tsx src/components/edutainment-runtime/babyLoveDrawingModel.ts src/services/edutainment-baby-drawing/babyDrawingClient.ts src/routing/appRoutes.tsx --ext .ts,.tsx --max-warnings 0
npm run typecheck
npm run check:encoding
```
## 9. 已覆盖测试
1. `src/components/edutainment-runtime/babyLoveDrawingModel.test.ts`:颜色 / 按钮悬停阈值、坐标归一化、笔触追加;
2. `src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.test.tsx`:画板、七色、画笔 / 橡皮、完成保存、返回按钮、左手位置指示器、mocap 摄像头视角到用户身体视角换算、左手输入不替换画笔光标位置、左手短暂缺帧不闪烁、用户左手不能抢占右手画笔、camera-left 大跳不接入画笔;
3. `src/services/edutainment-baby-drawing/babyDrawingClient.test.ts`:原图保存、原图加魔法图保存、后端魔法接口请求;
4. `src/routing/appRoutes.test.ts`:`/runtime/baby-love-drawing` 开启可达、关闭回落主应用;
5. `server-rs/crates/api-server/src/edutainment_baby_drawing.rs` 内部单测:prompt、Data URL 校验、PNG 输出和轨迹范围摘要;
6. `server-rs/crates/api-server/src/creation_entry_config.rs` 路由映射单测:确认后端熔断可识别 `baby-love-drawing`。
@@ -30,13 +30,15 @@ baby-object-match
宝贝识物
```
入口文件:
工程接入文件:
1. `src/config/newWorkEntryConfig.ts`
1. `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs`
2. `src/components/platform-entry/platformEntryCreationTypes.ts`
3. `src/components/platform-entry/PlatformEntryCreationTypeModal.tsx`
4. `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
`src/config/newWorkEntryConfig.ts` 已迁移删除,不再作为入口事实源。`baby-object-match` 必须存在于 SpacetimeDB `creation_entry_type_config` 默认种子中,默认展示名为 `宝贝识物`、`visible=true`、`open=true`、`sortOrder=90`;前端只通过 `GET /api/creation-entry/config` 读取后端配置并在 `platformEntryCreationTypes.ts` 做展示派生。
`baby-object-match` 必须复用 `VITE_ENABLE_EDUTAINMENT_ENTRY` 开关;开关关闭时,创作类型弹层不展示 `宝贝识物`,创作页作品架不展示本地宝贝识物草稿或已发布作品卡,公开发现、搜索、详情、作品号和浏览历史也继续完全不可见。
新增阶段:
@@ -62,7 +64,8 @@ packages/shared/src/contracts/edutainmentBabyObject.ts
2. `BabyObjectMatchDraft.templateName = "宝贝识物"`;
3. `BabyObjectMatchDraft.themeTags` 必须包含精确 `寓教于乐`;
4. `BabyObjectMatchItemAsset.generationProvider` 首版允许为 `vector-engine-gpt-image-2` 或 `placeholder`;
5. `BabyObjectMatchPublishRequest.draft.themeTags` 发布前必须归一化补齐 `寓教于乐`。
5. `BabyObjectMatchDraft.visualPackage` 可选承载背景环境、UI 装饰框、礼物盒、篮子和烟雾弹出特效五类视觉资源;
6. `BabyObjectMatchPublishRequest.draft.themeTags` 发布前必须归一化补齐 `寓教于乐`。
## 4. Service 边界
@@ -76,9 +79,64 @@ src/services/edutainment-baby-object/babyObjectMatchClient.ts
1. `createBabyObjectMatchDraft(payload)`;
2. `saveBabyObjectMatchDraft(draft)`;
3. `publishBabyObjectMatchWork(payload)`。
3. `publishBabyObjectMatchWork(payload)`;
4. `deleteLocalBabyObjectMatchDraft(profileId)`;
5. `regenerateBabyObjectMatchDraftAssets(draft)`;
6. `hasBabyObjectMatchPlaceholderAssets(draft)`。
当前后端正式接口未在本线程扩表落地,因此 service 先走本地 Demo 存储,并把 asset 结果标记为 `placeholder`。后续后端接入时,应替换为:
当前后端正式作品持久化接口未在本线程扩表落地,因此 service 仍使用本地 Demo 存储草稿和发布状态。由于 image-2 会返回多张 base64 PNG 大图,本地 Demo 草稿必须优先写入 IndexedDB `genarrative-edutainment-baby-object-drafts/drafts`,不得把完整草稿 JSON 写入 `localStorage`;`localStorage` 仅作为旧版小草稿迁移读取来源,读取后迁移到 IndexedDB 并清理旧 key,避免触发浏览器 `Storage` 配额错误。
物品图片生成已接入后端 image-2 接口:
```text
POST /api/creation/edutainment/baby-object-match/assets
```
请求体:
```json
{
"itemNames": ["苹果", "香蕉"]
}
```
响应体:
```json
{
"assets": [
{
"itemId": "baby-object-item-1",
"itemName": "苹果",
"imageSrc": "data:image/png;base64,...",
"assetObjectId": null,
"generationProvider": "vector-engine-gpt-image-2",
"prompt": "..."
}
],
"visualPackage": {
"themePrompt": "...",
"assets": [
{
"assetId": "baby-object-visual-background",
"assetKind": "background",
"imageSrc": "data:image/png;base64,...",
"assetObjectId": null,
"generationProvider": "vector-engine-gpt-image-2",
"prompt": "..."
}
]
}
}
```
该接口返回物品透明 PNG data URL,以及同一次创作生成的视觉主题包。本地 Demo 阶段暂不写入 OSS 或 SpacetimeDB `asset_object`。当前创作链路必须真实拿到 `generationProvider = "vector-engine-gpt-image-2"` 的物品图和视觉主题包后才允许进入结果页;若本地未配置 VectorEngine、登录态失效、接口返回 401/5xx、上游生成失败或响应缺少任一资源,前端 service 必须抛出错误并停留在生成失败状态,不得静默回退到占位图。
由于一次创作会生成 2 张物品图和 `background`、`ui-frame`、`gift-box`、`basket`、`smoke-puff` 5 张视觉包装图,该请求属于长耗时 image-2 链路。前端 `babyObjectMatchClient` 对该 POST 使用 10 分钟请求超时,且不做自动重试,避免第一次生成仍在后端执行时又发起第二次重复生成。后端同时启动物品图与视觉主题包生成,并把该路由的 VectorEngine 单图请求等待预算提升到至少 8 分钟,避免某张图 3 分钟附近仍在生成时被后端提前断开。后端日志记录每类资源的开始、完成和耗时,排查时优先按同一次 HTTP 请求查看 `宝贝识物 image-2 物品资源生成完成`、`宝贝识物 image-2 视觉资源生成完成` 与 `VectorEngine 图片生成上游错误`。
历史本地草稿中若已保存 `generationProvider = "placeholder"` 的旧占位资源,结果页必须提示“重新生成 image-2 资源”,并禁用试玩和发布。用户点击重新生成、发布或试玩前,前端统一调用 `regenerateBabyObjectMatchDraftAssets(draft)` 补齐资源;补齐失败时保留在结果页并展示错误。
后续正式作品持久化接入时,应补齐:
```text
POST /api/creation/edutainment/baby-object-match/drafts
@@ -88,6 +146,25 @@ POST /api/creation/edutainment/baby-object-match/drafts/{draftId}/publish
图片生成必须在后端调用 VectorEngine `gpt-image-2-all`,不得从前端直接调用外部图片接口。
后端 image-2 prompt 约束:
1. 锁定寓教于乐板块统一的卡通绘本草地舞台插画风;
2. 每张图只能围绕对应关键词生成一个单一物品;
3. 不生成背景、场景、氛围渲染、人物、手、篮子、礼物盒、文字、水印或 UI;
4. 优先要求纯白或透明抠图友好的干净背景,服务端再统一转透明 PNG 并执行背景 alpha 清理;
5. 返回 `generationProvider = "vector-engine-gpt-image-2"` 的素材必须已经完成透明抠图。
后端视觉主题包 prompt 约束:
1. 同一次请求根据两个物品关键词生成 `background`、`ui-frame`、`gift-box`、`basket`、`smoke-puff` 五类资源;
2. 总风格继续锁定寓教于乐明亮卡通绘本插画风;
3. 若关键词偏动漫角色、玩具或公仔,背景环境和 UI 元素匹配动漫、玩具主题;若关键词偏水果,匹配果园、自然主题;其它关键词按语义匹配合适主题;
4. 背景环境图使用非透明 16:9 图,但必须保证中间、中下方和底部左右篮子区域清爽,给放大后的礼物盒、中央物品和左右篮子预留空间,不画入礼物盒、篮子、物品、人物、文字或操作 UI;
5. UI 装饰框、礼物盒、篮子和烟雾弹出特效使用透明 PNG 后处理,不生成文字、数字、按钮、人物或待分类物品;
6. `gift-box` 提示词必须面向运行态约 2 倍视觉尺寸生成主体饱满的大号礼物盒,`basket` 提示词必须面向运行态约 1.5 倍视觉尺寸生成可读性高的大号篮子;
7. `smoke-puff` 只生成礼物盒打开瞬间使用的柔和烟雾云朵特效,不生成礼物盒、篮子、物品或文字;
8. 左右篮子的固定选项规则不受主题包影响,运行态只把 `basket` 作为篮子造型包装复用。
## 5. UI 边界
工作台只展示两个必填输入和生成按钮。
@@ -107,27 +184,37 @@ src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx
运行态直接消费 `BabyObjectMatchDraft`,必须使用草稿中的两个物品名称和物品图。
每轮只随机当前从礼物盒跳出的物品;左右篮子不随机交换,左侧固定为草稿 `itemAssets[0]`,右侧固定为草稿 `itemAssets[1]`。
若草稿包含 `visualPackage`,运行态通过背景图片层、CSS 变量和图片节点消费:
1. `background`:作为舞台最底层 `ResolvedAssetImage` 背景图;存在该资源时必须关闭默认草地兜底层,避免生成场景被 CSS 草地遮住或弱化;
2. `ui-frame`:作为字幕条和计数器装饰背景;
3. `gift-box`:替换 CSS 礼物盒主体,按旧视觉约 2 倍尺寸展示,只在礼盒入场和打开阶段存在;
4. `basket`:替换篮子主体造型,按旧视觉约 1.5 倍尺寸展示,左右两侧复用同一张主题篮子图;
5. `smoke-puff`:作为礼物盒打开和中央物品弹出期间的透明烟雾特效资源。
旧草稿或接口失败时 `visualPackage = null`,运行态继续使用现有 CSS 绘本风兜底。
首关状态机:
1. `waiting`:礼物盒关闭,等待任意手抬起;
2. `active`:当前物品停留在屏幕中央;
3. `correct`:展示“真棒”反馈,成功次数加 1;
4. `wrong`:展示“再想一想吧”反馈,当前物品回到中央;
5. `complete`:成功次数达到 20,展示“恭喜你!小朋友!”和按钮。
1. `gift-entering`:礼物盒从上方落下入场动画阶段,不接受动作判定;
2. `gift-opening`:礼物盒打开并播放烟雾特效阶段,不接受动作判定;
3. `item-appearing`:礼物盒从舞台移除,当前物品从烟雾中出现并停稳,不接受动作判定;
4. `active`:物品彻底出现后才开放选篮判定;
5. `correct`:展示“真棒”反馈,对应篮筐播放正确特效并停顿,成功次数加 1;特效完全结束后重新进入 `gift-entering`,下一轮礼物盒从上方落下;
6. `wrong`:展示“再想一想吧”反馈,物品弹回中央;反馈结束后回到 `active`,不重新随机物品;
7. `complete`:成功次数达到 20,展示“恭喜你!小朋友!”和按钮。
动作输入:
1. 任意手完成一次 `open_palm -> grab` 抓握序列:打开礼物盒并生成当前物品;
2. 左手连续横向移动达到阈值:将当前物品送入左侧篮子;
3. 右手连续横向移动达到阈值:将当前物品送入右侧篮子。
1. 左手连续横向移动达到阈值:将当前物品送入左侧篮子;
2. 右手连续横向移动达到阈值:将当前物品送入右侧篮子。
运行态直接通过 `useMocapInput` 消费本地 mocap WebSocket `/stream`。选篮只使用明确 `leftHand` 或 `rightHand` 的连续横向轨迹阈值,不再通过 `wave_left_hand`、`wave_right_hand`、`wave` 等动作名触发;侧别为 `unknown` 的手部轨迹也不参与选篮,以避免多套判定误命中和连续误触发。当前本地 mocap 输出的 handedness 按摄像头视角标记,宝贝识物运行态必须先换算为用户身体视角:`rightHand` 轨迹映射玩家左手并进入左侧篮子,`leftHand` 轨迹映射玩家右手并进入右侧篮子。草稿试玩、发布后正式体验和热身关后的本地 Demo 都复用同一个运行态,因此三条入口都必须具备同一套动作控制能力。
运行态直接通过 `useMocapInput` 消费本地 mocap WebSocket `/stream`。选篮只使用明确 `leftHand` 或 `rightHand` 的连续横向轨迹阈值,不再通过 `wave_left_hand`、`wave_right_hand`、`wave` 等动作名触发;侧别为 `unknown` 的手部轨迹也不参与选篮,以避免多套判定误命中和连续误触发。动作判定只在 `active` 阶段开放,礼盒入场、礼盒打开、物品出现、正确反馈和错误反馈阶段收到的动作包必须清空轨迹并忽略,不允许跨阶段补判定。当前本地 mocap 输出的 handedness 按摄像头视角标记,宝贝识物运行态必须先换算为用户身体视角:`rightHand` 轨迹映射玩家左手并进入左侧篮子,`leftHand` 轨迹映射玩家右手并进入右侧篮子。草稿试玩、发布后正式体验和热身关后的本地 Demo 都复用同一个运行态,因此三条入口都必须具备同一套动作控制能力。
开发者调试输入:
1. `F`:映射任意手抬起,打开礼物盒并生成当前物品;
2. 鼠标左键按下并拖动:映射左手轨迹,抬起后将当前物品送入左侧篮子;
3. 鼠标右键按下并拖动:映射右手轨迹,抬起后将当前物品送入右侧篮子。
1. 鼠标左键按下并拖动:映射左手轨迹,抬起后将当前物品送入左侧篮子;
2. 鼠标右键按下并拖动:映射右手轨迹,抬起后将当前物品送入右侧篮子。
运行态不得新增计时、失败次数、分数、体力或难度递增规则。
@@ -154,6 +241,7 @@ src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx
```bash
npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts src/components/edutainment-creation/BabyObjectMatchWorkspace.test.tsx src/components/edutainment-result/BabyObjectMatchResultView.test.tsx src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.test.tsx src/components/child-motion-demo/ChildMotionWarmupDemo.test.tsx src/services/edutainment-baby-object/babyObjectMatchClient.test.ts
cargo test -p api-server edutainment_baby_object --manifest-path server-rs/Cargo.toml
npx vitest run src/components/platform-entry/platformEdutainmentVisibility.test.ts src/components/platform-entry/PlatformWorkDetailView.test.tsx src/components/custom-world-home/creationWorkShelf.test.ts src/services/useMocapInput.test.ts src/services/child-motion-demo/childMotionDebugInput.test.ts src/routing/appRoutes.test.ts
npx eslint src/components/platform-entry/platformEntryCreationTypes.ts src/components/platform-entry/platformEntryCreationTypes.test.ts src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --ext .ts,.tsx --max-warnings 0
npm run check:encoding
@@ -4,7 +4,7 @@
### 1.1 背景
`bark-battle` / “汪汪声浪大作战”是一个浏览器 2D 声控狗叫对战玩法。玩家通过麦克风发出狗叫声,浏览器 runtime 根据音量峰值、有效叫声次数与节奏推动顶部红蓝能量条;每局默认 30 秒;结束后按能量条偏向判定胜负或平局。
`bark-battle` / “汪汪声浪大作战”是一个浏览器 2D 声控狗叫对战玩法。玩家通过麦克风发出狗叫声,浏览器 runtime 根据音量峰值、有效声浪触发次数与节奏推动顶部红蓝能量条;每局默认 30 秒;结束后按能量条偏向判定胜负或平局。
现有前端方案 `docs/technical/BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md` 已覆盖 Phaser / TypeScript / Vite / Web Audio / DOM HUD 的 runtime 落地方式,并明确不覆盖后端表结构、成绩持久化、作品发布、广场接入与实时多人协议。因此需要单独补充后端 DDD 技术方案,避免前端 runtime 在接入平台作品、正式游玩埋点、成绩、排行榜和发布闭环时承接不属于表现层的业务真相。
@@ -44,26 +44,80 @@ MVP 明确不做:
## 2. 玩法接入级别建议
### 2.1 推荐首版闭环
### 2.1 第二阶段范围:平台作品闭环
建议先支持“本地 runtime + 可发布配置化作品 + 单局结果记录 / 可选排行榜”的闭环:
第二阶段已明确为“Bark Battle 平台作品闭环”,不是单纯玩法表现深化。目标是让 bark-battle 成为 Genarrative 的正式 play type,并完成从轻创作配置、发布、正式 runtime、run start / finish、单局结果持久化、个人历史成绩、作品统计到最小排行榜的闭环。
1. 创作者创建 bark-battle 草稿,配置标题、描述、狗狗主题、背景、难度、单局时长、音量阈值、AI 对手参数和排行榜开关。
Phase 2 的作品配置边界是“轻创作配置作品”:创作者可以配置标题、描述、主题/背景预设、狗狗皮肤预设、难度预设和排行榜开关;不得直接配置单局时长、有效声浪阈值、`minBarkGapMs`、AI 对手细粒度参数、分数公式或反作弊阈值。难度预设只影响 AI 对手行为强度,不影响有效阈值、声浪冷却、单局时长或分数公式;排行榜必须按 `workId + difficultyPreset + rulesetVersion` 分榜,避免不同难度和不同规则版本混排。
建议先支持“本地 runtime + 可发布配置化作品 + 单局结果记录 + 个人历史成绩 / 作品统计 / 最小排行榜”的闭环:
1. 创作者从玩法选择进入 bark-battle 后创建草稿,通过单页轻配置表单 + 预览卡片配置标题、描述、主题/背景预设、狗狗皮肤预设、难度预设和排行榜开关。
2. 发布为稳定作品 ID,`playTypeId = "bark-battle"`。
3. 玩家从作品页或广场进入 runtime,前端获取发布态 runtime config。
3. 玩家可从作品详情页 CTA、广场/作品卡片、我的作品/个人作品架进入正式 runtime,前端使用稳定作品 ID 获取发布态 runtime config。
4. 玩家授权麦克风后在本地完成 30 秒声控对战。
5. 前端提交单局 finish 请求,只上传派生指标,例如峰值、有效叫声次数、节奏命中、最终能量、客户端结果摘要等。
6. 后端校验 work、config version、run token、时长、分数范围和权限后,生成服务端认可的 run result / score summary。
7. 若作品开启排行榜,则写入可投影的 leaderboard 记录。
8. 正式作品级游玩埋点统一写 `work_play_start`,其中 `scope_kind=work`,`scope_id=稳定作品 ID`,metadata 包含 `playType`、`workId`、`sourceRoute`、`userId`。
5. 前端提交单局 finish 请求,只上传派生指标,例如峰值、有效声浪触发次数、节奏命中、最终能量、客户端结果摘要等。
6. 后端校验 work、config version、ruleset version、difficulty preset、run token、时长、派生指标范围和权限后,生成服务端裁决的 run result / score summary。
7. 写入个人历史成绩与最小作品统计投影。
8. 若作品开启排行榜且后端裁决 `serverResult = player_win`,则写入可投影的 leaderboard 记录;排行榜首版只做最小排序与展示,不引入赛季、段位或复杂反作弊,并按 `workId + difficultyPreset + rulesetVersion` 分榜。
9. 正式作品级游玩埋点统一写 `work_play_start`,其中 `scope_kind=work`,`scope_id=稳定作品 ID`,metadata 包含 `playType`、`workId`、`sourceRoute`、`userId`。
### 2.2.1 难度预设与排行榜分榜
Phase 2 只允许三个难度预设:`easy`、`normal`、`hard`。难度预设只能影响 AI 对手推动力曲线和 AI 声浪节奏;不得影响单局时长、有效声浪阈值、`minBarkGapMs`、分数公式或反作弊阈值。排行榜记录和查询必须带上 `difficultyPreset` 与 `rulesetVersion`,以 `workId + difficultyPreset + rulesetVersion` 作为分榜维度。
### 2.2.2 单局结果后端裁决
Phase 2 不信任前端提交的最终胜负和正式分数。前端 `finish` 只提交不可还原原始音频的派生指标:`runId`、`workId`、`configVersion`、`rulesetVersion`、`difficultyPreset`、`clientStartedAt`、`clientFinishedAt`、`durationMs`、`triggerCount`、`maxVolume`、`averageVolume`、`finalEnergy`、`comboMax`、`clientResult`,以及可选的 `sampleDigest`。其中 `clientResult` 只用于 debug/对账,不进入正式结果或排行榜。
后端必须校验 run 由 start 创建且未 finish、run token 匹配、work/config/ruleset/difficulty 与 start 时一致、duration 处于合理窗口、triggerCount 不超过 `durationMs / minBarkGapMs + tolerance`、音量/能量/连击字段在合法范围内。后端生成 `serverResult`、`scoreSummary`、`leaderboardScore` 和 `antiCheatFlags`,排行榜只使用后端裁决后的胜利局成绩。
### 2.2.3 排行榜排序口径
Phase 2 排行榜只收录 `serverResult = player_win` 且未被反作弊规则拒绝的单局结果;平局和失败仍进入个人历史成绩与作品统计,但不进入排行榜。`leaderboardScore` 由后端规则集生成,排序优先级为:`finalEnergy` 降序、`triggerCount` 降序、`maxVolume` 降序、`durationMs` 越接近标准局时长越优、`finishedAt` 越早越优。
### 2.2.4 作品统计投影口径
Phase 2 的作品统计是最小后端投影,不从排行榜反推。`playStartCount` 在 start run 成功时计入一次,并对齐 `work_play_start` 埋点;`finishCount` 在 finish 被后端接受时计入一次,包含胜利、平局和失败。`accepted_with_flags` 可以计入 `finishCount`,但必须同时计入 `flaggedCount`;未 start 成功、run token 不合法、重复 finish、被后端 rejected 的结果不计入 `finishCount`。
作品统计字段首版包含:`playStartCount`、`finishCount`、`winCount`、`drawCount`、`lossCount`、`flaggedCount`、`leaderboardEntryCount`、`bestLeaderboardScore`、`bestFinalEnergy`、`averageFinalEnergy`、`updatedAt`。Phase 2 不做 DAU/留存、按小时曲线、原始音频分析或每玩家每天聚合统计。
### 2.2.5 个人历史成绩口径
Phase 2 的个人历史成绩由“最近记录列表 + 个人最佳摘要”组成,并且只允许本人查询。后端可以保存每次被接受的 finish 记录,但首版查询接口只暴露默认最近 20 条记录,可按 `workId` 和 `difficultyPreset` 过滤;最近记录包含胜利、平局、失败和是否 flagged,但不展示详细反作弊原因。
个人最佳摘要按 `userId + workId + difficultyPreset + rulesetVersion` 聚合,字段包含 `bestLeaderboardScore`、`bestFinalEnergy`、`bestTriggerCount`、`bestMaxVolume`、`winCount`、`finishCount`、`lastPlayedAt`。失败、平局和 flagged 历史不对其他玩家公开;排行榜只展示公开入榜的胜利成绩。Phase 2 不做无限滚动完整历史、每日/每周曲线、好友对比或普通玩家可见的详细反作弊说明。
### 2.2.6 正式作品入口闭环
Phase 2 必须接入 Bark Battle 正式作品入口闭环,但不新增独立专区、活动页、挑战分享页、好友邀请或多人房间入口。入口范围包括:创作入口/玩法选择中出现 `bark-battle`,进入单页轻配置表单 + 预览卡片;作品详情页 CTA 点击“开始游玩”进入正式 runtime;广场/作品卡片可以展示、打开详情并开始游玩;我的作品/个人作品架能看到作者发布的 Bark Battle 作品;runtime 路由使用稳定作品 ID 并从后端发布态 config 拉取配置。
正式 run start 成功后必须写 `work_play_start`,其中 `scope_kind=work`、`scope_id=稳定作品 ID`,metadata 至少包含 `playType=bark-battle`、`workId`、`sourceRoute`、`userId`。内部试玩入口可以作为开发调试保留,但不得作为 Phase 2 正式入口。
### 2.2.7 轻配置编辑流程
Phase 2 的创作编辑形态是“单页轻配置表单 + 预览卡片”,不是多步骤向导、拖拽编辑器或完整规则编辑器。表单字段包含:标题(必填)、简介(选填)、主题/背景预设(必填枚举)、狗狗皮肤预设(必填枚举)、难度预设(必填,默认 `normal`)、排行榜开关(默认开启)。
交互流程:创作者从玩法选择进入后生成草稿;在同一页编辑轻配置并查看预览卡片;支持保存草稿和发布;发布成功后跳转作品详情;可从我的作品再次编辑草稿或基于已发布作品创建新版本。Phase 2 不做 AI 生成配置、多步骤 wizard、规则参数编辑、复杂封面编辑、runtime 内嵌预览或大段玩法说明文案。
### 2.2 后续增强路径
后续再考虑多人实时:
第二阶段之后再考虑:
- Phase 2:排行榜、挑战分享、个人历史成绩、作品统计面板。
- Phase 2.1:挑战分享、作品统计面板细化、排行榜体验优化。
- Phase 3:异步影子对手 / ghost replay,但仍不保存原始音频,只保存低维派生曲线或聚合指标。
- Phase 4:实时多人对战协议,需要独立同步模型、房间服务、延迟补偿、断线恢复与更严格反作弊;不应混入 MVP。
- Phase 4:实时多人对战协议,需要独立同步模型、房间服务、延迟补偿、断线恢复与更严格反作弊;不应混入第二阶段平台作品闭环。
## 2.3 Phase 2 技术实施顺序
Phase 2 按“契约和领域规则先行,然后最小纵切,再扩展投影”的顺序实施,避免前端 mock 堆积、后端孤岛或排行榜 UI 先行。
1. 契约与领域规则:补 `shared-contracts` DTO、`module-bark-battle` 纯领域规则、`rulesetVersion` / `difficultyPreset` / score adjudication,并先写单测。
2. SpacetimeDB 表与 reducer + api-server BFF:落草稿/config/发布态 config、runtime run start / finish、score record、leaderboard entry、work stats projection、personal summary projection、`migration.rs` 与绑定生成。
3. 最小前端纵切:接创作入口、单页轻配置表单、发布到稳定 workId、作品详情 CTA、runtime 拉 config、start / finish 串通、结算展示 `serverResult`。
4. 投影与列表体验:接排行榜、个人历史最近记录 + 最佳摘要、作品统计、我的作品/广场卡片适配。
5. 收口验证:把 BDD 场景落到测试,执行编码检查、后端 `/healthz` + API smoke、前端人工验收路径,并更新 README/文档。
## 3. DDD 分层设计
@@ -90,7 +144,7 @@ frontend/runtime
- 定义配置版本兼容规则。
- 计算提交结果的派生分数区间与胜负判定是否自洽。
- 计算 `ScoreSummary`、排行榜排序分数、统计指标。
- 定义反作弊基础规则:时长范围、有效叫声次数上限、峰值范围、能量范围、提交窗口、run 状态机。
- 定义反作弊基础规则:时长范围、有效声浪触发次数上限、峰值范围、能量范围、提交窗口、run 状态机。
不职责:
@@ -170,6 +170,9 @@
3. 位置类状态必须满足“到达绿色圆环并保持 2 秒”。
4. 动作类状态没有最长等待时间。
5. 动作类状态等待 3 秒后可以播放对应引导动画。
6. 每个步骤进入时需要先展示本步骤文字字幕和语音播报入口,约 1 秒后再进入可交互阶段并展示绿色圆环、手势引导等检测提示。
7. 步骤完成后需要先进入完成停顿阶段,当前停顿约 0.8 秒;停顿期间保留完成反馈位置,后续可在该阶段补充完成特效或音效,再切换到下一步骤。
8. 入场等待和完成停顿阶段不消费动作完成判定,避免用户上一步残留动作直接触发下一步。
### 6.3 开发者调试输入
@@ -368,6 +371,17 @@
用户完成挥动左手。
当前本地 mocap 的 handedness 按摄像头视角输出,热身关内需要先换算成用户身体视角再判断:摄像头右侧手对应用户左手。挥动左手不是普通横向轨迹检测,而是用于确认现实环境中用户左侧手臂打开空间足够和安全。
完成条件必须同时满足:
1. 使用用户身体左手轨迹。
2. 手腕在左肩外侧达到最小外展距离。
3. 手腕不能处于自然下垂低位。
4. 最近连续有效帧中,手臂存在足够上下摆动幅度。
5. 最近连续有效帧中,肩膀到手腕向量的角度变化达到阈值。
6. 至少出现一次上下摆动方向变化。
#### 完成反馈
```text
@@ -376,7 +390,7 @@
#### 数据记录
记录用户挥动左手的空间,保存为该用户对应的行为坐标。
记录用户挥动左手的轨迹、空间包络、角度范围和最大外展距离,保存为该用户对应的行为坐标。
---
@@ -398,6 +412,17 @@
用户完成挥动右手。
当前本地 mocap 的 handedness 按摄像头视角输出,热身关内需要先换算成用户身体视角再判断:摄像头左侧手对应用户右手。挥动右手不是普通横向轨迹检测,而是用于确认现实环境中用户右侧手臂打开空间足够和安全。
完成条件必须同时满足:
1. 使用用户身体右手轨迹。
2. 手腕在右肩外侧达到最小外展距离。
3. 手腕不能处于自然下垂低位。
4. 最近连续有效帧中,手臂存在足够上下摆动幅度。
5. 最近连续有效帧中,肩膀到手腕向量的角度变化达到阈值。
6. 至少出现一次上下摆动方向变化。
#### 完成反馈
```text
@@ -406,7 +431,7 @@
#### 数据记录
记录用户挥动右手的空间,保存为该用户对应的行为坐标。
记录用户挥动右手的轨迹、空间包络、角度范围和最大外展距离,保存为该用户对应的行为坐标。
---
@@ -653,18 +678,21 @@
3. 鼠标左键按下并拖动映射左手轨迹。
4. 鼠标右键按下并拖动映射右手轨迹。
5. 空格键映射原地跳跃。
6. 调试输入只在步骤可交互阶段触发步骤完成;步骤入场字幕阶段和完成停顿阶段会忽略完成判定,便于观察节奏和后续补充特效。
当前硬件和动作检测接口接入:
1. 浏览器摄像头视频流已接入舞台背景。
2. 热身关全流程已通过 `src/services/useMocapInput.ts` 接入本地 mocap WebSocket `/stream`;动作数据源状态优先于浏览器背景摄像头状态展示。
3. mocap 包支持从 `general.body.center_norm` 读取身体中心,位置类步骤使用该身体中心更新角色剪影横向位置并完成圆环保持检测。
4. mocap 包支持从 `actions/action/gesture/gestures/event/name/type` 读取动作名,并支持 `hands[]`、`leftHand/rightHand`、`left_hand/right_hand` 读取左右手坐标。
5. `hands[].landmarks` 存在时优先用手腕和 MCP 点计算掌心中心;掌心点不足时退回 wrist landmark,再退回 hand 直出坐标。
6. `wave_greeting` 可由 `wave/wave_greeting/hand_wave/open_palm` 等动作或 open palm 手势完成。
7. `wave_left_hand` 和 `wave_right_hand` 优先消费对应左右手动作名;当硬件只持续输出手部坐标时,也可以根据连续手部横向轨迹完成挥手检测。
8. `jump_once` 消费 `jump/jump_once/hop` 等跳跃动作事件完成。
9. 键盘 `A/D/Space` 与鼠标左右键拖拽仍保留为本地 Demo 调试兜底,不代表正式硬件口径。
4. 身体中心横向坐标进入角色剪影前必须做输入稳定化处理:先 clamp 到 `0..1`,再使用小幅死区、低通阻尼和单包最大步长限制,避免硬件噪声造成角色左右误判、画面抽搐或视觉上的忽大忽小。当前实现参数为死区 `0.012`、阻尼系数 `0.28`、单包最大步长 `0.035`;位置保持检测使用稳定化后的角色坐标。
5. 角色剪影渲染需要把水平位移和跳跃表现拆开:外层只负责横向定位,内层资源只负责轮廓图和跳跃位移,避免 `left` 与 `transform` 同时抢占导致半透明资源重采样抖动。
6. mocap 包支持从 `actions/action/gesture/gestures/event/name/type` 读取动作名,并支持 `hands[]`、`leftHand/rightHand`、`left_hand/right_hand` 读取左右手坐标。
7. `hands[].landmarks` 存在时优先用手腕和 MCP 点计算掌心中心;掌心点不足时退回 wrist landmark,再退回 hand 直出坐标。
8. `wave_greeting` 只消费左手、右手或未知单手的连续横向挥手轨迹,不再使用 `wave`、`hand_wave`、`open_palm`、张手状态或动作名直接完成判定;进入轨迹判定前必须先满足抬手有效区:优先使用 `hands[].landmarks.wrist` 与 `general.limb_nodes` 的同侧 `*_elbow` / `*_shoulder` 判断,当前阈值为 `wrist.y <= elbow.y + 0.04`,缺少肘部时使用 `wrist.y <= shoulder.y + 0.08`;缺少同侧肘部和肩膀参考时不允许招呼通过,不再使用身体中心兜底判断抬手。轨迹阈值为至少 5 个连续抬手点,横向 `x` 范围差值不小于 `0.075`,且至少出现 1 次横向方向变化,避免“手刚露出画面”或“手自然下垂抖动”被误判为招手。
9. `wave_left_hand` 和 `wave_right_hand` 只消费用户身体侧对应手的连续坐标轨迹,不再使用动作名、张手状态或 primary hand 兜底完成判定;本地 mocap handedness 当前按摄像头视角输出,因此用户左手使用 camera-right,用户右手使用 camera-left。完成判定必须同时满足对应肩肘腕外展、手腕非自然下垂、连续有效帧、横向范围、上下摆动范围、肩腕角度范围和上下方向变化,当前阈值为连续外展点不少于 5 个、横向 `x` 范围不小于 `0.055`、垂直 `y` 范围不小于 `0.08`、肩腕角度范围不小于 `28°`、外展距离不小于 `0.12`、手腕相对肩膀外侧距离不小于 `0.1`;后续以真实体验结果继续调参。
10. `jump_once` 消费 `jump/jump_once/hop` 等跳跃动作事件完成。
11. 键盘 `A/D/Space` 与鼠标左右键拖拽仍保留为本地 Demo 调试兜底,不代表正式硬件口径。
当前未接入但已保留边界:
@@ -682,14 +710,17 @@
5. 当前已生成并接入以下正式 Demo 资源:
- `public/child-motion-demo/picture-book-grass-stage.png`:默认草地舞台背景。
- `public/child-motion-demo/picture-book-foreground-grass-v2.png`:底部前景草坪条,只覆盖舞台下沿,不作为整块地板拉伸。
- `public/child-motion-demo/picture-book-ground-ring-v2.png`:已按透视绘制的地面椭圆指示环,CSS 只等比缩放。
- `public/child-motion-demo/picture-book-ground-ring-v3.png`:已按透视绘制的浅蓝与暖黄色地面椭圆指示环,和草地材质做明显区分,CSS 只等比缩放。
- `public/child-motion-demo/picture-book-character-outline-v2.png`:半透明用户角色轮廓,使用独立去背后处理避免内部填充被误删。
- `public/child-motion-demo/picture-book-hud-strip-v2.png`:顶部 HUD 细长软纸条。
- `public/child-motion-demo/picture-book-calibration-strip-v2.png`:右下角五格热身状态条。
- `public/child-motion-demo/picture-book-start-panel-v2.png`:开始按钮背后的轻盈托盘。
- `public/child-motion-demo/picture-book-ui-button-v2.png`:开始按钮绘本风按钮底图。
- `public/child-motion-demo/picture-book-wave-cat-body-guide-v6.png`:招手阶段中央猫咪身体底座资源,按可动纸偶结构只包含猫头、短身体和肩部连接点,不再和旧猫头、胸口或猫爪资源叠加。
- `public/child-motion-demo/picture-book-wave-cat-arm-guide-v6.png`:招手阶段左右独立手臂资源,也用于左右手阶段单手提示;网页用同一拆件镜像复用,并围绕肩部挂点做挥手摆动动画。
6. v2 资源按最终用途拆分,CSS 必须按资源原始比例、`aspect-ratio` 或 `background-size: contain / auto` 等方式等比使用;禁止把方形面板强行拉伸为 HUD、状态条或地板,也禁止把底部草坪扩展成覆盖角色脚下的大色块。
7. 若后续补充或重绘资源,应先运行 `npm run assets:child-motion-demo -- --dry-run` 核对 prompt 和输出路径,再使用 `--live --only <asset-id>` 小批量生成;仅调整透明去背、裁切、画布归一或品红边缘时,可用 `npm run assets:child-motion-demo -- --live --postprocess-only --force --only <asset-id>` 复用 `tmp/child-motion-demo-assets/` 中的源图,不额外请求 image-2;不得把 `VECTOR_ENGINE_API_KEY`、源图或中间预览图提交到仓库。
7. 猫咪招手引导资源使用 `cat-guide` 透明后处理:先由 image-2 生成品红底源图,再通过边缘背景连通区域去背,避免把浅粉、淡橘和暖棕主体误删。源图只保存在 `tmp/child-motion-demo-assets/`,正式页面只引用 `public/child-motion-demo/` 下的最终 PNG。
8. 若后续补充或重绘资源,应先运行 `npm run assets:child-motion-demo -- --dry-run` 核对 prompt 和输出路径,再使用 `--live --only <asset-id>` 小批量生成;仅调整透明去背、裁切、画布归一或品红边缘时,可用 `npm run assets:child-motion-demo -- --live --postprocess-only --force --only <asset-id>` 复用 `tmp/child-motion-demo-assets/` 中的源图,不额外请求 image-2;不得把 `VECTOR_ENGINE_API_KEY`、源图或中间预览图提交到仓库。
已执行的定向验证命令:
@@ -32,6 +32,8 @@ process didn't exit successfully: `server-rs\target\debug\api-server.exe`
主站和后台 Vite 也追加 `--strictPort`,避免默认漂移到 `3001`、`3103` 等端口后让浏览器继续访问旧页面。
`--skip-spacetime` 是复用既有 SpacetimeDB 宿主的模式。该模式下脚本不会再把 SpacetimeDB 端口纳入可用性漂移;如果传入 `--spacetime-port 3101`,后端就会连接 `http://127.0.0.1:3101`。这可以避免 `3101` 已有 SpacetimeDB 在线时,端口工具误把它当作冲突并改到空闲的 `3102`,导致 api-server 连接空端口后 `/api/creation-entry/config`、作品架和公开图库接口同时返回 `502`。
## 排障步骤
PowerShell 查看默认端口占用:
@@ -72,3 +74,4 @@ node scripts/run-bash-script.mjs scripts/dev-rust-stack.sh \
1. `bash -n scripts/dev-rust-stack.sh` 通过。
2. 默认端口被占用时重新运行完整栈,脚本应在 publish 前失败并打印占用进程。
3. 清理占用进程或换端口后,重新启动时不再出现 Vite 端口漂移或 `api-server` `AddrInUse`。
4. 复用 SpacetimeDB 时执行 `npm run dev -- --skip-spacetime --skip-publish --spacetime-port 3101`,启动日志里的 `[dev:rust] spacetime:` 应保持为 `http://127.0.0.1:3101`,并且 `GET /api/creation-entry/config` 不应因连接 `3102` 这类空端口而失败。
@@ -9,6 +9,7 @@
1. 新建作品入口配置统一存放在 SpacetimeDB 的 `creation_entry_config` / `creation_entry_type_config` 表;默认种子位于 `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs`。
2. `visible` 控制玩法是否展示在创作 Tab 模板入口、新建作品入口和创作类型弹层中。
3. `open` 控制玩法是否允许点击创建以及对应创作 / runtime API 路由是否放行;`open: false` 时入口保持展示但禁用,并由 `api-server` 熔断对应玩法 API。
- 前端作品架、发现页聚合和预加载只应请求 `open: true` 的玩法接口;`open: false` 的未开放玩法可以展示为敬请期待入口,但不得把对应 API 熔断错误透传到草稿页或发现页。
4. `title`、`subtitle`、`badge` 控制玩法卡片文案。
5. `startCard` 控制旧创作中心顶部新建作品模块的标题、辅助文案和移动端角标文案;当前创作 Tab 首屏标题固定在 `PlatformEntryFlowShellImpl.tsx`,不再由 `startCard` 控制。
6. `typeModal` 控制平台创作类型弹层标题和描述。
@@ -28,6 +29,22 @@
| AIRP | 是 | 否 | 保留入口,显示敬请期待 |
| 视觉小说 | 是 | 否 | 保留入口,显示敬请期待,暂不允许创建视觉小说草稿 |
| 智能创作 | 否 | 是 | 入口隐藏,既有 `creative-agent` 链路保留 |
| 汪汪声浪 | 是 | 是 | `bark-battle` 正式轻创作入口,进入单页配置表单并发布后启动声控对战 runtime |
| 宝贝识物 | 是 | 是 | 寓教于乐首关模板,必须由 `creation_entry_type_config` 默认种子或后台入口开关保持存在 |
## 排障约束
`baby-object-match` 是寓教于乐创作模板和发现页宝贝识物作品合流的共同开关。若 `creation_entry_type_config` 缺少该行,前端会同时出现“创作界面没有宝贝识物模板”和“寓教于乐分类下已发布作品消失”的现象。
默认种子必须包含:
- `id = baby-object-match`
- `title = 宝贝识物`
- `visible = true`
- `open = true`
- `sort_order = 90`
入口图首版复用 `/child-motion-demo/picture-book-grass-stage.png`,后续如生成专属 image-2 入口图,可通过后台入口开关更新 `imageSrc`。
## 验收

Some files were not shown because too many files have changed in this diff Show More