Skip to content
← Back to Blog

How Whimcade Runs: What Happens After You Type a Wish

Whimcade is an arcade where you type a wish and an AI agent builds you a playable game. This is what happens under the hood between the wish and the game: the build fleet, the one-file rule that keeps every game sealed, the relay that lets sealed games play online, and the metered gateway a build uses to buy its 3D heroes.

Whimcade is an arcade. You type a wish, “a shark game where one player is the shark,” “a tower defense on a hex grid,” “a two-player duel on one keyboard,” and about half an hour later a game appears in the arcade that anyone can play in a browser, with no install and no account. Almost everything in the arcade started that way, as a wish from a visitor. This post is about what happens in those thirty minutes, and about the machinery that keeps the result sealed, playable, and, when the wish asks for it, online.

The Shape of the Thing

There are two halves. The arcade is a FastAPI application on one e2-medium virtual machine: Postgres, Redis, the API, a Celery worker, and Caddy on the host terminating TLS. Game bundles live in a Google Cloud Storage bucket and are served from a separate cookieless origin, so a game never shares a cookie jar with the arcade. The builder fleet is not Whimcade’s at all. When a wish is queued, the arcade posts it to a webhook on our agent platform, which boots a fresh VM running an agent with a single job: build this game, test it, deliver it. Whimcade holds no model credentials and runs no agent process of its own. It has exactly two surfaces facing the builder: a dispatch endpoint it calls, and a delivery endpoint the builder calls back, authenticated by a per-job bearer token that is rotated on every re-dispatch.

Whimcade architecture: a wish enters the arcade (FastAPI on one VM with Postgres, Redis, Celery and Caddy), the arcade dispatches a job to the agent platform webhook, a fresh builder VM runs one agent session that builds, self-tests and screenshots the game, delivers one self-contained HTML bundle back with a per-job bearer token, and the arcade serves it from the cookieless play origin with sandbox allow-scripts and connect-src none.
The two halves. The arcade never runs an agent; the builder never holds a key. Every game ends up as one sealed HTML file on a cookieless origin.

From a Wish to a Game

The agent on that VM writes the game as a single HTML file, loads it in a headless browser, plays it through a self-test (does it boot, does input move something, does the title screen say what the game is), fixes what it finds, and posts the bundle, a thumbnail, and a metadata record back to the delivery endpoint. The median build is twenty-eight minutes. The platform caps concurrent sessions for this automation at twenty; Whimcade dispatches while fewer than sixteen are in flight, which puts the ceiling at roughly thirty-two builds an hour. During the hours creators are awake, a warm pool of five pre-booted VMs stands ready, so an in-window build skips the three-minute boot.

Above the cap, wishes wait in a queue. The queue is a Redis sorted set scored by arrival time minus a head start that scales with the creator’s plan. That gives subscribers a standing priority lane with a property we could prove rather than tune: a wish that has waited longer than the largest head start is ahead of every later arrival of every weight, so nobody’s extra wait can exceed one head start no matter how many heavier wishes keep arriving. Builds that fail for reasons outside the game (a starved fleet, a VM that never called back) go back to the front of the queue without burning one of the build’s own retries, so a healthy wish is never abandoned because the fleet had a bad hour.

One File, Sealed

Every game on Whimcade is one HTML file. It runs in an iframe with sandbox="allow-scripts" on an origin whose content security policy says connect-src 'none'. The game cannot fetch, cannot open a WebSocket, cannot read a cookie, cannot reach the arcade’s API. That is the primary safety control for code an LLM wrote on behalf of an anonymous stranger, and it is also what makes bundles cacheable, portable, and remixable: save the file, open it on your own machine years from now, and it still plays.

Everything privileged happens one frame up. The player page, the first-party page that hosts the iframe, is the only thing that talks to the arcade. High scores leave the sandbox over postMessage to that page, which checks the shape of the message and posts the score with the visitor’s session. Saved progress goes out the same way. The game never learns where the data went. As it turned out, that same relay shape is how sealed games got multiplayer.

What a Game Promises

A lot of wishes describe other people: “a random player is chosen as the shark,” “Solo and Public buttons in the lobby,” “red team versus blue team.” So every bundle declares what it is. The builder writes <meta name="whimcade:players" content="solo|bots|local|online"> into the file, the delivery plane parses it into the game record, and the arcade card renders it as a badge: “You vs. bots,” “2+ players, one device,” “Online.” A wish for a 5v5 is built as your seat plus nine readable bots, announced on the title screen. A bundle that declares nothing gets no badge, never a wrong one, and a game is never allowed to promise online play it cannot deliver.

Playing Online Without Opening a Socket

Online play keeps the one-file rule. The game still cannot open a socket. The player page can. It holds one WebSocket to a small room service on the arcade and bridges frames both ways between the room and the iframe over postMessage. The bundle stays byte-for-byte self-contained, and opened standalone it simply plays solo.

The net relay: the sandboxed game iframe and the first-party player page exchange postMessage frames; the player page holds one WebSocket to the room relay, which validates envelopes and fans frames out untouched to other players' pages; an in-process room registry tracks seats, rejoin tokens and away grace, and Postgres keeps the room FSM and audit events.
The relay. The game speaks only postMessage; the player page holds the socket; the server never reads the payload.

The room service is deliberately dumb. It validates the frame envelope, enforces size and rate caps, and fans the payload out untouched, broadcast or unicast. There is no game logic server-side. Games are host-authoritative: the first peer in a room simulates, the others send inputs, and the host broadcasts compact snapshots at ten to twenty hertz. Clients render by interpolating between the last two snapshots. That is a forty-year-old pattern, and the reason it works here is that the builder agent is not asked to implement it. The builder fetches a finished kit from the arcade at build time, pastes it verbatim into an inline script ahead of the game code, and wires one call:

const NET = WhimcadeNet.start({
  maxPeers: 4, hz: 15,
  snapshot:   () => compactState(),                  // host: what to broadcast
  applyInput: (peer, input) => inputs[peer] = input, // host: a peer's input
  input:      () => localInput(),                    // client: what to send
  onRoster:   (peers, self, host, info) => seat(peers, info.names),
  onClosed:   (reason) => backToSolo(),              // room ended: bots again
});
// game loop:   if (WhimcadeNet.simulating()) simulate(dt);
// client draw: const { prev, cur, alpha } = WhimcadeNet.snapshots();

The kit runs the host and client timers, validates message shapes, exposes the roster and names as live fields, and falls back to solo when the room closes, so a callback bug in the game can never kill the room. The delivery self-test loads two copies of the bundle under a mock room and checks that both post ready and the host broadcasts. The game also tells the page how many seats it really has and which modes it offers, and the page sizes the room from that, so a 1v1 duel gets a two-seat room rather than an eight-seat one with quick play cheerfully adding players the game cannot seat.

Room membership is not the socket. A refresh, or a tab switch on iOS (Safari suspends background tabs and drops their sockets), does not end your seat. The peer goes away, keeps its seat and its id for a ninety-second grace, and the page reconnects with a per-seat rejoin token rather than the visitor fingerprint, which changes when a phone moves between networks and is shared by two people on the same home router. If the old socket is still attached when the rejoin arrives, the newcomer wins and the old page is told not to retry, so two tabs never fight over one seat. Nobody in the room is notified while a peer is away, because the roster did not change and a new event would force every already-published game to handle it. The frozen kit inside a published bundle keeps working through every change to the service because every new field on the wire is optional.

On top of that sit the things that make it feel like a lobby: names resolved once at socket accept, public rooms listed on the game page and in an “N games waiting for more players” strip at the top of the arcade, quick play that fills the oldest joinable room, a pill that follows you around the site while you hold a seat, and a host’s start button that runs a twenty-second ready check and opens the game in a new tab inside the tap so popup blockers allow it. Ranked play rates a match only when every player’s game, host and clients alike, reports the same result. Online play is a paid add-on in the arcade’s coin economy, switched on per build, with one honest rule: if the delivered bundle does not declare online, the surcharge is refunded automatically, exactly once, whether or not anyone complains.

Buying a 3D Hero

The most common complaint about 3D games was the characters. Two things fixed the rest of the picture first. Every 3D build follows a short look rule (tone map and gamma, sky-matched fog, one shadow map fitted to the view, bevels and analytic ambient occlusion, never ray-cast occlusion at load), and every model is a named tree of parts that a script audits before delivery for floating parts, coplanar box faces, missing parents, and duplicate names. What that cannot fix is a hero made of primitives, which looks like a hero made of primitives.

The answer is image-to-3D: the agent draws a concept image of the hero, a model turns the image into a textured mesh, and the mesh goes into the game. The design problem is not the model but the money. The builder agent is a session on a throwaway VM running code it wrote itself; it must never hold a provider key. So the arcade holds the keys, and a build buys each asset from an endpoint on the arcade using the same per-job bearer token it will use to deliver the game: POST /api/internal/builds/{job}/assets/{kind}, where kind is voice, image, video, or mesh. The gateway calls the provider with the arcade’s credentials and returns the bytes. Assets are only sold while the build is in the building state. Every call is a row in an audit table with its request, its cost, the provider’s job id, and the concept image it was made from, so “what did this build actually buy” is a query, not a guess.

Each build has a dollar credit set by what the creator bought. Before calling a provider the gateway quotes the call: a voice line from its character count at the slowest pace, a video clip from its length, a mesh at the provider’s fixed per-generation price. The call is refused with a 402 when used credit plus the quote exceeds the credit, where used is the exact cost of succeeded calls plus the quotes of calls still in flight, computed under the build’s row lock so two parallel calls cannot spend the same dollar twice. The cap also binds in count, because a ten-model credit quoted at three cents each would otherwise let a builder buy fifteen models at two cents.

Choosing the provider was a measured study rather than a guess. Six concept images from a cheap image model went through five hosted image-to-3D services, thirty generations, every result post-processed the same two ways and rendered from three fixed views, then scored blind by a model reading shuffled contact sheets. Textured output beat baked vertex colour by about a point for every provider, because baking loses the fine marks that sell a model: the golem’s glowing cracks, the tower’s brick lines. TRELLIS at two cents and twenty seconds tied the most expensive option at forty cents and eighty seconds for the best mean score. The newer TRELLIS release came last, because its meshes arrive as up to 169 loose fragments with inner shells that darken the whole model under occlusion. One well-known open model was ruled out on licence alone; its terms exclude the EU and the UK, including use of the outputs.

So the gateway’s mesh kind sells two grades: standard, TRELLIS with a simplified mesh and a 512-pixel texture, and detailed, a premium provider for a named hero. Every returned GLB has its textures shrunk to 512-pixel JPEG on the arcade before it goes back, so a hero ships at a few hundred kilobytes. The look picker has a fourth tier for it, priced at about three times provider cost in the arcade’s gem currency, and a free-look build never hears the endpoint exists. The rule for the builder is that a bought model must visibly replace a named hero at readable size, with a before-and-after screenshot in the delivery, and that concept images come from the gateway’s own image kind rather than the game’s existing sprites.

What the builders did with the models was the surprise. A two-dimensional isometric strategy builder bought fifteen models and rendered them into sprite sheets. A first-person builder converted five models into inline geometry for its own renderer. Neither bundle contains a GLB. The audit table is the only place you can see what was bought.

Making Them Move

A static hero is a statue. For humanoids, one hosted provider rigs a mesh for about ten cents and returns a 24-bone skeleton with walking and running clips, and that works: a game-size animated knight is a third of a megabyte and still walks after decimation. For anything else it fails in a specific way. The same endpoint accepts a fox and fits it with a biped skeleton and a human walking clip; the fox stands upright and folds in half. Nobody sells a quadruped rig as a service at a price that fits a two-cent model, so the creature path is self-hosted: UniRig, an open auto-rigger, on a rented L4 (or a T4 at twice the time when that is what is in stock), followed by our own clip retargeting that classifies both skeletons by shape into legs, arms, spine, head, and tail and copies each segment’s rotation away from rest in the body’s own frame. Sources are a CC0 humanoid library and the Khronos sample fox. The fox walks and runs on four legs on a captured cycle; the golem punches; files stay under four hundred kilobytes with three or four clips baked in.

Animated heroes are not on sale yet, even though every piece works. A mesh comes back from the gateway in twenty seconds. A rig needs a GPU, several minutes of work, and Blender, so it is a background job on a worker image that has them, returning the animated model on a later poll. In the meantime the paid 3D look ships a template rig kit that needs no GPU at all: it fits an eleven-bone biped, a twelve-bone quadruped, a vehicle with one rigid bone per wheel, or a single-bone prop to any mesh from its bounds, weights each vertex to its two nearest bones, and skins on the CPU with planted feet so a walking toe never dips through the floor. It checks itself at fit time, and the self-test reports a bad pose the same way it reports a missing part.

Try It

Browse the arcade at whimcade.com, play what people have wished for so far, or make a wish of your own. Every game is one file in a sealed box, every online room is a page talking to a dumb relay, and every hero was bought through a meter. If you build on AI agents and want to see what it takes to let strangers drive them in public, this is where we find out first.

Running agents in front of strangers?

We design and operate agent platforms with the boring parts built in: sealed sandboxes, metered access to paid APIs, and rooms that survive a phone going to sleep. Whimcade is where we prove it in public before it reaches a client.

Get in Touch

Building something real?

Tell us what slows your team down and get a scoped plan with a budget — drafted in minutes, confirmed by a human.

Scope a Pilot — Free