The deck can name a card, like "queen_of_spades". But a name is not a spot on the table. Fire deck.dealRequest now and you get a list of strings with nowhere to go. No seat owns them. Nothing knows if a card belongs to the player, an opponent, or the community row. And no two cards know they should sit side by side. Before any card can move, the table needs a coordinate system. That job belongs to PokerSeating, and it draws nothing at all.
Add it to the existing Dealer entity in src/scenes/poker.json, next to the DeckDealer you added last lesson:
"Dealer": {
"components": {
"DeckDealer": { "playerCount": 4, "cardsPerPlayer": 2, "riverSize": 5, "localIndex": 0, "roomCode": "table-7", "handNumber": 0 },
"PokerSeating": { "cardsPerPlayer": 2, "riverSize": 5, "npcCount": 3 }
}
}
You give it three counts, and it gives back the whole table's geometry. The header of PokerSeating.core.ts is very clear about its job:
* No card *flip* logic, no animation, no rendering — this is the static plan a
* runtime reads once to know where everything goes.

It all starts with buildLayout. It takes the variant counts and returns three things: one flat array of cards, a map of named groups that point into that array, and the order to deal them:
src/components/PokerSeating/PokerSeating.core.ts
export function buildLayout(variant: PokerSeatingVariant): PokerLayout {
const groups = new Map<CardGroupId, { start: number; count: number }>();
const allCards: DealtCardData[] = [];
let offset = 0;
const playerCards = createDealtCardData(variant.cardsPerPlayer, 10, -4.5, 1.5);
allCards.push(...playerCards);
groups.set('player', { start: offset, count: variant.cardsPerPlayer });
offset += variant.cardsPerPlayer;
if (variant.riverSize > 0) {
const riverCards = createDealtCardData(variant.riverSize, 10, 0, 1.5);
allCards.push(...riverCards);
groups.set('river', { start: offset, count: variant.riverSize });
offset += variant.riverSize;
}
const npcSeats = computeNpcSeats(variant.npcCount);
for (let i = 0; i < variant.npcCount; i++) {
const seat = npcSeats[i];
const npcCards = createCardGroup(
variant.cardsPerPlayer,
seat.origin, seat.target,
seat.spread, seat.spreadAxis, seat.baseRotY,
);
allCards.push(...npcCards);
const groupId: CardGroupId = `npc${i}`;
groups.set(groupId, { start: offset, count: variant.cardsPerPlayer });
offset += variant.cardsPerPlayer;
}
const seatGroups: { start: number; count: number }[] = [
groups.get('player')!,
...Array.from({ length: variant.npcCount }, (_, i) => groups.get(`npc${i}` as CardGroupId)!),
];
const dealOrder: number[] = [];
for (let round = 0; round < variant.cardsPerPlayer; round++) {
for (const g of seatGroups) {
if (round < g.count) dealOrder.push(g.start + round);
}
}
const riverGroup = groups.get('river');
if (riverGroup) {
for (let i = 0; i < riverGroup.count; i++) {
dealOrder.push(riverGroup.start + i);
}
}
return { allCards, dealOrder, groups };
}
Give it our three counts and it returns the whole table as data:
buildLayout({ cardsPerPlayer: 2, riverSize: 5, npcCount: 3 })
All the hands sit end-to-end in one list. groups is the directory that says where each hand begins. Ask for the player's cards and the answer is groups.player: start at 0, take two. A renderer reads that slice. It never searches the table for "the player's cards."