# Manual QA — NEO-48 (perk state GET + branch selection POST) Reference: [implementation plan](../plans/NEO-48-implementation-plan.md), [NEO-47 plan](../plans/NEO-47-implementation-plan.md) (unlock engine). ## Preconditions - Run `NeonSprawl.Server` (`dotnet run` from `server/NeonSprawl.Server`; default `http://localhost:5253`). - Dev player `dev-local-1` exists in position state (default in-memory / seeded Postgres). ## GET empty state ```bash curl -sS "http://localhost:5253/game/players/dev-local-1/perk-state" | jq . ``` - [ ] HTTP **200**; `schemaVersion` **1**; `branchPicks` **[]**; `unlockedPerkIds` **[]** (fresh server). ## POST tier-1 pick (happy path) 1. Grant salvage XP to reach level **2** (prototype curve: **100** total XP): ```bash curl -sS -X POST "http://localhost:5253/game/players/dev-local-1/skill-progression" \ -H "Content-Type: application/json" \ -d '{"schemaVersion":1,"skillId":"salvage","amount":100,"sourceKind":"activity"}' | jq . ``` 2. Select tier-1 branch: ```bash curl -sS -X POST "http://localhost:5253/game/players/dev-local-1/perk-state" \ -H "Content-Type: application/json" \ -d '{"schemaVersion":1,"skillId":"salvage","tierIndex":1,"branchId":"scrap_efficiency"}' | jq . ``` - [ ] `selected: true`; `perkState.branchPicks` contains salvage tier 1 `scrap_efficiency`; `unlockedEvents` empty at tier 1. - [ ] Repeat **GET** — same pick visible. ## POST deny (mastery fixture reset) Requires **`Game:EnableMasteryFixtureApi`** (on in Development) or Development environment. ```bash # Reset dev-local-1 to salvage level 1, no perks curl -sS -X POST "http://localhost:5253/game/players/dev-local-1/__dev/mastery-fixture" \ -H "Content-Type: application/json" \ -d '{"schemaVersion":1,"resetPerkState":true,"skillXp":{"salvage":0}}' | jq . curl -sS -X POST "http://localhost:5253/game/players/dev-local-1/perk-state" \ -H "Content-Type: application/json" \ -d '{"schemaVersion":1,"skillId":"salvage","tierIndex":1,"branchId":"scrap_efficiency"}' | jq . # expect level_too_low ``` - [ ] Fixture reset + branch POST → `level_too_low`. - [ ] Fixture reset with `skillXp.salvage: 100`, pick `scrap_efficiency`, POST `bulk_haul` → **`branch_already_chosen`**. ## Bruno - [ ] Run folder `bruno/neon-sprawl-server/perk-state/` (GET + POST happy + deny smoke). - [ ] Run **`Post branch select deny branch already chosen`** (self-contained; pre-request grant + first pick). ## Regression ```bash cd server && dotnet test NeonSprawl.sln ``` Expect all tests pass including `PerkStateApiTests`.