109 lines
12 KiB
Markdown
109 lines
12 KiB
Markdown
# E5.M3 — EncounterAndRewardTables
|
||
|
||
## Summary
|
||
|
||
| Field | Value |
|
||
|--------|--------|
|
||
| **Module ID** | E5.M3 |
|
||
| **Epic** | [Epic 5 — PvE Combat](../epics/epic_05_pve_combat.md) |
|
||
| **Stage target** | Prototype |
|
||
| **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) catalog + CI landed · **E5M3-02** [NEO-101](https://linear.app/neon-sprawl/issue/NEO-101) server load landed · **E5M3-03** [NEO-102](https://linear.app/neon-sprawl/issue/NEO-102) registries landed · **E5M3-04** [NEO-103](https://linear.app/neon-sprawl/issue/NEO-103) HTTP read landed · **E5M3-05** [NEO-104](https://linear.app/neon-sprawl/issue/NEO-104) progress + completion stores landed · **E5M3-06** [NEO-105](https://linear.app/neon-sprawl/issue/NEO-105) completion grants landed · **E5M3-07** [NEO-106](https://linear.app/neon-sprawl/issue/NEO-106) combat wiring landed · **E5M3-09** [NEO-107](https://linear.app/neon-sprawl/issue/NEO-107) event record landed · **E5M3-08** [NEO-108](https://linear.app/neon-sprawl/issue/NEO-108) per-player GET landed · **E5M3-12** [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111) |
|
||
| **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-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) |
|
||
|
||
## Purpose
|
||
|
||
Encounter templates, completion criteria, and reward drop routing into items ([E3.M3](E3_M3_ItemizationAndInventorySchema.md)) and quest credit via [E7.M2 — RewardAndUnlockRouter](E7_M2_RewardAndUnlockRouter.md).
|
||
|
||
## Responsibilities
|
||
|
||
- Instantiate `EncounterDef`; roll `RewardTable`; emit `EncounterCompleteEvent` for downstream routers and parity checks.
|
||
|
||
## Key contracts
|
||
|
||
| Contract | Role |
|
||
|----------|------|
|
||
| `EncounterDef` | Spawns, objectives, completion rules. |
|
||
| `RewardTable` | Loot and currency references validated against item schema. |
|
||
| `EncounterCompleteEvent` | Completion signal for quests and telemetry. |
|
||
|
||
## Module dependencies
|
||
|
||
- **E5.M1** — CombatRulesEngine.
|
||
- **E5.M2** — NpcAiAndBehaviorProfiles (defeat targets).
|
||
- **E3.M3** — ItemizationAndInventorySchema.
|
||
- **E7.M2** — RewardAndUnlockRouter (quest credit consumer; prototype uses quest-token loot + hook stub).
|
||
|
||
## Dependents (by design)
|
||
|
||
- **E5.M4** — GroupCombatScaling.
|
||
- **E6.M4** — RewardParityEnforcer.
|
||
- **E7.M4** — ContractMissionGenerator.
|
||
|
||
## Related implementation slices
|
||
|
||
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** |
|
||
|
||
**CI enforcement (NEO-100):** `scripts/validate_content.py` requires **exactly** encounter id **`prototype_combat_pocket`** and reward table id **`prototype_combat_pocket_clear`**; **`requiredNpcInstanceIds`** set must match server **`PrototypeNpcRegistry`** (`prototype_npc_melee`, `prototype_npc_ranged`, `prototype_npc_elite`); **`fixedGrants`** item ids from frozen item catalog with **no duplicate `itemId` per table**, quantities **`scrap_metal_bulk` ×10**, **`contract_handoff_token` ×1**; encounter **`rewardTableId`** cross-ref. Keep **`PROTOTYPE_E5M3_*`** and **`PROTOTYPE_E5M2_NPC_INSTANCE_IDS`** in sync with this table and [NEO-101](https://linear.app/neon-sprawl/issue/NEO-101) server loader. Plan: [NEO-100 implementation plan](../../plans/NEO-100-implementation-plan.md).
|
||
|
||
**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).
|
||
|
||
## Implementation snapshot (NEO-101)
|
||
|
||
**Server catalog load landed ([NEO-101](https://linear.app/neon-sprawl/issue/NEO-101)):** fail-fast startup loaders under [`server/NeonSprawl.Server/Game/Encounters/`](../../../server/NeonSprawl.Server/Game/Encounters/) — `RewardTableDefinitionCatalogLoader` (after item catalog) then `EncounterDefinitionCatalogLoader` (after reward tables); CI-parity E5M3 gates in C#; eager DI resolve in `Program.cs`. Config + discovery: [server README — Reward table catalog](../../../server/README.md#reward-table-catalog-contentreward-tables-neo-101), [Encounter catalog](../../../server/README.md#encounter-catalog-contentencounters-neo-101). Plan: [NEO-101 implementation plan](../../plans/NEO-101-implementation-plan.md).
|
||
|
||
**Injectable registries landed ([NEO-102](https://linear.app/neon-sprawl/issue/NEO-102)):** `IEncounterDefinitionRegistry` + `IRewardTableDefinitionRegistry` + DI in `AddEncounterAndRewardCatalogs`; game/HTTP callers should inject registries, not catalog types. Plan: [NEO-102 implementation plan](../../plans/NEO-102-implementation-plan.md).
|
||
|
||
**HTTP read model landed ([NEO-103](https://linear.app/neon-sprawl/issue/NEO-103)):** **`GET /game/world/encounter-definitions`** — `EncounterDefinitionsWorldApi` + DTOs in `Game/Encounters/`; nested **`rewardTable`** summary via **`IRewardTableDefinitionRegistry`**. Plan: [NEO-103 implementation plan](../../plans/NEO-103-implementation-plan.md); [server README — Encounter definitions](../../../server/README.md#encounter-definitions-neo-103); Bruno `bruno/neon-sprawl-server/encounter-definitions/`.
|
||
|
||
**Progress + completion stores landed ([NEO-104](https://linear.app/neon-sprawl/issue/NEO-104)):** **`IEncounterProgressStore`** + **`IEncounterCompletionStore`** (in-memory) + **`EncounterProgressOperations`** — activation, order-independent defeat tracking, idempotent completion flag; combat wiring NEO-106, HTTP read NEO-108. Plan: [NEO-104 implementation plan](../../plans/NEO-104-implementation-plan.md); [server README — Encounter progress](../../../server/README.md#encounter-progress--completion-stores-neo-104).
|
||
|
||
**Completion + inventory grants landed ([NEO-105](https://linear.app/neon-sprawl/issue/NEO-105)):** **`EncounterCompletionOperations.TryCompleteAndGrant`** + **`EncounterCompleteEvent`** result payload — reward-table fixed grants via **`PlayerInventoryOperations`**, fail-closed inventory deny, idempotent completion mark; event record persisted via **`IEncounterCompleteEventStore`** ([NEO-107](https://linear.app/neon-sprawl/issue/NEO-107)). Plan: [NEO-105 implementation plan](../../plans/NEO-105-implementation-plan.md); [server README — Encounter completion](../../../server/README.md#encounter-completion--inventory-grants-neo-105).
|
||
|
||
**Combat wiring landed ([NEO-106](https://linear.app/neon-sprawl/issue/NEO-106)):** **`EncounterCombatWiring.TryProcessCastOutcome`** on **`AbilityCastApi`** — activate on first damaging hit, mark defeats, invoke completion grants when all required NPCs defeated; NEO-44 gig XP preserved. Plan: [NEO-106 implementation plan](../../plans/NEO-106-implementation-plan.md); [server README — Encounter combat wiring](../../../server/README.md#encounter-combat-wiring-neo-106).
|
||
|
||
**Complete event record landed ([NEO-107](https://linear.app/neon-sprawl/issue/NEO-107)):** **`IEncounterCompleteEventStore`** + in-memory implementation — idempotent **`EncounterCompleteEvent`** record on successful grant commit; E7.M2 **`reward_delivery`** hook stub (comments only). Plan: [NEO-107 implementation plan](../../plans/NEO-107-implementation-plan.md); [server README — Encounter complete event (NEO-107)](../../../server/README.md#encounter-complete-event-record-neo-107).
|
||
|
||
**Per-player progress HTTP landed ([NEO-108](https://linear.app/neon-sprawl/issue/NEO-108)):** **`GET /game/players/{id}/encounter-progress`** — **`EncounterProgressApi`** + DTOs; projects progress, completion, and event stores; **`rewardGrantSummary`** from commit-time **`GrantedItems`**. Plan: [NEO-108 implementation plan](../../plans/NEO-108-implementation-plan.md); [server README — Per-player encounter progress (NEO-108)](../../../server/README.md#per-player-encounter-progress-neo-108); Bruno `bruno/neon-sprawl-server/encounter-progress/`. Client counterpart: [NEO-110](https://linear.app/neon-sprawl/issue/NEO-110).
|
||
|
||
**Slice 3 telemetry hooks landed ([NEO-109](https://linear.app/neon-sprawl/issue/NEO-109)):** comment-only **`encounter_start`**, **`encounter_complete`**, **`reward_attribution`**, and **`encounter_complete_denied`** in **`EncounterProgressOperations`** / **`EncounterCompletionOperations`**; **`EncounterCombatWiring`** delegates without duplicate hooks. Plan: [NEO-109 implementation plan](../../plans/NEO-109-implementation-plan.md); [server README — Encounter telemetry hooks (NEO-109)](../../../server/README.md#encounter-telemetry-hooks-neo-109).
|
||
|
||
**Client encounter HUD landed ([NEO-110](https://linear.app/neon-sprawl/issue/NEO-110)):** Godot **`encounter_progress_client.gd`** polls **`GET /game/players/{id}/encounter-progress`** on boot and after defeat casts; **`EncounterProgressLabel`** / **`EncounterCompleteLabel`** in combat HUD; inventory refresh on completion. Plan: [NEO-110 implementation plan](../../plans/NEO-110-implementation-plan.md); manual QA: [NEO-110.md](../../manual-qa/NEO-110.md); [client README — Encounter progress + loot HUD](../../../client/README.md#encounter-progress--loot-hud-neo-110).
|
||
|
||
**Client capstone ([NEO-111](https://linear.app/neon-sprawl/issue/NEO-111)):** playable encounter clear loop in Godot — [`NEO-111.md`](../../manual-qa/NEO-111.md) single-session script (flexible defeat order, Godot restart idempotency); [client README — End-to-end encounter clear loop (NEO-111)](../../../client/README.md#end-to-end-encounter-clear-loop-neo-111). Plan: [NEO-111 implementation plan](../../plans/NEO-111-implementation-plan.md). Manual QA pass pending human run.
|
||
|
||
## Source anchors
|
||
|
||
- Master plan: [`neon_sprawl_vision.plan.md`](../../../neon_sprawl_vision.plan.md) — Epic 5.
|
||
- [Module dependency register](module_dependency_register.md)
|