Files
SVSimServer/SVSim.EmulatedEntrypoint/Matching/BotRoster.cs
gamer147 d87f9beb81 fix(rank-battle): use prod-verified bot cosmetic ids to unblock LoadOpponentAssets
The "Waiting for opponent" hang traced to BattleStartControl.IsReady never
flipping true. That's gated by SBattleLoad.LoadOpponentAssets which calls
ResourcesManager.LoadAssetGroupSync with the bot's
{rank, emblemId, degreeId, countryCode} — and our placeholder ids (1/1/1/"NONE")
don't resolve to any asset in the client's resource bundle, so the callback
never fires.

Replaced with the Scripted bot's known-good prod values:
- SleeveId: 704141010
- EmblemId: 400001100
- DegreeId: 120027
- FieldId: 5
- CountryCode: "JPN"
- IsOfficial: 0

These are the same ids ScriptedBotParticipant.Context uses, which we know
load fine because the TK2 Scripted flow has been working end-to-end since
Phase 2.

Reference for the load chain (decompiled client):
  BattleUI.WaitForSetUp → m_SBattleLoad.WaitCallBack
    → BattleStartControl.SetUp → CheckAbleToInitialize
    → SBattleLoad.LoadOpponentAssets (SBattleLoad.cs:933)
    → ResourcesManager.LoadAssetGroupSync — hangs on missing assets

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 11:09:20 -04:00

99 lines
6.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using SVSim.BattleNode.Bridge;
namespace SVSim.EmulatedEntrypoint.Matching;
/// <summary>
/// Phase 3 hardcoded fixture: one stub bot per class (1..8). <c>ai_id</c> values are
/// the "series 1" tier from the client's <c>rm_ai_setting.csv</c> master table
/// (<c>1111</c>=Forest, <c>1121</c>=Sword, … <c>1181</c>=Portal). The client's
/// <c>RankMatchAISettingList.GetSettingData(aiId)</c> calls <c>.First(...)</c> against
/// this table and throws <c>InvalidOperationException</c> if the id is absent — so the
/// ids MUST match. See <c>data_dumps/client-assets/rm_ai_setting.csv</c> for the full
/// catalog (32 rows: classes 1-8 × tiers 1-2 × deck variants 1-2).
/// </summary>
public sealed class BotRoster : IBotRoster
{
// Cosmetic ids (sleeve / emblem / degree / field) intentionally use safe
// default values that match the master tables shipped in the project.
// The client-side AI catalog reads ai_id but renders cosmetics from the
// sleeveId/emblemId/etc returned here.
private static readonly IReadOnlyList<AIBotProfile> Roster = new[]
{
new AIBotProfile(AiId: 1111, CountryCode: "JPN", UserName: "Forestcraft AI",
// Cosmetic ids mirror the Scripted bot's prod-verified values
// (ScriptedProfiles / ScriptedBotParticipant.Context). Placeholder
// ids of 1 failed to resolve in ResourcesManager.LoadAssetGroupSync —
// LoadOpponentAssets (SBattleLoad.cs:933) hangs forever waiting for
// missing assets and the "Waiting for opponent" UI never closes.
SleeveId: 704141010, EmblemId: 400001100, DegreeId: 120027, FieldId: 5, IsOfficial: 0,
ClassId: 1, CharaId: 1, Rank: 10, BattlePoint: 0, IsMasterRank: 0, MasterPoint: 0),
new AIBotProfile(AiId: 1121, CountryCode: "JPN", UserName: "Swordcraft AI",
// Cosmetic ids mirror the Scripted bot's prod-verified values
// (ScriptedProfiles / ScriptedBotParticipant.Context). Placeholder
// ids of 1 failed to resolve in ResourcesManager.LoadAssetGroupSync —
// LoadOpponentAssets (SBattleLoad.cs:933) hangs forever waiting for
// missing assets and the "Waiting for opponent" UI never closes.
SleeveId: 704141010, EmblemId: 400001100, DegreeId: 120027, FieldId: 5, IsOfficial: 0,
ClassId: 2, CharaId: 2, Rank: 10, BattlePoint: 0, IsMasterRank: 0, MasterPoint: 0),
new AIBotProfile(AiId: 1131, CountryCode: "JPN", UserName: "Runecraft AI",
// Cosmetic ids mirror the Scripted bot's prod-verified values
// (ScriptedProfiles / ScriptedBotParticipant.Context). Placeholder
// ids of 1 failed to resolve in ResourcesManager.LoadAssetGroupSync —
// LoadOpponentAssets (SBattleLoad.cs:933) hangs forever waiting for
// missing assets and the "Waiting for opponent" UI never closes.
SleeveId: 704141010, EmblemId: 400001100, DegreeId: 120027, FieldId: 5, IsOfficial: 0,
ClassId: 3, CharaId: 3, Rank: 10, BattlePoint: 0, IsMasterRank: 0, MasterPoint: 0),
new AIBotProfile(AiId: 1141, CountryCode: "JPN", UserName: "Dragoncraft AI",
// Cosmetic ids mirror the Scripted bot's prod-verified values
// (ScriptedProfiles / ScriptedBotParticipant.Context). Placeholder
// ids of 1 failed to resolve in ResourcesManager.LoadAssetGroupSync —
// LoadOpponentAssets (SBattleLoad.cs:933) hangs forever waiting for
// missing assets and the "Waiting for opponent" UI never closes.
SleeveId: 704141010, EmblemId: 400001100, DegreeId: 120027, FieldId: 5, IsOfficial: 0,
ClassId: 4, CharaId: 4, Rank: 10, BattlePoint: 0, IsMasterRank: 0, MasterPoint: 0),
new AIBotProfile(AiId: 1151, CountryCode: "JPN", UserName: "Shadowcraft AI",
// Cosmetic ids mirror the Scripted bot's prod-verified values
// (ScriptedProfiles / ScriptedBotParticipant.Context). Placeholder
// ids of 1 failed to resolve in ResourcesManager.LoadAssetGroupSync —
// LoadOpponentAssets (SBattleLoad.cs:933) hangs forever waiting for
// missing assets and the "Waiting for opponent" UI never closes.
SleeveId: 704141010, EmblemId: 400001100, DegreeId: 120027, FieldId: 5, IsOfficial: 0,
ClassId: 5, CharaId: 5, Rank: 10, BattlePoint: 0, IsMasterRank: 0, MasterPoint: 0),
new AIBotProfile(AiId: 1161, CountryCode: "JPN", UserName: "Bloodcraft AI",
// Cosmetic ids mirror the Scripted bot's prod-verified values
// (ScriptedProfiles / ScriptedBotParticipant.Context). Placeholder
// ids of 1 failed to resolve in ResourcesManager.LoadAssetGroupSync —
// LoadOpponentAssets (SBattleLoad.cs:933) hangs forever waiting for
// missing assets and the "Waiting for opponent" UI never closes.
SleeveId: 704141010, EmblemId: 400001100, DegreeId: 120027, FieldId: 5, IsOfficial: 0,
ClassId: 6, CharaId: 6, Rank: 10, BattlePoint: 0, IsMasterRank: 0, MasterPoint: 0),
new AIBotProfile(AiId: 1171, CountryCode: "JPN", UserName: "Havencraft AI",
// Cosmetic ids mirror the Scripted bot's prod-verified values
// (ScriptedProfiles / ScriptedBotParticipant.Context). Placeholder
// ids of 1 failed to resolve in ResourcesManager.LoadAssetGroupSync —
// LoadOpponentAssets (SBattleLoad.cs:933) hangs forever waiting for
// missing assets and the "Waiting for opponent" UI never closes.
SleeveId: 704141010, EmblemId: 400001100, DegreeId: 120027, FieldId: 5, IsOfficial: 0,
ClassId: 7, CharaId: 7, Rank: 10, BattlePoint: 0, IsMasterRank: 0, MasterPoint: 0),
new AIBotProfile(AiId: 1181, CountryCode: "JPN", UserName: "Portalcraft AI",
// Cosmetic ids mirror the Scripted bot's prod-verified values
// (ScriptedProfiles / ScriptedBotParticipant.Context). Placeholder
// ids of 1 failed to resolve in ResourcesManager.LoadAssetGroupSync —
// LoadOpponentAssets (SBattleLoad.cs:933) hangs forever waiting for
// missing assets and the "Waiting for opponent" UI never closes.
SleeveId: 704141010, EmblemId: 400001100, DegreeId: 120027, FieldId: 5, IsOfficial: 0,
ClassId: 8, CharaId: 8, Rank: 10, BattlePoint: 0, IsMasterRank: 0, MasterPoint: 0),
};
public AIBotProfile Pick(MatchContext selfCtx)
{
// Deterministic: hash the ctx and pick from the roster. Same ctx →
// same bot so a mid-flight retry of /ai_<fmt>/start returns the same
// opponent (no fresh roster pick on each call).
var hash = StringComparer.Ordinal.GetHashCode(selfCtx.UserName)
^ StringComparer.Ordinal.GetHashCode(selfCtx.ClassId);
var index = (int)((uint)hash % Roster.Count);
return Roster[index];
}
}