Zampa Games — browser MMORPG on microservices
Eleven services, real-time physics over Socket.IO, and NPCs driven by FSM, RL and an LLM.
- .NET 8
- YARP
- Node.js
- Socket.IO
- Python
- FastAPI
- Three.js
- PostgreSQL
- Redis
- MongoDB
- RabbitMQ
The problem
A real-time multiplayer game puts pressure on everything at once: latency, shared state, server authority and cost per player. I wanted to find out where a microservices architecture actually breaks, and the only honest way to do that was to build one that had to hold up at roughly sixty updates a second.
Outcome
Eleven coordinated services, with physics separated from domain logic and NPC intelligence isolated in its own service. The system is not currently running — I shut it down to free resources on the server.
This project is not currently online: it ran on a Raspberry Pi shared with other systems and I removed it to reclaim memory and disk. It is included here because the code and the architectural decisions remain the most interesting thing I have written.
How the services divide
| Service | Technology | Role |
|---|---|---|
api-gateway |
.NET 8 + YARP | HTTP and WebSocket reverse proxy |
user-be |
.NET 8 | Authentication, inventory, quests, guilds |
physics-server |
Node.js + Socket.IO | Real-time physics, combat and NPCs |
ai-service |
Python + FastAPI | NPC behaviour |
game-client |
Three.js + Vite | 3D browser client |
ai-dashboard |
React | AI behaviour metrics |
Underneath: PostgreSQL for relational data, Redis for cache, MongoDB for documents and RabbitMQ for inter-service events.
Why physics runs alone
The simulation loop has a constraint no other service has: it must finish every frame inside a fixed window. Putting it in the same process as domain logic means a slow inventory query becomes a visible stutter on screen. Separating it also lets it scale on a different metric — concurrent players per zone, not requests per second.
NPCs: three layers, not one
NPC behaviour combines three mechanisms with very different costs:
- A finite state machine for immediate decisions: patrol, chase, flee. Negligible cost, runs every tick.
- Reinforcement learning for policies that improve over time.
- A language model as an advisor, not a driver: consulted rarely, for dialogue and high-level decisions.
The third rule is what makes the system affordable: an LLM called every frame is unaffordable in every sense of the word. The model suggests; the state machine executes.
What it taught me
That the right number of microservices is the one the domain boundaries impose, not the one that looks modern. Eleven services is a lot for a personal project, and the expensive part was not writing them — it was keeping the contracts between them in agreement.