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

84 lines
6.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# NEO-42 — Implementation plan
## Story reference
| Field | Value |
|--------|--------|
| **Key** | NEO-42 |
| **Title** | Craft / refine awards skill XP (sourceKind: activity) |
| **Linear** | https://linear.app/neon-sprawl/issue/NEO-42/craft-refine-awards-skill-xp-sourcekind-activity |
| **Labels** | E3.M2, E2.M2, server |
| **Blocked by (Linear)** | [NEO-38](https://linear.app/neon-sprawl/issue/NEO-38) (grant path — **done** on `main`); **E3.M2** recipe/refine execution (not present in repo at kickoff). |
## Kickoff clarifications
| Topic | Question / note | Resolution |
|--------|-----------------|------------|
| **Delivery shape** | User asked what to recommend until E3.M2 HTTP/recipe loop exists. | **Agent recommendation (accepted unless you object in chat):** add **`RefineSkillXpConstants`** + a thin **`RefineActivitySkillXpGrant`** (or equivalent) that delegates to **`SkillProgressionGrantOperations.TryApplyGrant`** — **no** new public craft HTTP in NEO-42. E3.M2 calls this helper from the real **`CraftResult`** success path when that module lands. |
| **Prototype XP amount** | User asked what to recommend. | **Agent recommendation:** **10** XP per successful craft/refine completion for the prototype, matching **[NEO-41](NEO-41-implementation-plan.md)** gather constant pattern; replace with recipe-driven amounts in a later issue when content exists. |
| **“Craft” vs “refine” skill id** | `content/skills/prototype_skills.json` defines **`refine`** (process) with **`activity`** in `allowedXpSourceKinds`; there is no separate **`craft`** skill row today. | **Prototype mapping:** both **recipe craft** and **refine** success paths call the helper with **`skillId: refine`** and **`sourceKind: activity`** so allowlist enforcement matches catalog. If a dedicated craft skill is added later, E3.M2 can call the same helper with a different `skillId` per recipe metadata. |
## Goal, scope, and out-of-scope
**Goal:** When **E3.M2** reports a **successful** craft or refine completion, award **refine** skill XP through the **NEO-38** grant stack (`TryApplyGrant` → registry allowlist → store → level curve) with **`sourceKind: activity`** wherever **`activity`** is listed on the target skills **`allowedXpSourceKinds`**.
**In scope (from Linear):**
- Wire (or **prepare the single call site** for) craft/refine completion → **`SkillProgressionGrantOperations.TryApplyGrant`** with **`activity`**.
- **Respect catalog allowlists** (no grant when `activity` is disallowed — mirror NEO-41 defensive tests).
**Out of scope:**
- Owning the full **E3.M2** recipe validation, inventory mutations, or new public craft routes (separate story/module).
- **Recipe-driven XP amounts** and multi-skill mapping from **`RecipeDef`** (follow-up once execution + content exist).
- **Telemetry** (`NEO-40`) — comment-only hook sites already noted on grant path.
## Acceptance criteria checklist
- [x] **Preparatory:** **`RefineActivitySkillXpGrant`** invokes the **NEO-38** path (**`TryApplyGrant`**, same stack as **`POST …/skill-progression`**). **Follow-up:** **E3.M2** success handler must call **`GrantOnSuccessfulCraftOrRefine`** (documented in **`server/README.md`** + manual QA); no craft route in repo yet.
- [x] Grant channels respect **skill catalog** `allowedXpSourceKinds` (stub registry test: **`refine`** without **`activity`** → no XP).
## Decisions (implementation)
- **`RefineActivityDeniedRegistryWebApplicationFactory`** added for allowlist-deny coverage (parallel to NEO-41 salvage-denied factory).
## Technical approach
1. **Constants** — Add `RefineSkillXpConstants` (parallel to `GatherSkillXpConstants`): **`refine`** skill id, **`activity`** source kind, **10** XP per completion (name clearly scoped to prototype craft/refine success).
2. **Shared grant helper** — Add a small static type (e.g. `RefineActivitySkillXpGrant`) whose sole job is to invoke **`SkillProgressionGrantOperations.TryApplyGrant`** with those constants. **Discard** the outcome on success paths intended to mirror gather (UI reads **`GET …/skill-progression`**); callers that need structured denies can call **`TryApplyGrant`** directly later.
3. **E3.M2 integration** — When **`CraftResult`** (or equivalent) success is finalized in server code, add **one call** to the helper with `playerId` + injected `ISkillDefinitionRegistry`, `IPlayerSkillProgressionStore`, `ISkillLevelCurve`. If E3.M2 is still absent when NEO-42 implements (1)(2), document the **exact line / type** in this plans **Decisions** after the merge that adds the success handler.
4. **Tests** — (a) Happy path: grant applies **refine** XP with **`activity`** when catalog allows it. (b) Deny path: stub registry where **`refine`** disallows **`activity`** → helper run leaves **0** XP (same spirit as `SalvageActivityDeniedRegistryWebApplicationFactory` in NEO-41). Use **in-memory** store + real or test JSON catalog + placeholder level curve; **no** dependency on craft HTTP.
5. **Manual QA** — If only (1)(2) ship before E3.M2: **`docs/manual-qa/NEO-42.md`** can note “verify via unit tests + temporary `POST …/skill-progression` control grant for `refine`/`activity`”; once E3.M2 exists, extend checklist to **Bruno** craft success + **GET** progression.
## Files to add
| Path | Purpose |
|------|---------|
| `server/NeonSprawl.Server/Game/Skills/RefineSkillXpConstants.cs` | NEO-42 prototype constants: `refine`, `activity`, flat XP amount. |
| `server/NeonSprawl.Server/Game/Skills/RefineActivitySkillXpGrant.cs` | Single entry point calling `TryApplyGrant` for successful craft/refine (prototype). |
| `server/NeonSprawl.Server.Tests/Game/Skills/RefineActivitySkillXpGrantTests.cs` | AAA tests: grant when allowed; no XP when `activity` not in allowlist for `refine`. |
| `server/NeonSprawl.Server.Tests/Game/Skills/RefineActivityDeniedRegistryWebApplicationFactory.cs` | Test host with **`refine`** catalog row that omits **`activity`**. |
| `docs/manual-qa/NEO-42.md` | Curl checklist + E3.M2 follow-up note. |
## Files to modify
| Path | Rationale |
|------|-----------|
| `server/README.md` | Document NEO-42 hook type and E3.M2 integration expectation. |
| **E3.M2 craft success handler** (path TBD) | Call **`RefineActivitySkillXpGrant.GrantOnSuccessfulCraftOrRefine`** once on successful craft/refine — **not done in this PR** (module absent). |
## Tests
| Test file | What it covers |
|-----------|------------------|
| `RefineActivitySkillXpGrantTests.cs` | **Arrange / Act / Assert:** successful grant increases **`refine`** XP via shared path; registry without **`activity`** for **`refine`** yields **no** XP delta (allowlist respected). |
## Open questions / risks
- **Merge ordering:** If NEO-42 merges **before** E3.M2, acceptance checkbox “craft/refine completion” is only fully satisfied after a tiny follow-up commit adds the call in the new success handler — track in Linear or a child issue if needed.
- **XP balance:** Flat **10** is a placeholder; design may change before public playtests.