neon-sprawl/docs/decomposition/modules/E7_M2_RewardAndUnlockRouter.md

72 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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).