There's a thing running on the Raspberry Pi at home that I struggle to explain in one sentence.

Half of it is a serious multiplayer Dou Dizhu (Chinese poker) table. The other half is a persistent shared world stitched together from seven parallel universes, with every NPC line generated by an LLM in real time. They share one account system, one wallet, one WebSocket hub. I didn't plan this — it grew this way over a few months and now I keep both halves alive because friends like both.

The card table

The card table is a Svelte 5 island. The entire experience — dealing, animations, the "auto-play" button for when someone goes AFK — comes in at just over 17 KB after gzip. The rest of the site stays on Jinja + Alpine; I don't reach for an SPA unless the interaction model demands it, and most pages don't.

On the server I implemented 14 different card pattern recognizers (single, pair, triplet with attached card, straight, plane, rocket, ...) and the full bidding flow. Plays go through WebSocket using a commit-reveal pattern:

client → server : H(play, salt)        # commit
server          : "your turn locked in"
client → server : play, salt           # reveal
server          : verify H, broadcast

It's overkill for friends playing card games, but it removed an entire category of "I swear I saw their card flash for a second" arguments.

The shared world

The other half is called Qílù Town (歧路镇 — "Town at the Crossroads"). Every player enters the same persistent world, made of seven regions: cyberpunk Tokyo, fantasy kingdom, wasteland, wuxia mountains, deep space, Victorian London, dreamscape. NPC dialogue and world events stream from a language model.

The hard part was concurrency. Players in the same region can act simultaneously, and many of those actions mutate shared state — picking up an item, talking to the one NPC who only has one quest left to give today, buying the last of something. I gave each region its own async write lock plus a snapshot version number; conflicting actions retry against the latest snapshot. The in-world market is wired to a real external price feed, so "the price of fuel crashed in the wasteland today" actually correlates with what the live market did. It makes the world feel less canned.

The trickiest engineering wasn't either game. It was the two-tier circuit breaker around the LLM: when the shared pool burns through 80% of its daily budget, NPC responses automatically degrade to templated lines; when a single user blows past their per-day cap, they get cut off entirely. Cost control matters more than dialogue quality when the platform is hosted at home.

I started this because I wanted somewhere to play cards with friends without installing yet another app. The fantasy world is just what happened when I left the LLM cost knobs in my own hands.