Giuseppe.Rega
← Back to projects

Zampa Games — browser MMORPG on microservices

Eleven services, real-time physics over Socket.IO, and NPCs driven by FSM, RL and an LLM.

2025 — 2026
  • .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:

  1. A finite state machine for immediate decisions: patrol, chase, flee. Negligible cost, runs every tick.
  2. Reinforcement learning for policies that improve over time.
  3. 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.