Prune stale docs and update .hermes content

Delete a large set of outdated documentation (many files under docs/ and .hermes/plans/, including audits, design, prd, technical, planning, assets, and todos). Update and consolidate .hermes content: refresh shared-memory pages (decision-log, development-workflow, document-map, pitfalls, project-overview, team-conventions) and several skills/references under .hermes/skills. Also modify AGENTS.md, README.md, UI_CODING_STANDARD.md, docs/README.md and .encoding-check-ignore. Purpose: clean up stale planning/audit material and keep current hermes documentation and related top-level docs in sync.
This commit is contained in:
2026-05-15 06:24:07 +08:00
parent 2eded08bc7
commit 3cb3efb4d0
708 changed files with 4033 additions and 142328 deletions

View File

@@ -1,124 +1,41 @@
# server-rs 工作区占位说明
# server-rs 工作区说明
日期`2026-04-20`
更新时间`2026-05-15`
## 1. 目录用途
`server-rs/` 是当前唯一后端工作区,承载 Rust `api-server`、SpacetimeDB module、领域模块、平台副作用适配和共享契约。旧 `server-node`、Express、PostgreSQL、Go 服务端和 `maincloud` 口径均为历史残留,不再作为当前实现目标。
`server-rs/` 是本项目新 Rust 后端的固定根目录,用于承载以下重写目标:
## 当前职责
1. `Axum` HTTP / SSE 边界层
2. `SpacetimeDB` 状态机模块
3. `阿里云 OSS` 资产接入与应用层编排
- `crates/api-server`Axum HTTP / SSE / BFF 门面、鉴权、中间件、外部服务编排和 DTO 映射。
- `crates/spacetime-module`SpacetimeDB 表、reducer、procedure、事务 adapter、row mapper 和迁移。
- `crates/spacetime-client`:后端访问 SpacetimeDB 的 typed facade。
- `crates/module-*`:领域模型、命令、应用规则、领域事件和领域错误。
- `crates/platform-*`OSS、LLM、语音、认证等外部平台能力。
- `crates/shared-contracts`:前后端共享 DTO 与公开契约。
- `crates/shared-kernel``crates/shared-logging``crates/tests-support`:跨模块基础能力、日志和测试支撑。
该目录固定放在仓库根目录,与 `src/``docs/` 同级。旧 `server-node/` 已完成物理删除,后续只可通过历史提交或迁移文档追溯。
## 开发规则
## 2. 当前阶段说明
1. `server-rs/Cargo.toml` 是 workspace 成员和依赖版本事实源;第三方依赖和 workspace 内 crate path 优先放在 `[workspace.dependencies]`
2. `module-*` 不直接依赖 Axum、SpacetimeDB table/reducer/procedure、`reqwest`、OSS、LLM、`spacetime-client``tokio` 或文件系统。
3. `api-server` 不承接领域真相;发现领域规则时优先沉到对应 `module-*`
4. SpacetimeDB schema 变化必须同步 `spacetime-module/src/migration.rs`、生成绑定和当前后端架构文档的表目录。
5. 人工命令、本地联调和文档示例不要使用 `spacetime --root-dir`
当前目录已经完成以下三十八项初始化:
## 常用命令
1. 为新后端预留正式目录并把路径固定到仓库结构中。
2. 创建虚拟 workspace `Cargo.toml`,后续 crate 会逐项挂入。
3. 明确内部采用“`crates/*` 统一承载主工程 crate 与独立模块 crate”的多 crate 组织方式。
4. 创建 `crates/api-server/` 目录占位,固定 Axum 主工程 crate 落位。
5. 创建 `crates/spacetime-module/` 目录占位,固定 SpacetimeDB 主工程 crate 落位。
6. 创建 `crates/module-auth/` 目录占位,固定鉴权模块 crate 落位。
7. 创建 `crates/module-runtime/` 目录占位,固定运行时状态基座模块 crate 落位。
8. 创建 `crates/module-story/` 目录占位,固定故事主循环模块 crate 落位。
9. 创建 `crates/module-combat/` 目录占位,固定战斗规则模块 crate 落位。
10. 创建 `crates/module-inventory/` 目录占位,固定背包与物品变更模块 crate 落位。
11. 创建 `crates/module-npc/` 目录占位,固定 NPC 状态与互动模块 crate 落位。
12. 创建 `crates/module-progression/` 目录占位,固定成长与章节推进模块 crate 落位。
13. 创建 `crates/module-quest/` 目录占位,固定任务运行时模块 crate 落位。
14. 创建 `crates/module-runtime-item/` 目录占位,固定运行时物品模块 crate 落位。
15. 创建 `crates/module-custom-world/` 目录占位,固定自定义世界与 agent 模块 crate 落位。
16. 创建 `crates/module-assets/` 目录占位,固定资产任务与对象绑定模块 crate 落位。
17. 创建 `crates/module-ai/` 目录占位,固定 AI 编排模块 crate 落位。
18. 创建 `crates/shared-contracts/` 目录占位,固定前后端兼容 contract 共享 crate 落位。
19. 创建 `crates/shared-kernel/` 目录占位,固定跨模块共享领域内核 crate 落位。
20. 创建 `crates/shared-logging/` 目录占位,固定工作区统一日志 crate 落位。
21. 创建 `crates/platform-auth/` 目录占位,固定鉴权平台适配 crate 落位。
22. 创建 `crates/platform-oss/` 目录占位,固定 OSS 平台适配 crate 落位。
23. 创建 `crates/platform-llm/` 目录占位,固定大模型平台适配 crate 落位。
24. 创建 `crates/spacetime-client/` 目录占位,固定 SpacetimeDB 客户端适配 crate 落位。
25. 创建 `crates/tests-support/` 共享测试支撑 crate固定 smoke/contract 测试辅助能力落位。
26. 创建 `scripts/dev.ps1`,固定 Windows 本地开发入口。
27. 创建 `scripts/dev.sh`,固定 Unix-like 本地开发入口。
28. 创建 `scripts/test.ps1`,固定 Windows 本地测试入口。
29. 创建 `scripts/test.sh`,固定 Unix-like 本地测试入口。
30. 创建 `scripts/check.ps1`,固定 Windows 本地统一检查入口。
31. 创建 `scripts/check.sh`,固定 Unix-like 本地统一检查入口。
32. 创建 `scripts/smoke.ps1`,固定 Windows 本地冒烟验证入口。
33. 创建 `scripts/smoke.sh`,固定 Unix-like 本地冒烟验证入口。
34. 创建 `scripts/spacetime-dev.ps1`,固定 Windows 本地 SpacetimeDB 启动入口。
35. 创建 `scripts/spacetime-dev.sh`,固定 Unix-like 本地 SpacetimeDB 启动入口。
36. 创建 `scripts/oss-smoke.ps1`,固定 Windows 本地阿里云 OSS 真实联调入口。
37. 固定 Vite dev proxy 的 Rust `api-server` 默认目标与 `GENARRATIVE_RUNTIME_SERVER_TARGET` 覆盖开关。
38. 固定 `Cargo.toml` 依赖集中配置口径,第三方版本和 workspace 内部 crate path 统一维护在根 `server-rs/Cargo.toml`
```bash
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:spacetime-schema
npm run spacetime:generate
npm run api-server
```
后续任务会继续在本目录内按顺序补齐:
涉及 API smoke 时用 `npm run api-server` 启动后端并检查 `/healthz`,不要使用旧 `api-server:maincloud`
1. `crates/spacetime-module` 的表、reducer、view 聚合入口
2. `module-auth` 的身份表、JWT 与 refresh cookie 主链
3. `platform-oss` 的浏览器直传签名、旧 `/generated-*` 前缀到 OSS object key 的映射与对象 URL 解析能力;`/generated-*` 不再作为可裸读 HTTP 路由
## 当前文档
当前本地脚本补充说明:
1. `scripts/smoke.ps1` 用于验证 `api-server` 的本地 `/healthz` 基础 contract。
2. `scripts/oss-smoke.ps1` 用于验证真实阿里云 OSS
- 读取仓库根目录 `.env` / `.env.local`
- 启动临时 `api-server`
- 请求 `/api/assets/direct-upload-tickets`
- 实际执行 `PostObject` 上传
- 校验对象存在并默认自动删除
## 3. 已冻结边界
本目录后续落地时必须继续遵守 `M0` 已冻结的边界:
1.`server-node/` 不再作为当前工程目录保留;若需查证旧实现,只允许通过历史提交、迁移文档或已迁移到 `server-rs/` 的实现对照。
2. 前端在 `M0 ~ M6` 期间只访问 Axum不直连 SpacetimeDB。
3. 外部副作用统一收口在 Axum / crate 内应用层 / infra。
4. `crates/api-server` 只组合与暴露协议,不直接吞并业务模块实现。
5. `crates/spacetime-module` 只负责汇总各模块 crate 的表、reducer、view。
6. 当前允许在 `M3 / M4 / M5` 前先行落地 `OSS` 基础设施,但不因此跳过后续资产状态建模与绑定迁移。
## 4. SpacetimeDB 实施约束
凡是涉及 `SpacetimeDB` 的工程修改、脚本执行、接口接入与前端绑定,统一要求显式使用以下 skill
1. [$spacetimedb-cli](.codex\\skills\\spacetimedb-cli\\SKILL.md)
2. [$spacetimedb-rust](.codex\\skills\\spacetimedb-rust\\SKILL.md)
3. [$spacetimedb-concepts](.codex\\skills\\spacetimedb-concepts\\SKILL.md)
4. [$spacetimedb-typescript](.codex\\skills\\spacetimedb-typescript\\SKILL.md)
执行口径:
1. `spacetime` CLI、发布、绑定生成、本地联调按 `spacetimedb-cli` 执行。
2. `crates/spacetime-module` 的 Rust 表、reducer、view 与模块 API 按 `spacetimedb-rust``spacetimedb-concepts` 执行。
3. 前端或 Node 侧的 SpacetimeDB TypeScript SDK、订阅、绑定使用按 `spacetimedb-typescript``spacetimedb-concepts` 执行。
4. 若仓库内旧实现或旧文档与这些 skill 冲突,先修正文档和方案,再继续编码。
## 6. DDD 目录与边界
`2026-04-28` 起,`server-rs` 进入 DDD 边界收口阶段,完整规则见 [../docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md](../docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md)。
新增或迁移业务代码时必须遵守:
1. `module-*` 统一维护 `domain.rs``commands.rs``application.rs``events.rs``errors.rs`
2. `module-*` 不新增 Axum、reqwest、OSS、LLM、文件系统、SpacetimeDB table/reducer/procedure 依赖。
3. `mapper.rs` 只允许出现在 `api-server``spacetime-module``spacetime-client` 等 adapter crate。
4. `spacetime-module` 新增业务入口前先确认是否已有对应上下文目录,禁止继续把大段业务流程堆回 `src/lib.rs`
5. 根目录可执行 `npm run check:server-rs-ddd` 检查第一阶段 DDD 骨架与绝对边界。
## 7. Cargo 依赖配置口径
`2026-05-07` 起,`server-rs` 的依赖版本和 workspace 内部 crate path 统一维护在根 `Cargo.toml``[workspace.dependencies]`,完整记录见 [../docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md](../docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md)。
成员 crate 的 `Cargo.toml` 默认使用 `{ workspace = true }` 继承依赖;只在成员 crate 内保留本 crate 的 feature、optional、target-specific dependency 等差异。新增 crate 或新增依赖时,应优先补根 workspace 依赖,再在成员 crate 中继承。
## 5. 关联文档
1. [../docs/technical/SPACETIMEDB_AXUM_OSS_BACKEND_REWRITE_DESIGN_2026-04-20.md](../docs/technical/SPACETIMEDB_AXUM_OSS_BACKEND_REWRITE_DESIGN_2026-04-20.md)
2. [../backend-rewrite-tasklist/M0_REPOSITORY_BOUNDARY_DECISIONS_2026-04-20.md](../backend-rewrite-tasklist/M0_REPOSITORY_BOUNDARY_DECISIONS_2026-04-20.md)
3. [../backend-rewrite-tasklist/01_M0_M2_FOUNDATION_AND_AUTH.md](../backend-rewrite-tasklist/01_M0_M2_FOUNDATION_AND_AUTH.md)
4. [../docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md](../docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md)
- [../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)DDD 边界、API 分组、schema 变更规则和表目录。
- [../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md](../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md)本地启动、检查、SpacetimeDB 操作和生产运维。

View File

@@ -81,6 +81,9 @@ pub fn resolve_creation_entry_route_id(path: &str) -> Option<&'static str> {
if normalized.starts_with("/api/runtime/bark-battle") {
return Some("bark-battle");
}
if normalized.starts_with("/api/creation/bark-battle") {
return Some("bark-battle");
}
if normalized.starts_with("/api/runtime/square-hole") {
return Some("square-hole");
}
@@ -156,6 +159,10 @@ mod tests {
resolve_creation_entry_route_id("/api/runtime/bark-battle/works/work-1/config"),
Some("bark-battle"),
);
assert_eq!(
resolve_creation_entry_route_id("/api/creation/bark-battle/drafts"),
Some("bark-battle"),
);
assert_eq!(
resolve_creation_entry_route_id("/api/creation/edutainment/baby-object-match/assets"),
Some("baby-object-match"),
@@ -168,7 +175,7 @@ mod tests {
}
#[test]
fn test_creation_entry_config_response_keeps_baby_object_match_visible() {
fn test_creation_entry_config_response_keeps_baby_object_match_coming_soon() {
let config = test_creation_entry_config_response();
let baby_object_match = config
.creation_types
@@ -178,7 +185,8 @@ mod tests {
assert_eq!(baby_object_match.title, "宝贝识物");
assert!(baby_object_match.visible);
assert!(baby_object_match.open);
assert!(!baby_object_match.open);
assert_eq!(baby_object_match.badge, "敬请期待");
assert_eq!(baby_object_match.sort_order, 90);
}
}

View File

@@ -108,6 +108,9 @@ const MATCH3D_OSS_PUT_TIMEOUT_MS: u64 = 3 * 60_000;
const MATCH3D_LEGACY_MODEL_MAX_BYTES: usize = 120 * 1024 * 1024;
const MATCH3D_ITEM_IMAGE_MAX_BYTES: usize = 20 * 1024 * 1024;
const MATCH3D_WORK_METADATA_LLM_MODEL: &str = "gpt-4o";
const MATCH3D_ITEM_SIZE_LARGE: &str = "";
const MATCH3D_ITEM_SIZE_MEDIUM: &str = "";
const MATCH3D_ITEM_SIZE_SMALL: &str = "";
const MATCH3D_CONTAINER_REFERENCE_IMAGE_PATH: &str =
"public/match3d-background-references/pot-fused-reference.png";
const MATCH3D_QUESTION_THEME: &str = "你想创作什么题材";
@@ -136,6 +139,7 @@ struct Match3DConfigJson {
struct Match3DGeneratedItemAsset {
item_id: String,
item_name: String,
item_size: Option<String>,
image_src: Option<String>,
image_object_key: Option<String>,
image_views: Vec<Match3DGeneratedItemImageView>,
@@ -195,6 +199,7 @@ struct Match3DGeneratedWorkMetadata {
#[derive(Clone, Debug)]
struct Match3DGeneratedItemPlan {
name: String,
item_size: String,
sound_prompt: String,
}
@@ -211,6 +216,8 @@ struct Match3DGeneratedItemAssetJson {
item_id: String,
item_name: String,
#[serde(default)]
item_size: Option<String>,
#[serde(default)]
image_src: Option<String>,
#[serde(default)]
image_object_key: Option<String>,
@@ -920,6 +927,10 @@ pub async fn persist_match3d_generated_model(
let next_asset = Match3DGeneratedItemAsset {
item_id: payload.item_id,
item_name,
item_size: current_asset
.as_ref()
.and_then(|asset| asset.item_size.clone())
.or_else(|| Some(MATCH3D_ITEM_SIZE_LARGE.to_string())),
image_src: current_asset
.as_ref()
.and_then(|asset| asset.image_src.clone()),
@@ -1343,7 +1354,7 @@ pub async fn generate_match3d_item_assets_for_work(
item: map_match3d_work_profile_response(profile),
generated_item_assets: sort_match3d_generated_assets(assets)
.into_iter()
.map(Match3DGeneratedItemAssetJson::from)
.map(Match3DGeneratedItemAssetJson::from)
.map(map_match3d_generated_item_asset_for_work)
.collect(),
},
@@ -2456,6 +2467,7 @@ fn map_match3d_generated_item_asset_for_agent(
Match3DAgentGeneratedItemAssetResponse {
item_id: asset.item_id,
item_name: asset.item_name,
item_size: asset.item_size,
image_src: asset.image_src,
image_object_key: asset.image_object_key,
image_views: asset
@@ -2488,6 +2500,7 @@ fn map_match3d_generated_item_asset_for_work(
shared_contracts::match3d_works::Match3DGeneratedItemAssetResponse {
item_id: asset.item_id,
item_name: asset.item_name,
item_size: asset.item_size.or_else(|| Some(MATCH3D_ITEM_SIZE_LARGE.to_string())),
image_src: asset.image_src,
image_object_key: asset.image_object_key,
image_views: asset
@@ -3222,6 +3235,7 @@ impl From<Match3DGeneratedItemAsset> for Match3DGeneratedItemAssetJson {
Self {
item_id: asset.item_id,
item_name: asset.item_name,
item_size: asset.item_size,
image_src: asset.image_src,
image_object_key: asset.image_object_key,
image_views: asset.image_views,
@@ -3248,6 +3262,7 @@ impl From<Match3DGeneratedItemAssetJson> for Match3DGeneratedItemAsset {
Self {
item_id: asset.item_id,
item_name: asset.item_name,
item_size: asset.item_size.or_else(|| Some(MATCH3D_ITEM_SIZE_LARGE.to_string())),
image_src: asset.image_src,
image_object_key: asset.image_object_key,
image_views: asset.image_views,
@@ -3276,6 +3291,7 @@ impl From<shared_contracts::match3d_works::Match3DGeneratedItemAssetResponse>
Self {
item_id: asset.item_id,
item_name: asset.item_name,
item_size: asset.item_size,
image_src: asset.image_src,
image_object_key: asset.image_object_key,
image_views: asset
@@ -3401,6 +3417,7 @@ async fn ensure_match3d_item_image_assets(
Some(Match3DItemImageGenerationSeed {
item_id,
item_name: item.name.clone(),
item_size: item.item_size.clone(),
sound_prompt: item.sound_prompt.clone(),
persist_asset: true,
background_music_title: None,
@@ -3454,6 +3471,7 @@ async fn ensure_match3d_item_image_assets(
struct Match3DItemImageGenerationSeed {
item_id: String,
item_name: String,
item_size: String,
sound_prompt: String,
persist_asset: bool,
background_music_title: Option<String>,
@@ -3580,6 +3598,9 @@ async fn generate_match3d_item_image_assets_in_batches(
asset: Match3DGeneratedItemAsset {
item_id: seed.item_id,
item_name: seed.item_name,
item_size: Some(normalize_match3d_item_size(seed.item_size.as_str()))
.filter(|value| !value.is_empty())
.or_else(|| Some(MATCH3D_ITEM_SIZE_LARGE.to_string())),
image_src: primary_view
.as_ref()
.and_then(|view| view.image_src.clone()),
@@ -3685,6 +3706,7 @@ async fn append_match3d_new_item_assets(
let item_id = allocate_match3d_generated_item_id(&assets, &mut next_item_index);
Match3DItemImageGenerationSeed {
item_id,
item_size: infer_match3d_item_size(item_name.as_str()),
sound_prompt: build_fallback_match3d_item_sound_prompt(config, item_name.as_str()),
item_name,
persist_asset: index < requested_item_count,
@@ -3766,6 +3788,12 @@ async fn replace_match3d_item_assets(
});
Match3DItemImageGenerationSeed {
item_id,
item_size: matched_asset
.as_ref()
.and_then(|asset| asset.item_size.clone())
.map(|value| normalize_match3d_item_size(value.as_str()))
.filter(|value| !value.is_empty())
.unwrap_or_else(|| infer_match3d_item_size(item_name.as_str())),
sound_prompt: matched_asset
.as_ref()
.and_then(|asset| asset.sound_prompt.clone())
@@ -3857,7 +3885,7 @@ async fn generate_match3d_draft_plan(
let gameplay_item_count = resolve_match3d_gameplay_item_count(config);
let generated_item_count = resolve_match3d_generated_item_count(config);
let user_prompt = format!(
"题材设定:{}\n请生成抓大鹅游戏草稿生成计划。要求:只返回 JSON 对象,字段为 gameName、summary、tags、backgroundPrompt、items。gameName 为 4 到 12 个中文字符不要包含“作品”“游戏”summary 为 18 到 48 个中文字符的作品描述说明题材氛围和核心体验不要写规则说明tags 为 3 到 6 个中文短标签,每个 2 到 6 个汉字后续会用同一作品信息再次生成作品标签backgroundPrompt 是用于生成局内纯背景图的中文提示词只描述竖屏移动端抓大鹅题材氛围、色彩和环境不得描述锅、圆盘、托盘、拼图槽、物品槽、HUD、UI、文字、按钮、倒计时、分数或物品当前玩法需要 {} 种物品,但素材图固定每 5 个物品一批,因此 items 必须向上补齐并正好返回 {} 项,每项包含 name 和 soundPromptname 为 2 到 6 个汉字soundPrompt 只作为历史字段保留,可返回空字符串。",
"题材设定:{}\n请生成抓大鹅游戏草稿生成计划。要求:只返回 JSON 对象,字段为 gameName、summary、tags、backgroundPrompt、items。gameName 为 4 到 12 个中文字符不要包含“作品”“游戏”summary 为 18 到 48 个中文字符的作品描述说明题材氛围和核心体验不要写规则说明tags 为 3 到 6 个中文短标签,每个 2 到 6 个汉字后续会用同一作品信息再次生成作品标签backgroundPrompt 是用于生成局内纯背景图的中文提示词只描述竖屏移动端抓大鹅题材氛围、色彩和环境不得描述锅、圆盘、托盘、拼图槽、物品槽、HUD、UI、文字、按钮、倒计时、分数或物品当前玩法需要 {} 种物品,但素材图固定每 5 个物品一批,因此 items 必须向上补齐并正好返回 {} 项,每项包含 name、itemSize 和 soundPromptname 为 2 到 6 个汉字itemSize 只能是“大”“中”“小”之一,按物品真实相对尺寸判断,例如西瓜/大箱子偏大,苹果/杯子偏中,糖果/钥匙偏小;soundPrompt 只作为历史字段保留,可返回空字符串。",
config.theme_text, gameplay_item_count, generated_item_count
);
let response = llm_client
@@ -3931,6 +3959,14 @@ fn parse_match3d_draft_plan(
if name.is_empty() {
return None;
}
let item_size = item
.get("itemSize")
.or_else(|| item.get("item_size"))
.or_else(|| item.get("size"))
.and_then(Value::as_str)
.map(normalize_match3d_item_size)
.filter(|value| !value.is_empty())
.unwrap_or_else(|| infer_match3d_item_size(&name));
let sound_prompt = item
.get("soundPrompt")
.or_else(|| item.get("sound_prompt"))
@@ -3938,7 +3974,11 @@ fn parse_match3d_draft_plan(
.map(normalize_match3d_audio_prompt)
.filter(|value| !value.is_empty())
.unwrap_or_else(|| build_fallback_match3d_item_sound_prompt(config, &name));
Some(Match3DGeneratedItemPlan { name, sound_prompt })
Some(Match3DGeneratedItemPlan {
name,
item_size,
sound_prompt,
})
})
.collect::<Vec<_>>()
})
@@ -4020,6 +4060,7 @@ fn fallback_match3d_draft_plan(config: &Match3DConfigJson) -> Match3DGeneratedDr
.into_iter()
.take(resolve_match3d_generated_item_count(config))
.map(|name| Match3DGeneratedItemPlan {
item_size: infer_match3d_item_size(&name),
sound_prompt: build_fallback_match3d_item_sound_prompt(config, &name),
name,
})
@@ -4042,6 +4083,36 @@ fn normalize_match3d_item_name(raw: &str) -> String {
.to_string()
}
fn normalize_match3d_item_size(raw: &str) -> String {
let normalized = raw.trim().trim_matches(['"', '\'', '“', '”', '。', '', ',', '、']);
match normalized {
"" | "大型" | "偏大" | "large" | "Large" | "L" | "l" => MATCH3D_ITEM_SIZE_LARGE.to_string(),
"" | "中型" | "中等" | "medium" | "Medium" | "M" | "m" => MATCH3D_ITEM_SIZE_MEDIUM.to_string(),
"" | "小型" | "偏小" | "small" | "Small" | "S" | "s" => MATCH3D_ITEM_SIZE_SMALL.to_string(),
_ => String::new(),
}
}
fn infer_match3d_item_size(item_name: &str) -> String {
let name = item_name.trim();
let large_keywords = [
"西瓜", "南瓜", "椰子", "", "", "", "", "", "", "瓶子", "大瓶", "",
"书包", "", "抱枕", "玩偶", "", "圆球", "足球", "篮球", "",
];
if large_keywords.iter().any(|keyword| name.contains(keyword)) {
return MATCH3D_ITEM_SIZE_LARGE.to_string();
}
let small_keywords = [
"草莓", "蓝莓", "葡萄", "樱桃", "", "", "糖果", "钥匙", "硬币", "纽扣", "徽章",
"戒指", "耳环", "铃铛", "星星", "宝石", "叶片", "花瓣", "蘑菇", "贝壳", "印章",
"彩蛋", "棋子", "骰子", "挂件",
];
if small_keywords.iter().any(|keyword| name.contains(keyword)) {
return MATCH3D_ITEM_SIZE_SMALL.to_string();
}
MATCH3D_ITEM_SIZE_MEDIUM.to_string()
}
fn fallback_match3d_item_names(theme_text: &str) -> Vec<String> {
let theme = theme_text.trim();
let normalized_theme = if theme.is_empty() { "主题" } else { theme };
@@ -4094,7 +4165,13 @@ fn normalize_match3d_item_plan(
continue;
}
let sound_prompt = normalize_match3d_audio_prompt(item.sound_prompt.as_str());
let item_size = normalize_match3d_item_size(item.item_size.as_str());
normalized.push(Match3DGeneratedItemPlan {
item_size: if item_size.is_empty() {
infer_match3d_item_size(&name)
} else {
item_size
},
sound_prompt: if sound_prompt.is_empty() {
build_fallback_match3d_item_sound_prompt(config, &name)
} else {
@@ -4113,6 +4190,7 @@ fn normalize_match3d_item_plan(
continue;
}
normalized.push(Match3DGeneratedItemPlan {
item_size: infer_match3d_item_size(&name),
sound_prompt: build_fallback_match3d_item_sound_prompt(config, &name),
name,
});
@@ -4149,6 +4227,7 @@ fn fill_match3d_item_plan_to_count(
.any(|candidate: &Match3DGeneratedItemPlan| candidate.name == name)
{
normalized.push(Match3DGeneratedItemPlan {
item_size: infer_match3d_item_size(&name),
sound_prompt: build_fallback_match3d_item_sound_prompt(config, &name),
name,
});
@@ -4488,6 +4567,10 @@ fn merge_regenerated_match3d_item_asset(
Match3DGeneratedItemAsset {
item_id: current_asset.item_id,
item_name: current_asset.item_name,
item_size: current_asset
.item_size
.or(generated_asset.item_size)
.or_else(|| Some(MATCH3D_ITEM_SIZE_LARGE.to_string())),
image_src: generated_asset.image_src,
image_object_key: generated_asset.image_object_key,
image_views: generated_asset.image_views,
@@ -7092,6 +7175,7 @@ mod tests {
Match3DGeneratedItemAsset {
item_id: format!("match3d-item-{index}"),
item_name: name.to_string(),
item_size: Some(infer_match3d_item_size(name)),
image_src: Some(format!(
"/generated-match3d-assets/s/p/items/i{index}/views/view-01.png"
)),
@@ -7592,9 +7676,33 @@ mod tests {
);
assert!(plan.background_prompt.contains("纯背景"));
assert_eq!(plan.items[0].name, "草莓");
assert_eq!(plan.items[0].item_size, MATCH3D_ITEM_SIZE_SMALL);
assert!(plan.items[0].sound_prompt.contains("草莓"));
}
#[test]
fn match3d_draft_plan_parses_relative_item_sizes() {
let plan = parse_match3d_draft_plan(
r#"{"gameName":"果园大鹅宴","summary":"果园小物堆满浅盘,轻快明亮适合随手消除。","tags":["水果","抓大鹅"],"backgroundPrompt":"果园主题竖屏纯背景","items":[{"name":"西瓜","itemSize":"大","soundPrompt":""},{"name":"苹果","itemSize":"中","soundPrompt":""},{"name":"糖果","itemSize":"小","soundPrompt":""}]}"#,
&config("水果", 3, 3),
)
.expect("draft plan should parse");
assert_eq!(plan.items[0].item_size, MATCH3D_ITEM_SIZE_LARGE);
assert_eq!(plan.items[1].item_size, MATCH3D_ITEM_SIZE_MEDIUM);
assert_eq!(plan.items[2].item_size, MATCH3D_ITEM_SIZE_SMALL);
}
#[test]
fn match3d_legacy_item_asset_without_size_defaults_to_large() {
let assets = parse_match3d_generated_item_assets(Some(
r#"[{"itemId":"match3d-item-1","itemName":"草莓","status":"image_ready"}]"#,
));
let asset = Match3DGeneratedItemAsset::from(assets[0].clone());
assert_eq!(asset.item_size.as_deref(), Some(MATCH3D_ITEM_SIZE_LARGE));
}
#[test]
fn match3d_draft_item_plan_rounds_up_to_full_five_item_sheets() {
let plan = parse_match3d_draft_plan(
@@ -7642,6 +7750,7 @@ mod tests {
let existing_assets = vec![Match3DGeneratedItemAsset {
item_id: "match3d-item-1".to_string(),
item_name: "草莓".to_string(),
item_size: Some(MATCH3D_ITEM_SIZE_SMALL.to_string()),
image_src: None,
image_object_key: None,
image_views: Vec::new(),
@@ -7686,6 +7795,7 @@ mod tests {
.map(|index| Match3DGeneratedItemAsset {
item_id: format!("match3d-item-{index}"),
item_name: format!("已有物品{index}"),
item_size: Some(MATCH3D_ITEM_SIZE_LARGE.to_string()),
image_src: None,
image_object_key: None,
image_views: Vec::new(),
@@ -7977,6 +8087,7 @@ mod tests {
let assets = vec![Match3DGeneratedItemAsset {
item_id: "match3d-item-1".to_string(),
item_name: "草莓".to_string(),
item_size: Some(MATCH3D_ITEM_SIZE_SMALL.to_string()),
image_src: None,
image_object_key: None,
image_views: Vec::new(),
@@ -8078,6 +8189,7 @@ mod tests {
let assets = vec![Match3DGeneratedItemAsset {
item_id: "match3d-item-1".to_string(),
item_name: "草莓".to_string(),
item_size: Some(MATCH3D_ITEM_SIZE_SMALL.to_string()),
image_src: None,
image_object_key: None,
image_views: Vec::new(),
@@ -8223,6 +8335,7 @@ mod tests {
Match3DGeneratedItemAsset {
item_id: "match3d-item-2".to_string(),
item_name: "苹果".to_string(),
item_size: Some(MATCH3D_ITEM_SIZE_MEDIUM.to_string()),
image_src: Some("/generated-match3d-assets/s/p/items/i2/image.png".to_string()),
image_object_key: Some(
"generated-match3d-assets/s/p/items/i2/image.png".to_string(),
@@ -8250,6 +8363,7 @@ mod tests {
Match3DGeneratedItemAsset {
item_id: "match3d-item-1".to_string(),
item_name: "草莓".to_string(),
item_size: Some(MATCH3D_ITEM_SIZE_SMALL.to_string()),
image_src: Some("/generated-match3d-assets/s/p/items/i1/image.png".to_string()),
image_object_key: Some(
"generated-match3d-assets/s/p/items/i1/image.png".to_string(),
@@ -8282,6 +8396,7 @@ mod tests {
Match3DGeneratedItemAsset {
item_id: "match3d-item-1".to_string(),
item_name: "草莓".to_string(),
item_size: Some(MATCH3D_ITEM_SIZE_SMALL.to_string()),
image_src: Some("/generated-match3d-assets/s/p/items/i1/image.png".to_string()),
image_object_key: Some(
"generated-match3d-assets/s/p/items/i1/image.png".to_string(),
@@ -8305,6 +8420,7 @@ mod tests {
Match3DGeneratedItemAsset {
item_id: "match3d-item-2".to_string(),
item_name: "苹果".to_string(),
item_size: Some(MATCH3D_ITEM_SIZE_MEDIUM.to_string()),
image_src: Some("/generated-match3d-assets/s/p/items/i2/image.png".to_string()),
image_object_key: Some(
"generated-match3d-assets/s/p/items/i2/image.png".to_string(),
@@ -8328,6 +8444,7 @@ mod tests {
Match3DGeneratedItemAsset {
item_id: "match3d-item-3".to_string(),
item_name: "香蕉".to_string(),
item_size: Some(MATCH3D_ITEM_SIZE_MEDIUM.to_string()),
image_src: Some("/generated-match3d-assets/s/p/items/i3/image.png".to_string()),
image_object_key: None,
image_views: Vec::new(),
@@ -8354,6 +8471,7 @@ mod tests {
.map(|index| Match3DGeneratedItemAsset {
item_id: format!("match3d-item-{index}"),
item_name: format!("物品{index}"),
item_size: Some(MATCH3D_ITEM_SIZE_LARGE.to_string()),
image_src: Some(format!(
"/generated-match3d-assets/s/p/items/i{index}/views/view-01.png"
)),

View File

@@ -52,11 +52,11 @@
首版详细设计见:
1. [../../../docs/technical/M4_MODULE_AI_BASELINE_DESIGN_2026-04-21.md](../../../docs/technical/M4_MODULE_AI_BASELINE_DESIGN_2026-04-21.md)
2. [../../../docs/technical/M4_MODULE_AI_SPACETIMEDB_BASELINE_2026-04-21.md](../../../docs/technical/M4_MODULE_AI_SPACETIMEDB_BASELINE_2026-04-21.md)
3. [../../../docs/technical/M4_MODULE_AI_AXUM_FACADE_DESIGN_2026-04-22.md](../../../docs/technical/M4_MODULE_AI_AXUM_FACADE_DESIGN_2026-04-22.md)
4. [../../../docs/technical/SERVER_RS_DDD_WP_AI_TASK_DOMAIN_REFACTOR_2026-04-29.md](../../../docs/technical/SERVER_RS_DDD_WP_AI_TASK_DOMAIN_REFACTOR_2026-04-29.md)
5. [../../../docs/technical/SERVER_RS_DDD_WP_AI_INTERNAL_MODULE_SPLIT_2026-04-29.md](../../../docs/technical/SERVER_RS_DDD_WP_AI_INTERNAL_MODULE_SPLIT_2026-04-29.md)
1. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
2. [../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)
3. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
4. [../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)
5. [../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)
## 3. 当前仍未进入的范围

View File

@@ -30,11 +30,11 @@
当前 `asset_object` 表的字段、索引与可编码约束见:
1. [../../../docs/technical/SPACETIMEDB_ASSET_OBJECT_TABLE_DESIGN_2026-04-21.md](../../../docs/technical/SPACETIMEDB_ASSET_OBJECT_TABLE_DESIGN_2026-04-21.md)
2. [../../../docs/technical/ASSET_OBJECT_CONFIRM_FLOW_DESIGN_2026-04-21.md](../../../docs/technical/ASSET_OBJECT_CONFIRM_FLOW_DESIGN_2026-04-21.md)
3. [../../../docs/technical/ASSET_ENTITY_BINDING_REDUCER_DESIGN_2026-04-21.md](../../../docs/technical/ASSET_ENTITY_BINDING_REDUCER_DESIGN_2026-04-21.md)
4. [../../../docs/technical/SERVER_RS_DDD_WP_AS_ASSET_OBJECT_TYPE_REHOME_2026-04-29.md](../../../docs/technical/SERVER_RS_DDD_WP_AS_ASSET_OBJECT_TYPE_REHOME_2026-04-29.md)
5. [../../../docs/technical/SERVER_RS_DDD_WP_AS_ASSET_CHAIN_CLOSURE_2026-05-01.md](../../../docs/technical/SERVER_RS_DDD_WP_AS_ASSET_CHAIN_CLOSURE_2026-05-01.md)
1. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
2. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
3. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
4. [../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)
5. [../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)
当前还已补齐:

View File

@@ -31,24 +31,15 @@
6. 微信登录 state 创建/消费
7. 微信身份解析与手机号绑定
## 3. 当前已冻结文档
## 当前文档入口
1. [../../../docs/technical/SPACETIMEDB_AUTH_USER_ACCOUNT_TABLE_DESIGN_2026-04-21.md](../../../docs/technical/SPACETIMEDB_AUTH_USER_ACCOUNT_TABLE_DESIGN_2026-04-21.md)
2. [../../../docs/technical/SPACETIMEDB_AUTH_IDENTITY_TABLE_DESIGN_2026-04-21.md](../../../docs/technical/SPACETIMEDB_AUTH_IDENTITY_TABLE_DESIGN_2026-04-21.md)
3. [../../../docs/technical/SPACETIMEDB_REFRESH_SESSION_TABLE_DESIGN_2026-04-21.md](../../../docs/technical/SPACETIMEDB_REFRESH_SESSION_TABLE_DESIGN_2026-04-21.md)
4. [../../../docs/technical/SPACETIMEDB_AUTH_AUDIT_LOG_TABLE_DESIGN_2026-04-21.md](../../../docs/technical/SPACETIMEDB_AUTH_AUDIT_LOG_TABLE_DESIGN_2026-04-21.md)
5. [../../../docs/technical/SPACETIMEDB_AUTH_RISK_BLOCK_TABLE_DESIGN_2026-04-21.md](../../../docs/technical/SPACETIMEDB_AUTH_RISK_BLOCK_TABLE_DESIGN_2026-04-21.md)
6. [../../../docs/technical/SPACETIMEDB_SMS_AUTH_EVENT_TABLE_DESIGN_2026-04-21.md](../../../docs/technical/SPACETIMEDB_SMS_AUTH_EVENT_TABLE_DESIGN_2026-04-21.md)
7. [../../../docs/technical/SPACETIMEDB_WECHAT_AUTH_STATE_TABLE_DESIGN_2026-04-21.md](../../../docs/technical/SPACETIMEDB_WECHAT_AUTH_STATE_TABLE_DESIGN_2026-04-21.md)
8. [../../../docs/technical/OIDC_JWT_CLAIMS_DESIGN_2026-04-21.md](../../../docs/technical/OIDC_JWT_CLAIMS_DESIGN_2026-04-21.md)
9. [../../../docs/technical/PLATFORM_AUTH_JWT_ADAPTER_DESIGN_2026-04-21.md](../../../docs/technical/PLATFORM_AUTH_JWT_ADAPTER_DESIGN_2026-04-21.md)
10. [../../../docs/technical/PLATFORM_AUTH_REFRESH_COOKIE_ADAPTER_DESIGN_2026-04-21.md](../../../docs/technical/PLATFORM_AUTH_REFRESH_COOKIE_ADAPTER_DESIGN_2026-04-21.md)
11. [../../../docs/technical/PASSWORD_ENTRY_FLOW_DESIGN_2026-04-21.md](../../../docs/technical/PASSWORD_ENTRY_FLOW_DESIGN_2026-04-21.md)
12. [../../../docs/technical/AUTH_REFRESH_ROTATION_DESIGN_2026-04-21.md](../../../docs/technical/AUTH_REFRESH_ROTATION_DESIGN_2026-04-21.md)
13. [../../../docs/technical/AUTH_LOGOUT_CURRENT_SESSION_DESIGN_2026-04-21.md](../../../docs/technical/AUTH_LOGOUT_CURRENT_SESSION_DESIGN_2026-04-21.md)
14. [../../../docs/technical/PHONE_AUTH_AXUM_REAL_SMS_PROVIDER_DESIGN_2026-04-22.md](../../../docs/technical/PHONE_AUTH_AXUM_REAL_SMS_PROVIDER_DESIGN_2026-04-22.md)
15. [../../../docs/technical/SERVER_RS_DDD_WP_A_AUTH_DOMAIN_VALUE_OBJECT_REFACTOR_2026-04-29.md](../../../docs/technical/SERVER_RS_DDD_WP_A_AUTH_DOMAIN_VALUE_OBJECT_REFACTOR_2026-04-29.md)
当前长期工程口径已融合到:
1. [../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)
2. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
3. [../../../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md](../../../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md)
旧阶段设计文档不再作为实现依据。
## 4. 边界约束
1. `module-auth` 负责鉴权领域规则与模块级编排,不直接把供应商 SDK 或阿里云 RPC 逻辑写进主工程。

View File

@@ -22,11 +22,15 @@
5. `src/events.rs` 承接发布门禁和运行态领域事件。
6. `src/lib.rs` 只保留模块声明、公开导出和测试,继续保持 `module_big_fish::*` 公开 API。
当前设计依据:
## 当前文档入口
1. [../../../docs/technical/SERVER_RS_DDD_WP_BF_RUNTIME_BACKEND_TRUTH_2026-04-29.md](../../../docs/technical/SERVER_RS_DDD_WP_BF_RUNTIME_BACKEND_TRUTH_2026-04-29.md)
2. [../../../docs/technical/SERVER_RS_DDD_WP_BF_AND_G2_DRIFT_CLEANUP_2026-04-30.md](../../../docs/technical/SERVER_RS_DDD_WP_BF_AND_G2_DRIFT_CLEANUP_2026-04-30.md)
当前长期工程口径已融合到:
1. [../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)
2. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
3. [../../../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md](../../../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md)
旧阶段设计文档不再作为实现依据。
## 3. 边界约束
1. `module-big-fish` 不直接调用图片生成、OSS、HTTP、SSE 或 SpacetimeDB SDK。

View File

@@ -34,12 +34,12 @@
落地依据见:
1. [../../../docs/technical/SERVER_RS_DDD_WP_RPG_COMBAT_DOMAIN_ENUM_REHOME_2026-04-29.md](../../../docs/technical/SERVER_RS_DDD_WP_RPG_COMBAT_DOMAIN_ENUM_REHOME_2026-04-29.md)
2. [../../../docs/technical/M4_MODULE_COMBAT_SPACETIMEDB_BASELINE_2026-04-21.md](../../../docs/technical/M4_MODULE_COMBAT_SPACETIMEDB_BASELINE_2026-04-21.md)
3. [../../../docs/technical/M4_MODULE_COMBAT_AXUM_FACADE_DESIGN_2026-04-21.md](../../../docs/technical/M4_MODULE_COMBAT_AXUM_FACADE_DESIGN_2026-04-21.md)
4. [../../../docs/technical/M4_MODULE_COMBAT_STATE_QUERY_DESIGN_2026-04-22.md](../../../docs/technical/M4_MODULE_COMBAT_STATE_QUERY_DESIGN_2026-04-22.md)
5. [../../../docs/technical/M4_PROGRESSION_QUEST_COMBAT_INTEGRATION_2026-04-21.md](../../../docs/technical/M4_PROGRESSION_QUEST_COMBAT_INTEGRATION_2026-04-21.md)
6. [../../../docs/prd/AI_NATIVE_BATTLE_SINGLE_ACTION_FUNCTION_PRD_2026-04-18.md](../../../docs/prd/AI_NATIVE_BATTLE_SINGLE_ACTION_FUNCTION_PRD_2026-04-18.md)
1. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
2. [../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)
3. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
4. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
5. [../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)
6. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
## 4. 边界约束

View File

@@ -41,22 +41,15 @@
3. 资产对象真相表、资产绑定表和完整资产历史。
4. 前端创作流程和 UI 表现状态。
当前设计依据:
## 当前文档入口
1. [../../../docs/technical/SERVER_RS_DDD_WP_CW_DOMAIN_ENUM_REHOME_2026-04-29.md](../../../docs/technical/SERVER_RS_DDD_WP_CW_DOMAIN_ENUM_REHOME_2026-04-29.md)
2. [../../../docs/technical/SERVER_RS_DDD_WP_CW_ACTION_AND_DOMAIN_SPLIT_2026-04-30.md](../../../docs/technical/SERVER_RS_DDD_WP_CW_ACTION_AND_DOMAIN_SPLIT_2026-04-30.md)
3. [../../../docs/technical/SPACETIMEDB_CUSTOM_WORLD_AGENT_STAGE1_TABLE_DESIGN_2026-04-21.md](../../../docs/technical/SPACETIMEDB_CUSTOM_WORLD_AGENT_STAGE1_TABLE_DESIGN_2026-04-21.md)
4. [../../../backend-rewrite-tasklist/04_M5_CUSTOM_WORLD_AND_AGENT.md](../../../backend-rewrite-tasklist/04_M5_CUSTOM_WORLD_AND_AGENT.md)
5. [../../../docs/technical/SPACETIMEDB_CUSTOM_WORLD_PUBLISHED_PROFILE_COMPILE_STAGE3_DESIGN_2026-04-21.md](../../../docs/technical/SPACETIMEDB_CUSTOM_WORLD_PUBLISHED_PROFILE_COMPILE_STAGE3_DESIGN_2026-04-21.md)
6. [../../../docs/technical/SPACETIMEDB_CUSTOM_WORLD_PUBLISH_WORLD_STAGE4_DESIGN_2026-04-21.md](../../../docs/technical/SPACETIMEDB_CUSTOM_WORLD_PUBLISH_WORLD_STAGE4_DESIGN_2026-04-21.md)
当前长期工程口径已融合到:
后续与本 package 直接相关的任务包括:
1. 设计 `custom_world_profile``custom_world_session`
2. 设计 `custom_world_agent_session`、消息、操作、卡片相关表
3. 对齐 traditional custom world、library、gallery、agent 兼容链路
4. 接入世界编译、场景图、封面图与角色资产的模块级编排
1. [../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)
2. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
3. [../../../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md](../../../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md)
旧阶段设计文档不再作为实现依据。
## 3. 边界约束
1. `module-custom-world` 负责世界状态真相、agent 状态与模块级编排,不把整个会话重新塞回单大 JSON 体。

View File

@@ -41,5 +41,5 @@
## 4. 关联文档
1. `docs/technical/M4_RPG_RUNTIME_INVENTORY_SPACETIMEDB_BASELINE_2026-04-21.md`
1. `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
2. `backend-rewrite-tasklist/03_M4_STORY_AND_GAMEPLAY.md`

View File

@@ -51,6 +51,8 @@ const MATCH3D_SIZE_TIER_RULES: [Match3DSizeTierRule; 5] = [
radius_scale: 0.76,
},
];
const MATCH3D_CONTAINER_MOUTH_SPAWN_RATIO: f32 = 0.78;
const MATCH3D_GOLDEN_ANGLE: f32 = 2.399_963_1;
pub fn compile_result_draft(config: &Match3DCreatorConfig) -> Match3DResultDraft {
let game_name = format!("{}抓大鹅", config.theme_text);
@@ -336,6 +338,7 @@ fn build_initial_items(
let item_type_count = resolve_item_type_count(clear_count, difficulty, item_type_count_override);
let selected_visual_keys = select_visual_keys(&mut rng, theme_text, item_type_count);
let size_tier_plan = resolve_size_tier_plan(item_type_count);
let total_item_count = clear_count * MATCH3D_ITEMS_PER_CLEAR;
let mut items = Vec::with_capacity((clear_count * MATCH3D_ITEMS_PER_CLEAR) as usize);
for clear_index in 0..clear_count {
@@ -345,8 +348,14 @@ fn build_initial_items(
let radius = resolve_item_radius_variant(base_radius, size_tier_plan[visual_index]);
for copy_index in 0..MATCH3D_ITEMS_PER_CLEAR {
let (x, y) = random_point_in_circle(&mut rng, max_spawn_offset(radius));
let instance_index = clear_index * MATCH3D_ITEMS_PER_CLEAR + copy_index;
let (x, y) = spawn_point_in_container_mouth(
instance_index,
total_item_count,
radius,
rng.next_unit_signed(),
rng.next_unit_signed(),
);
items.push(Match3DItemSnapshot {
item_instance_id: format!("match3d-item-{instance_index:04}"),
item_type_id: item_type_id.clone(),
@@ -474,15 +483,29 @@ fn max_spawn_offset(radius: f32) -> f32 {
(MATCH3D_BOARD_RADIUS - MATCH3D_BOARD_SAFE_MARGIN - radius).max(0.0)
}
fn random_point_in_circle(rng: &mut DeterministicRng, max_radius: f32) -> (f32, f32) {
for _ in 0..24 {
let x = rng.next_unit_signed() * max_radius;
let y = rng.next_unit_signed() * max_radius;
if x * x + y * y <= max_radius * max_radius {
return (MATCH3D_BOARD_CENTER + x, MATCH3D_BOARD_CENTER + y);
}
fn spawn_point_in_container_mouth(
item_index: u32,
total_item_count: u32,
radius: f32,
jitter_x: f32,
jitter_y: f32,
) -> (f32, f32) {
let safe_radius = max_spawn_offset(radius);
let mouth_radius = safe_radius * MATCH3D_CONTAINER_MOUTH_SPAWN_RATIO;
let total = total_item_count.max(1) as f32;
let index = item_index as f32;
let distance = ((index + 0.5) / total).sqrt() * mouth_radius;
let angle = index * MATCH3D_GOLDEN_ANGLE;
let jitter_radius = (mouth_radius * 0.035).min(0.012);
let mut dx = angle.cos() * distance + jitter_x * jitter_radius;
let mut dy = angle.sin() * distance + jitter_y * jitter_radius;
let current_distance = (dx * dx + dy * dy).sqrt();
if current_distance > safe_radius && current_distance > 0.0 {
let ratio = safe_radius / current_distance;
dx *= ratio;
dy *= ratio;
}
(MATCH3D_BOARD_CENTER, MATCH3D_BOARD_CENTER)
(MATCH3D_BOARD_CENTER + dx, MATCH3D_BOARD_CENTER + dy)
}
fn fully_covers(
@@ -943,6 +966,58 @@ mod tests {
}
}
#[test]
fn initial_positions_are_centered_in_container_mouth() {
let run = start_run_with_seed_at(
"run-centered-spawn".to_string(),
"user-1".to_string(),
"profile-1".to_string(),
&build_creator_config("玩具", None, 21, 8).expect("config should be valid"),
12,
1_000,
)
.expect("run should start");
let board_items = run
.items
.iter()
.filter(|item| item.state == Match3DItemState::InBoard)
.collect::<Vec<_>>();
let item_count = board_items.len() as f32;
let mean_x = board_items.iter().map(|item| item.x).sum::<f32>() / item_count;
let mean_y = board_items.iter().map(|item| item.y).sum::<f32>() / item_count;
let max_distance = board_items
.iter()
.map(|item| {
let dx = item.x - MATCH3D_BOARD_CENTER;
let dy = item.y - MATCH3D_BOARD_CENTER;
(dx * dx + dy * dy).sqrt()
})
.fold(0.0_f32, f32::max);
let far_item_count = board_items
.iter()
.filter(|item| {
let dx = item.x - MATCH3D_BOARD_CENTER;
let dy = item.y - MATCH3D_BOARD_CENTER;
(dx * dx + dy * dy).sqrt() > 0.32
})
.count();
let mut quadrants = BTreeMap::<String, u32>::new();
for item in board_items {
let quadrant = format!(
"{}-{}",
if item.x >= MATCH3D_BOARD_CENTER { "r" } else { "l" },
if item.y >= MATCH3D_BOARD_CENTER { "b" } else { "t" },
);
*quadrants.entry(quadrant).or_default() += 1;
}
assert!((mean_x - MATCH3D_BOARD_CENTER).abs() < 0.035);
assert!((mean_y - MATCH3D_BOARD_CENTER).abs() < 0.035);
assert!(max_distance < 0.4);
assert!(far_item_count > 0);
assert_eq!(quadrants.len(), 4);
}
#[test]
fn twenty_five_or_less_does_not_repeat_visual_keys() {
let run = start_run_with_seed_at_and_item_type_count(

View File

@@ -31,11 +31,11 @@
## 3. 当前已冻结关联文档
1. [../../../docs/design/LEVEL_PROGRESS_AND_CHAPTER_NPC_AUTO_SCALING_DESIGN_2026-04-20.md](../../../docs/design/LEVEL_PROGRESS_AND_CHAPTER_NPC_AUTO_SCALING_DESIGN_2026-04-20.md)
2. [../../../docs/technical/SPACETIMEDB_AXUM_OSS_BACKEND_REWRITE_DESIGN_2026-04-20.md](../../../docs/technical/SPACETIMEDB_AXUM_OSS_BACKEND_REWRITE_DESIGN_2026-04-20.md)
3. [../../../docs/technical/M4_MODULE_PROGRESSION_SPACETIMEDB_BASELINE_2026-04-21.md](../../../docs/technical/M4_MODULE_PROGRESSION_SPACETIMEDB_BASELINE_2026-04-21.md)
4. [../../../docs/technical/M4_PROGRESSION_QUEST_COMBAT_INTEGRATION_2026-04-21.md](../../../docs/technical/M4_PROGRESSION_QUEST_COMBAT_INTEGRATION_2026-04-21.md)
5. [../../../docs/technical/SERVER_RS_DDD_WP_RPG_PROGRESSION_DOMAIN_SPLIT_2026-04-30.md](../../../docs/technical/SERVER_RS_DDD_WP_RPG_PROGRESSION_DOMAIN_SPLIT_2026-04-30.md)
1. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
2. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
3. [../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)
4. [../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)
5. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
## 4. 边界约束

View File

@@ -38,15 +38,15 @@
2. Axum 路由、SSE 或前端展示状态。
3. SpacetimeDB table、reducer、procedure 的直接定义。
当前设计依据:
## 当前文档入口
1. [../../../docs/technical/SERVER_RS_DDD_WP_PZ_DOMAIN_ENUM_REHOME_2026-04-29.md](../../../docs/technical/SERVER_RS_DDD_WP_PZ_DOMAIN_ENUM_REHOME_2026-04-29.md)
2. [../../../docs/technical/SERVER_RS_DDD_WP_PZ_DOMAIN_SPLIT_2026-04-29.md](../../../docs/technical/SERVER_RS_DDD_WP_PZ_DOMAIN_SPLIT_2026-04-29.md)
3. [../../../docs/technical/SERVER_RS_DDD_WP_PZ_RUNTIME_BACKEND_TRUTH_CLOSURE_2026-05-01.md](../../../docs/technical/SERVER_RS_DDD_WP_PZ_RUNTIME_BACKEND_TRUTH_CLOSURE_2026-05-01.md)
4. [../../../docs/technical/SERVER_RS_DDD_WP_DEL_CLEANUP_2026-05-01.md](../../../docs/technical/SERVER_RS_DDD_WP_DEL_CLEANUP_2026-05-01.md)
5. [../../../docs/technical/PUZZLE_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-22.md](../../../docs/technical/PUZZLE_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-22.md)
6. [../../../docs/technical/PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md](../../../docs/technical/PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md)
当前长期工程口径已融合到:
1. [../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)
2. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
3. [../../../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md](../../../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md)
旧阶段设计文档不再作为实现依据。
## 3. 边界约束
1. `module-puzzle` 不直接调用图片生成、OSS、HTTP、SSE 或 SpacetimeDB SDK。

View File

@@ -52,4 +52,4 @@
1. `module-quest` 负责任务状态真相与任务规则,生成型任务草案与外部 AI 编排不直接塞进模块内部。
2. 任务状态最终回写到 `crates/spacetime-module` 聚合的状态模型中,前端兼容接口由 `crates/api-server` 暴露。
3. 任务不能再次散落到 story service、runtime service 或前端临时状态里分别维护。
4. 当前真实工程口径以 `docs/technical/M4_RPG_RUNTIME_QUEST_SPACETIMEDB_BASELINE_2026-04-21.md` 为准。
4. 当前真实工程口径以 `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md` 为准。

View File

@@ -30,4 +30,4 @@
1. 当前 crate 只负责任务奖励/宝藏奖励与 inventory 的字段桥接,不承接 story 编排或前端 DTO。
2. 宝藏、任务、交易等不同奖励入口若要写入 `inventory_slot`,优先复用这里与 `module-inventory` 的桥接口径,而不是各自重新拼装物品字段。
3. 当前真实工程口径以 `docs/technical/M4_RUNTIME_ITEM_TREASURE_SPACETIMEDB_BASELINE_2026-04-21.md` 为准。
3. 当前真实工程口径以 `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md` 为准。

View File

@@ -15,4 +15,4 @@
当前 WP-RS 写链路已经通过 `POST /api/story/sessions/runtime``POST /api/story/sessions/{storySessionId}/actions/resolve` 收口到 session scoped 新接口。后续只按 battle / forge / NPC / quest / presentation 的顺序增强领域规则和投影,并在 `WP-DEL` 中删除运行代码不再需要的旧入口命名。
配套记录见 [../../../docs/technical/SERVER_RS_DDD_WP_RS_RUNTIME_STORY_DOMAIN_SPLIT_2026-04-30.md](../../../docs/technical/SERVER_RS_DDD_WP_RS_RUNTIME_STORY_DOMAIN_SPLIT_2026-04-30.md)。
配套记录见 [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)。

View File

@@ -32,12 +32,12 @@
5. settings、browse history、profile/save 等记录投影 builder 已迁入 `src/application.rs`
6. checkpoint、profile/save archive meta、充值/邀请/兑换/钱包等剩余纯规则已迁入 `src/application.rs``spacetime-module` 只保留表事务读写,`api-server` 只保留 HTTP/BFF 映射。
7. 详细边界与验收记录见:
- `docs/technical/SERVER_RS_DDD_WP_RT_RUNTIME_SETTINGS_DOMAIN_REFACTOR_2026-04-29.md`
- `docs/technical/SERVER_RS_DDD_WP_RT_DOMAIN_SNAPSHOT_RECORD_REFACTOR_2026-04-29.md`
- `docs/technical/SERVER_RS_DDD_WP_RT_ERROR_LAYER_REFACTOR_2026-04-29.md`
- `docs/technical/SERVER_RS_DDD_WP_RT_COMMANDS_REFACTOR_2026-04-29.md`
- `docs/technical/SERVER_RS_DDD_WP_RT_APPLICATION_RECORD_REFACTOR_2026-04-29.md`
- `docs/technical/SERVER_RS_DDD_WP_RT_ADAPTER_API_CLOSURE_2026-04-29.md`
- `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
- `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
- `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
- `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
- `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## 3. 边界约束

View File

@@ -142,10 +142,10 @@ pub fn default_creation_entry_type_snapshots(
"bark-battle",
"汪汪声浪",
"声控对战挑战",
"可创建",
"敬请期待",
"/creation-type-references/creative-agent.webp",
true,
true,
false,
85,
updated_at_micros,
),
@@ -153,10 +153,10 @@ pub fn default_creation_entry_type_snapshots(
"baby-object-match",
"宝贝识物",
"亲子识物分类",
"可创建",
"敬请期待",
"/child-motion-demo/picture-book-grass-stage.png",
true,
true,
false,
90,
updated_at_micros,
),

View File

@@ -220,7 +220,8 @@ mod tests {
assert_eq!(baby_object_match.title, "宝贝识物");
assert_eq!(baby_object_match.subtitle, "亲子识物分类");
assert!(baby_object_match.visible);
assert!(baby_object_match.open);
assert!(!baby_object_match.open);
assert_eq!(baby_object_match.badge, "敬请期待");
assert_eq!(baby_object_match.sort_order, 90);
assert_eq!(
baby_object_match.image_src,
@@ -238,7 +239,8 @@ mod tests {
assert_eq!(bark_battle.title, "汪汪声浪");
assert!(bark_battle.visible);
assert!(bark_battle.open);
assert!(!bark_battle.open);
assert_eq!(bark_battle.badge, "敬请期待");
assert_eq!(bark_battle.sort_order, 85);
}

View File

@@ -73,6 +73,6 @@
## 6. 关联文档
1. [../../../docs/technical/OIDC_JWT_CLAIMS_DESIGN_2026-04-21.md](../../../docs/technical/OIDC_JWT_CLAIMS_DESIGN_2026-04-21.md)
2. [../../../docs/technical/PLATFORM_AUTH_JWT_ADAPTER_DESIGN_2026-04-21.md](../../../docs/technical/PLATFORM_AUTH_JWT_ADAPTER_DESIGN_2026-04-21.md)
3. [../../../docs/technical/PLATFORM_AUTH_REFRESH_COOKIE_ADAPTER_DESIGN_2026-04-21.md](../../../docs/technical/PLATFORM_AUTH_REFRESH_COOKIE_ADAPTER_DESIGN_2026-04-21.md)
1. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
2. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
3. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)

View File

@@ -37,10 +37,15 @@
## 4. 设计文档
详细约束与接口说明见:
## 当前文档入口
- [../../../docs/technical/PLATFORM_LLM_TEXT_GATEWAY_DESIGN_2026-04-21.md](../../../docs/technical/PLATFORM_LLM_TEXT_GATEWAY_DESIGN_2026-04-21.md)
当前长期工程口径已融合到:
1. [../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)
2. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
3. [../../../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md](../../../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md)
旧阶段设计文档不再作为实现依据。
## 5. 边界约束
1. `platform-llm` 只承接模型平台适配,不承接业务模块状态真相与业务规则。

View File

@@ -140,6 +140,8 @@ pub struct Match3DGeneratedItemImageViewResponse {
pub struct Match3DGeneratedItemAssetResponse {
pub item_id: String,
pub item_name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub item_size: Option<String>,
#[serde(default)]
pub image_src: Option<String>,
#[serde(default)]

View File

@@ -198,6 +198,8 @@ pub struct Match3DGeneratedItemImageViewResponse {
pub struct Match3DGeneratedItemAssetResponse {
pub item_id: String,
pub item_name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub item_size: Option<String>,
#[serde(default)]
pub image_src: Option<String>,
#[serde(default)]

View File

@@ -50,10 +50,12 @@
2. 任何进入本 crate 的类型都必须证明至少被多个模块稳定复用。
3. 不能把主模块实现重新堆进共享内核,避免形成新的“大公共垃圾桶”。
更详细的阶段性设计见:
## 当前文档入口
1. `docs/technical/RUST_SHARED_KERNEL_CRATE_STAGE1_DESIGN_2026-04-21.md`
2. `docs/technical/RUST_SHARED_KERNEL_CRATE_STAGE2_ADOPTION_2026-04-21.md`
3. `docs/technical/RUST_SHARED_KERNEL_CRATE_STAGE3_VALUE_NORMALIZATION_2026-04-22.md`
4. `docs/technical/RUST_SHARED_KERNEL_CRATE_STAGE4_REQUIRED_STRING_ADOPTION_2026-04-22.md`
5. `docs/technical/RUST_SHARED_KERNEL_CRATE_STAGE5_PURE_DOMAIN_FIELD_ADOPTION_2026-04-22.md`
当前长期工程口径已融合到:
1. [../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)
2. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
3. [../../../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md](../../../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md)
旧阶段设计文档不再作为实现依据。

View File

@@ -17,7 +17,7 @@
3. 统一 SDK 调用错误、业务 procedure 错误、缺失快照错误和超时错误。
4. 不承载领域规则,不直接定义 table / reducer / procedure不替代 `spacetime-module`
本轮方案见 [`SERVER_RS_DDD_WP_SC_SPACETIME_CLIENT_REFACTOR_2026-04-29.md`](../../../docs/technical/SERVER_RS_DDD_WP_SC_SPACETIME_CLIENT_REFACTOR_2026-04-29.md)。
当前约束见 [../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)。
## 2. 当前完成口径

View File

@@ -180,36 +180,15 @@
后续如果新增 SpacetimeDB 表、reducer、procedure 或同域 helper必须先判断属于哪个一级模块与二级落位点再写入对应文件禁止直接追加到 `src/lib.rs`
`asset_object` 的详细设计见:
## 当前文档入口
1. [../../../docs/technical/SPACETIMEDB_ASSET_OBJECT_TABLE_DESIGN_2026-04-21.md](../../../docs/technical/SPACETIMEDB_ASSET_OBJECT_TABLE_DESIGN_2026-04-21.md)
2. [../../../docs/technical/ASSET_ENTITY_BINDING_REDUCER_DESIGN_2026-04-21.md](../../../docs/technical/ASSET_ENTITY_BINDING_REDUCER_DESIGN_2026-04-21.md)
当前长期工程口径已融合到:
`M5 custom world / agent` 首批表设计见:
1. [../../../docs/technical/SPACETIMEDB_CUSTOM_WORLD_AGENT_STAGE1_TABLE_DESIGN_2026-04-21.md](../../../docs/technical/SPACETIMEDB_CUSTOM_WORLD_AGENT_STAGE1_TABLE_DESIGN_2026-04-21.md)
2. [../../../backend-rewrite-tasklist/04_M5_CUSTOM_WORLD_AND_AGENT.md](../../../backend-rewrite-tasklist/04_M5_CUSTOM_WORLD_AND_AGENT.md)
3. [../../../docs/technical/SPACETIMEDB_CUSTOM_WORLD_LIBRARY_GALLERY_STAGE2_DESIGN_2026-04-21.md](../../../docs/technical/SPACETIMEDB_CUSTOM_WORLD_LIBRARY_GALLERY_STAGE2_DESIGN_2026-04-21.md)
4. [../../../docs/technical/SPACETIMEDB_CUSTOM_WORLD_PUBLISHED_PROFILE_COMPILE_STAGE3_DESIGN_2026-04-21.md](../../../docs/technical/SPACETIMEDB_CUSTOM_WORLD_PUBLISHED_PROFILE_COMPILE_STAGE3_DESIGN_2026-04-21.md)
5. [../../../docs/technical/SPACETIMEDB_CUSTOM_WORLD_PUBLISH_WORLD_STAGE4_DESIGN_2026-04-21.md](../../../docs/technical/SPACETIMEDB_CUSTOM_WORLD_PUBLISH_WORLD_STAGE4_DESIGN_2026-04-21.md)
`module-ai` 的当前基座设计见:
1. [../../../docs/technical/M4_MODULE_AI_BASELINE_DESIGN_2026-04-21.md](../../../docs/technical/M4_MODULE_AI_BASELINE_DESIGN_2026-04-21.md)
2. [../../../docs/technical/M4_MODULE_AI_SPACETIMEDB_BASELINE_2026-04-21.md](../../../docs/technical/M4_MODULE_AI_SPACETIMEDB_BASELINE_2026-04-21.md)
3. [../../../docs/technical/M4_MODULE_AI_AXUM_FACADE_DESIGN_2026-04-22.md](../../../docs/technical/M4_MODULE_AI_AXUM_FACADE_DESIGN_2026-04-22.md)
当前身份透传设计依据:
1. [../../../docs/technical/OIDC_JWT_CLAIMS_DESIGN_2026-04-21.md](../../../docs/technical/OIDC_JWT_CLAIMS_DESIGN_2026-04-21.md)
当前本地开发脚本约定:
1. `../../scripts/spacetime-dev.ps1``../../scripts/spacetime-dev.sh` 当前固定执行 `spacetime start` 的 standalone 模式。
2. 默认监听 `127.0.0.1:3000`,与 `spacetime` CLI 的 `local` server 默认口径保持一致。
3. 本地数据目录固定到 `server-rs/.spacetimedb/local`,避免污染全局 SpacetimeDB 根目录。
4. 当前阶段暂不自动 publish `crates/spacetime-module`,待 module 实体 scaffold 与聚合入口落地后再扩展。
1. [../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md](../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)
2. [../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md](../../../docs/【玩法创作】平台入口与玩法链路-2026-05-15.md)
3. [../../../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md](../../../docs/【开发运维】本地开发验证与生产运维-2026-05-15.md)
旧阶段设计文档不再作为实现依据。
## 3. 边界约束
1. `spacetime-module` 只聚合状态模型,不直接承接 HTTP、Cookie、Header、OSS、短信、微信、LLM 等外部副作用。

View File

@@ -180,6 +180,28 @@ fn seed_creation_entry_config_if_missing(ctx: &ReducerContext) {
}
migrate_visual_novel_entry_from_old_visible_default(ctx, now);
migrate_coming_soon_entry_from_old_open_default(
ctx,
now,
ComingSoonEntryDefault {
id: "bark-battle",
title: "汪汪声浪",
subtitle: "声控对战挑战",
image_src: "/creation-type-references/creative-agent.webp",
sort_order: 85,
},
);
migrate_coming_soon_entry_from_old_open_default(
ctx,
now,
ComingSoonEntryDefault {
id: "baby-object-match",
title: "宝贝识物",
subtitle: "亲子识物分类",
image_src: "/child-motion-demo/picture-book-grass-stage.png",
sort_order: 90,
},
);
}
fn migrate_visual_novel_entry_from_old_visible_default(ctx: &ReducerContext, now: Timestamp) {
@@ -212,6 +234,47 @@ fn migrate_visual_novel_entry_from_old_visible_default(ctx: &ReducerContext, now
});
}
struct ComingSoonEntryDefault {
id: &'static str,
title: &'static str,
subtitle: &'static str,
image_src: &'static str,
sort_order: i32,
}
fn migrate_coming_soon_entry_from_old_open_default(
ctx: &ReducerContext,
now: Timestamp,
target: ComingSoonEntryDefault,
) {
let id = target.id.to_string();
let Some(row) = ctx.db.creation_entry_type_config().id().find(&id) else {
return;
};
// 中文注释:只把旧默认开放种子纠偏为敬请期待,不覆盖后台手动维护过的入口配置。
let still_old_open_default = row.title == target.title
&& row.subtitle == target.subtitle
&& row.badge == "可创建"
&& row.image_src == target.image_src
&& row.visible
&& row.open
&& row.sort_order == target.sort_order;
if !still_old_open_default {
return;
}
ctx.db
.creation_entry_type_config()
.id()
.update(CreationEntryTypeConfig {
badge: "敬请期待".to_string(),
open: false,
updated_at: now,
..row
});
}
fn default_creation_entry_type_configs(now: Timestamp) -> Vec<CreationEntryTypeConfig> {
module_runtime::default_creation_entry_type_snapshots(now.to_micros_since_unix_epoch())
.into_iter()