# Unified Engine: One Simulation Path for Online and Offline This document describes how AutoHero runs **all** gameplay logic (movement cadence, encounters, combat, rewards) in a **single** place: the Go `Engine` (`backend/internal/game/engine.go`). WebSocket is observation and command input only; there is no separate “offline world” loop that advances combat differently while the player is away. ## Authority | Layer | Role | |--------|------| | **Engine** | `processMovementTick`, `processCombatTick`, `startCombatLocked`, `HeroMovement` FSM, persistence hooks | | **WebSocket Hub** | Delivers envelopes only when the hero has at least one connected client (`SendToHero` no-op otherwise) | | **PostgreSQL** | Durable hero row; periodic and event-driven saves from the engine | | **Offline digest** | Aggregated summary for “while you were away” UI; filled only after disconnect grace (see below) | ## Resident heroes - After the **last** WebSocket disconnect for a hero, `HeroSocketDetached` **does not** remove them from `e.movements` or clear combat. The hero keeps ticking like an online session without a viewer. - In-memory `Hero.WsDisconnectedAt` is set on disconnect (aligned with `heroes.ws_disconnected_at` in the DB) for digest timing. - **Cold start:** `ListHeroesForEngineBootstrap` (`backend/internal/storage/hero_store.go`) selects heroes with `ws_disconnected_at IS NOT NULL` and a simulatable `state`. `BootstrapResidentHeroes` (`backend/internal/game/engine_bootstrap.go`) runs a **one-shot** wall-time catch-up via `OfflineSimulator.SimulateHeroAt`, then registers the hero in the engine. Live play after that uses only engine combat. - **Periodic save without WS:** heroes with no subscriber get a full `heroStore.Save` every `offlineDisconnectedFullSaveInterval` (30s) from the movement tick path (`backend/internal/game/engine.go`). ## Combat and encounters - **Live progression:** encounters call `startCombatLocked`; resolution uses `e.combats` and `processCombatTick` (same for subscribed and unsubscribed heroes). - **Batch-only paths** (no second “live” world): `SimulateOneFight` / `simulateHeroTick` remain for **bootstrap after restart** and for **server-downtime gap** recovery when the hero is **not** resident in the engine (`catchUpOfflineGap` in `backend/internal/handler/game.go`). If `HeroHasActiveMovement`, gap catch-up **skips** `SimulateHeroAt` so combat is not simulated twice. ## REST and engine consistency - `Engine.MergeResidentHeroState` copies the authoritative in-engine hero (after `SyncToHero`) into the handler’s hero struct. - **`GET /api/v1/hero/init`** and **`GET /api/v1/hero`**: if the hero is resident, merge from engine and persist so the client and DB match the single simulation. ## Offline digest - Helpers: `OfflineDigestGrace`, `OfflineDigestCollecting` (`backend/internal/game/offline.go`). - The engine applies digest deltas on kill, death (including DoT death path), and auto-revive **only when** `OfflineDigestCollecting(hero.WsDisconnectedAt, now)` is true. - Batch `simulateHeroTick` uses the same rule when a digest store is wired. ## Key source files | Area | File | |------|------| | Engine loop, combat, movement, digest hooks, auto-revive, disconnected save | `backend/internal/game/engine.go` | | Bootstrap query | `backend/internal/storage/hero_store.go` (`ListHeroesForEngineBootstrap`) | | Bootstrap orchestration | `backend/internal/game/engine_bootstrap.go` | | Batch catch-up + digest helpers | `backend/internal/game/offline.go` | | Hub send if connected | `backend/internal/handler/ws.go` | | Init / GetHero merge; gap catch-up guard | `backend/internal/handler/game.go` | | Wiring, bootstrap before `Engine.Run` | `backend/cmd/server/main.go` | ## Scaling notes - Bootstrap is capped (e.g. 500 heroes in `main`); not every account is loaded into RAM. - Long-term, explicit unload policy (TTL + final save) can reduce residency memory without reintroducing a second gameplay simulator. ## Related docs - [spec-server-authoritative.md](./spec-server-authoritative.md) — WS contract and phases. - [excursion_attractor_fsm.md](./excursion_attractor_fsm.md) — roadside/adventure attractor excursion FSM and persistence. - [blueprint_server_authoritative.md](./blueprint_server_authoritative.md) — historical gap analysis and migration context.