16 KiB
E7.M2 — Prototype story backlog (RewardAndUnlockRouter)
Working backlog for Epic 7 — Slice 2 (reward and unlock routing). Decomposition and contracts: E7.M2 — RewardAndUnlockRouter.
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: E7M1 (paired server+client), E5M3 (encounter loot capstone).
Labels (Linear): every issue E7.M2; add server or client + Story as listed.
Precursor (do not re-scope): E7.M1 Slice 1 Ready — quest state + objectives only; no duplicate completion grants on replay (NEO-123). E5.M3 encounter loot stays on EncounterCompletionOperations + rewardGrantSummary on encounter-progress GET — not migrated through E7.M2 in Slice 2. EncounterCompleteEvent (NEO-107) remains a future consumer hook. NEO-43 MissionRewardSkillXpGrant is the skill-XP apply path for bundle rows with sourceKind: mission_reward.
Upstream (must be landed): E7.M1 Ready (NEO-112–NEO-123); E2.M2 grant stack (NEO-38, NEO-43); E3.M3 inventory (NEO-54–NEO-55). NEO-122 quest HUD for client reward surfacing.
Prototype reward spine (frozen in E7M2-01): completionRewardBundle on each of the four frozen E7.M1 quests — quest completion only (not per-step). Grant kinds v1: itemGrants + skillXpGrants only. Solo only — per-character idempotent keys (quest_scope_and_party.md).
Linear issues (created): attach docs/plans/NEO-*-implementation-plan.md on the story branch when work starts.
| Slug | Layer | Linear |
|---|---|---|
| E7M2-01 | server | NEO-124 |
| E7M2-02 | server | NEO-125 |
| E7M2-03 | server | NEO-126 |
| E7M2-04 | server | NEO-127 |
| E7M2-05 | server | NEO-128 |
| E7M2-06 | server | NEO-129 |
| E7M2-07 | server | NEO-130 |
| E7M2-08 | client | NEO-131 |
| E7M2-09 | client | NEO-132 |
Dependency graph in Linear: E7M2-02 blocked by E7M2-01. E7M2-03 blocked by E7M2-01. E7M2-04 blocked by E7M2-02 and E7M2-03. E7M2-05 blocked by E7M2-04. E7M2-06 blocked by E7M2-05. E7M2-07 blocked by E7M2-05 (may parallel E7M2-06). E7M2-08 blocked by E7M2-06. E7M2-09 blocked by E7M2-08.
Board order: estimates 1–9 matching slug order (E7M2-01 = 1 … E7M2-09 = 9). On the Epic 7 project board, sort by Estimate (ascending).
Story order (recommended)
| Order | Slug | Layer | Depends on |
|---|---|---|---|
| 1 | E7M2-01 | server | E7.M1 quest catalog frozen |
| 2 | E7M2-02 | server | E7M2-01 |
| 3 | E7M2-03 | server | E7M2-01 |
| 4 | E7M2-04 | server | E7M2-02, E7M2-03 |
| 5 | E7M2-05 | server | E7M2-04 |
| 6 | E7M2-06 | server | E7M2-05 |
| 7 | E7M2-07 | server | E7M2-05 |
| 8 | E7M2-08 | client | E7M2-06 |
| 9 | E7M2-09 | client | E7M2-08 |
Downstream (separate modules): E6.M4 lint against bundles; E7.M3 reputation grants (pre-production); E5.M3 EncounterCompleteEvent full router consumer (optional follow-on).
Kickoff decisions (decomposition defaults)
| Topic | Decision | Rationale |
|---|---|---|
| Reward trigger | Quest completion only (not per-step) | Slice 2 AC is idempotent completion delivery; step bundles add scope without prototype need |
| Grant kinds (v1) | itemGrants + skillXpGrants (mission_reward at apply) |
NEO-43 prep; unlock/flag grants deferred |
| Unlock / world gates | Deferred to pre-production | Epic mentions unlocks; no prototype gate content yet — comment-only unlock_granted hook |
| Encounter loot | Unchanged — E5.M3 direct grants | Avoid breaking NEO-111 / NEO-123; EncounterCompleteEvent consumer is follow-on |
| Bundle attachment | completionRewardBundle on each frozen four quests |
Readable capstone; CI enforces shape on all E7.M1 ids |
| Idempotency key | {playerId}:quest_complete:{questId} |
quest_scope_and_party.md; once per character per quest |
| Delivery store | In-memory IRewardDeliveryStore (+ optional Postgres migration) |
Matches encounter/quest prototype stores |
| Fail-closed | Quest complete rolls back if bundle apply fails after mark (inventory full deny) | Mirror NEO-105 encounter completion compensating rollback |
| HTTP read | Extend GET …/quest-progress with completionRewardSummary per completed quest |
Mirror encounter rewardGrantSummary (NEO-108); no new top-level route in Slice 2 |
| Party mode | Solo only | E8.M1 not landed |
| HUD fidelity | Prototype labels / debug panels | NEO-122 / NEO-110 precedent |
Prototype completion bundles (E7M2-01 freeze — tunable display only)
| Quest id | Item grants | Skill XP grants |
|---|---|---|
prototype_quest_gather_intro |
— | salvage 25 (mission_reward) |
prototype_quest_refine_intro |
— | refine 25 (mission_reward) |
prototype_quest_combat_intro |
— | salvage 25 (mission_reward) |
prototype_quest_operator_chain |
survey_drone_kit ×1 |
salvage 50 (mission_reward) |
Combat intro bundle is skill XP only — encounter loot already granted contract_handoff_token via E5.M3 (no double item payout on same milestone).
E7M2-01 — QuestRewardBundle schema + quest catalog extension + CI
Goal: Lock completionRewardBundle content shape and CI validation on the four frozen quests before server load.
In scope
content/schemas/quest-reward-bundle.schema.json,content/schemas/quest-skill-xp-grant.schema.json(reuse or align with existing grant row patterns).- Extend
content/schemas/quest-def.schema.jsonwith optionalcompletionRewardBundle. - Update
content/quests/prototype_quests.json— bundles per freeze table above. scripts/validate_content.py: bundle schema validation, item id cross-refs, skill id cross-refs,mission_rewardallowlist alignment withprototype_skills.json.- Designer note in E7_M2 +
content/README.md.
Out of scope
- Server loader, delivery store, router, HTTP, Godot.
Acceptance criteria
- PR gate validates quest JSON including optional completion bundles.
- All four prototype quests include a
completionRewardBundlerow matching freeze table. - Invalid item/skill refs fail CI.
Client counterpart: none (infrastructure-only).
E7M2-02 — Server quest catalog load: reward bundle validation
Goal: Fail-fast startup validation of completionRewardBundle with CI parity.
In scope
- Extend
server/NeonSprawl.Server/Game/Quests/catalog loader / registry types. - Cross-check bundle grants against item + skill registries at startup.
- Unit tests (AAA) for happy path and broken bundle refs.
server/README.mdsection.
Out of scope
- Delivery apply, HTTP projection changes.
Acceptance criteria
- Host exits on invalid bundle refs at startup.
dotnet testcovers loader gates.
Client counterpart: none (infrastructure-only).
E7M2-03 — Reward delivery store (IRewardDeliveryStore)
Goal: Durable idempotency record for RewardDeliveryEvent per player + quest completion.
In scope
IRewardDeliveryStore, in-memory implementation, optional Postgres migration (NEO-29-style split).- Idempotency key
{playerId}:quest_complete:{questId}; store granted item + skill XP summary for audit/read projection. - Unit tests: first delivery applies; replay returns existing event without double-apply.
server/README.mdstore section.
Out of scope
- Router apply logic (E7M2-04), quest-state wiring (E7M2-05).
Acceptance criteria
TryRecordidempotent on same key.- Store readable summary for HTTP projection.
Client counterpart: none (infrastructure-only).
E7M2-04 — RewardRouterOperations (apply QuestRewardBundle)
Goal: Server-internal apply for item + skill XP completion bundles with structured outcomes.
In scope
RewardRouterOperations.TryDeliverQuestCompletioninserver/NeonSprawl.Server/Game/Quests/(orGame/Rewards/).- Item grants via
PlayerInventoryOperations; skill XP viaSkillProgressionGrantOperations.TryApplyGrantwith fixedmission_rewardsource kind (deny envelopes; not the fire-and-forgetMissionRewardSkillXpGrantwrapper). - Deny reason codes (inventory full, unknown item/skill, xp deny).
- Unit + integration tests (AAA): happy path, inventory deny, idempotent replay via store.
Out of scope
- Quest state transition hooks (E7M2-05), HTTP DTOs (E7M2-06).
Acceptance criteria
- Bundle rows apply atomically where possible; compensating rollback on partial failure before store record.
- Skill XP uses
mission_rewardsource kind only.
Client counterpart: none (server engine).
E7M2-05 — Wire router into QuestStateOperations.TryMarkComplete
Goal: First-time quest completion triggers bundle delivery exactly once.
In scope
- Call
RewardRouterOperationsfromTryMarkCompletesuccess path (after state commit or transactional equivalent per fail-closed decision). - Skip delivery on idempotent complete replay (already
Completedstatus). - Integration tests: complete gather intro → inventory/skills reflect bundle; replay → no duplicate grants.
- Bruno smoke in
bruno/neon-sprawl-server/quest-progress/(optional subfolder).
Out of scope
- Encounter completion path; step-complete bundles.
Acceptance criteria
- Completing a quest via objective wiring delivers bundle once.
- Idempotent complete does not double-grant items or XP.
Client counterpart: NEO-131 — HUD surfacing (blocked by E7M2-06).
E7M2-06 — Extend GET …/quest-progress with completionRewardSummary
Goal: Client-readable grant summary on completed quest rows (mirror encounter progress).
In scope
- Extend
QuestProgressApiDTOs + OpenAPI-stable JSON: per completed quest optionalcompletionRewardSummary(item lines + skill XP lines). - Populate from
IRewardDeliveryStore/ delivery event snapshot. - Integration tests + Bruno assertions on gather intro complete path.
server/README.mdquest-progress section update.
Out of scope
- Godot HUD (E7M2-08).
Acceptance criteria
- Completed quest rows include summary when delivery recorded.
not_started/activerows omit summary.
Client counterpart: NEO-131.
E7M2-07 — Reward telemetry hook sites (comment-only)
Goal: Anchor future E9.M1 catalog events without ingest.
In scope
- Comment-only
reward_deliveryinRewardRouterOperations(first-time success). - Comment-only
unlock_grantedstub (no runtime unlock apply in Slice 2). server/README.mdhook list.
Out of scope
- E9.M1 ingest, unlock engine.
Acceptance criteria
- Hook sites documented in README; no behavior change beyond comments.
Client counterpart: none (infrastructure-only).
E7M2-08 — Client quest completion reward HUD (Godot)
Goal: Player sees quest completion rewards in Godot without Bruno.
In scope
- Extend
quest_progress_client.gdparse forcompletionRewardSummary. QuestRewardDeliveryLabel(or extendQuestProgressLabel/quest_hud_controller.gd) — compact grant lines when quest transitions tocompleted.- Refresh on quest-progress GET after objective events (reuse NEO-122 triggers).
- GdUnit parse tests;
client/README.mdsection. docs/manual-qa/NEO-131.mdcomponent checklist.
Out of scope
- Final quest journal UI; reward VFX.
Acceptance criteria
- Completing a quest shows readable reward copy on HUD.
- Failed GET surfaces visible error (NEO-122 pattern).
Server counterpart: NEO-129.
E7M2-09 — Playable quest reward delivery capstone (Godot)
Goal: Prove Epic 7 Slice 2 acceptance in Godot: quest completion grants items + skill XP once; idempotent on replay.
In scope
docs/manual-qa/NEO-132.md: single-session capstone — extend NEO-123 flow or shorter reward-focused path; verifycompletionRewardSummary+ inventory/skills; Godot restart + duplicate complete attempt unchanged.client/README.md: End-to-end quest reward loop section; cross-links NEO-124–NEO-131.- Module alignment on completion: E7_M2, documentation_and_implementation_alignment.md, module_dependency_register.md.
Out of scope
- Encounter loot migration; faction reputation; Bruno-only proof.
Acceptance criteria
- Human completes
docs/manual-qa/NEO-132.mdwith server + client. - Epic 7 Slice 2 AC: idempotent reward delivery; replays cannot double-claim.
- Re-read epic_07 Slice 2 AC.
Client counterpart: this story is the Slice 2 client capstone.
Decomposition complete checklist
- Every player-visible AC has a
clientLinear issue (NEO-131, NEO-132) - No forbidden deferral language in backlog
- Capstone client story exists (E7M2-09 / NEO-132)
- Epic DoD updated for Godot verification (see epic_07 update)
- documentation_and_implementation_alignment.md E7.M2 row updated (decomposition pass)
- module_dependency_register.md E7.M2 note updated