# UE 代码架构地图（当前实现）

本文件记录 `Source/CoopSurvival` **此刻的真实代码结构**：模块/目录、核心类职责、数据所有权、复制与事件模式，以及正在进行的重构路线。

定位（与其他文档的关系）：
- `AGENTS.md`：规则与红线（**该怎么做**）。本文件不重复规则，只描述现状。
- `Docs/DevelopmentUnits.md`：按时间的单元日志（**改了什么、怎么验证**）。
- 本文件 `UE_CodeArchitecture.md`：当前代码架构快照（**现在长什么样**）。
- `Docs/Development/*`：单系统开发拆分与功能逻辑说明（**单系统深入**）。

维护规则：当一次单元**改变了类职责、数据所有权、复制方式或模块边界**时，在同一单元内更新本文件；纯数值/内容/美术调整不必更新。若本文件与 `AGENTS.md` 冲突，以 `AGENTS.md` 为准。

最后更新：2026-06-11（PlayerFeel-B 镜头手感 Unit 86：相机跟随延迟/下蹲反补偿/落地下沉/瞄准 FOV 节奏 + 室内软碰撞相机臂 `UCSCameraBoom`；新增 `Camera/CSLandCameraShake`、`Camera/CSCameraBoom`）。上一次 2026-06-09（代码审查驱动的性能/正确性收口：动画线程射线、HUD 快照缓存、相机/电梯 Tick 守卫、背包 owner-only 复制、静态世界 Actor 休眠）。

---

## 1. 模块与目录

单模块 Runtime：`CoopSurvival`（`Source/CoopSurvival`）。依赖见 `CoopSurvival.Build.cs`：`Core/CoreUObject/Engine/EnhancedInput/InputCore/NetCore/OnlineSubsystem(+Utils)/PhysicsCore/Slate/SlateCore`。

| 目录 | 职责 |
| --- | --- |
| `Core/` | GameMode、GameState、PlayerController、PlayerState、GameInstance |
| `Character/` | 第三人称角色、AnimInstance |
| `Components/` | 可复用 gameplay 组件（背包、战斗、交互、生存、脚步声、状态效果、换装外观） |
| `Items/` | 物品数据类型、物品数据子系统、世界物品 Actor、装备动画 Profile |
| `Weapons/` | 武器基类、近战/枪械、投掷物（`ACSThrowableProjectile`）、预览 Actor |
| `World/` | 场景作者摆放的站点、资源点、容器、电梯、战斗假人、世界存档 |
| `Animation/` | AnimNotify / AnimNotifyState（近战窗口、脚步） |
| `Audio/` | 脚步声子系统与类型 |
| `Voice/` | 角色声音参数契约（`FCSVoiceParams`/`FCSCharacterVoiceProfile`）+ `UCSVoiceProfileSubsystem` 查询（VG-A 地基；播放层尚未接） |
| `Online/` | 平台服务抽象子系统 |
| `Narrative/` | AIC 叙事导演世界子系统、叙事节拍/会话数据类型、可对话 NPC（事件驱动的本机表现层，建在 WorldProgress 之上） |
| `UI/` | UMG 数据基类（HUD/对话框/主菜单/设置/弹窗/Toast/加载屏 Loading-A/B）+ 仍手写的 Slate Widget（背包网格、合成、GM 面板、联机面板、AIC 叙事字幕覆盖）+ HUD 快照类型 + UI 层管理器 `UCSUILayerSubsystem`（输入模式/弹窗宿主）；编辑器专用生成器在 `UI/Editor/` |

---

## 2. 核心类职责与所有权

### Core
- `ACSGameMode`：服务端登录/出生流程。（游戏图的 GameMode；全局默认 `GlobalDefaultGameMode`。）
- `ACSMenuGameMode` + `ACSMenuPlayerController`（UI-Menu-A）：**主菜单关卡专用**，仅由 `Lvl_MainMenu` 的 World Settings「GameMode Override」绑定，不改全局默认。GameMode 无 Pawn（`DefaultPawnClass=nullptr`）；PlayerController `BeginPlay` 在本地控制器上加载并显示 `WBP_MainMenu`、切 `FInputModeUIOnly` + 显鼠标。与游戏内的 `ACSPlayerController` 完全分离，菜单不涉及 Pawn / 服务器权威。`GameDefaultMap` 指向 `Lvl_MainMenu`（启动进菜单）。
- `UCSGameInstance`：覆盖 `OverrideGameModeClass`（缺省回退 `ACSGameMode`）。加 `bPendingLoadOnStart` 标志 + `ConsumePendingLoadOnStart()`：主菜单「继续下潜」置位，进图后**服务端** `ACSPlayerController::BeginPlay` 消费一次 → 延迟 0.5s `LoadWorldOnServer`（「新建档案」不置位=全新世界）。
- `UCSGameUserSettings`（`: UGameUserSettings`，UI-Menu-A）：项目用户设置，经 `DefaultEngine.ini` `GameUserSettingsClassName` 注册。继承引擎画面设置全套（分辨率/窗口模式/质量/垂直同步），补 config `MasterVolume`（`ApplyCSSettings` 用 `FAudioDevice::SetTransientPrimaryVolume` 套总线，项目暂无 SoundClass）+ `bReduceMotion`（主菜单读它跳过扫描线/glitch）。
- `ACSGameState`：**世界级共享状态权威**——世界时钟（`ServerWorldTime`/`SurvivalDay`，服务端 Tick 推进）、`WorldProgress`（目标/世界标签/路线/样本/AIM/配方解锁）。对外提供 `ApplyObjectiveEvent` / `CompleteObjective` / `UnlockRoute` / `AddWorldStateTag` / `RemoveWorldStateTag`（断电/来回扳总闸用）等服务端写入入口，`OnWorldProgressChanged` 委托驱动 UI 与**叙事导演**（`UCSNarrativeDirectorSubsystem`，见 Narrative 小节）。任务视图 join（Quest-A，待验证）：`BuildResolvedQuestView()` 把 `UCSItemDataSubsystem` 的静态任务/目标定义与本机复制的 `WorldProgress`（可见性按 `WorldStateTags` 算、步骤 State 取 `ObjectiveStates` 缺省 Locked）现算成只读 `FCSResolvedQuestView`，供后续任务 UI 消费；`GetQuestViewDebugString()` + 控制器 exec `PrintQuests` 验证。
- `ACSPlayerController`：**面向玩家的本地 UI + 服务端请求协调器**。拥有 HUD/背包/合成/GM/联机面板的创建与输入模式；持有 `UCSCraftingComponent`；持久 gameplay 改动一律走拥有者 Server RPC + 服务端辅助方法。**注意：仍承载世界存档读写（`SaveWorldOnServer`/`LoadWorldOnServer`），是「Controller 瘦身」剩余重构对象，见第 6 节。** **UI-Modal-B 起接 UI 层管理器**：`BeginPlay` 本地控制器分支 `GetUILayer()->RegisterOwner(this, [恢复输入模式=ApplyMenuInputMode])`（弹窗叠在 Slate 面板之上、关闭后回面板/游戏态；也让管理器在游戏关有有效 `OwningPC`）；新增 `ClientShowToast(FText, ECSToastLevel)` 服务器→属主客户端 Client RPC（经 `GetUILayer()->ShowToast`，缺失降级 `ShowDebugLog`），已接 3 个反馈点（丢关键物被拒 / 存档完成 / 读档完成）。**UI-Modal-C 起 ESC（`CloseOpenPanels`）接管理器**：先 `UI->HandleEscape()` 兜底消层 → 按 flag 关 GM>Coop>Inv 面板（背包分支含 `InventoryWidget.IsValid()` 保留原清残留语义）→ **都没有时 `OpenPauseMenu()`**（经管理器 `PushScreen(WBP_PauseMenu)`，`HasAnyLayerOpen` 防叠开，`PauseMenuClass` 懒加载缓存）。Slate 背包/GM/联机面板的开关逻辑本身未动。把这些 Slate 面板也收口进 `ScreenStack` 留后续单元（需把 Slate 面板包装成层）。**UI-Modal-D 加** `RequestDropItemInteractive(ItemId, MaxQty)`：背包丢弃区调它，关键物客户端先拦（Danger Toast）、摞内多个弹 QtyInput 模态选数量再走服务端权威丢弃（首个"模态叠 Slate 背包面板"消费）；`UCSCraftingComponent` 经 `ClientShowToast` 加合成完成/材料不足/产物放不下的玩家可见 Toast。
- `ACSPlayerState`：**个人状态宿主**。跨 Pawn 占有保持存在；持有 `UCSInventoryComponent` 并通过 `GetInventory()` 暴露。`FCSInventoryStack` 结构体与 `FCSOnEquippedItemChanged` 委托类型定义在 `CSPlayerState.h`（被存档/HUD 等广泛 include）。
- `UCSGameInstance`：跨关卡状态、子系统宿主。

### Character
- `ACSPlayerCharacter`：第三人称移动/相机/冲刺/下蹲/瞄准/交互/战斗输入；暴露动画 facing 状态。持有 `SurvivalStatsComponent`、`InteractionComponent`、`CombatComponent`、`FootstepAudioComponent`、`NoiseEmitterComponent`、`StatusEffectComponent`、`AppearanceComponent`。装备表现通过订阅库存组件的 `OnEquippedItemChanged`（成员 `BoundInventory`）事件驱动刷新，不轮询；同样订阅库存的 `OnWornAppearanceChanged` 驱动 `AppearanceComponent` 换装重建（`HandleWornAppearanceChanged`，与装备绑定同生命周期）。**基础身体网格（`GetMesh()`）= Collection_22 `SKM_UE5_man_body`（`SK_UE5_Skeleton`，与 Manny 互标兼容→`ABP_Player_Body` 直接驱动），是模块化外观的 LeaderPose 领导者**；外观部件（头/服装/鞋…）不再写死在角色构造里，改由 `UCSCharacterAppearanceComponent` 数据驱动（Appearance-A）。`Tick` 做相机插值/后坐力/装备姿态/瞄准点更新（相机插值经 `IsLocallyControlled()` 守卫，仅本机跑）。**镜头手感层（PlayerFeel-B）**：弹簧臂开位置延迟（lag，`ApplyCameraSettings` 按瞄准态切跟随速度、**不开旋转延迟**保证鼠标视角跟手）；`OnStartCrouch/OnEndCrouch` 用 `ScaledHalfHeightAdjust` 反补偿胶囊体瞬移、消除下蹲镜头"硬跳"；`Landed` 按落地竖直冲击速度播单峰下沉镜头 `UCSLandCameraShake`（在 `Camera/`，与受击 `UCSHitCameraShake` 同套自建 `UCameraShakePattern`，不依赖 GameplayCameras 插件）。相机臂用项目子类 `UCSCameraBoom`（`Camera/`，继承 `USpringArmComponent`，角色按基类指针持有、`CreateDefaultSubobject` 时实例化子类），override `BlendLocations` 把镜头碰撞做"软"——撞墙/门框收近快、离墙推回慢（帧率无关指数，碰撞缩进量相对完整臂长算、与瞄准拉近解耦），治理室内大量近距几何下默认弹簧臂的瞬时收放"跳"。
- `UCSPlayerAnimInstance`：读取角色暴露状态选择动画；`NativePostEvaluateAnimation` 在 FootIK 后驱动脚步声采样。

### Components（可复用 gameplay）
- `UCSInventoryComponent`（挂在 `ACSPlayerState`）：**个人背包/装备服务端权威**。复制 `InventoryStacks`/`EquippedItemId`/`EquipmentHotbar`/`SelectedEquipmentSlotIndex`（各带 `OnRep`）；方法 `AddItem`/`RemoveItem`/`HasItemQuantity`/`GetItemQuantity`/`EquipItem`/`SelectEquipmentSlot`/`SetInventoryStacks`/`SetEquipmentHotbar` + `ToSave/FromSave` 风格的 getter；权威经 `GetOwner()->HasAuthority()`，UI 刷新经 owning controller。`OnEquippedItemChanged` 委托驱动角色装备表现；`OnInventoryStacksChanged` 委托（增/删/覆盖/`OnRep` 均广播）驱动属性组件重算负重（Attribute-A）。**网格背包（Grid-A，三角洲/塔科夫式）**：网格是叠在"逻辑物品集"之上的**摆放层**——`FCSInventoryStack` 加 `GridX/GridY/bRotated`（-1=未摆放），物品占格由 `Items.csv` 的 `GridWidth/GridHeight` 决定（`GetItemFootprint`）。`AddItem` 服务端**自动摆放**（并堆同物品未满栈→余量首位匹配先不旋转后旋转；**原子**：任一占格放不下整次回滚返 false，"背包满（无空间）"与负重双限制并存）；`GetItemQuantity` 跨同物品溢出项**求和**，`RemoveItem` 跨项扣减；`SetInventoryStacks`（读档/迁移同路径）逐项摆放、放不下留未摆放不丢物。**网格尺寸由穿戴背包驱动（Grid-C1）**：基础口袋 `BaseBackpackGridWidth/Height`=6×4，穿上 Backpack 槽物品则用其 `AppearanceDefinitions.BackpackGrid*`（`Apparel_Hiking_Backpack`=10×6）；`BackpackGridWidth/Height` 退化为派生缓存（各端从复制的 `WornAppearanceItemIds`+本地数据各自算、不复制），穿戴变化经 `HandleWornChangedForGrid`→`RecomputeEffectiveGridSize`（无背包回退基础）+ 服务端 `ReflowGridPlacement`（尺寸变了才重排：原位仍放得下保留、否则自动摆放、变大补放原未摆放项、变小溢出留未摆放不丢物）。预埋服务端 `MoveStackTo`/`RotateStackInPlace`（控制器 `RequestMove/RotateInventoryStack`→`Server*` RPC 转发，Grid-B 接拖拽/旋转 UI）。背包面板用交互网格控件 `SCSBackpackGridWidget`（Grid-B，`SConstraintCanvas` 渲染 + Slate 拖放：LMB 拖拽移动、拖动中 R 旋转、RMB 原地旋转、拖到 `SCSDiscardDropZone` 整栈丢世界；`FCSInventoryGridDragDropOp` 携带条目，跟随光标的幽灵装饰物大小/合法色用 Lambda 绑定即时反映；客户端近似高亮、服务端落点 `MoveStackTo` 权威再校验）+ 紧凑 Equip/Drop 列表。存档 `CurrentSaveVersion` 6→7（摆放惰性在读档完成）。**也拥有穿戴外观（换装）权威**（Appearance-C）：复制 `WornAppearanceItemIds`(ReplicatedUsing=`OnRep_WornAppearance`)，服务端 API `WearAppearanceItem`/`UnwearAppearanceSlot`/`ResetAppearanceToDefault`/`SetWornAppearance`（按外观槽去重，槽由 `AppearanceDefinitions` 查），`OnWornAppearanceChanged` 委托驱动角色外观表现组件重建；住 PlayerState 故**跨 Pawn 重生 + 存档持久**（`FCSPlayerSaveData.WornAppearanceItemIds`），是通往"不同服装不同效果"装备系统的权威地基。默认套装 `GetDefaultAppearanceItemIds()` 在服务端 BeginPlay 首次 `EnsureDefaultAppearanceInitialized`（`bWornAppearanceInitialized` 区分新玩家空 vs 脱光成空）。**类型化 5 槽 loadout（Loadout-A/B，Unit 88/92）**：复制属性 `EquipmentSlots`（`TArray<FCSInventoryStack>`，COND_OwnerOnly）固定 5 槽、下标语义=`ECSLoadoutSlot`（主/副/近战/投掷/快速道具，键 1-5）。**槽=真容器（Loadout-B，塔科夫式）**：装备=整摞从背包网格**移出**进槽（不再占格，`EquipItem` 按名找首摞 / `EquipStackByIndex` 拖拽精确指摞；同槽旧装备自动摆放放回网格，放不下整次回滚拒绝交换）；卸下=`ClearEquipmentSlot` 槽内整摞移回网格（背包满=拒绝卸下不丢物）；`EquippedItemId` 派生=选中槽内容物（`RefreshEquippedFromSelectedSlot`）；路由按 `UCSItemDataSubsystem::GetLoadoutSlotForItem`（路由不到=拒绝）；**刀常驻** `EnsureMeleeSlotResident`（近战槽空时从网格**移**第一摞近战进槽，接 AddItem/卸下/读档各路径）；网格路径（制作/丢弃/容器存取）吃不到槽内装备。**槽内扣减**：`ConsumeEquippedSlotItem`（投掷/快速使用，扣空自动清槽）。负重=网格+槽内物都计（`ComputeCurrentLoad`）。存档 v8：`FCSPlayerSaveData.EquipmentSlotStacks`（`SetEquipmentSlots` 整体覆盖+路由校验退回），v7 及更早走 `SetEquipmentHotbar` 旧档迁移（按名 EquipItem=从网格移进槽）。**消耗品使用（Consumable-A，Unit 89）**：共用 `TryGetUsableConsumable`/`ApplyConsumableEffectsToPawn`（校验 Consumable 类型/定义/Pawn 存活 → `ApplyHealthDelta`/`ApplyNutritionDelta` + Buff 先移除后施加）；双入口=背包格 RMB `UseItem`（网格扣 1，`RequestUseItem`）+ 快速槽选中按攻击键 `UseEquippedQuickItem`（槽内扣 1，`RequestUseEquippedQuickItem`）。
- `UCSCraftingComponent`（挂在 `ACSPlayerController`）：**合成服务端权威**。复制 `CraftingState`（进行态）；`StartCraftOnServer` 校验配方/附近工作台/材料→扣料→计时→`CompleteCraftOnServer` 产出；`FindNearestWorkbench`/`FindUsableWorkbenchForRecipe`/`GetRecipeDefinition`/`GetAvailableRecipesForStation` 工作台与配方查询。Controller 的请求入口/Server RPC 薄转发到本组件。
- `UCSSurvivalStatsComponent`（挂在角色）：服务端拥有生命/饥饿/口渴/体力（含 `MaxHealth`/`MaxStamina`，当前仍住本组件）；有界 Tick（累加 1s 间隔做衰减，无衰减时 `SetComponentTickEnabled(false)`）；`OnHealthChanged` 委托 + `OnRep_Stats`。
- `UCSAttributeComponent`（挂在角色，与生存组件同位，Attribute-A）：**装备效果系统的属性快照层——服务端权威 + 复制**。复制 `FCSAttributeSnapshot`(ReplicatedUsing=`OnRep_Snapshot`) + `OnAttributeSnapshotChanged` 委托。明面=负重 `Load`/`MaxLoad` + 护甲 `ProtectionPhysical`；隐藏战斗乘区=`ProtectionBallistic`/`Stability`/`MoveSpeedScalar`/`StaminaRegenScalar`（SCP 认知层暂不进结构）。服务端 `RecalculateAttributes` 按计算顺序 **基础 → `ApplyEquipmentModifiers`(空钩子,Armor-A 接) → `ApplyBuffModifiers`(空钩子,Buff 接) → `Load`=Σ背包 `Weight`×数量 → 负重超 `MaxLoad` 则 `MoveSpeedScalar`=`OverweightMoveSpeedScalar`(0.55) → Clamp**；客户端只读复制快照、不本地重算。**事件驱动重算（非 Tick）**：角色订阅库存组件新增的 `OnInventoryStacksChanged`（服务端→`RecalculateAttributes`）。**消费**：角色 `UpdateMovementSpeed` 末端乘 `MoveSpeedScalar`、超重（`IsOverEncumbered`）禁冲刺；UI 经 `FCSHUDSnapshot` 复制到 HUD——主 HUD 负重条（`ECSHUDBarKind::Load`，超重红）+ **背包面板 `SCSInventoryPanelWidget` 属性侧栏**（`BuildAttributePanel`：明面 生命/体力/饥饿/口渴/负重/护甲 + 隐藏战斗乘区 防弹/Stability/移速/体力恢复，`Text_Lambda` 逐帧实时）。与武器/换装表现完全同构（权威+复制在组件，pawn 读复制快照纯表现，跨重生/读档一致）。**当前未做：装备/护甲修正（Armor-A：读 PlayerState 穿戴/装备槽 → `ArmorDefinitions`）、Buff→属性修正、伤害路径减伤（`FCSDamageContext`）、`MaxHealth`/`MaxStamina`/`StaminaRegen` 迁入快照、SCP 认知层属性。**
- `UCSCombatComponent`（挂在角色）：服务端权威近战/枪械。近战伤害走 `UCSAnimNotifyState_MeleeWindow` 通知窗口内多 tick 刀刃扫掠；枪械 hitscan 服务端校验冷却/弹药（经库存组件扣弹）/命中。每发被接受的射击在服务端调宿主 `NoiseEmitterComponent->ReportGunshotNoise()` 向 AI 听觉发枪声（Enemy-H）。**开火表现（FirearmFX-A）**：服务端弹道检测时开 `bReturnPhysicalMaterial`，把世界几何命中收集成 `TArray<FCSShotImpactInfo>`（量化坐标/法线 + 表面名，可受击目标不收集——敌人受击反馈走 `CSEnemyFeedbackComponent`），随 `MulticastPlayFirearmShotPresentation(SourceItemId, LoadedAmmoItemId, Impacts)` 一次 RPC 下发；各端从本机数据子系统按 `SourceItemId→PresentationId` 查表现配置自行播放——**远近枪声分离**（开枪者本机=近距变体池、其他端=远处闷响池，枪口位置发声）、枪口火光+抛壳（`SpawnDataDrivenVFX` 文件级辅助，Cascade/Niagara 都收，分别附着武器 `MuzzleComponent`/`EjectPortComponent`）、C++ 枪口点光源闪光（60cd/0.06s 定时销毁，黑暗收容区照明反馈）、弹着点按表面查 `GetSurfaceImpactEffectSet` 播特效+音效+可选贴花；后坐力仍仅本地控制端。**空枪干响**：客户端 `TryPrimaryAttack` 里 `IsFirearmDryFire` 预判（弹匣空且同口径零储备）→ 本机播表现配置的干响、不发 RPC。**一次性声音 3D 衰减（FirearmFX-B）**：枪声/干响/弹着/爆炸/脚步兜底统一走 `Audio/CSOneShotAudio`（运行时构造、按距离档位缓存的共享 `USoundAttenuation`：NaturalSound+空间化+远距低通），距离来自数据列 `GunshotSoundRange`/`ImpactSoundRange`/`ExplosionSoundRange`；纯表现，与 AI 听觉（`UAISense_Hearing`）无关。调试绘制由 `bDrawDebugMeleeTrace`/`bDrawDebugFirearmTrace` 开关。**投掷（Throwable-A，Unit 91）**：`TryThrowEquipped`/`ServerThrowEquipped`→`ThrowEquippedOnServer`（校验存活/`ThrowableDefinitions` 定义/持有/冷却 → 扣 1 → 控制器视点方向生成复制投掷物 `ACSThrowableProjectile`，类从数据 `ProjectileClassPath` 读、回退 C++ 类）；攻击键分流在 `ACSPlayerCharacter::PrimaryAttack`（快速道具槽=UseItem、投掷槽=Throw、其余=常规攻击）。投掷物服务端引信爆炸：半径线性衰减伤害走 `MitigateDamage`+`ICSHitReactable`（与枪械同链）、物理 `AddRadialImpulse`+角色 `LaunchCharacter` 冲击击退、隔墙 LoS 不结算、`UAISense_Hearing` 爆点噪音、多播爆炸表现（占位复用弹着 Default 表面特效）。
- `UCSInteractionComponent`（挂在角色）：基于 Timer（非 Tick）、仅在拥有端（`IsLocallyControlled`）刷新焦点交互目标，焦点变化经 `ICSInteractable::SetFocused` 通知目标显示/隐藏其**世界空间「E」提示**（见 `UCSInteractionPromptComponent`）。聚焦管线=球形重叠→朝向锥→视线 LoS→`CanInteract`→取最近。**LoS 近距离免检**：目标在 `CloseRangeNoLineOfSightDistance`(250) 内直接放行、跳过射线（贴近不存在隔墙问题，省一次 trace；并修掉贴脸/斜坡上的尸体因射向飘空包围盒中心被地面挡住的假阴性），远目标才做 LoS 防墙后。
- `UCSInteractionPromptComponent`（`UWidgetComponent` 子类，挂在每个可交互 Actor 上）：世界空间「E」交互提示。承载一段 Slate 键帽（E + 动作名），`EWidgetSpace::World`（随距离缩放）+ 绝对缩放（不受父网格缩放影响）。`SetPrompt(bShow, Label)`/`HidePrompt()` 由各 Actor 的 `SetFocused_Implementation` 驱动；**纯本地表现、不复制**（聚焦只在拥有端算）。朝向相机需逐帧，故 Tick 只在可见时启用（同一时刻至多一个聚焦目标可见），符合事件驱动优先。键帽黑底白字、billboard 正面朝相机（`(相机−提示).Rotation()`，文字不镜像）。**Slate 控件树惰性构建**：`BeginPlay` 不预建，首次 `SetPrompt(true,...)` 才按 `IsValid` 兜底建一次并缓存——从未被聚焦的可交互物只挂一个空壳 `WidgetComponent`（零 Slate/render target 开销）。`WorldZOffset`（上方高度）与 `PromptScale`（整体绝对缩放，默认 0.3）每实例可在细节面板调。**已取代旧的屏幕空间后处理描边 + 中心屏幕交互提示条**。
- `UCSFootstepAudioComponent`（挂在角色）：组件不开 Tick，由 AnimInstance 在最终姿势后调用，按地面 Physical Surface 查表播放。**纯客户端表现**（专用服务器早退），不作 AI 噪音源。
- `UCSNoiseEmitterComponent`（挂在玩家，Enemy-H）：**服务器权威 AI 听觉噪音源**。组件内部仅 `HasAuthority` 才发声。低频 Timer（非 Tick）按宿主步态发移动噪音（蹲走/走/跑分级响度，站定不发），`ReportGunshotNoise()`/`ReportOneShotNoise()` 供开枪等一次性响动调用；统一经 `UAISense_Hearing::ReportNoiseEvent`（Instigator=宿主玩家）。响度线性缩放敌人听觉半径 = 潜行/暴露杠杆。
- `UCSCharacterAppearanceComponent`（挂在 `ACSPlayerCharacter`，Appearance-A/B/C）：**数据驱动的可替换外观（换装）表现层——纯表现、不持权威、不复制**。**穿戴真值在 PlayerState 的 `UCSInventoryComponent::WornAppearanceItemIds`**（服务器权威 + 复制 + 存档，见上）；本组件订阅其 `OnWornAppearanceChanged`（角色 `BindPlayerStateEvents` 里绑定，与 `OnEquippedItemChanged` 并列），各端调 `RebuildFromWornList(穿戴ID列表)` 重建部件网格——与武器持握 `OnEquippedItemChanged → UpdateEquipmentPresentation` 完全同构（外观=已复制穿戴状态的纯函数）。`ECSAppearanceSlot`（Head/Torso/Legs/Feet/Hands/Headgear/Vest/Backpack）+ `FCSAppearancePart`（槽位+来源 `SourceItemId`+`FSoftObjectPath` 骨骼网格+可选 body 遮罩材质；**表现层本地缓存、不复制**）。`RebuildFromWornList` 把穿戴 ID 列表经 `BuildPartFromItem`（查 `AppearanceDefinitions`）解析成按槽唯一的部件集 → `RebuildAppearance` 幂等地按槽位**运行时创建 `USkeletalMeshComponent`**（无碰撞、`AlwaysTickPoseAndRefreshBones`、`AttachToComponent(身体)` + `SetLeaderPoseComponent(身体)`），有部件设网格+显示、无部件清网格+隐藏。`GetWornItemId(Slot)` 给 UI 读当前已穿。**武器系统不受影响**（武器挂 `hand_r`、与骨架/网格无关）。穿戴请求经玩家 `ACSPlayerController`（换装面板 `SCSWardrobePanelWidget`／`J` 键，或 exec `CSWearAppearance`/`CSUnwearAppearance`/`CSResetAppearance`）→ `Server*` RPC → `*OnServer` **改库存组件的 `WornAppearanceItemIds`**（不再改本组件）。**当前未做：opacity 遮罩防穿模实装（`BodyOpacityMaskMaterial` 字段/hook 已留）。**
- `UCSStatusEffectComponent`（挂在玩家与敌人角色）：**跨系统底座的状态效果生效层（Buff-B）服务端权威**。`ApplyBuff`/`RemoveBuff`/`ClearAllBuffs` 按 `StackPolicy` 叠层/刷新；周期效果用服务器 Timer（非 Tick）经 owner 的 `SurvivalStatsComponent` 结算（Damage/Heal/Stamina）；`Duration` 到期单次 Timer 移除。复制 `ActiveEffects` 摘要 + `OnActiveBuffsChanged` 委托。**当前未做：Buff→属性快照修正（属性层 `UCSAttributeComponent` 已落地 Attribute-A，但 Buff 的 `AttributeModifiers` 接入快照的 `ApplyBuffModifiers` 钩子还是空的）、独立多实例叠加（Buff-E）、存档（Buff-I）、命中自动附加（Combat-B）。Contamination/Noise 周期类型暂跳过（无对应属性/系统）。**

### AI（敌人）
- `ACSEnemyCharacter`（服务端权威）：血量（复用 `SurvivalStatsComponent`）/受击（`ICSHitReactable`，方向+轻重+部位逐骨判定，爆头倍率数据驱动）/近战攻击（变体+命中 notify）/死亡（动作停末帧）/尸体搜刮（`ICSInteractable`+`ICSLootSource`）。逐骨命中：骨骼网格用物理资产 `QueryOnly`+`Visibility=Block`、胶囊对 `Visibility` `Ignore`（只管移动），trace 返回 `Hit.BoneName`。数值真值在 `EnemyDefinitions`（按 `EnemyId` 套用），BP_AI_* 只管内容/表现。**战斗情绪**：复制 `bIsAggressive`（`SetCombatMood`，仅服务器写、纯复制无 RepNotify），由 `AIController` 在状态切换处推（追击=true/调查·待机=false），供 AnimBP 切平静/攻击性移动姿态——仅表现，不碰任何权威战斗数值。
- `UCSEnemyAnimInstance`（仅算值，AnimBP 连图，工作线程更新）：`GroundSpeed`/`bIsMoving` 驱动移动 BlendSpace；`bIsAggressive`(取自角色) 供 AnimGraph 在平静(Normal)/攻击性(Offensive)两条移动 BlendSpace 间 `Blend Poses by bool`；`bIsDead` 供死亡姿态。**步态多样化 = 速度落点而非 fork 资产**：单条速度驱动 BlendSpace + 每怪 `MoveSpeed`（数据）决定落在 walk/run 段（慢怪=走/快怪=跑）；脚步匹配主力为 **Stride Warping**（`AnimationWarping` 插件，2026-06-09 启用）：本类线程安全算好 `StrideScale`（=地速/参考脚速，夹 `[StrideScaleMin,StrideScaleMax]`）与 `LocomotionDirection` 喂 AnimGraph 的 `Stride Warping` 节点，按地速缩放步幅(脚 IK)让脚钉地不滑，不改播放倍率、与 NavMesh 胶囊移动解耦。参考脚速默认按本怪 `MoveSpeed` 归一化（`bDriveStrideRefFromMoveSpeed`，一个 AnimBP 适配 Basic/Brute/Stalker）。`LocomotionPlayRate` 默认恒 1.0，`bDrivePlayRateBySpeed=true` 为旧式 C++ 单参考速度缩放兜底。**注：`Stride Warping` 节点在 `ABP_ContaminatedMutant` AnimGraph 的连线为编辑器手动工作（AnimGraph 拓扑 Python 不可改），见 `DevelopmentUnits.md` 同日单元；骨架 `SK_MC03_Full` 为完整 UE5 Mannequin 骨架，Foot Definitions 用 `ik_foot_root`/`ik_foot_l/r`+`foot_l/r`+`pelvis` 标准填。**
- `UCSEnemyFeedbackComponent`（挂敌人，Enemy-Feedback-A，**纯表现层、不复制、专用服务器跳过**）：受击表现。`PlayHitFeedback(命中点,命中方向,伤害)` 在命中点喷血液 Niagara（朝攻击行进方向、`SpawnSystemAtLocation` 池化 AutoRelease）+ 整身闪白（`SkeletalMesh->SetOverlayMaterial` 叠加 `M_HitFlash_Overlay`，`FlashAmount` 峰值后衰减，仅闪白时开 Tick）。由 `ACSEnemyCharacter::MulticastPlayHitImpact`（表现多播，受击时每次有效命中都调，**先于**攻击中/节流早退）在各端触发；命中点/方向复用既有 `ICSHitReactable::ReceiveHitReaction` 的 HitLocation +（命中点−攻击者）。默认血效/材质构造期 `ConstructorHelpers` 装入（`NS_ShadedBlood_1` / `M_HitFlash_Overlay`），BP/数据可覆盖。血条不做（真实感）；伤害数字留作 CVar 开关后置。新增 `Niagara` 模块依赖。
- `ACSEnemyAIController`（仅服务器）：**感知 + 追击 + 调查**大脑。`UAIPerceptionComponent` 配 `UAISenseConfig_Sight` + `UAISenseConfig_Hearing`（参数按 `EnemyId` 从 `EnemyDefinitions` 套用，`OnPossess` 时 `ConfigureSense`）。四态 `ECSEnemyAIState{Idle,Investigating,Chasing,Returning}`：`HandleTargetPerceptionUpdated` 按 `Stimulus.Type` 分流——视觉成功→`StartChase`（全速 `MoveToActor`，首次锁定触发 `OnAggroAcquired` 转身+咆哮）；听到噪音(非追击中)/丢失视线→`StartInvestigate(点)`（谨慎慢走 `MoveToLocation`，`OnMoveCompleted` 到点张望，无果超时回 Idle，途中看到玩家则升级追击）。受伤即被发现走 `ForceAggro`。**巡逻/脱战(Enemy-Patrol-A)**：`OnPossess` 记 `HomeLocation`；**Idle=出生点附近随机游荡**（`EnterIdle`→`BeginWanderStep` 用 `UNavigationSystemV1::GetRandomReachablePointInRadius(Home,WanderRadius)` 选点 `MoveToLocation`，到点停顿再走，悠闲慢速）；**脱战 leash**：`UpdateChase` 查自身离 `HomeLocation`>`LeashRadius` → `StartReturning` 走回出生点；**Returning 态忽略被动感知**（防 leash 边缘 Chasing↔Returning 抖动），但 `ForceAggro`(受伤) 仍可打断；到家回 Idle 游荡。`EnterIdle` 是丢失目标/调查无果/返回到家的统一汇合点；`StopChase` 只停追击+复位平静、不设终态。`SetWalkSpeedForState`(switch) 决定每态速度(Idle 游荡/Investigating 调查/Returning 返回/Chasing 全速)与情绪(仅 Chasing=攻击性，故游荡/返回用 Calm BlendSpace 的 Normal 姿态)。巡逻/调查行为参数（WanderRadius/WanderSpeedFraction/WanderPauseSeconds/LeashRadius/ReturnSpeedFraction/InvestigateSpeedFraction）**从 `EnemyDefinitions` 套用（数据驱动，每怪可不同；改表+重生成即生效、不用重编）**，`ApplyPerceptionDefinition` 里随感知参数一并套；控制器 UPROPERTY 仅作未入表的兜底默认。需场景 `NavMeshBoundsVolume`。调试 `cs.ai.DrawPerceptionDebug` 加出生点/游荡半径/leash 半径/状态文字。**听到 ⇔ 距离 ≤ `HearingRange × 噪音响度`**（噪音来自玩家 `UCSNoiseEmitterComponent`）。

### Items
- `UCSItemDataSubsystem`（GameInstance 子系统）：运行时按 `ItemId`/`RecipeId`/`BuffId`/`EnemyId` 查询表格驱动的物品/配方/装备/枪械/近战/Buff/敌人/外观定义（来自 `Data/Generated/*`）。外观（Appearance-A）：`GetAppearanceDefinition`/`LoadAppearanceDefinitionsCsv`，`FCSAppearanceDefinition`（`ItemId`→`SlotType`(FName)/`SkeletalMesh`/`BodyOpacityMaskMaterial`），供 `UCSCharacterAppearanceComponent::EquipAppearanceItem` 把穿戴物品映射成模块化外观部件。Buff 数据为跨系统底座第一层（Buff-A，仅数据）：`GetBuffDefinition`/`IsKnownBuff`/`GetAllBuffIds` 提供查询，`FCSBuffDefinition` 含属性修正与周期效果（由 `BuffAttributeModifiers`/`BuffPeriodicEffects` 副表按 `BuffId` join）。运行时施加由 `UCSStatusEffectComponent`（Buff-B）负责；属性快照修正/存档留给 Attribute-A/Buff-I。敌人定义（Enemy-Data-A）：`GetEnemyDefinition`/`IsKnownEnemy`/`GetAllEnemyIds`，`FCSEnemyDefinition` 承载敌人的服务器权威数值（血量/速度/攻击/视野/听觉半径/追击/命中Buff/爆头倍率/掉落表）——`ACSEnemyCharacter::BeginPlay` 与 `ACSEnemyAIController::OnPossess` 按 `EnemyId` 套用，表里没有该 ID 则保留 C++/BP 默认。**敌人数值的真值在此表，不在角色/BP 硬编码**；BP_AI_* 只管内容/表现（Mesh/动画/选 `EnemyId`）。任务定义层（Quest-A，待验证）：`GetQuestDefinition`/`GetAllQuestDefinitions`/`GetObjectivesForQuest`，`FCSQuestDefinition`（`QuestId`→`ECSQuestType`(Main/Side/Hidden)/Layer/前置·揭示 `WorldStateTags`/图坐标，源表 `Quests.csv`）+ `FCSObjectiveDefinition`（`ObjectiveId` **复用 `WorldProgress.ObjectiveStates` 的 join 键**，源表 `Objectives.csv`，按 Order 分组）——静态只读、各机一致、不复制不存档；类型定义在 `World/CSObjectiveTypes.h`。join 活状态在 `ACSGameState`（见上）；UI（Quest-B 面板/Quest-D 状态图）与条件求值器（Quest-C）留后续。**枪械开火表现层（FirearmFX-A）**：`FCSFirearmDefinition.PresentationId` → `GetFirearmPresentation`（`FCSFirearmPresentationDefinition`：枪口/抛壳 VFX + 本地/远处枪声变体池 + 干响，源表 `FirearmPresentationDefinitions.csv`）；弹着点按表面 `GetSurfaceImpactEffectSet`（`FCSSurfaceImpactEffectSet`：特效/音效变体/贴花，源表 `SurfaceImpactEffectSets.csv`，未配置表面回退 `Default` 行）+ `GetSurfaceTypeName(EPhysicalSurface)`（与脚步声系统同一套 `UPhysicsSettings.PhysicalSurfaces` 名映射）。**开火声音/枪口特效的真值已从 `FirearmDefinitions` 迁到表现表**（多枪可共用一份表现配置）。
- `ACSWorldItemActor`：可拾取世界物品，服务端发放到库存组件，防重复拾取。

### World（场景作者摆放）
- `ACSResourceNode` / `ACSLootContainer` / `ACSWorkbenchStation` / `ACSStreamingElevatorStation` / `ACSCombatTargetDummy`：各自实现 `ICSInteractable`，服务端权威改自身状态；焦点反馈用**世界空间「E」提示**——每个可交互 Actor 带一个 `UCSInteractionPromptComponent`，`SetFocused_Implementation` 按各自的可用判定调 `SetPrompt(...)` 显示「E + 动作名」。**屏幕空间后处理描边（`SetRenderCustomDepth` + `M_InteractOutline_PP`）与中心屏幕交互提示条已移除**；`M_InteractOutline_PP` 资产保留在盘上但不再被引用，`r.CustomDepth=3` 保留（无写入方即失效）。
- **搜刮来源抽象 `ICSLootSource`（`Components/CSLootSource.h`，Enemy-F）**：`GetLootDisplayName`/`GetLootPersistentId`/`CopyRemainingLoot`/`TryTakeLootItem`。`ACSLootContainer`（摆放容器，按 `PersistentId` 入档）和 `ACSEnemyCharacter`（死亡尸体，瞬时不入档）都实现它。玩家控制器的搜刮流程（缓存 `OpenLootContainer:AActor*` + `ClientOpenLootContainer`/`ServerTakeLootContainerItem` + HUD 快照实时读）只面向接口 `Cast<ICSLootSource>`，与具体类型解耦；搜刮 UI 控件本就只读 HUD 快照、零绑定。敌人尸体：死亡时按 `LootTableId` 生成 `CorpseLoot`(复制)，`ApplyDeadCollision` 把胶囊设为 `QueryOnly`+仅 `Visibility` 重叠（被交互射线发现但不挡移动/近战/导航；无战利品则 `NoCollision`）。尸体**不做骨骼网格描边**（Custom Depth 与死亡姿势同步有坑），焦点反馈与其他可交互物一致用 `UCSInteractionPromptComponent` 世界空间「E」提示。
- 电梯 `ACSStreamingElevatorStation`：从 `LayerDefinitions` 注册表读层级，读取 `WorldProgress` 做路线门禁；移动期间 Tick（**空闲时未关 Tick，见第 6 节**）。
- **门 `ACSDoorActor`（`ICSInteractable`，Interact-Door-A，2026-06-13）**：服务器权威安全门，复制态 `bIsOpen`/`bIsUnlocked`（`OnRep_DoorVisual` 驱动门扇摆动 + 锁灯红/绿）。组件=门框(根)/门扇(过渡期才开 Tick 的相对 Yaw 插值)/锁面板/锁灯(`UPointLightComponent`)/`UCSInteractionPromptComponent`，默认网格取门交互包 `SM_DoorFrame`/`SM_MetalDoor`/`SM_CardLock`。**数据驱动解锁条件 `TArray<FCSDoorRequirement>`**：每条 `{Type∈WorldStateTag/ObjectiveComplete/RouteUnlocked/Keycard, Value:FName, Quantity, bConsumeOnUse, CustomFailurePrompt}`，全满足才可开（空列表=普通门）。`EvaluateRequirements` 服务端裁决、客户端也算（GameState 复制 + 本地玩家背包对 owner 复制）→ 聚焦提示直接显示"缺什么"；`CanInteract` 恒 true（锁着也给反馈）。钥匙卡 `bConsumeOnUse` 开门即 `RemoveItem`；`bLatchUnlock`(默认破封永久解锁)/`bAutoClose`/`WorldTagOnFirstOpen`/`ObjectiveOnFirstOpen`(开门驱动世界进度)。订阅 `OnWorldProgressChanged` → 通电/解锁瞬间锁灯+提示刷新（门不自开）。入档：`FCSWorldActorSaveData.Doors`（`FCSDoorSaveData`，按 `PersistentId`，`ApplySavedState`），存读循环在 `CSPlayerController`。
- **世界状态开关 `ACSWorldStateSwitch`（`ICSInteractable`，Interact-Door-A）= "通电"来源**：交互在服务端 `ACSGameState::AddWorldStateTag(WorldStateTag)`（如 `Power.SectorB.On`），点亮挂同名条件的门；支持 `RequiredItemId`(保险丝/电池可消耗)、`bOneShot`(一次性/可来回扳)，默认网格 `SM_Lever`。开关态本就活在 `WorldProgress` 存档里、无需单独入档。配套 GameState 新增 `RemoveWorldStateTag(FName)`（断电/来回扳，清 `<tag>_Set` 事件Id 允许再置位）。
- `UCSWorldSaveGame`：版本化存档（`CurrentSaveVersion`），分组为 `PlayerSaves` / `WorldProgress` / `WorldActors`，含旧版字段迁移入口。

### UI（Slate → UMG 迁移中）
**表现层方向（2026-06-10 用户拍板）：全面转 UMG 代码生成**——Unit 24 的 HTML→WBP 生成器（`UI/Editor/CSWidgetAuthoringLibrary`，编辑器专用）为终态，运行时实例化生成的 WBP、逐屏淘汰手写 Slate `SCS*`。已落地三屏=对话框（`UCSDialogueUserWidget`）+ 主 HUD（`UCSHUDUserWidget`）+ 主菜单（`UCSMainMenuUserWidget`，见下）；背包/属性/搜刮/合成各屏待逐个生成+接线。迁移进度见第 6 节。
- `UCSHUDUserWidget`（`UUserWidget`，HUD UMG 转换 Unit 26）：**主 HUD 的运行时表现（已转 UMG，第二个 UMG 屏）**。布局在 `WBP_HUD`（`/Game/UI/HUD/WBP_HUD`，生成器产出）。`NativeTick` **自驱**读 owning `ACSPlayerController` 的单帧 `FCSHUDSnapshot`+`IsAnyMenuPanelOpen()`，刷新时钟/5 条状态条/电梯/弹药/背包摘要/4 装备槽；菜单开时折叠 `ContentRoot`（本体保持 Tick）。单例 `BindWidgetOptional`，重复结构（Bar0..4/Slot0..3_*）按名 `GetWidgetFromName` 解析成数组。颜色走 `FCSUIStyle`。由 `ACSPlayerController::CreateHUD` 经 `LoadClass(WBP_HUD_C)`→`CreateWidget`→`AddToViewport(10)` 实例化（UPROPERTY `HUDUMGWidget` 防 GC）。**旧 Slate `SCSHUDWidget` 已退役**（源文件暂留，PIE 确认后删）。
- `SCSInventoryPanelWidget` / `SCSGMPanelWidget` / `SCSCoopSessionPanelWidget` / `SCSNarrativeOverlay`：仍是手写 Slate，纯表现，经 `ACSPlayerController` 的请求方法发起意图，背包等数据来自 `FCSHUDSnapshot`（`BuildHUDSnapshot()`）。**是 UMG 迁移的后续目标。**
- `SCSPaperdollWidget`（UI-Loadout-A，Unit 90）：背包面板左列的**一屏纸娃娃**——`SCSLoadoutSlotRow` ×5（武器槽：LMB 选中/RMB 卸下/接受背包网格拖放，类型匹配绿、不匹配红，落下走 `RequestEquipItem` 服务端路由）+ `SCSAppearanceSlotRow` ×8（外观槽：当前穿戴实时显示、Change 下拉穿/脱、RMB 快速脱、接受 Apparel 拖放）+ Reset Outfit。**原独立换装面板 `SCSWardrobePanelWidget`（J）已删除**，其功能并入本控件；J 键改为背包面板快捷键（=B）。改动全走既有服务端权威链（装备请求 / `CSWearAppearance` 穿戴流），控件零私有权威状态。
- `UCSNarrativeOverlayUserWidget`（`UUserWidget`，UI-Narrative-A → UMG 转换）：底部居中的 **AIC 旁白字幕**覆盖（已转 UMG 终端质感，替换旧纯 Slate 黑框 `SCSNarrativeOverlay`）。布局在 `WBP_NarrativeOverlay`（生成器 `BuildNarrativeOverlayWBP`，`/Game/UI/Narrative/`：终端框 + 左 cold 强调条 + 琥珀说话人标签 + 扫描线）。本类只绑数据 + 驱动动效：`NativeTick` 自驱轮询 `UCSNarrativeDirectorSubsystem`，仅 `HasActiveLine() && !IsActiveLineConversation()`（=AIC 旁白，与 NPC 对话互斥）时 `HitTestInvisible` 显示，刷新说话人/台词；动效 = 整框入场淡入上滑 / 每行解码揭示 / 扫描线漂移。由 `ACSPlayerController::CreateHUD` 经 `LoadClass(WBP_NarrativeOverlay)`→`CreateWidget`→`AddToViewport(11)` 实例化（`UPROPERTY NarrativeOverlayUMGWidget` 防 GC），与 HUD 一并挂/移、仍 `NotifyLocalControllerReady`。**旧 Slate `SCSNarrativeOverlay` 已退役**（源文件 + 控制器 include 暂留作回退，PIE 确认后删）。
- `UCSCoopSessionUserWidget`（`UUserWidget`，UI-UMG-Coop → UMG 转换）：**合作会话面板**（已转 UMG，替换 Slate `SCSCoopSessionPanelWidget`，迁移路线第 1 单元）。布局在 `WBP_CoopSession`（生成器 `BuildCoopSessionWBP`，全屏 scrim + 居中终端面板）。功能 parity：`NativeTick` 轮询 `UCSPlatformServiceSubsystem` 刷状态条，5 按钮（主持/搜索/加入/销毁/关闭）路由到既有 `ACSPlayerController` 方法（`HostCoop`/`FindCoop`/`JoinCoop(0)`/`DestroyCoopSession`/`RequestCloseCoopSessionPanel`），`NativeOnKeyDown` 收 ESC/F3 关、入场淡入上滑动效。由 `ACSPlayerController::CreateCoopSessionPanel` 经 `LoadClass`→`CreateWidget`→`AddToViewport(30)` 实例化（`UPROPERTY CoopSessionUMGWidget` 防 GC），`bCoopSessionPanelOpen` 标志/延迟关/`ApplyMenuInputMode`（coop 焦点分支改聚焦 `CoopSessionUMGWidget->TakeWidget()`）不变。**旧 Slate `SCSCoopSessionPanelWidget` 已退役**（暂留回退）。
- `UCSDialogueUserWidget`（`UUserWidget`，Dialogue-UI-A → UMG 转换 Unit 25）：**NPC 会话专用对话框的运行时表现（已转 UMG，第一个落地的 UMG 屏）**。布局在 `WBP_Dialogue`（Unit 24 生成器产出，`/Game/UI/Dialogue/WBP_Dialogue`）里，本类只绑数据：`NativeTick` **自驱**轮询本机导演当前句，只在 `IsActiveLineConversation()` 时显示（`HitTestInvisible`），刷新名牌/台词/头像（头像变化时才 `SetBrushFromTexture` 避免每帧重建）。颜色/字/皮来自中央主题 `FCSUIStyle`（生成器灌入 WBP）。由 `ACSPlayerController::CreateHUD` 经 `LoadClass(WBP_Dialogue_C)`→`CreateWidget`→`AddToViewport(12)` 实例化（ZOrder 12 高于字幕；UMG 控件 UPROPERTY `DialogueUMGWidget` 持有防 GC），`RemoveHUD` 里 `RemoveFromParent`。**旧 Slate `SCSDialogueWidget` 已退役**（不再被控制器引用，源文件暂留作回退，PIE 确认 UMG 版正常后删）。头像数据链不变：来自交谈 NPC 的 `ACSNarrativeNPC::Portrait`，导演 `GetActiveLinePortrait()` 提供 `UTexture2D*`。
- `UCSMainMenuUserWidget`（`UUserWidget`，UI-Menu-A，第三个落地的 UMG 屏）：**主菜单的运行时表现**。布局在 `WBP_MainMenu`（生成器 `BuildMainMenuWBP` 产出）。**可点击交互**屏：`NativeOnInitialized` 绑 5 个 `UButton`（`BtnContinue/BtnNewGame/BtnCoop/BtnSettings/BtnQuit`）的 `OnClicked`。接线：新建档案 → 不读档（`GameInstance->SetPendingLoadOnStart(false)`）+ `OpenLevel(NewGameLevelName)`（默认 `Lvl_L0_C12`，EditAnywhere）；继续下潜 → 有存档才可点（`DoesSaveGameExist` 否则 `SetIsEnabled(false)`），读存档 `MapName` + `SetPendingLoadOnStart(true)` + OpenLevel；设置 → 创建 `WBP_Settings` 叠加（ZOrder 5）；退出 → `QuitGame`；合作 → `ShowStub` 占位。**动效**走 `NativeTick`（菜单非性能敏感，可 Tick）：进场淡入上滑（IntroGroups 按名错峰）/ 扫描线（`Scanline` Image，Y 循环）/ 标题 RGB glitch（`TitleGlitchR/C` 叠 `TitleOverlay`，周期闪现）/ 菜单悬停（轮询 `Btn*->IsHovered()` → `MenuAccent{i}`+`MenuText{i}` 转红）；全部受 `UCSGameUserSettings::GetReduceMotion()` 控制。FX 控件用 `BindWidgetOptional`（非 BindWidget 同名普通 UPROPERTY 会撞 OnVariableAdded，见 `UE_Gotchas` 3.11）。由 `ACSMenuPlayerController::BeginPlay` 实例化，**不经 `ACSPlayerController`**。
- `UCSSettingsUserWidget`（`UUserWidget`，UI-Menu-A）：**设置屏**，主菜单「设置」按钮创建并叠加、「返回」自 `RemoveFromParent`。布局在 `WBP_Settings`（生成器 `BuildSettingsWBP`，全屏暗底 + 居中面板）。画面（窗口模式/分辨率/质量/垂直同步）走引擎 `UGameUserSettings`（循环按钮即时 Apply+Save）；主音量 `USlider` + 减少动态 toggle 走 `UCSGameUserSettings`。无私有权威状态。
- `UCSUILayerSubsystem`（`ULocalPlayerSubsystem`，UI-Modal-A）：**全工程唯一的"谁获焦 + 什么输入模式"决策点**，也是通用弹窗/Toast 的宿主。挂 `LocalPlayer`（**跨非无缝 `OpenLevel` 持久**——`PlayerController` 随关卡销毁重建，`LocalPlayer` 不会；菜单关与游戏关拿同一实例）。持模态栈（`UPROPERTY` 强引用防 GC）+ 常驻 Toast 容器。对外：`RegisterOwner(PC, 恢复底层输入模式的 lambda)`（控制器 `BeginPlay` 调，新关卡新 PC 重注册会先清旧关残留控件）、`PushConfirm`/`PushModal`（返回控件指针，**WBP 缺失/PC 失效/跨世界返 null 让调用方降级**）、`ShowToast`（**返回 bool**，无 World/WBP 缺失返 false）、`HandleEscape`、`HasModalOpen`。`RecomputeInputMode`：栈非空→`UIOnly`+`SetWidgetToFocus(栈顶)`+显鼠标；栈空→调注册的恢复函数。`RemoveModalDeferred`=**同步出栈 + 同步重算输入模式**（无一帧焦点缺口），**仅延迟一帧销毁控件**（守 `UE_Gotchas` 1.1；World 空则跳过销毁）。WBP 类路径常量 + 懒加载 `EnsureClasses`，产物在 `/Game/UI/Modal/`。**UI-Modal-C 加全屏层栈** `ScreenStack`（全屏 UMG 层，如暂停菜单，Z200=面板之上模态之下）：`PushScreen(class)`/`RemoveScreenDeferred`/`HasAnyLayerOpen`；`RecomputeInputMode`（焦点=顶层模态否则顶层全屏）/`HandleEscape`（先消模态再消全屏层）/`ResetForNewWorld` 一并覆盖（模态逻辑不动，纯增量）。
- `UCSModalUserWidget`（`UUserWidget`，UI-Modal-A）：**一类三态通用弹窗**（`ECSModalMode`=Confirm/QtyInput/TextInput，类型见 `UI/CSModalTypes.h`）。布局在 `WBP_Modal`（生成器 `BuildModalWBP`）。`Setup(config, onClosed, owner)` 填配置 + 绑非动态委托 `FCSOnModalClosed`（可绑 lambda）。自收 ESC（取消）/Enter（确认，确认钮禁用时不响应）。关闭=组装 `FCSModalResult`（**按模式过滤**结果，不带无关脏值）→ 先广播 `OnClosed` → 请管理器 `RemoveModalDeferred`。**入场动效**（`NativeTick` 驱动，还原 HTML `bootin .2s`）：scrim+面板淡入 + 面板从 +27px 上滑归位（ease-out），面板靠 `BindWidgetOptional ModalPanelBox`（生成器已注册为变量，按名直绑、无需重生成 WBP）。无权威状态。
- `UCSToastUserWidget`（`UUserWidget`，UI-Modal-A）：**非模态自消失提示**（不吃输入）。布局在 `WBP_Toast`（生成器 `BuildToastWBP`）；容器 `WBP_ToastLayer`（顶部居中 `ToastStack` VBox，`HitTestInvisible`）。`Init(text, level, duration)` 设文案 + 按 `ECSToastLevel`（Info/Ok/Warn/Danger）取 AccentBar/文字配色。**整条动效包络**（`NativeTick` 驱动，还原 HTML `toastin`）：从 -20px 滑入+淡入(~0.26s)→停留→滑到 -13px+淡出(~0.45s)跑完自 `RemoveFromParent`（不再用单独定时器）。无权威状态。
- `UCSPauseMenuUserWidget`（`UUserWidget`，UI-Modal-C）：**游戏内暂停/系统菜单**，由管理器作为"全屏层"托管（`PushScreen`）。布局在 `WBP_PauseMenu`（生成器 `BuildPauseMenuWBP`）。四项：继续（`RemoveScreenDeferred` 关自己）/ 设置（叠 `WBP_Settings`，`NativeTick` 轮询 `IsInViewport` 清 `bSettingsOpen`）/ 返回主菜单（`PushConfirm`→`OpenLevel(MainMenuLevelName)`）/ 退出（`PushConfirm`→`QuitGame`）。自处理 ESC（设置开着→先关设置，否则继续）；入场动效 `NativeTick` 还原 HTML `bootin`（scrim+面板上滑淡入）。由 `ACSPlayerController::OpenPauseMenu`（ESC 无面板时）经管理器 `PushScreen` 打开。**纯本地表现**（多人下不暂停服务端 sim、不碰权威状态）。无权威状态。

### Narrative（叙事/演出表现层）
- `UCSNarrativeDirectorSubsystem`（`UWorldSubsystem`，Narrative-A）：**AIC 叙事导演——纯本机表现层，不持权威状态、不新增复制**。由本机 `ACSPlayerController::CreateHUD` 经 `NotifyLocalControllerReady` 唤醒（专用服务器无本机玩家则休眠），有界重试绑定 `ACSGameState::OnWorldProgressChanged`。对世界状态「新增」求差（`WorldStateTags`/`CompletedObjectiveIds`/`UnlockedRouteIds` 三组）命中 `FCSNarrativeBeat`→拆句入队→喂给 `SCSNarrativeOverlay`，按 `LineSeconds` 用世界 Timer 顺序推进（无 Tick）。**节拍数据表驱动**：`Data/Generated/NarrativeBeats.csv`（源表 `Data/Source/`，经 `GenerateGameData.py` 校验生成），按 `UCSItemDataSubsystem` 同模式从磁盘加载。**去重/读档安全**：首个世界状态快照只建基线不播（读档广播全量 / 中途加入收全量都不重播历史）；每节拍持久去重——播完经本机 Controller 既有 `SetWorldStateTag` 服务器路径写 `Narrative.Played.<BeatId>`（开场白用显式 `Narrative.Intro.Played`），据世界标签跳过已播。`ECSNarrativeTriggerType`（GameStart/ObjectiveCompleted/WorldStateTagAdded/RouteUnlocked）与 `ECSNarrativeSpeaker`（AIC/Varga/Hale/System，后三为实体 NPC 预留）定义在 `Narrative/CSNarrativeTypes.h`（纯 C++，无反射）。
- **演示节拍接现有事件**（不动 Loop-A）：开场白（GameStart）+ `ACSStreamingElevatorStation::CompleteTravel` 写 `Site.FirstDescent`/`Site.ReturnedToS0` + `ACSLootContainer::TryTakeItem` 首次成功搜刮写 `Site.FirstSearch`（均服务器权威、幂等）。
- `ACSNarrativeNPC`（`AActor`+`ICSInteractable`，Narrative-C）：**可对话 NPC，纯触发器**——不持对话内容、不写权威状态。`UCapsuleComponent`（根，**Block `ECC_Visibility`** 才能被交互聚焦+LoS 发现，顺带挡路）+ `USkeletalMeshComponent`（编辑器指定外观）+ `UCSInteractionPromptComponent`（头顶「E 交谈」提示，复用 Unit 63）。`Interact_Implementation`（服务器，照搬 `ACSWorkbenchStation`→`ClientOpenCraftingStation` 模式）→ `ACSPlayerController::ClientPlayNPCConversation(NPCId)`（仅发交谈者）→ 本机导演 `PlayNPCConversation`。**Dialogue-UI-A 加** `TSoftObjectPtr<UTexture2D> Portrait`（BP 可编辑，对话框左侧头像；视觉身份挂在 actor 上，文本说话人仍来自会话表）。
- **会话模型（导演侧，可扛多对话 + 随剧情变化）**：`UCSNarrativeDirectorSubsystem` 另加载 `Conversations.csv`（`ConversationId/OwnerNPCId/Speaker/RequiredTags/ForbiddenTags/SetTagsOnEnd/Priority`）+ `ConversationLines.csv`（每行一句）到 `ConversationsById`。`PickEligibleConversation(NPCId)`=按 `OwnerNPCId` + 当前世界状态（RequiredTags 全在 / ForbiddenTags 全不在）取 `Priority` 最高的一段；`PlayNPCConversation` 拆句入队（说话人=会话 `Speaker`），把 `SetTagsOnEnd` 挂到**最后一句**上，该句播完经 Controller 既有 `SetWorldStateTag` 服务器路径写入（**谈完才写、接任务系统**）。同一 NPC 靠多段会话 + 标签/优先级随剧情说不同的话。对话**可反复触发**（无去重）。`FCSNarrativeQueuedLine` 存解析后的 `FText SpeakerText`（AIC 节拍/NPC 共用）；`RequestSetWorldStateTag` 为节拍已播标记与会话标签共用。**Dialogue-UI-A 加** `bIsConversation`（分流 AIC 字幕 vs 对话框）+ `TSoftObjectPtr<UTexture2D> Portrait`（会话句的 NPC 头像，入队时从 NPC actor 取）。
- **世界状态标签登记（约定命名，不做注册表校验）**：`Site.*`=地点/进度（`Site.FirstDescent`/`Site.ReturnedToS0`/`Site.FirstSearch`）；`Narrative.*`=叙事已播去重（`Narrative.Intro.Played`/`Narrative.Played.<BeatId>`）；`Talked.*`=对话进度（`Talked.<NPC>.<会话>`，如 `Talked.Varga.Intro`）；`Obj.*`/`CompleteObjective` 写入的目标标签；路线解锁用 `UnlockedRouteIds`。新增标签沿用命名分区并在此登记。
- **配音播放与同步（Narrative-Voice / Unit 65）**：纯表现层，按台词 key 推导资产 `/Game/Audio/VO/<key>`（节拍 `<BeatId>_<行序>`、会话 `<ConversationId>_<LineIndex>`），`StartNextLineIfIdle` 里 `FSoftObjectPath::TryLoad` 命中即播、字幕按 `USoundBase::GetDuration()` 计时，未命中降级纯文字（无音频不回归）。**AIC=2D 本机**（`PlaySound2D`，各端据复制状态自触发即同步，零新增 RPC）；**NPC=3D 多播**（导演→`ACSPlayerController::ServerPlayNarrativeVoice`→`ACSNarrativeNPC::MulticastPlayVoice`（`bReplicates=true`）全端 `PlaySoundAtLocation`，队友同步）。`FCSNarrativeQueuedLine` 携 `LineKey`+`VoiceSourceActor`。音频**生成**由外部 CosyVoice2 管线按同 key 产出（Unit 68，引擎在工程外）。打包需把 `/Game/Audio/VO` 加进 cook 目录。
- **后续**：NPC 分支选择/对话树（含玩家选项 + 手动推进/跳过，需把配音播放层改成可停的音频组件）；过场动画/Sequencer（Narrative-D）。（专用对话框 UI 已由 Dialogue-UI-A 落地。）

---

## 3. 数据所有权速查

| 数据 | 权威 | 复制/通知 | 存档归属 |
| --- | --- | --- | --- |
| 背包/装备 | `UCSInventoryComponent`（在 PlayerState） | RepNotify + `OnEquippedItemChanged` | `FCSPlayerSaveData`（个人） |
| 生存数值 | `UCSSurvivalStatsComponent` | RepNotify + `OnHealthChanged` | 个人 |
| 属性快照（负重/护甲/移速倍率等） | `UCSAttributeComponent`（在角色，Attribute-A/Armor-A） | RepNotify(`Snapshot`) + `OnAttributeSnapshotChanged`；服务端从背包(负重)+穿戴护甲(`ApplyEquipmentModifiers`)+(待接)Buff 重算；还提供静态减伤入口 `MitigateDamage(Target,Raw,DamageType)`（三处造伤汇点调用，目标无组件=不减伤） | 不存（派生量，从背包/穿戴/Buff 重算） |
| 世界进度/解锁 | `ACSGameState.WorldProgress` | RepNotify + `OnWorldProgressChanged` | `FCSWorldProgressSaveData`（世界共享） |
| 世界时钟 | `ACSGameState` | 复制浮点（待优化为本地推算） | 世界 |
| 合成进行态 | `UCSCraftingComponent`（在 Controller） | RepNotify | 不存 |
| 状态效果/Buff | `UCSStatusEffectComponent`（在玩家/敌人角色） | RepNotify(`ActiveEffects`) + `OnActiveBuffsChanged` | 暂不存（Buff-I 按 `SavePolicy` 收口） |
| 可替换外观/换装 | `UCSInventoryComponent`（在 PlayerState） | RepNotify(`WornAppearanceItemIds`) + `OnWornAppearanceChanged` → 角色外观组件 `RebuildFromWornList` | `FCSPlayerSaveData`（个人，跨重生/存档持久） |
| 世界 Actor 状态 | 各 World Actor | RepNotify | `FCSWorldActorSaveData`（按 `PersistentID`） |

---

## 4. 复制与事件约定（现状）

- 服务端权威：客户端只发输入/请求；状态改动在服务端（库存组件、生存组件、战斗组件、GameState、World Actor）。
- 玩家请求经**拥有者** `ACSPlayerController` 的 Server RPC，再调组件/GameState。客户端不直接对门/箱/资源点发 Server RPC。
- 持久状态用复制变量 + `RepNotify`；短时表现用 Multicast。
- 事件驱动优先：库存/装备/生命/世界进度/交互焦点均有委托或 RepNotify，UI 与角色表现订阅事件而非轮询（HUD 快照是当前例外）。

---

## 5. 数据/脚本生成链

- `Data/Source/*.csv` → `Scripts/GenerateGameData.py` → `Data/Generated/*` → `UCSItemDataSubsystem` 运行时读取。一对多关系用副表按 ID join（`Recipes`+`RecipeRequirements`、`BuffDefinitions`+`BuffAttributeModifiers`+`BuffPeriodicEffects`）；生成期做枚举白名单与引用完整性校验（含 `EnemyDefinitions` 的 `GrantedBuffIdsOnHit⊆BuffDefinitions`、`LootTableId⊆掉落表`、`LoseSightRadius>=SightRadius` 等）。源表顶部可带 `#CN`/`#DESC` 说明行，生成时剥离。单机敌人数值表为 `EnemyDefinitions.csv`（Enemy-Data-A）。
- 编辑器内容由 `Scripts/Create*.py` / `Configure*.py` 经 `UnrealEditor-Cmd -run=pythonscript` 生成（材质、物理材质、蓝图、动画 Notify、测试地图等）。
  - 注意：对**已被 CDO 引用的现有基材质**执行 `delete_all_material_expressions` 等图重建操作会在 commandlet 下触发 `!IsRooted()` 断言崩溃；调参应改用**材质实例**（`MI_*`，见 `CreateInteractOutlineInstance.py`）或在脚本里走「不存在才建图」分支。

---

## 6. 进行中的重构与已知待办

**UI 表现层 → UMG 代码生成迁移（2026-06-10 用户拍板的方向）**
- 终态：手写 Slate `SCS*` 逐屏淘汰，改用 Unit 24 的 HTML→WBP 生成器（`UI/Editor/CSWidgetAuthoringLibrary`，编辑器专用）产出 WBP + 运行时实例化。逻辑/数据/服务端权威全留 C++，只换表现层；颜色/字/皮走中央主题 `FCSUIStyle`（生成器灌入）。基准分辨率 1920×1080。
- ✅ Dialogue（Unit 25, 2026-06-10）：`WBP_Dialogue`/`UCSDialogueUserWidget` 接进运行时，替换 Slate `SCSDialogueWidget`（旧文件待 PIE 确认后删）。顺带修了"运行时模块 UFUNCTION 签名禁用编辑器专用反射类型"的潜伏编译坑（见 `UE_Gotchas` 3.4）。**待 PIE 验证**。
- ✅ HUD（Unit 26, 2026-06-10）：`WBP_HUD`/`UCSHUDUserWidget` 接进运行时，替换 Slate `SCSHUDWidget`（旧文件待 PIE 确认后删）。生成器加 `BuildHUDWBP`。**待 PIE 验证**。
- ✅ 主菜单 + 设置 + 进出存档（UI-Menu-A, 2026-06-13）：**独立菜单关卡** `Lvl_MainMenu`（GameMode 覆盖 `ACSMenuGameMode`/无 Pawn/`ACSMenuPlayerController`/UIOnly）+ `WBP_MainMenu`/`UCSMainMenuUserWidget` + `WBP_Settings`/`UCSSettingsUserWidget` + `UCSGameUserSettings`；`GameDefaultMap` 改指菜单图。生成器加 `BuildMainMenuWBP`/`BuildSettingsWBP`。新建档案→进 `Lvl_L0_C12`（不读档）、继续下潜→读存档 MapName+进图后服务端自动读档（有存档才可点）、设置→开设置屏、退出→QuitGame；菜单动效（淡入/扫描线/标题glitch/悬停，受减少动态控制）。坑：Interchange 贴图导入 + `LevelEditorSubsystem` 无头不可靠、块注释 `*/` 提前闭合、生成控件名撞非 BindWidget 同名 UPROPERTY（见 `UE_Gotchas` 3.8–3.11）。**待 PIE 验证**。
- 🔶 AIC 旁白字幕（UI-Narrative-A, 2026-06-14）：`WBP_NarrativeOverlay`/`UCSNarrativeOverlayUserWidget` 接进运行时替换纯 Slate 黑框 `SCSNarrativeOverlay`（最后一块未美化的手写 Slate UI）；生成器加 `BuildNarrativeOverlayWBP`（终端框 + cold 强调条 + 琥珀说话人 + 扫描线）；自驱轮询导演（与对话框互斥）；动效 = 入场淡入上滑 / 逐行解码揭示 / 扫描线漂移。runtime 整编 Succeeded，**编辑器目标 + WBP 待关编辑器**（新 UCLASS 反射）。旧 Slate 待 PIE 后删。**待 PIE。**
- **剩余 Slate 屏迁 UMG 路线（2026-06-14 立，workflow 规划）**：背包/纸娃娃/合成/GM/联机迁 UMG，按风险升序 6 单元：**①UI-UMG-Coop（已实现）→②UI-UMG-GMPanel→③UI-UMG-DragCore（UMG 拖拽地基 `UCSInventoryDragDropOp`/幽灵装饰/`UCSDragSlotReceiverWidget`，背包前必做）→④UI-UMG-InvShell（背包外壳+3 简单子面板，网格/纸娃娃先内嵌 Slate 脚手架）→⑤UI-UMG-Paperdoll（13 个 1D 槽=DragCore 首消费者）→⑥UI-UMG-Grid（塔科夫 2D 网格全 UMG，XL/高，放最后并删 `FCSInventoryGridDragDropOp`）**。混用 Slate+UMG 已是生产现状（UMG Z10-12 / Slate Z20 同视口）故内嵌脚手架安全；DragCore 一次做好供网格+纸娃娃共用。
  - 🔶 UI-UMG-Coop（2026-06-14）：`WBP_CoopSession`/`UCSCoopSessionUserWidget` 替换 Slate `SCSCoopSessionPanelWidget`；生成器 `BuildCoopSessionWBP`；控制器 `CreateCoopSessionPanel`/`RemoveCoopSessionPanel`/`ApplyMenuInputMode` coop 分支 + 成员换 UMG。功能 parity（状态条轮询平台子系统 + 5 按钮路由既有控制器方法）。双目标整编 Succeeded + WBP 落盘。**待 PIE（Listen Server+客户端）。**
- ✅ 通用弹窗/Toast 系统 + UI 层管理器（UI-Modal-A, 2026-06-14）：建 `UCSUILayerSubsystem`（`ULocalPlayerSubsystem`，唯一输入模式决策点 + 弹窗/Toast 宿主）+ 一类三态 `UCSModalUserWidget` + 自消失 `UCSToastUserWidget` + 类型头 `CSModalTypes.h`；生成器加 `BuildModalWBP`/`BuildToastLayerWBP`/`BuildToastWBP`（产物 `/Game/UI/Modal/`）。**首接线点=主菜单**（退出/覆盖确认窗 + 合作占位 Toast），降级判断为"弹窗是否真压栈成功"而非"子系统是否存在"。动效还原 HTML（弹窗 bootin 上滑淡入 / Toast toastin 整条包络）。经对抗式审查修 6 项（菜单降级 HIGH / ShowToast 返 bool / PC 跨世界校验 / RemoveModalDeferred 同步出栈延迟销毁 / 结果按模式过滤 / TextInput 确认钮同步）。**游戏关 `ACSPlayerController` 接管理器见下条；QtyInput/TextInput 业务调用方、Alert/Loading/Tooltip/ContextMenu 控件未做。用户 2026-06-14 PIE 通过 ✅。**
- ✅ 游戏关控制器接 UI 层管理器 + 服务器→客户端 Toast 通道（UI-Modal-B, 2026-06-14）：`ACSPlayerController::BeginPlay` 注册进 `UCSUILayerSubsystem`（恢复函数=`ApplyMenuInputMode`）+ 新增 `ClientShowToast(FText, ECSToastLevel)` Client RPC（缺失降级 `ShowDebugLog`），接 3 反馈点（丢关键物被拒 / 存档完成 / 读档完成）。**纯增量，ESC/Slate 面板逻辑未动**。runtime + 编辑器双目标整编 Succeeded（`ClientShowToast` 反射已重链）。把 Slate 面板 ESC 栈/`Toggle*Panel` 收口进管理器（需泛化成"UI 层栈"）、游戏内 Confirm 真实调用方留后续。**用户 2026-06-14 PIE 通过 ✅。**
- ✅ 游戏内暂停/系统菜单经管理器接入（UI-Modal-C, 2026-06-14）：管理器加 `ScreenStack`（全屏 UMG 层）+ `PushScreen`/`RemoveScreenDeferred`（幂等守卫）/`HasAnyLayerOpen`（模态逻辑不动）；新 `UCSPauseMenuUserWidget`（继续/设置/返回主菜单/退出，自处理 ESC + 入场动效 + 嵌套 Confirm，复用 `WBP_Settings`，`NativeDestruct` 统一清叠加设置屏）；生成器 `BuildPauseMenuWBP`→`WBP_PauseMenu`；`ACSPlayerController::CloseOpenPanels` ESC 无面板时 `OpenPauseMenu`。**暂停纯本地表现**（多人不暂停 sim）。双目标整编 Succeeded + WBP 落盘 + 对抗审查（修 2 项）。把 Slate 面板收口进 `ScreenStack`、单机真正暂停 sim 留后续。**用户 2026-06-14 PIE 通过 ✅。**
- 🔶 加载屏接入（Loading-A/B, 2026-06-16）：抽象基类 `UCSLoadingScreenUserWidget`（i18n `T(En,Zh)`/真进度驱动/壳动效/最短显示/自消）+ `UCSDescentLoadingScreenUserWidget`（电梯下行深度计）+ `UCSBootLoadingScreenUserWidget`（菜单→进游戏开机屏，经 `PushScreen`）。**真预加载**：`UCSAssetPreloadSubsystem` 加 `GetProgress()`(真 `FStreamableHandle::GetProgress`)+会话级预加载；开机屏读它、电梯屏到层前封顶 0.98 由服务器 `IsLevelLoaded&&Visible` 门控补满。电梯 `ACSStreamingElevatorStation` 改异步流送+`CompleteTravel` 双门控(防穿地)+`ClientShow/HideDescentTransition`+`SetTransitImmunity`(过场免伤经 `UCSAttributeComponent::MitigateDamage` 统一入口守卫，服务器权威)。`UCSGameUserSettings::UILanguage`(默认中文)。生成器 `BuildDescentLoadingWBP/BuildBootLoadingWBP`→`/Game/UI/Loading/WBP_{Descent,Boot}Loading`(复用 `T_MainMenu_BG`/`T_Emblem`)。双目标整编 Succeeded + 两 WBP 落盘。**待 PIE(Listen Server+客户端)。** Loading-C 撤离屏 + 声效(submix+音频资产+PA 人声)留后续。
- 🔶 加载屏质感整改（Loading-Fidelity, 2026-06-16）：根因=生成器只用纯色 `FSlateColorBrush`、画不出网页的渐变/辉光/扫描线。新建**首套 UI 材质**（`/Game/UI/Materials`，`Scripts/CreateUIMaterials.py`，被 `CreateLoadingWBP.py` 先 exec）：`M_UI_Vignette`/`M_UI_Scanline`/`M_UI_GradientFill`(+`MI_UI_GradWarm/Cold`)/`M_UI_Glow`，均 **MD_UI 半透明**、Custom HLSL + 参数节点。生成器改用 **`FSlateMaterialBrush`**（暗角层/满屏 CRT 扫描线/渐变进度/数字+徽记辉光背衬，`LoadUIMat` 缺失 null-safe 退纯色）。基类扫描线改驱动材质 `ScrollY`/`Flicker`（`GetDynamicMaterial`），新增绑定 `DepthGlow`/`EmblemGlow` 由子类脉冲 `Intensity`/设 `GlowColor`。开发开关 cvar `CS.UI.LoadingScreens`（`-1` 自动=PIE 跳开机屏 / `0` 全关 / `1` 全开）。双目标整编 Succeeded + 材质/WBP 重生成落盘。**待 PIE 验质感。** 这套材质所有代码生成 UMG 屏可复用。
- 🔶 深化游戏内模态/Toast（UI-Modal-D, 2026-06-14）：`ACSPlayerController::RequestDropItemInteractive` 给背包丢弃接 QtyInput 模态（关键物客户端先拦、多个选数量、走服务端权威丢弃；首个"模态叠 Slate 背包面板"消费）；`UCSCraftingComponent` 经 `ClientShowToast` 加合成完成/材料不足/产物放不下 Toast（显示名服务端 `ResolveCraftItemName` 查 `UCSItemDataSubsystem`）。**无反射变更**（可 Live Coding）。runtime 整编 Succeeded + 聚焦对抗审查无真实缺陷。更多事件 Toast（拾取/解锁，注意与 AIC 叙事重叠）、卖出/转移 QtyInput 留后续。**待 PIE。**
- ⬜ 其余屏（背包 `SCSInventoryPanelWidget`+`SCSBackpackGridWidget`+纸娃娃 `SCSPaperdollWidget` / GM `SCSGMPanelWidget` / 联机 `SCSCoopSessionPanelWidget` / AIC 字幕 `SCSNarrativeOverlay`）逐个生成 WBP + 接线替换。背包最复杂（网格拖放+纸娃娃），建议 CJK 字体地基先于背包屏补。换装独立面板 `SCSWardrobePanelWidget` 已退役删除（Unit 90 并入纸娃娃），不再在迁移清单里。
- ⬜ 主题接入 / CJK 字体 / 控制器面板开关样板统一：方向无关、各屏迁移时一并收口（中文游戏却用默认字体渲染不了中文，是迁移必须补的硬伤；`FCSUIStyle` 目前仅对话框在用，字号 token 未接）。`FCSUIStyle` 仍惰性初始化、未在模块 Startup/Shutdown 托管（退出可能一条注销警告）。

**Controller 瘦身 / 组件化（三步）**
- ✅ Inventory-A（2026-06-06）：背包/装备复制状态 + 逻辑从 `ACSPlayerState` 抽到 `UCSInventoryComponent`；PlayerState 退化为宿主。
- ✅ Crafting-A（2026-06-06）：`CraftingState` + 合成校验/计时/产出 + 工作台/配方查询从 `ACSPlayerController` 抽到 `UCSCraftingComponent`；Controller 仅转发。
- ⬜ Save-B：把 `SaveWorldOnServer`/`LoadWorldOnServer` 迁入 `UCSWorldSaveSubsystem`（`UWorldSubsystem`），Controller exec 仅转发。

**性能/正确性待办**（2026-06-09 代码审查批次，详见 `Docs/DevelopmentUnits.md` 同日摘要）
- ✅ HUD 快照缓存（2026-06-09）：`SCSHUDWidget` / `SCSInventoryPanelWidget` 重写 `SWidget::Tick` 每帧只建一次 `BuildHUDSnapshot()` 缓存，getter 读缓存；`SCSGMPanelWidget` 的每帧 `TActorIterator` 改 0.5s 限频缓存。（`BuildHUDSnapshot` 本身仍含配方表扫描，HUD 路径剔除配方扫描为可选后续。）
- ✅ 角色相机（2026-06-09）：`UpdateCameraHeight` 加 `IsLocallyControlled()` 守卫，相机插值不再在服务端/模拟代理空跑。
- ✅ 电梯空闲 Tick（2026-06-09）：`bStartWithTickEnabled=false`，仅 `StartTravel→CompleteTravel` 期间于服务端开 Tick。
- ✅ 背包 owner-only（2026-06-09）：`InventoryStacks`/`EquipmentHotbar`/`SelectedEquipmentSlotIndex`/`WeaponMagazines`/`WeaponUpgrades` 改 `COND_OwnerOnly`（`EquippedItemId`/`WornAppearanceItemIds` 保持全端）。正式格子背包仍可考虑 `FFastArraySerializer`。
- ✅ 动画线程射线（2026-06-09）：`UCSPlayerAnimInstance` 不再在动画更新线程发世界射线，改读角色游戏线程 Tick 缓存的瞄准偏移（`GetCachedMuzzleAimOffset`）。
- ✅ 治疗/Buff 正确性（2026-06-09）：`ApplyHealthDelta` 治疗加 `IsAlive()` 守卫（禁复活）；Buff 剩余时间改绝对到期时刻 `ServerFinishSeconds`（UI 倒计时不再卡住）。
- ✅ 静态世界 Actor 休眠（2026-06-09）：`ACSResourceNode`/`ACSLootContainer`/`ACSWorkbenchStation` 设 `DORM_Initial`，状态写入处补齐 `ForceNetUpdate`（自动 `FlushNetDormancy`）。`ACSWorldItemActor`（动态短命）未纳入。
- ⬜ 世界时钟：`ServerWorldTime` 仍持续复制；改 epoch+DayLength 客户端本地推算属重构、有 desync 风险，留专门单元。
- ⬜ 属性快照：`UCSAttributeComponent::Snapshot` 是否改 `COND_OwnerOnly` 待核实（`MoveSpeedScalar` 可能被模拟代理移动预测读取）。
- ⬜ 上述 owner-only / 休眠批次为网络相关改动，待用户 Listen Server + 1 客户端 PIE 验证后转 ✅完成。

**全模块代码审查批次（2026-06-16，详见 `Docs/Development/CodeReview_2026-06-16.md`；可视化见 `Docs/SystemDesign/程序框架图.html`）**
- 六块并行静态审查，全工程屎山度约 3.9/5。核实：Tick 按需开关（门/电梯/生存组件）、查询哈希化、动画线程只读缓存、叙事 diff 无深拷贝**均已真做到**。A（真问题）/B（性能）/C（维护债）三批用户 2026-06-16 决定全做。
- ✅ 已落地（runtime + editor 双目标整编 Succeeded，**待 PIE 验收**，A1/A2 网络项必须 Listen Server+客户端）：
  - A1 GM/世界状态 13 个 Server RPC 加 `ACSPlayerController::IsGMActionAuthorized()` 房主闸（shipping no-op，防客户端作弊；外观 3 RPC 故意不闸）；A2 loot `RemainingItems` 去复制改 `ClientOpenLootContainer` 定向下发 + HUD 读本地副本（搜刮联机保密）；A3 debug draw 默认 false。
  - B1 melee `TInlineAllocator`+武器/QueryParams 窗口缓存；B2 枪口闪光复用常驻点光源；B3 单发枪快路径；B4 `UCSItemDataSubsystem::FindItemDefinition/GetItemWeight` 只读 API；B5 `RecipesByStation` 缓存；B6 网格增量 occupancy（O(n²)→O(n·W·H)）；B7 HUD `NativeTick` 哈希门控；B8 交互焦点缓冲复用+锥判定传参；B9 AI bank BeginPlay 压实；B10 持握姿态跳过专用服务器；B11 脚步组件缓存。
  - C1 Controller 收口（`GetOwningInventory()` + `CS_DISPATCH_TO_SERVER` 宏 + `CreateViewportWidget`，不对称组保留）；C2 删 5 对退役 Slate；C3 敌人两处掉落改共享 `RollLootTable`；C4 新建 `ACSInteractableActorBase`（抽 `SetFocused`/`IsInUseRange`，**只抽行为不挪 UPROPERTY/组件**；消 C7 死代码）；C8 `MaxLootContainerUseDistance` 单一常量；C9 三元加括号。
- ⬜ 故意未做/留后续：C3 容器侧种子化 `FRandomStream`（需 `RollLootTable(Id, FRandomStream&)` 重载）；A2 敌人尸体 `CorpseLoot` 仍全场复制（同法待修）；A1 外观 3 RPC；C5 拆超长函数 / C6 CSV 解析器抽共享 / `IsHeadHit`·`IsHeadBone` 去重 / D1–D8（EndPlay 清 Timer、Server RPC `WithValidation`、loot `bSearchClaimed` 锁不一致 等）。

**工程/流程待办**
- 输入仍为 Legacy `BindAxis`/`BindAction`（`EnhancedInput` 已链但未用），待专门输入单元迁移。
- 源码管理：尚未配置 Git LFS（`AGENTS.md` 要求），二进制资产以原始 blob 入库；待用户决定时机迁移。
- `.uproject` 启用了 `ModelContextProtocol`/`AIAssistant`/`VibeUE` 等开发/AI 工具插件，打包前需确认为 Editor-only 或排除。
