{"record":{"id":"05cc54a313a64091","repo":"babalae/better-genshin-impact","slug":"error-05cc54","errorCode":null,"errorMessage":"切换角色：当前角色状态为空","messagePattern":"切换角色：当前角色状态为空","errorType":"exception","errorClass":"PartySetupFailedException","httpStatus":null,"severity":"critical","filePath":"BetterGenshinImpact/GameTask/Common/Job/SwitchCharacterStateMachineTask.cs","lineNumber":828,"sourceCode":"        }\n\n        _pendingFilterElementType = null;\n        _pendingFilterWeaponType = null;\n        _workflowState = SwitchCharacterState.FindAndClickAvatar;\n        return Task.FromResult(StateHandlerResult.Success);\n    }\n\n    /// <summary>\n    /// 查找并点击当前角色头像。\n    /// </summary>\n    /// <param name=\"page\">页面操作对象。</param>\n    /// <returns>头像查找并提交完成后进入准备下一个目标角色状态。</returns>\n    [StateHandler(SwitchCharacterState.FindAndClickAvatar, RetryTimeout = 12000, RetryInterval = 300, TransitionTimeout = 3000)]\n    private async Task<StateHandlerResult> HandleFindAndClickAvatar(BvPage page)\n    {\n        if (_currentRole == null)\n        {\n            throw new PartySetupFailedException(\"切换角色：当前角色状态为空\");\n        }\n\n        var avatarFound = false;\n        if (_currentRole.UseFriendshipSort)\n        {\n            await EnsureFriendshipSort(page);\n            for (var attempt = 1; attempt <= FriendshipSortSearchAttemptCount; attempt++)\n            {\n                ClickSortDirectionButton();\n                if (await FindAndClickAvatar(_currentRole, Recognizer, _ct))\n                {\n                    avatarFound = true;\n                    break;\n                }\n\n                _logger.LogDebug(\n                    \"切换角色：好感排序第 {Attempt}/{AttemptCount} 次未找到 {Name}\",\n                    attempt,","sourceCodeStart":810,"sourceCodeEnd":846,"githubUrl":"https://github.com/babalae/better-genshin-impact/blob/a7cb36712dcb409be610257d877fcea3597e9d6b/BetterGenshinImpact/GameTask/Common/Job/SwitchCharacterStateMachineTask.cs#L810-L846","documentation":"HandleFindAndClickAvatar asserts _currentRole is non-null. This is a state-machine invariant: the FindAndClickAvatar state should only be active after PrepareNextRole/OpenFilterPanel set a current role. A null here means the detectors routed to this handler with no role selected — a logic/transition bug rather than a normal runtime condition.","triggerScenarios":"DetectFindAndClickAvatar returned true (workflowState == FindAndClickAvatar and IsCharacterList && !IsFilterPanel) while _currentRole was still null — e.g. PrepareNextRole deferred role selection and the workflowState was set to FindAndClickAvatar directly.","commonSituations":"A code change that sets _workflowState = FindAndClickAvatar without calling SetCurrentRole first; a rebuild/append path that clears _currentRole but leaves the workflow state pointing at avatar-finding; an unhandled transition into this state.","solutions":["Treat as a bug: trace every assignment to _workflowState = SwitchCharacterState.FindAndClickAvatar and confirm each is preceded by SetCurrentRole.","If a soft recovery is acceptable, return StateHandlerResult.Retry and re-run PrepareNextRole instead of throwing, so the machine re-picks a role.","Add a unit test over the transition table proving FindAndClickAvatar is only reachable after a role is bound."],"exampleFix":"// before\nif (_currentRole == null)\n{\n    throw new PartySetupFailedException(\"切换角色：当前角色状态为空\");\n}\n\n// after\nif (_currentRole == null)\n{\n    _workflowState = SwitchCharacterState.PrepareNextRole;\n    return StateHandlerResult.Retry;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await switchTask.Start(...); }\ncatch (PartySetupFailedException ex) when (ex.Message.Contains(\"当前角色状态为空\"))\n{ _logger.LogError(ex, \"状态机逻辑异常：FindAndClickAvatar 在未选定角色时触发，请提交 bug\"); }","preventionTips":["Ensure every _workflowState = FindAndClickAvatar assignment is preceded by SetCurrentRole.","Consider returning StateHandlerResult.Retry to re-enter PrepareNextRole instead of throwing on this invariant.","Add transition-table tests proving FindAndClickAvatar only follows a role-binding state."],"tags":["party-setup","state-machine","invariant","logic-bug","switch-character"],"backgroundTag":null,"analyzedSha":"a7cb36712dcb409be610257d877fcea3597e9d6b","analyzedAt":"2026-08-13T16:44:57.548Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}