Game Server Startup Phases
The game server starts through a sequence of validated phases. Each phase registers rollback handlers so failures at any point clean up earlier resources in reverse order.
Entry point: app.NewWithAddrContext() in internal/services/game/app/bootstrap.go. The top-level bootstrap now sequences phase helpers; phase-local work lives in bootstrap_sequence.go and sibling bootstrap_*.go collaborators.
Phase overview
flowchart TD
START([NewWithAddrContext]) --> P1
P1["1. Registries\ncommand/event/system types"]
P1 -->|fatal| P2
P2["2. Network\nTCP listener"]
P2 -->|fatal| P3
P3["3. Storage\nevents + projections + content DBs"]
P3 -->|fatal| P4
P4["4. Domain\nstores, applier, write runtime"]
P4 -->|fatal| P5
P5["5. Systems\nparity check, gap repair, policy"]
P5 -->|fatal| P6
P6["6. Dependencies\nAuth, Social, AI, Status"]
P6 -->|"Auth fatal\nothers graceful"| P7
P7["7. Transport\ngRPC service registration"]
P7 -->|fatal| P8
P8["8. Runtime\nprojection mode, workers, status"]
P8 --> SERVE([Server.Serve])
P1 & P2 & P3 & P4 & P5 & P6 & P7 & P8 -.->|"on failure"| RB["LIFO Rollback\nclean up in reverse order"]
| # | Phase | File | Failure behavior |
|---|---|---|---|
| 1 | Registries | bootstrap.go | Fatal — command/event types misconfigured |
| 2 | Network | bootstrap.go | Fatal — port unavailable |
| 3 | Storage | server_bootstrap.go | Fatal — database open/migration failure |
| 4 | Domain | bootstrap.go | Fatal — store wiring or applier validation failure |
| 5 | Systems | bootstrap.go, system_registration.go | Fatal — module/adapter/metadata parity violation |
| 6 | Dependencies | server_bootstrap.go | Auth fatal; Social/AI/Status graceful |
| 7 | Transport | bootstrap_service_registration.go | Fatal — gRPC service registration failure |
| 8 | Runtime | bootstrap.go | Fatal — projection mode or worker configuration error |
Phase details
1. Registries
engine.BuildRegistries(registeredSystemModules()...)
Initializes command, event, and system registries from declared game system modules. registeredSystemModules() is manifest-derived, so built-in system registration enters startup through the same SystemDescriptor list that also feeds metadata and adapters. The phase validates that command types map to handlers and event types map to fold functions. Output drives all downstream wiring.
2. Network
net.Listen("tcp", addr)
Opens the gRPC listener. Registered for rollback cleanup. The listener stays open until the serve loop starts.
3. Storage
openStorageBundle() in server_bootstrap.go
Opens three SQLite databases:
- Events (
FRACTURING_SPACE_GAME_EVENTS_DB_PATH) — event journal with integrity keyring and chain verification - Projections (
FRACTURING_SPACE_GAME_PROJECTIONS_DB_PATH) — materialized read models - Content (
FRACTURING_SPACE_GAME_CONTENT_DB_PATH) — Daggerheart content metadata
Each database runs migrations on open and is registered for rollback cleanup.
4. Domain
Builds the root transport concern groups and the explicit projection Applier as separate collaborators:
- Create
WriteRuntimefor in-flight write tracking - Build root projection, infrastructure, content, and runtime concerns from the core projection database plus the explicit built-in system store bundle bound from that backend
- Attach event registry
- Configure domain execution layer Domain bootstrap mutates only the infrastructure/runtime concern groups: the event journal feeds engine construction and the runtime write path gets the executor.
- Validate all service stores are wired
- Build and validate the root
projection.Applierfrom explicit applier deps Root store and applier construction now flow through exact app-owned source structs in dedicated phase-4 helper builders rather than inline config literals insideconfigureStoresAndApplier. Startup now calls the projection-owned applier constructor directly instead of rebuilding that projection surface in the root gRPC package. Startup also binds an explicit audit policy for the applier instead of relying on nil-store no-op inference. The phase returns a narrowed domain state of concern groups plus applier instead of carrying a root omnibus container forward into later startup phases.
5. Systems
Three substeps:
- System metadata registry — loads game system metadata (Daggerheart)
- Parity validation — ensures module registries, adapter registries, and metadata registries all agree on the systems declared in the manifest
- Projection gap repair — detects campaigns with stale projections and replays missing events
- Session lock policy validation — ensures transport interceptor and domain policy agree on blocked commands
6. Dependencies
Connects to external microservices:
- Auth (required) —
FRACTURING_SPACE_AUTH_ADDR - Social (graceful) —
FRACTURING_SPACE_SOCIAL_ADDR— logs warning if unavailable - AI (graceful) —
FRACTURING_SPACE_AI_ADDR— logs warning if unavailable - Status (advisory) —
FRACTURING_SPACE_STATUS_ADDR— accumulates locally if unavailable - AI session grant config — loaded from environment
After dialing, startup attaches only the social client into the root ContentStores concern group; the dependency phase no longer mutates a broad root service container.
7. Transport
registerServices() in bootstrap_service_registration.go
Builds and mounts gRPC service descriptors:
- 3 Daggerheart services (core, content, assets)
- 13 game core services (Campaign, Participant, Character, Session, etc.)
- Health service with per-service status
The root registration file now delegates constructor wiring to capability-local helpers in bootstrap_service_builders.go so campaign, session, Daggerheart, and infrastructure service families stay readable and reviewable in isolation. Startup now assembles explicit Daggerheart, campaign, session, and infrastructure registration deps directly before transport bootstrap from the root projection, infrastructure, content, runtime, and system concern groups. No transport-wide registration bundle remains between startup and service registration, and the campaign/session family builders now consume exact app-owned source structs rather than the full root store container or concern-group bundles. Infrastructure and Daggerheart registration now follow the same exact-source pattern, and the top-level bootstrap delegates the remaining registration source fan-out to a dedicated startup-owned assembly helper with one exact startup-owned source input.
8. Runtime
Configures background workers and status reporting:
- Projection apply mode — resolves one of
inline_apply_only,outbox_apply_only, orshadow_only - Runtime store seam — projection runtime policy mutates only the root
RuntimeStoresconcern group rather than a broad root service container - Outbox worker — processes queued projection events and owns per-campaign projection scheduling when apply mode is enabled
- Status reporter — heartbeat and catalog availability monitoring
Serve loop
After bootstrap, Server.Serve(ctx) starts:
- Background workers (projection, status, catalog monitor)
- gRPC server on the listener
- Shutdown on context cancellation — workers stop, gRPC graceful stop, resources closed
Rollback on failure
All phases register cleanup in a LIFO stack:
- Listener close
- Storage bundle close
- Auth connection close
- Social connection close
- AI connection close
- Status connection close
On error in any phase, cleanup runs in reverse order.