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

@@ -130,6 +130,47 @@ internal static class KnownListBuilder
}
}
/// <summary>Mine copy/clone-token identities: for each copy <c>add</c> op
/// (<c>{idx:[...], isSelf, card:{baseIdx, isPremium}}</c>), resolve its cardId from the appropriate
/// side's idx->cardId map. The copied card lives at <c>baseIdx</c> in the actor's OWN index space —
/// <c>RegisterCopyToken</c> is emitted only for <c>!IsReferenceOpponenCard</c>
/// (<c>NetworkBattleManagerBase.cs:1106</c>); a cross-side copy sends a concrete <c>cardId</c> via a
/// plain <c>RegisterToken</c> instead (handled by <see cref="MineAddOps"/>). Yields
/// <c>(idx, cardId, isSelf)</c> — same shape as <see cref="MineAddOps"/>, routed by the same
/// <see cref="BattleSessionState.RecordTokensFrom"/> rule: <c>isSelf:1</c> resolves+records into the
/// sender's map (<paramref name="selfMap"/>), <c>isSelf:0</c> into the opponent's
/// (<paramref name="otherMap"/>). Skips an add with a concrete <c>cardId</c> (→ MineAddOps), one with
/// <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(
object? orderList,
IReadOnlyDictionary<int, long> selfMap,
IReadOnlyDictionary<int, long> otherMap)
{
if (orderList is not IEnumerable<object?> ops) yield break;
foreach (var op in ops)
{
if (op is not IDictionary<string, object?> opDict) continue;
if (!opDict.TryGetValue("add", out var addRaw) || addRaw is not IDictionary<string, object?> add) continue;
if (!add.TryGetValue("card", out var cardRaw) || cardRaw is not IDictionary<string, object?> card) continue;
if (card.ContainsKey("cardId")) continue; // concrete token → MineAddOps
if (!card.TryGetValue("baseIdx", out var baseRaw)) continue; // not a copy (candidates → MineChoicePicks)
if (baseRaw is string) continue; // private-group copy → string baseIdx, skip
var baseIdx = (int)AsLong(baseRaw);
add.TryGetValue("isSelf", out var isSelfRaw);
var isSelf = (int)AsLong(isSelfRaw);
var map = isSelf == 1 ? 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;
foreach (var i in idxList)
yield return ((int)AsLong(i), cardId, isSelf);
}
}
/// <summary>Map an inbound keyAction (the active player's send) to the opponent-facing list:
/// for each Choice(1)/HaveBeforeSkillChoice(5) entry, keep <c>{type,cardId}</c> and drop
/// <c>selectCard</c> when its <c>open==0</c> (hidden draw-to-hand pick stays secret), pass it