Architecture

Monorepo structure, backend layers, frontend patterns, and infrastructure.

Repository Layout

DirectoryPurpose
backend/Star.ApiASP.NET 10 REST API
frontend/React 19 + Vite game client
ships/Three.js ship blueprint visualizer (standalone)
planets/Node.js + Puppeteer planet video pipeline
docs/This documentation site (Nuxt + shadcn-docs-nuxt)
protos/Protobuf definitions (star/v1/*.proto)
infra/docker/Docker Compose for local Postgres and docs

All tasks are run via mise: mise run <task>.


Backend

Stack: ASP.NET 10 Minimal APIs · EF Core 10 + Npgsql · FluentResults · FluentValidation · Clerk JWT auth

Layer Structure

Endpoints/           Minimal API handlers — one file per domain
Repositories/        IReader<T> + IWriter<T> per domain
Services/Economy/    EconomicTickService + pure static Calculators
Services/Buildings/  BuildingService + BuildingDefinitions + BuildingCalculator
Services/Production/ ShipProductionService
Services/Movement/   FleetMovementService
Services/Trade/      TradeRouteService
Services/Espionage/  EspionageService
Entities/            EF Core entity classes (snake_case columns)
Data/                AppDbContext with seed data (Frames, Modules)
Configuration/       MainConfiguration, GameConfiguration (tick interval)

API Response Envelope

ApiResponse<T> {
  content: T | null
  errors: [{ message }]
}

All endpoints return this envelope. FluentResults is used internally and converted at the endpoint boundary.

Auth

Clerk JWT by default. Set DISABLE_AUTH=true to use DemoAuthHandler (injects an anonymous user) for local dev.

Configuration

Loaded from .env (or env.demo for APP_ENV=demo) with .env.local overlay.

VariablePurpose
DB_CONNECTIONPostgres connection string
CORS_ORIGINSComma-separated allowed origins
CLERK_ISSUERClerk JWT issuer URL
DISABLE_AUTHSkip auth (local dev)
API_VERSIONShown in Scalar API docs
TICK_INTERVAL_SECONDSEconomy tick interval in seconds (default: 300)

Frontend

Stack: React 19 · Vite 7 · TailwindCSS v4 · Zustand · TanStack Query · Clerk · React Router v7

Key Patterns

  • src/api/ — typed apiFetch wrappers per domain, each accepting getToken callback
  • src/stores/gameStore.ts — Zustand for cross-page state (selectedPlanetId, lastTickResults)
  • src/pages/ — route-level components, data fetching via TanStack Query
  • VITE_DISABLE_AUTH=true bypasses Clerk (must match backend DISABLE_AUTH=true)

Pages

RouteComponentPurpose
/planetsPlanetsPageList all planets (owned first)
/planets/:idPlanetDetailPageStockpiles, buildings, production queue, trade routes, economy panel
/hangarHangarPageBlueprint list and designer
/fleetsFleetsPageFleet list, ship stacks, dispatch modal
/mapGamePageSVG galaxy map with fleet routes and real-time tween
/gameGamePageAdmin panel — resource injection, forced tick

Ships Visualizer

A self-contained TypeScript + Three.js app at ships/ with no framework. Runs on port 5174.

Key files:

  • shipBuilder.ts — assembles 3D hull from frame profile + module placements
  • renderer.ts — Three.js scene with blueprint grid and OrbitControls
  • ui.ts — DOM-based slot assignment UI
  • frames.ts / modules.ts — static definitions matching backend seed data

Infrastructure

Local Dev

mise run infra        # Start Postgres on port 5434 (+ docs on 4000)
mise run dev          # Backend + frontend in parallel

Docker Compose Services

ServicePortPurpose
db5434Postgres 17
docs4000This documentation site

Migrations

mise run migrations:add -- <Name>   # Scaffold new migration
mise run migrations:update          # Apply pending migrations
mise run migrations:revert          # Revert last migration

Determinism Requirements

All game simulation must be:

  • Pure function based — same inputs → same outputs, always
  • Replayable — any game state can be replayed from event log
  • Hash verifiable — combat results signed with sha256(inputs + engine_version)

This applies to the economy calculators and especially to the combat engine. No Random, no DateTime.Now inside simulation logic.