neon-sprawl/docs/manual-qa/NEO-29.md

3.2 KiB

NEO-29 — Manual QA checklist

Field Value
Key NEO-29
Title E1M4-01: HotbarLoadout v1 contract + baseline persistence path
Linear https://linear.app/neon-sprawl/issue/NEO-29/e1m4-01-hotbarloadout-v1-contract-baseline-persistence-path
Plan docs/plans/NEO-29-implementation-plan.md
Branch NEO-29-hotbarloadout-v1-contract-baseline-persistence-path

1) Setup sanity

  • Start server: cd server/NeonSprawl.Server && dotnet run.
  • Confirm health endpoint responds: curl -s http://localhost:5253/health.
  • Confirm baseline loadout for dev player is reachable: curl -s http://localhost:5253/game/players/dev-local-1/hotbar-loadout.

2) Happy path (bind and re-read)

  • Bind slot 0 + slot 3 in one request: curl -s -X POST http://localhost:5253/game/players/dev-local-1/hotbar-loadout -H "Content-Type: application/json" -d '{"schemaVersion":1,"slots":[{"slotIndex":0,"abilityId":"prototype_pulse"},{"slotIndex":3,"abilityId":"prototype_burst"}]}'
  • Verify response contains updated=true and loadout.slots[0].abilityId == "prototype_pulse" and loadout.slots[3].abilityId == "prototype_burst".
  • Run GET again and verify the same two bindings are still present.

3) Reconnect / persistence behavior

  • If Postgres is configured (ConnectionStrings__NeonSprawl set), stop and restart the server, then GET loadout and verify slot 0/3 bindings survived restart.
  • If Postgres is not configured (in-memory fallback), verify loadout behavior in the same process is stable and document that restart reset is expected for this mode.

4) Deny paths and reason codes

  • Send out-of-bounds slot (slotIndex: 8) and confirm response has updated=false with reasonCode == "slot_out_of_bounds".
  • Send unknown ability id (abilityId: "not_real") and confirm response has updated=false with reasonCode == "unknown_ability".
  • Send duplicate slot index entries in one POST and confirm response has updated=false with reasonCode == "duplicate_slot".

5) Client hydration sanity

  • Run client/scenes/main.tscn in Godot after slot bindings exist on server.
  • Confirm no startup warnings from HotbarLoadoutClient and verify HotbarState node exists under the scene root with 8-slot mirror populated from server response.

6) Client-driven update sanity (no curl)

  • With main.tscn running, open the remote SceneTree and select HotbarLoadoutClient under the scene root.
  • Execute request_bind_slot(2, "prototype_guard") on that node (Editor script console or remote call).
  • Confirm no client warning log from HotbarLoadoutClient and verify HotbarState.slots_snapshot() now contains "prototype_guard" at index 2.
  • Restart the client scene and verify hydration still reflects slot 2 from server (proves client fetch + apply path, not just local mutation).

7) Client deny-path signal sanity

  • With main.tscn running, execute request_bind_slot(2, "not_real") on HotbarLoadoutClient.
  • Confirm the client logs/handles a deny response (server updated=false, reasonCode="unknown_ability"), and HotbarState remains unchanged from the last valid loadout.