neon-sprawl/docs/plans/NS-15-implementation-plan.md

5.7 KiB

NS-15 — Implementation plan

Story reference

Field Value
Key NS-15
Title E1.M1: Authoritative PositionState API (in-memory)
Jira NS-15
Parent context NS-10 — E1.M1 InputAndMovementRuntime

Goal, scope, and out-of-scope

Goal: The game server is the source of truth for a single development PositionState, exposed read-only over HTTP as JSON (spike alignment with tech stack).

In scope

  • ASP.NET Core endpoint: GET /game/players/{id}/position returning JSON for the agreed PositionState shape (x, y, z, plus optional facing and/or sequence as needed for future sync).
  • Seed one logical player id for local development (configuration entry or documented constant; prefer config so environments stay obvious).
  • In-memory store only (dictionary or small service holding positions); no PostgreSQL.

Out of scope (per Jira)

  • Godot or other client integration, move submission, database persistence.

Acceptance criteria checklist

  • GET returns stable JSON matching the agreed PositionState shape; field names documented in PR description and/or XML doc comments on the response/DTO types.
  • Verification via dotnet test and/or a curl snippet in server/README.md (story allows either; plan: both — minimal integration test + README curl for ad-hoc checks).
  • Payload is versionable: top-level schemaVersion (integer) or an explicit wrapper DTO so v2 can extend without breaking consumers that check version first.

Technical approach

  1. Response contract

    • Define a small set of types (e.g. record/DTO) for the HTTP JSON body.
    • Suggested v1 shape (adjust names in implementation if parent epic standardizes differently):
      • schemaVersion: 1
      • playerId: string (echo path id for sanity)
      • position: object with x, y, z as doubles (or floats serialized as JSON numbers)
      • Optional: facing (e.g. yaw radians or degrees — pick one and document), sequence (ulong/int for future tick ordering)
    • XML doc comments on public API types document each property; PR description repeats the sample JSON.
  2. In-memory authority

    • Register a singleton service (e.g. IPositionStateStore) backed by ConcurrentDictionary<string, …> or immutable snapshots.
    • On startup, ensure the dev player id from config exists with a default position (e.g. origin); other ids may return 404 until a later story seeds them.
  3. Minimal API

    • MapGet("/game/players/{id}/position", …) resolves the store, returns Results.Json(...) with correct content type, or Results.NotFound() for unknown players.
  4. Configuration

    • Add section in appsettings.json / appsettings.Development.json (e.g. Game:DevPlayerId, optional Game:DefaultPosition or nested object). Bind with IOptions or read once at startup when seeding the store.
  5. Tests

    • Add NeonSprawl.Server.Tests (xUnit), reference Microsoft.AspNetCore.Mvc.Testing, and add public partial class Program { } to the server assembly (empty partial) so WebApplicationFactory<Program> works with top-level statements.
    • One integration test: GET for the configured dev player returns 200, body parses, schemaVersion present, position.x/y/z present.
    • Optional second test: unknown id returns 404.
  6. Documentation

    • Extend server/README.md with “Position state (NS-15)”: endpoint path, example curl, note that state is in-memory and authoritative only at the HTTP spike layer.

Files to add

Path Purpose
server/NeonSprawl.Server.Tests/NeonSprawl.Server.Tests.csproj xUnit + Microsoft.AspNetCore.Mvc.Testing test project.
server/NeonSprawl.Server.Tests/PositionStateApiTests.cs (or similar) Integration test(s) for GET /game/players/{id}/position.
New C# files under server/NeonSprawl.Server/ as needed DTOs/contracts, IPositionStateStore + implementation, optional mapping extension methods — keep Program.cs thin via AddSingleton + MapGet only if small; otherwise extract to GameEndpoints.cs or ServiceCollectionExtensions.cs per existing repo style (currently single-file host).

Files to modify

Path Rationale
server/NeonSprawl.Server/Program.cs Register store, map GET /game/players/{id}/position, expose partial class Program for test host.
server/NeonSprawl.Server/appsettings.json and/or appsettings.Development.json Dev player id and default position seed.
server/README.md curl example + short contract note.
NeonSprawl.sln Include test project in solution.

Tests

  • Automated: Integration test(s) via WebApplicationFactory<Program> hitting GET /game/players/{id}/position — satisfies AC and testing expectations (first testable server module gets a test project).
  • Manual: curl snippet in README against dotnet run (default port from launchSettings.json, typically http://localhost:5253).

Open questions / risks

  • Facing convention: If facing is included, confirm units (radians vs degrees) with NS-10 / future sync design; if unclear, omit v1 facing and document “reserved for v2” or add schemaVersion bump when added.
  • Player id format: Use a simple string (e.g. dev-local-1); path segment must be URL-safe.
  • CI: If no workflow runs dotnet test yet, local dotnet test still satisfies AC; consider a follow-up chore to wire CI when available.