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;

View File

@@ -414,7 +414,7 @@ public class CaptureConformanceTests
.MineCopyTokens(orderList, new Dictionary<int, long>(), otherMap)
.ToList();
Assert.That(mined, Is.EquivalentTo(new[] { (49, 123_456_789L, 0) }));
Assert.That(mined, Is.EquivalentTo(new[] { (49, 123_456_789L, CardOwner.Opponent) }));
}
[Test]

View File

@@ -131,7 +131,7 @@ public class KnownListBuilderTests
var orderList = new List<object?> { AddOp(new[] { 31L, 32L }, 900111010L) };
var mined = KnownListBuilder.MineAddOps(orderList).ToList();
Assert.That(mined, Is.EquivalentTo(new[] { (31, 900111010L, 1), (32, 900111010L, 1) }));
Assert.That(mined, Is.EquivalentTo(new[] { (31, 900111010L, CardOwner.Self), (32, 900111010L, CardOwner.Self) }));
}
[Test]
@@ -142,7 +142,7 @@ public class KnownListBuilderTests
// it; the caller routes it into the OTHER side's map.
var orderList = new List<object?> { AddOp(new[] { 31L }, 900111010L, isSelf: 0) };
Assert.That(KnownListBuilder.MineAddOps(orderList),
Is.EquivalentTo(new[] { (31, 900111010L, 0) }));
Is.EquivalentTo(new[] { (31, 900111010L, CardOwner.Opponent) }));
}
[Test]
@@ -205,7 +205,7 @@ public class KnownListBuilderTests
AddOp(new[] { 32L }, 900811090L),
};
var mined = KnownListBuilder.MineAddOps(orderList).ToList();
Assert.That(mined, Is.EquivalentTo(new[] { (31, 900111010L, 1), (32, 900811090L, 1) }));
Assert.That(mined, Is.EquivalentTo(new[] { (31, 900111010L, CardOwner.Self), (32, 900811090L, CardOwner.Self) }));
}
// A choice/Discover add op as it arrives in a RawBody: candidates-only (no concrete cardId —
@@ -248,7 +248,7 @@ public class KnownListBuilderTests
var keyAction = KeyActionChoice(generatingCardId: 810014030L, chosen: new[] { 810041260L }, open: 0);
Assert.That(KnownListBuilder.MineChoicePicks(orderList, keyAction),
Is.EquivalentTo(new[] { (46, 810041260L, 1) }));
Is.EquivalentTo(new[] { (46, 810041260L, CardOwner.Self) }));
}
[Test]
@@ -260,7 +260,7 @@ public class KnownListBuilderTests
var keyAction = KeyActionChoice(810014030L, new[] { 101041020L }, open: 0);
Assert.That(KnownListBuilder.MineChoicePicks(orderList, keyAction),
Is.EquivalentTo(new[] { (46, 101041020L, 0) }));
Is.EquivalentTo(new[] { (46, 101041020L, CardOwner.Opponent) }));
}
[Test]
@@ -357,7 +357,7 @@ public class KnownListBuilderTests
var selfMap = new Dictionary<int, long> { [5] = 100_011_010L };
var otherMap = new Dictionary<int, long>();
var mined = KnownListBuilder.MineCopyTokens(orderList, selfMap, otherMap).ToList();
Assert.That(mined, Is.EquivalentTo(new[] { (31, 100_011_010L, 1) }));
Assert.That(mined, Is.EquivalentTo(new[] { (31, 100_011_010L, CardOwner.Self) }));
}
[Test]
@@ -369,7 +369,7 @@ public class KnownListBuilderTests
var selfMap = new Dictionary<int, long>();
var otherMap = new Dictionary<int, long> { [21] = 900_841_330L };
var mined = KnownListBuilder.MineCopyTokens(orderList, selfMap, otherMap).ToList();
Assert.That(mined, Is.EquivalentTo(new[] { (49, 900_841_330L, 0) }));
Assert.That(mined, Is.EquivalentTo(new[] { (49, 900_841_330L, CardOwner.Opponent) }));
}
[Test]
@@ -422,7 +422,7 @@ public class KnownListBuilderTests
var orderList = new List<object?> { CopyOp(new[] { 31L, 32L }, baseIdx: 5L, isSelf: 1) };
var selfMap = new Dictionary<int, long> { [5] = 700L };
var mined = KnownListBuilder.MineCopyTokens(orderList, selfMap, new Dictionary<int, long>()).ToList();
Assert.That(mined, Is.EquivalentTo(new[] { (31, 700L, 1), (32, 700L, 1) }));
Assert.That(mined, Is.EquivalentTo(new[] { (31, 700L, CardOwner.Self), (32, 700L, CardOwner.Self) }));
}
// A uList entry as it arrives in a RawBody. Minimal = the 5 always-present fields