diff --git a/Game.Blackjack.Tests/TestHelpers.cs b/Game.Blackjack.Tests/TestHelpers.cs
new file mode 100644
index 0000000..8795743
--- /dev/null
+++ b/Game.Blackjack.Tests/TestHelpers.cs
@@ -0,0 +1,131 @@
+using Game.Blackjack;
+using OECS;
+using System.Text;
+
+namespace Game.Blackjack.Tests;
+
+internal static class TestHelpers
+{
+ private const int StartingChips = 100;
+
+ ///
+ /// Creates a fresh Blackjack world with systems registered and GameState singleton initialized.
+ ///
+ public static (World World, SystemGroup Group) SetupGame(uint seed = 42)
+ {
+ var world = new World();
+ var group = new SystemGroup(world);
+ group.Add(new DeckSetupSystem());
+ group.Add(new DealSystem());
+ group.Add(new PlayerBustCheckSystem());
+ group.Add(new DealerSystem());
+
+ world.SetSingleton(new GameState
+ {
+ Phase = GamePhase.Betting,
+ Result = RoundResult.None,
+ Chips = StartingChips,
+ CurrentBet = 0,
+ RoundNumber = 1,
+ Seed = seed
+ });
+ world.PostChanges();
+
+ return (world, group);
+ }
+
+ ///
+ /// Returns a human-readable textual snapshot of the world state.
+ ///
+ public static string SnapshotWorld(World world)
+ {
+ var sb = new StringBuilder();
+ var state = world.ReadSingleton();
+ sb.AppendLine($"Phase: {state.Phase}");
+ sb.AppendLine($"Chips: {state.Chips}");
+ sb.AppendLine($"Bet: {state.CurrentBet}");
+ sb.AppendLine($"Round: {state.RoundNumber}");
+ sb.AppendLine($"Result: {state.Result}");
+ sb.AppendLine($"Seed: {state.Seed}");
+
+ var deckEntity = FindEntity(world);
+ if (deckEntity != Entity.Null)
+ {
+ var cardsInDeck = world.GetSources(deckEntity).ToList();
+ sb.AppendLine($"Cards in deck: {cardsInDeck.Count}");
+ }
+
+ var playerHand = FindEntity(world);
+ if (playerHand != Entity.Null)
+ {
+ var cards = world.GetSources(playerHand).ToList();
+ sb.AppendLine($"Player hand: {cards.Count} cards");
+ foreach (var cardEntity in cards)
+ {
+ var card = world.ReadComponent(cardEntity);
+ sb.AppendLine($" {card.Rank} of {card.Suit}");
+ }
+ }
+
+ var dealerHand = FindEntity(world);
+ if (dealerHand != Entity.Null)
+ {
+ var cards = world.GetSources(dealerHand).ToList();
+ sb.AppendLine($"Dealer hand: {cards.Count} cards");
+ foreach (var cardEntity in cards)
+ {
+ var card = world.ReadComponent(cardEntity);
+ sb.AppendLine($" {card.Rank} of {card.Suit}");
+ }
+ }
+
+ int entityCount = 0;
+ using (var iter = world.Select())
+ {
+ while (iter.MoveNext()) entityCount++;
+ }
+ sb.AppendLine($"Card entities: {entityCount}");
+
+ return sb.ToString().TrimEnd();
+ }
+
+ public static Entity FindEntity(World world) where T : struct
+ {
+ return world.FindEntity();
+ }
+
+ public static int CountCardsInHand(World world, Entity handEntity)
+ {
+ return world.GetSources(handEntity).Count;
+ }
+
+ public static List GetHandCards(World world, Entity hand)
+ {
+ var cards = new List();
+ foreach (var cardEntity in world.GetSources(hand))
+ {
+ var card = world.ReadComponent(cardEntity);
+ cards.Add($"{card.Rank} of {card.Suit}");
+ }
+ return cards;
+ }
+
+ public static List<(Suit, Rank)> GetAllCards(World world)
+ {
+ var cards = new List<(Suit, Rank)>();
+ using (var iter = world.Select())
+ {
+ while (iter.MoveNext())
+ {
+ var card = iter.Val1;
+ cards.Add((card.Suit, card.Rank));
+ }
+ }
+ cards.Sort((a, b) =>
+ {
+ int cmp = a.Item1.CompareTo(b.Item1);
+ return cmp != 0 ? cmp : a.Item2.CompareTo(b.Item2);
+ });
+ return cards;
+ }
+}
\ No newline at end of file