logo

Babylon.js Market

TurnPacer

tabletop-casino · poker

TurnPacer

1
Unlock

Install with the CLI:

bjs download TurnPacer

TurnPacer

PokerBetting settles a whole betting round in a single tick — every opponent acts at once, the glow flashes through, and you never see it happen. TurnPacer buffers that burst and replays it on a clock, so the table acts at a pace you can watch.

What it does

A betting round resolves synchronously. In one tick PokerBetting emits, in seat order, a poker.currentTurn + poker.actionTaken per NPC, then either poker.currentTurn {null} (round closed) or poker.currentTurn {player} + poker.actionRequired {menu} (the human is up). Correct, but instant — the opponents' turns are invisible.

TurnPacer is a pure presentation layer. The System buffers that burst into a TurnPaceQueue (the core) and replays it on its own wall-clock, re-emitting each step as a paced poker.display.* event:

  • poker.display.turn {playerId} — the paced glow move. The core also derives one of these from each actionTaken, fired right before that seat's action reveal, so the glow hops to each opponent as it acts. The raw currentTurn stream marks the next seat, never the acting NPC, so the glow would otherwise never land on one.
  • poker.display.action {playerId, action} — the paced action reveal.
  • poker.display.actionRequired {playerId, actions} — the gated human prompt, emitted only after the preceding NPC steps have played out. This is what makes the human's controls go live only once the opponents have visibly acted.

The queue drains FIFO, pausing pacePerStep seconds (default 0.75) after each action so each turn is readable; glue turn steps are near-instant and ride out together. A human step halts the drain until poker.actionSubmitted releases the gate, so the next street's burst drains at once rather than waiting out a stale pause. pokerTable.handStarted resets the queue clean, and the pacer keeps working across all four betting streets per hand.

Nothing is dropped or reordered — every payload is re-emitted verbatim, just delayed. All the timing lives in a deterministic, dt-driven core (TurnPaceQueue.enqueue* / step / reset); the System is glue over the bus and reaches no renderer at all.

Use it in a scene

Seat one TurnPacer per table on the presentation entity. It needs no other wiring — it sits between PokerBetting and the display systems.

A scene is just data — a list of entities, each with its components. At startup the SceneLoader turns this JSON into a live world; edit the file and reload to rebuild it.

Build it from scratch with the bjs CLI:

  1. 1Scaffold a project
    npm create @babylonjsmarket/arcade@latest my-game

    World, renderer, and dev server — ready to run.

  2. 2Install dependencies
    cd my-game && npm install
  3. 3Add TurnPacer
    bjs download TurnPacer

    Copies its source into src/ so the scene resolves.

    First time? Run bjs login once.

  4. 4Paste the scene into src/scenes/arcade-room.ts and run
    npm run dev

    SceneLoader builds the world from the JSON; reload to rebuild.

JSON
{
  "Presentation": {
    "components": {
      "TurnPacer": { "pacePerStep": 0.75 },
      "PokerHUD": {},
      "PokerTableCards": { "selectedBack": 1, "basePath": "/poker" }
    }
  }
}

PokerHUD and PokerInput listen to the poker.display.* events TurnPacer emits, not the raw betting events — wire them to the paced stream and the table paces itself.

Props

PropTypeDefaultMeaning
pacePerStepnumber0.75Seconds of pause after each action reveal. A zero/negative value falls back to the default.

Events

DirectionEventPayload
outpoker.display.turn{ playerId }
outpoker.display.action{ playerId, action }
outpoker.display.actionRequired{ playerId, actions }
inpoker.currentTurn{ playerId }
inpoker.actionTaken{ playerId, action }
inpoker.actionRequired{ playerId, actions }
inpokerTable.handStarted{} (reset the queue)
inpoker.actionSubmitted{} (release the human gate)

Notes

  • The human step is terminal for its burst: the queue stops draining there until humanSubmitted() or reset() releases it. The next burst then drains on its own clock.
  • The first step after a pause plays immediately — no leading delay. Only a paced action arms the next pacePerStep gap, so glue moves don't accrue dead time.
  • The replay queue lives on the System in a WeakMap<TurnPacerComponent, …>, never on the Component, so serialize() round-trips to just { pacePerStep }.

More like this

PokerBetting
PokerHUD
PokerInput
PokerTableDirector
BallReset
CardFan
DeckDealer
GameConfig
Goal
MissionDirector
PokerAI
PokerAIBill
PokerAICali
PokerAIRandy
PokerAIShark
PokerAISurge
PokerAITilt
PokerHandEval
PokerMultiplayer
PokerSeating
PokerTableCards
SoccerDirector
WaveDirector

Was this page helpful?

We read every note — tell us what's working and what isn't.

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search