{"record":{"id":"dc6f514b353d965e","repo":"babalae/better-genshin-impact","slug":"nextrole-slot-maxroleswitchattempts","errorCode":null,"errorMessage":"切换角色：{nextRole.Slot} 号位连续 {MaxRoleSwitchAttempts} 次未能切换为 {nextRole.Name}","messagePattern":"切换角色：(.+?) 号位连续 (.+?) 次未能切换为 (.+?)","errorType":"exception","errorClass":"PartySetupFailedException","httpStatus":null,"severity":"error","filePath":"BetterGenshinImpact/GameTask/Common/Job/SwitchCharacterStateMachineTask.cs","lineNumber":665,"sourceCode":"            _rebuildStartSlot = null;\n        }\n\n        if (_currentRole == null)\n        {\n            var currentNameBySlot = _currentTeamSlots.ToDictionary(slot => slot.Slot, slot => slot.Name);\n            var nextRole = _requestedTargetRoles\n                .OrderBy(role => role.Slot)\n                .FirstOrDefault(role =>\n                    !currentNameBySlot.TryGetValue(role.Slot, out var currentName) || !role.Matches(currentName))\n                ?? _targetRoles\n                .OrderBy(role => role.Slot)\n                .FirstOrDefault(role =>\n                    !currentNameBySlot.TryGetValue(role.Slot, out var currentName) || !role.Matches(currentName));\n            if (nextRole != null)\n            {\n                if (_roleSwitchAttempts.GetValueOrDefault(nextRole.Slot) >= MaxRoleSwitchAttempts)\n                {\n                    throw new PartySetupFailedException(\n                        $\"切换角色：{nextRole.Slot} 号位连续 {MaxRoleSwitchAttempts} 次未能切换为 {nextRole.Name}\");\n                }\n\n                SetCurrentRole(nextRole);\n            }\n        }\n\n        if (_currentRole == null)\n        {\n            if (_needsFinalVerification)\n            {\n                _logger.LogDebug(\"切换角色：所有目标已提交，开始统一识别并验证实际队伍\");\n                _teamSnapshotDirty = true;\n                return StateHandlerResult.Wait;\n            }\n\n            _workflowState = SwitchCharacterState.ReturnMainUi;\n            return StateHandlerResult.Success;","sourceCodeStart":647,"sourceCodeEnd":683,"githubUrl":"https://github.com/babalae/better-genshin-impact/blob/a7cb36712dcb409be610257d877fcea3597e9d6b/BetterGenshinImpact/GameTask/Common/Job/SwitchCharacterStateMachineTask.cs#L647-L683","documentation":"Thrown during the normal PrepareNextRole selection when a slot's retry budget is exhausted: the next role to switch (_requestedTargetRoles or _targetRoles) has _roleSwitchAttempts[slot] >= MaxRoleSwitchAttempts (3). The state machine could not switch that slot to the requested role in 3 tries.","triggerScenarios":"In HandlePrepareNextRole, with _currentRole == null, a mismatched nextRole is found and _roleSwitchAttempts[nextRole.Slot] >= 3. Across 3 OpenFilterPanel->select->save cycles the slot never settled on a role that Matches(nextRole).","commonSituations":"The role is not owned; the element/weapon filter for the role is mis-detected so it never appears in the grid; AvatarGridIconRecognizer mis-reads the placed icon; alias resolution produced a name absent from the roster; a UI transition was dropped so the save never committed.","solutions":["Verify the role name is owned and that ToConfiguredAvatarName/alias resolution yields a valid avatar.","Check Recognizer.GetWeaponType/GetElementType for the role — a wrong filter excludes it from the grid.","Increase MaxRoleSwitchAttempts or downgrade the failure to a skip-and-continue for non-critical slots.","Log the recognized slot name each attempt to pinpoint whether selection or recognition is failing."],"exampleFix":"null","handlingStrategy":"try-catch","validationCode":"// Validate roster ownership and alias resolution before starting\nforeach (var name in new[] { slot1, slot2, slot3, slot4 }.Where(s => !string.IsNullOrWhiteSpace(s)))\n{\n    var standard = ToConfiguredAvatarName(name.Trim());\n    if (!DefaultAutoFightConfig.CombatAvatarMap.ContainsKey(standard) && !IsOwnedAvatar(standard))\n        throw new ArgumentException($\"账号未拥有角色：{standard}\");\n}","typeGuard":"static bool RoleIsOwned(string name)\n{\n    var standard = ToConfiguredAvatarName(name.Trim());\n    return DefaultAutoFightConfig.CombatAvatarMap.ContainsKey(standard) || IsOwnedAvatar(standard);\n}","tryCatchPattern":"try { await sm.Start(s1, s2, s3, s4, usePhysicalSlots, ct); }\ncatch (PartySetupFailedException e) when (e.Message.Contains(\"未能切换为\"))\n{ /* degrade gracefully: skip the slot or surface a user-facing warning */ }","preventionTips":["Ensure every requested role is owned and its alias resolves to a valid avatar.","Check Recognizer.GetWeaponType/GetElementType so the filter does not hide the role.","Log the recognized slot name each attempt to distinguish selection vs recognition failure."],"tags":["party-setup","state-machine","retry-budget","recognition"],"backgroundTag":null,"analyzedSha":"a7cb36712dcb409be610257d877fcea3597e9d6b","analyzedAt":"2026-08-13T16:44:57.548Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}