# E2.M3 — Pre-production story backlog (MasteryAndPerkUnlocks) Working backlog for **Epic 2 — Slice 4** ([mastery and pacing](../decomposition/epics/epic_02_skills_and_progression.md#slice-4---mastery-and-pacing-pre-production)). Decomposition and contracts: [E2.M3 — MasteryAndPerkUnlocks](../decomposition/modules/E2_M3_MasteryAndPerkUnlocks.md). **Labels (Linear):** every issue **`E2.M3`**; add **`server`** / **`Feature`** as listed per issue. **Precursor (do not re-scope):** skill **level** authority lives in **E2.M2** ([E2.M2 module](../decomposition/modules/E2_M2_XpAwardAndLevelEngine.md)); `SkillDef` ids from **E2.M1**. **Gig** perks are **not** `SkillDef` mastery. **Flagship skill:** **`salvage`** (gather loop — [NEO-41](https://linear.app/neon-sprawl/issue/NEO-41)). **Linear issues (created):** attach `docs/plans/NEO-*-implementation-plan.md` on the same branch as implementation work. | Slug | Linear | |------|--------| | E2M3-01 | [NEO-45](https://linear.app/neon-sprawl/issue/NEO-45/e2m3-lock-prototype-salvage-mastery-catalog-schemas-ci) | | E2M3-02 | [NEO-46](https://linear.app/neon-sprawl/issue/NEO-46/e2m3-server-loads-mastery-catalog-at-startup-fail-fast) | | E2M3-03 | [NEO-47](https://linear.app/neon-sprawl/issue/NEO-47/e2m3-perk-unlock-engine-perkstate-persistence) | | E2M3-04 | [NEO-48](https://linear.app/neon-sprawl/issue/NEO-48/e2m3-perk-state-get-branch-selection-post-bruno) | | E2M3-05 | [NEO-49](https://linear.app/neon-sprawl/issue/NEO-49/e2m3-perk-unlock-telemetry-hook-sites) | **Dependency graph in Linear:** E2M3-02 blocked by E2M3-01. E2M3-03 blocked by E2M3-02 and [NEO-38](https://linear.app/neon-sprawl/issue/NEO-38). E2M3-04 blocked by E2M3-03. E2M3-05 blocked by E2M3-03 (may parallel E2M3-04). --- ## Story order (recommended) | Order | Slug | Depends on | |-------|------|------------| | 1 | **E2M3-01** | E2.M1 `salvage` `SkillDef` ([NEO-33](https://linear.app/neon-sprawl/issue/NEO-33)) | | 2 | **E2M3-02** | E2M3-01 | | 3 | **E2M3-03** | E2M3-02, [NEO-38](https://linear.app/neon-sprawl/issue/NEO-38) (skill level) | | 4 | **E2M3-04** | E2M3-03 | | 5 | **E2M3-05** | E2M3-03 (parallel OK with E2M3-04) | --- ### E2M3-01 — Prototype salvage mastery catalog + schemas + CI **Goal:** Lock content shape and CI validation for one **`salvage`** `MasteryTrack` before any server load. **In scope** - `content/schemas/mastery-catalog.schema.json` (or equivalent single-catalog schema). - `content/mastery/prototype_salvage_mastery.json` with tier 1 @ level 2 (`scrap_efficiency` vs `bulk_haul`) and tier 2 @ level 4 perks. - `scripts/validate_content.py` checks: unknown `skillId`, duplicate perk ids, branch integrity. - Module doc designer note (mutually exclusive branches; no respec v1). **Out of scope** - Server loader, perk state, HTTP, client HUD. **Acceptance criteria** - [x] PR gate validates mastery JSON against schema. (**Done** — NEO-45.) - [x] Only **`salvage`** has a track; `refine` / `intrusion` not required. (**Done** — NEO-45.) - [x] Stable `branchId` / `perkId` documented in [E2_M3](../decomposition/modules/E2_M3_MasteryAndPerkUnlocks.md) freeze box. (**Done** — NEO-45.) --- ### E2M3-02 — Server mastery catalog load (fail-fast) **Goal:** Disk → host: startup load of mastery catalog(s) with cross-check against `ISkillDefinitionRegistry`. **NEO-46 kickoff:** mirror CI gate constants from [NEO-45 implementation plan](NEO-45-implementation-plan.md) (see E2.M3 **NEO-46 handoff**). **In scope** - Loader + `IMasteryCatalogRegistry` under `server/NeonSprawl.Server/Game/Skills/` (or `Game/Mastery/` if split). - Fail-fast on malformed files, duplicate ids, unknown `skillId`. - Unit tests (AAA) for loader happy path and failure modes. **Out of scope** - Per-player state, unlock logic, HTTP. **Acceptance criteria** - [ ] Server refuses boot when catalog invalid (mirror E2.M1 catalog behavior). - [ ] Registry resolves track by `skillId` and perk metadata by `perkId`. --- ### E2M3-03 — Perk unlock engine + PerkState persistence **Goal:** Server-authoritative unlock evaluation from **skill level** + branch selection; persist `PerkState`; re-run on level-up. **In scope** - `IPlayerPerkStateStore` (in-memory + Postgres + migration), mirroring skill progression stores. - Unlock engine: tier eligibility, mutually exclusive branch pick, `PerkUnlockEvent` emission. - Hook after `SkillProgressionGrantOperations` (or dedicated service) on level-up for affected `skillId`. - Integration/unit tests (AAA): eligibility, select branch, deny invalid pick. **Out of scope** - HTTP API, Bruno, client UI. - Applying `effectKind` in gather/craft/combat. **Acceptance criteria** - [ ] Player at salvage level 2 can select one tier-1 branch; other branch’s tier-1 perks stay locked. - [ ] Level 4 unlocks tier-2 perks on the chosen branch path only. - [ ] Stable `reasonCode` on deny (unknown tier, branch already chosen, level too low, etc.). --- ### E2M3-04 — Perk state HTTP + Bruno **Goal:** Versioned **GET** snapshot and **POST** branch selection for manual QA and future client. **In scope** - `GET /game/players/{id}/perk-state` (path finalized in implementation plan). - `POST` branch selection endpoint with structured success/deny payloads. - Bruno `bruno/neon-sprawl-server/perk-state/`. - `server/README.md` section; API tests (AAA). **Out of scope** - Godot HUD — decompose per [full-stack epic decomposition](../../.cursor/rules/full-stack-epic-decomposition.md) (planned **E2 client slice**; skill/perk UI not covered by E3 Slice 5 alone). - Gameplay stat application. **Acceptance criteria** - [ ] GET returns unlocked perks + branch picks for prototype salvage track. - [ ] POST select branch persists and matches engine rules from E2M3-03. - [ ] Bruno exercises happy path + at least one deny case. --- ### E2M3-05 — `perk_unlock` telemetry hook sites **Goal:** Comment-only hooks in unlock / branch-select path for future E9.M1 catalog event **`perk_unlock`**. **In scope** - Hook placement in unlock engine (and POST path if applicable). - `TODO(E9.M1)` comments; no production logging. - README + module doc pointer. **Out of scope** - Telemetry ingest, dashboards. **Acceptance criteria** - [ ] Hook sites documented in code and `server/README.md`. - [ ] Matches Epic 2 Slice 4 telemetry vocabulary. --- ## After this backlog - Track delivery in Linear; keep `blockedBy` links synchronized if scope changes. - For each issue kickoff, add `docs/plans/{NEO-XX}-implementation-plan.md` per [story-kickoff](../../.cursor/rules/story-kickoff.md). - Add `docs/manual-qa/{NEO-XX}.md` when HTTP surfaces land (E2M3-04+). ## Related docs - [E2_M3_MasteryAndPerkUnlocks.md](../decomposition/modules/E2_M3_MasteryAndPerkUnlocks.md) - [E2_M2_XpAwardAndLevelEngine.md](../decomposition/modules/E2_M2_XpAwardAndLevelEngine.md) - [E2_M1_SkillDefinitionRegistry.md](../decomposition/modules/E2_M1_SkillDefinitionRegistry.md) - [epic_02_skills_and_progression.md](../decomposition/epics/epic_02_skills_and_progression.md) - [skills.md](../game-design/skills.md)