yyl
2026-05-26 ed4312ff529fd56bad20ef15eeeb1a7e45051ae3
花鬘复活卡死问题修复

(cherry picked from commit c5cb2d17558e481dac4f955e52e74f9745393b72)
5个文件已修改
409 ■■■■■ 已修改文件
Main/System/Battle/BattleField/RecordActions/DeathRecordAction.cs 266 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleObject/HeroBattleObject.cs 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Motion/MotionBase.cs 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Skill/SkillBase.Cast.cs 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Skill/SkillBase.Finish.cs 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleField/RecordActions/DeathRecordAction.cs
@@ -24,6 +24,12 @@
    
    private SkillRecordAction playSkillRecordAction = null;
#if UNITY_EDITOR
    private int deathDebugNotFinishedCount = 0;
    private const int DeathDebugFirstDumpCount = 120;
    private const int DeathDebugRepeatDumpCount = 180;
#endif
    public DeathRecordAction(BattleField _battleField, List<BattleDeadPack> _deadPackList, SkillRecordAction _parentSkillAction = null, SkillRecordAction _playSkillRecordAction = null)
        : base(RecordActionType.Death, _battleField, null)
    {
@@ -68,6 +74,10 @@
        {
            isRunOnce = true;
#if UNITY_EDITOR
            // DumpDeathDebugState("首次Run");
#endif
            SkillRecordAction waitingAnimeAction = playSkillRecordAction;
            foreach (var battleDeadPack in deadPackList)
@@ -78,6 +88,10 @@
                    if (null != skillAction)
                    {
                        deathActionDict.Add(battleDeadPack, skillAction);
#if UNITY_EDITOR
                        // BattleDebug.LogError($"[DeathRecordAction诊断] 创建死亡触发技能 {FormatDeadPackForDebug(battleDeadPack)} -> {FormatSkillActionForDebug(skillAction)} waitingAnimeAction={(waitingAnimeAction == null ? "null" : FormatSkillActionForDebug(waitingAnimeAction))}");
#endif
                        
                        // 【使用 BattleField.recordPlayer】
                        // 原因:死亡触发技能是顶层的RecordAction,不是在某个RecordAction内部产生的
@@ -99,9 +113,18 @@
                            waitingAnimeAction = skillAction;
                        }
                    }
#if UNITY_EDITOR
                    else
                    {
                        // BattleDebug.LogError($"[DeathRecordAction诊断] 死亡触发技能创建失败 {FormatDeadPackForDebug(battleDeadPack)}");
                    }
#endif
                }
                else
                {
#if UNITY_EDITOR
                    // BattleDebug.LogError($"[DeathRecordAction诊断] 创建普通死亡状态 {FormatDeadPackForDebug(battleDeadPack)}");
#endif
                    deadActionStatesDict.Add(battleDeadPack, CreateDeadActionState(battleDeadPack));
                }
            }
@@ -149,6 +172,10 @@
                }
            }
#if UNITY_EDITOR
            // ReportDeathNotFinishedIfNeeded(completeNum);
#endif
            if (completeNum == deadPackList.Count)
            {
                // 死亡处理完成后,分发死亡后的包
@@ -177,12 +204,46 @@
        BattleObject deadObj = battleField.battleObjMgr.GetBattleObject((int)deadPack.deadPack.ObjID);
        if (null == deadObj)
        {
#if UNITY_EDITOR
            // BattleDebug.LogError($"[DeathRecordAction诊断] 死亡对象不存在,直接视为完成 {FormatDeadPackForDebug(deadPack)}");
#endif
            return () => true;
        }
            if (deadObj.IsReborning())
            {
        #if UNITY_EDITOR
            // BattleDebug.LogError($"[DeathRecordAction诊断] 创建死亡状态时对象已在复活流程,直接视为完成 {FormatDeadPackForDebug(deadPack)} withoutAnime={withoutAnime}");
        #endif
                return () => true;
            }
            if (deadObj.GetCurHp() > 0)
            {
        #if UNITY_EDITOR
            // BattleDebug.LogError($"[DeathRecordAction诊断] 创建死亡状态时对象血量已回正,直接视为完成 {FormatDeadPackForDebug(deadPack)} withoutAnime={withoutAnime}");
        #endif
                return () => true;
            }
            if (deadObj.IsDead())
            {
        #if UNITY_EDITOR
            // BattleDebug.LogError($"[DeathRecordAction诊断] 创建死亡状态时对象已死亡,直接视为完成 {FormatDeadPackForDebug(deadPack)} withoutAnime={withoutAnime}");
        #endif
                return () => true;
            }
        bool playDeath = false;
        bool isComplete = false;
#if UNITY_EDITOR
        bool loggedWaitingCanStartDeath = false;
        bool loggedReborningComplete = false;
            bool loggedHpRestoredComplete = false;
            bool loggedAlreadyDeadComplete = false;
#endif
        //  如果没有释放技能 则直接死亡
        if (deadObj.CanStartDeath())
@@ -191,10 +252,23 @@
            deadObj.OnDeath(() => {
                isComplete = true;
#if UNITY_EDITOR
                // BattleDebug.LogError($"[DeathRecordAction诊断] 死亡动画回调完成 {FormatDeadPackForDebug(deadPack)} withoutAnime={withoutAnime}");
#endif
            }, withoutAnime);
            playDeath = true;
#if UNITY_EDITOR
            // BattleDebug.LogError($"[DeathRecordAction诊断] 开始播放死亡表现 {FormatDeadPackForDebug(deadPack)} withoutAnime={withoutAnime}");
#endif
        }
#if UNITY_EDITOR
        else
        {
            loggedWaitingCanStartDeath = true;
            // BattleDebug.LogError($"[DeathRecordAction诊断] 暂不能播放死亡表现,等待 CanStartDeath {FormatDeadPackForDebug(deadPack)} deadObj.CanStartDeath={deadObj.CanStartDeath()} isReborning={deadObj.IsReborning()} isDead={deadObj.IsDead()}");
        }
#endif
@@ -207,14 +281,58 @@
                deadObj.OnDeath(() => {
                    isComplete = true;
#if UNITY_EDITOR
                    // BattleDebug.LogError($"[DeathRecordAction诊断] 延迟死亡动画回调完成 {FormatDeadPackForDebug(deadPack)} withoutAnime={withoutAnime}");
#endif
                }, withoutAnime);
                playDeath = true;
#if UNITY_EDITOR
                // BattleDebug.LogError($"[DeathRecordAction诊断] 延迟开始播放死亡表现 {FormatDeadPackForDebug(deadPack)} withoutAnime={withoutAnime}");
#endif
            }
#if UNITY_EDITOR
            else if (!playDeath && !loggedWaitingCanStartDeath)
            {
                loggedWaitingCanStartDeath = true;
                // BattleDebug.LogError($"[DeathRecordAction诊断] 仍不能播放死亡表现,等待 CanStartDeath {FormatDeadPackForDebug(deadPack)} deadObj.CanStartDeath={deadObj.CanStartDeath()} isReborning={deadObj.IsReborning()} isDead={deadObj.IsDead()}");
            }
#endif
            
            if (deadObj.IsReborning())
            {
                isComplete = true;
#if UNITY_EDITOR
                if (!loggedReborningComplete)
                {
                    loggedReborningComplete = true;
                    // BattleDebug.LogError($"[DeathRecordAction诊断] 死亡对象已进入复活流程,死亡状态视为完成 {FormatDeadPackForDebug(deadPack)}");
                }
#endif
            }
            if (!isComplete && deadObj.GetCurHp() > 0)
            {
                isComplete = true;
#if UNITY_EDITOR
                if (!loggedHpRestoredComplete)
                {
                    loggedHpRestoredComplete = true;
                    // BattleDebug.LogError($"[DeathRecordAction诊断] 死亡对象血量已回正,死亡状态视为完成 {FormatDeadPackForDebug(deadPack)}");
                }
#endif
            }
            if (!isComplete && deadObj.IsDead())
            {
                isComplete = true;
#if UNITY_EDITOR
                if (!loggedAlreadyDeadComplete)
                {
                    loggedAlreadyDeadComplete = true;
                    // BattleDebug.LogError($"[DeathRecordAction诊断] 死亡对象已处于死亡状态,死亡状态视为完成 {FormatDeadPackForDebug(deadPack)}");
                }
#endif
            }
            return isComplete;
@@ -284,18 +402,19 @@
        // 遍历所有死亡包,分发它们的 packListAfterDeath
        foreach (var deadPack in deadPackList)
        {
#if UNITY_EDITOR
            // BattleDebug.LogError($"[DeathRecordAction诊断] 准备分发死亡后包 {FormatDeadPackForDebug(deadPack)} afterDeathCount={deadPack.packListAfterDeath?.Count ?? 0}");
#endif
            if (deadPack.packListAfterDeath != null && deadPack.packListAfterDeath.Count > 0)
            {
                foreach (var pack in deadPack.packListAfterDeath)
                {
                    // 获取包的类型和UID用于调试
#if UNITY_EDITOR
                    string packType = pack.GetType().Name;
                    ulong packUID = 0;
                    var packUIDField = pack.GetType().GetField("packUID");
                    if (packUIDField != null)
                    {
                        packUID = (ulong)packUIDField.GetValue(pack);
                    }
                    ulong packUID = GetPackUIDForDebug(pack);
                    // BattleDebug.LogError($"[DeathRecordAction诊断] 分发死亡后包 objId={GetDeadObjIdForDebug(deadPack)} packType={packType} packUID={packUID} playSkillRecordAction={(playSkillRecordAction == null ? "null" : FormatSkillActionForDebug(playSkillRecordAction))}");
#endif
                    
                    
                    // 特殊处理 CustomHB426CombinePack:使用其自己的 Distribute 方法
@@ -379,6 +498,141 @@
    }
#if UNITY_EDITOR
    private void ReportDeathNotFinishedIfNeeded(int completeNum)
    {
        if (isFinish || isActionCompleted)
        {
            deathDebugNotFinishedCount = 0;
            return;
        }
        deathDebugNotFinishedCount++;
        bool firstDump = deathDebugNotFinishedCount == DeathDebugFirstDumpCount;
        bool repeatDump = deathDebugNotFinishedCount > DeathDebugFirstDumpCount
                          && (deathDebugNotFinishedCount - DeathDebugFirstDumpCount) % DeathDebugRepeatDumpCount == 0;
        if (!firstDump && !repeatDump)
        {
            return;
        }
        DumpDeathDebugState($"未完成 completeNum={completeNum}/{deadPackList.Count} checkCount={deathDebugNotFinishedCount}");
    }
    private void DumpDeathDebugState(string reason)
    {
        var sb = new System.Text.StringBuilder();
        sb.Append($"[DeathRecordAction诊断] {reason} actionID={actionID} ");
        sb.Append($"deadPackList.Count={deadPackList?.Count ?? 0} ");
        sb.Append($"deathActionDict.Count={deathActionDict.Count} ");
        sb.Append($"deadActionStatesDict.Count={deadActionStatesDict.Count} ");
        sb.Append($"hasDeathTriggerSkill={hasDeathTriggerSkill} ");
        sb.Append($"hasDistributedPacksAfterDeath={hasDistributedPacksAfterDeath} ");
        sb.Append($"parentSkillAction={(parentSkillAction == null ? "null" : FormatSkillActionForDebug(parentSkillAction))} ");
        sb.Append($"playSkillRecordAction={(playSkillRecordAction == null ? "null" : FormatSkillActionForDebug(playSkillRecordAction))}");
        if (deadPackList != null)
        {
            for (int i = 0; i < deadPackList.Count; i++)
            {
                BattleDeadPack deadPack = deadPackList[i];
                sb.Append("\n  [").Append(i).Append("] ").Append(FormatDeadPackForDebug(deadPack));
                if (deathActionDict.TryGetValue(deadPack, out var skillAction))
                {
                    sb.Append(" deathSkill=").Append(FormatSkillActionForDebug(skillAction));
                }
                sb.Append(" hasState=").Append(deadActionStatesDict.ContainsKey(deadPack));
            }
        }
        // BattleDebug.LogError(sb.ToString());
    }
    private string FormatDeadPackForDebug(BattleDeadPack deadPack)
    {
        ulong objId = GetDeadObjIdForDebug(deadPack);
        BattleObject deadObj = null;
        if (battleField != null && battleField.battleObjMgr != null && objId > 0)
        {
            deadObj = battleField.battleObjMgr.GetBattleObject((int)objId);
        }
        string objName = deadObj?.GetName() ?? "null";
        bool canStartDeath = deadObj != null && deadObj.CanStartDeath();
        bool isReborning = deadObj != null && deadObj.IsReborning();
        bool isDead = deadObj != null && deadObj.IsDead();
        long curHp = deadObj != null ? deadObj.GetCurHp() : 0;
        int afterDeathCount = deadPack?.packListAfterDeath?.Count ?? 0;
        string triggerTag = deadPack?.deadTriggerSkill?.startTag?.Tag ?? "null";
        return $"objId={objId} name={objName} hp={curHp} isDead={isDead} canStartDeath={canStartDeath} isReborning={isReborning} hasDeadTriggerSkill={deadPack?.deadTriggerSkill != null} triggerTag={triggerTag} isPlaySkill={deadPack?.isPlaySkill ?? false} afterDeathCount={afterDeathCount}";
    }
    private ulong GetDeadObjIdForDebug(BattleDeadPack deadPack)
    {
        if (deadPack == null || deadPack.deadPack == null)
        {
            return 0;
        }
        return deadPack.deadPack.ObjID;
    }
    private string FormatSkillActionForDebug(SkillRecordAction skillAction)
    {
        if (skillAction == null)
        {
            return "null";
        }
        SkillBase skillBase = skillAction.skillBase;
        if (skillBase == null)
        {
            return $"SkillRecordAction(actionID={skillAction.actionID}, skillBase=null)";
        }
        int skillId = skillBase.skillConfig != null ? skillBase.skillConfig.SkillID : 0;
        ulong casterId = skillBase.tagUseSkillAttack != null ? skillBase.tagUseSkillAttack.ObjID : 0;
        return $"SkillRecordAction(actionID={skillAction.actionID}, skillId={skillId}, caster={casterId}, StateFlags={skillBase.StateFlagsForDebug}, IsActionCompleted={skillAction.IsActionCompleted()})";
    }
    private ulong GetPackUIDForDebug(GameNetPackBasic pack)
    {
        if (pack == null)
        {
            return 0;
        }
        if (pack is HB422_tagMCTurnFightObjDead deadPack)
        {
            return deadPack.packUID;
        }
        if (pack is CustomHB426CombinePack combinePack)
        {
            return combinePack.packUID;
        }
        var packUIDField = pack.GetType().GetField("packUID");
        if (packUIDField == null)
        {
            return 0;
        }
        object value = packUIDField.GetValue(pack);
        if (value is ulong ulongValue)
        {
            return ulongValue;
        }
        if (value is uint uintValue)
        {
            return uintValue;
        }
        if (value is int intValue)
        {
            return (ulong)intValue;
        }
        return 0;
    }
    /// <summary>
    /// 首次运行时打印日志(仅编辑器)
    /// 打印死亡对象的名字
Main/System/Battle/BattleObject/HeroBattleObject.cs
@@ -476,6 +476,12 @@
    public override void OnDeath(Action _onDeathAnimationComplete, bool withoutAnime = false)
    {
#if UNITY_EDITOR
        if (Launch.Instance != null && Launch.Instance.isOpenBattleDebug)
        {
            // BattleDebug.LogError($"[HeroBattleObject死亡诊断] OnDeath objId={ObjID} name={GetName()} hp={GetCurHp()} isDead={IsDead()} isReborning={IsReborning()} withoutAnime={withoutAnime} motionHash={motionBase?.GetHashCode() ?? 0}");
        }
#endif
        buffMgr.RemoveAllBuff();
        battleField.soundManager.PlayEffectSound(teamHero.heroConfig.DeathSFX, false);
        if (withoutAnime)
@@ -487,6 +493,12 @@
        {
            motionBase.PlayDeadAnimation(() =>
            {
#if UNITY_EDITOR
                if (Launch.Instance != null && Launch.Instance.isOpenBattleDebug)
                {
                    // BattleDebug.LogError($"[HeroBattleObject死亡诊断] 死亡动画回调 objId={ObjID} name={GetName()} hp={GetCurHp()} isDead={IsDead()} isReborning={IsReborning()} motionHash={motionBase?.GetHashCode() ?? 0}");
                }
#endif
                SetDeath();
                _onDeathAnimationComplete?.Invoke();
            });
@@ -511,6 +523,12 @@
    //  释放者就是复活者时调用
    public override void PreReborn(bool reviveSelf = false)
    {
#if UNITY_EDITOR
        if (Launch.Instance != null && Launch.Instance.isOpenBattleDebug)
        {
            // BattleDebug.LogError($"[HeroBattleObject死亡诊断] PreReborn objId={ObjID} name={GetName()} hp={GetCurHp()} isDead={IsDead()} isReborning={IsReborning()} reviveSelf={reviveSelf} motionHash={motionBase?.GetHashCode() ?? 0}");
        }
#endif
        heroGo.SetActive(true);
        motionBase.skeletonAnim.skeleton.A = 0f;
        motionBase.skeletonAnim.LateUpdate();
@@ -521,6 +539,12 @@
    //  复活action
    public override void OnReborn(HB427_tagSCUseSkill.tagSCUseSkillHurt vNetData, bool reviveSelf = false, RecordAction parentAction = null)
    {
#if UNITY_EDITOR
        if (Launch.Instance != null && Launch.Instance.isOpenBattleDebug)
        {
            // BattleDebug.LogError($"[HeroBattleObject死亡诊断] OnReborn objId={ObjID} name={GetName()} hp={GetCurHp()} isDead={IsDead()} isReborning={IsReborning()} reviveSelf={reviveSelf} motionHash={motionBase?.GetHashCode() ?? 0}");
        }
#endif
        isReborning = true;
        heroGo.SetActive(true);
        motionBase.ResetForReborn(reviveSelf);
@@ -531,6 +555,12 @@
    public override void AfterReborn()
    {
#if UNITY_EDITOR
        if (Launch.Instance != null && Launch.Instance.isOpenBattleDebug)
        {
            // BattleDebug.LogError($"[HeroBattleObject死亡诊断] AfterReborn objId={ObjID} name={GetName()} hp={GetCurHp()} isDead={IsDead()} isReborning={IsReborning()} motionHash={motionBase?.GetHashCode() ?? 0}");
        }
#endif
        //  清空所有
        motionBase.ResetForReborn(false);
        isReborning = false;
Main/System/Battle/Motion/MotionBase.cs
@@ -63,8 +63,8 @@
            {
                string owner = skeletonAnim != null && skeletonAnim.gameObject != null ? skeletonAnim.gameObject.name : "(no-skel)";
                // 每次变更打印栈,定位到底是谁把 playingSkillWithAnim 设 true 了、谁清/不清
                BattleDebug.LogError($"[MotionBase owner={owner} hash={GetHashCode()}] playingSkillWithAnim {_playingSkillWithAnim} -> {value}\n"
                    + UnityEngine.StackTraceUtility.ExtractStackTrace());
                // BattleDebug.LogError($"[MotionBase owner={owner} hash={GetHashCode()}] playingSkillWithAnim {_playingSkillWithAnim} -> {value}\n"
                //     + UnityEngine.StackTraceUtility.ExtractStackTrace());
            }
            //  [卡死诊断] 维护锁的持有者信息(上一轮的字段被回滚了,这里是最小必要集合)
            if (value && !_playingSkillWithAnim)
@@ -161,7 +161,14 @@
    public virtual Spine.TrackEntry PlayDeadAnimation(Action onComplete = null)
    {
        if (animState == null) return null;
        if (animState == null)
        {
#if UNITY_EDITOR
            LogDeathTrackDebug("PlayDeadAnimation animState=null,直接完成死亡回调", null, false);
#endif
            onComplete?.Invoke();
            return null;
        }
        
        // 使用轨道9作为死亡动画专用轨道(独立于主轨道0和子技能轨道1-8)
        
@@ -169,7 +176,12 @@
        if (activeSkillTracks.TryGetValue(DeathTrackIndex, out var oldDeathTrack))
        {
            if (trackEntryCallbacks.ContainsKey(oldDeathTrack))
            {
#if UNITY_EDITOR
                LogDeathTrackDebug("PlayDeadAnimation 替换旧死亡轨道,移除旧回调", oldDeathTrack, true);
#endif
                trackEntryCallbacks.Remove(oldDeathTrack);
            }
        }
        
        Spine.Animation deadAnim = FindAnim(MotionName.dead.ToString());
@@ -189,9 +201,42 @@
            if (onComplete != null)
                trackEntryCallbacks[deathTrack] = onComplete;
        }
#if UNITY_EDITOR
        LogDeathTrackDebug("PlayDeadAnimation 创建死亡轨道", deathTrack, onComplete != null && deathTrack != null);
#endif
        if (deathTrack == null)
        {
            onComplete?.Invoke();
        }
        
        return deathTrack;
    }
#if UNITY_EDITOR
    private void LogDeathTrackDebug(string reason, Spine.TrackEntry trackEntry, bool hasCallback)
    {
        if (Launch.Instance == null || !Launch.Instance.isOpenBattleDebug)
        {
            return;
        }
        string owner = skeletonAnim != null && skeletonAnim.gameObject != null ? skeletonAnim.gameObject.name : "(no-skel)";
        // BattleDebug.LogError($"[MotionBase死亡诊断] {reason} owner={owner} hash={GetHashCode()} track={FormatTrackForDebug(trackEntry)} hasCallback={hasCallback} callbacks={trackEntryCallbacks.Count} activeSkillTracks={activeSkillTracks.Count}");
    }
    private string FormatTrackForDebug(Spine.TrackEntry trackEntry)
    {
        if (trackEntry == null)
        {
            return "null";
        }
        string animName = trackEntry.Animation != null ? trackEntry.Animation.Name : "(null)";
        float duration = trackEntry.Animation != null ? trackEntry.Animation.Duration : -1f;
        return $"hash={trackEntry.GetHashCode()} anim={animName} TrackTime={trackEntry.TrackTime:F3} duration={duration:F3} isComplete={trackEntry.IsComplete} timeScale={trackEntry.TimeScale}";
    }
#endif
    private void AddAction(Action action) => runningActions.Add(action);
    private void RemoveAction(Action action) => runningActions.Remove(action);
@@ -636,6 +681,13 @@
        string animName = trackEntry.Animation.Name.ToLower();
#if UNITY_EDITOR
        if (animName == MotionName.dead.ToString().ToLower())
        {
            LogDeathTrackDebug("OnAnimationComplete 收到死亡动画完成", trackEntry, trackEntryCallbacks.ContainsKey(trackEntry));
        }
#endif
        if (AttackMotionList.Contains(animName))
        {
            OnAttackAnimationComplete?.Invoke();
@@ -734,7 +786,7 @@
        if (Launch.Instance != null && Launch.Instance.isOpenBattleDebug)
        {
            string owner = skeletonAnim != null && skeletonAnim.gameObject != null ? skeletonAnim.gameObject.name : "(no-skel)";
            BattleDebug.LogError($"[MotionBase.HaveRest owner={owner} hash={GetHashCode()}] 调用栈:\n{UnityEngine.StackTraceUtility.ExtractStackTrace()}");
            // BattleDebug.LogError($"[MotionBase.HaveRest owner={owner} hash={GetHashCode()}] 调用栈:\n{UnityEngine.StackTraceUtility.ExtractStackTrace()}");
        }
#endif
        // 先强制结束所有正在运行的 skillBase,避免清字典后这些 skillBase 的 frameHandler 被删但自身 StateFlags 永远停在 Started
@@ -789,7 +841,14 @@
        if (Launch.Instance != null && Launch.Instance.isOpenBattleDebug)
        {
            string owner = skeletonAnim != null && skeletonAnim.gameObject != null ? skeletonAnim.gameObject.name : "(no-skel)";
             BattleDebug.LogError($"[MotionBase.ResetForReborn owner={owner} hash={GetHashCode()}] reviveSelf={reviveSelf} 调用栈:\n{UnityEngine.StackTraceUtility.ExtractStackTrace()}");
             // BattleDebug.LogError($"[MotionBase.ResetForReborn owner={owner} hash={GetHashCode()}] reviveSelf={reviveSelf} 调用栈:\n{UnityEngine.StackTraceUtility.ExtractStackTrace()}");
            Spine.TrackEntry deathTrack = null;
            bool hasDeathTrack = activeSkillTracks.TryGetValue(DeathTrackIndex, out deathTrack);
            bool hasDeathCallback = hasDeathTrack && deathTrack != null && trackEntryCallbacks.ContainsKey(deathTrack);
            if (hasDeathTrack || hasDeathCallback || trackEntryCallbacks.Count > 0)
            {
                // BattleDebug.LogError($"[MotionBase死亡诊断] ResetForReborn 即将清理轨道 owner={owner} hash={GetHashCode()} reviveSelf={reviveSelf} hasDeathTrack={hasDeathTrack} hasDeathCallback={hasDeathCallback} callbacks={trackEntryCallbacks.Count} activeSkillTracks={activeSkillTracks.Count} deathTrack={FormatTrackForDebug(deathTrack)}");
            }
        }
#endif
        // 跟 HaveRest 同理:先强制结束所有正在运行的 skillBase,
Main/System/Battle/Skill/SkillBase.Cast.cs
@@ -18,7 +18,7 @@
                ? caster.GetRectTransform().anchoredPosition : Vector2.zero;
            bool casterTweening = caster != null && caster.GetRectTransform() != null
                && DG.Tweening.DOTween.IsTweening(caster.GetRectTransform());
            BattleDebug.LogError($"[前冲诊断] Cast 入口 skillId={skillConfig?.SkillID} caster={caster?.ObjID} battleType={tagUseSkillAttack?.BattleType} castMode={skillSkinConfig?.castMode} anchoredPos={castEntryPos} casterTweening={casterTweening}");
            // BattleDebug.LogError($"[前冲诊断] Cast 入口 skillId={skillConfig?.SkillID} caster={caster?.ObjID} battleType={tagUseSkillAttack?.BattleType} castMode={skillSkinConfig?.castMode} anchoredPos={castEntryPos} casterTweening={casterTweening}");
        }
#endif
        // 广播技能释放事件
@@ -175,13 +175,13 @@
    private void ExecuteMoveAndCastSequence(RectTransform target, Action onReturnComplete)
    {
#if UNITY_EDITOR
        BattleDebug.LogError($"[前冲诊断] ExecuteMoveAndCastSequence 开始 skillId={skillConfig?.SkillID} caster={caster?.ObjID} battleType={tagUseSkillAttack?.BattleType} CastDistance={skillSkinConfig?.CastDistance} castMode={skillSkinConfig?.castMode}");
        // BattleDebug.LogError($"[前冲诊断] ExecuteMoveAndCastSequence 开始 skillId={skillConfig?.SkillID} caster={caster?.ObjID} battleType={tagUseSkillAttack?.BattleType} CastDistance={skillSkinConfig?.CastDistance} castMode={skillSkinConfig?.castMode}");
#endif
        ShadowIllutionCreate(true);
        MoveToTarget(target, new Vector2(skillSkinConfig.CastDistance, 0), () =>
        {
#if UNITY_EDITOR
            BattleDebug.LogError($"[前冲诊断] 前冲完成 skillId={skillConfig?.SkillID} caster={caster?.ObjID} 准备 CastImpl");
            // BattleDebug.LogError($"[前冲诊断] 前冲完成 skillId={skillConfig?.SkillID} caster={caster?.ObjID} 准备 CastImpl");
#endif
            if (skillSkinConfig.CastDistance < 9999 && skillSkinConfig.SkinllSFX2 != 0)
            {
@@ -220,12 +220,12 @@
            ? caster.GetRectTransform().anchoredPosition : Vector2.zero;
        bool mttTweening = caster != null && caster.GetRectTransform() != null
            && DG.Tweening.DOTween.IsTweening(caster.GetRectTransform());
        BattleDebug.LogError($"[前冲诊断] MoveToTarget 入口 skillId={skillConfig?.SkillID} caster={caster?.ObjID} battleType={tagUseSkillAttack?.BattleType} CastDistance={skillSkinConfig?.CastDistance} offset={offset} speed={speed} fromPos={fromPos} casterTweening={mttTweening}");
        // BattleDebug.LogError($"[前冲诊断] MoveToTarget 入口 skillId={skillConfig?.SkillID} caster={caster?.ObjID} battleType={tagUseSkillAttack?.BattleType} CastDistance={skillSkinConfig?.CastDistance} offset={offset} speed={speed} fromPos={fromPos} casterTweening={mttTweening}");
#endif
        if (skillSkinConfig.CastDistance >= 9999)
        {
#if UNITY_EDITOR
            BattleDebug.LogError($"[前冲诊断] CastDistance>=9999 直接跳过移动 skillId={skillConfig?.SkillID} caster={caster?.ObjID}");
            // BattleDebug.LogError($"[前冲诊断] CastDistance>=9999 直接跳过移动 skillId={skillConfig?.SkillID} caster={caster?.ObjID}");
#endif
            _onComplete?.Invoke();
            return;
@@ -238,14 +238,14 @@
        Vector3 targetWorld = target != null ? target.position : Vector3.zero;
        Vector3 targetLossyScale = target != null ? (Vector3)target.lossyScale : Vector3.one;
        Vector2 targetAnchored = target != null ? target.anchoredPosition : Vector2.zero;
        BattleDebug.LogError($"[前冲诊断] target信息 skillId={skillConfig?.SkillID} caster={caster?.ObjID} casterCamp={caster?.Camp} target.name={targetName} target.anchoredPos={targetAnchored} target.worldPos={targetWorld} target.lossyScale={targetLossyScale}");
        // BattleDebug.LogError($"[前冲诊断] target信息 skillId={skillConfig?.SkillID} caster={caster?.ObjID} casterCamp={caster?.Camp} target.name={targetName} target.anchoredPos={targetAnchored} target.worldPos={targetWorld} target.lossyScale={targetLossyScale}");
#endif
        var tweener = BattleUtility.MoveToTarget(caster.GetRectTransform(), target, offset, () =>
        {
#if UNITY_EDITOR
            Vector2 toPos = caster != null && caster.GetRectTransform() != null
                ? caster.GetRectTransform().anchoredPosition : Vector2.zero;
            BattleDebug.LogError($"[前冲诊断] MoveToTarget 完成 skillId={skillConfig?.SkillID} caster={caster?.ObjID} toPos={toPos}");
            // BattleDebug.LogError($"[前冲诊断] MoveToTarget 完成 skillId={skillConfig?.SkillID} caster={caster?.ObjID} toPos={toPos}");
#endif
            //  tween 完成时清除 caster 上的 activeMoveTween 句柄,放开 CanCastSkillAnimation 的闸门。
            if (caster != null)
@@ -283,7 +283,7 @@
                ? caster.GetRectTransform().anchoredPosition : Vector2.zero;
            bool finTweening = caster != null && caster.GetRectTransform() != null
                && DG.Tweening.DOTween.IsTweening(caster.GetRectTransform());
            BattleDebug.LogError($"[前冲诊断] OnAttackFinish skillId={skillConfig?.SkillID} caster={caster?.ObjID} battleType={tagUseSkillAttack?.BattleType} anchoredPos={finPos} casterTweening={finTweening}");
            // BattleDebug.LogError($"[前冲诊断] OnAttackFinish skillId={skillConfig?.SkillID} caster={caster?.ObjID} battleType={tagUseSkillAttack?.BattleType} anchoredPos={finPos} casterTweening={finTweening}");
        }
#endif
        TurnBack(null, 1f);
Main/System/Battle/Skill/SkillBase.Finish.cs
@@ -131,7 +131,7 @@
    /// </summary>
    private void ReportStuckIfNeeded(string reason)
    {
#if UNITY_EDITOR
#if false && UNITY_EDITOR
        _stuckCheckCount++;
        //  阻塞原因切换了 → 立刻打一次,并重置计数
@@ -210,19 +210,19 @@
        string skinInfo = $"  skillSkinConfig.SkillMotionName={(skillSkinConfig == null ? "null" : (string.IsNullOrEmpty(skillSkinConfig.SkillMotionName) ? "(空)" : skillSkinConfig.SkillMotionName))}";
        string skillEffectDump = skillEffect == null ? "  skillEffect=null" : $"  skillEffect: {skillEffect.DumpState()}";
        BattleDebug.LogError(
            "SkillBase.IsFinished 疑似卡死 (持续 " + _stuckCheckCount + " 次未完成)\n" +
            $"  skillId={skillId} caster={casterId} 原因: {reason}\n" +
            $"  StateFlags={_stateFlags}\n" +
            $"{skillEffectDump}\n" +
            $"{skinInfo}\n" +
            $"{casterAnim}\n" +
            $"  currentWaitingSkill.Count={currentWaitingSkill.Count}\n{subSkillDump}\n" +
            $"  packList.Count={(packList?.Count ?? 0)}\n{packListDump}\n" +
            $"  innerRecordPlayer.IsPlaying={innerPlaying}\n" +
            $"{innerPlayerDump}\n" +
            $"  tempDeadPackList.Count={tempDeadPackList.Count}\n" +
            $"  buffPackCollections.Count={buffPackCollections.Count}");
        // BattleDebug.LogError(
        //     "SkillBase.IsFinished 疑似卡死 (持续 " + _stuckCheckCount + " 次未完成)\n" +
        //     $"  skillId={skillId} caster={casterId} 原因: {reason}\n" +
        //     $"  StateFlags={_stateFlags}\n" +
        //     $"{skillEffectDump}\n" +
        //     $"{skinInfo}\n" +
        //     $"{casterAnim}\n" +
        //     $"  currentWaitingSkill.Count={currentWaitingSkill.Count}\n{subSkillDump}\n" +
        //     $"  packList.Count={(packList?.Count ?? 0)}\n{packListDump}\n" +
        //     $"  innerRecordPlayer.IsPlaying={innerPlaying}\n" +
        //     $"{innerPlayerDump}\n" +
        //     $"  tempDeadPackList.Count={tempDeadPackList.Count}\n" +
        //     $"  buffPackCollections.Count={buffPackCollections.Count}");
#endif
    }