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.
5.2 KiB
5.2 KiB
Excursion FSM: attractor movement (roadside + adventure)
This document describes the server-authoritative mini-excursion flow: the hero moves in world coordinates toward successive attractors instead of using a time-based perpendicular offset from the road spine.
Terminology
| Name | ExcursionPhase |
Meaning |
|---|---|---|
| First exit into forest | out |
Walk from the frozen road point to the first forest attractor. |
| Wilderness | wild |
Heal / wander / encounters (depends on ExcursionKind). |
| Return leg | return |
Walk back to the road (adventure) or to saved StartX/Y (roadside). |
Product language sometimes calls the return leg “out”; in code it is always return.
Session kinds (ExcursionKind)
| Kind | Trigger | HeroMovement state |
|---|---|---|
roadside |
Low HP on road → beginRoadsideRest |
StateResting, RestKindRoadside |
adventure |
Random roll while walking → beginExcursion |
StateWalking with active Excursion |
town |
In-town tour (separate sub-FSM) | StateInTown |
Roadside and adventure share attractor stepping helpers; town uses its own tour phases (TownTourPhase in model/excursion.go).
Kinematics
CurrentX/CurrentYare the true world position duringout/wild/return.- Movement uses
stepTowardAttractor(excursion speed fromrefreshSpeed) orstepTowardWorldPointfor town NPC/center walks, with arrival epsilonExcursionArrivalEpsilonWorld(tuning). - For attractor-based excursions,
displayOffsetis zero;hero.position/hero_movematch world coords. - Legacy JSON blobs without
excursion.kindbut with a non-empty phase are cleared on load (applyExcursionFromBlob) so old offset-only sessions are not resumed.
Roadside (roadside)
- Start:
StartX/Y= road position; road progress frozen (RoadFreezeWaypoint/RoadFreezeFraction); first forest attractor frompickExcursionForestAttractor(depth from tuning). out: Step toward attractor until within epsilon →wild.wild: RegenRoadsideRestHpPerS; cap by random duration[RoadsideRestMinMs, RoadsideRestMaxMs]or early exit whenHP/MaxHP ≥ RoadsideRestExitHp(default 0.85).return: Attractor =StartX/Y; on arrival →endExcursion, restore road progress, clear rest.
Persisted under heroes.town_pause → excursion (ExcursionPersisted).
Adventure (adventure)
- Start:
StartX/Yon road;AdventureEndsAt = now + uniform[AdventureDurationMinMs, AdventureDurationMaxMs]; firstoutattractor like roadside (depthAdventureDepthWorldUnits). out: Reach attractor →wild, schedule wander (WanderNextAt,adventurePickWanderAttractorwithinAdventureWanderRadius).wild: Whilenow < AdventureEndsAt: step toward current attractor; retarget onWanderNextAt; roll encounters; ifHP/MaxHP < LowHpThreshold→beginAdventureInlineRestuntil≥ AdventureRestTargetHp(default 0.85), then back towild.- Timer elapsed:
tryBeginAdventureReturn: if fighting, setPendingReturnAfterCombat; elseenterAdventureReturnToRoad(attractor = closest point on frozen road polyline). - After combat win:
ResumeWalkingthenTryAdventureReturnAfterCombat(now)— also handles timer elapsed while movement ticks were skipped during combat (checksAdventureEndsAt, not only the pending flag). return: On arrival at road attractor →endExcursion,excursion_endWS when applicable.
Persistence
TownPausePersisted.Excursionholds kind, phase, freeze snapshot,startX/Y, attractor,adventureEndsAt,wanderNextAt,pendingReturnAfterCombat, etc.- Engine persists when
TownPausePersistDuesignature changes (seetownPausePersistSignatureinmovement.go).
WebSocket (online)
Typical messages: excursion_start, excursion_phase, hero_move, hero_state, excursion_end. After admin “force return” on adventure, engine sends excursion_phase + hero_move (not immediate excursion_end).
Tuning keys (reference)
| Key | Role |
|---|---|
AdventureDurationMinMs / MaxMs |
Adventure wild window |
AdventureWanderRadius |
Random retarget radius around hero |
AdventureWanderRetargetMinMs / MaxMs |
Retarget interval |
ExcursionArrivalEpsilonWorld |
Arrival threshold (shared with town step-to-point) |
RoadsideRestExitHp |
Early end of roadside wild |
AdventureRestTargetHp |
End of adventure inline heal |
Client
excursionPhaseandexcursionKind(roadside|adventure) on hero JSON; visuals followhero_moveworld coordinates.
Primary source files
| Area | File |
|---|---|
| Session model + persist DTO | backend/internal/model/excursion.go |
| FSM, stepping, persist blob | backend/internal/game/movement.go |
| Post-combat return | backend/internal/game/engine.go, offline.go |
| Defaults | backend/internal/tuning/runtime.go |
Related docs
- engine_unified_offline_online.md — single simulation path.
- spec-server-authoritative.md — WS envelopes and authority boundaries.