feat(battle-node): resolve copy-token cardIds from baseIdx (pure)

KnownListBuilder.MineCopyTokens resolves a copy add's baseIdx against the
actor's own idx->cardId map (self/other by isSelf), yielding (idx,cardId,
isSelf). Skips concrete/choice adds, string (private-group) baseIdx, and
unknown sources (degrade). Third token-reveal slice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-04 10:09:36 -04:00
parent 5c3835f4fd
commit f9c7e6124b
2 changed files with 127 additions and 0 deletions

View File

@@ -336,4 +336,90 @@ public class KnownListBuilderTests
Assert.That(KnownListBuilder.StripKeyActionForOpponent(null), Is.Null);
Assert.That(KnownListBuilder.StripKeyActionForOpponent(new List<object?>()), Is.Null);
}
// A copy add op as it arrives in a RawBody: { "add": { "idx":[..], "isSelf":n, "card":{ "baseIdx":m, "isPremium":0 } } }
private static Dictionary<string, object?> CopyOp(long[] idxs, long baseIdx, long isSelf = 1) => new()
{
["add"] = new Dictionary<string, object?>
{
["idx"] = idxs.Select(i => (object?)i).ToList(),
["isSelf"] = isSelf,
["card"] = new Dictionary<string, object?> { ["baseIdx"] = baseIdx, ["isPremium"] = 0L },
}
};
[Test]
public void MineCopyTokens_resolves_baseIdx_against_selfMap_for_isSelf_1()
{
var orderList = new List<object?> { CopyOp(new[] { 31L }, baseIdx: 5L, isSelf: 1) };
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) }));
}
[Test]
public void MineCopyTokens_resolves_baseIdx_against_otherMap_for_isSelf_0()
{
// Cross-side copy shape (battle-traffic_tk2_regular.ndjson:196 is an isSelf:0 Echo, baseIdx 21):
// the source lives in the OPPONENT's index space, so resolve against otherMap and record there.
var orderList = new List<object?> { CopyOp(new[] { 49L }, baseIdx: 21L, isSelf: 0) };
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) }));
}
[Test]
public void MineCopyTokens_skips_copy_when_baseIdx_absent_from_map()
{
// Unknown source (e.g. a card the node never recorded) → no record, no desync, the play degrades.
var orderList = new List<object?> { CopyOp(new[] { 31L }, baseIdx: 99L, isSelf: 1) };
Assert.That(
KnownListBuilder.MineCopyTokens(orderList, new Dictionary<int, long>(), new Dictionary<int, long>()),
Is.Empty);
}
[Test]
public void MineCopyTokens_ignores_concrete_and_choice_adds()
{
// A concrete-cardId add is MineAddOps' job; a candidates add is MineChoicePicks' — both skipped here.
var orderList = new List<object?>
{
new Dictionary<string, object?> { ["add"] = new Dictionary<string, object?>
{ ["idx"] = new List<object?> { 31L }, ["isSelf"] = 1L,
["card"] = new Dictionary<string, object?> { ["cardId"] = 900_111_010L } } },
new Dictionary<string, object?> { ["add"] = new Dictionary<string, object?>
{ ["idx"] = new List<object?> { 32L }, ["isSelf"] = 1L,
["card"] = new Dictionary<string, object?> { ["candidates"] = new List<object?> { 1L, 2L } },
["isChoice"] = "1" } },
};
var map = new Dictionary<int, long> { [1] = 5L };
Assert.That(KnownListBuilder.MineCopyTokens(orderList, map, map), Is.Empty);
}
[Test]
public void MineCopyTokens_skips_string_baseIdx_private_group()
{
// PrivateGroupIndexMsg != "" makes baseIdx a STRING (RegisterCopyToken.cs:19-22) — the hidden
// private-card path; skipped just like private-group idx in MineAddOps.
var orderList = new List<object?>
{
new Dictionary<string, object?> { ["add"] = new Dictionary<string, object?>
{ ["idx"] = new List<object?> { 31L }, ["isSelf"] = 1L,
["card"] = new Dictionary<string, object?> { ["baseIdx"] = "g1", ["isPremium"] = 0L } } },
};
Assert.That(
KnownListBuilder.MineCopyTokens(orderList, new Dictionary<int, long>(), new Dictionary<int, long>()),
Is.Empty);
}
[Test]
public void MineCopyTokens_yields_for_every_idx_in_a_multi_idx_copy_op()
{
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) }));
}
}