diff --git a/.cursor/rules/git-workflow.md b/.cursor/rules/git-workflow.md index 443b550..d785cd2 100644 --- a/.cursor/rules/git-workflow.md +++ b/.cursor/rules/git-workflow.md @@ -1,5 +1,5 @@ --- -description: Branch only for code; documentation commits on main +description: New Jira story = new branch from kickoff; doc-only on main when not story-scoped alwaysApply: true --- @@ -7,8 +7,10 @@ alwaysApply: true **Agent:** Never run `git commit` without explicit user instruction; the user reviews diffs first. See [commit-and-review](commit-and-review.md). -- **Documentation-only work** — Commit directly on **`main`**. This means Markdown (`.md`) under `docs/`, root and nested `README.md` files, `neon_sprawl_vision.plan.md`, and other prose docs. No feature branch required. +- **Beginning work on a new Jira story** — Create a **new branch** as soon as story work starts (planning, implementation, or both). **Branch names must start with the Jira key** (e.g. `NS-15-position-state-api`); see [jira-git-naming](jira-git-naming.md). Stay on that branch for everything scoped to that issue, including **`docs/plans/{KEY}-implementation-plan.md`**, until the story is merged. Do not put ticketed story plans only on `main` while implementation lives on a branch. -- **Code or implementation changes** — Use a **branch**, then merge to `main` when ready. **Branch names must start with the Jira key** (e.g. `NS-14-click-to-move`); **commit subjects must start with the same key** (e.g. `NS-14: …`). See [jira-git-naming](jira-git-naming.md). This includes application source (**C#**, **GDScript**), Godot scenes and project files, `docker-compose.yml`, `.csproj` / build config, CI workflows, **JSON/YAML game data** under `content/`, and JSON Schema when it ships with data pipelines—not standalone doc prose. +- **Documentation-only work** — Commit directly on **`main`** when the change is **not** tied to an active Jira story branch: general Markdown under `docs/` (except per-story implementation plans for an issue you are working on that branch), root and nested `README.md` files, `neon_sprawl_vision.plan.md`, and other cross-cutting prose. No feature branch required. -When a change mixes documentation and code, treat it as a **code change** (use a branch). +- **Code or implementation changes** — Use a **branch**, then merge to `main` when ready. **Commit subjects must start with the Jira key** when the work maps to an issue (e.g. `NS-14: …`). See [jira-git-naming](jira-git-naming.md). This includes application source (**C#**, **GDScript**), Godot scenes and project files, `docker-compose.yml`, `.csproj` / build config, CI workflows, **JSON/YAML game data** under `content/`, and JSON Schema when it ships with data pipelines—not standalone doc prose. + +When a change mixes documentation and code, treat it as a **code change** (use a branch). Per-story implementation plans are part of the story branch, not an exception for `main`. diff --git a/.cursor/rules/story-kickoff.md b/.cursor/rules/story-kickoff.md index 6a1ec21..7e2f37b 100644 --- a/.cursor/rules/story-kickoff.md +++ b/.cursor/rules/story-kickoff.md @@ -1,5 +1,5 @@ --- -description: When starting Jira story work, load issue + repo context first; no implementation code until a plan exists under docs/plans/. +description: When starting Jira story work, create story branch, load issue + repo context; no implementation code until a plan exists under docs/plans/. alwaysApply: true --- @@ -12,6 +12,12 @@ When the user starts work on a **Jira story** (e.g. issue key `NS-14`, phrases l - Fetch the issue when possible (e.g. Atlassian MCP `getJiraIssue`), or use a URL / pasted description the user provides. - Capture summary, description, acceptance criteria, and anything explicitly **out of scope**. +## 1b. Story branch + +- **Create and use a new branch** for this issue as soon as story work begins (planning counts). Name it with the **Jira key first** and a short kebab-case slug (e.g. `NS-15-position-state-api`); see [jira-git-naming](jira-git-naming.md) and [git workflow](git-workflow.md). +- If the user is already on a branch whose **first path segment** matches this issue key, continue on it unless they ask to rename. +- Do not wait for implementation to start before branching; the implementation plan and all story commits belong on this branch. + ## 2. Load codebase context (read-only) - Open only what the story implies: entry scenes/scripts, related modules, configs, tests/CI, and relevant [`docs/`](docs/) files. @@ -24,7 +30,7 @@ When the user starts work on a **Jira story** (e.g. issue key `NS-14`, phrases l ## 4. Planning document - Add **`docs/plans/{JIRA_KEY}-implementation-plan.md`** (example: `docs/plans/NS-14-implementation-plan.md`). Create `docs/plans/` if it does not exist. -- **Do not** `git commit` the plan unless the user explicitly asks; see [commit-and-review](commit-and-review.md). When the user commits plan-only changes, use **`main`** per [git workflow](git-workflow.md). +- **Do not** `git commit` the plan unless the user explicitly asks; see [commit-and-review](commit-and-review.md). When the user commits plan-only changes for this story, commit on the **story branch** from step 1b, not `main`, per [git workflow](git-workflow.md). **Required sections** in that file: @@ -39,4 +45,4 @@ When the user starts work on a **Jira story** (e.g. issue key `NS-14`, phrases l ## 5. After the plan -- Implement only after the user confirms. For code, scenes, data, or CI changes, use a **branch** and follow [git workflow](git-workflow.md). +- Implement only after the user confirms. All implementation commits stay on the **same story branch** from step 1b; follow [git workflow](git-workflow.md). diff --git a/docs/plans/NS-15-implementation-plan.md b/docs/plans/NS-15-implementation-plan.md new file mode 100644 index 0000000..05d48a3 --- /dev/null +++ b/docs/plans/NS-15-implementation-plan.md @@ -0,0 +1,87 @@ +# 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.