# 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.
