# 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)
