neon-sprawl/docs/plans/NEO-29-implementation-plan.md

8.9 KiB

NEO-29 — Implementation plan

Story reference

Field Value
Key NEO-29
Title E1M4-01: HotbarLoadout v1 contract + baseline persistence path
Linear NEO-29
Slug E1M4-01
Git branch NEO-29-hotbarloadout-v1-contract-baseline-persistence-path
Parent context Epic 1 — Core Player Runtime · E1M4 prototype backlog
Decomposition E1.M4 — AbilityInputScaffold, E1.M3 — InteractionAndTargetingLayer, E5.M1 — CombatRulesEngine

Kickoff clarifications

  • Q1 (blocking): What should prototype HotbarLoadout scope be keyed to (player, character, or session)?
  • Answer: Per player id for the prototype.
  • Q2 (blocking): What baseline persistence backend should NEO-29 use?
  • Answer: Mirror position persistence pattern: use Postgres when ConnectionStrings:NeonSprawl exists, otherwise in-memory fallback.
  • Q3 (blocking): What fixed slot count should v1 expose?
  • Answer: 8 slots.

Goal, scope, and out-of-scope

Goal: Define and ship a prototype HotbarLoadout v1 read/write contract with server-owned validation and persistence, then hydrate client hotbar state from that server contract on boot/reconnect.

In scope

  • New server API surface for loadout fetch + update with explicit slotIndex and abilityId.
  • Validation denies for out-of-bounds slots and unknown ability ids with stable machine-readable reasonCode.
  • Prototype persistence policy documentation and implementation: per-player loadout, Postgres when configured, in-memory fallback otherwise.
  • Client bootstrap path to fetch and apply hotbar loadout state.

Out of scope

  • Final account-vs-character policy hardening beyond this prototype decision.
  • Cast execution, cooldown timing, and full combat authorization flow (follow-up stories in E1.M4 / E5.M1).

Acceptance criteria checklist

  • A player can bind at least two slots and retrieve the same bindings after reconnect/reload.
  • Invalid slot/ability input is rejected with a stable machine-readable reason code.
  • Module docs and this plan describe the chosen prototype persistence policy.

Technical approach

  1. Add a dedicated HotbarLoadout v1 API to the server with GET (current loadout) and POST (replace/update slots) routes under /game/players/{id}/hotbar-loadout.
  2. Keep the response shape versioned (schemaVersion) and explicit per-slot entries (slotIndex, abilityId) so E1.M4 follow-up stories can extend without breaking the v1 envelope.
  3. Validate slot index range against fixed v1 slot count (8) and validate abilityId against a prototype server-side registry/list; return deterministic reasonCode values on deny (for example slot_out_of_bounds, unknown_ability).
  4. Mirror PositionState persistence wiring: add an abstraction with in-memory and Postgres implementations selected from configuration, so reconnect/reload behavior is stable in both local-no-db and db-backed runs.
  5. Hydrate the client hotbar model on startup/reconnect by requesting the loadout from server, then applying slot->ability bindings to local state without introducing cast execution behavior in this story.
  6. Update decomposition/module docs and server/client README notes so the prototype persistence choice (per-player + Postgres-or-memory) is explicit and discoverable.

Files to add

Path Rationale
server/NeonSprawl.Server/Game/AbilityInput/HotbarLoadoutDtos.cs New v1 DTO contracts for loadout read/write API payloads.
server/NeonSprawl.Server/Game/AbilityInput/HotbarLoadoutApi.cs New route mapping and validation/deny response logic for loadout endpoints.
server/NeonSprawl.Server/Game/AbilityInput/IPlayerHotbarLoadoutStore.cs Persistence abstraction to mirror existing position store pattern.
server/NeonSprawl.Server/Game/AbilityInput/InMemoryPlayerHotbarLoadoutStore.cs In-memory fallback implementation for prototype/dev without DB.
server/NeonSprawl.Server/Game/AbilityInput/PostgresPlayerHotbarLoadoutStore.cs Postgres-backed persistence path when connection string is configured.
server/NeonSprawl.Server/Game/AbilityInput/PostgresHotbarLoadoutBootstrap.cs One-time migration bootstrap for hotbar table DDL.
server/NeonSprawl.Server/Game/AbilityInput/HotbarLoadoutServiceCollectionExtensions.cs DI registration and backend selection wiring.
server/NeonSprawl.Server/Game/AbilityInput/PrototypeAbilityRegistry.cs Canonical prototype ability-id allowlist for validation.
server/db/migrations/V002__player_hotbar_loadout.sql Postgres schema for per-player slot bindings persistence.
server/NeonSprawl.Server.Tests/Game/AbilityInput/HotbarLoadoutApiTests.cs Server contract + validation tests for loadout endpoints.
server/NeonSprawl.Server.Tests/Game/AbilityInput/HotbarLoadoutPersistenceIntegrationTests.cs Postgres persistence survives process restart for bound slots.
client/scripts/hotbar_loadout_client.gd Client HTTP wrapper for fetch/update loadout API.
client/scripts/hotbar_state.gd Client-side hotbar state holder that applies hydrated slot bindings.
client/test/hotbar_loadout_client_test.gd Client tests for hydration/apply and update request behavior.
docs/manual-qa/NEO-29.md Story-specific manual checklist for bind/reconnect/deny validation.

Files to modify

Path Rationale
server/NeonSprawl.Server/Program.cs Register hotbar loadout store/services and map new API routes.
server/NeonSprawl.Server.Tests/InMemoryWebApplicationFactory.cs Keep in-memory test harness forcing both position and hotbar stores in CI/local.
server/README.md Document new loadout endpoints and prototype persistence behavior.
client/scripts/main.gd Wire initial hotbar hydration call on boot/reconnect path.
client/README.md Document hotbar loadout hydration/update flow and debug expectations.
docs/decomposition/modules/E1_M4_AbilityInputScaffold.md Record NEO-29 implementation snapshot + persistence policy decision.
docs/plans/E1M4-prototype-backlog.md Mark NEO-29 implementation notes/decision alignment if scope wording changes.
docs/plans/NEO-29-implementation-plan.md Keep decisions and file/test plan aligned as implementation choices evolve.

Tests

File Coverage
server/NeonSprawl.Server.Tests/Game/AbilityInput/HotbarLoadoutApiTests.cs GET/POST contract shape, slot bounds validation, unknown ability validation, stable deny reasonCode, and successful bind persistence round-trip.
server/NeonSprawl.Server.Tests/Game/AbilityInput/HotbarLoadoutPersistenceIntegrationTests.cs Postgres-enabled persistence survives host restart/reconnect; in-memory fallback behavior remains consistent when DB is absent.
client/test/hotbar_loadout_client_test.gd Client hydration applies server slot bindings, preserves empty slots, and surfaces server deny reason codes from failed updates.
docs/manual-qa/NEO-29.md Manual checklist for two-slot bind + reconnect/reload verification and deny-path UX/log visibility.

Executed during implementation

  • dotnet test NeonSprawl.sln --filter "FullyQualifiedName~HotbarLoadoutApiTests" (passed)
  • dotnet test NeonSprawl.sln --filter "FullyQualifiedName~HotbarLoadoutPersistenceIntegrationTests" (passed)
  • godot --headless --path . -s res://addons/gdUnit4/bin/GdUnitCmdTool.gd --ignoreHeadlessMode -a res://test/hotbar_loadout_client_test.gd (passed)

Open questions / risks

  • Risk: Ability-id allowlist source may drift before full E5.M1 cast contract lands; keep a single prototype registry and document ownership to avoid client/server mismatch.
  • Risk: First cut may choose full-replace update semantics for simplicity; if partial patch semantics are needed for E1M4-02+, call that out as a follow-up to avoid contract churn.

Decisions (implementation updates)

  • Update semantics: NEO-29 uses slot upsert semantics (specified slots are updated; unspecified slots remain unchanged) to keep v1 payload compact while preserving server ownership.
  • Deny shape: Validation denials return HTTP 200 with updated=false, stable reasonCode, and authoritative loadout snapshot so clients can reconcile immediately.

Decisions

Topic Decision
Persistence scope Per-player loadout for prototype v1.
Persistence backend Postgres when ConnectionStrings:NeonSprawl is configured; otherwise in-memory fallback.
Slot count (v1) Fixed 8-slot hotbar contract.