test(battlenode): reveal test stresses cardId substitution with mismatched seed (M-HC-2 review)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -224,13 +224,16 @@ public class HeadlessConductorTests
|
||||
Is.True, "turn2 TurnStart (seat B active)");
|
||||
|
||||
// Seat B's opening hand is hidden (deck reads full minus its single turn-1 draw); its cards
|
||||
// have NOT been disclosed to the relay yet. The dummy at engine Index 1 is the deck-idx-1 card
|
||||
// (the vanilla follower), seated in a hidden zone — NOT on the board. Confirm seat B's board is
|
||||
// empty BEFORE the reveal, so the post-reveal +1 is decisively the reveal seating the card.
|
||||
// (Node-native, the harness seeds each side's cards with their real id rather than cardId-0
|
||||
// dummies — it knows both decks — so the reveal substitution is identity-preserving here; the
|
||||
// board delta is what proves ReplaceReceivedCard.ReplaceCard -> CreateActualCard resolved the
|
||||
// card onto the board headless. M-HC-5 exercises a true cardId-0 -> cardId substitution.)
|
||||
// have NOT been disclosed to the relay yet. The dummy at engine Index 1 is whatever card the
|
||||
// shuffle seated at that index (shuffledDeck[0]), parked in a hidden zone — NOT on the board.
|
||||
// Confirm seat B's board is empty BEFORE the reveal, so the post-reveal +1 is decisively the
|
||||
// reveal seating the card. (Node-native, the harness seeds each side's cards with their real id
|
||||
// — it knows both decks — so this test's reveal substitution is identity-preserving by choice;
|
||||
// CreateActualCard builds the card purely from the wire cardId regardless of which card the
|
||||
// shuffle parked at Index 1. The board delta is what proves ReplaceReceivedCard.ReplaceCard ->
|
||||
// CreateActualCard resolved the card onto the board headless. The companion test
|
||||
// Opponent_reveal_overrides_seeded_identity_headless stresses a MISMATCHED cardId to prove the
|
||||
// wire id — not the seeded identity — is what gets seated.)
|
||||
var boardBefore = harness.BoardCount(playerSeat: false);
|
||||
Assert.That(boardBefore, Is.EqualTo(0), "seat B has no board followers before the reveal");
|
||||
|
||||
@@ -247,6 +250,60 @@ public class HeadlessConductorTests
|
||||
"the seated card's identity must equal the wire cardId from the reveal");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Opponent_reveal_overrides_seeded_identity_headless()
|
||||
{
|
||||
// This is the substitution half of M-HC-2: prove the seated card's POST-reveal identity is the
|
||||
// WIRE cardId even when it DIFFERS from whatever the shuffle parked at that engine Index.
|
||||
// ReplaceReceivedCard.CreateActualCard builds the card purely from cardData.CardId, independent
|
||||
// of the seated dummy's id — so a reveal whose cardId mismatches the seed must OVERRIDE it.
|
||||
//
|
||||
// Z (seeded) vs W (revealed) are DIFFERENT cost-1 vanilla followers, both present + creatable in
|
||||
// cards.json:
|
||||
// Z = 100011010 — the proven vanilla follower (char_type 1, cost 1). Seat B's deck is made
|
||||
// UNIFORMLY of Z, so whichever idx the shuffle parked at Index 1 is unambiguously Z.
|
||||
// W = 101211120 — a different cost-1 vanilla follower (char_type 1, cost 1, no skill). Cost 1
|
||||
// seats at seat B's first-turn PP (1). The id is NOT in seat B's deck, so the only way it
|
||||
// can appear on the board is the reveal substituting it in.
|
||||
const long Z = NodeNativeBattleHarness.VanillaFollowerId; // 100011010
|
||||
const long W = 101211120;
|
||||
Assert.That(W, Is.Not.EqualTo(Z), "Z and W must differ for the substitution to be observable");
|
||||
|
||||
// Uniform Z deck for seat B (every dummy is Z regardless of shuffle). Seat A left at default.
|
||||
var seatBDeck = Enumerable.Repeat(Z, 30).ToList();
|
||||
|
||||
using var harness = NodeNativeBattleHarness.Create(seatBDeck: seatBDeck);
|
||||
|
||||
// --- drive to seat B's turn (same two-turn sequence as the sibling reveal test) -------------
|
||||
Assert.That(harness.Push(NetworkBattleUri.Deal, DealBody(), isPlayerSeat: true).Accepted,
|
||||
Is.True, "Deal");
|
||||
Assert.That(harness.Push(NetworkBattleUri.Swap, SwapBody(), isPlayerSeat: true).Accepted,
|
||||
Is.True, "Swap");
|
||||
Assert.That(harness.Push(NetworkBattleUri.Ready, ReadyBody(), isPlayerSeat: true).Accepted,
|
||||
Is.True, "Ready");
|
||||
Assert.That(harness.Push(NetworkBattleUri.TurnStart, TurnStartBody(), isPlayerSeat: true).Accepted,
|
||||
Is.True, "turn1 TurnStart");
|
||||
Assert.That(harness.Push(NetworkBattleUri.TurnEnd, TurnEndBody(), isPlayerSeat: true).Accepted,
|
||||
Is.True, "turn1 TurnEnd");
|
||||
Assert.That(harness.Push(NetworkBattleUri.TurnStart, TurnStartBody(), isPlayerSeat: false).Accepted,
|
||||
Is.True, "turn2 TurnStart (seat B active)");
|
||||
|
||||
var boardBefore = harness.BoardCount(playerSeat: false);
|
||||
Assert.That(boardBefore, Is.EqualTo(0), "seat B has no board followers before the reveal");
|
||||
|
||||
// The reveal discloses idx 1 (seeded as Z) with the MISMATCHED wire cardId W.
|
||||
var reveal = harness.Push(
|
||||
NetworkBattleUri.PlayActions, RevealPlayBody(idx: 1, cardId: W), isPlayerSeat: false);
|
||||
|
||||
Assert.That(reveal.Accepted, Is.True, $"opponent reveal rejected: {reveal.RejectReason}");
|
||||
Assert.That(harness.BoardCount(playerSeat: false), Is.EqualTo(boardBefore + 1),
|
||||
"the revealed follower must seat on seat B's board");
|
||||
// The decisive assertion: the seated identity is W (the wire cardId), NOT Z (the seeded id).
|
||||
// Because the deck is uniformly Z, this can only pass if the reveal OVERRODE the seeded identity.
|
||||
Assert.That(harness.InPlayCardId(playerSeat: false, boardPos: 0), Is.EqualTo((int)W),
|
||||
"the seated card must be the wire cardId W, overriding the seeded Z identity at that idx");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Deal_seats_three_card_hand_headless()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user