chore: Decompose E5.M3 prototype backlog (NEO-100–NEO-111)

Epic 5 Slice 3 encounter + reward routing: backlog doc, module/epic/register
updates, and Linear issues for one combat-pocket clear with idempotent loot.
pull/138/head
VinPropane 2026-05-30 18:38:18 -04:00
parent 2f1b7f10be
commit db9ddb9fe0
7 changed files with 424 additions and 5 deletions

View File

@ -11,6 +11,8 @@ Data-driven definitions (skills, items, recipes, quests) validated in CI with **
| [`recipes/`](recipes/) | Recipe catalogs; each row matches [`schemas/recipe-def.schema.json`](schemas/recipe-def.schema.json) — **stable `id`**, **`recipeKind`**, **`requiredSkillId`**, inputs/outputs for [E3.M2](../docs/decomposition/modules/E3_M2_RefinementAndRecipeExecution.md) |
| [`abilities/`](abilities/) | Combat ability catalogs; each row matches [`schemas/ability-def.schema.json`](schemas/ability-def.schema.json) — **stable `id`**, **`baseDamage`**, **`cooldownSeconds`** for [E5.M1](../docs/decomposition/modules/E5_M1_CombatRulesEngine.md) |
| [`npc-behaviors/`](npc-behaviors/) | NPC behavior archetype catalogs; each row matches [`schemas/npc-behavior-def.schema.json`](schemas/npc-behavior-def.schema.json) — **stable `id`**, **`archetypeKind`**, aggro/leash radii, telegraph + attack timings for [E5.M2](../docs/decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md) |
| [`encounters/`](encounters/) | Encounter templates; each row matches [`schemas/encounter-def.schema.json`](schemas/encounter-def.schema.json) — **stable `id`**, completion criteria, **`rewardTableId`** for [E5.M3](../docs/decomposition/modules/E5_M3_EncounterAndRewardTables.md) |
| [`reward-tables/`](reward-tables/) | Reward table catalogs; each row matches [`schemas/reward-table.schema.json`](schemas/reward-table.schema.json) — **stable `id`**, **`fixedGrants`** referencing frozen item ids for [E5.M3](../docs/decomposition/modules/E5_M3_EncounterAndRewardTables.md) |
| [`resource-nodes/`](resource-nodes/) | Resource node + yield catalogs; node rows match [`schemas/resource-node-def.schema.json`](schemas/resource-node-def.schema.json), yield rows match [`schemas/resource-yield-row.schema.json`](schemas/resource-yield-row.schema.json) — **stable `nodeDefId`**, **`gatherLens`**, **`maxGathers`** for [E3.M1](../docs/decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md) |
**Prototype Slice 1 (NEO-33):** CI expects **exactly three** skill rows with ids **`salvage`**, **`refine`**, **`intrusion`** (gather + process + tech). Each row must declare **`allowedXpSourceKinds`** (non-empty); see [E2.M1 — Designer note: `allowedXpSourceKinds`](../docs/decomposition/modules/E2_M1_SkillDefinitionRegistry.md#designer-note-allowedxpsourcekinds-and-grant-call-sites) for what each kind means for grant wiring.
@ -25,6 +27,8 @@ Data-driven definitions (skills, items, recipes, quests) validated in CI with **
**Prototype E5 Slice 2 — NPC behaviors (NEO-87):** CI expects **exactly three** npc behavior ids aligned to [E5.M2 behavior freeze](../docs/decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md#prototype-slice-2-freeze-e5m2-01) — **`prototype_melee_pressure`**, **`prototype_ranged_control`**, **`prototype_elite_mini_boss`**. Each row requires **`leashRadius` > `aggroRadius`**. **Do not rename** behavior `id` after ship—change **`displayName`** only. See [E5M2-prototype-backlog.md](../docs/plans/E5M2-prototype-backlog.md) and [NEO-87 plan](../docs/plans/NEO-87-implementation-plan.md).
**Prototype E5 Slice 3 — encounters + rewards (NEO-100):** CI expects **exactly one** encounter id **`prototype_combat_pocket`** and one reward table id **`prototype_combat_pocket_clear`** aligned to [E5.M3 encounter freeze](../docs/decomposition/modules/E5_M3_EncounterAndRewardTables.md#prototype-slice-3-freeze-e5m3-01). **`requiredNpcInstanceIds`** must match the three E5.M2 NPC instance ids; **`fixedGrants`** reference frozen item ids only. **Do not rename** ids after ship—change **`displayName`** only. See [E5M3-prototype-backlog.md](../docs/plans/E5M3-prototype-backlog.md).
**Prototype Slice 4 (NEO-45):** CI expects **exactly one** `MasteryTrack` for **`salvage`** only (`refine` / `intrusion` have no mastery rows yet). Tier 1 @ skill level **2** is a **mutually exclusive** branch pick (`scrap_efficiency` vs `bulk_haul`) with **no** perks; tier 2 @ level **4** unlocks one perk per branch path. **Do not rename** `branchId` / `perkId` after ship—change `displayName` only. See [E2.M3 — Designer note](../docs/decomposition/modules/E2_M3_MasteryAndPerkUnlocks.md#designer-note-tiers-branches-and-level-gates) and [freeze table](../docs/decomposition/modules/E2_M3_MasteryAndPerkUnlocks.md#prototype-slice-4-freeze--salvage-flagship-neo-45).
Server load path and hot-reload policy are TBD; keep files versioned here as the source of truth.

View File

@ -69,11 +69,13 @@ Deliver tab-target combat resolution, NPC behaviors and telegraphs, encounter te
### Slice 3 - Encounters and loot routing
- Scope: E5.M3 wired to E3.M3 and E7.M2 for rewards and quest progress.
- Dependencies: E5.M2, E3.M3, E7.M2
- Dependencies: E5.M2, E3.M3, E7.M2 (quest router stub + quest-token loot until E7.M2 lands)
- Acceptance criteria:
- Completing defined encounters grants expected items/quest credit once.
- Telemetry hooks: `encounter_complete`, reward attribution.
**Linear backlog:** [E5M3-prototype-backlog.md](../../plans/E5M3-prototype-backlog.md) — [NEO-100](https://linear.app/neon-sprawl/issue/NEO-100) → [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111); client capstone **E5M3-12** [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111). Verify in **Godot**, not Bruno-only.
### Slice 4 - Group scaling (pre-production)
- Scope: E5.M4 with party-sized modifiers behind E8.M1.

View File

@ -7,7 +7,8 @@
| **Module ID** | E5.M3 |
| **Epic** | [Epic 5 — PvE Combat](../epics/epic_05_pve_combat.md) |
| **Stage target** | Prototype |
| **Status** | Planned (see [dependency register](module_dependency_register.md)) |
| **Status** | In Progress — Slice 3 backlog [E5M3-prototype-backlog.md](../../plans/E5M3-prototype-backlog.md): **E5M3-01** [NEO-100](https://linear.app/neon-sprawl/issue/NEO-100) → **E5M3-12** [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111) decomposed |
| **Linear** | Label **`E5.M3`** · [E5M3-prototype-backlog.md](../../plans/E5M3-prototype-backlog.md) **E5M3-01** [NEO-100](https://linear.app/neon-sprawl/issue/NEO-100) · **E5M3-02** [NEO-101](https://linear.app/neon-sprawl/issue/NEO-101) · **E5M3-03** [NEO-102](https://linear.app/neon-sprawl/issue/NEO-102) · **E5M3-04** [NEO-103](https://linear.app/neon-sprawl/issue/NEO-103) · **E5M3-05** [NEO-104](https://linear.app/neon-sprawl/issue/NEO-104) · **E5M3-06** [NEO-105](https://linear.app/neon-sprawl/issue/NEO-105) · **E5M3-07** [NEO-106](https://linear.app/neon-sprawl/issue/NEO-106) · **E5M3-08** [NEO-107](https://linear.app/neon-sprawl/issue/NEO-107) · **E5M3-09** [NEO-108](https://linear.app/neon-sprawl/issue/NEO-108) · **E5M3-10** [NEO-109](https://linear.app/neon-sprawl/issue/NEO-109) · **E5M3-11** [NEO-110](https://linear.app/neon-sprawl/issue/NEO-110) · **E5M3-12** [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111) |
## Purpose
@ -28,8 +29,9 @@ Encounter templates, completion criteria, and reward drop routing into items ([E
## Module dependencies
- **E5.M1** — CombatRulesEngine.
- **E5.M2** — NpcAiAndBehaviorProfiles (defeat targets).
- **E3.M3** — ItemizationAndInventorySchema.
- **E7.M2** — RewardAndUnlockRouter.
- **E7.M2** — RewardAndUnlockRouter (quest credit consumer; prototype uses quest-token loot + hook stub).
## Dependents (by design)
@ -41,6 +43,39 @@ Encounter templates, completion criteria, and reward drop routing into items ([E
Epic 5 **Slice 3**`encounter_complete`, reward attribution once.
## Linear backlog (decomposed)
Full story tables, kickoff defaults, and **`blockedBy` graph:** [E5M3-prototype-backlog.md](../../plans/E5M3-prototype-backlog.md).
| Slug | Linear |
|------|--------|
| E5M3-01 | [NEO-100](https://linear.app/neon-sprawl/issue/NEO-100) |
| E5M3-02 | [NEO-101](https://linear.app/neon-sprawl/issue/NEO-101) |
| E5M3-03 | [NEO-102](https://linear.app/neon-sprawl/issue/NEO-102) |
| E5M3-04 | [NEO-103](https://linear.app/neon-sprawl/issue/NEO-103) |
| E5M3-05 | [NEO-104](https://linear.app/neon-sprawl/issue/NEO-104) |
| E5M3-06 | [NEO-105](https://linear.app/neon-sprawl/issue/NEO-105) |
| E5M3-07 | [NEO-106](https://linear.app/neon-sprawl/issue/NEO-106) |
| E5M3-08 | [NEO-108](https://linear.app/neon-sprawl/issue/NEO-108) |
| E5M3-09 | [NEO-107](https://linear.app/neon-sprawl/issue/NEO-107) |
| E5M3-10 | [NEO-109](https://linear.app/neon-sprawl/issue/NEO-109) |
| E5M3-11 | [NEO-110](https://linear.app/neon-sprawl/issue/NEO-110) |
| E5M3-12 | [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111) |
## Prototype Slice 3 freeze (E5M3-01)
The **first shipped encounter + reward spine** is **frozen** for prototype tuning until a deliberate migration issue expands the roster.
| `EncounterDef.id` | `displayName` | `completionCriteria.kind` | `requiredNpcInstanceIds` | `rewardTableId` |
|-------------------|---------------|---------------------------|--------------------------|-----------------|
| **`prototype_combat_pocket`** | Prototype Combat Pocket | **`defeat_all_targets`** | **`prototype_npc_melee`**, **`prototype_npc_ranged`**, **`prototype_npc_elite`** | **`prototype_combat_pocket_clear`** |
| `RewardTable.id` | `fixedGrants` |
|------------------|---------------|
| **`prototype_combat_pocket_clear`** | **`scrap_metal_bulk` ×10**, **`contract_handoff_token` ×1** |
**Payout policy:** Per-defeat **gig XP** stays on [NEO-44](../../plans/NEO-44-implementation-plan.md). Encounter complete grants **loot + quest token** **once** per player per encounter id. Full **E7.M2** quest credit routing consumes **`EncounterCompleteEvent`** (E5M3-09 hook stub).
## Source anchors
- Master plan: [`neon_sprawl_vision.plan.md`](../../../neon_sprawl_vision.plan.md) — Epic 5.

File diff suppressed because one or more lines are too long

View File

@ -73,13 +73,15 @@ Fleshed-out scope, contracts, and integration notes live in **per-module documen
|---|---|---|---|---|---|
| E5.M1 | CombatRulesEngine | E1.M3, E2.M2 | CombatAction, CombatResolution, ThreatState | Prototype | Ready |
| E5.M2 | NpcAiAndBehaviorProfiles | E5.M1 | NpcBehaviorDef, TelegraphEvent, AggroRule | Prototype | In Progress |
| E5.M3 | EncounterAndRewardTables | E5.M1, E3.M3, E7.M2 | EncounterDef, RewardTable, EncounterCompleteEvent | Prototype | Planned |
| E5.M3 | EncounterAndRewardTables | E5.M1, E5.M2, E3.M3, E7.M2 | EncounterDef, RewardTable, EncounterCompleteEvent | Prototype | In Progress |
| E5.M4 | GroupCombatScaling | E5.M3, E8.M1 | ScalingProfile, PartySizeModifier, CombatDifficultyBand | Pre-production | Planned |
**E5.M1 note:** Epic 5 **Slice 1** backlog in Linear ([Epic 5 — PvE Combat and Encounter Content](https://linear.app/neon-sprawl/project/epic-5-pve-combat-and-encounter-content-cf3c94c3-5346-40e0-8aaf-281e846f2846)): [NEO-76](https://linear.app/neon-sprawl/issue/NEO-76) → [NEO-86](https://linear.app/neon-sprawl/issue/NEO-86); label **`E5.M1`**. Gig XP on defeat: **E5M1-10** [NEO-44](https://linear.app/neon-sprawl/issue/NEO-44). See [E5M1-prototype-backlog.md](../../plans/E5M1-prototype-backlog.md), [E5_M1_CombatRulesEngine.md](E5_M1_CombatRulesEngine.md). Builds on E1.M4 cast funnel (NEO-28/31/32); extends **`POST …/ability-cast`** with **`CombatResolution`**. **NEO-76 landed:** frozen four-ability catalog + CI ([NEO-76 plan](../../plans/NEO-76-implementation-plan.md)). **NEO-77 landed:** fail-fast server load of `content/abilities/*_abilities.json` ([NEO-77 plan](../../plans/NEO-77-implementation-plan.md)). **NEO-79 landed:** injectable **`IAbilityDefinitionRegistry`** + DI; hotbar/cast use **`TryNormalizeKnown`** ([NEO-79 plan](../../plans/NEO-79-implementation-plan.md)). **NEO-78 landed:** **`GET /game/world/ability-definitions`** — `AbilityDefinitionsWorldApi` + DTOs in `Game/Combat/` ([NEO-78 plan](../../plans/NEO-78-implementation-plan.md)); Bruno `bruno/neon-sprawl-server/ability-definitions/`. **NEO-80 landed:** **`ICombatEntityHealthStore`** / **`InMemoryCombatEntityHealthStore`** — lazy init (Slice 1 flat 100 HP; **superseded by [NEO-91 plan](../../plans/NEO-91-implementation-plan.md)** per-archetype catalog max HP + three NPC instance ids); DI via **`AddCombatEntityHealthStore()`** ([NEO-80 plan](../../plans/NEO-80-implementation-plan.md)); [server README — Combat entity health (NEO-80, NEO-91)](../../../server/README.md#combat-entity-health-neo-80-neo-91). **NEO-81 landed:** **`CombatOperations.TryResolve`** + **`CombatResult`** + **`CombatReasonCodes`** — defeated pre-check deny vocabulary (`target_defeated`, `unknown_ability`, `unknown_target`) ([NEO-81 plan](../../plans/NEO-81-implementation-plan.md)); [server README — Combat engine (NEO-81)](../../../server/README.md#combat-engine-neo-81). **NEO-82 landed:** **`AbilityCastApi`** invokes **`CombatOperations.TryResolve`** after E1.M4 gates; nested wire **`combatResolution`** on accept; per-ability catalog **`cooldownSeconds`** on successful resolve; **`target_defeated`** JSON cast deny without cooldown ([NEO-82 plan](../../plans/NEO-82-implementation-plan.md)); [server README — Ability cast (NEO-82)](../../../server/README.md#ability-cast-neo-31-neo-82); Bruno `bruno/neon-sprawl-server/ability-cast/` defeat spine. **NEO-83 landed:** **`GET /game/world/combat-targets`** — `CombatTargetsWorldApi` + DTOs; authoritative HP snapshot from **`ICombatEntityHealthStore`** for client HUD ([NEO-83 plan](../../plans/NEO-83-implementation-plan.md)); [server README — Combat targets snapshot (NEO-83)](../../../server/README.md#combat-targets-snapshot-neo-83); Bruno `bruno/neon-sprawl-server/combat-targets/`. **NEO-44 landed:** **`CombatDefeatGigXpGrant`** on cast **`targetDefeated`** — **25** gig XP to **`breach`** via **`IPlayerGigProgressionStore`** (V007); **`GET …/gig-progression`** ([NEO-44 plan](../../plans/NEO-44-implementation-plan.md)); [server README — Gig progression (NEO-44)](../../../server/README.md#gig-progression-snapshot-neo-44). **NEO-84 landed:** comment-only **`ability_used`** / **`enemy_defeat`** telemetry hook sites in **`CombatOperations.TryResolve`**; reserved **`encounter_start`** / **`player_death`** ([NEO-84 plan](../../plans/NEO-84-implementation-plan.md)); [server README — Combat telemetry hooks (NEO-84)](../../../server/README.md#combat-telemetry-hooks-neo-84). **NEO-85 landed:** client combat HUD — **`combat_targets_client.gd`**, **`CombatTargetHpLabel`**, **`CastFeedbackLabel`** resolution copy; event-driven **`GET /game/world/combat-targets`** refresh ([NEO-85 plan](../../plans/NEO-85-implementation-plan.md), [`NEO-85` manual QA](../../manual-qa/NEO-85.md)); [client README — Combat feedback + target HP HUD (NEO-85)](../../../client/README.md#combat-feedback--target-hp-hud-neo-85). **NEO-86 landed:** playable combat capstone — **`gig_progression_client.gd`**, **`GigXpLabel`**, defeat-triggered gig GET refresh; capstone manual QA [`NEO-86`](../../manual-qa/NEO-86.md) ([NEO-86 plan](../../plans/NEO-86-implementation-plan.md)); [client README — End-to-end combat loop (NEO-86)](../../../client/README.md#end-to-end-combat-loop-neo-86). **Epic 5 Slice 1 complete — register row Ready.**
**E5.M2 note:** Epic 5 **Slice 2** backlog in Linear ([Epic 5 — PvE Combat and Encounter Content](https://linear.app/neon-sprawl/project/epic-5-pve-combat-and-encounter-content-cf3c94c3-5346-40e0-8aaf-281e846f2846)): [NEO-87](https://linear.app/neon-sprawl/issue/NEO-87) → [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98); label **`E5.M2`**. Client capstone **E5M2-12** [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98). See [E5M2-prototype-backlog.md](../../plans/E5M2-prototype-backlog.md), [E5_M2_NpcAiAndBehaviorProfiles.md](E5_M2_NpcAiAndBehaviorProfiles.md). Upstream **E5.M1 Ready**. **NEO-87 landed:** frozen three-behavior catalog + CI ([NEO-87 plan](../../plans/NEO-87-implementation-plan.md)). **NEO-88 landed:** fail-fast server load of `content/npc-behaviors/*_npc_behaviors.json` ([NEO-88 plan](../../plans/NEO-88-implementation-plan.md)); [server README — NPC behavior catalog](../../../server/README.md#npc-behavior-catalog-contentnpc-behaviors-neo-88). **NEO-89 landed:** injectable **`INpcBehaviorDefinitionRegistry`** + DI ([NEO-89 plan](../../plans/NEO-89-implementation-plan.md)). **NEO-90 landed:** **`GET /game/world/npc-behavior-definitions`** — `NpcBehaviorDefinitionsWorldApi` + DTOs in `Game/Npc/` ([NEO-90 plan](../../plans/NEO-90-implementation-plan.md)); [server README — NPC behavior definitions (NEO-90)](../../../server/README.md#npc-behavior-definitions-neo-90); Bruno `bruno/neon-sprawl-server/npc-behavior-definitions/`. **NEO-91 landed:** **`PrototypeNpcRegistry`** + combat-target migration — three NPC instance ids, per-archetype catalog max HP ([NEO-91 plan](../../plans/NEO-91-implementation-plan.md)); [server README — Combat entity health (NEO-91)](../../../server/README.md#combat-entity-health-neo-80-neo-91). **NEO-92NEO-96 landed:** aggro/threat, runtime state machine, **`GET …/npc-runtime-snapshot`**, player combat HP + NPC attack resolve, telemetry hooks ([NEO-92](../../plans/NEO-92-implementation-plan.md)[NEO-96](../../plans/NEO-96-implementation-plan.md)). **NEO-97 landed:** client telegraph HUD ([NEO-97 plan](../../plans/NEO-97-implementation-plan.md), [`NEO-97` manual QA](../../manual-qa/NEO-97.md)). **NEO-98 landed:** playable NPC telegraph combat capstone ([NEO-98 plan](../../plans/NEO-98-implementation-plan.md), [`NEO-98` manual QA](../../manual-qa/NEO-98.md)); [client README — End-to-end NPC telegraph combat loop (NEO-98)](../../../client/README.md#end-to-end-npc-telegraph-combat-loop-neo-98). **Epic 5 Slice 2 complete — register row Ready.** Replaces **`prototype_target_alpha` / `beta`** with three NPC archetype instances; owns **`ThreatState`**, **`TelegraphEvent`**, session **`IPlayerCombatHealthStore`**.
**E5.M3 note:** Epic 5 **Slice 3** backlog in Linear ([Epic 5 — PvE Combat and Encounter Content](https://linear.app/neon-sprawl/project/epic-5-pve-combat-and-encounter-content-cf3c94c3-5346-40e0-8aaf-281e846f2846)): [NEO-100](https://linear.app/neon-sprawl/issue/NEO-100) → [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111); label **`E5.M3`**. Client capstone **E5M3-12** [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111). See [E5M3-prototype-backlog.md](../../plans/E5M3-prototype-backlog.md), [E5_M3_EncounterAndRewardTables.md](E5_M3_EncounterAndRewardTables.md). Upstream **E5.M2 Ready**, **E3.M3** inventory landed. Prototype spine: one encounter **`prototype_combat_pocket`** (defeat all three E5.M2 NPC ids) → reward table **`prototype_combat_pocket_clear`** ( **`scrap_metal_bulk` ×10**, **`contract_handoff_token` ×1** ); idempotent completion per player; per-defeat gig XP unchanged ([NEO-44](../../plans/NEO-44-implementation-plan.md)). **Backlog decomposed** — implementation not started.
### Epic 6 — PvP Security
| Module ID | Module Name | Depends On | Contract Needed | Phase Required | Status |

View File

@ -388,7 +388,7 @@ Working backlog for **Epic 5 — Slice 2** ([NPC archetypes and telegraphs](../d
## After this backlog
- **[E5.M3](../decomposition/modules/E5_M3_EncounterAndRewardTables.md)** Slice 3 — decompose when E5.M2 lands (encounters + loot routing).
- **[E5.M3](../decomposition/modules/E5_M3_EncounterAndRewardTables.md)** Slice 3 — decomposed in [E5M3-prototype-backlog.md](E5M3-prototype-backlog.md) (**NEO-100** → **NEO-111**).
- 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.

View File

@ -0,0 +1,375 @@
# E5.M3 — Prototype story backlog (EncounterAndRewardTables)
Working backlog for **Epic 5 — Slice 3** ([encounters and loot routing](../decomposition/epics/epic_05_pve_combat.md#slice-3---encounters-and-loot-routing)). 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 **112** 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**
- [ ] PR gate validates encounter + reward-table JSON against schema.
- [ ] Exactly one prototype encounter id and one reward table id; duplicate `id` fails CI.
- [ ] Stable id list documented in module doc freeze box.
---
### 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**
- [ ] Host fails startup on invalid encounter or reward-table JSON (mirror CI rules).
- [ ] Tests cover at least one happy path and one malformed catalog rejection.
---
### 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**
- [ ] Registries resolve frozen encounter + reward table ids.
- [ ] Unknown id normalization fails closed.
---
### 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**
- [ ] GET returns frozen encounter in ascending `id` order with `schemaVersion` **1**.
- [ ] Bruno happy GET passes in CI Bruno step.
---
### 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**
- [ ] Defeating required NPCs accumulates progress in any order.
- [ ] Completion predicate true only when all required ids defeated.
- [ ] Second completion attempt on same player+encounter is idempotent no-op at store layer.
---
### 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**
- [ ] First completion grants **`scrap_metal_bulk` ×10** and **`contract_handoff_token` ×1** deterministically.
- [ ] Second completion does not duplicate grants.
- [ ] Inventory deny fails closed without marking complete.
**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**
- [ ] Defeating third required NPC triggers exactly one completion grant.
- [ ] Partial progress (12 defeats) does not grant loot.
- [ ] Gig XP still awarded per defeat via **`CombatDefeatGigXpGrant`**.
---
### 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**
- [ ] GET reflects progress after each defeat without client-side inference.
- [ ] Completed encounter shows **`completed`** state and grant summary.
- [ ] Bruno smoke: defeat chain → GET shows **`completed`** once.
**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**
- [ ] Completion path emits/records event exactly once per player+encounter.
- [ ] Hook documents E7.M2 **`QuestRewardBundle`** consumer contract.
- [ ] No runtime behavior beyond event record + comments.
---
### 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#slice-3---encounters-and-loot-routing) 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#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)