# E5.M3 — Prototype story backlog (EncounterAndRewardTables) Working backlog for **Epic 5 — Slice 3** ([Epic 5 · Slice 3 — encounters and loot routing](../decomposition/epics/epic_05_pve_combat.md#epic-5-slice-3)). Decomposition and contracts: [E5.M3 — EncounterAndRewardTables](../decomposition/modules/E5_M3_EncounterAndRewardTables.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: [E5M2 (paired server+client)](../plans/E5M2-prototype-backlog.md). **Labels (Linear):** every issue **`E5.M3`**; add **`server`** or **`client`** + **`Story`** as listed. **Precursor (do not re-scope):** [E5.M2](../decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md) NPC combat pocket — three NPC instance ids (**`prototype_npc_melee`**, **`prototype_npc_ranged`**, **`prototype_npc_elite`**) with defeat via **`CombatOperations`** ([NEO-82](https://linear.app/neon-sprawl/issue/NEO-82)). Per-defeat **gig XP** stays on [NEO-44](https://linear.app/neon-sprawl/issue/NEO-44) — encounter complete adds **loot + quest-credit stand-in**, not a second gig XP bundle. **Upstream (must be landed):** [E5.M2](../decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md) **Ready**; [E3.M3](../decomposition/modules/E3_M3_ItemizationAndInventorySchema.md) inventory **`PlayerInventoryOperations`** ([NEO-54](https://linear.app/neon-sprawl/issue/NEO-54)–[NEO-55](https://linear.app/neon-sprawl/issue/NEO-55)). **E7.M2** full **`QuestRewardBundle`** router is **not** required — prototype uses **`contract_handoff_token`** loot + **`EncounterCompleteEvent`** hook for future quest credit ([NEO-43](https://linear.app/neon-sprawl/issue/NEO-43) pattern). **Prototype encounter spine (frozen in E5M3-01):** **one** `EncounterDef` — id **`prototype_combat_pocket`** — completion **`defeat_all_targets`** over the three E5.M2 NPC instance ids. **One** `RewardTable` — id **`prototype_combat_pocket_clear`** — deterministic fixed item grants (no RNG). **Linear issues (created):** attach `docs/plans/NEO-*-implementation-plan.md` on the story branch when work starts. | Slug | Layer | Linear | |------|-------|--------| | E5M3-01 | server | [NEO-100](https://linear.app/neon-sprawl/issue/NEO-100) | | E5M3-02 | server | [NEO-101](https://linear.app/neon-sprawl/issue/NEO-101) | | E5M3-03 | server | [NEO-102](https://linear.app/neon-sprawl/issue/NEO-102) | | E5M3-04 | server | [NEO-103](https://linear.app/neon-sprawl/issue/NEO-103) | | E5M3-05 | server | [NEO-104](https://linear.app/neon-sprawl/issue/NEO-104) | | E5M3-06 | server | [NEO-105](https://linear.app/neon-sprawl/issue/NEO-105) | | E5M3-07 | server | [NEO-106](https://linear.app/neon-sprawl/issue/NEO-106) | | E5M3-08 | server | [NEO-108](https://linear.app/neon-sprawl/issue/NEO-108) | | E5M3-09 | server | [NEO-107](https://linear.app/neon-sprawl/issue/NEO-107) | | E5M3-10 | server | [NEO-109](https://linear.app/neon-sprawl/issue/NEO-109) | | E5M3-11 | client | [NEO-110](https://linear.app/neon-sprawl/issue/NEO-110) | | E5M3-12 | client | [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111) | **Dependency graph in Linear:** E5M3-02 blocked by E5M3-01. E5M3-03 blocked by E5M3-02. E5M3-04 blocked by E5M3-03. E5M3-05 blocked by E5M3-04. E5M3-06 blocked by E5M3-05. E5M3-07 blocked by E5M3-06. E5M3-08 blocked by E5M3-07. E5M3-09 blocked by E5M3-06 (may parallel E5M3-08). E5M3-10 blocked by E5M3-07. E5M3-11 blocked by E5M3-08 and E5M3-09. E5M3-12 blocked by E5M3-11. *(Linear ids: E5M3-08 = **NEO-108**, E5M3-09 = **NEO-107** — creation order swap.)* **Board order:** estimates **1–12** matching slug order (E5M3-01 = 1 … E5M3-12 = 12). On the Epic 5 project board, sort by **Estimate** (ascending). --- ## Story order (recommended) | Order | Slug | Layer | Depends on | |-------|------|-------|------------| | 1 | **E5M3-01** | server | E5.M2 Ready, E3.M3 inventory landed | | 2 | **E5M3-02** | server | E5M3-01 | | 3 | **E5M3-03** | server | E5M3-02 | | 4 | **E5M3-04** | server | E5M3-03 | | 5 | **E5M3-05** | server | E5M3-04 | | 6 | **E5M3-06** | server | E5M3-05 | | 7 | **E5M3-07** | server | E5M3-06 | | 8 | **E5M3-08** | server | E5M3-07 | | 9 | **E5M3-09** | server | E5M3-06 | | 10 | **E5M3-10** | server | E5M3-07 | | 11 | **E5M3-11** | client | E5M3-08, E5M3-09 | | 12 | **E5M3-12** | client | E5M3-11 | **Downstream (separate modules):** [E5.M4](../decomposition/modules/E5_M4_GroupCombatScaling.md) group scaling; [E7.M1](../decomposition/modules/E7_M1_QuestStateMachine.md) / [E7.M2](../decomposition/modules/E7_M2_RewardAndUnlockRouter.md) consume **`EncounterCompleteEvent`**; [E4.M2](../decomposition/modules/E4_M2_SpawnEcologyController.md) spawn ecology. --- ## Kickoff decisions (decomposition defaults) | Topic | Decision | Rationale | |-------|----------|-----------| | Encounter count | **1** frozen `EncounterDef` | Vision prototype minimum; epic Slice 3 | | Completion rule | **`defeat_all_targets`** over three E5.M2 NPC ids | Reuses static registry; order-independent | | Reward table | **Deterministic fixed grants** (no RNG rolls) | Readable tests; RNG is pre-production | | Frozen loot | **`scrap_metal_bulk` ×10**, **`contract_handoff_token` ×1** | Material + quest-token stand-in for E7.M2 credit | | Idempotency | **Once per player per `encounterId`** | Slice 3 AC: grants expected payout **once** | | Gig XP | **Keep [NEO-44](https://linear.app/neon-sprawl/issue/NEO-44) per-defeat** | Combat → gig XP is separate from encounter loot bundle | | Quest credit | **`contract_handoff_token`** item + **`EncounterCompleteEvent`** record | Full E7.M2 router deferred; visible in inventory HUD | | Encounter start | **Auto** on first damaging cast vs any required NPC | Activates progress tracking; **`encounter_start`** hook site | | Progress store | **In-memory** **`IEncounterProgressStore`** + **`IEncounterCompletionStore`** | Session prototype; Postgres optional follow-on | | HTTP read | **`GET /game/players/{id}/encounter-progress`** | Client HUD without trusting local defeat math | | HUD fidelity | Prototype Labels / debug panels (not final art) | NEO-110 precedent | --- ### E5M3-01 — Prototype EncounterDef + RewardTable catalog + schemas + CI **Goal:** Lock content shape and CI validation for **one** frozen encounter + **one** reward table before server load. **In scope** - `content/schemas/encounter-def.schema.json`, `content/schemas/reward-table.schema.json`, `content/schemas/reward-grant-row.schema.json`. - `content/encounters/prototype_encounters.json` — stable **`id`**: **`prototype_combat_pocket`**; **`completionCriteria.kind`**: **`defeat_all_targets`**; **`requiredNpcInstanceIds`**: three E5.M2 NPC ids; **`rewardTableId`**: **`prototype_combat_pocket_clear`**. - `content/reward-tables/prototype_reward_tables.json` — stable **`id`**: **`prototype_combat_pocket_clear`**; **`fixedGrants`**: **`scrap_metal_bulk` ×10**, **`contract_handoff_token` ×1**. - `scripts/validate_content.py`: schema validation, duplicate ids, exact one-encounter / one-table allowlist, cross-ref **`itemId`** → frozen item catalog, **`requiredNpcInstanceIds`** → frozen NPC instance ids, **`rewardTableId`** → reward table ids. - Designer note in [E5_M3](../decomposition/modules/E5_M3_EncounterAndRewardTables.md) + `content/README.md`. **Out of scope** - Server loader, runtime engine, HTTP, Godot. **Acceptance criteria** - [x] PR gate validates encounter + reward-table JSON against schema. - [x] Exactly one prototype encounter id and one reward table id; duplicate `id` fails CI. - [x] Stable id list documented in module doc freeze box. **Landed ([NEO-100](https://linear.app/neon-sprawl/issue/NEO-100)):** encounter + reward-table schemas, `prototype_encounters.json`, `prototype_reward_tables.json`, CI gates in `validate_content.py`; plan [NEO-100-implementation-plan.md](NEO-100-implementation-plan.md). --- ### E5M3-02 — Server encounter + reward catalog load (fail-fast) **Goal:** Disk → host: startup load of `content/encounters/*.json` and `content/reward-tables/*.json` with CI-parity validation. **In scope** - Loader + catalog types under `server/NeonSprawl.Server/Game/Encounters/` (path finalized in plan). - Fail-fast on malformed files, duplicate ids, allowlist mismatch with CI, broken cross-refs. - Unit tests (AAA) for loader happy path and failure modes. - `server/README.md` section. **Out of scope** - Injectable registry interface (E5M3-03), HTTP projection, runtime completion. **Acceptance criteria** - [x] Host fails startup on invalid encounter or reward-table JSON (mirror CI rules). - [x] Tests cover at least one happy path and one malformed catalog rejection. **Landed ([NEO-101](https://linear.app/neon-sprawl/issue/NEO-101)):** fail-fast encounter + reward-table catalog loaders, DI registration, 22 AAA tests; plan [NEO-101-implementation-plan.md](NEO-101-implementation-plan.md). ### E5M3-03 — Injectable encounter/reward registries + DI **Goal:** Replace direct catalog access with injectable registries; wire into host DI. **In scope** - `IEncounterDefinitionRegistry`, `IRewardTableDefinitionRegistry` + implementations backed by E5M3-02 catalogs. - `TryNormalizeKnown` / list-in-id-order parity with ability/item registry patterns. - DI extension method; unit tests (AAA). **Out of scope** - HTTP routes, progress store, client. **Acceptance criteria** - [x] Registries resolve frozen encounter + reward table ids. - [x] Unknown id normalization fails closed. **Landed ([NEO-102](https://linear.app/neon-sprawl/issue/NEO-102)):** injectable encounter + reward-table registries + DI; 18 AAA tests; plan [NEO-102-implementation-plan.md](NEO-102-implementation-plan.md). --- ### E5M3-04 — GET `/game/world/encounter-definitions` **Goal:** Read-only world projection of encounter defs (+ nested or linked reward table summary) for client labels and Bruno smokes. **In scope** - `EncounterDefinitionsWorldApi` + versioned DTOs in `Game/Encounters/`. - Integration tests (AAA); Bruno folder `bruno/neon-sprawl-server/encounter-definitions/`. - `server/README.md` route section. **Out of scope** - Per-player progress, completion grants, Godot. **Acceptance criteria** - [x] GET returns frozen encounter in ascending `id` order with `schemaVersion` **1**. - [x] Bruno happy GET passes in CI Bruno step. **Landed ([NEO-103](https://linear.app/neon-sprawl/issue/NEO-103)):** `GET /game/world/encounter-definitions` + nested `rewardTable` summary; Bruno + AAA integration test; plan [NEO-103-implementation-plan.md](NEO-103-implementation-plan.md). --- ### E5M3-05 — Encounter progress + completion stores **Goal:** Server-owned per-player encounter activation, defeat progress, and idempotent completion flag. **In scope** - `IEncounterProgressStore` — tracks **`started`**, defeated subset of **`requiredNpcInstanceIds`**, keyed by **`(playerId, encounterId)`**. - `IEncounterCompletionStore` — records **`completedAt`** (or boolean) once rewards applied; replays are no-op. - `EncounterProgressOperations.TryMarkTargetDefeated` / `TryActivateOnFirstEngagement` (first damaging cast vs required NPC). - Unit tests (AAA) with injected stores. **Out of scope** - Inventory grants (E5M3-06), HTTP DTOs (E5M3-08), Godot. **Acceptance criteria** - [x] Defeating required NPCs accumulates progress in any order. - [x] Completion predicate true only when all required ids defeated. - [x] Second completion attempt on same player+encounter is idempotent no-op at store layer. **Landed ([NEO-104](https://linear.app/neon-sprawl/issue/NEO-104)):** `IEncounterProgressStore` + `IEncounterCompletionStore` + `EncounterProgressOperations`; in-memory DI; AAA unit tests; plan [NEO-104-implementation-plan.md](NEO-104-implementation-plan.md). --- ### E5M3-06 — EncounterCompletionOperations + inventory reward apply **Goal:** On first completion, apply **`RewardTable.fixedGrants`** via **`PlayerInventoryOperations`**; emit internal **`EncounterCompleteEvent`** payload for downstream routers. **In scope** - `EncounterCompletionOperations.TryCompleteAndGrant` — checks completion store, applies fixed grants, marks complete atomically (best-effort rollback on inventory deny documented in plan). - Structured deny reason codes (`inventory_full`, `already_completed`, `not_ready`, …). - Integration tests: defeat all targets → inventory stacks increase exactly once. - Bruno spine optional in plan (may land in E5M3-08). **Out of scope** - Godot HUD — **client counterpart [NEO-110](#)**. - Full E7.M2 quest router (E5M3-09 hook stub). **Acceptance criteria** - [x] First completion grants **`scrap_metal_bulk` ×10** and **`contract_handoff_token` ×1** deterministically. - [x] Second completion does not duplicate grants. - [x] Inventory deny fails closed without marking complete. **Landed ([NEO-105](https://linear.app/neon-sprawl/issue/NEO-105)):** `EncounterCompletionOperations.TryCompleteAndGrant` + `EncounterCompleteEvent` result payload; AAA unit tests; plan [NEO-105-implementation-plan.md](NEO-105-implementation-plan.md). **Client counterpart:** [NEO-110](https://linear.app/neon-sprawl/issue/NEO-110) — encounter progress + loot feedback labels. --- ### E5M3-07 — Wire combat defeat → encounter progress/completion **Goal:** Hook **`AbilityCastApi`** (or shared combat defeat helper) so NPC **`targetDefeated`** updates encounter progress and invokes E5M3-06 when the encounter completes. **In scope** - Call **`EncounterProgressOperations`** after successful player → NPC defeat ([NEO-82](https://linear.app/neon-sprawl/issue/NEO-82) path). - **`TryActivateOnFirstEngagement`** on first damaging cast vs encounter NPC ( **`encounter_start`** comment hook site). - Integration tests: Bruno or in-proc defeat-all-three → completion + inventory grant once. - Preserve existing **NEO-44** gig XP on each defeat (do not remove). **Out of scope** - Godot presentation (E5M3-11). - Quest router beyond completion event record (E5M3-09). **Acceptance criteria** - [x] Defeating third required NPC triggers exactly one completion grant. - [x] Partial progress (1–2 defeats) does not grant loot. - [x] Gig XP still awarded per defeat via **`CombatDefeatGigXpGrant`**. **Landed ([NEO-106](https://linear.app/neon-sprawl/issue/NEO-106)):** **`EncounterCombatWiring`** on **`AbilityCastApi`**; in-proc defeat-all-three integration tests; plan [NEO-106-implementation-plan.md](NEO-106-implementation-plan.md). --- ### E5M3-08 — GET `/game/players/{id}/encounter-progress` + wire DTOs **Goal:** Authoritative per-player encounter rows for client poll / event refresh. **In scope** - `EncounterProgressApi` + DTOs: per encounter **`state`** (`inactive` / `active` / `completed`), **`defeatedTargetIds`**, **`completedAt`**, last **`rewardGrantSummary`** (item ids + quantities on complete). - Integration tests (AAA); Bruno `bruno/neon-sprawl-server/encounter-progress/`. - `server/README.md` section. **Out of scope** - Godot poll client (E5M3-11). **Acceptance criteria** - [x] GET reflects progress after each defeat without client-side inference. - [x] Completed encounter shows **`completed`** state and grant summary. - [x] Bruno smoke: defeat chain → GET shows **`completed`** once. **Landed ([NEO-108](https://linear.app/neon-sprawl/issue/NEO-108)):** **`GET /game/players/{id}/encounter-progress`** + Bruno defeat spine; plan [NEO-108-implementation-plan.md](NEO-108-implementation-plan.md). **Client counterpart:** [NEO-110](https://linear.app/neon-sprawl/issue/NEO-110) — poll/refresh from this GET. --- ### E5M3-09 — EncounterCompleteEvent + E7.M2 quest-credit hook stub **Goal:** Persist **`EncounterCompleteEvent`** record and document **E7.M2** consumer hook for quest credit (beyond quest-token item loot). **In scope** - `EncounterCompleteEvent` internal type (playerId, encounterId, idempotencyKey, grantedItemSummary, completedAt). - Comment-only **`reward_delivery`** / quest-credit hook site in completion path (TODO E7.M2). - `server/README.md` pointer; module doc cross-link to [E7_M2](../decomposition/modules/E7_M2_RewardAndUnlockRouter.md). **Out of scope** - Production quest state machine, Godot. **Acceptance criteria** - [x] Completion path emits/records event exactly once per player+encounter. - [x] Hook documents E7.M2 **`QuestRewardBundle`** consumer contract. - [x] No runtime behavior beyond event record + comments. **Landed ([NEO-107](https://linear.app/neon-sprawl/issue/NEO-107)):** **`IEncounterCompleteEventStore`** + in-memory implementation; record on **`TryCompleteAndGrant`** success; E7.M2 **`reward_delivery`** hook comments. Plan: [NEO-107-implementation-plan.md](NEO-107-implementation-plan.md). --- ### E5M3-10 — Slice 3 encounter telemetry hook sites **Goal:** Comment-only **`encounter_complete`** and **`reward_attribution`** hook sites in encounter completion path (activates reserved **`encounter_start`** from E5M3-07). **In scope** - TODO(E9.M1) markers at encounter start, completion commit, and per-item grant attribution. - `server/README.md` pointer; epic Slice 3 vocabulary cross-link. **Out of scope** - Production ingest, Godot. **Acceptance criteria** - [ ] Hook names match [epic_05 Slice 3](../decomposition/epics/epic_05_pve_combat.md#epic-5-slice-3) vocabulary. - [ ] No runtime behavior change beyond comments. --- ### E5M3-11 — Client encounter progress + loot feedback HUD **Goal:** Godot shows **server-owned** encounter progress and completion loot using encounter-progress GET. **In scope** - `client/scripts/encounter_progress_client.gd` — poll or event-driven **`GET /game/players/{id}/encounter-progress`** after combat defeats. - HUD labels: **`EncounterProgressLabel`**, **`EncounterCompleteLabel`** under `UICanvas`; refresh **`InventoryLabel`** on completion (reuse [NEO-72](https://linear.app/neon-sprawl/issue/NEO-72) client). - GdUnit tests with HTTP doubles ([testing-expectations.md](../../.cursor/rules/testing-expectations.md)). - `docs/manual-qa/NEO-110.md` — **Godot** steps (server + client running). - `client/README.md` encounter section. **Out of scope** - Final loot VFX art; encounter selection UI beyond single frozen encounter. **Acceptance criteria** - [ ] Player-visible progress updates after each NPC defeat (e.g. **2/3** targets). - [ ] Completion label shows grant summary matching server GET. - [ ] Manual QA checklist exercisable without Bruno. **Client counterpart:** this issue (**NEO-110**). --- ### E5M3-12 — Playable encounter clear capstone (Godot) **Goal:** Prove Epic 5 Slice 3 acceptance **in Godot**: defeat all three NPCs, receive loot + quest token **once**, verify inventory — without Bruno. **In scope** - `docs/manual-qa/NEO-111.md` single-session script (includes idempotency check — no duplicate loot on re-login / fresh defeat attempt after complete). - `client/README.md` end-to-end encounter loop section. - Update [documentation_and_implementation_alignment.md](../decomposition/modules/documentation_and_implementation_alignment.md) E5.M3 row when complete. **Out of scope** - Group scaling ([E5.M4](../decomposition/modules/E5_M4_GroupCombatScaling.md)), dynamic spawn ([E4.M2](../decomposition/modules/E4_M2_SpawnEcologyController.md)). **Acceptance criteria** - [ ] Human completes script with server + client; loot readable in inventory HUD. - [ ] Epic 5 Slice 3 AC: completing defined encounter grants expected items + quest token **once**. - [ ] Re-read [epic_05 Definition of Done](../decomposition/epics/epic_05_pve_combat.md#epic-5-definition-of-done) for prototype minimums. **Client counterpart:** this issue (**NEO-111**). --- ## After this backlog - Track delivery in Linear; keep `blockedBy` synchronized if scope changes. - For each issue kickoff: `docs/plans/{NEO-XX}-implementation-plan.md` per [story-kickoff](../../.cursor/rules/story-kickoff.md). - Add `docs/manual-qa/{NEO-XX}.md` for player-visible **client** stories when implementation lands. ## Decomposition complete checklist - [x] Every player-visible AC has a **`client`** Linear issue (**NEO-110**, **NEO-111**) - [x] No “optional follow-up” / “future client” without NEO-XX - [x] Capstone client issue exists for playable Godot verification (**NEO-111**) - [x] [documentation_and_implementation_alignment.md](../decomposition/modules/documentation_and_implementation_alignment.md) updated (decomposed row) - [x] [module_dependency_register.md](../decomposition/modules/module_dependency_register.md) E5.M3 note updated when issues are created ## Related docs - [E5_M3_EncounterAndRewardTables.md](../decomposition/modules/E5_M3_EncounterAndRewardTables.md) - [E5_M2_NpcAiAndBehaviorProfiles.md](../decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md) - [E3_M3_ItemizationAndInventorySchema.md](../decomposition/modules/E3_M3_ItemizationAndInventorySchema.md) - [E7_M2_RewardAndUnlockRouter.md](../decomposition/modules/E7_M2_RewardAndUnlockRouter.md) - [epic_05_pve_combat.md](../decomposition/epics/epic_05_pve_combat.md) - [client_server_authority.md](../decomposition/modules/client_server_authority.md)