Public online tables
Public online tables
The online lobby offers Find a table and Create a table. Public tables are listed for authenticated Apple/guest accounts; private invite tables retain their existing code/link flow. Guests can browse a table’s settings and roster before claiming a seat. There is no spectator access to hands or mid-match seat replacement.
Conventions and match length
Presentation names change without changing persisted rules identifiers:
| Display name | Persisted variant | Scoring profile | Pool closure |
|---|---|---|---|
| Vienna | odesa |
Existing balanced profile | Individual limits, American aid |
| Prague | wien |
Existing doubled-whist profile | Shared table total |
| Berlin | thessaloniki |
Existing semi-responsible, fixed all-pass profile | Individual limits, American aid |
The fourth existing local/private agreement remains available outside public tables.
Public creation uses three or four human seats and starts with the short pulka preset.
The API represents the total pool target: three individual limits of 11 are encoded
as 33, and four as 44. Prague displays the actual shared table total explicitly.
The Worker supplies canonical rules and all-pass progression from the preset, so
a creator cannot advertise one convention while sending different scoring rules.
The checked-in JSON profiles are verified against the Swift engine by
PublicTableRulesTests.
Readiness and lifecycle
Each player explicitly confirms Ready after seeing the immutable table settings. The host can start only when all seats are occupied, all human sockets are connected, and all players are ready. The server validates this independently of the UI. A new join clears all confirmations; rejoining clears that player’s confirmation; disconnecting clears disconnected players’ confirmations while retaining their seats. Later deals use the existing match flow and do not require a new readiness vote.
Leaving a public waiting table releases the seat. Remaining players inherit lobby management; the table closes if no humans remain. Removal from the departing player’s library is a durable retryable outbox, committed after the room membership changes. Leaving an active game’s screen retains its seat and the existing Your games resume flow. Explicit match abandonment remains a separate action.
Directory and privacy
GET /v2/public-tables requires an account bearer session. It returns at most 50
entries, supports offset pagination and optional variant/player-count/pool/status
filters, and places waiting tables ahead of running games. The app refreshes every
15 seconds while its Find screen is active and also supports manual refresh.
PublicTableDirectory receives only display names, readiness/connection flags, pool
settings, room codes, occupancy and lifecycle information. No account IDs, seat tokens,
authenticated socket URLs, engine states, projections, decks or discards enter that
directory. Unseated public room summaries also omit real account identities.
Room mutations and socket events mark a durable publication outbox. Connected rooms
renew their listings every 30 seconds; directory leases expire after 90 seconds.
Empty or fully disconnected tables and completed/abandoned games are not advertised.
Joining always checks the current authoritative room, so a stale listing cannot
grant a seat in an already-started or full game.
Public participant rosters retain only the authenticated caller’s own global ID. Other human seats use stable room-scoped IDs, including library entries and engine projections. Previously persisted projections are masked on delivery. Rejoining cancels queued library removal; alarms never remove a currently seated account.
The client defaults to invite-only while server deployment is unverified. Public creation becomes selectable after a successful directory response. An old server cannot silently downgrade a public-create request to a private room: the client requires explicit public visibility in the response.
Deployment
Migration v6-public-directory adds a new namespace and preserves existing rooms
and accounts. The local configuration adds its own corresponding namespace.
Deploy the Worker and current Swift container before distributing the new client.
Older clients can still use private tables, but do not have the public Ready controls.
Local verification commands:
cd workers/room-worker
bun install --frozen-lockfile
bun run check
bun run test:public-network
The network test requires the loopback Worker on 8787 and the Swift engine on 18081.
It creates isolated accounts, discovers and joins three/four-player public tables,
leaves/rejoins a seat, rejects unauthorized readiness and premature dealing, accepts
the first deal after all confirmations, rejects an outsider after start, and deletes
its accounts. NativeOnlineUITests/testNativePlayerFindsPublicTableReadiesAndReceivesFirstDeal
checks the corresponding app path against those same services.
The production container pool remains three instances. Local verification does not
establish production concurrency or cold-start latency. Worker-only bundling can be
checked with wrangler deploy --dry-run --containers-rollout=none; the complete
container deployment requires Docker and Cloudflare authentication.