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.
Forced ingress transport: 'http' so the WebSocket upgrade always runs over HTTP/1.1.
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.
shared/src/floors/floor1.ts史 slime · 蝠 bat · 钥 key · 剑 swordClaude 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.
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.
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.
Rules, combat math, co-op mechanics, and edge cases, written down before any code.
Phaser 3 client, Colyseus room, shared TypeScript types, and tested combat logic.
Multi-stage Docker build that serves the client and the WebSocket room from one port.
Bicep infrastructure and a GitHub Actions pipeline to Azure, committed and pushed.
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.
Every push to master, or a manual run, starts the workflow.
Builds the shared package and runs the combat tests. Deploy waits for this job.
GitHub gets a short-lived Azure token. No client secret exists.
The image is built in Azure and tagged with the commit SHA and latest.
The Container App moves to a new revision on the SHA-tagged image.
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.
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.
Forced ingress transport: 'http' so the WebSocket upgrade always runs over HTTP/1.1.
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.
The first deploy uses a public placeholder image. A useAcrRegistry flag switches to the private registry once the role assignment has propagated.
The placeholder image listens on port 80 and the game listens on 8080. The startup probe got "connection refused."
Made the ingress target port a parameter, and added a container HEALTHCHECK on /healthz.
Mobile browsers close background WebSockets when you switch apps or lock the screen, and the player lost their place in the tower.
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.
After launch night, every change came from watching people play on their own phones.
Started from a loose idea ("co-op 魔塔 with friends"), wrote down the rules and edge cases first, then built to that spec.
Ran a team of specialized AI agents and kept the key decisions: architecture, security posture, and what to leave out.
Deployed to Azure with IaC and OIDC, debugged problems that only show up in production, and shipped fixes from real use.