ROWS
ROWS — Rust Open World Server
Section titled “ROWS — Rust Open World Server”ROWS is a single-binary Rust reimplementation of the OWS (Open World Server) game backend. It consolidates the five .NET microservices (PublicAPI, InstanceManagement, CharacterPersistence, GlobalData, Management) into one unified service with both REST and gRPC interfaces.
Architecture
Section titled “Architecture”- REST API (Axum) — player-facing endpoints: login, registration, character CRUD, zone connections
- gRPC (Tonic) — internal service-to-service communication and UE5 dedicated server coordination
- RabbitMQ — async job queue for instance lifecycle events (spin-up, shutdown, health checks)
- PostgreSQL — persistent storage for accounts, characters, abilities, world state
- ValKey — session cache and ephemeral game state
- Agones — game server fleet management for UE5 dedicated server instances
Endpoints
Section titled “Endpoints”| Group | Path Prefix | Description |
|---|---|---|
| Auth | /api/users/* | Login, register, session management |
| Characters | /api/characters/* | Character CRUD, class selection |
| Zones | /api/zones/* | Zone listing, server-to-connect-to |
| Instance | /api/instance/* | Server instance lifecycle |
| Global | /api/global/* | Key/value world state |
| Health | /health, /ready | Liveness and readiness probes |
| gRPC | Port 4322 (multiplexed) | Internal service mesh |
Deployment
Section titled “Deployment”ROWS deploys as a single Docker image (ghcr.io/kbve/rows). ArgoCD syncs the manifest from apps/kube/rows/manifest/rows-deployment.yaml with auto-prune and self-heal enabled.
The container exposes:
- Port 4322 — REST + gRPC multiplexed (Axum + Tonic on a single port)
Per-tenant deployment
Section titled “Per-tenant deployment”Each game + environment is a separate ROWS deployment pinned to its own tenant via env. The config is loaded and validated once at boot (RowsConfig::from_env):
| Env | Purpose | Notes |
|---|---|---|
OWS_API_KEY | Tenant customer_guid (UUID) | Required for beta/release; dev falls back to an ephemeral guid with a warning |
OWS_TENANT_SLUG | Human label (e.g. chuckrpg-dev) | Surfaced in logs, DeploymentInfo, and the Prometheus service label (rows-<slug>) |
OWS_ENV | dev | beta | release | Drives the required-env guards |
AGONES_FLEET | Per-tenant Agones fleet | Required for beta/release; the GameServer watcher filters by agones.dev/fleet=<fleet> |
AGONES_NAMESPACE | Agones namespace | Required for beta/release |
SUPABASE_JWT_SECRET / SUPABASE_SERVICE_KEY_HASH | Player + trusted-server auth | Optional; ROWS boots in legacy mode without them |
GET /api/System/DeploymentInfo echoes the active tenant_slug, environment, and customer_guid for verification.
Authentication & Identity
Section titled “Authentication & Identity”ROWS has three distinct identities. They are different axes — do not conflate them.
| Identity | What it is | Source | How ROWS reads it |
|---|---|---|---|
Tenant (customer_guid) | A game + environment — e.g. chuckrpg-dev, chuckrpg-beta, xy-release. One fixed value per deployment. | OWS_API_KEY env (per-tenant secret) | X-CustomerGUID header, enforced equal to the process tenant |
Player (userguid) | A person. | Supabase UUID (sub) | Supabase JWT, or a session GUID minted from it |
| Trusted server | The UE dedicated server / Iris instances. | Service key | x-service-key header, argon2-verified against SUPABASE_SERVICE_KEY_HASH |
The tenant is not the player. customer_guid partitions every table (users, characters, zones, world servers, maps). Within one tenant, all players and all UE instances share that customer_guid so they inhabit the same world — multiple Iris instances are just rows in worldservers / mapinstances under that tenant. The player is identified by their Supabase UUID, which becomes the OWS userguid.
Multi-tenant by deployment, single-tenant by process. ROWS runs one process per tenant (game + environment); each deployment pins its customer_guid from OWS_API_KEY at boot. The Postgres DB is shared across tenants and isolated only by the customerguid column, so the require_customer_guid middleware rejects (403) any request whose X-CustomerGUID differs from the process tenant — a client cannot reach another game’s rows by swapping the header. gRPC, WebSocket, MQ consumers, and background jobs always use the process tenant directly.
Player login. The client signs in via Supabase (GoTrue) and posts the JWT to ExternalLoginAndCreateSession. ROWS validates it (HS256, SUPABASE_JWT_SECRET), keys the OWS user on the Supabase sub, and returns a UserSessionGUID. The UE client glue lives in the ROWSupabase module of the KBVESupabase plugin.
Gates.
- World IP (
GetServerToConnectTo/JoinMap) requires a confirmed login — a Supabase bearer JWT or a live session GUID — and verifies the caller owns the requested character before returning a server address. - Server-write endpoints (position/stats/logout, zone status, launcher register, spin-up/down) require a valid service key; player credentials do not pass. The dedicated server loads the key from
OWS_SERVICE_KEYonly onIsRunningDedicatedServer(), so it never enters a client build, and talks to ROWS in-cluster over the ClusterIP — the key never crosses the public internet.
SUPABASE_SERVICE_KEY_HASH (rows) and the plaintext OWS_SERVICE_KEY (fleet) both derive from the one kilobase secret supabase-service-key (hash + key). The GoTrue customer_guid JWT claim is intentionally unused — tenant identity comes from the process config (OWS_API_KEY), never from the token.
Procedural World — Seed-Based Multi-Zone Architecture
Section titled “Procedural World — Seed-Based Multi-Zone Architecture”Overview
Section titled “Overview”Each game zone is a procedurally generated region driven by a deterministic seed. The same seed always produces identical terrain, foliage, and spawn layouts. This enables:
- One map, many worlds — a single
Lvl_Proceduralmap generates different zones from different seeds - Elastic scaling — any server pod can host any zone, just give it a seed
- Crash recovery — replace a dead server, give it the same seed, world is identical
- Discovery — new zones are just new seeds, no additional map packaging required
Data Model
Section titled “Data Model”The maps table gains a seed column:
ALTER TABLE ows.maps ADD COLUMN seed BIGINT DEFAULT 0;
-- Example zonesINSERT INTO maps (customerguid, mapname, zonename, seed) VALUES ('83d88046-...', 'Lvl_Procedural', 'Grassland', 0xA1B2C3D4), ('83d88046-...', 'Lvl_Procedural', 'Arctic', 0xDEAD0002), ('83d88046-...', 'Lvl_Procedural', 'Desert', 0xCAFE0003), ('83d88046-...', 'Lvl_Procedural', 'Marshlands', 0xBEEF0004), ('83d88046-...', 'Lvl_PvPArena', 'PvPArena', 0); -- static map, no seedROWS Flow
Section titled “ROWS Flow”1. Player → GetServerToConnectTo(zoneName: "Arctic")2. ROWS → Agones allocate → server starts on Entry3. ROWS → GetZoneAssignment returns: { "assigned": true, "mapName": "Lvl_Procedural", "zoneName": "Arctic", "seed": 3735552002, "zoneInstanceId": 42 }4. Server → ServerTravel("/Game/Procedural/Lvl_Procedural?listen?seed=3735552002")5. Lvl_Procedural BeginPlay → reads seed → PCG generates Arctic biome6. Server reports ready → players connect to deterministic worldUE5 Integration
Section titled “UE5 Integration”PCG (Procedural Content Generation) Framework:
| Layer | Driven By | Examples |
|---|---|---|
| Terrain heightmap | Seed + biome type | Mountain ranges, valleys, coastlines |
| Biome selection | Seed bits [0:7] | Grassland, Arctic, Desert, Marshland, Tropic |
| Foliage placement | Seed + density rules | Trees, bushes, grass patches |
| Resource nodes | Seed + spawn tables | Ore veins, herb clusters, fishing spots |
| Mob spawns | Seed + difficulty curve | Creature type, patrol routes, density |
| Landmarks | Fixed coordinates | Towns, dungeons, POIs (handcrafted, placed at deterministic positions) |
| Weather | Seed + time-of-day | Rain cycles, fog density, wind patterns |
Hybrid approach:
- Handcrafted landmarks are pre-built actors placed at coordinates derived from the seed
- Procedural fill generates the terrain and natural environment between landmarks
- Biome blending uses seed-derived Voronoi regions for smooth transitions
GameMode seed consumption:
// In Lvl_Procedural's GameMode BeginPlayFString SeedStr = UGameplayStatics::ParseOption(OptionsString, TEXT("seed"));int64 Seed = FCString::Atoi64(*SeedStr);FRandomStream WorldRNG(Seed);
// Feed to PCG subsystemUPCGSubsystem* PCG = GetWorld()->GetSubsystem<UPCGSubsystem>();PCG->SetGlobalSeed(Seed);Zone Travel
Section titled “Zone Travel”Players move between zones via zone portals or world map travel:
Player enters Arctic→Desert portal → Client calls SetSelectedCharacterAndGetUserSession → Client calls GetServerToConnectTo(zoneName: "Desert") → ROWS finds/allocates Desert server (seed: 0xCAFE0003) → Client disconnects from Arctic, connects to Desert → Character position saved on Arctic, loaded on DesertSeed Registry
Section titled “Seed Registry”Seeds can be:
- Static — admin-created zones with curated seeds (main world regions)
- Dynamic — player-triggered instanced content (dungeons, events) with random seeds
- Seasonal — time-limited zones that rotate seeds periodically
GET /api/System/SeedRegistry{ "zones": [ { "zoneName": "Grassland", "seed": 2712847316, "type": "static", "biome": "grassland" }, { "zoneName": "Arctic", "seed": 3735552002, "type": "static", "biome": "arctic" }, { "zoneName": "Dungeon_42", "seed": 8827361024, "type": "dynamic", "biome": "cave", "expires": "2026-04-01T00:00:00Z" } ]}Implementation Phases
Section titled “Implementation Phases”| Phase | Scope | Dependencies |
|---|---|---|
| Phase 1 | Add seed column to maps table, include in GetZoneAssignment response | dbmate migration |
| Phase 2 | Lvl_Procedural map with basic PCG — terrain heightmap from seed | UE5 PCG framework |
| Phase 3 | Biome system — seed bits select biome, PCG generates appropriate flora/fauna | Biome data tables |
| Phase 4 | Handcrafted landmarks — POI actors placed at seed-derived coordinates | Level design |
| Phase 5 | Zone portals — client-side travel between seed-based zones | OWS travel flow |
| Phase 6 | Dynamic instances — player-triggered dungeons with random seeds | Instance lifecycle |
| Phase 7 | Seed registry API — admin tools for zone management | Dashboard integration |