Bot roster pick was hashing (UserName, ClassId) — same player always faced the same bot class. Now hashes battleId so different matches get different opponents while retries of the same pending battle stay consistent. AI start response hardcoded Seed=0 for both sides, so the client's deck shuffle/mulligan/draw RNG was deterministic every match. The BattleNode's per-battle MasterSeed (Random.Shared) was never sent to bot-mode clients because InitBattleHandler skips the Matched frame. Now populates Seed with Random.Shared.Next() on the HTTP response. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
23 lines
858 B
C#
23 lines
858 B
C#
using SVSim.BattleNode.Bridge;
|
|
|
|
namespace SVSim.EmulatedEntrypoint.Matching;
|
|
|
|
/// <summary>
|
|
/// Picks a bot opponent for an incoming AI rank battle. Used by
|
|
/// <c>RankBattleController.AiStart</c> to compose <c>oppo_info</c>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Backed by the <c>BotRoster</c> table (seeded from
|
|
/// <c>SVSim.Bootstrap/Data/seeds/bot-roster.json</c>). Edit the seed + re-run
|
|
/// <c>SVSim.Bootstrap</c> to change the pool without recompiling.
|
|
/// </remarks>
|
|
public interface IBotRoster
|
|
{
|
|
/// <summary>
|
|
/// Returns a bot profile. Deterministic per <paramref name="battleId"/> so a
|
|
/// mid-flight retry of <c>/ai_<fmt>/start</c> picks the same opponent,
|
|
/// but different battles get different bots.
|
|
/// </summary>
|
|
Task<AIBotProfile> PickAsync(MatchContext selfCtx, string battleId, CancellationToken ct = default);
|
|
}
|