{"record":{"id":"796ddebc23b462e4","repo":"babalae/better-genshin-impact","slug":"error-796dde","errorCode":null,"errorMessage":"切换角色：换下错位角色后无法确定需要补回的槽位","messagePattern":"切换角色：换下错位角色后无法确定需要补回的槽位","errorType":"exception","errorClass":"PartySetupFailedException","httpStatus":null,"severity":"error","filePath":"BetterGenshinImpact/GameTask/Common/Job/SwitchCharacterStateMachineTask.cs","lineNumber":604,"sourceCode":"    private async Task<StateHandlerResult> HandlePrepareNextRole(BvPage page)\n    {\n        if (_teamSnapshotDirty)\n        {\n            _currentTeamSlots = await RecognizeTeamSlotsFromCharacterList(Recognizer, _expectedTeamCount, _ct);\n            _teamSnapshotDirty = false;\n            if (_needsFinalVerification)\n            {\n                _logger.LogInformation(\"切换角色：已完成最终队伍识别，按实际队伍继续验证目标槽位\");\n                _needsFinalVerification = false;\n            }\n        }\n\n        if (_prepareSuffixRebuildAfterRemoval)\n        {\n            var firstMismatchSlot = FindFirstMismatchSlot(_targetRoles, _currentTeamSlots);\n            if (firstMismatchSlot == null)\n            {\n                throw new PartySetupFailedException(\"切换角色：换下错位角色后无法确定需要补回的槽位\");\n            }\n\n            StartSuffixRebuild(firstMismatchSlot.Value);\n            _prepareSuffixRebuildAfterRemoval = false;\n        }\n\n        if (_rebuildStartSlot is int rebuildStartSlot)\n        {\n            var minimumRetainedCount = Math.Max(1, rebuildStartSlot - 1);\n            if (_isRebuildClearing && _currentTeamSlots.Count > minimumRetainedCount)\n            {\n                ClickFixedTeamSlot(rebuildStartSlot);\n                await Delay(500, _ct);\n                if (!TryClickText(page, \"换下\", Rect1080(382, 994, 87, 51)))\n                {\n                    throw new PartySetupFailedException($\"切换角色：未找到 {rebuildStartSlot} 号位的换下按钮\");\n                }\n","sourceCodeStart":586,"sourceCodeEnd":622,"githubUrl":"https://github.com/babalae/better-genshin-impact/blob/a7cb36712dcb409be610257d877fcea3597e9d6b/BetterGenshinImpact/GameTask/Common/Job/SwitchCharacterStateMachineTask.cs#L586-L622","documentation":"Thrown during HandlePrepareNextRole after a misplaced role was removed (_prepareSuffixRebuildAfterRemoval) when FindFirstMismatchSlot returns null — i.e. the freshly-recognized current team already satisfies all target slots, so the state machine cannot decide which slot to rebuild from.","triggerScenarios":"_prepareSuffixRebuildAfterRemoval is true, the team is re-recognized, but FindFirstMismatchSlot(_targetRoles, _currentTeamSlots) returns null (no mismatch). The machine expected to refill a cleared suffix but the targets already match, leaving no rebuild anchor.","commonSituations":"Team recognition mis-identified a role as already matching (OCR/recognizer false positive) right after the removal; target roles were satisfied by the retained prefix unexpectedly; a race where _targetRoles was mutated; recognizer returned a name that Matches() the target by alias coincidence.","solutions":["When FindFirstMismatchSlot is null after a removal, treat the team as complete and transition to completion instead of throwing.","Re-run RecognizeTeamSlotsFromCharacterList once more to rule out a transient recognition error before deciding.","Loosen/verify the TargetRole.Matches logic so a near-name does not falsely satisfy a slot.","Log _currentTeamSlots vs _targetRoles at this point to diagnose the false-match."],"exampleFix":"// before\nvar firstMismatchSlot = FindFirstMismatchSlot(_targetRoles, _currentTeamSlots);\nif (firstMismatchSlot == null)\n{\n    throw new PartySetupFailedException(\"切换角色：换下错位角色后无法确定需要补回的槽位\");\n}\nStartSuffixRebuild(firstMismatchSlot.Value);\n\n// after: a null mismatch means the team already matches -> proceed\nvar firstMismatchSlot = FindFirstMismatchSlot(_targetRoles, _currentTeamSlots);\nif (firstMismatchSlot == null)\n{\n    _logger.LogInformation(\"切换角色：换下错位角色后队伍已满足目标，跳过补回\");\n    _workflowState = SwitchCharacterState.ReturnMainUi;\n    return StateHandlerResult.Success;\n}\nStartSuffixRebuild(firstMismatchSlot.Value);","handlingStrategy":"fallback","validationCode":"null","typeGuard":"null","tryCatchPattern":"try { await sm.Start(s1, s2, s3, s4, usePhysicalSlots, ct); }\ncatch (PartySetupFailedException e) when (e.Message.Contains(\"无法确定需要补回的槽位\"))\n{ /* re-recognize the team; if it matches targets, accept; else restart the switch */ }","preventionTips":["When no mismatch is found after a removal, treat the team as complete rather than throwing.","Re-run team recognition once to rule out a transient misread before deciding.","Review TargetRole.Matches so a near-name does not falsely satisfy a slot."],"tags":["party-setup","state-machine","recognition","logic"],"backgroundTag":null,"analyzedSha":"a7cb36712dcb409be610257d877fcea3597e9d6b","analyzedAt":"2026-08-13T16:44:57.548Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}