首页 / 周报 / 2026-W38

2026-W38 周报

覆盖 2026/09/14(周一)– 2026/09/20(周日)。 四大类固定顺序:1 需求 · 2 Bug · 3 经验总结 · 4 问题复盘,大类下面直接 1. 2. 3. 排。

周期09/14 – 09/20
需求3 个
Bug5 条
经验总结1 条
问题复盘0 条
分支zhaozibo_071

要直接交周报?跳到 §5 飞书周报(一键复制)。

0. 本周速览

大类#一句话
需求 1 【主线】传送点模块梳理(解锁状态与场景表现)
2 【主线】传送加载规则:统一 Loading / 黑屏 / 无缝的遮挡分工,并把黑屏入口收口到单一判定
3 【主线】传送 V2 Seq2 → Seq3 衔接梳理(V2 表现 · 切场阶段与实测耗时)
Bug 1 【主线】重新进图后 #OpenDebugGameEnv 打开的 GM 调试信息面板被关闭且不再恢复
2 【主线】副本结算时,特写 / 全家福里的结算角色模型全部不显示
3 【主线】副本 20100301 典狱长作战区域坠落重生有概率回到副本入口,而不是就近落点
4 【主线】传送快结束时用脱离卡死自杀,传送结束后角色身上的淡变材质残留、表现异常
5 【主线】传送过场收尾时重置动画模式,把角色链接的动画层清掉,落地后三段跳表现异常
经验总结 1 传送遮挡链路上三处会漏遮挡的口子:判定不同源 / 命令不建界面 / 存在无遮挡档位

1需求3 个

本周做的新功能 / 改造 / 配置支持。需求卡字段单元:模块 / 需求 / 实现细节 / 结果 / 验收。

1. 【主线】 传送点模块梳理(解锁状态与场景表现)
模块:传送点 / SceneItemController · EtTeleportComponent · TeleportMarkItem
需求
场景里可交互的传送水晶 / 传送门,其解锁状态与场景表现分散在三处:SceneItemController 持有解锁数据、EtTeleportComponent 负责场景表现、TeleportMarkItem 负责地图标记。目标是理清三者职责与数据流向,明确「解锁状态不能用服务器实体 State(全服共用)」这条核心约束,沉淀成可复用的模块文档。
实现细节
按三个可分离关注点组织:解锁数据由 SceneItemController.UnlockTeleportIdSet 唯一权威持有;场景表现由 EtTeleportComponent 按解锁与否切本地 GameplayTag(关卡.Common.数字状态.状态2 / 状态1);地图标记由 TeleportMarkItem 按地图类型选资源并实时转发解锁态。
解锁数据梳理出三条来源:配置表 Teleporter.DefaultUnlock、服务器地图信息 LevelMapModel.AddMapInfo().unlockTeleportList、增量补解锁 LevelTeleportAssistant;三者统一收敛到 UnlockTeleport 入口,先 has 判重保证幂等,避免重复 Emit(OnTeleportUnlocked) 导致表现层重复刷新。
表现层在 OnStart 订阅事件并立刻刷一次初始态(实体可能在解锁事件之后才创建),切状态用 ChangeLevelTagByClient(纯本地,不同步服务器);标记侧不缓存解锁态,用 getter 实时查权威持有者。
另梳理了发起传送的唯一调用点 TeleportController 的 RequestTeleportByEntityPrefabDataId、协议 23707 WorldTeleportCSReq(IsTeleporting 无条件复位),以及最近传送点检索:只遍历已解锁集合、过滤同 mapPath、按 Size2D() 取 2D 距离。
结果
产出《传送点》模块文档,覆盖模块定位、配置表 Teleporter(4 个字段 + PrefabDataId ↔ TeleportId 双向映射)、解锁三条来源与幂等、EtTeleportComponent 表现层、发起传送、TeleportMarkItem、最近传送点检索,以及「加新传送点 / 改解锁表现 / 排查该亮没亮」三类常见修改场景。
验收
文档已上线并登记站内导航(传送点);全站内链 / 锚点 / 搜索索引校验通过。
起止
模块文档 2026/09/07(W37)完成,2026/09/11(W37)登记入库
2. 【主线】 传送加载规则:统一 Loading / 黑屏 / 无缝的遮挡分工,并把黑屏入口收口到单一判定
模块:传送 / TeleportPresentation · TeleportController · BlackScreenController · BaseMoveSyncComponent · SceneLoadingTeleportCmd
需求
需求规定「所有由传送点、传送门发起的传送都要有 Loading 界面」。落到代码上,真正要解决的是遮挡手段的互斥与分工:黑屏挂在 ELayerType.CG(1<<10)层,系统 Loading 挂在 Loading(1<<8)层,层级上黑屏恒高于 Loading——两者并存时玩家只看得到黑屏,连加载百分比都被盖住。所以不能让黑屏和系统 Loading 各按各的节奏自行开合,必须有一条统一判定决定这一次传送由谁遮丑。
实现细节
判定收口:TeleportPresentation.ShouldUseUiLoadingInsteadOfBlackScreen(enterShowType?)——淡入淡出表现(TeleportShowType.TELEPORT_SHOW_TYPE_FADE_IN_OUT)返回 false(它需要黑屏当淡出起点,保持原行为),其余返回 ShouldShowUiLoadingForSequence()(两个静态开关,默认配置下所有主动传送链路由系统 Loading 承担遮挡)。一条硬约束写进了注释:不能依赖 TeleportModel.IsTeleporting——传送门走「同步瞬移 + ServerMove」链路,不经过 InitData(),该字段在传送门分支上恒为 false,拿它做前置会照旧叠出黑屏。
三处黑屏入口收敛:OpenSeamlessTransitionMask()(同图无缝的入口瞬时遮罩)、OpenBlackView()(跨图传输入口)、BaseMoveSyncComponent 的同步瞬移(自持 tag [传送-黑屏-同步瞬移]);三条路都命中时只打日志跳过,不再 Add 黑屏。
无缝升格 uiload:表现被判为无缝、但本次位移超过 SeamlessPromoteDistanceMeters = 100(米)且目标地形流送未完成时,无缝依赖的「目的地已就绪」前提不成立,ShouldPromoteSeamlessToUiLoading() 返回真,由 PromoteSeamlessToUiLoading() 串行 PromotedUiLoadingTask 链开系统 Loading,并撤掉自己的无缝遮罩(黑屏还盖着的话,升格出的 uiload 与百分比同样看不到);收尾对称走 ClosePromotedUiLoading(),开关任务用 AwaitWithTimeout 包一层,避免单次异常把串行链永久卡住。
全局兜底:BlackScreenController.AddBlackScreenAsync 里查 LoadingModel.IsLoadingView,系统 Loading 界面正在显示时直接跳过黑屏并打日志。以 IsLoadingView 而不是 IsLoading 为准是有意的——后者只发 Start/FinishLoadingState,不一定有界面。
现场排障用的三个 GM、以及按日志区分当前走了哪种遮挡的对照表,见《传送 · 技术方案文档》§6.3 黑屏与遮罩。
结果
把「这一次传送由谁遮丑」从一个散落在各链路的隐含约定,变成单一入口 + 单一判定:黑屏与系统 Loading 互斥,位移超阈值的无缝传送会升格成 uiload 兜住流送窗口,判定之外的调用点由 BlackScreenController 按 IsLoadingView 兜底。同步把判定链、升格闸门与三处黑屏入口登记进《传送 · 技术方案文档》§6.3 黑屏与遮罩。
验收
两条口径:互斥——同一时刻只出一种遮挡,黑屏不再盖住 uiload 与加载百分比;不漏遮挡——表现判为无缝但位移超阈值、目标地形未流送完时,必须有 uiload 兜住。代码侧三处黑屏入口已统一走 ShouldUseUiLoadingInsteadOfBlackScreen;现场可用 TeleportSysLoadingToggle / TeleportNoUiLoadingToggle 切换两种配置各跑一遍主动传送与传送门复验。
起止
梳理并完成 2026/09/14–09/16(W38)
3. 【主线】 传送 V2 Seq2 → Seq3 衔接梳理(V2 表现 · 切场阶段与实测耗时)
模块:传送 V2 / TeleportV2Flow · Seq2 循环退出 · 切场分段 · Seq3 绑定
需求
Seq2 是可循环的空中等待段、Seq3 是降落段,两者之间的切场由多次跨帧等待拼成,但这些等待散在代码里没有统一时间轴。一旦出现切场穿帮、角色缺失或看不到落地表现,只能临时加日志逐条试,无法判断某一段耗时是否正常。需要把这一段的阶段顺序与实测耗时固化成可对照的时间轴。
实现细节
把「Seq2 循环退出 → 玩家可操作」之间的链路拆成 7 个阶段:Seq2 循环等待 → 退出 loading 并暂停 Seq2 → Seq3 资产就绪 → 演员接管 → 换装等待(跨帧)→ binding 编译等待(跨帧)→ Seq3 播放至 OnFinished。
其中退出 loading 与暂停 Seq2 同帧完成,资产就绪与演员接管同帧完成;换装等待与 binding 编译等待是仅有的两处刻意跨帧等待,两者串联约 500 ms,是 Seq3 起播前的固定成本。
落地收尾单独拉出:OnFinished 后不等待降落动画播完,而是立刻走还原链并 OnEnd,整段只有 10 ms 量级,且相机在此前约 3 s 就已交回战斗相机。
结果
产出《传送 › V2 表现 › Seq2 → Seq3 衔接问题》专栏页:含三类现象定位、阶段逻辑图(7 阶段)、两组实测耗时对照(换装 203 ms、binding 编译 312 ms、落地收尾 ≈11 ms / ≈10 ms),以及落地收尾段的原始日志时序。专栏按「传送 → V2 表现 → Seq2 → Seq3 衔接问题」的层级挂进站点导航,排查表现问题时可直接把实测值与本页对照,先判断是「切场等待异常」还是「落地段本来就没有时间窗」。
验收
专栏页已上线并按层级登记站内导航(传送 › V2 表现 › Seq2 → Seq3 衔接问题);全站内链 / 锚点 / 搜索索引校验通过。
起止
梳理并完成 2026/09/16(W38)

2Bug5 条

本周处理的线上 / QA(质量保证)问题。

1. 【主线】 重新进图后 #OpenDebugGameEnv 打开的 GM 调试信息面板被关闭且不再恢复
模块:Loading / LoadingController · 传送 / Seq3 换装遮罩 · Debug / GameEnvDebugView ·定责:客户端 已修复
问题描述详细版本
GM 命令 #OpenDebugGameEnv 打开调试信息面板(帧率、耗时等),随后做一次传送(重新进图),面板被关掉且之后不再恢复;同源受影响的还有 TimeInfoDebugView。
日志核对:面板恢复发生在 EndEnterLevel(15:50:22.683),之后 EndClientLoading 完成(15:50:25.901);紧接着落地阶段 Seq3 换装遮罩又打开一次系统 Loading(15:50:25.902),8 毫秒后面板被销毁(15:50:25.910)——此后再没有 EndEnterLevel,面板再也回不来。
原因
LoadingController.GameModeOpenLoading() 在打开系统 Loading 前会调 UiInterfaceHelper.Clear() 做一次全量 UI 清理(只放行常驻界面与白名单里的 LoadingView / MView)。首段 Loading 靠 NormalLoadingViewGlobalData.FirstProgressPromise 做重入保护,重复调用直接返回、不清理;但传送落地阶段的 Seq3 换装遮罩是第二次打开 Loading,此时首段的 Promise 已置空,重入判断失效,于是又执行了一遍全量清理——而这时新场景的 UI 已经恢复完毕(EndEnterLevel 已经走过),清掉的正是刚恢复的 GameEnvDebugView / TimeInfoDebugView。
正常 HUD 没暴露这个问题,是因为它们都是常驻界面,走 UiConfig.ShouldKeepWhileClearScene() 的 IsPermanent 分支被豁免;调试面板是临时界面,不在豁免范围。
解决方案
给 GameModeOpenLoading 加 keepExistingUi 开关(默认 false,保持原有语义),只让 Seq3 换装遮罩这一处传 true:把 UiInterfaceHelper.Clear() 包成 if (!keepExistingUi) { UiInterfaceHelper.Clear(); },调用侧改为 ControllerHolder.LoadingController.GameModeOpenLoading(undefined, true)。
依据:Seq3 这次清理发生在新场景 UI 已恢复之后,清掉的本就是本轮新开的界面,语义上不该清;而且「不清 UI 也能打开 Loading」代码里已有先例(首段未完成时的早返回分支同样不执行清理)。其余 5 处 GameModeOpenLoading() 调用点语义不变。
验证:#OpenDebugGameEnv 开面板 → 同图传送,面板保持打开;同时回归传送落地后 HUD / 主界面表现正常。
总结
「打开 Loading」和「清空场景 UI」原本耦合在同一个方法里。同一条流程里第二次打开 Loading 时,重入保护已经失效,这次清理就会打到本轮刚恢复的界面上。判断一处 Clear() 该不该执行,看的是它清的是「上一轮残留」还是「本轮新开」。
起止
提出并修复 2026/09/11
2. 【主线】 b1271064 副本结算时,特写 / 全家福里的结算角色模型全部不显示
模块:副本结算 / DungeonSettlementController · 技能序列换装 / SkillSequenceAvatarBindingSession · 换装队列 / AvatarJobController ·定责:客户端 已定责 待改
问题描述详细版本
线上单 b1271064:副本结算进入特写(Seq2)与全家福(Seq3)时,结算角色的模型不显示。
日志核对:结算时 4 个角色的 BindActor 都正常返回(851287 / 851290 / 851294 / 851298),但整个结算窗口内 0 条「换装成功!」;21:36:08 结算卸载时出现 4 条「换装系统衣服选择被强制中断」(852783–852786);结算关闭后(864973)「换装成功!」立刻恢复。即:结算期间换装任务根本没被执行,不是模型创建成功却没显示。
原因
毒点不在结算侧,在战斗期的技能序列换装。SkillSequenceAvatarBindingSession.TryAutoPlayPreparedSequence 里读 this.SeqActor.LevelSequence.AssetPath.FullPathString,而这个 Actor 属于 additive camera prepared sequence——序列由 KuroCustomAdditiveCameraRenderSubSystem 持有,Actor 本身就不挂 LevelSequence,于是抛 Cannot read properties of undefined (reading 'AssetPath')(日志 569105 行 GlobalException)。
异常发生在资源加载回调栈里,被 ResourceSystem.TryCallback 的 try/catch 吞掉(日志只留「资源加载回调方法执行异常」),但副作用是致命的:AvatarClothJob.OnStepCompletedCallBack 里先调外部回调、后调 ControllerHolder.AvatarJobController.OnJobCompletedCallBack(this);外部回调一抛异常,后面那步就不再执行,job 不会被移出 ExecutingJobs。
AvatarJobController.MaxJobCount = 4,4 次异常(21:29:12 / 21:30:15 / 21:31:45 / 21:31:55)正好把 4 个槽位漏光,此后 availableCount === 0 直接 return,所有换装 Job 全被堵死——结算换装走的是同一条队列,于是模型不显示。
解决方案
两处上游改(本单根因,已给出改法、待落地):
① 异常源 SkillSequenceAvatarBindingSession 读序列路径时给 LevelSequence 兜底(去掉直接插值,或退化成 ?? 'PreparedSequence'),不再让 prepared sequence 的 Actor 走到这条取值上。
② 止损 AvatarJobController 的换装步骤回调里把外部回调包 try/catch,保证 OnJobCompletedCallBack 无论如何都执行——外部回调再抛异常也不会让 job 出不了队。
结算侧本单改动(changelist 812994):删掉已废弃的 Seq1 Tween 推镜整段死代码与 Seq3 资源依赖(现在结算只剩 Seq2 特写 + Seq3 程序化站位两段),并对 DungeonSettlementController 做了 4 处加固(见「顺带发现」)。
验证:GetDiagnostics 无报错,npx eslint 0 error。
总结
换装走的是全局队列(AvatarJobController,MaxJobCount = 4),而槽位是在「外部回调返回之后」才释放的。任何在换装回调里抛出的异常都会让这个 job 出不了队,槽位只出不进,4 次之后整条队列对所有模块堵死。
表现层看到的是「某个不相关模块的模型不显示」——所以排查时不要盯报障的那个界面,先看该模块的「换装成功!」日志有没有;没有就顺着时间戳往上游找第一个异常,那里才是真正被卡住的地方。
起止
提出 2026/09/10(周包跑测 W37)· 定位并修复 2026/09/14(W38)
环境
线上单 b1271064 · 日志 20260910 周包跑测(7.1)· 客户端 changelist 812994
顺带发现
审结算侧代码时挖出 4 个同型隐患(现象和复现路径都不同,未单独立条):
① 取播放器无兜底 + 非空断言 GetSequencePlayer() 不保证及时返回对象(KURO 因此额外保留了 SequencePlayer 属性),原代码直接 this.LevelSequencePlayer2!.OnFinished.Add(...);一旦为 undefined 就在资源回调栈内抛异常并被吞掉,finishOnce 不执行、界面卡在打开特写的黑屏,只能等 15s 保底超时。已改成 GetSequencePlayer() ?? SequencePlayer 判有效,拿不到就跳过监听并打 Warn。
② 结构体直改不生效 PlaybackSettings 是结构体,原代码 player.PlaybackSettings.bPauseAtEnd = true 改字段等于没改,必须读-改-写回。
③ 静默的错机位 Seq2 相机绑定 DungeonSettlementCamera2 找不到时完全没有日志,流程却照样把 Seq2Ready 置 true——特写会播、机位却是错的。已补 Warn。(有意不动语义:若因此不播 Seq2,OnFinished 不触发,非 GM 侧只能等 UI 60s 保底才推进。)
④ 静默消失的角色 UiRoleActorProxy.LoadAvatar 在「正在加载 / 无外观配置」时会提前回调且不创建 Actor,而该类各接口都是空安全、不抛错,于是这个角色会静默从全家福里消失。已补 Error 日志。
另:SwitchCamera3 是三条相机路径里唯一没有告警分支的(本次未改)。
3. 【主线】 副本 20100301 典狱长作战区域坠落重生有概率回到副本入口,而不是就近落点
模块:坠落重生 / FallingRebornController · 关卡 / 副本 20100301 ·定责:关卡 已修复
问题描述详细版本
副本 20100301 典狱长作战区域,玩家坠落(掉出地形或触发 Z 轴保底)后执行坠落重生,有概率被送回副本入口(即关卡出生点 LevelBornPosition),而不是在掉落点就近重生。
概率性来自前置条件:只有玩家在掉落前于带 BP_UpdateFallingRebornArea_C 范围盒的区域里站立过,坠落重生点才会被刷到该处。
日志核对:按 [坠落重生] 前缀过滤,出现「流送后的地形不安全,重生到关卡出生点」即命中本现象。
原因
坠落重生的落点有两个来源,最终都指向 g.关卡.xlsx 的 LevelBornPosition(副本里就是入口):
① 记录点没有被刷新 —— FallingRebornLocation 只在有缝进关卡时初始化为玩家进本落点(副本入口);此后仅当玩家走进 BP_UpdateFallingRebornArea_C 范围盒、由 QuickUpdateFallingReborn / SlowUpdateFallingReborn 驱动 UpdateFallingReborn 才会更新(且要求 3×3 射线全部命中 WorldStatic、与上次记录点位移超过 352)。典狱长作战区域若没有覆盖范围盒,玩家整段战斗期间记录点一直停在进本位置。
② 二次校验判定不安全 —— 即使记录点正确,CalculateSafeRebornLocation 向下探 SafeDepth=2000 未命中时(地面非 WorldStatic、或地形尚未流送完)返回 LevelBornPosition;流送后复检仍不安全,就下一帧直接流送到关卡出生点。
两条叠加解释「有概率」:进本后走过范围盒 → 落点正常;直奔典狱长作战区域开打 → 落点仍是副本入口。
解决方案
关卡侧:在典狱长作战区域(含通往典狱长房的位移路径)补 BP_UpdateFallingRebornArea_C 范围盒,让玩家在该区域内站立时把坠落重生点刷到就近安全地面;跳跃 / 高低差段落按需挂 LookAtLocation 指定重生朝向。
客户端链路本次不改:UpdateFallingReborn 的位移阈值(352)与 3×3 射线步长(水平 132 / 垂直 176)已覆盖该区域,缺的是范围盒把玩家「推进检查」。
验证:进本后直接跑向典狱长作战区域,坠落重生落在就近地面;战斗区域内出现「[坠落重生] 设置重生点。站立在安全地面设置重生点」,且不再出现「流送后的地形不安全,重生到关卡出生点」。
总结
坠落重生的记录点是靠范围盒推进的:没有任何 BP_UpdateFallingRebornArea_C 的区域,记录点不会更新,会一直停在进本落点。关卡里新增「大范围战斗 / 跳跃」区域时,摆范围盒是坠落重生落点正确的前置条件。
再收到「重生回到副本入口」的反馈,先查该区域有没有范围盒,再按 [坠落重生] 日志看是记录点没更新还是二次校验判了不安全。
起止
提出并修复 2026/09/14(W38)
环境
副本 20100301 · 典狱长作战区域
4. 【主线】 传送快结束时用脱离卡死自杀,传送结束后角色身上的淡变材质残留、表现异常
模块:传送 / TeleportTransitionFlow · TeleportPresentation · TeleportController ·定责:客户端 已修复
问题描述详细版本
走淡入淡出表现的传送(enterShowType = TELEPORT_SHOW_TYPE_FADE_IN_OUT),在到达阶段的淡入环节被死亡打断后表现异常:传送流程本身收尾正常(黑屏关掉、Loading 关掉、输入恢复),但玩家角色身上仍挂着淡变材质,角色保持隐形 / 半透明,直到下一次传送才自愈。
复现路径:任意走淡入淡出的传送,在淡入阶段(材质挂上后的 800ms 窗口内)用脱离卡死自杀。脱离卡死发的是 WorldSuicideCSReq,即直接自杀,链路为 CharOnDead → TeleportController.OnLocalPlayerDead → TransitionFlow.OnEnd()。
原因
淡入阶段挂上的材质只有淡入分支自己会摘,而这条分支被代次机制提前作废了:
① 先挂材质、后等时长 —— PlayTeleportFadeInWithMask() 内先 AttachFadeMaterial 把淡变材质挂到玩家身上,再 await onAttached()(关黑屏 + 关 Loading),然后等 TeleportFadeDurationMs = 800。
② 窗口内死亡中断 —— 这 800ms 内死亡打断进来,OnEnd() 首行就把 FlowGeneration += 1,当前协程的代次随之作废。
③ 清理代码被早 return 跳过 —— 800ms 结束后 if (!this.IsCurrentFlow(generation)) return; 命中,紧跟其后的 this.Presentation.StopTeleportFade() 不会执行。
④ 兜底也没有 —— OnEnd() 的收尾分支、RecoverPresentationFromTransitionFailure()、Presentation.Clear() 都不摘淡变材质,于是 FadeMaterialHandle 一直残留。
解决方案
在 OnEnd() 的 !KeepFadeInOutOnEnd 分支里补一次 this.Presentation.StopTeleportFade(),作为流程收尾的统一兜底。
两个前提让它安全:StopTeleportFade() 本身幂等(FadeMaterialHandle 为 0 时直接返回),正常路径下淡入分支已经摘过,这里再摘一次无副作用;KeepFadeInOutOnEnd 为 true 表示「同图淡入淡出刚起步,OnEnd() 只是阶段结束、淡出还要继续」,此时不清理,避免误杀正在播的淡出。
验证:走淡入淡出传送,在 800ms 淡入窗口内自杀,传送结束后角色表现正常,不再残留材质。
总结
传送里凡「先挂表现、再 await、再自己摘」的写法,都要考虑 await 返回时代次已被改掉、清理代码走不到的情况。清理动作应该由流程收尾(OnEnd())兜底,而不是只交给发起方——发起方一旦被 IsCurrentFlow 早 return 拦下,就再没人清。
淡变材质挂在玩家 Actor 上,表现就是角色隐形 / 半透明,容易被误判成角色模型或渲染问题;复现路径里带「传送途中死亡 / 脱离卡死」时,先查这类流程收尾有没有跑完。
起止
提出并修复 2026/09/14(W38)
环境
跨图传送 · TELEPORT_SHOW_TYPE_FADE_IN_OUT
顺带发现
复核这条时挖出的同源隐患(现象和复现路径都不同,未单独立条):
① 中断判定覆盖不到淡入淡出尾部 TeleportModel.IsTeleporting 只由 InitData() 置位、ClearData() 复位,而淡入淡出在 OnFadeInOutLevelInternal() 一开始就 OnEnd()(内部含 ClearData()),流送与淡入阶段该标记已经是 false;OnLocalPlayerDead 正是靠它判断「是否正在传送」,在流送 / 淡入阶段死亡会直接早 return,连 OnEnd() 的兜底都走不到。
② 失败兜底清理不全 RecoverPresentationFromTransitionFailure() 不摘淡变材质、不关 OpenUiLoadingForSeq3 打开的系统 Loading、不复位 IsStreamingLevel。
③ OnEnd() 职责混用 既当「某个阶段结束」又当「整体收尾」,KeepFadeInOutOnEnd 这个标志就是为区分两种语义打的补丁。
5. 【主线】 传送过场收尾时重置动画模式,把角色链接的动画层清掉,落地后三段跳表现异常
模块:传送 / TeleportSeq3Binder · 关卡序列收尾 ·定责:客户端 已修复
问题描述详细版本
走传送 V2 的过场表现(Seq3 落地段)后,角色落地后三种跳的表现异常——跳跃本身能触发,但该走的那套链接动画层没有生效。
日志核对到的情况很有误导性:现场 AnimationMode 是 0(AnimationBlueprint,看着正常)、AnimScriptInstance 存在,三个链接层(三段跳 / 骨骼后处理 / 坐姿层)在实例清单里都还在。也就是「模式对、实例在、层都在」,但表现就是不对。
原因
序列收尾时业务侧多写了一次动画模式复位,把链接动画层注销掉了:
① 序列是「借走」骨骼动画的输出权 —— 序列驱动骨骼动画时,会切到 AnimationCustomMode(自定义模式),注入 UAnimSequencerInstance 顶掉原来的 AnimScriptInstance,原 AnimBP 不再参与姿态输出。播放期间角色身上那套 AnimBP(含链接动画层)是离线的:只是被替换,不是被关闭。
② 收尾的复位是重复且破坏性的 —— TeleportSeq3Binder 在归还 Mesh 相对变换的同一段里,又调了一次 SetAnimationMode(AnimationBlueprint) 想「把模式还回去」。这个接口第二个参数 bForceInitAnimScriptInstance 默认为 true,含义是即使新模式与当前模式相同,也要强制重新初始化动画实例——同值调用同样会真的重建实例、重置链接动画实例,对由动画图表创建的层调用 UninitializeAnimation()。
③ 层被注销后没人再补挂 —— 层对象还在(所以清单里三个层都「在」),但已不再产出姿态。Locomotion 组件只在「动画层未挂载」标志为 false 时才尝试补挂,而该标志在这次注销后没有被置回,补挂逻辑认为已经挂好,于是不补。
于是「层还在」被当成了「层在工作」,异常一直留到下一次相关重置。
解决方案
归还交给序列本身,业务侧不插手:
① 删掉收尾处手写的 SetAnimationMode 及其紧随的动画重初始化调用。
② 把 Seq3 动画节的「完成时」设为恢复状态(RestoreState)——引擎会用在接管阶段存下的姿态快照把姿态还回去,链接动画层不受影响。
两件事必须成对,实测三态:保留两行 + 保持状态 = 原 bug;删掉两行 + 保持状态 = 跳跃没有动作(没人还姿态);删掉两行 + 恢复状态 = 表现正常。
注意改播放设置(FinishCompletionStateOverride)替代不了动画节的完成时:它只作用于序列结束时仍激活的 section,在序列结束前就播完的 section 由自身设置决定;而且传送流程复制播放设置时已经把它显式置为 None,这个覆盖本来就是关着的。
总结
序列驱动的角色表现,收尾归还的职责在序列自己身上,业务侧不要手写动画模式复位——SetAnimationMode 第二个参数默认强制初始化,同值调用也会重建实例并注销链接动画层。
要归还姿态,改序列动画节的「完成时」为「恢复状态」,而不是改播放设置里的完成状态覆盖(它只管序列结束时仍激活的 section)。
排查提示:日志里「链接层都在」不等于层在工作,被注销的层仍会留在清单里;反过来「模式是对的、实例也在」同样不能证明动画链路正常。
起止
提出并修复 2026/09/17(W38)
环境
传送 V2 表现 · Seq3 落地段

3经验总结1 条

可复用的模块知识 / 工具用法。这一类只在明确要求时记录。

1. 传送遮挡链路上三处会漏遮挡的口子:判定不同源 / 命令不建界面 / 存在无遮挡档位
把「这次传送由谁遮丑」的判定链整条梳理完时,发现三处会让遮挡漏掉的口子(只读分析,未改代码):
① 插入 Loading 与跳过黑屏用的是两条不同源的判定 SceneLoadingTeleportCmd 用 TeleportModel.IsTeleporting 决定要不要插 SceneLoadingOpenUiCmd,而跳过黑屏用的是 ShouldUseUiLoadingInsteadOfBlackScreen。前者只由 InitData() 置位,传送门不经过它 → 该分支在传送门路径上恒为 false。两条判定分叉的结果是:黑屏确实被跳过了,却没有对应的 Loading 补上。
② 插入的那条 OpenUi 命令在传送链上并不创建 Loading 界面 SceneLoadingOpenUiCmd.OnExecute 只有 IsLogin 分支才调 LoadingController.GameModeOpenLoading()(真正创建 LoadingView 的地方),非 Login 分支只做 OpenBlackView() + LoadingController.SetIsLoading(true),而 SetIsLoading 只发 Start/FinishLoadingState、跟界面无关。所以即便把 ① 的判定改成同源,这条分支也实现不了「传送点 / 传送门都要有 Loading 界面」。
③ 无缝窗口存在一段无遮挡档位 同步瞬移的遮挡有三个来源:入口瞬时遮罩、BaseMoveSyncComponent 自己的黑屏、以及升格出来的 uiload。前两者共用同一个闸门,默认配置下恒为「跳过」;uiload 则要求 !IsTeleportStreaming 且位移 > 100 米才升格。于是「表现被判为无缝(非 isHeavy)且位移 ≤ 100 米」这一段既没有黑屏也没有 Loading——命中它的场景是流送窗口,而不是原以为的「远距离」,前提要修正。
另有两处更窄的口子:IsTeleportStreaming 若因残留或并发被置真,ShouldPromoteSeamlessToUiLoading 会直接 return false,位移再大也不升格;若没收到 WorldTeleportSelfSCNotify(15512),待处理的表现类型没落库,OnLocalPlayerServerMoveApplied 会直接 return,整条路变成零遮挡。
来源:传送加载规则:统一 Loading / 黑屏 / 无缝的遮挡分工 · 适用:改传送遮挡、排查「传完没遮住」。现场按日志区分当前走了哪种遮挡,见 传送 · 技术方案文档 §6.3。

4问题复盘0 条

排查走了弯路、造成外部代价的事。这一类只在明确要求时记录。

本周无

5. 飞书周报(一键复制)

下面这块是按飞书周报模板压缩后的对外版,只保留结果、关键方案与风险 / 后续。

飞书周报 · 2026-W38(09/14 – 09/20) file:// 下若复制被拒,会自动帮你选中,按 Ctrl+C
1.本周工作进展:

    a. 【主线】传送点模块梳理(解锁状态与场景表现)
       模块:传送点 / SceneItemController · EtTeleportComponent · TeleportMarkItem
       需求:场景里可交互的传送水晶 / 传送门,其解锁状态与场景表现分散在三处:SceneItemController 持有解锁数据、EtTeleportComponent 负责场景表现、TeleportMarkItem 负责地图标记。目标是理清三者职责与数据流向,明确「解锁状态不能用服务器实体 State(全服共用)」这条核心约束,沉淀成可复用的模块文档。
       实现细节:按三个可分离关注点组织:解锁数据由 SceneItemController.UnlockTeleportIdSet 唯一权威持有;场景表现由 EtTeleportComponent 按解锁与否切本地 GameplayTag(关卡.Common.数字状态.状态2 / 状态1);地图标记由 TeleportMarkItem 按地图类型选资源并实时转发解锁态。
       解锁数据梳理出三条来源:配置表 Teleporter.DefaultUnlock、服务器地图信息 LevelMapModel.AddMapInfo().unlockTeleportList、增量补解锁 LevelTeleportAssistant;三者统一收敛到 UnlockTeleport 入口,先 has 判重保证幂等,避免重复 Emit(OnTeleportUnlocked) 导致表现层重复刷新。
       表现层在 OnStart 订阅事件并立刻刷一次初始态(实体可能在解锁事件之后才创建),切状态用 ChangeLevelTagByClient(纯本地,不同步服务器);标记侧不缓存解锁态,用 getter 实时查权威持有者。
       另梳理了发起传送的唯一调用点 TeleportController 的 RequestTeleportByEntityPrefabDataId、协议 23707 WorldTeleportCSReq(IsTeleporting 无条件复位),以及最近传送点检索:只遍历已解锁集合、过滤同 mapPath、按 Size2D() 取 2D 距离。
       结果:产出《传送点》模块文档,覆盖模块定位、配置表 Teleporter(4 个字段 + PrefabDataId ↔ TeleportId 双向映射)、解锁三条来源与幂等、EtTeleportComponent 表现层、发起传送、TeleportMarkItem、最近传送点检索,以及「加新传送点 / 改解锁表现 / 排查该亮没亮」三类常见修改场景。
       验收:文档已上线并登记站内导航(传送点);全站内链 / 锚点 / 搜索索引校验通过。

    b. 【主线】传送加载规则:统一 Loading / 黑屏 / 无缝的遮挡分工,并把黑屏入口收口到单一判定
       模块:传送 / TeleportPresentation · TeleportController · BlackScreenController · BaseMoveSyncComponent · SceneLoadingTeleportCmd
       需求:需求规定「所有由传送点、传送门发起的传送都要有 Loading 界面」。落到代码上,真正要解决的是遮挡手段的互斥与分工:黑屏挂在 ELayerType.CG(1<<10)层,系统 Loading 挂在 Loading(1<<8)层,层级上黑屏恒高于 Loading——两者并存时玩家只看得到黑屏,连加载百分比都被盖住。所以不能让黑屏和系统 Loading 各按各的节奏自行开合,必须有一条统一判定决定这一次传送由谁遮丑。
       实现细节:判定收口:TeleportPresentation.ShouldUseUiLoadingInsteadOfBlackScreen(enterShowType?)——淡入淡出表现(TeleportShowType.TELEPORT_SHOW_TYPE_FADE_IN_OUT)返回 false(它需要黑屏当淡出起点,保持原行为),其余返回 ShouldShowUiLoadingForSequence()(两个静态开关,默认配置下所有主动传送链路由系统 Loading 承担遮挡)。一条硬约束写进了注释:不能依赖 TeleportModel.IsTeleporting——传送门走「同步瞬移 + ServerMove」链路,不经过 InitData(),该字段在传送门分支上恒为 false,拿它做前置会照旧叠出黑屏。
       三处黑屏入口收敛:OpenSeamlessTransitionMask()(同图无缝的入口瞬时遮罩)、OpenBlackView()(跨图传输入口)、BaseMoveSyncComponent 的同步瞬移(自持 tag [传送-黑屏-同步瞬移]);三条路都命中时只打日志跳过,不再 Add 黑屏。
       无缝升格 uiload:表现被判为无缝、但本次位移超过 SeamlessPromoteDistanceMeters = 100(米)且目标地形流送未完成时,无缝依赖的「目的地已就绪」前提不成立,ShouldPromoteSeamlessToUiLoading() 返回真,由 PromoteSeamlessToUiLoading() 串行 PromotedUiLoadingTask 链开系统 Loading,并撤掉自己的无缝遮罩(黑屏还盖着的话,升格出的 uiload 与百分比同样看不到);收尾对称走 ClosePromotedUiLoading(),开关任务用 AwaitWithTimeout 包一层,避免单次异常把串行链永久卡住。
       全局兜底:BlackScreenController.AddBlackScreenAsync 里查 LoadingModel.IsLoadingView,系统 Loading 界面正在显示时直接跳过黑屏并打日志。以 IsLoadingView 而不是 IsLoading 为准是有意的——后者只发 Start/FinishLoadingState,不一定有界面。
       现场排障用的三个 GM、以及按日志区分当前走了哪种遮挡的对照表,见《传送 · 技术方案文档》§6.3 黑屏与遮罩。
       结果:把「这一次传送由谁遮丑」从一个散落在各链路的隐含约定,变成单一入口 + 单一判定:黑屏与系统 Loading 互斥,位移超阈值的无缝传送会升格成 uiload 兜住流送窗口,判定之外的调用点由 BlackScreenController 按 IsLoadingView 兜底。同步把判定链、升格闸门与三处黑屏入口登记进《传送 · 技术方案文档》§6.3 黑屏与遮罩。
       验收:两条口径:互斥——同一时刻只出一种遮挡,黑屏不再盖住 uiload 与加载百分比;不漏遮挡——表现判为无缝但位移超阈值、目标地形未流送完时,必须有 uiload 兜住。代码侧三处黑屏入口已统一走 ShouldUseUiLoadingInsteadOfBlackScreen;现场可用 TeleportSysLoadingToggle / TeleportNoUiLoadingToggle 切换两种配置各跑一遍主动传送与传送门复验。

    c. 【主线】传送 V2 Seq2 → Seq3 衔接梳理(V2 表现 · 切场阶段与实测耗时)
       模块:传送 V2 / TeleportV2Flow · Seq2 循环退出 · 切场分段 · Seq3 绑定
       需求:Seq2 是可循环的空中等待段、Seq3 是降落段,两者之间的切场由多次跨帧等待拼成,但这些等待散在代码里没有统一时间轴。一旦出现切场穿帮、角色缺失或看不到落地表现,只能临时加日志逐条试,无法判断某一段耗时是否正常。需要把这一段的阶段顺序与实测耗时固化成可对照的时间轴。
       实现细节:把「Seq2 循环退出 → 玩家可操作」之间的链路拆成 7 个阶段:Seq2 循环等待 → 退出 loading 并暂停 Seq2 → Seq3 资产就绪 → 演员接管 → 换装等待(跨帧)→ binding 编译等待(跨帧)→ Seq3 播放至 OnFinished。
       其中退出 loading 与暂停 Seq2 同帧完成,资产就绪与演员接管同帧完成;换装等待与 binding 编译等待是仅有的两处刻意跨帧等待,两者串联约 500 ms,是 Seq3 起播前的固定成本。
       落地收尾单独拉出:OnFinished 后不等待降落动画播完,而是立刻走还原链并 OnEnd,整段只有 10 ms 量级,且相机在此前约 3 s 就已交回战斗相机。
       结果:产出《传送 › V2 表现 › Seq2 → Seq3 衔接问题》专栏页:含三类现象定位、阶段逻辑图(7 阶段)、两组实测耗时对照(换装 203 ms、binding 编译 312 ms、落地收尾 ≈11 ms / ≈10 ms),以及落地收尾段的原始日志时序。专栏按「传送 → V2 表现 → Seq2 → Seq3 衔接问题」的层级挂进站点导航,排查表现问题时可直接把实测值与本页对照,先判断是「切场等待异常」还是「落地段本来就没有时间窗」。
       验收:专栏页已上线并按层级登记站内导航(传送 › V2 表现 › Seq2 → Seq3 衔接问题);全站内链 / 锚点 / 搜索索引校验通过。

    d. 【主线】重新进图后 #OpenDebugGameEnv 打开的 GM 调试信息面板被关闭且不再恢复
       模块:Loading / LoadingController · 传送 / Seq3 换装遮罩 · Debug / GameEnvDebugView
       问题描述详细版本:GM 命令 #OpenDebugGameEnv 打开调试信息面板(帧率、耗时等),随后做一次传送(重新进图),面板被关掉且之后不再恢复;同源受影响的还有 TimeInfoDebugView。
       日志核对:面板恢复发生在 EndEnterLevel(15:50:22.683),之后 EndClientLoading 完成(15:50:25.901);紧接着落地阶段 Seq3 换装遮罩又打开一次系统 Loading(15:50:25.902),8 毫秒后面板被销毁(15:50:25.910)——此后再没有 EndEnterLevel,面板再也回不来。
       原因:LoadingController.GameModeOpenLoading() 在打开系统 Loading 前会调 UiInterfaceHelper.Clear() 做一次全量 UI 清理(只放行常驻界面与白名单里的 LoadingView / MView)。首段 Loading 靠 NormalLoadingViewGlobalData.FirstProgressPromise 做重入保护,重复调用直接返回、不清理;但传送落地阶段的 Seq3 换装遮罩是第二次打开 Loading,此时首段的 Promise 已置空,重入判断失效,于是又执行了一遍全量清理——而这时新场景的 UI 已经恢复完毕(EndEnterLevel 已经走过),清掉的正是刚恢复的 GameEnvDebugView / TimeInfoDebugView。
       正常 HUD 没暴露这个问题,是因为它们都是常驻界面,走 UiConfig.ShouldKeepWhileClearScene() 的 IsPermanent 分支被豁免;调试面板是临时界面,不在豁免范围。
       解决方案:给 GameModeOpenLoading 加 keepExistingUi 开关(默认 false,保持原有语义),只让 Seq3 换装遮罩这一处传 true:把 UiInterfaceHelper.Clear() 包成 if (!keepExistingUi) { UiInterfaceHelper.Clear(); },调用侧改为 ControllerHolder.LoadingController.GameModeOpenLoading(undefined, true)。
       依据:Seq3 这次清理发生在新场景 UI 已恢复之后,清掉的本就是本轮新开的界面,语义上不该清;而且「不清 UI 也能打开 Loading」代码里已有先例(首段未完成时的早返回分支同样不执行清理)。其余 5 处 GameModeOpenLoading() 调用点语义不变。
       验证:#OpenDebugGameEnv 开面板 → 同图传送,面板保持打开;同时回归传送落地后 HUD / 主界面表现正常。
       总结:「打开 Loading」和「清空场景 UI」原本耦合在同一个方法里。同一条流程里第二次打开 Loading 时,重入保护已经失效,这次清理就会打到本轮刚恢复的界面上。判断一处 Clear() 该不该执行,看的是它清的是「上一轮残留」还是「本轮新开」。

    e. 【主线】b1271064,副本结算时,特写 / 全家福里的结算角色模型全部不显示
       模块:副本结算 / DungeonSettlementController · 技能序列换装 / SkillSequenceAvatarBindingSession · 换装队列 / AvatarJobController
       问题描述详细版本:线上单 b1271064:副本结算进入特写(Seq2)与全家福(Seq3)时,结算角色的模型不显示。
       日志核对:结算时 4 个角色的 BindActor 都正常返回(851287 / 851290 / 851294 / 851298),但整个结算窗口内 0 条「换装成功!」;21:36:08 结算卸载时出现 4 条「换装系统衣服选择被强制中断」(852783–852786);结算关闭后(864973)「换装成功!」立刻恢复。即:结算期间换装任务根本没被执行,不是模型创建成功却没显示。
       原因:毒点不在结算侧,在战斗期的技能序列换装。SkillSequenceAvatarBindingSession.TryAutoPlayPreparedSequence 里读 this.SeqActor.LevelSequence.AssetPath.FullPathString,而这个 Actor 属于 additive camera prepared sequence——序列由 KuroCustomAdditiveCameraRenderSubSystem 持有,Actor 本身就不挂 LevelSequence,于是抛 Cannot read properties of undefined (reading 'AssetPath')(日志 569105 行 GlobalException)。
       异常发生在资源加载回调栈里,被 ResourceSystem.TryCallback 的 try/catch 吞掉(日志只留「资源加载回调方法执行异常」),但副作用是致命的:AvatarClothJob.OnStepCompletedCallBack 里先调外部回调、后调 ControllerHolder.AvatarJobController.OnJobCompletedCallBack(this);外部回调一抛异常,后面那步就不再执行,job 不会被移出 ExecutingJobs。
       AvatarJobController.MaxJobCount = 4,4 次异常(21:29:12 / 21:30:15 / 21:31:45 / 21:31:55)正好把 4 个槽位漏光,此后 availableCount === 0 直接 return,所有换装 Job 全被堵死——结算换装走的是同一条队列,于是模型不显示。
       解决方案:两处上游改(本单根因,已给出改法、待落地):
       ① 异常源 SkillSequenceAvatarBindingSession 读序列路径时给 LevelSequence 兜底(去掉直接插值,或退化成 ?? 'PreparedSequence'),不再让 prepared sequence 的 Actor 走到这条取值上。
       ② 止损 AvatarJobController 的换装步骤回调里把外部回调包 try/catch,保证 OnJobCompletedCallBack 无论如何都执行——外部回调再抛异常也不会让 job 出不了队。
       结算侧本单改动(changelist 812994):删掉已废弃的 Seq1 Tween 推镜整段死代码与 Seq3 资源依赖(现在结算只剩 Seq2 特写 + Seq3 程序化站位两段),并对 DungeonSettlementController 做了 4 处加固(见「顺带发现」)。
       验证:GetDiagnostics 无报错,npx eslint 0 error。
       总结:换装走的是全局队列(AvatarJobController,MaxJobCount = 4),而槽位是在「外部回调返回之后」才释放的。任何在换装回调里抛出的异常都会让这个 job 出不了队,槽位只出不进,4 次之后整条队列对所有模块堵死。
       表现层看到的是「某个不相关模块的模型不显示」——所以排查时不要盯报障的那个界面,先看该模块的「换装成功!」日志有没有;没有就顺着时间戳往上游找第一个异常,那里才是真正被卡住的地方。
       顺带发现:审结算侧代码时挖出 4 个同型隐患(现象和复现路径都不同,未单独立条):
       ① 取播放器无兜底 + 非空断言 GetSequencePlayer() 不保证及时返回对象(KURO 因此额外保留了 SequencePlayer 属性),原代码直接 this.LevelSequencePlayer2!.OnFinished.Add(...);一旦为 undefined 就在资源回调栈内抛异常并被吞掉,finishOnce 不执行、界面卡在打开特写的黑屏,只能等 15s 保底超时。已改成 GetSequencePlayer() ?? SequencePlayer 判有效,拿不到就跳过监听并打 Warn。
       ② 结构体直改不生效 PlaybackSettings 是结构体,原代码 player.PlaybackSettings.bPauseAtEnd = true 改字段等于没改,必须读-改-写回。
       ③ 静默的错机位 Seq2 相机绑定 DungeonSettlementCamera2 找不到时完全没有日志,流程却照样把 Seq2Ready 置 true——特写会播、机位却是错的。已补 Warn。(有意不动语义:若因此不播 Seq2,OnFinished 不触发,非 GM 侧只能等 UI 60s 保底才推进。)
       ④ 静默消失的角色 UiRoleActorProxy.LoadAvatar 在「正在加载 / 无外观配置」时会提前回调且不创建 Actor,而该类各接口都是空安全、不抛错,于是这个角色会静默从全家福里消失。已补 Error 日志。
       另:SwitchCamera3 是三条相机路径里唯一没有告警分支的(本次未改)。

    f. 【主线】副本 20100301 典狱长作战区域坠落重生有概率回到副本入口,而不是就近落点
       模块:坠落重生 / FallingRebornController · 关卡 / 副本 20100301
       问题描述详细版本:副本 20100301 典狱长作战区域,玩家坠落(掉出地形或触发 Z 轴保底)后执行坠落重生,有概率被送回副本入口(即关卡出生点 LevelBornPosition),而不是在掉落点就近重生。
       概率性来自前置条件:只有玩家在掉落前于带 BP_UpdateFallingRebornArea_C 范围盒的区域里站立过,坠落重生点才会被刷到该处。
       日志核对:按 [坠落重生] 前缀过滤,出现「流送后的地形不安全,重生到关卡出生点」即命中本现象。
       原因:坠落重生的落点有两个来源,最终都指向 g.关卡.xlsx 的 LevelBornPosition(副本里就是入口):
       ① 记录点没有被刷新 —— FallingRebornLocation 只在有缝进关卡时初始化为玩家进本落点(副本入口);此后仅当玩家走进 BP_UpdateFallingRebornArea_C 范围盒、由 QuickUpdateFallingReborn / SlowUpdateFallingReborn 驱动 UpdateFallingReborn 才会更新(且要求 3×3 射线全部命中 WorldStatic、与上次记录点位移超过 352)。典狱长作战区域若没有覆盖范围盒,玩家整段战斗期间记录点一直停在进本位置。
       ② 二次校验判定不安全 —— 即使记录点正确,CalculateSafeRebornLocation 向下探 SafeDepth=2000 未命中时(地面非 WorldStatic、或地形尚未流送完)返回 LevelBornPosition;流送后复检仍不安全,就下一帧直接流送到关卡出生点。
       两条叠加解释「有概率」:进本后走过范围盒 → 落点正常;直奔典狱长作战区域开打 → 落点仍是副本入口。
       解决方案:关卡侧:在典狱长作战区域(含通往典狱长房的位移路径)补 BP_UpdateFallingRebornArea_C 范围盒,让玩家在该区域内站立时把坠落重生点刷到就近安全地面;跳跃 / 高低差段落按需挂 LookAtLocation 指定重生朝向。
       客户端链路本次不改:UpdateFallingReborn 的位移阈值(352)与 3×3 射线步长(水平 132 / 垂直 176)已覆盖该区域,缺的是范围盒把玩家「推进检查」。
       验证:进本后直接跑向典狱长作战区域,坠落重生落在就近地面;战斗区域内出现「[坠落重生] 设置重生点。站立在安全地面设置重生点」,且不再出现「流送后的地形不安全,重生到关卡出生点」。
       总结:坠落重生的记录点是靠范围盒推进的:没有任何 BP_UpdateFallingRebornArea_C 的区域,记录点不会更新,会一直停在进本落点。关卡里新增「大范围战斗 / 跳跃」区域时,摆范围盒是坠落重生落点正确的前置条件。
       再收到「重生回到副本入口」的反馈,先查该区域有没有范围盒,再按 [坠落重生] 日志看是记录点没更新还是二次校验判了不安全。

    g. 【主线】传送快结束时用脱离卡死自杀,传送结束后角色身上的淡变材质残留、表现异常
       模块:传送 / TeleportTransitionFlow · TeleportPresentation · TeleportController
       问题描述详细版本:走淡入淡出表现的传送(enterShowType = TELEPORT_SHOW_TYPE_FADE_IN_OUT),在到达阶段的淡入环节被死亡打断后表现异常:传送流程本身收尾正常(黑屏关掉、Loading 关掉、输入恢复),但玩家角色身上仍挂着淡变材质,角色保持隐形 / 半透明,直到下一次传送才自愈。
       复现路径:任意走淡入淡出的传送,在淡入阶段(材质挂上后的 800ms 窗口内)用脱离卡死自杀。脱离卡死发的是 WorldSuicideCSReq,即直接自杀,链路为 CharOnDead → TeleportController.OnLocalPlayerDead → TransitionFlow.OnEnd()。
       原因:淡入阶段挂上的材质只有淡入分支自己会摘,而这条分支被代次机制提前作废了:
       ① 先挂材质、后等时长 —— PlayTeleportFadeInWithMask() 内先 AttachFadeMaterial 把淡变材质挂到玩家身上,再 await onAttached()(关黑屏 + 关 Loading),然后等 TeleportFadeDurationMs = 800。
       ② 窗口内死亡中断 —— 这 800ms 内死亡打断进来,OnEnd() 首行就把 FlowGeneration += 1,当前协程的代次随之作废。
       ③ 清理代码被早 return 跳过 —— 800ms 结束后 if (!this.IsCurrentFlow(generation)) return; 命中,紧跟其后的 this.Presentation.StopTeleportFade() 不会执行。
       ④ 兜底也没有 —— OnEnd() 的收尾分支、RecoverPresentationFromTransitionFailure()、Presentation.Clear() 都不摘淡变材质,于是 FadeMaterialHandle 一直残留。
       解决方案:在 OnEnd() 的 !KeepFadeInOutOnEnd 分支里补一次 this.Presentation.StopTeleportFade(),作为流程收尾的统一兜底。
       两个前提让它安全:StopTeleportFade() 本身幂等(FadeMaterialHandle 为 0 时直接返回),正常路径下淡入分支已经摘过,这里再摘一次无副作用;KeepFadeInOutOnEnd 为 true 表示「同图淡入淡出刚起步,OnEnd() 只是阶段结束、淡出还要继续」,此时不清理,避免误杀正在播的淡出。
       验证:走淡入淡出传送,在 800ms 淡入窗口内自杀,传送结束后角色表现正常,不再残留材质。
       总结:传送里凡「先挂表现、再 await、再自己摘」的写法,都要考虑 await 返回时代次已被改掉、清理代码走不到的情况。清理动作应该由流程收尾(OnEnd())兜底,而不是只交给发起方——发起方一旦被 IsCurrentFlow 早 return 拦下,就再没人清。
       淡变材质挂在玩家 Actor 上,表现就是角色隐形 / 半透明,容易被误判成角色模型或渲染问题;复现路径里带「传送途中死亡 / 脱离卡死」时,先查这类流程收尾有没有跑完。
       顺带发现:复核这条时挖出的同源隐患(现象和复现路径都不同,未单独立条):
       ① 中断判定覆盖不到淡入淡出尾部 TeleportModel.IsTeleporting 只由 InitData() 置位、ClearData() 复位,而淡入淡出在 OnFadeInOutLevelInternal() 一开始就 OnEnd()(内部含 ClearData()),流送与淡入阶段该标记已经是 false;OnLocalPlayerDead 正是靠它判断「是否正在传送」,在流送 / 淡入阶段死亡会直接早 return,连 OnEnd() 的兜底都走不到。
       ② 失败兜底清理不全 RecoverPresentationFromTransitionFailure() 不摘淡变材质、不关 OpenUiLoadingForSeq3 打开的系统 Loading、不复位 IsStreamingLevel。
       ③ OnEnd() 职责混用既当「某个阶段结束」又当「整体收尾」,KeepFadeInOutOnEnd 这个标志就是为区分两种语义打的补丁。

    h. 【主线】传送过场收尾时重置动画模式,把角色链接的动画层清掉,落地后三段跳表现异常
       模块:传送 / TeleportSeq3Binder · 关卡序列收尾
       问题描述详细版本:走传送 V2 的过场表现(Seq3 落地段)后,角色落地后三种跳的表现异常——跳跃本身能触发,但该走的那套链接动画层没有生效。
       日志核对到的情况很有误导性:现场 AnimationMode 是 0(AnimationBlueprint,看着正常)、AnimScriptInstance 存在,三个链接层(三段跳 / 骨骼后处理 / 坐姿层)在实例清单里都还在。也就是「模式对、实例在、层都在」,但表现就是不对。
       原因:序列收尾时业务侧多写了一次动画模式复位,把链接动画层注销掉了:
       ① 序列是「借走」骨骼动画的输出权 —— 序列驱动骨骼动画时,会切到 AnimationCustomMode(自定义模式),注入 UAnimSequencerInstance 顶掉原来的 AnimScriptInstance,原 AnimBP 不再参与姿态输出。播放期间角色身上那套 AnimBP(含链接动画层)是离线的:只是被替换,不是被关闭。
       ② 收尾的复位是重复且破坏性的 —— TeleportSeq3Binder 在归还 Mesh 相对变换的同一段里,又调了一次 SetAnimationMode(AnimationBlueprint) 想「把模式还回去」。这个接口第二个参数 bForceInitAnimScriptInstance 默认为 true,含义是即使新模式与当前模式相同,也要强制重新初始化动画实例——同值调用同样会真的重建实例、重置链接动画实例,对由动画图表创建的层调用 UninitializeAnimation()。
       ③ 层被注销后没人再补挂 —— 层对象还在(所以清单里三个层都「在」),但已不再产出姿态。Locomotion 组件只在「动画层未挂载」标志为 false 时才尝试补挂,而该标志在这次注销后没有被置回,补挂逻辑认为已经挂好,于是不补。
       于是「层还在」被当成了「层在工作」,异常一直留到下一次相关重置。
       解决方案:归还交给序列本身,业务侧不插手:
       ① 删掉收尾处手写的 SetAnimationMode 及其紧随的动画重初始化调用。
       ② 把 Seq3 动画节的「完成时」设为恢复状态(RestoreState)——引擎会用在接管阶段存下的姿态快照把姿态还回去,链接动画层不受影响。
       两件事必须成对,实测三态:保留两行 + 保持状态 = 原 bug;删掉两行 + 保持状态 = 跳跃没有动作(没人还姿态);删掉两行 + 恢复状态 = 表现正常。
       注意改播放设置(FinishCompletionStateOverride)替代不了动画节的完成时:它只作用于序列结束时仍激活的 section,在序列结束前就播完的 section 由自身设置决定;而且传送流程复制播放设置时已经把它显式置为 None,这个覆盖本来就是关着的。
       总结:序列驱动的角色表现,收尾归还的职责在序列自己身上,业务侧不要手写动画模式复位——SetAnimationMode 第二个参数默认强制初始化,同值调用也会重建实例并注销链接动画层。
       要归还姿态,改序列动画节的「完成时」为「恢复状态」,而不是改播放设置里的完成状态覆盖(它只管序列结束时仍激活的 section)。
       排查提示:日志里「链接层都在」不等于层在工作,被注销的层仍会留在清单里;反过来「模式是对的、实例也在」同样不能证明动画链路正常。


2.下周工作计划:
    a. 继续沉淀传送 / 传送点模块文档,补充尚未覆盖的分支。

3.待讨论的问题/需要分享的内容:
    无。
条目格式(本站统一) 需求按模块 → 需求 → 实现细节 → 结果 → 验收记录;不展示责任方,不写逐文件流水账。英文术语补充中文说明,代码标识保持原样。

6. 下周待办

  1. 继续沉淀传送 / 传送点模块文档,补充尚未覆盖的分支。