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.
autohero/docs/excursion_attractor_fsm.md

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 / CurrentY are the true world position during out / wild / return.
  • Movement uses stepTowardAttractor (excursion speed from refreshSpeed) or stepTowardWorldPoint for town NPC/center walks, with arrival epsilon ExcursionArrivalEpsilonWorld (tuning).
  • For attractor-based excursions, displayOffset is zero; hero.position / hero_move match world coords.
  • Legacy JSON blobs without excursion.kind but with a non-empty phase are cleared on load (applyExcursionFromBlob) so old offset-only sessions are not resumed.

Roadside (roadside)

  1. Start: StartX/Y = road position; road progress frozen (RoadFreezeWaypoint / RoadFreezeFraction); first forest attractor from pickExcursionForestAttractor (depth from tuning).
  2. out: Step toward attractor until within epsilon → wild.
  3. wild: Regen RoadsideRestHpPerS; cap by random duration [RoadsideRestMinMs, RoadsideRestMaxMs] or early exit when HP/MaxHP ≥ RoadsideRestExitHp (default 0.85).
  4. return: Attractor = StartX/Y; on arrival → endExcursion, restore road progress, clear rest.

Persisted under heroes.town_pauseexcursion (ExcursionPersisted).

Adventure (adventure)

  1. Start: StartX/Y on road; AdventureEndsAt = now + uniform[AdventureDurationMinMs, AdventureDurationMaxMs]; first out attractor like roadside (depth AdventureDepthWorldUnits).
  2. out: Reach attractor → wild, schedule wander (WanderNextAt, adventurePickWanderAttractor within AdventureWanderRadius).
  3. wild: While now < AdventureEndsAt: step toward current attractor; retarget on WanderNextAt; roll encounters; if HP/MaxHP < LowHpThresholdbeginAdventureInlineRest until ≥ AdventureRestTargetHp (default 0.85), then back to wild.
  4. Timer elapsed: tryBeginAdventureReturn: if fighting, set PendingReturnAfterCombat; else enterAdventureReturnToRoad (attractor = closest point on frozen road polyline).
  5. After combat win: ResumeWalking then TryAdventureReturnAfterCombat(now) — also handles timer elapsed while movement ticks were skipped during combat (checks AdventureEndsAt, not only the pending flag).
  6. return: On arrival at road attractor → endExcursion, excursion_end WS when applicable.

Persistence

  • TownPausePersisted.Excursion holds kind, phase, freeze snapshot, startX/Y, attractor, adventureEndsAt, wanderNextAt, pendingReturnAfterCombat, etc.
  • Engine persists when TownPausePersistDue signature changes (see townPausePersistSignature in movement.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

  • excursionPhase and excursionKind (roadside | adventure) on hero JSON; visuals follow hero_move world 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