feat(replay): stash battle context at /ai_*/start time
AiStartInternal now writes a BattleContext keyed by viewer id; the next commit consumes it in /finish to write a ViewerBattleHistory row. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,9 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using SVSim.BattleNode.Bridge;
|
using SVSim.BattleNode.Bridge;
|
||||||
using SVSim.BattleNode.Sessions;
|
using SVSim.BattleNode.Sessions;
|
||||||
using SVSim.Database.Enums;
|
using SVSim.Database.Enums;
|
||||||
|
using SVSim.Database.Services.Replay;
|
||||||
using SVSim.EmulatedEntrypoint.Constants;
|
using SVSim.EmulatedEntrypoint.Constants;
|
||||||
|
using SVSim.EmulatedEntrypoint.Extensions;
|
||||||
using SVSim.EmulatedEntrypoint.Matching;
|
using SVSim.EmulatedEntrypoint.Matching;
|
||||||
using SVSim.EmulatedEntrypoint.Models.Dtos.RankBattle;
|
using SVSim.EmulatedEntrypoint.Models.Dtos.RankBattle;
|
||||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
|
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
|
||||||
@@ -27,6 +29,7 @@ public sealed class RankBattleController : ControllerBase
|
|||||||
private readonly IBattleSessionStore _sessionStore;
|
private readonly IBattleSessionStore _sessionStore;
|
||||||
private readonly IMatchContextBuilder _ctxBuilder;
|
private readonly IMatchContextBuilder _ctxBuilder;
|
||||||
private readonly IBotRoster _botRoster;
|
private readonly IBotRoster _botRoster;
|
||||||
|
private readonly IBattleContextStore _battleContextStore;
|
||||||
private readonly ILogger<RankBattleController> _log;
|
private readonly ILogger<RankBattleController> _log;
|
||||||
|
|
||||||
public RankBattleController(
|
public RankBattleController(
|
||||||
@@ -34,12 +37,14 @@ public sealed class RankBattleController : ControllerBase
|
|||||||
IBattleSessionStore sessionStore,
|
IBattleSessionStore sessionStore,
|
||||||
IMatchContextBuilder ctxBuilder,
|
IMatchContextBuilder ctxBuilder,
|
||||||
IBotRoster botRoster,
|
IBotRoster botRoster,
|
||||||
|
IBattleContextStore battleContextStore,
|
||||||
ILogger<RankBattleController> log)
|
ILogger<RankBattleController> log)
|
||||||
{
|
{
|
||||||
_resolver = resolver;
|
_resolver = resolver;
|
||||||
_sessionStore = sessionStore;
|
_sessionStore = sessionStore;
|
||||||
_ctxBuilder = ctxBuilder;
|
_ctxBuilder = ctxBuilder;
|
||||||
_botRoster = botRoster;
|
_botRoster = botRoster;
|
||||||
|
_battleContextStore = battleContextStore;
|
||||||
_log = log;
|
_log = log;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +170,33 @@ public sealed class RankBattleController : ControllerBase
|
|||||||
var bot = await _botRoster.PickAsync(selfCtx, pending.BattleId, ct);
|
var bot = await _botRoster.PickAsync(selfCtx, pending.BattleId, ct);
|
||||||
var seed = Random.Shared.Next();
|
var seed = Random.Shared.Next();
|
||||||
|
|
||||||
|
// Stash battle context for the upcoming /finish so the replay-history hook can
|
||||||
|
// compose a ViewerBattleHistory row. See docs/superpowers/specs/2026-06-10-replay-info-design.md.
|
||||||
|
if (long.TryParse(pending.BattleId, out var battleIdLong))
|
||||||
|
{
|
||||||
|
_battleContextStore.Set(vid, new BattleContext(
|
||||||
|
BattleId: battleIdLong,
|
||||||
|
// Wire battle_type: 2 = rank battle (per docs/api-spec/common/types.ts.md
|
||||||
|
// #battle-types). AI variant shares the rank-battle wire id.
|
||||||
|
BattleType: 2,
|
||||||
|
DeckFormat: format.ToApi(), // wire-int via existing converter
|
||||||
|
TwoPickType: 0,
|
||||||
|
SelfClassId: (int)selfCtx.ClassId, // CardClass enum
|
||||||
|
SelfSubClassId: 0,
|
||||||
|
SelfCharaId: int.TryParse(selfCtx.CharaId, out var ch) ? ch : 0, // CharaId is string on MatchContext
|
||||||
|
SelfRotationId: "0",
|
||||||
|
OpponentViewerId: 0, // AI bot — not a real viewer
|
||||||
|
OpponentName: bot.UserName,
|
||||||
|
OpponentClassId: bot.ClassId, // int on AIBotProfile
|
||||||
|
OpponentSubClassId: 0,
|
||||||
|
OpponentCharaId: bot.CharaId, // int on AIBotProfile
|
||||||
|
OpponentCountryCode: bot.CountryCode,
|
||||||
|
OpponentEmblemId: bot.EmblemId, // int → long widen
|
||||||
|
OpponentDegreeId: bot.DegreeId, // int → long widen
|
||||||
|
OpponentRotationId: "0",
|
||||||
|
BattleStartTime: DateTime.UtcNow));
|
||||||
|
}
|
||||||
|
|
||||||
// Per spec, ai-start.md TODO: turnState semantics unclear. Default 0 (player first).
|
// Per spec, ai-start.md TODO: turnState semantics unclear. Default 0 (player first).
|
||||||
return Ok(new AiBattleStartResponseDto
|
return Ok(new AiBattleStartResponseDto
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user