NEO-43: add implementation plan (kickoff)
parent
66d4c7a3f2
commit
48c59d4560
|
|
@ -0,0 +1,83 @@
|
||||||
|
# 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](https://linear.app/neon-sprawl/issue/NEO-38) — **done** on `main` (grant stack + `allowedXpSourceKinds`). **E7.M2** [RewardAndUnlockRouter](../decomposition/modules/E7_M2_RewardAndUnlockRouter.md) — **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? | **AskQuestion** offered options; **user skipped** — **default (agent):** mirror **[NEO-42](NEO-42-implementation-plan.md)** — add a **thin static helper** + **xUnit coverage** + **README / manual QA** documenting the contract; **E7.M2** calls the helper from the real quest/mission reward apply path when that module lands. |
|
||||||
|
| **Reward shape** | Fixed prototype vs generic skill + amount? | **Default:** **generic** parameters **`skillId`** + **`amount`** (positive int) with **`sourceKind`** fixed to catalog value **`mission_reward`** (matches future explicit reward line items; content chooses skill and payout). |
|
||||||
|
| **Combat encounters ≠ skill XP** | No encounter/gig XP code under `server/` today. | **Default:** satisfy AC by **(1)** plan + README stating the invariant; **(2)** confirming current grant call sites are **interact (gather)**, **refine prep (NEO-42)**, and **`POST …/skill-progression`** only — **no** combat encounter pipe. Future **E5 / encounter reward** work must **not** call `SkillProgressionGrantOperations` for default encounter outcomes (gig XP remains separate per [progression.md](../game-design/progression.md)). Optional follow-up: architecture test or doc lint if the team wants a machine-enforced guard once 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](../game-design/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_reward`** ∉ **`allowedXpSourceKinds`** 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](../decomposition/modules/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](../game-design/progression.md), [E2_M1](../decomposition/modules/E2_M1_SkillDefinitionRegistry.md)).
|
||||||
|
|
||||||
|
## 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.SourceKind`**.
|
||||||
|
- **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. **Documentation** — **`server/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_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
|
||||||
|
|
||||||
|
- **User override:** If you want a **fixed prototype** reward (single skill + constant XP) instead of **generic skillId + amount**, say so before implementation — the helper signature and tests adjust trivially.
|
||||||
|
- **Risk:** Low — additive surface; same persistence and validation as NEO-38; helper **unreferenced** until E7.M2 (same pattern as NEO-42).
|
||||||
Loading…
Reference in New Issue