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:
@@ -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 操作和生产运维。
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 和 soundPrompt,name 为 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 和 soundPrompt。name 为 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
|
||||
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. 当前仍未进入的范围
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
当前还已补齐:
|
||||
|
||||
|
||||
@@ -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 逻辑写进主工程。
|
||||
|
||||
@@ -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。
|
||||
|
||||
@@ -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. 边界约束
|
||||
|
||||
|
||||
@@ -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 体。
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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. 边界约束
|
||||
|
||||
|
||||
@@ -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。
|
||||
|
||||
@@ -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` 为准。
|
||||
|
||||
@@ -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` 为准。
|
||||
|
||||
@@ -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)。
|
||||
|
||||
@@ -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. 边界约束
|
||||
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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` 只承接模型平台适配,不承接业务模块状态真相与业务规则。
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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)
|
||||
|
||||
旧阶段设计文档不再作为实现依据。
|
||||
|
||||
@@ -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. 当前完成口径
|
||||
|
||||
|
||||
@@ -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 等外部副作用。
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user