refactor(battle-node): replace int IsSelf with CardOwner enum on mined-token tuples

MineAddOps/MineChoicePicks/MineCopyTokens return types and all
extraction casts changed from int to CardOwner. The 4 routing
comparisons in BattleSessionState now read isSelf == CardOwner.Self
instead of isSelf == 1.

No wire or behavioral change — CardOwner was already in use on the
wire-facing side (OppoTargetEntry, UnapprovedCardEntry); this extends
it to the internal mining path so the bare-int transpose risk is gone.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-04 22:25:01 -04:00
parent 564b1d678f
commit a3e445cf2f
4 changed files with 20 additions and 19 deletions

View File

@@ -1,4 +1,5 @@
using SVSim.BattleNode.Lifecycle;
using SVSim.BattleNode.Protocol;
using SVSim.BattleNode.Sessions;
namespace SVSim.BattleNode.Sessions.Dispatch;
@@ -84,7 +85,7 @@ internal sealed class BattleSessionState
public void RecordTokensFrom(IBattleParticipant from, IBattleParticipant other, object? orderList)
{
foreach (var (idx, cardId, isSelf) in KnownListBuilder.MineAddOps(orderList))
RecordToken(isSelf == 1 ? from : other, idx, cardId);
RecordToken(isSelf == CardOwner.Self ? from : other, idx, cardId);
}
/// <summary>Mine + record choice/Discover-token picks (<see cref="KnownListBuilder.MineChoicePicks"/>)
@@ -95,7 +96,7 @@ internal sealed class BattleSessionState
public void RecordChoicePicksFrom(IBattleParticipant from, IBattleParticipant other, object? orderList, object? keyAction)
{
foreach (var (idx, cardId, isSelf) in KnownListBuilder.MineChoicePicks(orderList, keyAction))
RecordToken(isSelf == 1 ? from : other, idx, cardId);
RecordToken(isSelf == CardOwner.Self ? from : other, idx, cardId);
}
/// <summary>Mine + record copy/clone-token identities (<see cref="KnownListBuilder.MineCopyTokens"/>)
@@ -112,6 +113,6 @@ internal sealed class BattleSessionState
var selfMap = GetOrSeedDeckMap(from);
var otherMap = GetOrSeedDeckMap(other);
foreach (var (idx, cardId, isSelf) in KnownListBuilder.MineCopyTokens(orderList, selfMap, otherMap))
RecordToken(isSelf == 1 ? from : other, idx, cardId);
RecordToken(isSelf == CardOwner.Self ? from : other, idx, cardId);
}
}

View File

@@ -55,7 +55,7 @@ internal static class KnownListBuilder
/// <c>idx</c>-is-list guards. This is the only place a freshly-generated card's identity exists on
/// the wire (bullet-3 audit F1; producing code <c>RegisterToken</c>/<c>RegisterActionBase</c>) —
/// the played-card op itself never carries a <c>cardId</c>.</summary>
public static IEnumerable<(int Idx, long CardId, int IsSelf)> MineAddOps(object? orderList)
public static IEnumerable<(int Idx, long CardId, CardOwner IsSelf)> MineAddOps(object? orderList)
{
if (orderList is not IEnumerable<object?> ops) yield break;
foreach (var op in ops)
@@ -64,7 +64,7 @@ internal static class KnownListBuilder
if (!opDict.TryGetValue("add", out var addRaw) || addRaw is not IDictionary<string, object?> add) continue;
add.TryGetValue("isSelf", out var isSelfRaw);
var isSelf = (int)AsLong(isSelfRaw);
var isSelf = (CardOwner)(int)AsLong(isSelfRaw);
if (!add.TryGetValue("card", out var cardRaw) || cardRaw is not IDictionary<string, object?> card) continue;
if (!card.TryGetValue("cardId", out var cardIdRaw)) continue; // candidates/isChoice → no identity yet
@@ -87,7 +87,7 @@ internal static class KnownListBuilder
/// only gates the strip (<see cref="StripKeyActionForOpponent"/>), not the recording. An add whose
/// candidates contain none of the picks is skipped (defensive — no record, no desync); Echo (no
/// keyAction) yields nothing, leaving it mining-only via <see cref="MineAddOps"/>.</summary>
public static IEnumerable<(int Idx, long CardId, int IsSelf)> MineChoicePicks(object? orderList, object? keyAction)
public static IEnumerable<(int Idx, long CardId, CardOwner IsSelf)> MineChoicePicks(object? orderList, object? keyAction)
{
if (orderList is not IEnumerable<object?> ops) yield break;
@@ -123,7 +123,7 @@ internal static class KnownListBuilder
if (chosen is null) continue; // no pick in this op's pool — skip (no desync, just no record)
add.TryGetValue("isSelf", out var isSelfRaw);
var isSelf = (int)AsLong(isSelfRaw);
var isSelf = (CardOwner)(int)AsLong(isSelfRaw);
if (!add.TryGetValue("idx", out var idxRaw) || idxRaw is not IEnumerable<object?> idxList) continue;
foreach (var i in idxList)
@@ -144,7 +144,7 @@ internal static class KnownListBuilder
/// <c>candidates</c> (→ MineChoicePicks), a <c>string</c> <c>baseIdx</c> (private-group copy,
/// <c>RegisterCopyToken.cs:19-22</c>), and a <c>baseIdx</c> absent from the chosen map (unknown source
/// → degrade, no desync). <c>isPremium</c> (IsFoil) is cosmetic and ignored.</summary>
public static IEnumerable<(int Idx, long CardId, int IsSelf)> MineCopyTokens(
public static IEnumerable<(int Idx, long CardId, CardOwner IsSelf)> MineCopyTokens(
object? orderList,
IReadOnlyDictionary<int, long> selfMap,
IReadOnlyDictionary<int, long> otherMap)
@@ -162,8 +162,8 @@ internal static class KnownListBuilder
var baseIdx = (int)AsLong(baseRaw);
add.TryGetValue("isSelf", out var isSelfRaw);
var isSelf = (int)AsLong(isSelfRaw);
var map = isSelf == 1 ? selfMap : otherMap;
var isSelf = (CardOwner)(int)AsLong(isSelfRaw);
var map = isSelf == CardOwner.Self ? selfMap : otherMap;
if (!map.TryGetValue(baseIdx, out var cardId)) continue; // unknown source → degrade
if (!add.TryGetValue("idx", out var idxRaw) || idxRaw is not IEnumerable<object?> idxList) continue;