package model import "time" // ExcursionPhase tracks where the hero is within a mini-adventure session. // The lifecycle is: Out → Wild → Return → (back to road, phase cleared). type ExcursionPhase string const ( ExcursionNone ExcursionPhase = "" ExcursionOut ExcursionPhase = "out" // moving off-road into the forest ExcursionWild ExcursionPhase = "wild" // in the wilderness (encounters happen here) ExcursionReturn ExcursionPhase = "return" // returning to the road (encounters still possible) ) // ExcursionKind distinguishes roadside rest vs walking adventure sessions. type ExcursionKind string const ( ExcursionKindNone ExcursionKind = "" ExcursionKindRoadside ExcursionKind = "roadside" ExcursionKindAdventure ExcursionKind = "adventure" ) // ExcursionSession holds the live state of an active mini-adventure (off-road excursion). // When Phase == ExcursionNone the session is inactive and all other fields are zero-valued. type ExcursionSession struct { Kind ExcursionKind Phase ExcursionPhase StartedAt time.Time // OutUntil / WildUntil / ReturnUntil: legacy time-based FSM (ignored when Kind is set). OutUntil time.Time WildUntil time.Time ReturnUntil time.Time // DepthWorldUnits is used to place forest attractors (perpendicular distance from road spine). DepthWorldUnits float64 // RoadFreezeWaypoint / RoadFreezeFraction capture road progress at the moment the hero // left the road, so it can be restored exactly when the excursion ends. RoadFreezeWaypoint int RoadFreezeFraction float64 // Attractor-based movement (Kind != ""): hero walks in world space toward AttractorX/Y. StartX, StartY float64 AttractorX, AttractorY float64 AttractorSet bool // Adventure-only: wall-time when wandering should end (then return to road). AdventureEndsAt time.Time // Adventure: next time to pick a new wander attractor (wild phase). WanderNextAt time.Time // PendingReturnAfterCombat: adventure timer elapsed; wait for combat end then enter return phase. PendingReturnAfterCombat bool } // Active reports whether an excursion session is in progress. func (s *ExcursionSession) Active() bool { return s.Phase != ExcursionNone } // ExcursionPersisted is the JSON-serialisable subset of ExcursionSession stored in the // heroes.town_pause JSONB column so that reconnect / offline catch-up can resume mid-adventure. type ExcursionPersisted struct { Kind string `json:"kind,omitempty"` Phase string `json:"phase,omitempty"` StartedAt *time.Time `json:"startedAt,omitempty"` OutUntil *time.Time `json:"outUntil,omitempty"` WildUntil *time.Time `json:"wildUntil,omitempty"` ReturnUntil *time.Time `json:"returnUntil,omitempty"` DepthWorldUnits float64 `json:"depthWorldUnits,omitempty"` RoadFreezeWaypoint int `json:"roadFreezeWaypoint,omitempty"` RoadFreezeFraction float64 `json:"roadFreezeFraction,omitempty"` StartX float64 `json:"startX,omitempty"` StartY float64 `json:"startY,omitempty"` AttractorX float64 `json:"attractorX,omitempty"` AttractorY float64 `json:"attractorY,omitempty"` AttractorSet bool `json:"attractorSet,omitempty"` AdventureEndsAt *time.Time `json:"adventureEndsAt,omitempty"` WanderNextAt *time.Time `json:"wanderNextAt,omitempty"` PendingReturnAfterCombat bool `json:"pendingReturnAfterCombat,omitempty"` }