133 lines
8.3 KiB
Markdown
133 lines
8.3 KiB
Markdown
# E2.M3 — MasteryAndPerkUnlocks
|
||
|
||
## Summary
|
||
|
||
| Field | Value |
|
||
|--------|--------|
|
||
| **Module ID** | E2.M3 |
|
||
| **Epic** | [Epic 2 — Skills and Progression Framework](../epics/epic_02_skills_and_progression.md) |
|
||
| **Linear (Epic 2 project)** | [Epic 2 — Classless Skill and Progression Framework](https://linear.app/neon-sprawl/project/epic-2-classless-skill-and-progression-framework-5200f84bf8b6) |
|
||
| **Stage target** | Pre-production |
|
||
| **Status** | Planned (see [dependency register](module_dependency_register.md)) |
|
||
|
||
## Linear backlog (Epic 2 Slice 4 — E2.M3)
|
||
|
||
Issues use label **`E2.M3`**. Dependency order: **NEO-45 → NEO-46 → NEO-47 → NEO-48**; **NEO-49** after **NEO-47** (may parallel **NEO-48**). Canonical story text: [E2M3-pre-production-backlog.md](../../plans/E2M3-pre-production-backlog.md).
|
||
|
||
| Order | Issue | Summary |
|
||
|------:|-------|---------|
|
||
| 1 | [NEO-45](https://linear.app/neon-sprawl/issue/NEO-45/e2m3-lock-prototype-salvage-mastery-catalog-schemas-ci) | Lock prototype **salvage** mastery catalog + schemas + CI |
|
||
| 2 | [NEO-46](https://linear.app/neon-sprawl/issue/NEO-46/e2m3-server-loads-mastery-catalog-at-startup-fail-fast) | Server loads mastery catalog at startup (fail-fast) |
|
||
| 3 | [NEO-47](https://linear.app/neon-sprawl/issue/NEO-47/e2m3-perk-unlock-engine-perkstate-persistence) | Perk unlock engine + `PerkState` persistence |
|
||
| 4 | [NEO-48](https://linear.app/neon-sprawl/issue/NEO-48/e2m3-perk-state-get-branch-selection-post-bruno) | Perk state GET + branch selection POST + Bruno |
|
||
| 5 | [NEO-49](https://linear.app/neon-sprawl/issue/NEO-49/e2m3-perk-unlock-telemetry-hook-sites) | `perk_unlock` telemetry hook sites |
|
||
|
||
`docs/plans/NEO-*-implementation-plan.md` files are added at story kickoff (per [planning-implementation-docs](../../../.cursor/rules/planning-implementation-docs.md)).
|
||
|
||
## Purpose
|
||
|
||
Mastery tracks and perk unlock state so **skill** builds have expressive identity beyond raw level curves from [E2.M2](E2_M2_XpAwardAndLevelEngine.md). **Gig** mastery/perks remain a **separate** system ([`docs/game-design/gigs.md`](../../game-design/gigs.md)).
|
||
|
||
## v1 design decisions (frozen for backlog)
|
||
|
||
| Topic | v1 choice |
|
||
|-------|-----------|
|
||
| Progress into mastery | **Skill level** from E2.M2 (`IPlayerSkillProgressionStore`) — no separate mastery XP bar |
|
||
| Branch model | One **`MasteryTrack`** per skill; **tiers** with **mutually exclusive** branch picks per tier |
|
||
| Respec | **Out of scope** (late-stage tuning per [`skills.md`](../../game-design/skills.md)) |
|
||
| Gig perks | **No** shared tree with gigs |
|
||
| Gameplay effects | **State + data only** in E2.M3 core; stat/yield hooks are optional follow-ups (e.g. E3.M1) |
|
||
|
||
## Data contract (schema)
|
||
|
||
| Artifact | Path |
|
||
|----------|------|
|
||
| **JSON Schema** | [`content/schemas/mastery-catalog.schema.json`](../../../content/schemas/mastery-catalog.schema.json) |
|
||
| **Prototype catalog** | [`content/mastery/prototype_salvage_mastery.json`](../../../content/mastery/prototype_salvage_mastery.json) |
|
||
|
||
**`MasteryTrack` fields (prototype):**
|
||
|
||
| Field | Rule |
|
||
|--------|------|
|
||
| **`skillId`** | Must exist in [E2.M1](E2_M1_SkillDefinitionRegistry.md) `SkillDef` catalog (`salvage` for flagship slice). |
|
||
| **`tiers`** | Ordered list; each tier has **`requiredLevel`** (skill level from E2.M2) and **`branches`**. |
|
||
| **`branches`** | Stable **`branchId`**; **mutually exclusive** pick per tier; each branch lists **`perkIds`** unlocked when chosen. |
|
||
|
||
**`PerkDef` fields (prototype, embedded or referenced in catalog):**
|
||
|
||
| Field | Rule |
|
||
|--------|------|
|
||
| **`id`** | Lowercase `snake_case`, **immutable** after ship. |
|
||
| **`displayName`** | Player-facing; may change without migrating unlock state. |
|
||
| **`effectKind`** | Optional stub string for future systems (gather yield, craft efficiency, etc.) — **no** gameplay apply in E2.M3 core stories. |
|
||
|
||
### Designer note: tiers, branches, and level gates
|
||
|
||
- **Level gates** come from E2.M2 only: when a player’s **skill level** reaches **`requiredLevel`**, that tier’s branches become **eligible** for selection (server-authoritative).
|
||
- **One branch per tier:** picking `scrap_efficiency` forecloses `bulk_haul` for that tier; perks on the chosen branch unlock; the other branch’s perks for that tier do **not** unlock.
|
||
- **Other skills** (`refine`, `intrusion`) have **no** mastery rows in the prototype catalog until a deliberate expansion issue relaxes CI.
|
||
- **Respec** is not implemented in v1 — document in content if a perk is “permanent for the character.”
|
||
|
||
## Prototype Slice 4 freeze — salvage flagship ([NEO-45](https://linear.app/neon-sprawl/issue/NEO-45))
|
||
|
||
The **first shipped mastery track** is **`salvage`** only:
|
||
|
||
| Tier | `requiredLevel` | Branches (pick one) | Tier-2 `perkId` (chosen branch only) |
|
||
|------|-----------------|---------------------|--------------------------------------|
|
||
| 1 | 2 | `scrap_efficiency` · `bulk_haul` | *(none — branch pick only)* |
|
||
| 2 | 4 | same branch ids | `salvage_scrap_efficiency_1` · `salvage_bulk_haul_1` |
|
||
|
||
**Rules:** Do **not** rename `branchId` / `perkId` values to “fix” wording—change `displayName` only, or add new ids and migrate content. Expanding mastery to **`refine`** / **`intrusion`** belongs in a new Linear issue once the catalog gate intentionally grows.
|
||
|
||
## Responsibilities
|
||
|
||
- Load and validate mastery catalog from data; expose track/perk metadata via `IMasteryCatalogRegistry` (name flexible).
|
||
- Persist **`PerkState`** per player: unlocked **`perkId`** set + chosen **`branchId`** per tier.
|
||
- Evaluate eligibility from **skill level**; apply branch selection; emit **`PerkUnlockEvent`** for telemetry and future hooks.
|
||
- Re-evaluate eligibility on **skill level-up** (after E2.M2 grants).
|
||
- Expose versioned HTTP read/write for perk state (prototype JSON v1).
|
||
|
||
## Key contracts
|
||
|
||
| Contract | Kind | Role |
|
||
|----------|------|------|
|
||
| `MasteryTrack` | Content | Per-skill tier/branch/perk tree. |
|
||
| `PerkDef` | Content | Stable perk metadata (+ optional `effectKind` stub). |
|
||
| `PerkState` | Server-internal + HTTP DTO | Unlocked perks + branch picks per tier. |
|
||
| `PerkUnlockEvent` | Server-internal | Fired when a perk becomes unlocked (UI/telemetry/craft gates later). |
|
||
|
||
## Module dependencies
|
||
|
||
- **E2.M2** — [XpAwardAndLevelEngine](E2_M2_XpAwardAndLevelEngine.md): skill **level** source for tier gates; level-up hook site for re-evaluation.
|
||
- **E2.M1** — [SkillDefinitionRegistry](E2_M1_SkillDefinitionRegistry.md): validate catalog `skillId` references.
|
||
|
||
## Dependents (by design)
|
||
|
||
- **E3.M1 / E3.M2** — Optional gates or modifiers on gather/craft when perk `effectKind` is wired (follow-up integration, not E2.M3 core).
|
||
- **E7.M2** — Mission rewards may reference perk prerequisites in content (future).
|
||
- Gameplay systems that gate recipes or abilities on **`perkId`** (integration per content).
|
||
|
||
## Acceptance criteria (E2.M3 / Slice 4)
|
||
|
||
- At least one **salvage** mastery path unlocks a perk **without** blocking progression in **`refine`** or **`intrusion`** (separate skills / tracks).
|
||
- Branch pick at tier 1 is **mutually exclusive**; server denies invalid or duplicate picks with stable **`reasonCode`** values.
|
||
- Catalog + perk state validated in CI or at server boot ([CT.M1](CT_M1_ContentValidationPipeline.md)).
|
||
- Telemetry hook sites documented for **`perk_unlock`** (no E9.M1 ingest until catalog exists).
|
||
|
||
## Related implementation slices
|
||
|
||
- **Epic 2 Slice 4** — E2.M3 (this module) + [E2.M4](E2_M4_ProgressionPacingControls.md) pacing (separate backlog; depends on E9.M2).
|
||
- **Epic 2 Slice 3** — skill XP integration remains on E2.M2; **NEO-44** (gig XP) is Epic 5, not E2.M3.
|
||
|
||
## Risks and telemetry
|
||
|
||
- **Risk:** Mastery feels mandatory for one skill and blocks alts — **Mitigation:** flagship is one skill; other prototype skills remain level-only until expanded.
|
||
- **Risk:** Branch picks without respec frustrate players — **Mitigation:** defer respec to late-stage tuning; keep v1 tree small.
|
||
- **Telemetry:** `perk_unlock` hook sites ([NEO-49](https://linear.app/neon-sprawl/issue/NEO-49)); align with [E9.M1](E9_M1_TelemetryEventSchema.md) when catalog exists.
|
||
|
||
## Source anchors
|
||
|
||
- Master plan: [`neon_sprawl_vision.plan.md`](../../../neon_sprawl_vision.plan.md) — Epic 2, E2.M3.
|
||
- Game design: [`docs/game-design/skills.md`](../../game-design/skills.md) — Mastery and perks.
|
||
- [Module dependency register](module_dependency_register.md)
|