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:
gamer147
2026-06-06 21:01:01 -04:00
parent 07ffc8906d
commit b73f0f7157

View File

@@ -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()
{