Session Model — Field Context vs Quest State

The key words MUST, MUST NOT, MAY, and REQUIRED below are used as in RFC 2119. This page defines the target contract; the current divergence section records where the code conflates the two layers today.

A field run involves two independent concerns that are easy to entangle. The session model keeps them separate:

The two layers

Crucially, presence or absence of Quest State is the only thing that distinguishes a quest run from a free field. There MUST NOT be a separate "type": "field" | "quest" discriminator on the field context — a free field is simply a Field Context with no Quest State.

Field Context lifecycle

The city-hub position cache (CityState)

Field Context holds the player's position in a field. There is a second, separate in-memory holder of player position for the city hub: CityState (the _position / _rotation / _area / _spawn_key autoload). It is written by city_area_base.gd every time the player walks between city areas or up to a shop NPC, so a push/pop back into the same area restores where they were standing. It is NOT part of SessionManager._session.

This reconciles — it does not contradict — the lifecycle rule above that “a title return clears the Field Context (incl. the player's position).” That claim only covered the SessionManager-owned position; this section closes the gap that the city-hub position lived in a separate autoload the teardown never touched. The start-menu “Return to Title” action (Start Menu) delegates its teardown to this title-scene reset; it does not clear CityState itself.

Quest State lifecycle (independent)

The telepipe is a movement primitive

A telepipe is a two-endpoint warp — like an area_warp — and it binds to the Field Context, not to objectives or rewards:

Quest lifecycle & the telepipe (settled)

The boundary is completion vs. exit:

Worked example: accept Search and Rescue → enter the valley → meet the objectives. The quest is now complete, but the player keeps exploring and round-tripping through the pipe. Only when they report at the guild (or cancel) does the pipe — and the Field Context — close.

Why this matters

Tying the telepipe and the free-field run to Quest State is the root cause of a cluster of bugs: the field is procedurally built from the quest's sections, so “return to the field” needs that layout — and historically the layout only lived inside a single quest-flavoured session dict that quest completion cleared. Keeping the Field Context separate and alive until the player leaves removes that coupling entirely.

Implemented by

Divergence history (mostly resolved)

The original single-dict design conflated the two layers and is what these issues describe:

Tracked in the architecture issue, with the crash detail in #378 and the free-field/quest confusion in #359 / #360.

Resolved: Field Context now survives city round-trips

The completion-vs-exit split (#384) keeps a quest's Field Context alive through objective completion and city round-trips, tearing it down only at the quest's exit. Free fields are now decoupled from the single suspended-session slot entirely: each unlocked field keeps its own run-state in a per-area Free-Roam store (SessionManager._free_roam_state), in memory, retained across city returns and switching between free fields, and reset only on quest accept, the quest's exit (report/cancel), and title return. This is the normative behavior in Quest vs Field (Free-Roam rules). The leftover divergence is cosmetic — _session still carries a "type" field — but a free field's context no longer rides in _suspended_session (which would evict one field's progress when another was entered).