You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4.3 KiB
4.3 KiB
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,
HeroSocketDetacheddoes not remove them frome.movementsor clear combat. The hero keeps ticking like an online session without a viewer. - In-memory
Hero.WsDisconnectedAtis set on disconnect (aligned withheroes.ws_disconnected_atin the DB) for digest timing. - Cold start:
ListHeroesForEngineBootstrap(backend/internal/storage/hero_store.go) selects heroes withws_disconnected_at IS NOT NULLand a simulatablestate.BootstrapResidentHeroes(backend/internal/game/engine_bootstrap.go) runs a one-shot wall-time catch-up viaOfflineSimulator.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.SaveeveryofflineDisconnectedFullSaveInterval(30s) from the movement tick path (backend/internal/game/engine.go).
Combat and encounters
- Live progression: encounters call
startCombatLocked; resolution usese.combatsandprocessCombatTick(same for subscribed and unsubscribed heroes). - Batch-only paths (no second “live” world):
SimulateOneFight/simulateHeroTickremain for bootstrap after restart and for server-downtime gap recovery when the hero is not resident in the engine (catchUpOfflineGapinbackend/internal/handler/game.go). IfHeroHasActiveMovement, gap catch-up skipsSimulateHeroAtso combat is not simulated twice.
REST and engine consistency
Engine.MergeResidentHeroStatecopies the authoritative in-engine hero (afterSyncToHero) into the handler’s hero struct.GET /api/v1/hero/initandGET /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
simulateHeroTickuses 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 — WS contract and phases.
- excursion_attractor_fsm.md — roadside/adventure attractor excursion FSM and persistence.
- blueprint_server_authoritative.md — historical gap analysis and migration context.