# E7.M2 — RewardAndUnlockRouter ## Summary | Field | Value | |--------|--------| | **Module ID** | E7.M2 | | **Epic** | [Epic 7 — Quest / Faction](../epics/epic_07_quest_faction.md) | | **Stage target** | Prototype | | **Status** | Planned — Slice 2 backlog [E7M2-prototype-backlog.md](../../plans/E7M2-prototype-backlog.md): **E7M2-01** [NEO-124](https://linear.app/neon-sprawl/issue/NEO-124) → **E7M2-09** [NEO-132](https://linear.app/neon-sprawl/issue/NEO-132). **NEO-43 prep landed:** `MissionRewardSkillXpGrant` for bundle skill XP (`mission_reward`). Upstream: E7.M1 **Ready**, E2.M2 grant stack, E3.M3 inventory. | ## Purpose Route quest and encounter outputs to XP, items, unlocks, and flags with **idempotent** delivery ([E7.M1](E7_M1_QuestStateMachine.md) completion, [E2.M2](E2_M2_XpAwardAndLevelEngine.md), [E3.M3](E3_M3_ItemizationAndInventorySchema.md)). **Party:** [Per-character idempotent grants](quest_scope_and_party.md#rewards-e7m2) when party credit completes multiple characters. ## Responsibilities - Apply `QuestRewardBundle`; grant `UnlockGrant`; emit `RewardDeliveryEvent` for audit. ## Key contracts | Contract | Role | |----------|------| | `QuestRewardBundle` | Composite rewards for a step or completion. | | `UnlockGrant` | Blueprints, gates, cosmetics. | | `RewardDeliveryEvent` | Idempotency key and outcome. | ## Module dependencies - **E2.M2** — XpAwardAndLevelEngine. - **E3.M3** — ItemizationAndInventorySchema. - **E7.M1** — QuestStateMachine. ## Dependents (by design) - **E5.M3** — EncounterAndRewardTables. - **E6.M4** — RewardParityEnforcer. ## Related implementation slices Epic 7 **Slice 2** — `reward_delivery`, `unlock_granted`; no double-claim on replay. Backlog: [E7M2-prototype-backlog.md](../../plans/E7M2-prototype-backlog.md). ## Prototype Slice 2 freeze (E7M2-01) **`completionRewardBundle`** on each frozen [E7.M1 quest id](E7_M1_QuestStateMachine.md#prototype-slice-1-freeze-e7m1-01). Grant kinds v1: **`itemGrants`** + **`skillXpGrants`** only (unlock/flag rows deferred). Skill XP apply uses **`mission_reward`** via **`MissionRewardSkillXpGrant`** — not stored in content rows. | Quest id | Item grants (`itemGrants`) | Skill XP (`skillXpGrants`) | |----------|----------------------------|----------------------------| | `prototype_quest_gather_intro` | — | `salvage` **25** | | `prototype_quest_refine_intro` | — | `refine` **25** | | `prototype_quest_combat_intro` | — | `salvage` **25** | | `prototype_quest_operator_chain` | `survey_drone_kit` ×1 | `salvage` **50** | **Idempotency:** `{playerId}:quest_complete:{questId}`. **Encounter loot:** [E5.M3](E5_M3_EncounterAndRewardTables.md) direct grants unchanged in Slice 2. **CI enforcement ([NEO-124](https://linear.app/neon-sprawl/issue/NEO-124)):** [`scripts/validate_content.py`](../../../scripts/validate_content.py) requires **`completionRewardBundle`** on all four frozen quest ids with exact freeze-table contents; cross-checks **`itemGrants[].itemId`** against the frozen item catalog and **`skillXpGrants[].skillId`** against skills that allow **`mission_reward`** in **`allowedXpSourceKinds`**. **Server load ([NEO-125](https://linear.app/neon-sprawl/issue/NEO-125)):** [`QuestDefinitionCatalogLoader`](../../../server/NeonSprawl.Server/Game/Quests/QuestDefinitionCatalogLoader.cs) mirrors the same E7M2 gates at startup via **`PrototypeE7M2QuestCatalogRules`**; see [server README — Quest catalog](../../../server/README.md#quest-catalog-contentquests-neo-113). ## Source anchors - Master plan: [`neon_sprawl_vision.plan.md`](../../../neon_sprawl_vision.plan.md) — Epic 7. - [Module dependency register](module_dependency_register.md) ## Implementation anchor (server) **NEO-43:** When applying scripted **skill XP** from a mission/quest reward bundle, call **`MissionRewardSkillXpGrant.GrantFromMissionReward`** (`server/NeonSprawl.Server/Game/Skills/`) — fixed **`sourceKind: mission_reward`**, **`skillId`** and **`amount`** from reward data — [NEO-43 implementation plan](../../plans/NEO-43-implementation-plan.md). **NEO-107 (E5.M3 producer hook):** On encounter completion grant commit, **`EncounterCompletionOperations.TryCompleteAndGrant`** records **`EncounterCompleteEvent`** in **`IEncounterCompleteEventStore`** (idempotency key **`{playerId}:{encounterId}`**). Future E7.M2 **`QuestRewardBundle`** router consumes this record for **`reward_delivery`** quest credit beyond prototype **`contract_handoff_token`** item loot — comment-only hook stub; no runtime router yet. See [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). **NEO-126 (E7M2-03 delivery store):** Quest completion reward delivery uses **`IRewardDeliveryStore`** + **`InMemoryRewardDeliveryStore`** in `server/NeonSprawl.Server/Game/Rewards/` — idempotent **`RewardDeliveryEvent`** per player+quest (idempotency key **`{playerId}:quest_complete:{questId}`**; grant snapshots for item + skill XP rows). Router apply and quest-state wiring land in NEO-127/NEO-128; HTTP **`completionRewardSummary`** in NEO-129. See [NEO-126 implementation plan](../../plans/NEO-126-implementation-plan.md); [server README — Reward delivery store (NEO-126)](../../../server/README.md#reward-delivery-store-neo-126).