# NS-15 — Implementation plan ## Story reference | Field | Value | |--------|--------| | **Key** | NS-15 | | **Title** | E1.M1: Authoritative PositionState API (in-memory) | | **Jira** | [NS-15](https://neon-sprawl.atlassian.net/browse/NS-15) | | **Parent context** | [NS-10 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NS-10) | ## 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](../../docs/architecture/tech_stack.md)). **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` 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`** 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` hitting `GET /game/players/{id}/position` — satisfies AC and [testing expectations](../../.cursor/rules/testing-expectations.md) (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.