165 lines
7.0 KiB
Markdown
165 lines
7.0 KiB
Markdown
# 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)
|