# Manual QA — NEO-38 (skill XP grant) Reference: [implementation plan](../plans/NEO-38-implementation-plan.md), [server README](../../server/README.md#skill-progression-grant-neo-38). ## Preconditions - Run `NeonSprawl.Server` from `server/NeonSprawl.Server` (`dotnet run`; default `http://localhost:5253`). - Use a **known** player id (e.g. `dev-local-1`, present in position state). Use a shell variable so you only change the base URL once: ```bash BASE=http://localhost:5253 ID=dev-local-1 ``` ## Happy path — grant + read back 1. **Baseline read** (optional): note `salvage.xp` before the grant (usually **0** on a fresh store). ```bash curl -sS "${BASE}/game/players/${ID}/skill-progression" ``` 2. **Grant** — `schemaVersion` **1**, `skillId` **`salvage`**, `amount` **10**, `sourceKind` **`activity`**. ```bash curl -sS -i -X POST "${BASE}/game/players/${ID}/skill-progression" \ -H "Content-Type: application/json" \ -d '{"schemaVersion":1,"skillId":"salvage","amount":10,"sourceKind":"activity"}' ``` Expect **HTTP 200**, JSON `granted` **true**, `levelUps` may be empty if no level boundary crossed (10 XP stays at level 1 with the prototype curve). 3. **Read back**: `salvage.xp` in the snapshot should reflect the persisted total (baseline + **10**, or cumulative if you repeated the POST). ```bash curl -sS "${BASE}/game/players/${ID}/skill-progression" ``` ## Deny — source not allowed `skillId` **`salvage`** does **not** allow **`trainer`** in the prototype catalog; the server still returns **200** with structured deny. ```bash curl -sS -i -X POST "${BASE}/game/players/${ID}/skill-progression" \ -H "Content-Type: application/json" \ -d '{"schemaVersion":1,"skillId":"salvage","amount":10,"sourceKind":"trainer"}' ``` Expect **HTTP 200**, `granted` **false**, **`reasonCode`** **`source_kind_not_allowed`**, and `progression` present (snapshot unchanged except what other requests already applied). ## Bonus — observe a level-up in one response With the prototype curve `level = 1 + floor(xp / 100)`, a grant of **100** XP from **0** on `intrusion` should set `granted` **true** and include **`levelUps`** (`previousLevel` **1**, `newLevel` **2**). ```bash curl -sS -i -X POST "${BASE}/game/players/${ID}/skill-progression" \ -H "Content-Type: application/json" \ -d '{"schemaVersion":1,"skillId":"intrusion","amount":100,"sourceKind":"activity"}' ``` ## Bruno - Folder: `bruno/neon-sprawl-server/skill-progression/` - `Post skill progression grant.bru` — happy grant. - `Post skill progression grant deny source kind.bru` — `source_kind_not_allowed` on salvage + `trainer`. - `Get skill progression.bru` — confirms read model after grants.