# 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`.
