BlackjackSummary

Type: object

Sent with QoS 1, not retained.

Sent exactly once per final round, at the same time as blackjack/round_over and
round_summary, and carrying the same round_id and sequence. This is the
blackjack-specific counterpart of round_summary: the dealer's finished hand, every player hand with its
cards, value and result, and each side bet with the outcome we evaluated for it.

It is a content superset of round_summary — we derive that message from this one —
so a blackjack-only consumer never has to subscribe to both or join them. Prefer
round_summary if you integrate a mixed floor and do not care which game a table deals.

Whereas blackjack/round_over carries the raw live table state (positions, tracking
data, chip stacks) and leaves the game rules to you, everything here is already
evaluated: hand values, win/loss, and what each bet paid.

Units and sign. Same convention as round_summary: no field is currency, every
money field is a multiple of the bet it settles with the initial bet as 1.0, and every
one of them is net and player-positive (-1 loss, 0 push, +1 win, -2 a lost doubled
hand, and the table's blackjack payout on a natural). Nothing in the payload is expressed
from the house's side.

See spec.BlackjackRoundSummary for the full field documentation.

BlackjackRoundSummary

Type: object

A finished blackjack round.

schema_version is this schema's version, independent of the round_summary one.
sequence is the same counter both summary topics use, incremented once per round, so
equal values identify the same round on both topics and a gap on either means rounds
were lost while we were disconnected from the broker.

players has one entry per configured box, in box order, so players[i].box_index == i.
Each is an ActivePlayer or an InactivePlayer, told apart by its status field.
configured_players is the length of that list and active_players the number of
ACTIVE entries in it.

Side bets are reported where they were placed: the per-box ones on the box that played
them (players[].side_bets), and the table-wide ones in layout_side_bets, which are
not attributable to a box. This is the only topic that reports what a side bet paid —
round_summary folds the same bets to uptake, since an outcome takes the game's rules to
work out. There is no round-level total here: sum the per-box entries, whose
opportunities and played already account for back betters and progressives.

bet_detection_available tells you whether this table can see bets at all. When False,
every played is 0 and says nothing about what players did.

Money is never currency: we detect bet presence and relative size, never chip
denomination, so every multiplier is in units of the bet it settles and every one of
them is player-positive. The house result for the round is the negated sum over
players[].payout_multiplier.

Type: integer

Type: integer

Type: stringFormat: uuid

Type: string

Type: string

Type: string

BlackjackTiming

Type: object

The round's span and its accumulated time per blackjack phase.

started_at is the first relevant detection (the first chip, or the first card when no
betting phase was seen) and ended_at is when the last card left the table, both ISO
8601 with offset in the system's local timezone.

The *_seconds fields are named after BlackjackGamePhase and are accumulated
occupancy
: the total time the round spent in each phase, summed over every visit. The
tracker steps backwards when cards are occluded, so a phase can be entered more than
once — which is why these are not differences between phase timestamps. They sum to
total_seconds, and a phase that was never entered is 0.0.

betting_seconds is the exception: it is None, not 0.0, on a table that does not
detect bets. The betting phase starts with the first chip, so without that detection the
round only starts at the first card and the time players spent betting is not something
we measured. total_seconds then covers the play only.

payout_seconds covers both DEALER_MAYBE_FINISHED and DEALER_FINISHED: the two are
one final state for reporting.

Phase timestamps are not repeated here: every transition is published with its exact
time on the state change topic.

Type: string

Type: number

DealerDetail

Type: object

The dealer's hand at the end of the round.

cards is in dealt order, so cards[0] is the up-card.

value is the hand's total and is None when the dealer had no cards; value_str is
the settlement category. The two are not redundant: a value of 21 is a blackjack only
when value_str says BJ, and a busted hand keeps its real total (22+) in value
while value_str is BUST. DEALER_UNFINISHED means the dealer never drew to a
standing total, which on a finished round means they did not have to: every hand had
busted, or every hand had already left the table (see HandDetail.settled_early).

Type: array
No Additional Items

Each item of this array must be:

CardLabel

Type: object

Value

Type: enum (of string)

Must be one of:

  • "10"
  • "2"
  • "3"
  • "4"
  • "5"
  • "6"
  • "7"
  • "8"
  • "9"
  • "A"
  • "J"
  • "K"
  • "Q"

Suit

Type: enum (of string)

Must be one of:

  • "C"
  • "D"
  • "H"
  • "S"

DealerValue

Type: enum (of string)

The dealer's hand as settlement and the odds tables see it.

This is both the internal type and the published one — the member values are the wire
values wherever we publish a dealer hand. DEALER_UNFINISHED means the dealer has not
drawn to a standing total yet, which for a finished round means they did not need to
(every hand busted).

display() / from_display() are the display form used by the panel, where an
unfinished hand shows as "·". Everywhere else the member value is the string.

Note that the totals are not the whole story: V21 and BJ are both 21 but BJ beats
it, which is what rank encodes.

Must be one of:

  • "17"
  • "18"
  • "19"
  • "20"
  • "21"
  • "BJ"
  • "BUST"
  • "DEALER_UNFINISHED"

Type: integer

Type: integer

Type: array
No Additional Items

Each item of this array must be:


ActivePlayer

Type: object

A configured box that was in play, i.e. one that was dealt cards.

status is always "ACTIVE"; match on it to tell this apart from an InactivePlayer.

"Box" is a position, not a person: bettors counts the main better plus every back
better sharing the position.

num_hands is how many hands the box played — one normally, more after a split. hands
is how many of them we could settle, so num_hands > len(hands) means we ran out of
answers partway through the box. Say the player splits and we read the first hand fine,
but the second hand's cards end the round half-covered by a chip tray: num_hands is 2,
hands holds only the first, and the hands after the one that failed are not settled
either — settling them would report money for a box we did not finish reading.

settled is True only when every hand was settled, i.e. len(hands) == num_hands. When
it is False, payout_multiplier is the sum over the hands we did read, not the box's
result, and the box has to be left out of revenue reporting rather than counted as a
smaller win or loss. It is not "the round went badly" — it is "do not trust this number".

decisions is what the box played, in the order it was played and flat across the whole
box: one SPLIT per extra hand first, then each hand's hits and doubles and the STAND
that closed it. strength is the box's weakest decision — the same measure the excel
report uses — and is None when no decision could be rated, including when the dealer
had no up-card to play against.

Type: enum (of string)

Must be one of:

  • "ACTIVE"

Type: array
No Additional Items

Each item of this array must be:

HandDetail

Type: object

One settled hand at a box — a box has more than one after a split.

value is the hand's total, above 21 when it busted. value_str is "BJ", "BUST",
or the total tagged soft or hard ("S18", "H18"): standing on soft 18 and on hard 18
are different decisions, and the total alone does not tell them apart.

bet_units is what the hand staked, with the box's initial bet as 1.0: 2.0 on a
doubled hand, 1.0 otherwise.

payout_multiplier is the net result on this hand's main bet, in those same units, and
is player-positive: +1.0 win, 0.0 push, -1.0 loss, -2.0 a lost doubled hand, and
the table's configured blackjack payout on a natural (+1.5 at 3:2, +1.2 at 6:5).
Divide it by bet_units for the return on what this hand actually risked.

outcome is SURRENDER or EVEN_MONEY on a hand the player took off the table before
the dealer played their own — folded, or a natural settled at 1:1 against a dealer ace.
Both are player decisions rather than results of the hand, which is why they are named
instead of being reported as the loss and the win they are in the money. Note that even
money pays one unit whatever the table pays for a blackjack: taking a certain 1:1 is the
whole point of it. They are also the two hands that settle a round the dealer never
finished, where an ordinary hand would be left unsettled.

Type: array
No Additional Items

Each item of this array must be:

HandOutcome

Type: enum (of string)

Outcome of a single settled hand relative to the house.

SURRENDER and EVEN_MONEY are the two hands that never played against the dealer: the
player took the decision instead. Folding them into LOSS and WIN would misreport how
the house won and lost, so each keeps its own name. They still are a loss and a win in
the money — half a unit forfeited, and one unit paid — which is what the generic round
summary buckets them as.

Must be one of:

  • "EVEN_MONEY"
  • "LOSS"
  • "PUSH"
  • "SURRENDER"
  • "WIN"

Type: array
No Additional Items

Each item of this array must be:

SideBet

Type: object

One side bet, for one round: who could play it, who did, and what it paid.

The same shape describes a bet offered at a box and one offered on the layout — the two
differ only in where they are reported, not in what we can say about them.

opportunities is how many people could have placed this bet. At a box that is everyone
sitting or standing behind it; a progressive jackpot is per seat, so back betters sharing
the box do not each get one. A layout bet counts every betting entity at the table — each
box in play, plus every back better standing behind one — so there a back better does get
an opportunity of their own. It is 0 on a round where the game did not offer the bet at
all — Insurance, unless the dealer shows an Ace. played is how many of those
opportunities were taken.

outcome names what happened, in the vocabulary of the bet itself: the winning hand
category ("PERFECT_PAIRS", "DEALER_BUST"), "LOSS", or "UNKNOWN" for a bet we
deliberately do not score, such as a progressive whose jackpot the casino's own system
resolves. payout is what one placed bet returned, as a multiple of that bet: -1.0 when
it lost, positive when it won, 0.0 on "UNKNOWN", or a "GRAND" / "MAJOR" /
"MINOR" tier for a progressive we meter but do not price.

Both are None together, and mean we have no result: nobody played the bet, or the round
never determined it. The latter is worth looking into — a side bet configured in the wrong
place, a dealer mistake, or a hand we mis-tracked — but it is not reported as an outcome
here, because it says nothing about how the bet ended.

Type: array
No Additional Items

Each item of this array must be:

Decision

Type: object

One move the player made, and how good it was.

strength scores the move against the best one available in that spot, normalised so
1.0 is basic-strategy-optimal play and anything lower gave something away. It is None
when we cannot rate the spot, i.e. the hand is not in the strategy tables.

The yardstick is fixed — eight decks, dealer stands on soft 17, blackjack pays 3:2 — and
does not follow this table's own rules or its blackjack payout, so strength stays
comparable across tables.

PlayerAction

Type: enum (of string)

Must be one of:

  • "DOUBLE"
  • "HIT"
  • "SPLIT"
  • "STAND"

InactivePlayer

Type: object

A configured box that was not dealt cards this round.

status is always "INACTIVE"; match on it to tell this apart from an ActivePlayer.

Type: enum (of string)

Must be one of:

  • "INACTIVE"

Type: array
No Additional Items

Each item of this array must be:

SideBet

Type: object

One side bet, for one round: who could play it, who did, and what it paid.

The same shape describes a bet offered at a box and one offered on the layout — the two
differ only in where they are reported, not in what we can say about them.

opportunities is how many people could have placed this bet. At a box that is everyone
sitting or standing behind it; a progressive jackpot is per seat, so back betters sharing
the box do not each get one. A layout bet counts every betting entity at the table — each
box in play, plus every back better standing behind one — so there a back better does get
an opportunity of their own. It is 0 on a round where the game did not offer the bet at
all — Insurance, unless the dealer shows an Ace. played is how many of those
opportunities were taken.

outcome names what happened, in the vocabulary of the bet itself: the winning hand
category ("PERFECT_PAIRS", "DEALER_BUST"), "LOSS", or "UNKNOWN" for a bet we
deliberately do not score, such as a progressive whose jackpot the casino's own system
resolves. payout is what one placed bet returned, as a multiple of that bet: -1.0 when
it lost, positive when it won, 0.0 on "UNKNOWN", or a "GRAND" / "MAJOR" /
"MINOR" tier for a progressive we meter but do not price.

Both are None together, and mean we have no result: nobody played the bet, or the round
never determined it. The latter is worth looking into — a side bet configured in the wrong
place, a dealer mistake, or a hand we mis-tracked — but it is not reported as an outcome
here, because it says nothing about how the bet ended.

Same definition as SideBet

Type: boolean

Type: array of string
No Additional Items

Each item of this array must be: