neon-sprawl/docs/plans/NEO-43-implementation-plan.md

7.7 KiB
Raw Permalink Blame History

NEO-43 — Implementation plan

Story reference

Field Value
Key NEO-43
Title Mission / quest rewards grant skill XP (sourceKind: mission_reward)
Linear https://linear.app/neon-sprawl/issue/NEO-43/mission-quest-rewards-grant-skill-xp-sourcekind-mission-reward
Labels E7.M2, E2.M2, server, Story
Blocked by (Linear text) NEO-38done on main (grant stack + allowedXpSourceKinds). E7.M2 RewardAndUnlockRouterdesign only at kickoff; no quest hand-in / QuestRewardBundle code in server/ yet.

Kickoff clarifications

Topic Question / note Resolution
Delivery before E7.M2 router E7.M2 router does not exist in repo; how to ship NEO-43? User (implementation): accepted agent recommendation — NEO-42-style prep: MissionRewardSkillXpGrant + tests + README + manual QA; E7.M2 wires the real quest hand-in later.
Reward shape Fixed prototype vs generic skill + amount? User (implementation): accepted agent recommendation — generic skillId + amount; helper fixes sourceKind: mission_reward.
Combat encounters ≠ skill XP No encounter/gig XP code under server/ today. User (implementation): accepted agent recommendation — README + manual QA + plan document the invariant and current call sites; no synthetic encounter guard test until encounter code exists.

Goal, scope, and out-of-scope

Goal: When E7.M2 (or any scripted mission/quest hand-in) applies explicit skill XP from a mission reward channel, route the grant through E2.M2 / NEO-38 using sourceKind: mission_reward, validated against the target skills allowedXpSourceKinds. Not the combat encounter pipe (progression.md Mission rewards vs combat XP).

In scope (from Linear + defaults above):

  • Preparatory (this slice): one server-side helper that delegates to SkillProgressionGrantOperations.TryApplyGrant with caller-supplied skillId / amount and fixed mission_reward sourceKind.
  • Tests: happy path (grant applies when allowlist permits); deny path when mission_rewardallowedXpSourceKinds for the chosen skill (stub registry pattern aligned with NEO-41/NEO-42 denied factories).
  • Docs: README subsection + docs/manual-qa/NEO-43.md (verify via POST …/skill-progression with mission_reward and via helper in a test host, same spirit as NEO-42).

Out of scope:

  • Full QuestRewardBundle schema, idempotent RewardDeliveryEvent, inventory/unlocks (E7_M2_RewardAndUnlockRouter.md backlog).
  • Combat encounter implementation or gig XP awards.
  • Batch mission grants in one HTTP call (can follow once router batches rewards).

Acceptance criteria checklist

  • Scripted mission rewards can include skill XP; validated against allowedXpSourceKinds (helper + tests; manual QA documents POST …/skill-progression with mission_reward for skills that allow it).
  • Combat encounters do not grant skill XP by default — documented invariant + current codebase has no encounter grant path; gig XP remains separate (design reference: progression.md, E2_M1).

Decisions (implementation)

  • MissionRewardSkillXpConstants.MissionRewardSourceKind — single canonical string for the helper and docs.
  • MissionRewardDeniedRegistryWebApplicationFactorysalvage stub omits mission_reward (deny coverage); happy paths use disk catalog via InMemoryWebApplicationFactory.
  • Code review follow-up (2026-05-15): documentation_and_implementation_alignment.md E2.M2 row lists NEO-43 landed and Slice 3 still openNEO-44 only; E7_M2_RewardAndUnlockRouter.md Summary Status notes NEO-43 prep; NEO-43-implementation-plan.md technical approach uses MissionRewardSkillXpConstants.MissionRewardSourceKind (matches code).

Technical approach

  1. Add MissionRewardSkillXpConstants (or equivalent single type) holding the canonical mission_reward string used by the helper (avoid string drift; match prototype_skills.json / NEO-38 docs).

  2. Add MissionRewardSkillXpGrant (static class, parallel to RefineActivitySkillXpGrant):

    • Method e.g. GrantFromMissionReward(playerId, skillId, amount, registry, xpStore, levelCurve) calling TryApplyGrant with MissionRewardSkillXpConstants.MissionRewardSourceKind.
    • Caller contract: playerId normalized like other grant hooks; amount > 0 (delegate invalid amount to existing deny behavior); outcome may be ignored on “fire and forget” success paths or inspected when E7.M2 needs telemetry/deny handling.
  3. Tests — factory with registry where a prototype skill excludes mission_reward for deny coverage; in-memory progression store; assert snapshot / store deltas consistent with RefineActivitySkillXpGrantTests style.

  4. Documentationserver/README.md: short “Mission / quest skill XP (NEO-43)” subsection: E7.M2 should call MissionRewardSkillXpGrant; until then verify mission_reward via POST …/skill-progression; link docs/manual-qa/NEO-43.md.

  5. Decomposition anchor — one sentence in docs/decomposition/modules/E7_M2_RewardAndUnlockRouter.md (or documentation_and_implementation_alignment.md if E7.M2 file should stay design-pure): server prep hook name + NEO-43 plan link.

Files to add

Path Purpose
server/NeonSprawl.Server/Game/Skills/MissionRewardSkillXpConstants.cs Canonical mission_reward source kind string for mission/quest grant path.
server/NeonSprawl.Server/Game/Skills/MissionRewardSkillXpGrant.cs Static helper delegating to SkillProgressionGrantOperations.TryApplyGrant with fixed mission_reward.
server/NeonSprawl.Server.Tests/Game/Skills/MissionRewardSkillXpGrantTests.cs Happy path + GET snapshot alignment (optional mirror NEO-42); deny when mission_reward not allowed.
server/NeonSprawl.Server.Tests/Game/Skills/MissionRewardDeniedRegistryWebApplicationFactory.cs WebApplicationFactory with stub ISkillDefinitionRegistry where target skill omits mission_reward from allowedXpSourceKinds.
docs/manual-qa/NEO-43.md curl/Bruno order: grant via POST …/skill-progression with mission_reward + note future E7.M2 calls helper.

Files to modify

Path Rationale
server/README.md Document NEO-43 mission-reward skill XP hook and verification until E7.M2 wires quest hand-in.
docs/decomposition/modules/E7_M2_RewardAndUnlockRouter.md Implementation anchor: which server helper to call when applying skill XP from QuestRewardBundle / mission payout.

Tests

Path Coverage
server/NeonSprawl.Server.Tests/Game/Skills/MissionRewardSkillXpGrantTests.cs Granted: helper applies XP when mission_rewardallowedXpSourceKinds for chosen skillId; optional round-trip GET …/skill-progression. Denied: stub registry without mission_reward on target skill → no XP delta; no throw.
Existing SkillProgressionGrantApiTests.cs Regression only if POST path or shared types change during implementation (likely unchanged).

Open questions / risks

None. Risk: Low — additive surface; helper unreferenced until E7.M2 (same pattern as NEO-42).