5.7 KiB
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}/positionreturning 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
GETreturns 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 testand/or a curl snippet inserver/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
-
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:1playerId: string (echo path id for sanity)position: object withx,y,zas 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.
-
In-memory authority
- Register a singleton service (e.g.
IPositionStateStore) backed byConcurrentDictionary<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.
- Register a singleton service (e.g.
-
Minimal API
MapGet("/game/players/{id}/position", …)resolves the store, returnsResults.Json(...)with correct content type, orResults.NotFound()for unknown players.
-
Configuration
- Add section in
appsettings.json/appsettings.Development.json(e.g.Game:DevPlayerId, optionalGame:DefaultPositionor nested object). Bind withIOptionsor read once at startup when seeding the store.
- Add section in
-
Tests
- Add
NeonSprawl.Server.Tests(xUnit), referenceMicrosoft.AspNetCore.Mvc.Testing, and addpublic partial class Program { }to the server assembly (empty partial) soWebApplicationFactory<Program>works with top-level statements. - One integration test:
GETfor the configured dev player returns 200, body parses,schemaVersionpresent,position.x/y/zpresent. - Optional second test: unknown id returns 404.
- Add
-
Documentation
- Extend
server/README.mdwith “Position state (NS-15)”: endpoint path, examplecurl, note that state is in-memory and authoritative only at the HTTP spike layer.
- Extend
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>hittingGET /game/players/{id}/position— satisfies AC and testing expectations (first testable server module gets a test project). - Manual:
curlsnippet in README againstdotnet run(default port fromlaunchSettings.json, typicallyhttp://localhost:5253).
Open questions / risks
- Facing convention: If
facingis included, confirm units (radians vs degrees) with NS-10 / future sync design; if unclear, omit v1facingand document “reserved for v2” or addschemaVersionbump 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 testyet, localdotnet teststill satisfies AC; consider a follow-up chore to wire CI when available.