I built Satos as a complete multiplayer game, not a client demo. Two players — Bull and Bear — fight over 25 territories. Combat is a quiz. Around that I shipped character customisation, regional themes, a live WebSocket match, and the admin tools you need to run it.

A match is about 15 minutes: lobby, spreading over empty land, then conquering neighbours, until a base falls or turns run out. I implemented that as stages on the server, not as one giant room class.

What I owned

  • Unity client — one project targeting WebGL, iOS, Android, and desktop
  • Node backend — Express for accounts, Colyseus for the match
  • Admin — a React dashboard, plus a trait packager for cosmetics
  • Deploy — the whole stack comes up with docker compose up
dobab/
├── Satos/              # Unity game client (C#)
├── Backend/            # Node.js API + game server
├── AdminUI/            # React admin dashboard
├── AdminTPM/           # Trait Package Manager
└── Deployment/         # Docker, nginx

Realtime, on purpose

The match server is a sequence of Stage classes. Lobby only handles ready, side select, and stakes. Spreading and conquer do not share that code. Client and server talk through a numeric protocol grouped by phase, so a typo is a compile problem, not a silent desync.

export class Lobby extends Stage {
    onInit() {
        this.countdownTimer = this.settings.COUNTDOWN_FULL_SEC
        this.game.onMessage(ClientMessage.LOBBY_READY, this.onPlayerReady)
        this.game.onMessage(ClientMessage.LOBBY_SELECTSIDE, this.onPlayerSelectSide)
        this.game.onMessage(ClientMessage.LOBBY_STAKE_OFFER, this.onPlayerStakeOffer)
        this.game.onMessage(ClientMessage.LOBBY_STAKE_RESPONSE, this.onPlayerStakeResponse)
    }
}

Quiz battle in Satos

I did not want a balance tweak to mean a new build. Almost every gameplay number — XP, quiz timers, lobby countdown, turn limit — lives in the database with a default and a description. Operators change it from the admin panel. The game picks it up at runtime.

Character traits

The admin is two tools I built for running a live game: a dashboard for accounts, config, the question bank, mail, and server health, and a YAML-based packager for hair, skins, and the rest of the cosmetics.

Satos admin dashboard

If you are reading this next to a CV: this is the project where I had to hold a game client, a realtime server, a REST API, and ops UI in one head — and make them agree.

Piece What I used
Game client Unity 6000
Backend Node.js
WebSocket Colyseus
REST Express.js
Database SQLite + Sequelize
Admin React + Vite
Deploy Docker