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>
99 lines
6.9 KiB
C#
99 lines
6.9 KiB
C#
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];
|
||
}
|
||
}
|