# E7.M1 — QuestStateMachine ## Summary | Field | Value | |--------|--------| | **Module ID** | E7.M1 | | **Epic** | [Epic 7 — Quest / Faction](../epics/epic_07_quest_faction.md) | | **Stage target** | Prototype | | **Status** | **Ready** — Slice 1 backlog [E7M1-prototype-backlog.md](../../plans/E7M1-prototype-backlog.md) **E7M1-01** [NEO-112](https://linear.app/neon-sprawl/issue/NEO-112) → **E7M1-12** [NEO-123](https://linear.app/neon-sprawl/issue/NEO-123) **landed**. Client capstone [`NEO-123` manual QA](../../manual-qa/NEO-123.md); [client README — End-to-end onboarding quest loop (NEO-123)](../../../client/README.md#end-to-end-onboarding-quest-loop-neo-123). Epic 7 Slice 1 client capstone complete. | | **Linear** | Label **`E7.M1`** · [E7M1-prototype-backlog.md](../../plans/E7M1-prototype-backlog.md) | ## Purpose Data-driven quest lifecycle: start, step progression, branching, failure/reset, and completion with durable persistence. Integrates crafting and combat outcomes so guided content ties together gather–craft–combat loops without duplicate rewards. **Scope / party:** [Per-character state by default](quest_scope_and_party.md#persistence-per-character-by-default); optional [party credit and turn-in rules](quest_scope_and_party.md#quest-modes-content-driven) via `QuestDef` and [E8.M1](E8_M1_PartyAndMatchAssembly.md). ## Responsibilities - Load and interpret `QuestDef`; track `QuestStepState` **per character**; synchronize party-credit advances per [quest_scope_and_party.md](quest_scope_and_party.md). - Apply `QuestStateTransition` rules for advance, abandon, fail, and complete. - Invoke or subscribe to [E3.M2](E3_M2_RefinementAndRecipeExecution.md) and [E5.M1](E5_M1_CombatRulesEngine.md) (or their events) for step completion predicates. ## Key contracts | Contract | Role | |----------|------| | `QuestDef` | Steps, prerequisites, objectives, and branching metadata. | | `QuestStepState` | Current step progress and internal flags. | | `QuestStateTransition` | Valid transitions and side effects (before E7.M2 reward routing). | ## Module dependencies - **E3.M2** — RefinementAndRecipeExecution: craft completion, item hand-ins, refine steps. - **E5.M1** — CombatRulesEngine: defeat objectives, encounter-linked steps. ## Dependents (by design) - **E7.M2** — [RewardAndUnlockRouter](E7_M2_RewardAndUnlockRouter.md): idempotent delivery after completion. - **E7.M3** — [FactionReputationLedger](E7_M3_FactionReputationLedger.md): reputation-gated content (pre-production). - **E5.M3** — [EncounterAndRewardTables](E5_M3_EncounterAndRewardTables.md): encounter completion and quest credit. - **E6.M4** — [RewardParityEnforcer](E6_M4_RewardParityEnforcer.md): quest/PvP reward alignment. ## Related implementation slices See Epic 7 **Slice 1 — Quest core and persistence**: 3–5 onboarding quests plus one chain across gather/craft/combat; telemetry `quest_start`, `quest_step_complete`, `quest_complete`, funnel times. ## Linear backlog (decomposed) Full story tables, kickoff defaults, and **`blockedBy` graph:** [E7M1-prototype-backlog.md](../../plans/E7M1-prototype-backlog.md). | Slug | Linear | |------|--------| | E7M1-01 | [NEO-112](https://linear.app/neon-sprawl/issue/NEO-112) | | E7M1-02 | [NEO-113](https://linear.app/neon-sprawl/issue/NEO-113) | | E7M1-03 | [NEO-114](https://linear.app/neon-sprawl/issue/NEO-114) | | E7M1-04 | [NEO-115](https://linear.app/neon-sprawl/issue/NEO-115) | | E7M1-05 | [NEO-116](https://linear.app/neon-sprawl/issue/NEO-116) | | E7M1-06 | [NEO-117](https://linear.app/neon-sprawl/issue/NEO-117) | | E7M1-07 | [NEO-118](https://linear.app/neon-sprawl/issue/NEO-118) | | E7M1-08 | [NEO-119](https://linear.app/neon-sprawl/issue/NEO-119) | | E7M1-09 | [NEO-120](https://linear.app/neon-sprawl/issue/NEO-120) | | E7M1-10 | [NEO-121](https://linear.app/neon-sprawl/issue/NEO-121) | | E7M1-11 | [NEO-122](https://linear.app/neon-sprawl/issue/NEO-122) | | E7M1-12 | [NEO-123](https://linear.app/neon-sprawl/issue/NEO-123) | ## Prototype Slice 1 freeze (E7M1-01) The **first shipped quest spine** is **frozen** for prototype tuning until a deliberate migration issue expands the roster. | `QuestDef.id` | `displayName` | `prerequisiteQuestIds` | Steps (summary) | |---------------|---------------|------------------------|-----------------| | **`prototype_quest_gather_intro`** | Intro: Salvage Run | *(none)* | 1× **`gather_item`**: **`scrap_metal_bulk` ×3** | | **`prototype_quest_refine_intro`** | Intro: Refine Stock | **`prototype_quest_gather_intro`** | 1× **`craft_recipe`**: **`refine_scrap_standard`** ×1 | | **`prototype_quest_combat_intro`** | Intro: Clear the Pocket | *(none)* | 1× **`encounter_complete`**: **`prototype_combat_pocket`** | | **`prototype_quest_operator_chain`** | Operator Chain | **`prototype_quest_gather_intro`**, **`prototype_quest_refine_intro`**, **`prototype_quest_combat_intro`** | 4 steps: gather **`scrap_metal_bulk` ×5** → craft **`refine_scrap_standard`** → craft **`make_field_stim_mk0`** → **`inventory_has_item`** **`contract_handoff_token` ×1** | **CI enforcement (NEO-112):** `scripts/validate_content.py` still enforces the **four Slice 1 quest ids** in the freeze table above (prerequisite graph, objective cross-refs, chain terminal step). **Roster allowlist** expanded to **five** quests under [E7.M3](E7_M3_FactionReputationLedger.md) (`PROTOTYPE_E7M3_QUEST_IDS`, NEO-133) — adds **`prototype_quest_grid_contract`**. Shared gates: objective cross-refs to frozen item/recipe/encounter catalogs; acyclic **`prerequisiteQuestIds`**; operator chain terminal step uses **`contract_handoff_token`** from [E5.M3 encounter loot](E5_M3_EncounterAndRewardTables.md#prototype-slice-3-freeze-e5m3-01). **Server load (NEO-113):** Host fail-fast load of `content/quests/*_quests.json` with the same gates — [server README — Quest catalog](../../../server/README.md#quest-catalog-contentquests-neo-113). **HTTP read model (NEO-115):** **`GET /game/world/quest-definitions`** — `QuestDefinitionsWorldApi` + DTOs in `Game/Quests/` ([NEO-115](../../plans/NEO-115-implementation-plan.md)); [server README — Quest definitions (NEO-115)](../../../server/README.md#quest-definitions-neo-115); Bruno `bruno/neon-sprawl-server/quest-definitions/`. **Player quest state store (NEO-116):** **`IPlayerQuestStateStore`** — in-memory + Postgres (`V008__player_quest_progress.sql`) per-player rows keyed by `(playerId, questId)`; implicit `not_started` when missing ([NEO-116](../../plans/NEO-116-implementation-plan.md)); [server README — Quest progress store (NEO-116)](../../../server/README.md#quest-progress-store-neo-116). **Quest state operations (NEO-117):** **`QuestStateOperations`** — accept, step advance, complete with **`QuestStateReasonCodes`**; prerequisite gate on accept; idempotent complete at operations layer ([NEO-117](../../plans/NEO-117-implementation-plan.md)); [server README — Quest state operations (NEO-117)](../../../server/README.md#quest-state-operations-neo-117). **Quest objective wiring (NEO-118):** **`QuestObjectiveWiring`** — gather/craft/encounter success hooks + **`inventory_has_item`** on accept/step advance/inventory mutations; auto advance/complete via **`QuestStateOperations`** ([NEO-118](../../plans/NEO-118-implementation-plan.md)); [server README — Quest objective wiring (NEO-118)](../../../server/README.md#quest-objective-wiring-neo-118). **Per-player quest progress GET (NEO-119):** **`GET /game/players/{id}/quest-progress`** — `QuestProgressApi` + DTOs; registry + store merge; GET-side **`inventory_has_item`** refresh ([NEO-119](../../plans/NEO-119-implementation-plan.md)); [server README — Per-player quest progress (NEO-119)](../../../server/README.md#per-player-quest-progress-neo-119); Bruno `bruno/neon-sprawl-server/quest-progress/`. **Quest accept POST (NEO-120):** **`POST /game/players/{id}/quests/{questId}/accept`** — `QuestAcceptApi` + DTOs; wires **`QuestStateOperations.TryAccept`**; structured **`accepted`** / **`reasonCode`** / optional **`quest`** row ([NEO-120](../../plans/NEO-120-implementation-plan.md)); [server README — Quest accept POST (NEO-120)](../../../server/README.md#quest-accept-post-neo-120); Bruno accept spine in `bruno/neon-sprawl-server/quest-progress/`. **Quest telemetry hooks (NEO-121):** comment-only **`quest_start`**, **`quest_step_complete`**, **`quest_complete`**, **`quest_accept_denied`** hook sites in **`QuestStateOperations`** ([NEO-121](../../plans/NEO-121-implementation-plan.md)); [server README — Quest telemetry hooks (NEO-121)](../../../server/README.md#quest-telemetry-hooks-neo-121). **Client quest HUD (NEO-122):** **`quest_progress_client.gd`**, **`quest_definitions_client.gd`**, **`QuestProgressLabel`** / **`QuestAcceptFeedbackLabel`**; boot + gather/craft/defeat/accept refresh; **Q** / **Shift+Q** accept bindings ([NEO-122](../../plans/NEO-122-implementation-plan.md), [`NEO-122` manual QA](../../manual-qa/NEO-122.md)); [client README — Quest progress + accept HUD (NEO-122)](../../../client/README.md#quest-progress--accept-hud-neo-122). **Reward policy (Slice 1):** Quest completion updates **`QuestStepState` only** — no duplicate item/XP grants. Gather/craft/encounter paths keep existing payouts; [E7.M2](E7_M2_RewardAndUnlockRouter.md) adds **`QuestRewardBundle`** apply in Slice 2. ## Risks and telemetry - Scripting weight: keep `QuestDef` data-first; templates in decomposition/tooling path per epic. - Double-claim: design for idempotency before E7.M2; lint bundles vs E6.M4 / E3.M5. - Epic 7 Slice 1 catalog events (comment-only until E9.M1): **`quest_start`**, **`quest_step_complete`**, **`quest_complete`**, **`quest_accept_denied`** — anchored in **`QuestStateOperations`** (NEO-121). ## Source anchors - Master plan: [`neon_sprawl_vision.plan.md`](../../../neon_sprawl_vision.plan.md) — Core Epic Map, Epic 7. - [Module dependency register](module_dependency_register.md)