games: a blackjack table you can actually sit down at

The engine, the escrow and the wire were all in place; nothing had a browser on
the end of it. This is that end: a lobby, a table, and the five endpoints between
them.

The browser holds no game. It sends intents and gets back a view — the cards it
is entitled to see, and the script of how they arrived, one event per card off
the shoe. The dealer's hole card is not in the payload at all until the reveal,
because a field the client is told to ignore is a field somebody reads in
devtools. The shoe lives in game_live_hands, which also means a redeploy
mid-hand no longer costs a player their stake: the hand is still there when they
come back.

The money is ordered so nothing can be spent twice. The stake leaves the stack in
the same statement that checks it exists, before a card is dealt. Every new hand
is seated with a plain INSERT, so a double-clicked Deal is decided by the primary
key rather than by a read that raced — it loses, gets its chips back, and the
hand in progress is untouched. A double takes its raise up front and hands it
straight back if the engine refuses the move.

Cards are dealt rather than swapped in — they fly out of the shoe and turn over,
which was a requirement and not a flourish. The faces and the chips are still
plain; that's next.
This commit is contained in:
prosolis
2026-07-13 23:20:42 -07:00
parent cb84e1d549
commit c69fbb63db
17 changed files with 1949 additions and 18 deletions

View File

@@ -53,13 +53,14 @@ const defaultDigestHour = 17
// WebConfig controls the read-only HTTP interface (news.parodia.dev style).
type WebConfig struct {
Enabled bool `toml:"enabled"`
ListenAddr string `toml:"listen_addr"` // e.g. ":8080" or "127.0.0.1:8080"
SiteTitle string `toml:"site_title"` // display name in the header
BaseURL string `toml:"base_url"` // public URL (used in metadata only)
Auth AuthConfig `toml:"auth"` // optional OIDC sign-in (Authentik)
Push PushConfig `toml:"push"` // optional Web Push digests (VAPID)
TTS TTSConfig `toml:"tts"` // optional server-side neural read-aloud (Piper)
Enabled bool `toml:"enabled"`
ListenAddr string `toml:"listen_addr"` // e.g. ":8080" or "127.0.0.1:8080"
SiteTitle string `toml:"site_title"` // display name in the header
BaseURL string `toml:"base_url"` // public URL (used in metadata only)
Auth AuthConfig `toml:"auth"` // optional OIDC sign-in (Authentik)
Push PushConfig `toml:"push"` // optional Web Push digests (VAPID)
TTS TTSConfig `toml:"tts"` // optional server-side neural read-aloud (Piper)
Games GamesConfig `toml:"games"` // optional casino (games.parodia.dev)
// AdminSubs is the allowlist of OIDC subjects allowed to view the
// owner-facing source-health dashboard at /status. Empty means the page is
// inaccessible to everyone (returns 404). Requires auth to be enabled.
@@ -108,6 +109,23 @@ type VoiceConfig struct {
Label string `toml:"label"` // menu label, e.g. "Ryan — US, male"
}
// GamesConfig wires the casino. It is signed-in only — there is money in it —
// so it does nothing without web.auth, and it needs the Matrix server name
// because that is how a player's identity reaches gogobee's euro ledger: an
// Authentik username is the Matrix localpart, and the Matrix id is the account.
type GamesConfig struct {
Enabled bool `toml:"enabled"`
// Host is the public hostname the casino answers on, e.g.
// "games.parodia.dev". Requests arriving on it are served the casino at "/";
// everywhere else the same pages live under /games. Empty means no host
// branching, which is the normal state of affairs in local development.
Host string `toml:"host"`
// MatrixServer is the server name half of a player's Matrix id
// ("parodia.dev" -> @reala:parodia.dev). Without it, no player can be
// identified in the economy and the casino stays shut.
MatrixServer string `toml:"matrix_server"`
}
// AuthConfig wires Pete's web UI to an OIDC provider (our Authentik instance).
// When enabled, signed-in users get their preferences stored server-side keyed
// by the OIDC subject; anonymous visitors keep using browser localStorage.