neon-sprawl/docs/decomposition/modules/E7_M1_QuestStateMachine.md

111 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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 gathercraftcombat 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**: 35 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` requires **exactly** these four quest ids; objective cross-refs to frozen item/recipe/encounter catalogs; acyclic **`prerequisiteQuestIds`**; chain quest 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)