---
name: Offline simulator architecture
description: Background OfflineSimulator in game/offline.go runs real server-side combat ticks for offline heroes; replaced fake retroactive simulateOffline
type: project
---

Real offline simulation system added 2026-03-27. Key decisions:

- `game.OfflineSimulator` ticks every 30s, queries heroes with `state='walking'` and `updated_at < now - 1min`
- `game.SimulateOneFight` is the canonical single-fight function used by both simulator and catch-up
- `game.PickEnemyForLevel`, `game.ScaleEnemyTemplate`, `game.AutoEquipWeapon`, `game.AutoEquipArmor` were extracted from handler to game package (exported)
- handler's `pickEnemyForLevel`/`autoEquipWeapon`/`autoEquipArmor` now delegate to game package
- `catchUpOfflineGap` in handler covers server-down gap (hero.UpdatedAt to serverStartedAt)
- `buildOfflineReport` reads real adventure_log entries instead of generating fake ones
- `serverStartedAt` passed from main.go -> router.Deps -> GameHandler

**Why:** Old simulateOffline generated fake events retroactively at login. New system writes real events to adventure_log as they happen.

**How to apply:** When touching offline/idle combat, work in game/offline.go. The handler no longer owns combat simulation logic for offline heroes.
