# E7.M1 — Prototype story backlog (QuestStateMachine) Working backlog for **Epic 7 — Slice 1** ([quest core and persistence](../decomposition/epics/epic_07_quest_faction.md#slice-1---quest-core-and-persistence)). Decomposition and contracts: [E7.M1 — QuestStateMachine](../decomposition/modules/E7_M1_QuestStateMachine.md). **Full-stack policy:** Epics deliver **playable game features**. Every **player-visible** story has a **`client`** Linear issue created in **this same decomposition pass** — not an undocumented follow-up. Reference: [E5M3 (paired server+client)](../plans/E5M3-prototype-backlog.md). **Labels (Linear):** every issue **`E7.M1`**; add **`server`** or **`client`** + **`Story`** as listed. **Precursor (do not re-scope):** [E5.M3](../decomposition/modules/E5_M3_EncounterAndRewardTables.md) encounter complete grants **`contract_handoff_token`** + records **`EncounterCompleteEvent`** ([NEO-107](https://linear.app/neon-sprawl/issue/NEO-107)) — prototype quest credit stand-in until [E7.M2](../decomposition/modules/E7_M2_RewardAndUnlockRouter.md) **`QuestRewardBundle`** router lands. **NEO-43** [`MissionRewardSkillXpGrant`](../../server/NeonSprawl.Server/Game/Skills/MissionRewardSkillXpGrant.cs) is the future skill-XP apply helper for E7.M2; E7.M1 tracks quest state only (no duplicate item grants on quest complete in Slice 1). **Upstream (must be landed):** [E3.M1](../decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md) gather on interact ([NEO-63](https://linear.app/neon-sprawl/issue/NEO-63)); [E3.M2](../decomposition/modules/E3_M2_RefinementAndRecipeExecution.md) craft POST ([NEO-70](https://linear.app/neon-sprawl/issue/NEO-70)); [E5.M3](../decomposition/modules/E5_M3_EncounterAndRewardTables.md) encounter wiring + progress GET ([NEO-106](https://linear.app/neon-sprawl/issue/NEO-106), [NEO-108](https://linear.app/neon-sprawl/issue/NEO-108)). [E3S5](../plans/E3S5-client-prototype-backlog.md) client economy HUD ([NEO-72](https://linear.app/neon-sprawl/issue/NEO-72)–[NEO-75](https://linear.app/neon-sprawl/issue/NEO-75)). **Prototype quest spine (frozen in E7M1-01):** **three** solo onboarding quests + **one** multi-step chain quest (vision minimum 3–5 onboarding + one integrated chain). **Solo only** — no party credit ([quest_scope_and_party.md](../decomposition/modules/quest_scope_and_party.md)). **Linear issues (created):** attach `docs/plans/NEO-*-implementation-plan.md` on the story branch when work starts. | Slug | Layer | Linear | |------|-------|--------| | E7M1-01 | server | [NEO-112](https://linear.app/neon-sprawl/issue/NEO-112) | | E7M1-02 | server | [NEO-113](https://linear.app/neon-sprawl/issue/NEO-113) | | E7M1-03 | server | [NEO-114](https://linear.app/neon-sprawl/issue/NEO-114) | | E7M1-04 | server | [NEO-115](https://linear.app/neon-sprawl/issue/NEO-115) | | E7M1-05 | server | [NEO-116](https://linear.app/neon-sprawl/issue/NEO-116) | | E7M1-06 | server | [NEO-117](https://linear.app/neon-sprawl/issue/NEO-117) | | E7M1-07 | server | [NEO-118](https://linear.app/neon-sprawl/issue/NEO-118) | | E7M1-08 | server | [NEO-119](https://linear.app/neon-sprawl/issue/NEO-119) | | E7M1-09 | server | [NEO-120](https://linear.app/neon-sprawl/issue/NEO-120) | | E7M1-10 | server | [NEO-121](https://linear.app/neon-sprawl/issue/NEO-121) | | E7M1-11 | client | [NEO-122](https://linear.app/neon-sprawl/issue/NEO-122) | | E7M1-12 | client | [NEO-123](https://linear.app/neon-sprawl/issue/NEO-123) | **Dependency graph in Linear:** E7M1-02 blocked by E7M1-01. E7M1-03 blocked by E7M1-02. E7M1-04 blocked by E7M1-03. E7M1-05 blocked by E7M1-03. E7M1-06 blocked by E7M1-05. E7M1-07 blocked by E7M1-06. E7M1-08 blocked by E7M1-07. E7M1-09 blocked by E7M1-06 (may parallel E7M1-08). E7M1-10 blocked by E7M1-07. E7M1-11 blocked by E7M1-08 and E7M1-09. E7M1-12 blocked by E7M1-11. **Board order:** estimates **1–12** matching slug order (E7M1-01 = 1 … E7M1-12 = 12). On the Epic 7 project board, sort by **Estimate** (ascending). --- ## Story order (recommended) | Order | Slug | Layer | Depends on | |-------|------|-------|------------| | 1 | **E7M1-01** | server | E3.M1/E3.M2/E5.M3 content ids frozen | | 2 | **E7M1-02** | server | E7M1-01 | | 3 | **E7M1-03** | server | E7M1-02 | | 4 | **E7M1-04** | server | E7M1-03 | | 5 | **E7M1-05** | server | E7M1-03 | | 6 | **E7M1-06** | server | E7M1-05 | | 7 | **E7M1-07** | server | E7M1-06 | | 8 | **E7M1-08** | server | E7M1-07 | | 9 | **E7M1-09** | server | E7M1-06 | | 10 | **E7M1-10** | server | E7M1-07 | | 11 | **E7M1-11** | client | E7M1-08, E7M1-09 | | 12 | **E7M1-12** | client | E7M1-11 | **Downstream (separate modules):** [E7.M2](../decomposition/modules/E7_M2_RewardAndUnlockRouter.md) idempotent **`QuestRewardBundle`** apply + consume **`EncounterCompleteEvent`**; [E7.M3](../decomposition/modules/E7_M3_FactionReputationLedger.md) faction ledger (pre-production); [E8.M1](../decomposition/modules/E8_M1_PartyAndMatchAssembly.md) party-credit quests. --- ## Kickoff decisions (decomposition defaults) | Topic | Decision | Rationale | |-------|----------|-----------| | Quest count | **3** onboarding + **1** multi-step chain (4 total) | Vision prototype minimum; readable manual QA | | Party mode | **Solo only** | [quest_scope_and_party.md](../decomposition/modules/quest_scope_and_party.md); E8.M1 not landed | | Accept flow | **`POST …/quests/{questId}/accept`** | Explicit start; boot does not auto-grant all quests | | Objective advance | **Server hooks** on gather/craft/encounter success | Client displays GET snapshot only ([client_server_authority.md](../decomposition/modules/client_server_authority.md)) | | Objective kinds (v1) | **`gather_item`**, **`craft_recipe`**, **`encounter_complete`**, **`inventory_has_item`** | Covers gather→craft→combat chain without custom scripting VM | | Chain quest | **4 sequential steps** in one `QuestDef` | Single HUD row with step index; epic “integrated chain” | | Prerequisites | **`prerequisiteQuestIds`** enforced on accept | Gather and combat intros parallel (no prereq); refine requires gather intro; chain requires all three intros | | Rewards (Slice 1) | **State-only completion**; no duplicate item/XP grants | Loot stays on gather/craft/encounter paths; E7.M2 owns bundles | | Token handoff | Chain final step **`inventory_has_item`** **`contract_handoff_token`** | Uses E5.M3 encounter loot; no consume mutation in Slice 1 | | Idempotency | **Once per player per quest id** complete | Slice 1 AC: no duped quest completion | | Store | **In-memory** **`IPlayerQuestStateStore`** (+ optional Postgres migration) | Matches encounter prototype stores | | HTTP read | **`GET /game/players/{id}/quest-progress`** | Client HUD without local objective math | | HUD fidelity | Prototype Labels / debug panels (not final art) | NEO-110 / NEO-122 precedent | --- ### E7M1-01 — Prototype QuestDef catalog + schemas + CI **Goal:** Lock content shape and CI validation for **four** frozen quests before server load. **In scope** - `content/schemas/quest-def.schema.json`, `content/schemas/quest-step-def.schema.json`, `content/schemas/quest-objective-def.schema.json`. - `content/quests/prototype_quests.json` — stable quest ids and step/objective graph (see module doc freeze table). - `scripts/validate_content.py`: schema validation, duplicate ids, exact four-quest allowlist, cross-ref **`itemId`** / **`recipeId`** / **`encounterId`** to frozen catalogs, **`prerequisiteQuestIds`** acyclic + known ids, objective kind enum. - Designer note in [E7_M1](../decomposition/modules/E7_M1_QuestStateMachine.md) + `content/README.md`. **Out of scope** - Server loader, runtime engine, HTTP, Godot. **Acceptance criteria** - [x] PR gate validates quest JSON against schema. - [x] Exactly four prototype quest ids; duplicate `id` fails CI. - [x] Every objective reference resolves to a frozen catalog id. - [x] Stable id list documented in module doc freeze box. **Client counterpart:** none (infrastructure-only). --- ### E7M1-02 — Server quest catalog load (fail-fast) **Goal:** Disk → host: startup load of `content/quests/*.json` with CI-parity validation. **In scope** - Loader + catalog types under `server/NeonSprawl.Server/Game/Quests/` (path finalized in plan). - Fail-fast on malformed files, duplicate ids, allowlist mismatch with CI, broken cross-refs (items, recipes, encounters). - Unit tests (AAA) for loader happy path and failure modes. - `server/README.md` section. **Out of scope** - Injectable registry interface (E7M1-03), HTTP projection, runtime state machine. **Acceptance criteria** - [x] Host fails startup on invalid quest JSON (mirror CI rules). - [x] Loader tests cover allowlist + cross-ref failures. **Client counterpart:** none (infrastructure-only). --- ### E7M1-03 — Injectable quest definition registry + DI **Goal:** **`IQuestDefinitionRegistry`** with **`TryGetDefinition`**, **`TryNormalizeKnown`**, and **`GetDefinitionsInIdOrder`**, startup registration from E7M1-02 catalog. **In scope** - Interface + in-memory implementation; extend **`AddQuestDefinitionCatalog`** to register **`IQuestDefinitionRegistry`** after the catalog singleton. - Unit tests: lookup known ids; unknown / null / empty id returns false; normalize trim/case; enumeration order; host DI resolve. - Wire registry in **`AddQuestDefinitionCatalog`** alongside catalog load (catalog remains eager boot surface). **Out of scope** - HTTP, player quest state. **Acceptance criteria** - [x] Registry resolves all four frozen quest ids at runtime. - [x] Unknown quest id fails closed for downstream operations. **Client counterpart:** none (infrastructure-only). --- ### E7M1-04 — GET /game/world/quest-definitions **Goal:** Versioned read-only projection of frozen quest defs for client display names and step summaries. **In scope** - `QuestDefinitionsWorldApi` + DTOs in `Game/Quests/`. - Nested steps/objectives summary (ids, kinds, display copy — no server secrets). - Bruno folder `bruno/neon-sprawl-server/quest-definitions/`. - Integration tests (AAA). **Out of scope** - Per-player progress (E7M1-08). **Acceptance criteria** - [x] GET returns all four prototype quests with stable JSON v1 envelope. - [x] Bruno request documents example response shape. **Client counterpart:** consumed by [NEO-122](https://linear.app/neon-sprawl/issue/NEO-122) for display names (optional cache). --- ### E7M1-05 — Player quest state store + QuestStepState persistence **Goal:** Durable per-player quest rows: status (`not_started` / `active` / `completed`), current step index, per-step objective counters. **In scope** - **`IPlayerQuestStateStore`** + in-memory implementation (registered in quest DI extension). - Optional Postgres migration **`V00X`** when DB configured (mirror encounter/inventory pattern). - **`QuestStepState`** / **`PlayerQuestProgressRow`** types aligned with module contracts. - Unit tests: accept transitions, idempotent complete mark. **Out of scope** - Objective evaluation (E7M1-06), HTTP. **Acceptance criteria** - [x] Store supports read/update per `(playerId, questId)`. - [x] Completed quest cannot regress to active without explicit reset API (none in prototype). **Client counterpart:** none (infrastructure-only). --- ### E7M1-06 — QuestStateOperations (accept, step advance, complete) **Goal:** Server-authoritative quest lifecycle without objective wiring yet. **In scope** - **`QuestStateOperations.TryAccept`**, **`TryAdvanceStep`**, **`TryMarkComplete`** with structured **`reasonCode`** denials (`unknown_quest`, `prerequisite_incomplete`, `already_completed`, `already_active`). - Prerequisite enforcement from **`prerequisiteQuestIds`**. - Idempotent complete: second complete returns success without state change. - Unit tests (AAA) for accept order, prerequisite gate, idempotent complete. **Out of scope** - Gather/craft/encounter hooks (E7M1-07); HTTP (E7M1-08/09). **Acceptance criteria** - [x] Accept fails when prerequisite quest not **completed**. - [x] Complete is idempotent per player+quest. - [x] Reason codes documented in `server/README.md`. **Client counterpart:** [NEO-120](https://linear.app/neon-sprawl/issue/NEO-120) exposes accept POST to Godot. --- ### E7M1-07 — Quest objective wiring (gather, craft, encounter) **Goal:** Advance active quest steps when server-side gather, craft, and encounter events succeed. **In scope** - **`QuestObjectiveWiring`** (or equivalent) invoked from: - **`GatherOperations.TryGather`** success path — **`gather_item`** objectives. - **`CraftOperations.TryCraft`** success path — **`craft_recipe`** objectives. - **`EncounterCompletionOperations.TryCompleteAndGrant`** success path — **`encounter_complete`** objectives. - Inventory snapshot read for **`inventory_has_item`** (post-grant or on polling hook from accept). - Counter accumulation for quantity objectives (e.g. gather 3 scrap). - Auto **`TryAdvanceStep`** / **`TryMarkComplete`** when step objectives satisfied. - Integration tests: accept quest → simulate gather/craft/encounter → progress row advances. **Out of scope** - Client-side objective tracking; item consume on turn-in; E7.M2 reward bundles. **Acceptance criteria** - [x] Active **`prototype_quest_gather_intro`** advances when **`scrap_metal_bulk`** gather succeeds. - [x] Active **`prototype_quest_refine_intro`** advances on **`refine_scrap_standard`** craft success. - [x] Active **`prototype_quest_combat_intro`** advances when **`prototype_combat_pocket`** completes. - [x] Chain quest steps advance in order across mixed objective kinds. **Client counterpart:** progress visible via [NEO-122](https://linear.app/neon-sprawl/issue/NEO-122) GET HUD after mutations. --- ### E7M1-08 — GET /game/players/{id}/quest-progress **Goal:** Per-player quest progress snapshot for client HUD. **In scope** - **`QuestProgressApi`** + DTOs: all known quests with status, current step index, objective progress counters, completion timestamp when applicable. - Known-player gate (mirror encounter-progress / skill-progression patterns). - Bruno `bruno/neon-sprawl-server/quest-progress/`. - Integration tests (AAA): accept → partial progress → complete rows. **Out of scope** - Quest definition catalog (E7M1-04). **Acceptance criteria** - [x] GET returns stable v1 envelope for **`dev-local-1`** player. - [x] Completed quests show **`completed`** with step count at terminal step. **Client counterpart:** [NEO-122](https://linear.app/neon-sprawl/issue/NEO-122) — **`quest_progress_client.gd`** + HUD labels. --- ### E7M1-09 — POST /game/players/{id}/quests/{questId}/accept **Goal:** Player-initiated quest accept from Godot (prototype “quest giver” is HUD button or key binding). **In scope** - **`QuestAcceptApi`**: POST accept; JSON body optional v1 `{}`; response includes updated progress row or deny payload. - Wire **`QuestStateOperations.TryAccept`**. - Bruno + integration tests: happy accept, prerequisite deny, duplicate accept. **Out of scope** - Abandon/reset POST (defer unless capstone QA requires). **Acceptance criteria** - [x] Successful accept transitions row **`not_started` → `active`**. - [x] Deny returns structured **`reasonCode`** without mutation. **Client counterpart:** [NEO-122](https://linear.app/neon-sprawl/issue/NEO-122) — accept binding + feedback label. --- ### E7M1-10 — Quest telemetry hook sites (comment-only) **Goal:** Document and place comment-only hook sites for future E9.M1 catalog events **`quest_start`**, **`quest_step_complete`**, **`quest_complete`**. **In scope** - Hook sites in **`QuestStateOperations`** / **`QuestObjectiveWiring`** at accept, step advance, and complete. - Reserved deny hook **`quest_accept_denied`** (optional comment). - `server/README.md` subsection; module doc note. **Out of scope** - E9.M1 ingest pipeline; runtime telemetry emit. **Acceptance criteria** - [x] Hook sites exist at accept, step complete, quest complete paths. - [x] Epic 7 Slice 1 telemetry vocabulary documented. **Landed (NEO-121):** comment-only **`quest_start`**, **`quest_step_complete`**, **`quest_complete`**, **`quest_accept_denied`** in **`QuestStateOperations`**; [server README — Quest telemetry hooks (NEO-121)](../../server/README.md#quest-telemetry-hooks-neo-121); plan [NEO-121](../../plans/NEO-121-implementation-plan.md). **Client counterpart:** none (infrastructure-only). --- ### E7M1-11 — Client quest progress + accept HUD (Godot) **Goal:** Godot **`GET …/quest-progress`** client + readable quest panel; accept via **`POST …/accept`**. **In scope** - `quest_progress_client.gd`, optional `quest_definitions_client.gd` for display names. - HUD: **`QuestProgressLabel`** (active/completed summary), **`QuestAcceptFeedbackLabel`** (deny/success copy). - Boot hydrate + refresh after gather/craft/cast/encounter events (mirror **`encounter_progress_client.gd`** pattern). - Prototype accept bindings: e.g. keys **Q** / **Shift+Q** for next eligible quest accept (document in manual QA). - GdUnit: HTTP double, parse happy path + deny path. - `client/README.md` section. - `docs/manual-qa/NEO-122.md`. **Out of scope** - Quest journal art, map pins, NPC dialog; final quest picker UI. **Acceptance criteria** - [ ] Player sees quest status change after accept without Bruno. - [ ] Objective progress updates after gather/craft/encounter in Godot session. - [ ] Failed GET/accept surfaces visible HUD error (match interaction client pattern). **Client counterpart:** this story **is** the client counterpart for E7M1-08/09. --- ### E7M1-12 — Playable onboarding quest chain capstone (Godot) **Goal:** Prove Epic 7 Slice 1 acceptance **in Godot**: complete all four prototype quests (gather → refine → combat → operator chain) without Bruno; satisfies vision **quest loop** gate. **In scope** - **`docs/manual-qa/NEO-123.md`**: numbered single-session capstone — fresh server restart, accept onboarding quests in order, perform gather/craft/combat actions, verify chain quest **`prototype_quest_operator_chain`** completes when **`contract_handoff_token`** held. - **`client/README.md`**: **End-to-end onboarding quest loop** section — flow table; cross-links NEO-112–NEO-122. - Module alignment on completion: [E7_M1](../decomposition/modules/E7_M1_QuestStateMachine.md), [documentation_and_implementation_alignment.md](../decomposition/modules/documentation_and_implementation_alignment.md), [module_dependency_register.md](../decomposition/modules/module_dependency_register.md). **Out of scope** - E7.M2 reward bundles; faction reputation; quest VFX; Bruno-only verification as prototype-complete proof. **Acceptance criteria** - [ ] Human completes **`docs/manual-qa/NEO-123.md`** with server + client. - [ ] Epic 7 Slice 1 AC: 3 onboarding + 1 chain complete without duplicate completion on replay. - [ ] Re-read [epic_07 Definition of Done](../decomposition/epics/epic_07_quest_faction.md#definition-of-done) for prototype minimums. **Client counterpart:** this story **is** the Slice 1 client capstone. --- ## Decomposition complete checklist - [x] Every player-visible AC has a **`client`** Linear issue (NEO-122, NEO-123) - [x] No forbidden deferral language in backlog - [x] Capstone client story exists (E7M1-12 / NEO-123) - [x] Epic DoD updated for Godot verification - [x] [documentation_and_implementation_alignment.md](../decomposition/modules/documentation_and_implementation_alignment.md) E7.M1 row updated - [x] [module_dependency_register.md](../decomposition/modules/module_dependency_register.md) E7.M1 note updated ## Related docs - [epic_07_quest_faction.md](../decomposition/epics/epic_07_quest_faction.md) - [quest_scope_and_party.md](../decomposition/modules/quest_scope_and_party.md) - [E5M3-prototype-backlog.md](E5M3-prototype-backlog.md) (encounter token precursor) - [E3S5-client-prototype-backlog.md](E3S5-client-prototype-backlog.md) (gather/craft client HUD) - [neon_sprawl_vision.plan.md — Prototype Scope](../../neon_sprawl_vision.plan.md)