1
This commit is contained in:
@@ -214,3 +214,4 @@
|
||||
- 对战预览里主角和对手要沿画面中线成对出现,但纵向不能只共用一个 `bottom` 常量。
|
||||
- 怪物精灵帧的空白、体型和脚底位置差异很大,运行画面应按帧高分档下沉,让怪物视觉底边落在主角同一条地面线上。
|
||||
- 后续新增怪物资源时,先检查红圈标注的实际落点,再调整锚点分档或单怪物偏移,避免出现“悬在地面上方”的状态。
|
||||
- 自定义世界里敌对角色已经先作为场景 NPC 存在,即使它同时携带 `characterId` 和 `monsterPresetId`,画布也不能直接沿用模板角色的 `groundOffsetY`;只要 encounter 自身有 `imageSrc` 或 `visual`,就按场景 NPC 自定义形象锚点处理。
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# 冒险实体详情 NPC 预览修复记录(2026-04-26)
|
||||
|
||||
## 背景
|
||||
|
||||
RPG 运行态点击画面中的对面 NPC 角色形象时,详情弹窗的立绘与画布上实际显示的 NPC 不一致,并伴随 React 报错:
|
||||
|
||||
`Encountered two children with the same key, ``.`
|
||||
|
||||
## 问题定位
|
||||
|
||||
1. 画布层 `GameCanvasEntityLayer` 渲染 NPC 时,会优先使用当前 `Encounter` 实例上的 `visual`、`imageSrc`、`monsterPresetId`,再回退到 `characterId` 对应的预设角色。
|
||||
2. 详情弹窗 `AdventureEntityModal` 原本优先按 `characterId` 渲染预设角色,导致运行时遭遇已经携带独立形象时,点击后弹窗显示成另一个角色内容。
|
||||
3. `AdventureEntityModal` 内部存在多个浮层共用同一个 `AnimatePresence`,直系子节点没有显式稳定 key;同时 NPC 运行时背包物品如果传入空 `id`,会把空字符串直接交给物品格列表作为 React key。
|
||||
|
||||
## 落地约束
|
||||
|
||||
1. NPC 详情立绘必须与画布点击对象一致:
|
||||
- `encounter.visual`
|
||||
- `encounter.imageSrc`
|
||||
- `encounter.monsterPresetId`
|
||||
- `encounter.characterId`
|
||||
- 通用 NPC 生成形象
|
||||
2. 前端只做展示优先级和 key 稳定性处理,不新增剧情规则、不改写运行时 NPC 数据来源。
|
||||
3. 所有列表和并列浮层都必须具备稳定、非空、可区分的渲染 key。
|
||||
|
||||
## 本次修改
|
||||
|
||||
1. `src/components/AdventureEntityModal.tsx`
|
||||
- 新增 `NpcEncounterPortrait`,让弹窗立绘优先使用遭遇实例形象,与画布渲染策略对齐。
|
||||
- 新增 `selectionRenderKey`,给实体详情、标签详情、技能详情浮层提供稳定 key。
|
||||
- 新增 NPC 背包物品渲染 id 规范化,避免空 id 或重复 id 触发 React key 冲突,并避免点击物品时选中错误项。
|
||||
- 技能附带状态标签 key 增加兜底字段,避免空 buff id 冲突。
|
||||
2. `src/components/AdventureEntityModal.test.tsx`
|
||||
- 覆盖“有 `characterId` 但遭遇实例提供 `imageSrc` 时,详情立绘必须显示遭遇图像”。
|
||||
- 覆盖“NPC 背包物品空 id 不再触发重复 key 警告”。
|
||||
|
||||
## 验证
|
||||
|
||||
已执行:
|
||||
|
||||
```bash
|
||||
npm run test -- AdventureEntityModal.test.tsx CharacterInfoShared.test.tsx
|
||||
```
|
||||
|
||||
结果:5 个测试全部通过。
|
||||
@@ -10,8 +10,10 @@
|
||||
|
||||
- 角色:`visualDescription`,用于打开角色形象图像生成面板时默认填入角色形象描述框。
|
||||
- 角色:`actionDescription`,用于打开角色动作视频生成面板时默认填入各动作描述框;当前每个动作会从同一角色默认动作描述起步,用户切换动作后可分别编辑并缓存。
|
||||
- 角色:`sceneVisualDescription`,用于描述角色常出现或关联的场景画面。三个角色默认描述字段必须在角色 outline 阶段同一次模型调用中产出;若模型遗漏,只允许后端本地兜底补字段,不再额外发起独立修复模型调用。
|
||||
- 每一幕:`sceneChapterBlueprints[*].acts[*].backgroundPromptText`,用于打开该幕背景图像生成面板时默认填入场景描述框。
|
||||
- 场景:`visualDescription` 只作为旧场景图或没有幕级描述时的兜底,不再从角色 AI 形象生成面板维护场景背景描述。
|
||||
- 场景:`actNPCNames`、`connectedLandmarkNames`、`entryHook` 必须在关键场景生成阶段同一次模型调用中产出,并由原场景解析链路写入 `landmarks` 与幕级 `primaryNpcId / oppositeNpcId / encounterNpcIds`;不再使用独立的场景网络补全提示词。旧草稿中的 `sceneNpcNames` 仅作为兼容读取兜底,不作为新生成字段。
|
||||
|
||||
草稿生成契约位置:
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ buildFoundationGenerationSeedText
|
||||
-> generateFoundationRoleOutlineEntries(playable)
|
||||
-> generateFoundationRoleOutlineEntries(story)
|
||||
-> generateFoundationLandmarkSeedEntries
|
||||
-> expandFoundationLandmarkNetworkEntries
|
||||
-> expandFoundationRoleEntries(playable, narrative)
|
||||
-> expandFoundationRoleEntries(playable, dossier)
|
||||
-> expandFoundationRoleEntries(story, narrative)
|
||||
@@ -30,15 +29,14 @@ buildFoundationGenerationSeedText
|
||||
2. 使用旧 Node 的 framework prompt 生成世界核心骨架。
|
||||
3. 分批生成可扮演角色 outline。
|
||||
4. 分批生成场景角色 outline。
|
||||
5. 分批生成关键场景 seed。
|
||||
6. 补全关键场景探索网络。
|
||||
7. 先补可扮演角色叙事档案,再补养成档案。
|
||||
8. 先补场景角色叙事档案,再补养成档案。
|
||||
9. 将分阶段结果编译回 `draftProfile`,再交给 SpacetimeDB action 落库。
|
||||
5. 分批生成关键场景;同一次模型调用必须同时产出 `actNPCNames`、`connectedLandmarkNames`、`entryHook`、`actBackgroundPromptTexts` 与 `actEventDescriptions`,其中 `actNPCNames` 表示三幕各自默认主场景角色。旧草稿的 `sceneNpcNames` 只允许作为读取兜底。
|
||||
6. 先补可扮演角色叙事档案,再补养成档案。
|
||||
7. 先补场景角色叙事档案,再补养成档案。
|
||||
8. 将分阶段结果编译回 `draftProfile`,再交给 SpacetimeDB action 落库。
|
||||
|
||||
## 约束
|
||||
|
||||
1. 未修改旧 Node 提示词原文的语义与阶段顺序。
|
||||
1. Rust 主链不再兼容旧 Node 的独立场景网络补全阶段;场景生成只允许通过 `build_custom_world_landmark_seed_batch_prompt` 一次完成。
|
||||
2. Rust 侧新增 prompt 构造只服务 `api-server` 外部 LLM 调用;SpacetimeDB reducer 仍只负责校验与落库,不承担联网生成。
|
||||
3. 当前仍保留 Rust 侧最小归一化,目的仅是保证 `publish gate / result preview` 需要的字段存在,不替代 Node 的 AI 工作流。
|
||||
4. 后续如继续迁移,需要优先把 Node `buildFoundationDraftProfileFromFramework` 的结构编译细节进一步完整 Rust 化,而不是回退到单 prompt 直出。
|
||||
@@ -63,8 +61,7 @@ cargo test -p api-server custom_world_foundation_draft --no-default-features
|
||||
- `12`:整理世界骨架。
|
||||
- `16-30`:生成可扮演角色。
|
||||
- `30-44`:生成场景角色。
|
||||
- `44-56`:生成关键场景。
|
||||
- `56-66`:建立场景连接。
|
||||
- `44-66`:生成关键场景,并同步产出幕 NPC 与场景连接。
|
||||
- `66-76`:补全可扮演角色叙事基础。
|
||||
- `76-84`:补全可扮演角色档案细节。
|
||||
- `84-92`:补全场景角色叙事基础。
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
新增字段:
|
||||
|
||||
- `oppositeNpcId: string`
|
||||
- 当前幕“对面的角色”,优先使用该场景 `sceneNpcNames` / `encounterNpcIds` 的第一个角色。
|
||||
- 当前幕“对面的角色”,优先使用该场景 `actNPCNames[actIndex]` 对应的角色。
|
||||
- 若 `actNPCNames` 缺少当前幕条目,使用 `actNPCNames[0]`;旧草稿只存在 `sceneNpcNames` 时,仅作为兼容兜底读取,不再作为新生成字段。
|
||||
- 若当前场景暂未绑定角色,使用空字符串,不在草稿合成阶段伪造角色 ID。
|
||||
- `eventDescription: string`
|
||||
- 描述当前幕正在发生的事件。
|
||||
@@ -36,6 +37,15 @@
|
||||
- 当前场景的核心任务描述。
|
||||
- 文本会作为游戏中首次进入某个场景生成章节任务的关键上下文。
|
||||
- 必须结合场景描述、场景入口钩子、出场角色与 3 幕事件,说明玩家首次进入该场景时要完成什么。
|
||||
- 世界档案的场景详情页必须直接展示该字段,便于创作者确认每个场景的默认章节任务。
|
||||
|
||||
### Landmark 生成源字段
|
||||
|
||||
- `actNPCNames: string[]`
|
||||
- 关键场景生成阶段一次模型调用内产出,表示第 1/2/3 幕各自的主场景角色。
|
||||
- 只能引用同一批角色生成链路中已有的场景角色名。
|
||||
- 解析到幕蓝图时,每一幕默认写入 `primaryNpcId`、`oppositeNpcId`,并作为 `encounterNpcIds` 的首位。
|
||||
- 新生成不再使用 `sceneNpcNames`;前端和后端可保留旧字段读取兜底,用于历史草稿不丢角色。
|
||||
|
||||
## 生成链路
|
||||
|
||||
@@ -43,6 +53,7 @@
|
||||
2. LLM 提示词需要要求:
|
||||
- `camp.sceneTaskDescription` 默认生成开局场景核心任务。
|
||||
- `landmarks[*].sceneTaskDescription` 默认生成关键场景核心任务。
|
||||
- `actNPCNames` 恰好 3 条,对应每一幕默认主场景角色;如果可用场景角色名单为空,输出空数组。
|
||||
- `actEventDescriptions` 恰好 3 条,对应每一幕事件。
|
||||
- `actEventDescriptions[0] / [1] / [2]` 必须分别承担铺垫、冲突、高潮,不允许三条只是同一事件的近义复述。
|
||||
- `actBackgroundPromptTexts[n]` 必须基于同序号幕事件和相关角色写出画面主体、站位空间、冲突痕迹与氛围,不能只用场景名或幕标题拼接。
|
||||
@@ -50,9 +61,12 @@
|
||||
- `sceneChapterBlueprints[*].sceneTaskDescription`
|
||||
- `sceneChapterBlueprints[*].acts[*].oppositeNpcId`
|
||||
- `sceneChapterBlueprints[*].acts[*].eventDescription`
|
||||
- `sceneChapterBlueprints[*].acts[*].primaryNpcId`
|
||||
- `sceneChapterBlueprints[*].acts[*].encounterNpcIds[0]`
|
||||
4. 若 LLM 遗漏字段,归一化阶段用场景描述、入口钩子、角色名单生成中文默认值,保证草稿阶段字段非空。
|
||||
5. 前端类型与归一化逻辑必须允许读取这些字段,旧草稿缺字段时仍自动补默认值。
|
||||
6. 幕信息编辑界面必须直接展示 `eventDescription`,并在保存时保留 `sceneTaskDescription / oppositeNpcId / eventDescription / backgroundPromptText`,避免旧草稿经前端编辑后丢失后端生成字段。
|
||||
7. 首次进入某场景时,现有章节任务生成流程必须优先读取对应 `sceneChapterBlueprints[*].sceneTaskDescription`,并把它作为 `buildChapterQuestForScene` 的章节任务覆盖上下文;同一场景只生成一次章节任务。
|
||||
|
||||
## 幕配置预览标识
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# 世界档案场景 Tab 与幕主角色隔离修复(2026-04-26)
|
||||
|
||||
## 背景
|
||||
|
||||
编辑世界档案时,开局场景已经复用普通场景的 `LandmarkEditor` 多幕配置面板,但幕角色归一化逻辑会把章节中任意一幕已选择的角色汇总成场景候选池。当第一幕刚配置主角色时,其他尚未配置角色的幕会被兜底补成同一个主角色,表现为“第一幕联动修改其他幕”。
|
||||
|
||||
## 落地规则
|
||||
|
||||
1. 世界档案实体 Tab 顺序调整为:世界、场景、可扮演角色、场景角色。
|
||||
2. 开局场景与普通场景的多幕角色保存规则保持一致:
|
||||
- 当前幕的主角色只由当前幕 `encounterNpcIds[0]` 决定。
|
||||
- 已存在幕蓝图但当前幕未选角色时,保持空槽位,不从其他幕角色兜底。
|
||||
- 只有旧草稿完全缺少幕蓝图时,才允许从场景已有角色列表生成默认幕槽位。
|
||||
3. 保存场景时,`sceneNpcIds` 继续作为所有幕已选角色的汇总字段,用于列表检索、旧字段兼容和运行时场景候选,不反向覆盖未配置幕。
|
||||
|
||||
## 验收点
|
||||
|
||||
1. 在开局场景编辑器中只配置第一幕主角色,第二幕、第三幕仍保持未配置状态。
|
||||
2. 保存后 `camp.sceneNpcIds` 包含第一幕角色,`sceneChapterBlueprints` 中只有第一幕写入该角色。
|
||||
3. 普通场景的幕角色槽位、幕预览、场景任务和连接关系不发生行为漂移。
|
||||
@@ -0,0 +1,37 @@
|
||||
# RPG 世界草稿属性六维生成 2026-04-26
|
||||
|
||||
## 背景
|
||||
|
||||
RPG Agent 生成世界草稿时,前端会把 `draftProfile` 归一化成 `CustomWorldProfile`。运行时已经支持 `attributeSchema`,但 foundation draft 当前没有稳定产出该字段,前端只能根据主题模式回退出固定模板,导致世界页面看到的六个维度更像预设,而不是本次世界草稿的一部分。
|
||||
|
||||
## 落地约束
|
||||
|
||||
- `draftProfile.attributeSchema` 是世界草稿真相源的一部分,必须随 foundation draft 一起生成并保存。
|
||||
- 六维固定使用 `axis_a` 到 `axis_f` 六个槽位,但 `schemaName`、每个槽位 `name` 和说明必须贴合本次世界设定。
|
||||
- 维度名不得沿用通用旧词:生命、法力、护甲、攻击、防御、力量、敏捷、智力、精神。
|
||||
- 若模型遗漏或结构不合规,后端必须生成中文兜底属性体系,不能让前端只靠固定模板补齐。
|
||||
- 世界页面的“世界”页签必须展示当前 `attributeSchema.slots` 的六个名称,作为玩家进入世界前可见的规则信号。
|
||||
|
||||
## 编码方案
|
||||
|
||||
1. `packages/shared/src/contracts/rpgAgentDraft.ts`
|
||||
- 增加 `RpgAgentWorldAttributeSchema` 与 `RpgAgentWorldAttributeSlot` 合同。
|
||||
- `RpgAgentFoundationDraftProfile` 增加 `attributeSchema` 字段。
|
||||
|
||||
2. `server-rs/crates/api-server/src/prompt/foundation_draft.rs`
|
||||
- framework 阶段要求模型输出 `attributeSchema`。
|
||||
- 修复提示也必须保留 `attributeSchema`,避免 JSON repair 丢字段。
|
||||
|
||||
3. `server-rs/crates/api-server/src/custom_world_foundation_draft.rs`
|
||||
- `normalize_framework_shape()` 归一化 `attributeSchema`。
|
||||
- `build_foundation_draft_profile_from_framework()` 将归一化后的 `attributeSchema` 写入 `draftProfile`。
|
||||
- 新增兜底生成器,基于世界名、基调、目标、冲突和种子文本生成六个中文维度。
|
||||
|
||||
4. `src/components/CustomWorldEntityCatalog.tsx`
|
||||
- 在世界页签增加“角色维度”区域,直接渲染 `profile.attributeSchema.slots` 的六个名称。
|
||||
|
||||
## 验收
|
||||
|
||||
- 新生成的 RPG 世界草稿 JSON 顶层包含 `attributeSchema.slots.length === 6`。
|
||||
- 结果页/世界页展示六个自定义维度名,而非固定的力量、敏捷、智力、精神。
|
||||
- 缺失或非法模型输出会被后端兜底为合法中文六维。
|
||||
Reference in New Issue
Block a user