Case study · AI-agent-built · Deployed on Azure

魔塔Tower of Doom

A real-time, two-player co-op puzzle RPG. I built it in one night by directing a studio of Claude Code agents, and shipped it to Azure Container Apps with infrastructure as code and a CI/CD pipeline that stores no Azure secrets.

Floor 1, rendered from shared/src/floors/floor1.ts史 slime · 蝠 bat · 钥 key · 剑 sword
1 nightfrom design doc to a playable multiplayer game with its Azure pipeline
5,277lines in the first commit: client, server, shared logic, Bicep, CI
15tower floors with a difficulty curve modeled on classic 魔塔
0Azure secrets stored in GitHub, thanks to OIDC federation
How it was built

One person running a studio of AI agents

Claude Code Game Studios turns Claude Code into a game studio: specialized agents organized like a real team, with directors who guard the vision, leads who own a domain, and specialists who write the code. Each agent has a defined scope, escalation path, and quality gate. I ran it the way a producer runs a team.

Me
Set the product vision, made the architecture and trade-off calls, reviewed agent output, and tested on real phones.
Directors
Held the design pillars: co-op under scarcity, and fights you can solve with math before you commit.
Leads
Owned design, gameplay, networking, and DevOps, and escalated conflicts between them.
Specialists
Wrote the game design doc, Phaser client, Colyseus server, combat formula with tests, floor data, Dockerfile, and Bicep.

Spec first, then code

The build started from a written game design doc (design/gdd/tower-of-doom.md) covering rules, formulas, and edge cases. Having the spec first let the agents work in parallel without drifting apart.

Decisions I made

The server has the final say on game state, so the two players can't fall out of sync. The combat formula lives in one shared package used by both client and server. Unwinnable fights are refused instead of killing the player. The room runs on a single replica, and the README states that limit.

Evening

Design doc

Rules, combat math, co-op mechanics, and edge cases, written down before any code.

Night

Game + server

Phaser 3 client, Colyseus room, shared TypeScript types, and tested combat logic.

Late night

Container

Multi-stage Docker build that serves the client and the WebSocket room from one port.

Before sleep

Cloud

Bicep infrastructure and a GitHub Actions pipeline to Azure, committed and pushed.

Deployment

Production on Azure, not a localhost demo

A real-time multiplayer game is harder to host than a web page. It needs long-lived WebSocket connections, game state held in memory, and a client that survives phones dropping the connection. Everything below is defined in the repo and redeploys on every push to master.

Players Phone + desktop browsers Phaser 3 client touch + keyboard AZURE · rg-tower-of-doom · infra/main.bicep Container App Colyseus TowerRoom (Node 20) Authoritative game state Serves static client + /healthz replicas: 1 · 0.5 vCPU · 1 GiB system-assigned identity Container Registry admin user off · AcrPull role Log Analytics app + system logs 30-day retention GitHub Actions test → build → deploy OIDC federated login on push to master WSS, HTTP/1.1 image pull az acr build az containerapp update

Push

Every push to master, or a manual run, starts the workflow.

Test gate

Builds the shared package and runs the combat tests. Deploy waits for this job.

OIDC login

GitHub gets a short-lived Azure token. No client secret exists.

Build in ACR

The image is built in Azure and tagged with the commit SHA and latest.

Roll out

The Container App moves to a new revision on the SHA-tagged image.

Four production problems, found and fixed

These came from deploying for real, and each fix is written into main.bicep or the server code so the next person doesn't hit it again.

Symptom · mobile players hung on "joining"

Azure ingress in auto mode negotiated HTTP/2 with mobile browsers, and WebSocket over HTTP/2 wasn't reliable end to end. The Colyseus join never finished.

Fix

Forced ingress transport: 'http' so the WebSocket upgrade always runs over HTTP/1.1.

Symptom · first deploy failed with "Operation expired"

A chicken-and-egg problem. The app's managed identity needs the AcrPull role before it can pull from the private registry, but that identity doesn't exist until the app does.

Fix

The first deploy uses a public placeholder image. A useAcrRegistry flag switches to the private registry once the role assignment has propagated.

Symptom · revision never became healthy

The placeholder image listens on port 80 and the game listens on 8080. The startup probe got "connection refused."

Fix

Made the ingress target port a parameter, and added a container HEALTHCHECK on /healthz.

Symptom · switching apps dropped the player

Mobile browsers close background WebSockets when you switch apps or lock the screen, and the player lost their place in the tower.

Fix

The server holds the player's seat for 10 minutes (allowReconnection). The client stores a reconnection token and resumes the same session.

Known limit, stated up front: a Colyseus room keeps state in one process's memory, so the template pins maxReplicas to 1. To scale out, add Redis for shared presence and state first. Otherwise players in the "same" room could land on different instances.

Git history

Shipped in one night, then improved from real play

After launch night, every change came from watching people play on their own phones.

Initial commit: real-time co-op game, Docker, Bicep, and the CI/CD pipeline
Mobile touch controls; fixed the Azure deploy and connection issues
Character select (princess / warrior), mobile-responsive UI, sprite fixes
Pressure-plate and gate puzzle built for two players; tap-for-info tooltips
Reconnection support so switching apps doesn't drop players; audio start fix
Reconnection grace period raised to 10 minutes, based on how people actually played
Tower expanded from 5 to 15 floors with a difficulty curve modeled on classic 魔塔
Why this matters for forward-deployed engineering

The same loop as a customer engagement

Ambiguity to a spec

Started from a loose idea ("co-op 魔塔 with friends"), wrote down the rules and edge cases first, then built to that spec.

Agents as a force multiplier

Ran a team of specialized AI agents and kept the key decisions: architecture, security posture, and what to leave out.

Into their cloud, then iterate

Deployed to Azure with IaC and OIDC, debugged problems that only show up in production, and shipped fixes from real use.