neon-sprawl/docs/plans/E7M2-prototype-backlog.md

17 KiB
Raw Permalink Blame History

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-112NEO-123); E2.M2 grant stack (NEO-38, NEO-43); E3.M3 inventory (NEO-54NEO-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 questsquest 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 19 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): E7.M3 Slice 3 backlog E7M3-pre-production-backlog.md (NEO-133NEO-143); E8.M1 party-credit quests.


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.json with optional completionRewardBundle.
  • 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_reward allowlist alignment with prototype_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 completionRewardBundle row 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.md section.

Out of scope

  • Delivery apply, HTTP projection changes.

Acceptance criteria

  • Host exits on invalid bundle refs at startup.
  • dotnet test covers 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.md store section.

Out of scope

  • Router apply logic (E7M2-04), quest-state wiring (E7M2-05).

Acceptance criteria

  • TryRecord idempotent 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.TryDeliverQuestCompletion in server/NeonSprawl.Server/Game/Quests/ (or Game/Rewards/).
  • Item grants via PlayerInventoryOperations; skill XP via SkillProgressionGrantOperations.TryApplyGrant with fixed mission_reward source kind (deny envelopes; not the fire-and-forget MissionRewardSkillXpGrant wrapper).
  • 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_reward source 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 RewardRouterOperations from TryMarkComplete success path (after state commit or transactional equivalent per fail-closed decision).
  • Skip delivery on idempotent complete replay (already Completed status).
  • 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 QuestProgressApi DTOs + OpenAPI-stable JSON: per completed quest optional completionRewardSummary (item lines + skill XP lines).
  • Populate from IRewardDeliveryStore / delivery event snapshot.
  • Integration tests + Bruno assertions on gather intro complete path.
  • server/README.md quest-progress section update.

Out of scope

  • Godot HUD (E7M2-08).

Acceptance criteria

  • Completed quest rows include summary when delivery recorded.
  • not_started / active rows 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_delivery in RewardRouterOperations (first-time success).
  • Comment-only unlock_granted stub (no runtime unlock apply in Slice 2).
  • server/README.md hook list.

Out of scope

  • E9.M1 ingest, unlock engine.

Acceptance criteria

  • Hook sites documented in README; no behavior change beyond comments.

Landed (NEO-130): comment-only reward_delivery + unlock_granted stub in RewardRouterOperations; server README — Reward telemetry hooks (NEO-130); plan NEO-130.

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.gd parse for completionRewardSummary.
  • QuestRewardDeliveryLabel (or extend QuestProgressLabel / quest_hud_controller.gd) — compact grant lines when quest transitions to completed.
  • Refresh on quest-progress GET after objective events (reuse NEO-122 triggers).
  • GdUnit parse tests; client/README.md section.
  • docs/manual-qa/NEO-131.md component 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).

Landed (NEO-131): QuestRewardDeliveryLabel + quest_hud_controller.gd transition detection for completionRewardSummary; client README — Quest completion reward HUD (NEO-131); plan NEO-131; manual QA NEO-131.

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

Out of scope

  • Encounter loot migration; faction reputation; Bruno-only proof.

Acceptance criteria

  • Human completes docs/manual-qa/NEO-132.md with server + client.
  • Epic 7 Slice 2 AC: idempotent reward delivery; replays cannot double-claim.
  • Re-read epic_07 Slice 2 AC.

Landed (NEO-132): capstone manual QA NEO-132; client README — End-to-end quest reward loop (NEO-132); economy HUD refresh on quest completion transition; plan NEO-132.

Client counterpart: this story is the Slice 2 client capstone.


Decomposition complete checklist