This commit is contained in:
2026-04-26 20:50:58 +08:00
parent a3a9bfa194
commit 67161bd6d1
142 changed files with 3349 additions and 10674 deletions

View File

@@ -8,7 +8,7 @@
1. 战斗中一次点击只完成一个明确行为,不再做连续多轮击打、连续多 actor 轮转的 function 设计。
2. 战斗中除逃跑外,不再为每次动作额外触发剧情推理,而是直接结算数值并刷新下一轮战斗选项。
3. 只有在逃跑成功或战斗正式结束后,才触发一次剧情推理,生成脱战后的 storyText 与后续剧情选项。
3. 战斗动作、逃跑、战斗胜利、切磋结束与玩家死亡都不再触发剧情推理,只做确定性数值结算与固定流程选项。
---
@@ -143,24 +143,13 @@ ongoing battle 的本地/后端结果文本只负责说明这一次动作结算
3. 直接刷新新一轮战斗选项
4. `storyText` 直接使用本次结算结果文本,不请求 AI 续写
### 5.2 必须触发剧情推理的情况
### 5.2 战斗结束后的固定流程
以下情况必须触发剧情推理
战斗正式结束后仍然不触发剧情推理,直接走固定 UI 流程
1. `battle_escape_breakout` 执行后成功脱战
2. 任意战斗动作执行后,战斗正式结束
战斗正式结束包括:
- 敌方被击败
- 切磋结束
- 玩家被系统判定为本轮战斗已断开
此时系统行为为:
1. 先完成数值结算与状态落地
2. 再以“本次动作 + 本次战斗结果”为上下文触发一次剧情推理
3. 生成脱战后的 `storyText` 与非战斗态 options
1. 敌方被击败或切磋结束:展示结算文本,并只给一个“继续前进”选项。
2. 如果当前场景当前幕已经是最后一幕,则不再给“继续前进”,改为列出可前往的其他场景选项。
3. 玩家血量小于等于 0先播放死亡动画三秒后把玩家复活到第一个场景第一幕。
---
@@ -196,7 +185,7 @@ ongoing battle 的本地/后端结果文本只负责说明这一次动作结算
1. 后端 runtime 战斗 option 池切换到单行为模型
2. 后端 combat resolution 支持普通攻击 / 指定技能 / 恢复 / 战斗物品 / 逃跑
3. 后端只在逃跑或战斗结束后做剧情推理
3. 后端和前端都不在战斗流程里触发剧情推理,胜负只走固定结算选项
4. 前端支持透传战斗 option 的 `runtimePayload`
5. 前端支持 disabled battle option 展示
6. 文档、测试同步更新
@@ -228,6 +217,6 @@ ongoing battle 的本地/后端结果文本只负责说明这一次动作结算
- 每个技能一个独立技能项
- 逃跑
3. 点击普通攻击 / 恢复 / 使用物品 / 技能时,不请求新的剧情推理,直接返回结算结果并刷新下一轮战斗 options。
4. 点击逃跑成功后,请求一次剧情推理并切回脱战后的剧情 options。
5. 任意攻击或技能把敌人打死后,请求一次剧情推理并切回脱战后的剧情 options
4. 点击逃跑成功后,请求剧情推理,直接切回非战斗固定 options。
5. 任意攻击或技能把敌人打死后,请求剧情推理,直接显示“继续前进”或其他场景选项
6. 旧存档里残留旧 battle functionId 时,不会因为 function 不识别而报错。

View File

@@ -0,0 +1,45 @@
# 世界底稿开局场景批生成解耦说明 2026-04-26
## 背景
当前第一版世界底稿生成链路里,`framework` 阶段同时要求模型输出 `camp.sceneTaskDescription``camp.actBackgroundPromptTexts``camp.actEventDescriptions`。这让“世界核心骨架”和“开局场景多幕内容”混在同一次世界生成任务里,后续普通场景批生成又要单独生成相同粒度的场景任务、三幕事件、三幕背景和幕 NPC 分配。
这与《AI 原生多幕场景创作与玩法流程 PRD》中“开局场景不是特殊系统只是玩家开局所处的第一个场景”的约束不一致。开局场景应复用普通场景的批生成能力而不是由世界骨架阶段提前生成一套缩水版内容。
## 落地边界
1. `framework` 阶段只负责世界顶层信息和轻量 `camp` 占位:
- `name`
- `description`
2. 场景批生成阶段负责生成完整场景骨架:
- 场景名与描述
- 默认场景生图描述
- `sceneTaskDescription`
- `actBackgroundPromptTexts`
- `actEventDescriptions`
- `actNPCNames`
- `connectedLandmarkNames`
- `entryHook`
3. 批生成场景结果的第一项固定视为开局场景:
- 写回 `profile.camp`
- `camp.id` 缺失时固定为 `camp-1`
- `camp.kind` 固定为 `camp`
4. 批生成场景结果的其余项写入 `profile.landmarks`
5. `sceneChapterBlueprints` 仍由统一场景蓝图编译函数生成:
- 第 0 项来自 `camp`
- 后续项来自 `landmarks`
- 开局场景和普通场景共用三幕、NPC、任务、背景提示词规则
## 兼容策略
为了减少前端和存量链路改动,场景批生成 API 层仍沿用现有 `landmarks` JSON 字段名。字段名不再表示“只包含普通地标”,而表示“本批生成的场景条目”,其中第一项是 opening/camp 场景。
如果模型没有返回任何场景条目,则继续使用 `framework.camp` 的轻量占位构造兜底开局场景;这只是异常兜底,不是主生成路径。
## 验收点
1. 世界骨架 prompt 不再要求 `camp.sceneTaskDescription``camp.actBackgroundPromptTexts``camp.actEventDescriptions`
2. 场景批生成 prompt 明确要求第一项是开局场景。
3. 生成后的 `profile.camp` 来自场景批生成第一项,而不是来自世界骨架阶段的开局多幕内容。
4. `profile.landmarks[0]` 是第一个普通场景,不再重复包含开局场景。
5. `sceneChapterBlueprints[0].sceneId === "camp-1"`,且仍包含 3 幕背景与事件描述。

View File

@@ -0,0 +1,38 @@
# RPG 战斗确定性结束流程
更新时间:`2026-04-26`
## 背景
旧战斗单行为 PRD 允许在逃跑成功或战斗结束后再触发一次剧情推理。新的运行时规则改为:战斗过程与战斗结果都不再请求剧情推理,所有胜负出口都由确定性状态与固定选项驱动。
## 落地规则
1. 战斗中的 `battle_attack_basic / battle_use_skill / battle_recover_breath / inventory_use / battle_escape_breakout` 只结算数值、血量、冷却、物品与战斗状态,不再请求 `generateNextStep` 或 Rust `generate_action_story_payload` 的战斗推理。
2. 玩家血量小于等于 0 时,先展示死亡动画;三秒后复活到当前世界的第一个场景第一幕。
3. 复活状态必须清理战斗、遭遇、NPC 交互、战斗特效、当前 NPC 战斗结果,并把生命与灵力恢复到最大值。
4. 战斗胜利或切磋结束时,弹出固定选项:
- 若当前场景仍有下一幕:只显示“继续前进”,并把下一幕运行时状态与可用选项挂到 `deferredRuntimeState / deferredOptions`,点击后只揭开下一幕选项,不触发剧情推理。
- 若当前场景已是最后一幕:显示可前往的其他场景选项,每个选项透传 `runtimePayload.targetSceneId`
5. “继续前进”和“前往其他场景”都是本地状态推进,不展示额外说明面板,不把规则描述写入 UI。
## 工程落点
1. `src/hooks/rpg-runtime-story/postBattleFlow.ts`
- 生成死亡复活状态。
- 生成战斗胜利后的固定 `StoryMoment`
- 解析下一幕或其他场景选项。
2. `src/hooks/rpg-runtime-story/storyChoiceContinuation.ts`
- 本地 fallback 胜利分支不再调用 `generateStoryForState(...)`
- 玩家死亡分支播放死亡态并延迟复活。
3. `src/hooks/rpg-runtime-story/storyChoiceRuntime.ts`
- 服务端 runtime action 回包若是战斗死亡或胜利,同样覆盖为固定流程。
4. `server-rs/crates/api-server/src/runtime_story/compat/ai.rs`
- 战斗 outcome 不再触发 reasoned story payload。
## 验收
1. 战斗中普通攻击、技能、恢复、物品、逃跑不会触发剧情推理。
2. 玩家被打到 0 血时先播放死亡动画,约三秒后复活到第一个场景第一幕。
3. 战斗胜利后只出现“继续前进”;最后一幕后出现其他场景入口。
4. 服务端 runtime action 与本地 fallback 两条链表现一致。

View File

@@ -18,6 +18,8 @@
3. `battle_use_skill` 的本地兜底结算必须尊重 `runtimePayload.skillId`,不能重新随机挑技能。
4. `battle_recover_breath` 的本地兜底不能伪装成攻击动作;它只做恢复、冷却推进和后续敌方压力。
5. 服务端战斗回包如果带 `presentation.battle`,前端先播放一次短动作和血量变化,再落 `hydratedSnapshot.gameState`,避免选项点击后血量直接跳变。
6. 战斗中任一角色血量发生变化时,表现层根据前后血量差派生一次浮字:伤害使用红色负数,治疗使用绿色正数;该浮字只表达已经结算的血量差,不参与数值计算。
7. 伤害浮字出现时,受击角色形象需要沿自身背向短距离后退再恢复;该后撤只作用在角色形象视觉容器,不改变 `playerX``sceneHostileNpcs.xMeters`、同伴站位或任何战斗结算字段。
## 验收点
@@ -25,3 +27,5 @@
- 点击具体技能按钮时,播放与结算使用同一个 `skillId`
- 点击恢复时不会出现玩家同时释放攻击技能的错位表现。
- 走服务端 runtime action 的战斗选项仍以 server-rs 返回快照为最终状态。
- 受到伤害时,角色形象上方出现类似 `-9` 的红色浮字,并有短暂后撤回弹。
- 获得治疗时,角色形象上方出现类似 `+1` 的绿色浮字,不触发受击后撤。

View File

@@ -0,0 +1,27 @@
# RPG 创作结果页新增实体同步修复 2026-04-26
## 问题
Agent 草稿结果页点击“新增可扮演角色”后,后端生成请求已完成,但前端提示:
`生成请求已完成,但结果页未收到新增内容,请返回创作页重新打开草稿后重试。`
触发点在结果页实体生成完成后的数量校验:前端会执行 `generate_characters`,等待操作完成后重新拉取 Agent session再把 session 编译成结果页 `CustomWorldProfile`。如果新 profile 中对应实体数量没有增加,就显示该错误。
## 根因
当前链路已经约定 `session.draftProfile` 是 Agent 草稿与 RPG 运行时的真相源,但 `rpgCreationPreviewAdapter.buildPreviewFromSession()` 仍优先读取 `session.resultPreview.preview`
当后端 action 已把新增角色写入 `draftProfile`,但 `resultPreview` 仍是旧快照时,结果页会继续消费旧预览,导致数量校验误判为“没有新增内容”。
## 修复
- `buildCustomWorldProfileFromAgentSession()` 改为优先归一化 `session.draftProfile`
- `resultPreview.preview` 只作为 `draftProfile` 缺失时的兼容 fallback。
- 更新单测覆盖“session 同时存在 draftProfile 与 resultPreview 时,结果页 profile 必须来自 draftProfile”。
## 后续约束
- Agent 草稿结果页新增、删除、进入世界都应以 `draftProfile` 为数据源。
- `resultPreview` 继续承载发布质量、blocker、预览外壳信息不再作为结果页实体列表的优先数据源。
- 若新增实体字段在结果页缺失,应扩展 `draftProfile` 的归一化兼容,而不是把实体读取切回 `resultPreview.preview`

View File

@@ -0,0 +1,102 @@
# RPG 创作编辑器历史素材复用设计
日期:`2026-04-26`
## 1. 本次目标
编辑场景角色与编辑场景图片时,玩家可以复用历史生成素材:
1. 场景角色形象区删除 `基于预设素材修改` 入口,以及它打开的预设拼装编辑下游。
2. 场景角色形象区新增 `使用历史素材`,打开独立素材面板。
3. 场景图片的幕背景配置新增 `使用历史素材`,打开独立素材面板。
4. 历史素材面板需要展示所有账号过去生成过的对应类型素材,并明确标注素材归属账号。
5. 选择素材后直接应用到当前角色或当前幕背景,不触发新一轮生成。
## 2. 数据真相
历史素材不从前端草稿 profile 扫描,也不从预设素材目录扫描。
历史素材统一来自 SpacetimeDB 的 `asset_object` 表:
| 使用位置 | `asset_kind` | 应用字段 |
| --- | --- | --- |
| 场景角色形象 | `character_visual` | `imageSrc``generatedVisualAssetId` |
| 场景幕背景 | `scene_image` | `backgroundImageSrc``backgroundAssetId` |
`asset_object.owner_user_id` 是归属账号标注主源。为空时 UI 标注为 `未记录账号`,不能隐藏归属栏。
## 3. 后端接口
新增只读接口:
```text
GET /api/assets/history?kind=character_visual
GET /api/assets/history?kind=scene_image
```
返回字段只包含前端选择所需脱敏信息:
1. `assetObjectId`
2. `assetKind`
3. `imageSrc`
4. `ownerUserId`
5. `ownerLabel`
6. `profileId`
7. `entityId`
8. `createdAt`
9. `updatedAt`
`asset_object` 仍保持 private table。读取历史列表必须通过 SpacetimeDB procedure + Axum facade不能让前端直接订阅整表。
## 4. SpacetimeDB 设计
`module-assets` 新增:
1. `AssetHistoryListInput`
2. `AssetHistoryEntrySnapshot`
3. `AssetHistoryListResult`
`spacetime-module` 新增 procedure
```rust
list_asset_history_and_return(input: AssetHistoryListInput) -> AssetHistoryListResult
```
实现规则:
1. 只允许 `character_visual``scene_image`
2.`asset_kind` 过滤。
3.`created_at` 倒序返回。
4. 默认最多返回 120 条,允许调用方传更小 limit。
5. `image_src``object_key` 转为兼容代理路径:`/{object_key}`
## 5. 前端交互
历史素材面板为独立弹层,不在当前面板下方展开。
角色历史素材卡片:
1. 展示素材图。
2. 展示归属账号。
3. 展示创建时间。
4. 点击 `使用` 后写入当前草稿角色:
- `imageSrc = imageSrc`
- `generatedVisualAssetId = assetObjectId`
- `generatedAnimationSetId = undefined`
- `animationMap = undefined`
场景历史素材卡片:
1. 展示 16:9 预览。
2. 展示归属账号。
3. 展示创建时间。
4. 点击 `使用` 后写入当前幕:
- `backgroundImageSrc = imageSrc`
- `backgroundAssetId = assetObjectId`
## 6. 非目标
1. 不新增图片生成模型能力。
2. 不把历史素材复制成新 OSS 对象。
3. 不修改旧预设素材目录。
4. 不恢复 server-node 或 PostgreSQL 链路。

View File

@@ -0,0 +1,24 @@
# RPG 创作场景幕资产一致性修复 2026-04-26
## 背景
当前世界草稿和场景编辑器存在三类一致性问题:
1. 世界草稿生成后,开局场景的三幕可能没有默认主角色。
2. 开局场景列表层、幕卡片层、幕背景配置弹层可能显示不同图片。
3. 幕背景智能生成弹层的默认提示词可能退回规则拼接文本,且预览图和外层当前幕不一致。
## 落地约束
1. 后端草稿生成必须为 `sceneChapterBlueprints[*].acts[*]` 写入稳定的幕级字段:`encounterNpcIds``primaryNpcId``oppositeNpcId``eventDescription``backgroundPromptText`
2. 开局场景 `camp` 在生成角色名单之前建立,但最终编译草稿时必须基于已生成的场景角色,为三幕自动补默认主角色,不允许把“开局关键角色”这类占位词留到可编辑草稿里。
3. `backgroundPromptText` 必须优先来自模型生成的自然画面描述;缺失时才使用规则兜底,兜底也要基于真实主角色名。
4. 前端场景编辑器展示某一幕时,列表卡、幕卡、配置弹层、智能生成弹层都应读取同一个幕级 `backgroundImageSrc`;只有旧草稿缺幕图时才展示场景主图作为视觉兜底,保存时不得把兜底图反写到所有幕。
5. 智能生成幕背景时,默认提示词必须使用当前幕 `backgroundPromptText`,不再用标题、摘要、目标拼接替代。
## 验收点
1. 新生成的开局三幕每幕都有非空 `primaryNpcId`,并且第一位 `encounterNpcIds[0]``primaryNpcId` 一致。
2. 普通场景与开局场景都能在幕背景生图 prompt 中写入真实主角色名。
3. 开局场景第 2 幕在列表层、编辑卡片层、配置弹层、智能生成弹层中的预览图保持一致。
4. 点击“跟随场景主图”只影响当前幕,不会把同一张图同步覆盖到三幕。

View File

@@ -0,0 +1,58 @@
# RPG 创作世界角色维度信息编辑落地说明
更新时间:`2026-04-26`
## 1. 背景
统一角色属性系统把一个世界中“角色能力如何被理解”收口到 `CustomWorldProfile.attributeSchema.slots`。这六个 slot 是世界级设定,不是单个角色自己的六个字段。
当前结果页世界页可以展示角色维度,但编辑世界信息时只能修改世界名称、概述、基调、目标等文本,尚不能手动修订六个维度本身的信息。
## 2. 本次目标
在“编辑世界信息”独立面板中允许用户编辑六个角色维度的信息:
1. 修改 `attributeSchema.slots` 中每个维度的 `name``definition``positiveSignals``negativeSignals``combatUseText``socialUseText``explorationUseText`
2. 不在可扮演角色或场景角色编辑器中新增单角色六维数值编辑。
3. 保存时同步更新 `profile.attributeSchema`
4.`profile.ownedSettingLayers.ruleProfile.attributeSchema` 存在,同步写入同一份 schema避免世界档案和设定层出现双源漂移。
5. 前端只负责编辑结构化文本,不新增属性结算逻辑。
## 3. 交互设计
入口位置:
- 世界页点击“世界概述”里的编辑按钮
- 打开现有“编辑世界信息”面板
- 在基础世界文本字段下方增加“角色维度”区块
每个维度展示并允许编辑:
- 维度名称
- 定义
- 正向信号
- 负向信号
- 战斗体现
- 社交体现
- 探索体现
正向信号与负向信号使用逗号、中文逗号或换行拆分成数组。
## 4. 数据落点
保存路径:
- `profile.attributeSchema.slots[n]`
- `profile.ownedSettingLayers.ruleProfile.attributeSchema.slots[n]`,仅当 `ownedSettingLayers` 已存在时同步
不修改:
- `profile.playableNpcs[n].attributeProfile`
- `profile.storyNpcs[n].attributeProfile`
## 5. 验收
1. 世界信息面板能看到六个角色维度。
2. 修改任一维度名称、定义、信号或三类用途说明后,保存到 `profile.attributeSchema.slots`
3. 编辑角色自身时不出现单角色六维数值输入区。
4. UI 仍读取当前世界 schema不回退写死旧四维文案。

View File

@@ -0,0 +1,19 @@
# 世界档案 PC 布局扩展2026-04-26
## 背景
世界档案结果页和各实体编辑子面板在 PC 端仍沿用偏窄的弹窗与两列列表布局,宽屏下画布利用率不足。移动端现有单列滚动、底部弹层和触控布局已经稳定,本次不改变移动端结构。
## 落地规则
1. 世界档案主页面只在 `xl` 及以上断点扩展画布宽度与内部间距,移动端和 `sm/md/lg` 断点保持原有布局。
2. 场景、可扮演角色、场景角色列表在 PC 端提高列数上限,让宽屏能显示更多卡片。
3. 世界 Tab 在 PC 端由窄两列调整为更充分的三列信息布局,减少首屏纵向堆叠。
4. 世界信息、基本设定、角色与场景编辑子面板在 PC 端使用更宽的 `max-width` 与更高可用高度;移动端仍保持 `h-[92vh]` 的底部弹层。
5. 不新增说明类 UI 文案,不改写现有中文内容,不改变保存、删除、生成、发布等行为。
## 验收点
1. PC 端世界档案结果页内容区域能接近占满工作区宽度,列表在 2K 宽屏下可呈现更多列。
2. PC 端打开世界、基本设定、角色、场景子面板时,弹窗宽度明显大于旧版 `sm:max-w-2xl` 默认宽度。
3. 移动端仍为单列滚动列表,编辑子面板仍从底部弹出且宽度为全屏。

View File

@@ -29,6 +29,7 @@
- 单轮变化限制在 `[-3, 3]`
5. `chatDirective.forceExitAfterTurn / closingMode=foreshadow_close` 时不生成建议,返回空数组,并在 `complete.chatDirective.forceExit` 中显式告知前端退出。
6. LLM 未配置或失败时继续返回后端兜底 SSE保证相遇和点击聊天链路不断。
7. 在 Node 已完成平台账号鉴权并通过内部密钥转发到 Rust API 的链路中,`/api/runtime/chat/` 必须纳入 Rust 内部转发鉴权白名单;否则 NPC 主动开场会在进入 `runtime_chat` handler 前被 401 拒绝前端只能收到“NPC 聊天续写失败”。
## 暂不落地
@@ -46,11 +47,14 @@
- 优先 `LlmClient.stream_text(...)` 生成 `reply_delta`
- 再调用 `request_text(...)` 生成建议。
- 计算 `affinityDelta / affinityText / chatDirective` 后输出 `complete`
3. 修改 `server-rs/crates/api-server/src/main.rs`
3. 修改 `server-rs/crates/api-server/src/auth.rs`
- `allows_internal_forwarded_auth(...)` 允许 `/api/runtime/chat/`,与 big-fish、puzzle 的内部转发鉴权策略保持一致。
- 单测覆盖 `/api/runtime/chat/npc/turn/stream`,防止后续新增 runtime 路由时再次遗漏内部转发白名单。
4. 修改 `server-rs/crates/api-server/src/main.rs`
- 注册 `runtime_chat_prompt` 模块。
## 验收
1. `cargo fmt -p api-server`
2. `cargo check -p api-server`
3. `node scripts/check-encoding.mjs docs/technical/RUNTIME_NPC_CHAT_LLM_MIGRATION_2026-04-25.md server-rs/crates/api-server/src/runtime_chat.rs server-rs/crates/api-server/src/runtime_chat_prompt.rs server-rs/crates/api-server/src/main.rs`
3. `node scripts/check-encoding.mjs docs/technical/RUNTIME_NPC_CHAT_LLM_MIGRATION_2026-04-25.md server-rs/crates/api-server/src/auth.rs server-rs/crates/api-server/src/runtime_chat.rs server-rs/crates/api-server/src/runtime_chat_prompt.rs server-rs/crates/api-server/src/main.rs`

View File

@@ -8,19 +8,19 @@
Maincloud 发布不复用本地 `spacetime.local.json`,避免误把本地开发库名发布到云端。需要显式提供:
| 变量 | 用途 |
| --- | --- |
| `GENARRATIVE_SPACETIME_MAINCLOUD_DATABASE` | Maincloud 数据库名,发布脚本优先读取 |
| 变量 | 用途 |
| -------------------------------------------- | ------------------------------------------------------------ |
| `GENARRATIVE_SPACETIME_MAINCLOUD_DATABASE` | Maincloud 数据库名,发布脚本优先读取 |
| `GENARRATIVE_SPACETIME_MAINCLOUD_SERVER_URL` | Maincloud 服务地址,默认 `https://maincloud.spacetimedb.com` |
| `GENARRATIVE_SPACETIME_MAINCLOUD_TOKEN` | `api-server` 连接 Maincloud 时使用的 token |
| `GENARRATIVE_SPACETIME_MAINCLOUD_TOKEN` | `api-server` 连接 Maincloud 时使用的 token |
兼容 `api-server` 现有变量:
| 变量 | 用途 |
| --- | --- |
| `GENARRATIVE_SPACETIME_SERVER_URL` | `api-server` 实际连接地址 |
| `GENARRATIVE_SPACETIME_DATABASE` | `api-server` 实际连接数据库 |
| `GENARRATIVE_SPACETIME_TOKEN` | `api-server` 实际连接 token |
| 变量 | 用途 |
| ---------------------------------- | --------------------------- |
| `GENARRATIVE_SPACETIME_SERVER_URL` | `api-server` 实际连接地址 |
| `GENARRATIVE_SPACETIME_DATABASE` | `api-server` 实际连接数据库 |
| `GENARRATIVE_SPACETIME_TOKEN` | `api-server` 实际连接 token |
## npm 命令
@@ -50,10 +50,12 @@ npm run api-server:maincloud
1.`.env``.env.local` 读取默认环境。
2.`GENARRATIVE_SPACETIME_MAINCLOUD_*` 映射为 `api-server` 已支持的 `GENARRATIVE_SPACETIME_*`
3. 启动 `cargo run -p api-server --manifest-path server-rs/Cargo.toml`
3. 在 Windows 启动前检查 `server-rs/target/debug/api-server.exe` 对应的旧进程;如果旧进程仍在运行,先停止它,避免 Rust 编译阶段覆盖 exe 时出现 `failed to remove file ... 拒绝访问。 (os error 5)`
4. 启动 `cargo run -p api-server --manifest-path server-rs/Cargo.toml`
## 设计约束
- Maincloud 数据库名必须显式配置,不能默认读取本地 `spacetime.local.json`
- 发布脚本只处理 SpacetimeDB 模块发布,不启动本地 SpacetimeDB。
- `api-server` 继续通过 `SpacetimeClientConfig``server_url / database / token` 连接数据库,不在前端增加逻辑。
- Windows 进程清理只能匹配本仓库 `server-rs/target/debug/api-server.exe` 的完整路径,不能按进程名泛化清理,避免影响其他 Rust 服务。