Skip to content

Multi-Regional Architecture

SectorWars 2102 supports a hub-and-spoke topology where a Central Nexus federates many player-owned regional servers. Each region is its own database, container, and governance domain. Travel and diplomacy between regions are first-class game mechanics.

This document focuses on the runtime / service-level picture. The data model details live in DATA_MODELS; this file connects those tables to the actual services.

Region kinds

Defined as a Postgres enum on region.region_type (services/gameserver/src/models/region.py):

Type Sectors Notes
central_nexus exactly 5000 The hub. Single instance.
terran_space exactly 300 The fixed starter region for new players.
player_owned 100–1000 Variable, owner-configurable, paid (PayPal subscriptions).

These constraints are enforced by a CHECK constraint on the regions table (see lines 139-141 of services/gameserver/src/models/region.py).

The Central Nexus uses a cluster-based geographic model. services/gameserver/src/api/routes/nexus.py exposes clusters via ClusterInfoResponse as the primary geographic grouping inside the Nexus.

Services involved

gameserver / central-nexus-server

Same Docker image (services/gameserver), two roles distinguished by env:

  • gameserver (dev profile) — single instance pointing at the dev Postgres.
  • central-nexus-server (multi-regional / production profiles) — SERVICE_TYPE=central-nexus, REGION_ID=central-nexus, NEXUS_ADMIN_MODE=true, ENABLE_CROSS_REGIONAL_API=true. Connects to central-nexus-db and redis-nexus.

Multi-regional API surface lives in:

  • services/gameserver/src/api/routes/nexus.py (prefix /nexus) — generate Nexus, view stats, list clusters.
  • services/gameserver/src/api/routes/regional_governance.py (prefix /regions) — economic / governance config, policies, elections, treaties.
  • services/gameserver/src/services/nexus_generation_service.py and regional_auth_service.py — domain logic.

regional-server-template

Defined in docker-compose.yml under the regional-template profile but not started as part of a normal docker compose up. It is a recipe — region-manager clones it for each provisioned region. Key env at runtime:

  • SERVICE_TYPE=regional-server.
  • REGION_ID=${REGION_NAME}.
  • CENTRAL_NEXUS_URL=http://central-nexus-server:8080.
  • REGIONAL_ISOLATION=true.
  • Connects to its own Postgres ({REGION_NAME}-db) and shares redis-nexus (DB index 1).

region-manager

Standalone FastAPI service that orchestrates region lifecycle.

  • Source: services/region-manager/src/main.py, region_provisioner.py, monitoring.py, models.py, config.py.
  • Templates: services/region-manager/templates/docker-compose.region.yml.j2 — Jinja-rendered Compose snippet for a new region.
  • Talks directly to the Docker daemon via the mounted socket (/var/run/docker.sock) — it can docker run siblings.

Its endpoints:

Method Path Effect
POST /regions/provision Validate the request, allocate DB, render compose template, start container, register with the Nexus. Returns immediately; the heavy lift is a background task.
DELETE /regions/{name} Stop + remove containers, drop the regional DB, unregister from Nexus.
POST /regions/{name}/scale Adjust CPU / memory / disk on a running region.
GET /regions, /regions/{name} List / inspect tracked regions.
GET /metrics Aggregate region count, player count, CPU/memory totals.
GET /health Used by the compose healthcheck.

A 30-second background loop (monitor_regions_loop) polls Docker stats per region. When a region averages >80 % CPU or >85 % memory it auto-scales up — capped at 8 cores / 16 GB. Symmetric scale-down kicks in when CPU stays under 20 % and memory under 30 % for the configured cool-down window.

redis-nexus

Dedicated Redis instance on port 6380 for cross-region pub/sub and shared cache. Lives in the nexus-network. The dev profile's redis-cache is not the same instance — production deployments run both.

nginx-gateway routing

services/nginx-gateway/nginx.conf knows about regional fan-out. Any path matching /api/v1/regions/{region_name}/... is rewritten and proxied to region-{region_name}-server:8080. This is how the player client and admin UI reach a specific regional server without each frontend having to know its URL.

Provisioning flow (verified)

  1. Admin or paid player calls POST {region-manager}/regions/provision with name, owner ID, sector count, etc.
  2. region-manager:
  3. Validates the request (provisioner.validate_region_request).
  4. Marks the region provisioning in its in-memory map.
  5. Schedules a background task that:
    • Creates a new Postgres database for the region.
    • Generates a region config (generate_region_config).
    • Renders the Jinja Compose template at templates/docker-compose.region.yml.j2.
    • Starts the regional container(s) via the Docker SDK.
    • Calls register_with_nexus so the Central Nexus knows about it.
  6. The new regional gameserver boots, connects to its DB and to redis-nexus, and starts answering API calls.
  7. Players reach it via nginx-gateway at /api/v1/regions/{name}/....

Termination is the inverse: DELETE /regions/{name} revokes containers, drops the DB, and unregisters from the Nexus.

Travel between regions

Modeled on inter_regional_travels (see services/gameserver/src/models/region.py):

  • Source region, destination region, travel method (platform_gate, player_gate, warp_jumper).
  • assets_transferred JSONB — what the player carries with them.
  • Status workflow in_transit → completed | failed | cancelled.
  • Constraints: source ≠ destination; cost ≥ 0.

The runtime path uses Redis pub/sub on redis-nexus to coordinate handoffs between source and destination regional servers; the relevant logic is split between regional_governance.py and the regional auth service.

Governance and diplomacy

Each player-owned region picks a governance_type (autocracy | democracy | council), a voting_threshold (0.1–0.9), and an election_frequency_days (30–365). These show up as columns on the regions table.

  • regional_policies — proposed changes (tax rate, PvP rules, trade policy, immigration, defense, cultural). Voted on by region members; outcome captured in votes_for / votes_against / status.
  • regional_elections + regional_votes — elections for governor, council_member, ambassador, trade_commissioner. Vote weight is 0.0–5.0.
  • regional_treaties — bilateral agreements (trade_agreement, defense_pact, non_aggression, cultural_exchange).

Membership is per-player per-region (regional_memberships): visitor | resident | citizen, with reputation scores -1000…1000 and voting power 0.0…5.0.

These tables and their constraints all live in services/gameserver/src/models/region.py; the API endpoints are in services/gameserver/src/api/routes/regional_governance.py (prefix /regions).

Validations

Numeric bounds (tax rate, voting threshold, election frequency, etc.) are enforced as SQL CHECK constraints in services/gameserver/src/models/region.py and as Pydantic Field(ge=..., le=...) validators in services/gameserver/src/api/routes/regional_governance.py (e.g. EconomicConfigUpdate, GovernanceConfigUpdate).

Subscription / billing

Region ownership ties into PayPal subscriptions. central-nexus-server carries the relevant env vars (PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_GALACTIC_CITIZEN_PLAN_ID, PAYPAL_REGIONAL_OWNER_PLAN_ID, PAYPAL_WEBHOOK_ID, PAYPAL_MODE). The relevant route file is services/gameserver/src/api/routes/paypal.py.

  • services.md — service-level details on region-manager, central-nexus-server, nginx-gateway.
  • deployment.md — profile / network / volume reference.
  • auth.md — how regional auth ties back to the Nexus user store.
  • Sectorwars2102/services/gameserver/src/models/region.py — the canonical schema for everything regional.