7.3 KiB
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-38 — done on main (grant stack + allowedXpSourceKinds). E7.M2 RewardAndUnlockRouter — design 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 skill’s 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.TryApplyGrantwith caller-suppliedskillId/amountand fixedmission_rewardsourceKind. - Tests: happy path (grant applies when allowlist permits); deny path when
mission_reward∉allowedXpSourceKindsfor the chosen skill (stub registry pattern aligned with NEO-41/NEO-42 denied factories). - Docs: README subsection +
docs/manual-qa/NEO-43.md(verify viaPOST …/skill-progressionwithmission_rewardand via helper in a test host, same spirit as NEO-42).
Out of scope:
- Full
QuestRewardBundleschema, idempotentRewardDeliveryEvent, 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 documentsPOST …/skill-progressionwithmission_rewardfor 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.MissionRewardDeniedRegistryWebApplicationFactory—salvagestub omitsmission_reward(deny coverage); happy paths use disk catalog viaInMemoryWebApplicationFactory.
Technical approach
-
Add
MissionRewardSkillXpConstants(or equivalent single type) holding the canonicalmission_rewardstring used by the helper (avoid string drift; matchprototype_skills.json/ NEO-38 docs). -
Add
MissionRewardSkillXpGrant(static class, parallel toRefineActivitySkillXpGrant):- Method e.g.
GrantFromMissionReward(playerId, skillId, amount, registry, xpStore, levelCurve)callingTryApplyGrantwithMissionRewardSkillXpConstants.SourceKind. - Caller contract:
playerIdnormalized 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.
- Method e.g.
-
Tests — factory with registry where a prototype skill excludes
mission_rewardfor deny coverage; in-memory progression store; assert snapshot / store deltas consistent withRefineActivitySkillXpGrantTestsstyle. -
Documentation —
server/README.md: short “Mission / quest skill XP (NEO-43)” subsection: E7.M2 should callMissionRewardSkillXpGrant; until then verifymission_rewardviaPOST …/skill-progression; linkdocs/manual-qa/NEO-43.md. -
Decomposition anchor — one sentence in
docs/decomposition/modules/E7_M2_RewardAndUnlockRouter.md(ordocumentation_and_implementation_alignment.mdif 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_reward ∈ allowedXpSourceKinds 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).