Merge pull request #27 from ViPro-Technologies/chore/jira-reorganization

chore: migrate Jira references from NS-* to NEON-* in docs and rules
pull/28/head^2
VinPropane 2026-04-07 00:17:18 -04:00 committed by GitHub
commit 839b8a7c31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 247 additions and 247 deletions

View File

@ -29,7 +29,7 @@ Align recommendations with repo rules and docs, including:
For every review, **identify and cite** the documentation that defines intent for the change, then **check the implementation against it** (not only style and correctness). For every review, **identify and cite** the documentation that defines intent for the change, then **check the implementation against it** (not only style and correctness).
1. **Story / implementation plans** — If the work maps to a ticket or plan under `docs/plans/` (e.g. `NS-*-implementation-plan.md`), treat that document as acceptance criteria. If the diff diverges without an updated plan or an explicit “out of scope” note, call it out (blocking or non-blocking by severity). 1. **Story / implementation plans** — If the work maps to a ticket or plan under `docs/plans/` (e.g. `NEON-*-implementation-plan.md`), treat that document as acceptance criteria. If the diff diverges without an updated plan or an explicit “out of scope” note, call it out (blocking or non-blocking by severity).
2. **Module docs** — Map the change to one or more modules in [`docs/decomposition/modules/module_dependency_register.md`](../../docs/decomposition/modules/module_dependency_register.md) and the corresponding `docs/decomposition/modules/E*_*.md` pages. Verify behavior matches **Purpose**, **Responsibilities**, **Key contracts**, and **Authority** / linked policy sections where relevant. 2. **Module docs** — Map the change to one or more modules in [`docs/decomposition/modules/module_dependency_register.md`](../../docs/decomposition/modules/module_dependency_register.md) and the corresponding `docs/decomposition/modules/E*_*.md` pages. Verify behavior matches **Purpose**, **Responsibilities**, **Key contracts**, and **Authority** / linked policy sections where relevant.
3. **Cross-cutting policies** — When applicable, align with `docs/decomposition/modules/` policy docs (e.g. [`contracts.md`](../../docs/decomposition/modules/contracts.md), [`client_server_authority.md`](../../docs/decomposition/modules/client_server_authority.md), [`pvp_combat_integration.md`](../../docs/decomposition/modules/pvp_combat_integration.md), [`data_and_ops_policy.md`](../../docs/decomposition/modules/data_and_ops_policy.md)). 3. **Cross-cutting policies** — When applicable, align with `docs/decomposition/modules/` policy docs (e.g. [`contracts.md`](../../docs/decomposition/modules/contracts.md), [`client_server_authority.md`](../../docs/decomposition/modules/client_server_authority.md), [`pvp_combat_integration.md`](../../docs/decomposition/modules/pvp_combat_integration.md), [`data_and_ops_policy.md`](../../docs/decomposition/modules/data_and_ops_policy.md)).
4. **Implementation status** — If the register or [`documentation_and_implementation_alignment.md`](../../docs/decomposition/modules/documentation_and_implementation_alignment.md) tracks the module, note whether **Status** / the implementation tracking table should be updated after this merge. 4. **Implementation status** — If the register or [`documentation_and_implementation_alignment.md`](../../docs/decomposition/modules/documentation_and_implementation_alignment.md) tracks the module, note whether **Status** / the implementation tracking table should be updated after this merge.
@ -56,12 +56,12 @@ Work through what applies to the diff (skip irrelevant sections briefly).
**Every** invocation must produce a **saved markdown document** in the repo, not only a chat reply. **Every** invocation must produce a **saved markdown document** in the repo, not only a chat reply.
1. **Directory:** `docs/reviews/` (create the folder if it does not exist). 1. **Directory:** `docs/reviews/` (create the folder if it does not exist).
2. **Filename:** `YYYY-MM-DD-{slug}.md` using the **authoritative “Todays date”** from the session when known; otherwise use the actual calendar date. **Slug:** derive from the Jira key if the user named one (e.g. `NS-15`), else the branch name, else a short topic (kebab-case, lowercase). Examples: `2026-03-29-NS-15.md`, `2026-03-29-position-state-api.md`. 2. **Filename:** `YYYY-MM-DD-{slug}.md` using the **authoritative “Todays date”** from the session when known; otherwise use the actual calendar date. **Slug:** derive from the Jira key if the user named one (e.g. `NEON-3`), else the branch name, else a short topic (kebab-case, lowercase). Examples: `2026-03-29-NEON-3.md`, `2026-03-29-position-state-api.md`.
3. **Preamble** at the top of the file (after an optional `#` title): 3. **Preamble** at the top of the file (after an optional `#` title):
- **Date** (same as in filename unless corrected) - **Date** (same as in filename unless corrected)
- **Scope** — branch name, PR link, issue key, and/or `git` range the user asked to review (or “working tree / unstaged” if that was the scope) - **Scope** — branch name, PR link, issue key, and/or `git` range the user asked to review (or “working tree / unstaged” if that was the scope)
- **Base** — e.g. `origin/main` or commit SHA if stated or inferable - **Base** — e.g. `origin/main` or commit SHA if stated or inferable
4. **Body:** the full review using the **Output format** sections below (verdict through verification). This is the canonical copy; the chat response may be a short summary plus **path to the file** (e.g. `docs/reviews/2026-03-29-NS-15.md`). 4. **Body:** the full review using the **Output format** sections below (verdict through verification). This is the canonical copy; the chat response may be a short summary plus **path to the file** (e.g. `docs/reviews/2026-03-29-NEON-3.md`).
5. **Commits:** Follow [commit-and-review](commit-and-review.md) — you may **commit** the review file at your discretion; **never** `git push`. 5. **Commits:** Follow [commit-and-review](commit-and-review.md) — you may **commit** the review file at your discretion; **never** `git push`.
In the markdown file, use **normal fenced code blocks** for code snippets and **backtick file paths** (e.g. ``server/Program.cs``). Do **not** use IDE-only line-number code citations in the saved document—they do not render on GitHub or in plain Markdown viewers. In the markdown file, use **normal fenced code blocks** for code snippets and **backtick file paths** (e.g. ``server/Program.cs``). Do **not** use IDE-only line-number code citations in the saved document—they do not render on GitHub or in plain Markdown viewers.

View File

@ -23,7 +23,7 @@ alwaysApply: true
## Commit message format when a Jira story applies ## Commit message format when a Jira story applies
- Any commit that is **part of implementing or delivering a Jira story or task** must put the **Jira issue key first** in the subject line, then **`:`**, then the summary (e.g. `NS-17: persist position state in PostgreSQL`). - Any commit that is **part of implementing or delivering a Jira story or task** must put the **Jira issue key first** in the subject line, then **`:`**, then the summary (e.g. `NEON-5: persist position state in PostgreSQL`).
- Infer the key from the active branch name, the issue under discussion, or Jira context. Full rules (multi-issue commits, `chore:` when there is no ticket) are in [jira-git-naming](jira-git-naming.md). - Infer the key from the active branch name, the issue under discussion, or Jira context. Full rules (multi-issue commits, `chore:` when there is no ticket) are in [jira-git-naming](jira-git-naming.md).
## Scope ## Scope

View File

@ -7,11 +7,11 @@ alwaysApply: true
**Agent:** You may **`git commit`** at your discretion while on story/ticket work; do **not** **`git push`**. See [commit-and-review](commit-and-review.md). **Agent:** You may **`git commit`** at your discretion while on story/ticket work; do **not** **`git push`**. See [commit-and-review](commit-and-review.md).
- **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. - **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. `NEON-3-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.
- **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. - **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.
- **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. - **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. `NEON-2: …`). 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`. 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`.

View File

@ -5,27 +5,27 @@ alwaysApply: true
# Jira keys in branches and commits (Neon Sprawl) # Jira keys in branches and commits (Neon Sprawl)
Project key in Jira is **`NS`** unless a different key is used for the issue (match the issue you are implementing). Project key in Jira is **`NEON`** unless a different key is used for the issue (match the issue you are implementing).
## Branch names ## Branch names
- Put the **Jira key first**, then a short **kebab-case** slug (and optional type). The key must be the **first path segment** of the branch name. - Put the **Jira key first**, then a short **kebab-case** slug (and optional type). The key must be the **first path segment** of the branch name.
- **Good:** `NS-14-click-to-move`, `NS-20-fix-health-desync`, `NS-14/feature/terrain-spike` (key still first segment if using slashes). - **Good:** `NEON-2-click-to-move`, `NEON-13-fix-health-desync`, `NEON-2/feature/terrain-spike` (key still first segment if using slashes).
- **Avoid:** `feature/click-to-move` with no key, or `fix/ns-14-foo` with the key not at the start. - **Avoid:** `feature/click-to-move` with no key, or `fix/neon-2-foo` with the key not at the start.
When suggesting or creating a branch for story **NS-14**, use something like **`NS-14-<slug>`**, not `feature/ns-14-…` with the key buried after a prefix. When suggesting or creating a branch for story **NEON-2**, use something like **`NEON-2-<slug>`**, not `feature/neon-2-…` with the key buried after a prefix.
## Commit messages ## Commit messages
- For **any commit done as part of a Jira story, bug, or task** (work tracked under an issue), the **first token** of the subject line must be the **Jira key and number**, then **`:`** and the summary. - For **any commit done as part of a Jira story, bug, or task** (work tracked under an issue), the **first token** of the subject line must be the **Jira key and number**, then **`:`** and the summary.
- **Good:** `NS-14: add direct click-to-move steering`, `NS-20: fix duplicate spawn on reconnect` - **Good:** `NEON-2: add direct click-to-move steering`, `NEON-13: fix duplicate spawn on reconnect`
- **Avoid:** `fix(client): …` or `feat: …` **without** the Jira key at the front. If you use Conventional Commits, place them **after** the key: `NS-14: feat(client): click-to-move prototype` - **Avoid:** `fix(client): …` or `feat: …` **without** the Jira key at the front. If you use Conventional Commits, place them **after** the key: `NEON-2: feat(client): click-to-move prototype`
**Multi-issue commits:** Prefer one issue per commit; if unavoidable, list keys: `NS-14, NS-18: shared nav refactor`. **Multi-issue commits:** Prefer one issue per commit; if unavoidable, list keys: `NEON-2, NEON-6: shared nav refactor`.
**No Jira issue (rare):** Maintenance or repo-only changes with no ticket — use **`chore:`** as the prefix (e.g. `chore: update .gitignore`). Do not invent fake keys. **No Jira issue (rare):** Maintenance or repo-only changes with no ticket — use **`chore:`** as the prefix (e.g. `chore: update .gitignore`). Do not invent fake keys.
## Agent behavior ## Agent behavior
- When creating a **branch** or **commit** for tracked work, **infer the key** from the current story (e.g. user says NS-14), the branch already in use, or the Jira issue fetched via MCP. - When creating a **branch** or **commit** for tracked work, **infer the key** from the current story (e.g. user says NEON-2), the branch already in use, or the Jira issue fetched via MCP.
- Follow [git workflow](git-workflow.md) (branch vs `main`) and [commit-and-review](commit-and-review.md) (commits at discretion on story work; **never** `git push`). - Follow [git workflow](git-workflow.md) (branch vs `main`) and [commit-and-review](commit-and-review.md) (commits at discretion on story work; **never** `git push`).

View File

@ -5,7 +5,7 @@ alwaysApply: true
# Story end (Jira) # Story end (Jira)
When the user **ends story work** on a ticketed branch—phrases like “end story”, “story is merged”, “clean up branch after NS-…”, or they confirm the PR is **merged** to `main`—run this **git** sequence so local state matches the remote default branch. When the user **ends story work** on a ticketed branch—phrases like “end story”, “story is merged”, “clean up branch after NEON-…”, or they confirm the PR is **merged** to `main`—run this **git** sequence so local state matches the remote default branch.
## 1. Preconditions ## 1. Preconditions
@ -16,7 +16,7 @@ When the user **ends story work** on a ticketed branch—phrases like “end sto
1. **`git checkout main`** 1. **`git checkout main`**
2. **`git pull origin main`** (or `git pull` if `main` already tracks `origin/main` and that is correct for this repo) 2. **`git pull origin main`** (or `git pull` if `main` already tracks `origin/main` and that is correct for this repo)
3. **`git branch -d <story-branch>`** — use the branch that was used for the issue (first path segment should be the Jira key, e.g. `NS-19-move-validation`). If Git reports “not fully merged”, confirm whether **`origin/main`** already contains the story (e.g. squash merge or follow-up commits only cherry-picked to `main`). If yes, **`git branch -D <story-branch>`** is OK **only with user confirmation** so local unpushed tip is not lost by mistake. 3. **`git branch -d <story-branch>`** — use the branch that was used for the issue (first path segment should be the Jira key, e.g. `NEON-7-move-validation`). If Git reports “not fully merged”, confirm whether **`origin/main`** already contains the story (e.g. squash merge or follow-up commits only cherry-picked to `main`). If yes, **`git branch -D <story-branch>`** is OK **only with user confirmation** so local unpushed tip is not lost by mistake.
## 2a. When `main` is push-protected ## 2a. When `main` is push-protected
@ -25,7 +25,7 @@ If the user asks to **push** changes that sit on **`main`** and **`git push orig
## 3. What not to do ## 3. What not to do
- Do **not** delete **`main`** or **`origin/*`**. - Do **not** delete **`main`** or **`origin/*`**.
- Do **not** assume the remote story branch should be deleted unless the user asks (many teams keep `origin/NS-…` for history or delete via GitHub UI). - Do **not** assume the remote story branch should be deleted unless the user asks (many teams keep `origin/NEON-…` for history or delete via GitHub UI).
## 4. Jira (optional) ## 4. Jira (optional)

View File

@ -5,7 +5,7 @@ alwaysApply: true
# Story kickoff (Jira) # Story kickoff (Jira)
When the user starts work on a **Jira story** (e.g. issue key `NS-14`, phrases like “begin work on story”, “implement NS-…”), run this sequence **before** writing or changing application code. When the user starts work on a **Jira story** (e.g. issue key `NEON-2`, phrases like “begin work on story”, “implement NEON-…”), run this sequence **before** writing or changing application code.
## 1. Load Jira context ## 1. Load Jira context
@ -20,7 +20,7 @@ When the user starts work on a **Jira story** (e.g. issue key `NS-14`, phrases l
## 1b. Story branch ## 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). - **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. `NEON-3-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. - 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. - Do not wait for implementation to start before branching; the implementation plan and all story commits belong on this branch.
@ -35,7 +35,7 @@ When the user starts work on a **Jira story** (e.g. issue key `NS-14`, phrases l
## 4. Planning document ## 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. - Add **`docs/plans/{JIRA_KEY}-implementation-plan.md`** (example: `docs/plans/NEON-2-implementation-plan.md`). Create `docs/plans/` if it does not exist.
- **Commit** the plan on the **story branch** from step 1b when it is ready (at agent discretion per [commit-and-review](commit-and-review.md)); do not put ticketed plan-only work only on `main` while implementation stays on a branch—see [git workflow](git-workflow.md). - **Commit** the plan on the **story branch** from step 1b when it is ready (at agent discretion per [commit-and-review](commit-and-review.md)); do not put ticketed plan-only work only on `main` while implementation stays on a branch—see [git workflow](git-workflow.md).
**Required sections** in that file (do **not** ship a plan that omits any of these): **Required sections** in that file (do **not** ship a plan that omits any of these):

View File

@ -1,5 +1,5 @@
--- ---
description: Unit vs integration tests; integration when data persistence/mutation exists; C# xUnit; Godot client unit tests with script changes (NS-21 harness + gdscript.yml CI). description: Unit vs integration tests; integration when data persistence/mutation exists; C# xUnit; Godot client unit tests with script changes (NEON-14 harness + gdscript.yml CI).
alwaysApply: true alwaysApply: true
--- ---
@ -23,7 +23,7 @@ alwaysApply: true
## Godot client (GDScript) ## Godot client (GDScript)
- **Harness (NS-21):** **GdUnit4** lives under **`client/addons/gdUnit4/`**; suites under **`client/test/`**. **`client/README.md`** documents local and headless runs; **`.github/workflows/gdscript.yml`** runs **gdlint**, **gdformat**, and **headless GdUnit** when `client/scripts/`, `client/test/`, `client/addons/`, or `client/project.godot` change. - **Harness (NEON-14):** **GdUnit4** lives under **`client/addons/gdUnit4/`**; suites under **`client/test/`**. **`client/README.md`** documents local and headless runs; **`.github/workflows/gdscript.yml`** runs **gdlint**, **gdformat**, and **headless GdUnit** when `client/scripts/`, `client/test/`, `client/addons/`, or `client/project.godot` change.
- **Production script changes:** Any PR that **adds or changes** GDScript under **`client/scripts/`** (and any other **application** `.gd` outside **`client/addons/`** and **`client/test/`**) must **add or update** tests under **`client/test/`** in the same change set. If a change is **not** reasonably testable, say so in the PR (or story plan) with a short reason—do not skip tests silently. - **Production script changes:** Any PR that **adds or changes** GDScript under **`client/scripts/`** (and any other **application** `.gd` outside **`client/addons/`** and **`client/test/`**) must **add or update** tests under **`client/test/`** in the same change set. If a change is **not** reasonably testable, say so in the PR (or story plan) with a short reason—do not skip tests silently.
- **Scenes / assets / manual checks:** Scene tweaks, visuals, and flows that unit tests do not cover still deserve **manual verification** notes in the PR when behavior could regress. - **Scenes / assets / manual checks:** Scene tweaks, visuals, and flows that unit tests do not cover still deserve **manual verification** notes in the PR when behavior could regress.

View File

@ -9,4 +9,4 @@ Optional **personas** for Cursor. Enable by **@ mentioning** the rule in chat or
Project-wide conventions live under [`.cursor/rules/`](.cursor/rules/) (many are `alwaysApply`). **Godot client layout** (thin `main.gd`, split by concern): [`.cursor/rules/godot-client-script-organization.md`](.cursor/rules/godot-client-script-organization.md). **Git:** agents may **commit** at discretion on story/ticket work; **never** `git push` unless the user asks — [`.cursor/rules/commit-and-review.md`](.cursor/rules/commit-and-review.md). **Story lifecycle:** kickoff [`.cursor/rules/story-kickoff.md`](.cursor/rules/story-kickoff.md); after merge / end of story — `checkout main`, `pull`, delete local story branch — [`.cursor/rules/story-end.md`](.cursor/rules/story-end.md). **PR / push text:** no “Made-with: Cursor” boilerplate (same file). Project-wide conventions live under [`.cursor/rules/`](.cursor/rules/) (many are `alwaysApply`). **Godot client layout** (thin `main.gd`, split by concern): [`.cursor/rules/godot-client-script-organization.md`](.cursor/rules/godot-client-script-organization.md). **Git:** agents may **commit** at discretion on story/ticket work; **never** `git push` unless the user asks — [`.cursor/rules/commit-and-review.md`](.cursor/rules/commit-and-review.md). **Story lifecycle:** kickoff [`.cursor/rules/story-kickoff.md`](.cursor/rules/story-kickoff.md); after merge / end of story — `checkout main`, `pull`, delete local story branch — [`.cursor/rules/story-end.md`](.cursor/rules/story-end.md). **PR / push text:** no “Made-with: Cursor” boilerplate (same file).
**Commits tied to a Jira issue** must start the subject with the **issue key** and a colon (e.g. `NS-17: …`). Branch naming and exceptions (`chore:` when there is no ticket) — [`.cursor/rules/jira-git-naming.md`](.cursor/rules/jira-git-naming.md). **Commits tied to a Jira issue** must start the subject with the **issue key** and a colon (e.g. `NEON-5: …`). Branch naming and exceptions (`chore:` when there is no ticket) — [`.cursor/rules/jira-git-naming.md`](.cursor/rules/jira-git-naming.md).

View File

@ -11,47 +11,47 @@ Do not grow an all-in-one **`main.gd`**. The main scene root should **compose**
**Tests with script changes:** Any PR that adds or changes GDScript under **`scripts/`** should **add or update** tests under **`test/`** in the same change set (see [`.cursor/rules/testing-expectations.md`](../.cursor/rules/testing-expectations.md)). CI runs them via **`.github/workflows/gdscript.yml`**. **Tests with script changes:** Any PR that adds or changes GDScript under **`scripts/`** should **add or update** tests under **`test/`** in the same change set (see [`.cursor/rules/testing-expectations.md`](../.cursor/rules/testing-expectations.md)). CI runs them via **`.github/workflows/gdscript.yml`**.
## Authoritative movement (NS-16, NS-23) ## Authoritative movement (NEON-4, NEON-8)
With the game server running ([`server/README.md`](../server/README.md)), each valid floor click sends a **`MoveCommand`** (**`POST`**) and a follow-up **`GET`** for **`PositionState`**. The server still **snaps** authority to the target (NS-16/19); the client **moves** toward that verified position using **`NavigationAgent3D`** + a **baked mesh** instead of teleporting on the **`GET`** (NS-23). **Boot** `sync_from_server()` **snaps** once so spawn matches the server. With the game server running ([`server/README.md`](../server/README.md)), each valid floor click sends a **`MoveCommand`** (**`POST`**) and a follow-up **`GET`** for **`PositionState`**. The server still **snaps** authority to the target (NEON-4/19); the client **moves** toward that verified position using **`NavigationAgent3D`** + a **baked mesh** instead of teleporting on the **`GET`** (NEON-8). **Boot** `sync_from_server()` **snaps** once so spawn matches the server.
**Tradeoff (prototype):** `player.gd` often steers **straight in xz** toward the goal when the picks **Y** is below the capsule origin (smooth **stepped bumps**). **Automatic routing around tall obstacles on one click is not guaranteed** — use **several clicks** to go around e.g. the gray **`Obstacle`** when needed. **Tradeoff (prototype):** `player.gd` often steers **straight in xz** toward the goal when the picks **Y** is below the capsule origin (smooth **stepped bumps**). **Automatic routing around tall obstacles on one click is not guaranteed** — use **several clicks** to go around e.g. the gray **`Obstacle`** when needed.
**NS-19 / movement QA bumps:** On **run**, **`spawn_short_random_bumps`** adds **two** green cylinders, each on its own **`StaticBody3D`** **sibling** of **`Floor`** under **`NavigationRegion3D`** (**`walkable`** on bump roots — avoids compound **internal-edge** jitter vs floor+cylinder on one body). Bump meshes use Godot group **`random_floor_bump_mesh`**. **Collision radius** = mesh **+ `COLLISION_RADIUS_EXTRA`** (see **`scripts/random_floor_bump_collision_constants.gd`**, capped by **`COLLISION_RADIUS_MAX`**). **`bake_navigation_mesh(false)`** after spawn. **NEON-7 / movement QA bumps:** On **run**, **`spawn_short_random_bumps`** adds **two** green cylinders, each on its own **`StaticBody3D`** **sibling** of **`Floor`** under **`NavigationRegion3D`** (**`walkable`** on bump roots — avoids compound **internal-edge** jitter vs floor+cylinder on one body). Bump meshes use Godot group **`random_floor_bump_mesh`**. **Collision radius** = mesh **+ `COLLISION_RADIUS_EXTRA`** (see **`scripts/random_floor_bump_collision_constants.gd`**, capped by **`COLLISION_RADIUS_MAX`**). **`bake_navigation_mesh(false)`** after spawn.
**Idle stability (NS-24):** **Jolt Physics**; **TPS** **120**. **`physics/common/physics_interpolation`** is **off** — with **on**, small physics **position** changes on bump **edges** were **blended** across render frames and looked like **ghosting / extra jitter**. **`snap_to_server()`** still calls **`reset_physics_interpolation()`** for compatibility if you turn interpolation on later. **`floor_max_angle`** **~50°** walking / **~35°** idle; **loose** angle **~0.8 s** after walk stops. **Walk step assist**. **Idle rim / straddle:** **moving** `floor_max_angle` when floor normal is **shallow** or slide hits mix **floor + wall**. One idle **`move_and_slide()`**, rim **settle**, **`random_floor_bump_mesh`** **lip / rim / vertical-wall** escape (**`IDLE_BUMP_ESCAPE_STEP`**, **`PLAYER_CAPSULE_RADIUS`**, collider fudge from **`random_floor_bump_collision_constants.gd`**). **`Player`:** interp **Off**, **`avoidance_enabled`** **false**, **`floor_block_on_wall`** **true** — do **not** rewrite **`global_transform`** in **`_process`** (can **ghost** **`CharacterBody3D`**). **`_snap_capsule_upright()`** after motion. Idle **`FLOOR_SNAP_IDLE`** ~**11 cm**; walking **`FLOOR_SNAP_MOVING`** ~**0.32**. **Rendering:** **`Mat_player_capsule`**, **`cast_shadow = 0`**, capsule mesh **+Y ~3.4 cm** (visual-only vs **`CapsuleShape3D`** — less **z-fight** vs floor/bump tops), **`light_specular = 0`**, **`msaa_3d = 2`**. **`safe_margin`** **0.055**. **Idle stability (NEON-16):** **Jolt Physics**; **TPS** **120**. **`physics/common/physics_interpolation`** is **off** — with **on**, small physics **position** changes on bump **edges** were **blended** across render frames and looked like **ghosting / extra jitter**. **`snap_to_server()`** still calls **`reset_physics_interpolation()`** for compatibility if you turn interpolation on later. **`floor_max_angle`** **~50°** walking / **~35°** idle; **loose** angle **~0.8 s** after walk stops. **Walk step assist**. **Idle rim / straddle:** **moving** `floor_max_angle` when floor normal is **shallow** or slide hits mix **floor + wall**. One idle **`move_and_slide()`**, rim **settle**, **`random_floor_bump_mesh`** **lip / rim / vertical-wall** escape (**`IDLE_BUMP_ESCAPE_STEP`**, **`PLAYER_CAPSULE_RADIUS`**, collider fudge from **`random_floor_bump_collision_constants.gd`**). **`Player`:** interp **Off**, **`avoidance_enabled`** **false**, **`floor_block_on_wall`** **true** — do **not** rewrite **`global_transform`** in **`_process`** (can **ghost** **`CharacterBody3D`**). **`_snap_capsule_upright()`** after motion. Idle **`FLOOR_SNAP_IDLE`** ~**11 cm**; walking **`FLOOR_SNAP_MOVING`** ~**0.32**. **Rendering:** **`Mat_player_capsule`**, **`cast_shadow = 0`**, capsule mesh **+Y ~3.4 cm** (visual-only vs **`CapsuleShape3D`** — less **z-fight** vs floor/bump tops), **`light_specular = 0`**, **`msaa_3d = 2`**. **`safe_margin`** **0.055**.
- **Scripts:** `scripts/ground_pick.gd` (walkable pick + `target_chosen`), `scripts/position_authority_client.gd` (`PositionAuthorityClient`: POST move, GET verify; second signal arg = boot snap vs nav goal), `scripts/player.gd` (path-follow), thin `scripts/main.gd` (nav bake on first frame, then wiring). - **Scripts:** `scripts/ground_pick.gd` (walkable pick + `target_chosen`), `scripts/position_authority_client.gd` (`PositionAuthorityClient`: POST move, GET verify; second signal arg = boot snap vs nav goal), `scripts/player.gd` (path-follow), thin `scripts/main.gd` (nav bake on first frame, then wiring).
- **Scene:** `scenes/main.tscn` — walkable **`StaticBody3D`** geometry lives under **`World/NavigationRegion3D`**. `main.gd` waits one **`process_frame`**, spawns **random test bumps** on **`Floor`**, then **`bake_navigation_mesh(false)`** (main-thread bake), then waits two **`physics_frame`**s so **`NavigationServer3D`** has a map before agents query paths. The baked **`NavigationMesh`** asset in the scene file is **stale** until you run or re-bake in the editor. - **Scene:** `scenes/main.tscn` — walkable **`StaticBody3D`** geometry lives under **`World/NavigationRegion3D`**. `main.gd` waits one **`process_frame`**, spawns **random test bumps** on **`Floor`**, then **`bake_navigation_mesh(false)`** (main-thread bake), then waits two **`physics_frame`**s so **`NavigationServer3D`** has a map before agents query paths. The baked **`NavigationMesh`** asset in the scene file is **stale** until you run or re-bake in the editor.
- **Inspector:** select **`PositionAuthorityClient`** on the main scene to set **`base_url`** (default `http://127.0.0.1:5253`) and **`dev_player_id`** (default `dev-local-1`, must match server `Game:DevPlayerId`). - **Inspector:** select **`PositionAuthorityClient`** on the main scene to set **`base_url`** (default `http://127.0.0.1:5253`) and **`dev_player_id`** (default `dev-local-1`, must match server `Game:DevPlayerId`).
### Manual check (NS-16 + NS-23) ### Manual check (NEON-4 + NEON-8)
1. From repo root: `cd server/NeonSprawl.Server && dotnet run` (note the URL/port, usually `http://localhost:5253`). 1. From repo root: `cd server/NeonSprawl.Server && dotnet run` (note the URL/port, usually `http://localhost:5253`).
2. If the port differs, set **`base_url`** on **`PositionAuthorityClient`** accordingly (e.g. `http://127.0.0.1:5253`). 2. If the port differs, set **`base_url`** on **`PositionAuthorityClient`** accordingly (e.g. `http://127.0.0.1:5253`).
3. Open the client in Godot and run the main scene (**F5**). The player should **snap** to the servers default position (e.g. **(-5, 0.9, -5)** per `Game:DefaultPosition` / NS-18 walk demo). 3. Open the client in Godot and run the main scene (**F5**). The player should **snap** to the servers default position (e.g. **(-5, 0.9, -5)** per `Game:DefaultPosition` / NEON-6 walk demo).
4. **Left-click** the floor: the client **POST**s the target, then **GET**s position; the capsule **walks** toward the authoritative target (may follow nav waypoints or bee-line in xz per the tradeoff above). NS-19 reject clicks still show the reject label and do **not** start a path. 4. **Left-click** the floor: the client **POST**s the target, then **GET**s position; the capsule **walks** toward the authoritative target (may follow nav waypoints or bee-line in xz per the tradeoff above). NEON-7 reject clicks still show the reject label and do **not** start a path.
5. Click the **far pad** or **pedestal top** (from spawn) to confirm **`horizontal_step_exceeded`** / **`vertical_step_exceeded`** behavior is unchanged. 5. Click the **far pad** or **pedestal top** (from spawn) to confirm **`horizontal_step_exceeded`** / **`vertical_step_exceeded`** behavior is unchanged.
6. After a move (or at spawn), **stand idle** with no click goal for **10+ seconds** and confirm the capsule does **not** visibly vibrate on the floor or the **random green bumps** (positions change each run). 6. After a move (or at spawn), **stand idle** with no click goal for **10+ seconds** and confirm the capsule does **not** visibly vibrate on the floor or the **random green bumps** (positions change each run).
If the server is **down**, boot **`GET`** fails silently (check Output for warnings); clicks while a request is in flight are ignored until it finishes. If the server is **down**, boot **`GET`** fails silently (check Output for warnings); clicks while a request is in flight are ignored until it finishes.
## Interaction + range preview (NS-18) ## Interaction + range preview (NEON-6)
The main scene includes a **prototype terminal** at the map center (same world **X/Z** as the servers static registry) and **two glowing markers** driven by `scripts/interaction_radius_indicators.gd`. That script compares the **`CharacterBody3D`** **`global_position`** (after server snap) to the anchor in **`scripts/prototype_interaction_constants.gd`** using **horizontal distance on X/Z** and the same **`interaction_radius`** as **`PrototypeInteractableRegistry.cs`** — **preview only**; the server **`POST /game/players/{id}/interact`** is authoritative. The main scene includes a **prototype terminal** at the map center (same world **X/Z** as the servers static registry) and **two glowing markers** driven by `scripts/interaction_radius_indicators.gd`. That script compares the **`CharacterBody3D`** **`global_position`** (after server snap) to the anchor in **`scripts/prototype_interaction_constants.gd`** using **horizontal distance on X/Z** and the same **`interaction_radius`** as **`PrototypeInteractableRegistry.cs`** — **preview only**; the server **`POST /game/players/{id}/interact`** is authoritative.
- **`InteractionRequestClient`** (child of the main scene root): press **E** to POST interact for the prototype id; **Output** prints `allowed` / `reasonCode` (or HTTP errors). While a request is in flight, further **E** presses are ignored (same pattern as **`PositionAuthorityClient`**). - **`InteractionRequestClient`** (child of the main scene root): press **E** to POST interact for the prototype id; **Output** prints `allowed` / `reasonCode` (or HTTP errors). While a request is in flight, further **E** presses are ignored (same pattern as **`PositionAuthorityClient`**).
- **Inspector:** match **`base_url`** / **`dev_player_id`** to **`PositionAuthorityClient`** and the server `Game:DevPlayerId`. - **Inspector:** match **`base_url`** / **`dev_player_id`** to **`PositionAuthorityClient`** and the server `Game:DevPlayerId`.
### Manual check (NS-18) ### Manual check (NEON-6)
1. Run the game server and client as in NS-16. 1. Run the game server and client as in NEON-4.
2. **F5** in Godot: default spawn is out of range of the terminal; markers should stay **dim**. **Click-move** toward the center until markers **brighten** (within **3** m on the floor plane). 2. **F5** in Godot: default spawn is out of range of the terminal; markers should stay **dim**. **Click-move** toward the center until markers **brighten** (within **3** m on the floor plane).
3. Press **E** (input action **`interact`** in `project.godot`): Output should show **`allowed=true`** when markers glow, **`allowed=false`** with **`reasonCode=out_of_range`** when dim (if you walk back out). Interaction uses **`_input`**, not `_unhandled_input`, so keys register reliably in the embedded **Game** dock; click the game view if the editor had focus elsewhere. 3. Press **E** (input action **`interact`** in `project.godot`): Output should show **`allowed=true`** when markers glow, **`allowed=false`** with **`reasonCode=out_of_range`** when dim (if you walk back out). Interaction uses **`_input`**, not `_unhandled_input`, so keys register reliably in the embedded **Game** dock; click the game view if the editor had focus elsewhere.
## Movement prototype (NS-14 → NS-23) ## Movement prototype (NEON-2 → NEON-8)
**`player.gd`** uses **`NavigationAgent3D.get_next_path_position()`** + **`move_and_slide()`** for horizontal motion when following the mesh; it may steer **directly at the goal** in xz when the descend bypass applies (NS-23). **`snap_to_server()`** remains for **boot** (and would apply for any future hard reconcile). **`player.gd`** uses **`NavigationAgent3D.get_next_path_position()`** + **`move_and_slide()`** for horizontal motion when following the mesh; it may steer **directly at the goal** in xz when the descend bypass applies (NEON-8). **`snap_to_server()`** remains for **boot** (and would apply for any future hard reconcile).
- The avatar is on **physics layer 2** with **mask 1** (floor + obstacle on layer 1); the pick ray uses **mask 1** so clicks pass through the avatar and hit the floor. - The avatar is on **physics layer 2** with **mask 1** (floor + obstacle on layer 1); the pick ray uses **mask 1** so clicks pass through the avatar and hit the floor.
@ -85,7 +85,7 @@ godot --headless --path . --quit-after 5
A clean checkout has **`client/.godot/`** gitignored, so scripts intentionally avoid **`class_name`** global types that require a full editor import before headless can resolve them. A clean checkout has **`client/.godot/`** gitignored, so scripts intentionally avoid **`class_name`** global types that require a full editor import before headless can resolve them.
## Automated tests (GDScript, NS-21) ## Automated tests (GDScript, NEON-14)
**Framework:** **[GdUnit4](https://github.com/godot-gdunit-labs/gdUnit4)** **v6.1.2** is vendored under **`addons/gdUnit4/`** (plugin enabled in `project.godot`). Test suites live in **`test/`** (`*_test.gd` files extending `GdUnitTestSuite`). **Framework:** **[GdUnit4](https://github.com/godot-gdunit-labs/gdUnit4)** **v6.1.2** is vendored under **`addons/gdUnit4/`** (plugin enabled in `project.godot`). Test suites live in **`test/`** (`*_test.gd` files extending `GdUnitTestSuite`).

View File

@ -22,7 +22,7 @@ Deliver the foundational client runtime: movement, locked isometric camera, worl
- Key contracts: `MoveCommand`, `PositionState`, `InteractionRequest` - Key contracts: `MoveCommand`, `PositionState`, `InteractionRequest`
- Dependencies: None - Dependencies: None
- Stage target: Prototype - Stage target: Prototype
- Status: **Ready** in the [module dependency register](../modules/module_dependency_register.md); Jira [NS-10](https://neon-sprawl.atlassian.net/browse/NS-10) **Done** for prototype scope — [module doc](../modules/E1_M1_InputAndMovementRuntime.md#implementation-snapshot) for snapshot and follow-on work. - Status: **Ready** in the [module dependency register](../modules/module_dependency_register.md); Jira [NEON-9](https://neon-sprawl.atlassian.net/browse/NEON-9) **Done** for prototype scope — [module doc](../modules/E1_M1_InputAndMovementRuntime.md#implementation-snapshot) for snapshot and follow-on work.
### E1.M2 - IsometricCameraController ### E1.M2 - IsometricCameraController

View File

@ -7,7 +7,7 @@
| **Module ID** | E1.M1 | | **Module ID** | E1.M1 |
| **Epic** | [Epic 1 — Core Player Runtime](../epics/epic_01_core_player_runtime.md) | | **Epic** | [Epic 1 — Core Player Runtime](../epics/epic_01_core_player_runtime.md) |
| **Stage target** | Prototype | | **Stage target** | Prototype |
| **Status** | Ready — prototype milestone complete ([NS-10](https://neon-sprawl.atlassian.net/browse/NS-10) Done); see [dependency register](module_dependency_register.md) and [implementation snapshot](#implementation-snapshot) | | **Status** | Ready — prototype milestone complete ([NEON-9](https://neon-sprawl.atlassian.net/browse/NEON-9) Done); see [dependency register](module_dependency_register.md) and [implementation snapshot](#implementation-snapshot) |
## Purpose ## Purpose
@ -33,7 +33,7 @@ Contract readiness is tracked in the [module dependency register](module_depende
## Implementation snapshot ## Implementation snapshot
- **Done (prototype):** Server-side authoritative **`PositionState`** read + **`MoveCommand`** apply (HTTP JSON v1, snap-to-target, `sequence` increments); **NS-19** server-side step + optional district bounds validation with **`reasonCode`** rejections ([NS-19](../../plans/NS-19-implementation-plan.md), `MoveCommandValidation`, [server README — Move command](../../../server/README.md#move-command-ns-16-ns-19)); default **in-memory** store; optional **PostgreSQL** persistence when `ConnectionStrings:NeonSprawl` is set ([NS-17](../../plans/NS-17-implementation-plan.md)); Godot client **`POST`/`GET`** move flow ([NS-15](../../plans/NS-15-implementation-plan.md), [NS-16](../../plans/NS-16-implementation-plan.md), `server/NeonSprawl.Server/Game/PositionState/`, `client/scripts/`). **NS-23** — client **`NavigationRegion3D` / `NavigationAgent3D`** for click-to-move visuals while server authority unchanged; **single-click obstacle detours not guaranteed** (see plan tradeoff) ([NS-23](../../plans/NS-23-implementation-plan.md); [client README](../../../client/README.md#authoritative-movement-ns-16-ns-23)). **`InteractionRequest`** + server-side horizontal range check ([NS-18](../../plans/NS-18-implementation-plan.md); `Game/Interaction/`, `Game/World/HorizontalReach.cs`, [server README — Interaction](../../../server/README.md#interaction-ns-18)). See [server README — Position persistence](../../../server/README.md#position-persistence-ns-17). Jira **[NS-10](https://neon-sprawl.atlassian.net/browse/NS-10)** (E1.M1 Feature) is **Done** for this prototype scope. - **Done (prototype):** Server-side authoritative **`PositionState`** read + **`MoveCommand`** apply (HTTP JSON v1, snap-to-target, `sequence` increments); **NEON-7** server-side step + optional district bounds validation with **`reasonCode`** rejections ([NEON-7](../../plans/NEON-7-implementation-plan.md), `MoveCommandValidation`, [server README — Move command](../../../server/README.md#move-command-neon-4-neon-7)); default **in-memory** store; optional **PostgreSQL** persistence when `ConnectionStrings:NeonSprawl` is set ([NEON-5](../../plans/NEON-5-implementation-plan.md)); Godot client **`POST`/`GET`** move flow ([NEON-3](../../plans/NEON-3-implementation-plan.md), [NEON-4](../../plans/NEON-4-implementation-plan.md), `server/NeonSprawl.Server/Game/PositionState/`, `client/scripts/`). **NEON-8** — client **`NavigationRegion3D` / `NavigationAgent3D`** for click-to-move visuals while server authority unchanged; **single-click obstacle detours not guaranteed** (see plan tradeoff) ([NEON-8](../../plans/NEON-8-implementation-plan.md); [client README](../../../client/README.md#authoritative-movement-neon-4-neon-8)). **`InteractionRequest`** + server-side horizontal range check ([NEON-6](../../plans/NEON-6-implementation-plan.md); `Game/Interaction/`, `Game/World/HorizontalReach.cs`, [server README — Interaction](../../../server/README.md#interaction-neon-6)). See [server README — Position persistence](../../../server/README.md#position-persistence-neon-5). Jira **[NEON-9](https://neon-sprawl.atlassian.net/browse/NEON-9)** (E1.M1 Feature) is **Done** for this prototype scope.
- **Follow-on:** Client prediction/reconciliation; Epic 1 Slice 1 telemetry and movement-loop polish; optional **Protobuf** wire promotion for `MoveCommand` / `PositionState` per [contracts.md](contracts.md). - **Follow-on:** Client prediction/reconciliation; Epic 1 Slice 1 telemetry and movement-loop polish; optional **Protobuf** wire promotion for `MoveCommand` / `PositionState` per [contracts.md](contracts.md).
- **Alignment:** [Documentation and implementation alignment](documentation_and_implementation_alignment.md). - **Alignment:** [Documentation and implementation alignment](documentation_and_implementation_alignment.md).

View File

@ -10,7 +10,7 @@ The [module dependency register](module_dependency_register.md) **Status** colum
|--------|------| |--------|------|
| **Register + module markdown** | Intent, dependencies, contracts, policies (authority, PvP, quests, ops). | | **Register + module markdown** | Intent, dependencies, contracts, policies (authority, PvP, quests, ops). |
| **Code + tests** | What actually runs; paths under `server/`, `client/`, etc. | | **Code + tests** | What actually runs; paths under `server/`, `client/`, etc. |
| **Plans** | `docs/plans/` (e.g. NS-* implementation plans) tie stories to acceptance criteria. | | **Plans** | `docs/plans/` (e.g. NEON-* implementation plans) tie stories to acceptance criteria. |
| **This docs tracking table** | Quick **inventory** of modules that have started implementation; expand as work lands. | | **This docs tracking table** | Quick **inventory** of modules that have started implementation; expand as work lands. |
--- ---
@ -45,7 +45,7 @@ Rows appear when work starts; default for unlisted modules is **Planned** / not
| Module | Register status | Snapshot | Plans / pointers | | Module | Register status | Snapshot | Plans / pointers |
|--------|-----------------|----------|-------------------| |--------|-----------------|----------|-------------------|
| E1.M1 | Ready | Prototype milestone **Done** ([NS-10](https://neon-sprawl.atlassian.net/browse/NS-10)). Authoritative `PositionState` + **MoveCommand** over HTTP (JSON v1 snap); **in-memory** store by default, **Postgres** when configured ([NS-17](../../plans/NS-17-implementation-plan.md)); Godot sync + path-follow ([NS-16](../../plans/NS-16-implementation-plan.md), [NS-23](../../plans/NS-23-implementation-plan.md)); **InteractionRequest** + horizontal range ([NS-18](../../plans/NS-18-implementation-plan.md)). Follow-on: prediction/reconciliation, Slice 1 telemetry, Protobuf wire per [contracts.md](contracts.md). | [NS-15](../../plans/NS-15-implementation-plan.md), [NS-16](../../plans/NS-16-implementation-plan.md), [NS-17](../../plans/NS-17-implementation-plan.md), [NS-18](../../plans/NS-18-implementation-plan.md), [NS-19](../../plans/NS-19-implementation-plan.md), [NS-23](../../plans/NS-23-implementation-plan.md); `server/NeonSprawl.Server/Game/PositionState/`, `Game/Interaction/`; [server README](../../../server/README.md) | | E1.M1 | Ready | Prototype milestone **Done** ([NEON-9](https://neon-sprawl.atlassian.net/browse/NEON-9)). Authoritative `PositionState` + **MoveCommand** over HTTP (JSON v1 snap); **in-memory** store by default, **Postgres** when configured ([NEON-5](../../plans/NEON-5-implementation-plan.md)); Godot sync + path-follow ([NEON-4](../../plans/NEON-4-implementation-plan.md), [NEON-8](../../plans/NEON-8-implementation-plan.md)); **InteractionRequest** + horizontal range ([NEON-6](../../plans/NEON-6-implementation-plan.md)). Follow-on: prediction/reconciliation, Slice 1 telemetry, Protobuf wire per [contracts.md](contracts.md). | [NEON-3](../../plans/NEON-3-implementation-plan.md), [NEON-4](../../plans/NEON-4-implementation-plan.md), [NEON-5](../../plans/NEON-5-implementation-plan.md), [NEON-6](../../plans/NEON-6-implementation-plan.md), [NEON-7](../../plans/NEON-7-implementation-plan.md), [NEON-8](../../plans/NEON-8-implementation-plan.md); `server/NeonSprawl.Server/Game/PositionState/`, `Game/Interaction/`; [server README](../../../server/README.md) |
--- ---

View File

@ -1,13 +1,13 @@
# NS-21 — Implementation plan # NEON-14 — Implementation plan
## Story reference ## Story reference
| Field | Value | | Field | Value |
|--------|--------| |--------|--------|
| **Key** | NS-21 | | **Key** | NEON-14 |
| **Title** | Set up GDScript unit testing and cover existing client scripts | | **Title** | Set up GDScript unit testing and cover existing client scripts |
| **Jira** | [NS-21](https://neon-sprawl.atlassian.net/browse/NS-21) | | **Jira** | [NEON-14](https://neon-sprawl.atlassian.net/browse/NEON-14) |
| **Parent** | [NS-20 — Tech Debt](https://neon-sprawl.atlassian.net/browse/NS-20) | | **Parent** | [NEON-13 — Tech Debt](https://neon-sprawl.atlassian.net/browse/NEON-13) |
## Goal, scope, and out-of-scope ## Goal, scope, and out-of-scope
@ -28,7 +28,7 @@
- **Full integration** tests of `main.gd` / `main.tscn` (navigation bake timing, label timers, full pick pipeline)—**intentionally deferred** here: thin composition + `await` chains are brittle without a dedicated scene test harness; README should state that explicitly (per Jira). - **Full integration** tests of `main.gd` / `main.tscn` (navigation bake timing, label timers, full pick pipeline)—**intentionally deferred** here: thin composition + `await` chains are brittle without a dedicated scene test harness; README should state that explicitly (per Jira).
- Replacing **server** tests or adding **E2E** browser/gameplay automation. - Replacing **server** tests or adding **E2E** browser/gameplay automation.
- Changing **production** gameplay contracts (NS-16/19/23) except where a test exposes a real bug (unlikely in this story). - Changing **production** gameplay contracts (NEON-4/19/23) except where a test exposes a real bug (unlikely in this story).
**Ongoing policy (effective once this story merges):** Any change that **adds or modifies** production GDScript under **`client/scripts/`** (and any future app `.gd` outside **`client/addons/`** and **`client/test/`**) must **add or update** unit tests under **`client/test/`** in the same change set, unless the PR or plan **explicitly documents** a justified exception (e.g. generated or no-logic glue). **`client/README.md`** should state this expectation for contributors; **[`.cursor/rules/testing-expectations.md`](../../.cursor/rules/testing-expectations.md)** is updated to match. **Ongoing policy (effective once this story merges):** Any change that **adds or modifies** production GDScript under **`client/scripts/`** (and any future app `.gd` outside **`client/addons/`** and **`client/test/`**) must **add or update** unit tests under **`client/test/`** in the same change set, unless the PR or plan **explicitly documents** a justified exception (e.g. generated or no-logic glue). **`client/README.md`** should state this expectation for contributors; **[`.cursor/rules/testing-expectations.md`](../../.cursor/rules/testing-expectations.md)** is updated to match.

View File

@ -1,23 +1,23 @@
# NS-24 — Implementation plan # NEON-16 — Implementation plan
## Story reference ## Story reference
| Field | Value | | Field | Value |
|--------|--------| |--------|--------|
| **Key** | NS-24 | | **Key** | NEON-16 |
| **Title** | Godot client: eliminate CharacterBody3D idle / rest vibration | | **Title** | Godot client: eliminate CharacterBody3D idle / rest vibration |
| **Jira** | [NS-24](https://neon-sprawl.atlassian.net/browse/NS-24) | | **Jira** | [NEON-16](https://neon-sprawl.atlassian.net/browse/NEON-16) |
| **Parent** | [NS-20 — Tech Debt](https://neon-sprawl.atlassian.net/browse/NS-20) | | **Parent** | [NEON-13 — Tech Debt](https://neon-sprawl.atlassian.net/browse/NEON-13) |
| **Related** | [NS-23](https://neon-sprawl.atlassian.net/browse/NS-23) (path-follow baseline; idle jitter explicitly deferred here) | | **Related** | [NEON-8](https://neon-sprawl.atlassian.net/browse/NEON-8) (path-follow baseline; idle jitter explicitly deferred here) |
## Goal, scope, and out-of-scope ## Goal, scope, and out-of-scope
**Goal:** **Stable rest pose** — no perceptible micro-vibration when horizontal velocity is zero, there is no active walk goal, and the avatar stands on walkable geometry, including prototype floors and **NS-19** stepped bumps. **Goal:** **Stable rest pose** — no perceptible micro-vibration when horizontal velocity is zero, there is no active walk goal, and the avatar stands on walkable geometry, including prototype floors and **NEON-7** stepped bumps.
**In scope** **In scope**
- Diagnose and fix (or materially reduce) idle jitter on the prototype **`CharacterBody3D`** player driven by **`player.gd`** (`move_and_slide`, floor interaction, **`NavigationAgent3D`** coupling as relevant). - Diagnose and fix (or materially reduce) idle jitter on the prototype **`CharacterBody3D`** player driven by **`player.gd`** (`move_and_slide`, floor interaction, **`NavigationAgent3D`** coupling as relevant).
- Preserve **NS-23** behavior: walking, arrival at goal, **`snap_to_server`** on boot, descend bypass tradeoff, and **NS-19** validation UX on the server side (client must not regress those flows). - Preserve **NEON-8** behavior: walking, arrival at goal, **`snap_to_server`** on boot, descend bypass tradeoff, and **NEON-7** validation UX on the server side (client must not regress those flows).
- Document non-obvious fixes in **`client/README.md`** and/or the **`player.gd`** header (per Jira). - Document non-obvious fixes in **`client/README.md`** and/or the **`player.gd`** header (per Jira).
**Out of scope** **Out of scope**
@ -28,7 +28,7 @@
## Acceptance criteria checklist ## Acceptance criteria checklist
- [x] At rest (no click-to-move goal, feet on floor), **no visible vibration** in the default prototype scene for **several seconds** of observation. - [x] At rest (no click-to-move goal, feet on floor), **no visible vibration** in the default prototype scene for **several seconds** of observation.
- [x] **Walking**, **arrival**, and **`snap_to_server`** still behave correctly; **no regression** on NS-19 bumps or the **NS-23** obstacle / descend-bypass tradeoff documented in **`client/README.md`**. - [x] **Walking**, **arrival**, and **`snap_to_server`** still behave correctly; **no regression** on NEON-7 bumps or the **NEON-8** obstacle / descend-bypass tradeoff documented in **`client/README.md`**.
- [x] If a **scene** or **project** setting change is required, it is **checked in** and called out for designers (README or short comment in scene notes). - [x] If a **scene** or **project** setting change is required, it is **checked in** and called out for designers (README or short comment in scene notes).
## Resolution (shipped) ## Resolution (shipped)
@ -43,7 +43,7 @@
4. **Camera / parenting** — rule out camera or child node noise if transform root is stable. 4. **Camera / parenting** — rule out camera or child node noise if transform root is stable.
5. **Ship** the smallest change that clears the AC (code, scene, or project), then update **`client/README.md`** to remove or narrow the “Known issue” idle jitter line and describe the chosen pattern. 5. **Ship** the smallest change that clears the AC (code, scene, or project), then update **`client/README.md`** to remove or narrow the “Known issue” idle jitter line and describe the chosen pattern.
**Shipped note:** Idle uses **non-zero** **`floor_snap_length`** (see **`player.gd`**) rather than zero snap from the investigation list; straddle/rim logic and bump geometry split were the main levers. Code review: **`docs/reviews/2026-04-05-NS-24.md`**. **Shipped note:** Idle uses **non-zero** **`floor_snap_length`** (see **`player.gd`**) rather than zero snap from the investigation list; straddle/rim logic and bump geometry split were the main levers. Code review: **`docs/reviews/2026-04-05-NEON-16.md`**.
## Files to add ## Files to add
@ -64,7 +64,7 @@
| Action | What to cover | | Action | What to cover |
|--------|----------------| |--------|----------------|
| **Manual (required)** | Run main scene with server as in **NS-16/NS-23** README steps: stand idle **10+ s** at spawn and after a move; click-move including **stepped bumps** (NS-19 props); cold boot **`snap_to_server`**; far/vertical rejects still show NS-19 UX; optional multi-click around **`Obstacle`** per NS-23 tradeoff. | | **Manual (required)** | Run main scene with server as in **NEON-4/NEON-8** README steps: stand idle **10+ s** at spawn and after a move; click-move including **stepped bumps** (NEON-7 props); cold boot **`snap_to_server`**; far/vertical rejects still show NEON-7 UX; optional multi-click around **`Obstacle`** per NEON-8 tradeoff. |
| **Automated** | **None** — no Godot/GDScript test harness in repo; server **xUnit** suites unchanged unless an unrelated regression appears (not expected). | | **Automated** | **None** — no Godot/GDScript test harness in repo; server **xUnit** suites unchanged unless an unrelated regression appears (not expected). |
## Open questions / risks ## Open questions / risks
@ -73,4 +73,4 @@
## PR / review ## PR / review
Cross-check [E1.M1 — InputAndMovementRuntime](../decomposition/modules/E1_M1_InputAndMovementRuntime.md), **`docs/plans/NS-23-implementation-plan.md`** (residual polish pointer), and **`client/README.md`** after changes. Cross-check [E1.M1 — InputAndMovementRuntime](../decomposition/modules/E1_M1_InputAndMovementRuntime.md), **`docs/plans/NEON-8-implementation-plan.md`** (residual polish pointer), and **`client/README.md`** after changes.

View File

@ -1,13 +1,13 @@
# NS-14 — Implementation plan # NEON-2 — Implementation plan
## Story reference ## Story reference
| Field | Value | | Field | Value |
|--------|--------| |--------|--------|
| **Key** | NS-14 | | **Key** | NEON-2 |
| **Title** | E1.M1: Click-to-move prototype (client-only) | | **Title** | E1.M1: Click-to-move prototype (client-only) |
| **Jira** | [NS-14](https://neon-sprawl.atlassian.net/browse/NS-14) | | **Jira** | [NEON-2](https://neon-sprawl.atlassian.net/browse/NEON-2) |
| **Parent context** | [NS-10 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NS-10) | | **Parent context** | [NEON-9 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NEON-9) |
## Goal, scope, and out-of-scope ## Goal, scope, and out-of-scope
@ -46,8 +46,8 @@
- `_unhandled_input` or `_input`: on left mouse button, raycast from camera through cursor into the physics world; if the collider is the ground (or a dedicated “walkable” layer/mask), set the navigation target. - `_unhandled_input` or `_input`: on left mouse button, raycast from camera through cursor into the physics world; if the collider is the ground (or a dedicated “walkable” layer/mask), set the navigation target.
4. **Documentation** 4. **Documentation**
- Update `client/README.md` with a short “Movement prototype (NS-14)” subsection: mouse click-to-move, navigation-based obstacle avoidance, **not** server-authoritative. - Update `client/README.md` with a short “Movement prototype (NEON-2)” subsection: mouse click-to-move, navigation-based obstacle avoidance, **not** server-authoritative.
- Optional: `Editable Children` note on root node in scene or a comment in `main.gd` pointing to NS-14 / temporariness. - Optional: `Editable Children` note on root node in scene or a comment in `main.gd` pointing to NEON-2 / temporariness.
## Files to add ## Files to add
@ -79,4 +79,4 @@
- **Navigation baking:** First-time contributors need to bake the navigation mesh in the editor after pulling scene changes; document that in README if it is not obvious. - **Navigation baking:** First-time contributors need to bake the navigation mesh in the editor after pulling scene changes; document that in README if it is not obvious.
- **Isometric later:** Camera rig may be replaced for isometric; keep movement logic independent of camera style where possible (raycast from current camera). - **Isometric later:** Camera rig may be replaced for isometric; keep movement logic independent of camera style where possible (raycast from current camera).
- **Performance:** Single-agent prototype is sufficient; no need to optimize for crowds in NS-14. - **Performance:** Single-agent prototype is sufficient; no need to optimize for crowds in NEON-2.

View File

@ -1,14 +1,14 @@
# NS-15 — Implementation plan # NEON-3 — Implementation plan
## Story reference ## Story reference
| Field | Value | | Field | Value |
| ------------------ | --------------------------------------------------------------------------------------- | | ------------------ | --------------------------------------------------------------------------------------- |
| **Key** | NS-15 | | **Key** | NEON-3 |
| **Title** | E1.M1: Authoritative PositionState API (in-memory) | | **Title** | E1.M1: Authoritative PositionState API (in-memory) |
| **Jira** | [NS-15](https://neon-sprawl.atlassian.net/browse/NS-15) | | **Jira** | [NEON-3](https://neon-sprawl.atlassian.net/browse/NEON-3) |
| **Parent context** | [NS-10 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NS-10) | | **Parent context** | [NEON-9 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NEON-9) |
## Goal, scope, and out-of-scope ## Goal, scope, and out-of-scope
@ -54,7 +54,7 @@
- One integration test: `GET` for the configured dev player returns **200**, body parses, **`schemaVersion`** present, **`position.x`**, **`position.y`**, **`position.z`** present. - One integration test: `GET` for the configured dev player returns **200**, body parses, **`schemaVersion`** present, **`position.x`**, **`position.y`**, **`position.z`** present.
- Optional second test: unknown id returns **404**. - Optional second test: unknown id returns **404**.
6. **Documentation** 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. - Extend `server/README.md` with “Position state (NEON-3)”: endpoint path, example `curl`, note that state is in-memory and authoritative only at the HTTP spike layer.
## Files to add ## Files to add
@ -88,7 +88,7 @@
## Pull request description (Jira AC) ## Pull request description (Jira AC)
Jira NS-15 asks for stable JSON **field names** documented for reviewers outside the repo. **When you open the GitHub PR** (and optionally comment on [NS-15](https://neon-sprawl.atlassian.net/browse/NS-15)), paste the following so AC is satisfied without hunting the README. Jira NEON-3 asks for stable JSON **field names** documented for reviewers outside the repo. **When you open the GitHub PR** (and optionally comment on [NEON-3](https://neon-sprawl.atlassian.net/browse/NEON-3)), paste the following so AC is satisfied without hunting the README.
**Endpoint:** `GET /game/players/{id}/position`**200** when the player exists, **404** when unknown. **Endpoint:** `GET /game/players/{id}/position`**200** when the player exists, **404** when unknown.
@ -115,12 +115,12 @@ Jira NS-15 asks for stable JSON **field names** documented for reviewers outside
| `position.x` | number | X coordinate. | | `position.x` | number | X coordinate. |
| `position.y` | number | Y coordinate. | | `position.y` | number | Y coordinate. |
| `position.z` | number | Z coordinate. | | `position.z` | number | Z coordinate. |
| `sequence` | number (integer) | Increments on each successful move apply ([NS-16](NS-16-implementation-plan.md)); `0` until first move for the seeded dev player. | | `sequence` | number (integer) | Increments on each successful move apply ([NEON-4](NEON-4-implementation-plan.md)); `0` until first move for the seeded dev player. |
**v1 omissions:** `facing` is not included (see Open questions). JSON uses **camelCase** property names (ASP.NET Core default serialization). **v1 omissions:** `facing` is not included (see Open questions). JSON uses **camelCase** property names (ASP.NET Core default serialization).
## Open questions / risks ## 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. - **Facing convention:** If `facing` is included, confirm units (radians vs degrees) with NEON-9 / 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. - **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. - **CI:** If no workflow runs `dotnet test` yet, local `dotnet test` still satisfies AC; consider a follow-up chore to wire CI when available.

View File

@ -1,13 +1,13 @@
# NS-16 — Implementation plan # NEON-4 — Implementation plan
## Story reference ## Story reference
| Field | Value | | Field | Value |
|--------|--------| |--------|--------|
| **Key** | NS-16 | | **Key** | NEON-4 |
| **Title** | E1.M1: Submit MoveCommand → server updates PositionState | | **Title** | E1.M1: Submit MoveCommand → server updates PositionState |
| **Jira** | [NS-16](https://neon-sprawl.atlassian.net/browse/NS-16) | | **Jira** | [NEON-4](https://neon-sprawl.atlassian.net/browse/NEON-4) |
| **Parent context** | [NS-10 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NS-10) | | **Parent context** | [NEON-9 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NEON-9) |
## Goal, scope, and out-of-scope ## Goal, scope, and out-of-scope
@ -16,8 +16,8 @@
**In scope** **In scope**
- **Server:** HTTP endpoint accepting a **MoveCommand** JSON payload: at minimum **target position** + **player id** (route and/or body; dev session using configured `Game:DevPlayerId` is sufficient). - **Server:** HTTP endpoint accepting a **MoveCommand** JSON payload: at minimum **target position** + **player id** (route and/or body; dev session using configured `Game:DevPlayerId` is sufficient).
- **Server rule (document in plan + XML on types):** apply movement with a single clear rule. **Planned v1 rule:** **snap** authoritative position to the commands target immediately (no fixed-step simulation). Increment **`sequence`** on each successful apply so consumers can detect updates (NS-15 left `sequence` at `0`; this story advances semantics). - **Server rule (document in plan + XML on types):** apply movement with a single clear rule. **Planned v1 rule:** **snap** authoritative position to the commands target immediately (no fixed-step simulation). Increment **`sequence`** on each successful apply so consumers can detect updates (NEON-3 left `sequence` at `0`; this story advances semantics).
- **Client:** Build on [NS-14](NS-14-implementation-plan.md) click-to-move: add a minimal **HTTP** client to **submit** the command and **confirm** state via **`GET`** (poll or follow-up GET after POST — both satisfy “read back”). - **Client:** Build on [NEON-2](NEON-2-implementation-plan.md) click-to-move: add a minimal **HTTP** client to **submit** the command and **confirm** state via **`GET`** (poll or follow-up GET after POST — both satisfy “read back”).
- **Wire format:** JSON for the prototype (explicit throwaway per [`docs/decomposition/modules/contracts.md`](../decomposition/modules/contracts.md) until protobuf). - **Wire format:** JSON for the prototype (explicit throwaway per [`docs/decomposition/modules/contracts.md`](../decomposition/modules/contracts.md) until protobuf).
**Out of scope (per Jira)** **Out of scope (per Jira)**
@ -26,8 +26,8 @@
**Dependencies** **Dependencies**
- [NS-15](NS-15-implementation-plan.md): in-memory `PositionState` **read** API and store. - [NEON-3](NEON-3-implementation-plan.md): in-memory `PositionState` **read** API and store.
- [NS-14](NS-14-implementation-plan.md): click-to-move scaffold. - [NEON-2](NEON-2-implementation-plan.md): click-to-move scaffold.
## Acceptance criteria checklist ## Acceptance criteria checklist
@ -39,7 +39,7 @@
1. **MoveCommand (v1 JSON)** 1. **MoveCommand (v1 JSON)**
- New request DTO (e.g. `MoveCommandRequest`) with top-level **`schemaVersion`** (`1`) and **`target`**: object with **`x`**, **`y`**, **`z`** (same numeric style as `PositionVector`). - New request DTO (e.g. `MoveCommandRequest`) with top-level **`schemaVersion`** (`1`) and **`target`**: object with **`x`**, **`y`**, **`z`** (same numeric style as `PositionVector`).
- **Endpoint:** `POST /game/players/{id}/move` with JSON body; `{id}` identifies the player (trimmed, case-insensitive match to store, consistent with NS-15 `GET`). - **Endpoint:** `POST /game/players/{id}/move` with JSON body; `{id}` identifies the player (trimmed, case-insensitive match to store, consistent with NEON-3 `GET`).
- **Responses:** **200** + body same shape as **`PositionStateResponse`** after apply; **404** if player unknown; **400** if `schemaVersion` ≠ 1 or payload invalid / missing `target`. - **Responses:** **200** + body same shape as **`PositionStateResponse`** after apply; **404** if player unknown; **400** if `schemaVersion` ≠ 1 or payload invalid / missing `target`.
2. **Store mutation** 2. **Store mutation**
@ -53,14 +53,14 @@
- Unknown player → **404**; bad `schemaVersion`**400**. - Unknown player → **404**; bad `schemaVersion`**400**.
5. **Godot client****Split scripts by concern** (required repo policy: [godot-client-script-organization.md](../../.cursor/rules/godot-client-script-organization.md)); do **not** grow a monolithic `main.gd` with pick + HTTP + wiring. 5. **Godot client****Split scripts by concern** (required repo policy: [godot-client-script-organization.md](../../.cursor/rules/godot-client-script-organization.md)); do **not** grow a monolithic `main.gd` with pick + HTTP + wiring.
- **`ground_pick.gd`** on a new **`Node3D`** child (e.g. `GroundPick`): owns walkable **ray pick** (logic moved out of NS-14 `main.gd`). Exposes **`target_chosen(Vector3)`** when the user clicks valid ground. **`main.gd`** sets **`fallback_camera`** (or equivalent) in `_ready`. - **`ground_pick.gd`** on a new **`Node3D`** child (e.g. `GroundPick`): owns walkable **ray pick** (logic moved out of NEON-2 `main.gd`). Exposes **`target_chosen(Vector3)`** when the user clicks valid ground. **`main.gd`** sets **`fallback_camera`** (or equivalent) in `_ready`.
- **`position_authority_client.gd`** on a new **`Node`** child: `@export` **base URL** (default `http://127.0.0.1:5253`, match `launchSettings.json`) and **dev player id** (`dev-local-1`). Creates **`HTTPRequest`** in **`_ready`** via **`add_child`** (not as a node hand-placed in `main.tscn`). Flow: **`POST`** `MoveCommand`, then **`GET`** position (tests must show **GET** reflects apply). Emits **`authoritative_position_received(Vector3, bool)`** (NS-23: second arg = boot snap vs nav goal) so **`main`** snaps on boot and path-follows after move verify. - **`position_authority_client.gd`** on a new **`Node`** child: `@export` **base URL** (default `http://127.0.0.1:5253`, match `launchSettings.json`) and **dev player id** (`dev-local-1`). Creates **`HTTPRequest`** in **`_ready`** via **`add_child`** (not as a node hand-placed in `main.tscn`). Flow: **`POST`** `MoveCommand`, then **`GET`** position (tests must show **GET** reflects apply). Emits **`authoritative_position_received(Vector3, bool)`** (NEON-8: second arg = boot snap vs nav goal) so **`main`** snaps on boot and path-follows after move verify.
- **`main.gd`:** Thin composer only—connect signals, call **`sync_from_server()`** on run, delegate pick and HTTP to the two child scripts. - **`main.gd`:** Thin composer only—connect signals, call **`sync_from_server()`** on run, delegate pick and HTTP to the two child scripts.
- Align **client README** with NS-14 note: server authority path replaces pure local steering for this demo. - Align **client README** with NEON-2 note: server authority path replaces pure local steering for this demo.
6. **Documentation** 6. **Documentation**
- **`server/README.md`:** new subsection for move submission: method, path, sample **`curl`**, v1 snap rule, link to DTO XML. - **`server/README.md`:** new subsection for move submission: method, path, sample **`curl`**, v1 snap rule, link to DTO XML.
- **`client/README.md`:** how to run server + client for NS-16 demo, exported vars, expectation of snap-to-server. - **`client/README.md`:** how to run server + client for NEON-4 demo, exported vars, expectation of snap-to-server.
## Files to add ## Files to add
@ -77,20 +77,20 @@
| `server/NeonSprawl.Server/Game/PositionState/IPositionStateStore.cs` | Declare authoritative apply API. | | `server/NeonSprawl.Server/Game/PositionState/IPositionStateStore.cs` | Declare authoritative apply API. |
| `server/NeonSprawl.Server/Game/PositionState/InMemoryPositionStateStore.cs` | Implement snap + sequence increment. | | `server/NeonSprawl.Server/Game/PositionState/InMemoryPositionStateStore.cs` | Implement snap + sequence increment. |
| `server/NeonSprawl.Server/Game/PositionState/PositionStateApi.cs` | Map `POST /game/players/{id}/move`. | | `server/NeonSprawl.Server/Game/PositionState/PositionStateApi.cs` | Map `POST /game/players/{id}/move`. |
| `server/NeonSprawl.Server/Game/PositionState/PositionStateResponse.cs` | XML: clarify **`sequence`** increments after moves (postNS-15). | | `server/NeonSprawl.Server/Game/PositionState/PositionStateResponse.cs` | XML: clarify **`sequence`** increments after moves (postNEON-3). |
| `server/NeonSprawl.Server.Tests/Game/PositionState/MoveCommandApiTests.cs` | `POST` + `GET` integration and error cases. | | `server/NeonSprawl.Server.Tests/Game/PositionState/MoveCommandApiTests.cs` | `POST` + `GET` integration and error cases. |
| `server/NeonSprawl.Server.Tests/Game/PositionState/PositionStateApiTests.cs` | Existing `GET` integration tests (same feature folder). | | `server/NeonSprawl.Server.Tests/Game/PositionState/PositionStateApiTests.cs` | Existing `GET` integration tests (same feature folder). |
| `client/scripts/main.gd` | **Thin composer:** add/wire `GroundPick` + `PositionAuthorityClient`, connect signals, snap player to server (remove inlined pick + HTTP). | | `client/scripts/main.gd` | **Thin composer:** add/wire `GroundPick` + `PositionAuthorityClient`, connect signals, snap player to server (remove inlined pick + HTTP). |
| `client/scenes/main.tscn` | Add child nodes that attach **`ground_pick.gd`** and **`position_authority_client.gd`** only. **Do not** add **`HTTPRequest`** to the scene—the authority script owns and instantiates it. | | `client/scenes/main.tscn` | Add child nodes that attach **`ground_pick.gd`** and **`position_authority_client.gd`** only. **Do not** add **`HTTPRequest`** to the scene—the authority script owns and instantiates it. |
| `client/scripts/player.gd` | Only if needed (e.g. **`velocity = ZERO`** on snap, or helper **`snap_to(world_pos)`**); prefer minimal changes. | | `client/scripts/player.gd` | Only if needed (e.g. **`velocity = ZERO`** on snap, or helper **`snap_to(world_pos)`**); prefer minimal changes. |
| `server/README.md` | Move command + curl + v1 behavior. | | `server/README.md` | Move command + curl + v1 behavior. |
| `client/README.md` | NS-16 runbook and authority note. | | `client/README.md` | NEON-4 runbook and authority note. |
## Tests ## Tests
- **Automated:** xUnit integration tests via `WebApplicationFactory<Program>``POST` then `GET` equality, `404` / `400` as above. - **Automated:** xUnit integration tests via `WebApplicationFactory<Program>``POST` then `GET` equality, `404` / `400` as above.
- **Manual:** Godot F5 with server running; click floor; avatar snaps to server-reported position. - **Manual:** Godot F5 with server running; click floor; avatar snaps to server-reported position.
- **No new client automated test harness** in scope unless one already exists (none planned for NS-16). - **No new client automated test harness** in scope unless one already exists (none planned for NEON-4).
## Pull request description (Jira AC) — draft for merge ## Pull request description (Jira AC) — draft for merge
@ -114,9 +114,9 @@ Paste when opening the PR (adjust if shapes differ slightly in implementation).
| `target` | object | Destination world position. | | `target` | object | Destination world position. |
| `target.x` / `y` / `z` | number | Coordinates (snap applied server-side). | | `target.x` / `y` / `z` | number | Coordinates (snap applied server-side). |
**PositionState v1 — response** (unchanged from NS-15; **`sequence`** increments after each successful move) **PositionState v1 — response** (unchanged from NEON-3; **`sequence`** increments after each successful move)
- Same as NS-15 **`GET`** response body; successful **`POST`** returns the updated snapshot. - Same as NEON-3 **`GET`** response body; successful **`POST`** returns the updated snapshot.
**Server rule:** position is set to **`target`** immediately (**snap**); **`sequence`** increases by one per successful apply. **Server rule:** position is set to **`target`** immediately (**snap**); **`sequence`** increases by one per successful apply.

View File

@ -1,13 +1,13 @@
# NS-17 — Implementation plan # NEON-5 — Implementation plan
## Story reference ## Story reference
| Field | Value | | Field | Value |
|--------|--------| |--------|--------|
| **Key** | NS-17 | | **Key** | NEON-5 |
| **Title** | E1.M1: Persist character position (PostgreSQL) | | **Title** | E1.M1: Persist character position (PostgreSQL) |
| **Jira** | [NS-17](https://neon-sprawl.atlassian.net/browse/NS-17) | | **Jira** | [NEON-5](https://neon-sprawl.atlassian.net/browse/NEON-5) |
| **Parent context** | [NS-10 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NS-10) · [NS-16](NS-16-implementation-plan.md) (MoveCommand → `PositionState`) | | **Parent context** | [NEON-9 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NEON-9) · [NEON-4](NEON-4-implementation-plan.md) (MoveCommand → `PositionState`) |
## Goal, scope, and out-of-scope ## Goal, scope, and out-of-scope
@ -32,7 +32,7 @@
- [ ] Restart **`dotnet run`** with the same Postgres volume: last committed position is still returned from **`GET /game/players/{id}/position`** (after a prior **`POST …/move`**). - [ ] Restart **`dotnet run`** with the same Postgres volume: last committed position is still returned from **`GET /game/players/{id}/position`** (after a prior **`POST …/move`**).
- [ ] Connection / database configuration documented (env vars or `ConnectionStrings`, aligned with ASP.NET conventions). - [ ] Connection / database configuration documented (env vars or `ConnectionStrings`, aligned with ASP.NET conventions).
- [ ] No regression to the **in-memory** happy path: default **`dotnet test`** / CI without Postgres continues to behave as NS-15/NS-16 (see [Tests](#tests)). - [ ] No regression to the **in-memory** happy path: default **`dotnet test`** / CI without Postgres continues to behave as NEON-3/NEON-4 (see [Tests](#tests)).
## Technical approach ## Technical approach
@ -93,4 +93,4 @@
## Pull request description (draft) ## Pull request description (draft)
**NS-17:** Persist `PositionState` in PostgreSQL when a connection string is configured; keep in-memory store when not. Adds DDL + Npgsql-backed `IPositionStateStore`, **PostgreSQL integration tests** (including simulating restart via a second web factory), **Postgres service in `dotnet.yml`**, and README notes for env vars and local test runs. **NEON-5:** Persist `PositionState` in PostgreSQL when a connection string is configured; keep in-memory store when not. Adds DDL + Npgsql-backed `IPositionStateStore`, **PostgreSQL integration tests** (including simulating restart via a second web factory), **Postgres service in `dotnet.yml`**, and README notes for env vars and local test runs.

View File

@ -1,20 +1,20 @@
# NS-18 — Implementation plan # NEON-6 — Implementation plan
## Story reference ## Story reference
| Field | Value | | Field | Value |
|--------|--------| |--------|--------|
| **Key** | NS-18 | | **Key** | NEON-6 |
| **Title** | E1.M1: InteractionRequest with server-side range check | | **Title** | E1.M1: InteractionRequest with server-side range check |
| **Jira** | [NS-18](https://neon-sprawl.atlassian.net/browse/NS-18) | | **Jira** | [NEON-6](https://neon-sprawl.atlassian.net/browse/NEON-6) |
| **Parent context** | [NS-1 — Epic 1 — Core Player Runtime](https://neon-sprawl.atlassian.net/browse/NS-1) · [NS-10 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NS-10) | | **Parent context** | [NEON-1 — Epic 1 — Core Player Runtime](https://neon-sprawl.atlassian.net/browse/NEON-1) · [NEON-9 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NEON-9) |
| **Decomposition** | [E1.M1 — InputAndMovementRuntime](../decomposition/modules/E1_M1_InputAndMovementRuntime.md) (range / authority); seeds [E1.M3 — InteractionAndTargetingLayer](../decomposition/modules/E1_M3_InteractionAndTargetingLayer.md) contracts | | **Decomposition** | [E1.M1 — InputAndMovementRuntime](../decomposition/modules/E1_M1_InputAndMovementRuntime.md) (range / authority); seeds [E1.M3 — InteractionAndTargetingLayer](../decomposition/modules/E1_M3_InteractionAndTargetingLayer.md) contracts |
**Delivery:** Ship **`docs/plans/NS-18-implementation-plan.md`** on the **same branch / PR** as the NS-18 implementation (plan + code pushed together). **Delivery:** Ship **`docs/plans/NEON-6-implementation-plan.md`** on the **same branch / PR** as the NEON-6 implementation (plan + code pushed together).
## Goal, scope, and out-of-scope ## Goal, scope, and out-of-scope
**Goal:** **Server-authoritative** answer to “can this player use this interactable **now**?” using the same **position source of truth** as movement ([NS-15](NS-15-implementation-plan.md) / [NS-16](NS-16-implementation-plan.md) / [NS-17](NS-17-implementation-plan.md)). **Goal:** **Server-authoritative** answer to “can this player use this interactable **now**?” using the same **position source of truth** as movement ([NEON-3](NEON-3-implementation-plan.md) / [NEON-4](NEON-4-implementation-plan.md) / [NEON-5](NEON-5-implementation-plan.md)).
**Design decision — floor plane only (Y ignored for reach)** **Design decision — floor plane only (Y ignored for reach)**
@ -33,7 +33,7 @@ The game is **3D**, but **prototype interaction and movement tuning** treat all
**Dependencies** **Dependencies**
- Authoritative **`PositionState`** + **move** path ([NS-15](NS-15-implementation-plan.md), [NS-16](NS-16-implementation-plan.md)); optional Postgres ([NS-17](NS-17-implementation-plan.md)) unchanged—interaction reads current store only. - Authoritative **`PositionState`** + **move** path ([NEON-3](NEON-3-implementation-plan.md), [NEON-4](NEON-4-implementation-plan.md)); optional Postgres ([NEON-5](NEON-5-implementation-plan.md)) unchanged—interaction reads current store only.
## Acceptance criteria checklist ## Acceptance criteria checklist
@ -111,8 +111,8 @@ Authoritative values live in **`PrototypeInteractableRegistry.cs`**. **Godot** a
### 7. Documentation ### 7. Documentation
- **`server/README.md`:** subsection **Interaction (NS-18)** — endpoint, curl example, `reasonCode` table, note on horizontal distance. - **`server/README.md`:** subsection **Interaction (NEON-6)** — endpoint, curl example, `reasonCode` table, note on horizontal distance.
- **`docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md`** (and optionally **`documentation_and_implementation_alignment.md`**): one-line snapshot that **InteractionRequest** + range check landed (NS-18). - **`docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md`** (and optionally **`documentation_and_implementation_alignment.md`**): one-line snapshot that **InteractionRequest** + range check landed (NEON-6).
## Files to add ## Files to add
@ -136,9 +136,9 @@ Authoritative values live in **`PrototypeInteractableRegistry.cs`**. **Godot** a
| `server/NeonSprawl.Server/Program.cs` | Register `MapInteractionApi()`. | | `server/NeonSprawl.Server/Program.cs` | Register `MapInteractionApi()`. |
| `client/scenes/main.tscn` | Add prototype interactable + **2+ glow marker meshes** (no collision); materials suitable for emission toggle. | | `client/scenes/main.tscn` | Add prototype interactable + **2+ glow marker meshes** (no collision); materials suitable for emission toggle. |
| `client/scripts/main.gd` | Compose interaction client (signals/exports). | | `client/scripts/main.gd` | Compose interaction client (signals/exports). |
| `client/README.md` | NS-18 manual check: markers **glow** in-range / **dim** out-of-range (preview = same XZ+radius as server); walk + **E**; compare **Output** to glow. | | `client/README.md` | NEON-6 manual check: markers **glow** in-range / **dim** out-of-range (preview = same XZ+radius as server); walk + **E**; compare **Output** to glow. |
| `server/README.md` | Interaction API + reason codes + distance rule. | | `server/README.md` | Interaction API + reason codes + distance rule. |
| `docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | Implementation snapshot for NS-18. | | `docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | Implementation snapshot for NEON-6. |
| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | Optional tracking row update for E1.M1. | | `docs/decomposition/modules/documentation_and_implementation_alignment.md` | Optional tracking row update for E1.M1. |
## Tests ## Tests

View File

@ -1,16 +1,16 @@
# NS-19 — Implementation plan # NEON-7 — Implementation plan
## Story reference ## Story reference
| Field | Value | | Field | Value |
|--------|--------| |--------|--------|
| **Key** | NS-19 | | **Key** | NEON-7 |
| **Title** | E1.M1: Server movement validation (speed + bounds) | | **Title** | E1.M1: Server movement validation (speed + bounds) |
| **Jira** | [NS-19](https://neon-sprawl.atlassian.net/browse/NS-19) | | **Jira** | [NEON-7](https://neon-sprawl.atlassian.net/browse/NEON-7) |
| **Parent context** | [NS-1 — Epic 1 — Core Player Runtime](https://neon-sprawl.atlassian.net/browse/NS-1) · [NS-10 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NS-10) | | **Parent context** | [NEON-1 — Epic 1 — Core Player Runtime](https://neon-sprawl.atlassian.net/browse/NEON-1) · [NEON-9 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NEON-9) |
| **Decomposition** | [E1.M1 — InputAndMovementRuntime](../decomposition/modules/E1_M1_InputAndMovementRuntime.md) (`MoveCommand` / `PositionState` authority) | | **Decomposition** | [E1.M1 — InputAndMovementRuntime](../decomposition/modules/E1_M1_InputAndMovementRuntime.md) (`MoveCommand` / `PositionState` authority) |
**Delivery:** Ship **`docs/plans/NS-19-implementation-plan.md`** on the **same branch / PR** as the NS-19 implementation (plan + code pushed together). **Delivery:** Ship **`docs/plans/NEON-7-implementation-plan.md`** on the **same branch / PR** as the NEON-7 implementation (plan + code pushed together).
## Goal, scope, and out-of-scope ## Goal, scope, and out-of-scope
@ -18,7 +18,7 @@
**Design alignment — floor play (XZ emphasis)** **Design alignment — floor play (XZ emphasis)**
Match [NS-18](NS-18-implementation-plan.md): prototype gameplay is **on the floor**. **Primary** move cap should be **horizontal displacement on X/Z** (same spirit as `HorizontalReach`). Optionally cap **|ΔY|** separately so small floor bumps are allowed but vertical teleports are not. Document the exact rule in code and server README. Match [NEON-6](NEON-6-implementation-plan.md): prototype gameplay is **on the floor**. **Primary** move cap should be **horizontal displacement on X/Z** (same spirit as `HorizontalReach`). Optionally cap **|ΔY|** separately so small floor bumps are allowed but vertical teleports are not. Document the exact rule in code and server README.
**In scope** **In scope**
@ -34,11 +34,11 @@ Match [NS-18](NS-18-implementation-plan.md): prototype gameplay is **on the floo
**Dependencies** **Dependencies**
- **`MoveCommand` endpoint** and stores ([NS-15](NS-15-implementation-plan.md), [NS-16](NS-16-implementation-plan.md), [NS-17](NS-17-implementation-plan.md)) — **done**. - **`MoveCommand` endpoint** and stores ([NEON-3](NEON-3-implementation-plan.md), [NEON-4](NEON-4-implementation-plan.md), [NEON-5](NEON-5-implementation-plan.md)) — **done**.
## Policy — reject (locked) ## Policy — reject (locked)
Invalid moves are **rejected**: return the error payload; **do not** change authoritative position or sequence. Client can log **`reasonCode`** or show a short label. **Clamp** (silent or partial apply) is **out of scope** for NS-19; a future story would need explicit product rules if we ever add it. Invalid moves are **rejected**: return the error payload; **do not** change authoritative position or sequence. Client can log **`reasonCode`** or show a short label. **Clamp** (silent or partial apply) is **out of scope** for NEON-7; a future story would need explicit product rules if we ever add it.
## Acceptance criteria checklist ## Acceptance criteria checklist
@ -105,8 +105,8 @@ Invalid moves are **rejected**: return the error payload; **do not** change auth
| `server/README.md` | Document move validation, config keys, rejection status/body, and `reasonCode` values. | | `server/README.md` | Document move validation, config keys, rejection status/body, and `reasonCode` values. |
| `server/NeonSprawl.Server.Tests/InMemoryWebApplicationFactory.cs` | Optional: small helper or documented pattern to override `Game` options with **strict** limits for HTTP tests without relying on production defaults. | | `server/NeonSprawl.Server.Tests/InMemoryWebApplicationFactory.cs` | Optional: small helper or documented pattern to override `Game` options with **strict** limits for HTTP tests without relying on production defaults. |
| `client/scripts/position_authority_client.gd` | Handle non-**200** from move POST: log **`reasonCode`** (and optional user-visible hint); do not emit authoritative snap as success. | | `client/scripts/position_authority_client.gd` | Handle non-**200** from move POST: log **`reasonCode`** (and optional user-visible hint); do not emit authoritative snap as success. |
| `client/scenes/main.tscn` | Add **walkable** low bumps + tall **walkable** pedestal (and optional far pad) with meshes/materials for manual NS-19 validation; keep floor/interactable layout coherent. | | `client/scenes/main.tscn` | Add **walkable** low bumps + tall **walkable** pedestal (and optional far pad) with meshes/materials for manual NEON-7 validation; keep floor/interactable layout coherent. |
| `docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | Implementation snapshot: NS-19 move validation + link to this plan. | | `docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | Implementation snapshot: NEON-7 move validation + link to this plan. |
## Tests ## Tests

View File

@ -1,16 +1,16 @@
# NS-23 — Implementation plan # NEON-8 — Implementation plan
## Story reference ## Story reference
| Field | Value | | Field | Value |
|--------|--------| |--------|--------|
| **Key** | NS-23 | | **Key** | NEON-8 |
| **Title** | E1.M1: Client path-follow baseline (navigation + visible locomotion) | | **Title** | E1.M1: Client path-follow baseline (navigation + visible locomotion) |
| **Jira** | [NS-23](https://neon-sprawl.atlassian.net/browse/NS-23) | | **Jira** | [NEON-8](https://neon-sprawl.atlassian.net/browse/NEON-8) |
| **Parent context** | [NS-10 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NS-10) · [NS-1 — Epic 1](https://neon-sprawl.atlassian.net/browse/NS-1) · Slice 1 — Movement and position sync | | **Parent context** | [NEON-9 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NEON-9) · [NEON-1 — Epic 1](https://neon-sprawl.atlassian.net/browse/NEON-1) · Slice 1 — Movement and position sync |
| **Decomposition** | [E1.M1 — InputAndMovementRuntime](../decomposition/modules/E1_M1_InputAndMovementRuntime.md) | | **Decomposition** | [E1.M1 — InputAndMovementRuntime](../decomposition/modules/E1_M1_InputAndMovementRuntime.md) |
**Delivery:** Ship **`docs/plans/NS-23-implementation-plan.md`** on the **same branch / PR** as the NS-23 implementation (plan + code together), matching the NS-19 pattern. **Delivery:** Ship **`docs/plans/NEON-8-implementation-plan.md`** on the **same branch / PR** as the NEON-8 implementation (plan + code together), matching the NEON-7 pattern.
## Goal, scope, and out-of-scope ## Goal, scope, and out-of-scope
@ -20,7 +20,7 @@
- **Godot:** `NavigationRegion3D` (baked mesh) over the prototype walkable floor in `main.tscn` (obstacle geometry included in bake for future use); **`NavigationAgent3D`** on the player driving **`CharacterBody3D`** motion (`move_and_slide` toward next path position when the client is following the mesh — Godot 4.x pattern). - **Godot:** `NavigationRegion3D` (baked mesh) over the prototype walkable floor in `main.tscn` (obstacle geometry included in bake for future use); **`NavigationAgent3D`** on the player driving **`CharacterBody3D`** motion (`move_and_slide` toward next path position when the client is following the mesh — Godot 4.x pattern).
- **Pick integration:** Reuse **`ground_pick.gd`** (`walkable` group, upward normal); clicked point becomes **`NavigationAgent3D.target_position`** (after any needed Y / agent height adjustment consistent with current capsule). - **Pick integration:** Reuse **`ground_pick.gd`** (`walkable` group, upward normal); clicked point becomes **`NavigationAgent3D.target_position`** (after any needed Y / agent height adjustment consistent with current capsule).
- **Server authority (unchanged contract):** One **`MoveCommand`** per successful pick with the **same world target** as today ([NS-16](NS-16-implementation-plan.md)); server **v1 snap** + **[NS-19](NS-19-implementation-plan.md)** validation unchanged. - **Server authority (unchanged contract):** One **`MoveCommand`** per successful pick with the **same world target** as today ([NEON-4](NEON-4-implementation-plan.md)); server **v1 snap** + **[NEON-7](NEON-7-implementation-plan.md)** validation unchanged.
**Out of scope (per Jira)** **Out of scope (per Jira)**
@ -33,9 +33,9 @@
|--------|----------| |--------|----------|
| **When to POST** | **Once per click**, immediately with the **picked destination** (same JSON as today). No waypoint streaming to the server in this story. | | **When to POST** | **Once per click**, immediately with the **picked destination** (same JSON as today). No waypoint streaming to the server in this story. |
| **Server truth** | Still **snap to target** on **200** after validation; **`GET /position`** after POST remains the reconciliation read (existing `PositionAuthorityClient` flow). | | **Server truth** | Still **snap to target** on **200** after validation; **`GET /position`** after POST remains the reconciliation read (existing `PositionAuthorityClient` flow). |
| **Straight-line vs path** | NS-19 validates **displacement** from **current authoritative position** to **target** (horizontal + vertical limits). It does **not** check path clearance through geometry. **Client** may follow nav waypoints when the descend bypass is inactive; **automatic obstacle detours are not required** for this slice (see **Locked — prototype movement tradeoff**). | | **Straight-line vs path** | NEON-7 validates **displacement** from **current authoritative position** to **target** (horizontal + vertical limits). It does **not** check path clearance through geometry. **Client** may follow nav waypoints when the descend bypass is inactive; **automatic obstacle detours are not required** for this slice (see **Locked — prototype movement tradeoff**). |
| **After successful move** | **Do not** teleport the avatar on the verifying **`GET`** when the player is expected to walk: apply the authoritative position as a **navigation goal** and let the agent complete the path; **initial boot** `sync_from_server` remains a **hard snap** so spawn matches the server. | | **After successful move** | **Do not** teleport the avatar on the verifying **`GET`** when the player is expected to walk: apply the authoritative position as a **navigation goal** and let the agent complete the path; **initial boot** `sync_from_server` remains a **hard snap** so spawn matches the server. |
| **POST 400** | Existing behavior: emit **`move_rejected`**, show NS-19 UX; **do not** start (or clear) a nav path to that target. | | **POST 400** | Existing behavior: emit **`move_rejected`**, show NEON-7 UX; **do not** start (or clear) a nav path to that target. |
| **Distance limits** | Default **`MaxHorizontalStep`** (e.g. 18 m) remains compatible with **single-command** long clicks across the prototype; if a future scene needs shorter steps, chain commands or tune limits in a later story. | | **Distance limits** | Default **`MaxHorizontalStep`** (e.g. 18 m) remains compatible with **single-command** long clicks across the prototype; if a future scene needs shorter steps, chain commands or tune limits in a later story. |
## Locked — prototype movement tradeoff ## Locked — prototype movement tradeoff
@ -43,13 +43,13 @@
| Topic | Decision | | Topic | Decision |
|--------|----------| |--------|----------|
| **Uneven surfaces vs obstacles** | The client uses a **descend bypass** in `player.gd`: when the authoritative goal **Y** is below the body origin (typical for floor picks vs mid-capsule), steering goes **straight in xz** toward the goal instead of following nav waypoints first. This keeps **stepped bumps** smooth; it does **not** guarantee routing around tall **static obstacles** on a **single** click. | | **Uneven surfaces vs obstacles** | The client uses a **descend bypass** in `player.gd`: when the authoritative goal **Y** is below the body origin (typical for floor picks vs mid-capsule), steering goes **straight in xz** toward the goal instead of following nav waypoints first. This keeps **stepped bumps** smooth; it does **not** guarantee routing around tall **static obstacles** on a **single** click. |
| **Player expectation** | **Multi-click** (or chained moves) is acceptable to navigate around geometry the client would otherwise bee-line into. **No** automatic obstacle navigation is required for NS-23 closure. | | **Player expectation** | **Multi-click** (or chained moves) is acceptable to navigate around geometry the client would otherwise bee-line into. **No** automatic obstacle navigation is required for NEON-8 closure. |
| **Residual polish** | Visible idle jitter at rest is tracked as tech debt — [NS-24](https://neon-sprawl.atlassian.net/browse/NS-24) (not a blocker for this story). | | **Residual polish** | Visible idle jitter at rest is tracked as tech debt — [NEON-16](https://neon-sprawl.atlassian.net/browse/NEON-16) (not a blocker for this story). |
## Acceptance criteria checklist ## Acceptance criteria checklist
- [x] Clicking a valid floor target produces **continuous motion** toward the verified target without sliding through geometry in the common case; **navmesh waypoints** are used when the descend bypass does not apply. **Obstacle detours on one click are not required** (tradeoff above). - [x] Clicking a valid floor target produces **continuous motion** toward the verified target without sliding through geometry in the common case; **navmesh waypoints** are used when the descend bypass does not apply. **Obstacle detours on one click are not required** (tradeoff above).
- [x] Authoritative position still **converges** with the server (`MoveCommand` + `GET`); no regression on NS-19 rejection UX (`reasonCode` / label timeout). - [x] Authoritative position still **converges** with the server (`MoveCommand` + `GET`); no regression on NEON-7 rejection UX (`reasonCode` / label timeout).
- [x] Short note in **`server/README.md`** and/or **client script header**: client nav vs server authority for this slice. - [x] Short note in **`server/README.md`** and/or **client script header**: client nav vs server authority for this slice.
## Technical approach ## Technical approach
@ -59,7 +59,7 @@
- Bake after layout changes; document any **editor step** in `client/README.md` if bake is not fully reproducible from scene alone. - Bake after layout changes; document any **editor step** in `client/README.md` if bake is not fully reproducible from scene alone.
2. **Player / agent** 2. **Player / agent**
- Attach **`NavigationAgent3D`**; replace NS-14-style **direct steering toward `_goal`** with **path following** (velocity toward `get_next_path_position()` or equivalent, `move_and_slide`). - Attach **`NavigationAgent3D`**; replace NEON-2-style **direct steering toward `_goal`** with **path following** (velocity toward `get_next_path_position()` or equivalent, `move_and_slide`).
- Keep **`snap_to_server`** for **boot** and any **hard reconcile** case you explicitly document (default: boot only). - Keep **`snap_to_server`** for **boot** and any **hard reconcile** case you explicitly document (default: boot only).
3. **Wiring (`main.gd` / `PositionAuthorityClient`)** 3. **Wiring (`main.gd` / `PositionAuthorityClient`)**
@ -103,18 +103,18 @@ Prototype player collider in [`main.tscn`](../../client/scenes/main.tscn): **`Ca
| Path | Rationale | | Path | Rationale |
|------|-----------| |------|-----------|
| `client/scenes/main.tscn` | `NavigationRegion3D`, bake source meshes, agent node setup. | | `client/scenes/main.tscn` | `NavigationRegion3D`, bake source meshes, agent node setup. |
| `client/scripts/player.gd` | Path-follow via `NavigationAgent3D`; clarify header (NS-14 → NS-23 evolution). | | `client/scripts/player.gd` | Path-follow via `NavigationAgent3D`; clarify header (NEON-2 → NEON-8 evolution). |
| `client/scripts/main.gd` | Boot snap vs post-move nav goal wiring. | | `client/scripts/main.gd` | Boot snap vs post-move nav goal wiring. |
| `client/scripts/position_authority_client.gd` | If needed: signal shape or phase so main can snap only on boot. | | `client/scripts/position_authority_client.gd` | If needed: signal shape or phase so main can snap only on boot. |
| `client/README.md` | Manual check steps for path-follow + server. | | `client/README.md` | Manual check steps for path-follow + server. |
| `server/README.md` | Client nav vs authority note (AC). | | `server/README.md` | Client nav vs authority note (AC). |
| `docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | Implementation snapshot when NS-23 ships. | | `docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | Implementation snapshot when NEON-8 ships. |
## Tests ## Tests
| Action | What to cover | | Action | What to cover |
|--------|----------------| |--------|----------------|
| **Manual (required)** | Server running; click-move toward targets (including stepped bumps); NS-19 reject targets — same rejection behavior as before; cold start — player matches server position. Optional: confirm multi-click can route around the prototype **`Obstacle`** when a single click bee-lines. | | **Manual (required)** | Server running; click-move toward targets (including stepped bumps); NEON-7 reject targets — same rejection behavior as before; cold start — player matches server position. Optional: confirm multi-click can route around the prototype **`Obstacle`** when a single click bee-lines. |
| **Automated** | None required for this story unless an existing headless hook can assert nav baking (unlikely); prefer manual for Godot nav. | | **Automated** | None required for this story unless an existing headless hook can assert nav baking (unlikely); prefer manual for Godot nav. |
## Open questions / risks ## Open questions / risks
@ -123,4 +123,4 @@ Prototype player collider in [`main.tscn`](../../client/scenes/main.tscn): **`Ca
## PR / review ## PR / review
Cross-check [E1.M1](../decomposition/modules/E1_M1_InputAndMovementRuntime.md), [client README](../../client/README.md), and [server README](../../server/README.md). Confirm NS-19 props in `main.tscn` still behave after nav region changes. Cross-check [E1.M1](../decomposition/modules/E1_M1_InputAndMovementRuntime.md), [client README](../../client/README.md), and [server README](../../server/README.md). Confirm NEON-7 props in `main.tscn` still behave after nav region changes.

View File

@ -1,13 +1,13 @@
# Code review — NS-15 (authoritative position read API) # Code review — NEON-3 (authoritative position read API)
## Review metadata ## Review metadata
| Field | Value | | Field | Value |
|--------|--------| |--------|--------|
| **Date** | 2026-03-29 | | **Date** | 2026-03-29 |
| **Scope** | Branch `NS-15-position-state-api` vs `origin/main`, plus **working tree**: modified tracked files and **untracked** implementation/tests/agent files (full change set as present locally) | | **Scope** | Branch `NEON-3-position-state-api` vs `origin/main`, plus **working tree**: modified tracked files and **untracked** implementation/tests/agent files (full change set as present locally) |
| **Base** | `origin/main` | | **Base** | `origin/main` |
| **Issue** | NS-15 — E1.M1: Authoritative PositionState API (in-memory) | | **Issue** | NEON-3 — E1.M1: Authoritative PositionState API (in-memory) |
--- ---
@ -17,11 +17,11 @@
## Summary ## Summary
The NS-15 work adds an in-memory `IPositionStateStore`, `Game` configuration for a dev player id and default position, `PositionStateApi.MapPositionStateApi()` for `GET /game/players/{id}/position`, XML-documented DTOs with `schemaVersion`, integration tests via `WebApplicationFactory<Program>`, and `Program.Waf.cs` for the test host. `Program.cs` stays thin (DI + map extension calls only), matching `docs/plans/NS-15-implementation-plan.md`. README documents `curl` and sample JSON. Separately, `.cursor/rules/csharp-style.md` gains primary-constructor guidance, and the code-review agent + `AGENTS.md` add process docs. Overall risk is **low** for a read-only spike; no persistence or client integration. The NEON-3 work adds an in-memory `IPositionStateStore`, `Game` configuration for a dev player id and default position, `PositionStateApi.MapPositionStateApi()` for `GET /game/players/{id}/position`, XML-documented DTOs with `schemaVersion`, integration tests via `WebApplicationFactory<Program>`, and `Program.Waf.cs` for the test host. `Program.cs` stays thin (DI + map extension calls only), matching `docs/plans/NEON-3-implementation-plan.md`. README documents `curl` and sample JSON. Separately, `.cursor/rules/csharp-style.md` gains primary-constructor guidance, and the code-review agent + `AGENTS.md` add process docs. Overall risk is **low** for a read-only spike; no persistence or client integration.
## Blocking issues ## Blocking issues
1. **Version control gap** — As of this review, `server/NeonSprawl.Server/Game/`, `server/NeonSprawl.Server.Tests/`, `server/NeonSprawl.Server/Program.Waf.cs`, `.cursor/rules/code-review-agent.md`, and `AGENTS.md` are **untracked**, while `Program.cs`, `appsettings.json`, `server/README.md`, `NeonSprawl.sln`, `docs/plans/NS-15-implementation-plan.md`, and `.cursor/rules/csharp-style.md` are **modified but uncommitted**. **Before merge:** `git add` all intended files and push so CI/reviewers see the real diff. 1. **Version control gap** — As of this review, `server/NeonSprawl.Server/Game/`, `server/NeonSprawl.Server.Tests/`, `server/NeonSprawl.Server/Program.Waf.cs`, `.cursor/rules/code-review-agent.md`, and `AGENTS.md` are **untracked**, while `Program.cs`, `appsettings.json`, `server/README.md`, `NeonSprawl.sln`, `docs/plans/NEON-3-implementation-plan.md`, and `.cursor/rules/csharp-style.md` are **modified but uncommitted**. **Before merge:** `git add` all intended files and push so CI/reviewers see the real diff.
## Suggestions ## Suggestions
@ -29,7 +29,7 @@ The NS-15 work adds an in-memory `IPositionStateStore`, `Game` configuration for
2. **Case sensitivity** — Lookups use `StringComparer.Ordinal`. Document that player ids in URLs are case-sensitive, or normalize route id and stored keys the same way if product expects case-insensitivity. 2. **Case sensitivity** — Lookups use `StringComparer.Ordinal`. Document that player ids in URLs are case-sensitive, or normalize route id and stored keys the same way if product expects case-insensitivity.
3. **Implementation plan markdown** — In `docs/plans/NS-15-implementation-plan.md`, the acceptance section mixes raw `**` markers with bullets (e.g. `` `**dotnet test**` ``) and no longer uses `- [ ]` checkboxes. Restoring valid Markdown and checkboxes improves scanability. 3. **Implementation plan markdown** — In `docs/plans/NEON-3-implementation-plan.md`, the acceptance section mixes raw `**` markers with bullets (e.g. `` `**dotnet test**` ``) and no longer uses `- [ ]` checkboxes. Restoring valid Markdown and checkboxes improves scanability.
4. **Solution file noise** — If `NeonSprawl.sln` picks up a UTF-8 BOM or Windows-style path separators, consider normalizing to reduce cross-platform diff churn (non-functional). 4. **Solution file noise** — If `NeonSprawl.sln` picks up a UTF-8 BOM or Windows-style path separators, consider normalizing to reduce cross-platform diff churn (non-functional).
@ -38,7 +38,7 @@ The NS-15 work adds an in-memory `IPositionStateStore`, `Game` configuration for
## Nits ## Nits
- **Nit:** `SchemaVersion` is set to `1` in both `PositionStateResponse` and the handler; a single named constant could prevent drift. - **Nit:** `SchemaVersion` is set to `1` in both `PositionStateResponse` and the handler; a single named constant could prevent drift.
- **Nit:** No logging on 404 vs success—acceptable for NS-15; revisit when debugging multi-player seeds. - **Nit:** No logging on 404 vs success—acceptable for NEON-3; revisit when debugging multi-player seeds.
## Verification ## Verification
@ -62,13 +62,13 @@ Expect HTTP 200 and JSON including `schemaVersion`, `playerId`, `position` with
## Files touched (review scope) ## Files touched (review scope)
**Modified (tracked):** `.cursor/rules/csharp-style.md`, `NeonSprawl.sln`, `docs/plans/NS-15-implementation-plan.md`, `server/NeonSprawl.Server/Program.cs`, `server/NeonSprawl.Server/appsettings.json`, `server/README.md` **Modified (tracked):** `.cursor/rules/csharp-style.md`, `NeonSprawl.sln`, `docs/plans/NEON-3-implementation-plan.md`, `server/NeonSprawl.Server/Program.cs`, `server/NeonSprawl.Server/appsettings.json`, `server/README.md`
**Untracked (must be added for a complete PR):** `.cursor/rules/code-review-agent.md`, `AGENTS.md`, `server/NeonSprawl.Server/Program.Waf.cs`, `server/NeonSprawl.Server/Game/PositionState/*.cs`, `server/NeonSprawl.Server.Tests/*` **Untracked (must be added for a complete PR):** `.cursor/rules/code-review-agent.md`, `AGENTS.md`, `server/NeonSprawl.Server/Program.Waf.cs`, `server/NeonSprawl.Server/Game/PositionState/*.cs`, `server/NeonSprawl.Server.Tests/*`
## Conformance notes ## Conformance notes
- **architecture-authority:** Server-held in-memory state, HTTP read spike; no client or move submission—matches NS-15 out-of-scope. - **architecture-authority:** Server-held in-memory state, HTTP read spike; no client or move submission—matches NEON-3 out-of-scope.
- **testing-expectations:** First server module gets `NeonSprawl.Server.Tests` with integration-style coverage for happy path and 404—appropriate. - **testing-expectations:** First server module gets `NeonSprawl.Server.Tests` with integration-style coverage for happy path and 404—appropriate.
- **csharp-style:** Test class uses primary constructor and `using Xunit;`—aligned with updated rule. - **csharp-style:** Test class uses primary constructor and `using Xunit;`—aligned with updated rule.
- **Plan alignment:** Separate `PositionStateApi`, `PositionStateServiceCollectionExtensions`, no position routes inlined in `Program.cs`—matches plan. - **Plan alignment:** Separate `PositionStateApi`, `PositionStateServiceCollectionExtensions`, no position routes inlined in `Program.cs`—matches plan.

View File

@ -1,8 +1,8 @@
# Code review: NS-16 (MoveCommand → PositionState) # Code review: NEON-4 (MoveCommand → PositionState)
**Date:** 2026-03-30 **Date:** 2026-03-30
**Scope:** Branch `NS-16-move-command-position-state` vs `origin/main` (working tree / uncommitted changes included at review time). **Scope:** Branch `NEON-4-move-command-position-state` vs `origin/main` (working tree / uncommitted changes included at review time).
**Issue:** [NS-16](https://neon-sprawl.atlassian.net/browse/NS-16) — E1.M1: Submit MoveCommand → server updates PositionState **Issue:** [NEON-4](https://neon-sprawl.atlassian.net/browse/NEON-4) — E1.M1: Submit MoveCommand → server updates PositionState
**Base:** `origin/main` **Base:** `origin/main`
## Verdict ## Verdict
@ -11,15 +11,15 @@
## Summary ## Summary
The branch delivers NS-16 end-to-end: **`POST /game/players/{id}/move`** with versioned **`MoveCommandRequest`**, thread-safe snap + **`sequence`** in **`InMemoryPositionStateStore`**, **`PositionStateResponse`** on success, integration tests under **`Game/PositionState/`** with **`MethodName_ShouldExpectedOutcome_WhenScenario`** naming and AAA layout, and a Godot client split into **`ground_pick.gd`**, **`position_authority_client.gd`**, and a thin **`main.gd`**. **`UseAppHost=false`** for Debug avoids Linux apphost probing **`/usr/share/dotnet`** when .NET 10 lives under **`~/.dotnet`**. Overall risk is **low** for a prototype slice; there is **no auth** on move (acceptable for the story). Repo rules and READMEs were extended for tests, Godot layout, and Linux/Rider notes. The branch delivers NEON-4 end-to-end: **`POST /game/players/{id}/move`** with versioned **`MoveCommandRequest`**, thread-safe snap + **`sequence`** in **`InMemoryPositionStateStore`**, **`PositionStateResponse`** on success, integration tests under **`Game/PositionState/`** with **`MethodName_ShouldExpectedOutcome_WhenScenario`** naming and AAA layout, and a Godot client split into **`ground_pick.gd`**, **`position_authority_client.gd`**, and a thin **`main.gd`**. **`UseAppHost=false`** for Debug avoids Linux apphost probing **`/usr/share/dotnet`** when .NET 10 lives under **`~/.dotnet`**. Overall risk is **low** for a prototype slice; there is **no auth** on move (acceptable for the story). Repo rules and READMEs were extended for tests, Godot layout, and Linux/Rider notes.
## Documentation checked ## Documentation checked
| Document | Result | | Document | Result |
|----------|--------| |----------|--------|
| `docs/plans/NS-16-implementation-plan.md` | **Matches** — endpoint, snap rule, client split, tests, README expectations align with implementation. | | `docs/plans/NEON-4-implementation-plan.md` | **Matches** — endpoint, snap rule, client split, tests, README expectations align with implementation. |
| `docs/plans/NS-15-implementation-plan.md` | **Partially matches** — PR table row for **`sequence`** was stale (“v1 is 0” only); **updated** in this review pass to reference NS-16 semantics. | | `docs/plans/NEON-3-implementation-plan.md` | **Partially matches** — PR table row for **`sequence`** was stale (“v1 is 0” only); **updated** in this review pass to reference NEON-4 semantics. |
| `docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | **Partially matched → updated** — snapshot still said “Not yet: MoveCommand”; **updated** to reflect NS-16. | | `docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | **Partially matched → updated** — snapshot still said “Not yet: MoveCommand”; **updated** to reflect NEON-4. |
| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | **Partially matched → updated** — E1.M1 row still said no MoveCommand/client sync; **updated**. | | `docs/decomposition/modules/documentation_and_implementation_alignment.md` | **Partially matched → updated** — E1.M1 row still said no MoveCommand/client sync; **updated**. |
| `docs/decomposition/modules/module_dependency_register.md` | **N/A** — no row text change required for this review beyond alignment table (register Status unchanged **In Progress**). | | `docs/decomposition/modules/module_dependency_register.md` | **N/A** — no row text change required for this review beyond alignment table (register Status unchanged **In Progress**). |
| `docs/decomposition/modules/contracts.md` | **Matches** — JSON spike / throwaway until protobuf acknowledged in code comments. | | `docs/decomposition/modules/contracts.md` | **Matches** — JSON spike / throwaway until protobuf acknowledged in code comments. |
@ -33,7 +33,7 @@ The branch delivers NS-16 end-to-end: **`POST /game/players/{id}/move`** with ve
## Suggestions ## Suggestions
1. **Jira / plan checkboxes**`docs/plans/NS-16-implementation-plan.md` acceptance bullets are still unchecked; tick them when you close NS-16 or paste PR proof in Jira. 1. **Jira / plan checkboxes**`docs/plans/NEON-4-implementation-plan.md` acceptance bullets are still unchecked; tick them when you close NEON-4 or paste PR proof in Jira.
2. **`PositionAuthorityClient` boot failure** — if the initial GET fails, the client fails quietly (warnings only); acceptable for the spike; consider a single user-visible error later. 2. **`PositionAuthorityClient` boot failure** — if the initial GET fails, the client fails quietly (warnings only); acceptable for the spike; consider a single user-visible error later.
3. **Observability** — server does not log failed moves or bad payloads; fine for prototype; add when debugging multiplayer. 3. **Observability** — server does not log failed moves or bad payloads; fine for prototype; add when debugging multiplayer.
@ -43,7 +43,7 @@ The branch delivers NS-16 end-to-end: **`POST /game/players/{id}/move`** with ve
**Resolved after review:** **Resolved after review:**
- NS-16 plan now states explicitly that scripts are **split per client policy**, and **`HTTPRequest`** is created in **`position_authority_client._ready`** (not placed in **`main.tscn`**). - NEON-4 plan now states explicitly that scripts are **split per client policy**, and **`HTTPRequest`** is created in **`position_authority_client._ready`** (not placed in **`main.tscn`**).
- **`PositionAuthorityClient`** reuses **`_request_get()`** for the post-POST verify GET instead of duplicating the position URL. - **`PositionAuthorityClient`** reuses **`_request_get()`** for the post-POST verify GET instead of duplicating the position URL.
## Verification ## Verification

View File

@ -1,16 +1,16 @@
# Code review — NS-17 (PostgreSQL position persistence) # Code review — NEON-5 (PostgreSQL position persistence)
**Date:** 2026-03-30 **Date:** 2026-03-30
**Scope:** Branch `NS-17-persist-position-postgres`; Jira [NS-17](https://neon-sprawl.atlassian.net/browse/NS-17). Working tree includes **modified** and **untracked** files (e.g. `PostgresPositionStateStore.cs`, `server/db/`, Postgres test types are **untracked** at review time). **Scope:** Branch `NEON-5-persist-position-postgres`; Jira [NEON-5](https://neon-sprawl.atlassian.net/browse/NEON-5). Working tree includes **modified** and **untracked** files (e.g. `PostgresPositionStateStore.cs`, `server/db/`, Postgres test types are **untracked** at review time).
**Base:** `main` (comparison: `git diff main --stat` plus untracked NS-17 assets). **Base:** `main` (comparison: `git diff main --stat` plus untracked NEON-5 assets).
--- ---
## Verdict ## Verdict
**Approve with nits** — Persistence wiring matches the NS-17 plan and authority docs; tests and CI cover the durable path. Decomposition **implementation snapshot** docs updated for NS-17 (see **Author follow-up** below). **Approve with nits** — Persistence wiring matches the NEON-5 plan and authority docs; tests and CI cover the durable path. Decomposition **implementation snapshot** docs updated for NEON-5 (see **Author follow-up** below).
--- ---
@ -18,7 +18,7 @@
This work adds a PostgreSQL-backed `IPositionStateStore`, a versioned DDL file under `server/db/migrations/`, configuration via `ConnectionStrings:NeonSprawl`, idempotent schema application once per process, and per-request idempotent dev-row seeding so behavior stays consistent with TRUNCATE-heavy tests. GitHub Actions gains a Postgres service and passes the connection string into `dotnet test`. In-memory API tests are isolated via `InMemoryWebApplicationFactory` + `ConfigureTestServices` so a global CI env var does not poison the in-memory suite. Overall risk is **lowmedium**: prototype-grade lifecycle (no explicit `NpgsqlDataSource` disposal), runtime DDL from the app, and every request paying a seed `INSERT` (no-op when row exists). This work adds a PostgreSQL-backed `IPositionStateStore`, a versioned DDL file under `server/db/migrations/`, configuration via `ConnectionStrings:NeonSprawl`, idempotent schema application once per process, and per-request idempotent dev-row seeding so behavior stays consistent with TRUNCATE-heavy tests. GitHub Actions gains a Postgres service and passes the connection string into `dotnet test`. In-memory API tests are isolated via `InMemoryWebApplicationFactory` + `ConfigureTestServices` so a global CI env var does not poison the in-memory suite. Overall risk is **lowmedium**: prototype-grade lifecycle (no explicit `NpgsqlDataSource` disposal), runtime DDL from the app, and every request paying a seed `INSERT` (no-op when row exists).
The diff also includes **repo-wide C# brace style** updates in `.cursor/rules/csharp-style.md` and touched server files — acceptable but **mixed scope** vs NS-17; consider separate commit or callout in PR. The diff also includes **repo-wide C# brace style** updates in `.cursor/rules/csharp-style.md` and touched server files — acceptable but **mixed scope** vs NEON-5; consider separate commit or callout in PR.
--- ---
@ -26,12 +26,12 @@ The diff also includes **repo-wide C# brace style** updates in `.cursor/rules/cs
| Document | Assessment | | Document | Assessment |
|----------|------------| |----------|------------|
| `docs/plans/NS-17-implementation-plan.md` | **Matches** — columns not JSONB, `player_position`, `IPositionStateStore`, connection string toggle, CI service + env, integration tests (POST→GET, second factory, unknown player), README/env docs. **Partially matches** — plan listed optional `appsettings.Development.json`; implementation correctly leaves connection string unset by default (still within plan). | | `docs/plans/NEON-5-implementation-plan.md` | **Matches** — columns not JSONB, `player_position`, `IPositionStateStore`, connection string toggle, CI service + env, integration tests (POST→GET, second factory, unknown player), README/env docs. **Partially matches** — plan listed optional `appsettings.Development.json`; implementation correctly leaves connection string unset by default (still within plan). |
| `docs/decomposition/modules/client_server_authority.md` | **Matches** — server remains authoritative for `PositionState`; persistence does not move truth to the client. | | `docs/decomposition/modules/client_server_authority.md` | **Matches** — server remains authoritative for `PositionState`; persistence does not move truth to the client. |
| `docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | **Matches (post-review update)** — Snapshot describes optional Postgres persistence + NS-17; “Not yet” trimmed to prediction / full slice / telemetry. | | `docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | **Matches (post-review update)** — Snapshot describes optional Postgres persistence + NEON-5; “Not yet” trimmed to prediction / full slice / telemetry. |
| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | **Matches (post-review update)** — E1.M1 tracking row includes NS-17 and Postgres-when-configured. | | `docs/decomposition/modules/documentation_and_implementation_alignment.md` | **Matches (post-review update)** — E1.M1 tracking row includes NEON-5 and Postgres-when-configured. |
| `docs/decomposition/modules/module_dependency_register.md` | **Matches** — E1.M1 remains **In Progress**; no contract version promotion required for this slice. | | `docs/decomposition/modules/module_dependency_register.md` | **Matches** — E1.M1 remains **In Progress**; no contract version promotion required for this slice. |
| `docs/decomposition/modules/contracts.md` | **Matches** — HTTP JSON shapes unchanged; NS-17 is durability, not a breaking wire change. | | `docs/decomposition/modules/contracts.md` | **Matches** — HTTP JSON shapes unchanged; NEON-5 is durability, not a breaking wire change. |
| `server/README.md` | **Matches** — persistence, env vars, local Postgres test instructions. | | `server/README.md` | **Matches** — persistence, env vars, local Postgres test instructions. |
--- ---
@ -44,8 +44,8 @@ The diff also includes **repo-wide C# brace style** updates in `.cursor/rules/cs
## Suggestions ## Suggestions
1. ~~**Docs alignment (recommended before merge):**~~ **Done**`E1_M1_InputAndMovementRuntime.md` and `documentation_and_implementation_alignment.md` updated for NS-17 / optional Postgres (planned follow-up; separate from **Nit 1** below). 1. ~~**Docs alignment (recommended before merge):**~~ **Done**`E1_M1_InputAndMovementRuntime.md` and `documentation_and_implementation_alignment.md` updated for NEON-5 / optional Postgres (planned follow-up; separate from **Nit 1** below).
2. ~~**Lifecycle:**~~ **Tracked:** [NS-22](https://neon-sprawl.atlassian.net/browse/NS-22) under epic [NS-20 — Tech Debt](https://neon-sprawl.atlassian.net/browse/NS-20) — dispose `NpgsqlDataSource` on host shutdown (was: singleton never disposed; acceptable for prototype until addressed). 2. ~~**Lifecycle:**~~ **Tracked:** [NEON-15](https://neon-sprawl.atlassian.net/browse/NEON-15) under epic [NEON-13 — Tech Debt](https://neon-sprawl.atlassian.net/browse/NEON-13) — dispose `NpgsqlDataSource` on host shutdown (was: singleton never disposed; acceptable for prototype until addressed).
3. ~~**Naming clarity:**~~ **Done**`EnsureSchema()` XML summary in `PostgresPositionStateStore.cs` now states the heavy path (read migration + DDL) runs at most once per process; later calls are the fast volatile-read path. No rename. 3. ~~**Naming clarity:**~~ **Done**`EnsureSchema()` XML summary in `PostgresPositionStateStore.cs` now states the heavy path (read migration + DDL) runs at most once per process; later calls are the fast volatile-read path. No rename.
--- ---
@ -68,4 +68,4 @@ Before merge, the author should run:
and `dotnet test NeonSprawl.sln -c Release` — all tests including `PostgresPositionStateIntegrationTests` should pass. and `dotnet test NeonSprawl.sln -c Release` — all tests including `PostgresPositionStateIntegrationTests` should pass.
3. **Manual AC (optional):** `dotnet run` with connection string set, `POST …/move`, restart server, `GET …/position` unchanged. 3. **Manual AC (optional):** `dotnet run` with connection string set, `POST …/move`, restart server, `GET …/position` unchanged.
Ensure **all** NS-17 files (including `server/db/` and new test types) are **staged and committed** — at review time much of the implementation was still untracked relative to `git status`. Ensure **all** NEON-5 files (including `server/db/` and new test types) are **staged and committed** — at review time much of the implementation was still untracked relative to `git status`.

View File

@ -1,12 +1,12 @@
# Code review — NS-18 (InteractionRequest + related) # Code review — NEON-6 (InteractionRequest + related)
**Date:** 2026-04-04 **Date:** 2026-04-04
**Scope:** Branch `NS-18-interaction-request-range-check` and **current working tree** (large NS-18 implementation + Postgres test ergonomics + dev HTTP logging + Godot fixes are **modified/untracked** vs `origin/main`; only the plan file is committed ahead of main in this clone). **Scope:** Branch `NEON-6-interaction-request-range-check` and **current working tree** (large NEON-6 implementation + Postgres test ergonomics + dev HTTP logging + Godot fixes are **modified/untracked** vs `origin/main`; only the plan file is committed ahead of main in this clone).
**Base:** `origin/main` (merge-base `a2015dd4bfe2e6554d677507499ed33e6a9e50b5` where available). **Base:** `origin/main` (merge-base `a2015dd4bfe2e6554d677507499ed33e6a9e50b5` where available).
## Verdict ## Verdict
**Approve with nits** — Behavior matches NS-18 and E1.M1 intent; tests and docs are in good shape. **Commit and push the full implementation** before opening/merging the PR so review and CI run on the real diff. **Approve with nits** — Behavior matches NEON-6 and E1.M1 intent; tests and docs are in good shape. **Commit and push the full implementation** before opening/merging the PR so review and CI run on the real diff.
## Summary ## Summary
@ -16,25 +16,25 @@ The work delivers **server-authoritative** `POST /game/players/{id}/interact` wi
| Document | Assessment | | Document | Assessment |
|----------|------------| |----------|------------|
| `docs/plans/NS-18-implementation-plan.md` | **Matches** — Endpoint, HTTP semantics, horizontal distance, registry defaults, DTO rules, client glow + POST, file list, and AC checklist align with the implementation. | | `docs/plans/NEON-6-implementation-plan.md` | **Matches** — Endpoint, HTTP semantics, horizontal distance, registry defaults, DTO rules, client glow + POST, file list, and AC checklist align with the implementation. |
| `docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | **Matches** — Snapshot documents InteractionRequest + range check and links to NS-18 / server README. | | `docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | **Matches** — Snapshot documents InteractionRequest + range check and links to NEON-6 / server README. |
| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | **Matches** — E1.M1 row references NS-18 and server paths. | | `docs/decomposition/modules/documentation_and_implementation_alignment.md` | **Matches** — E1.M1 row references NEON-6 and server paths. |
| `docs/decomposition/modules/client_server_authority.md` (E1.M1) | **Matches** — Position-derived rules stay server-side; client glow is explicitly non-authoritative. | | `docs/decomposition/modules/client_server_authority.md` (E1.M1) | **Matches** — Position-derived rules stay server-side; client glow is explicitly non-authoritative. |
| `server/README.md` | **Matches** — Interaction subsection, Postgres + `postgres.runsettings`, Rider xUnit note, Docker automation note, dev logging implied via Development config. | | `server/README.md` | **Matches** — Interaction subsection, Postgres + `postgres.runsettings`, Rider xUnit note, Docker automation note, dev logging implied via Development config. |
| `client/README.md` | **Matches** — NS-16/NS-18 manual checks, `interact` / `_input` note. | | `client/README.md` | **Matches** — NEON-4/NEON-6 manual checks, `interact` / `_input` note. |
| `docs/decomposition/modules/module_dependency_register.md` | **Partially matches** — E1.M1 status remains “In Progress”; no conflict. Optional follow-up: bump or refine **register** row if you track NS-18 explicitly there (not required if table stays high-level). | | `docs/decomposition/modules/module_dependency_register.md` | **Partially matches** — E1.M1 status remains “In Progress”; no conflict. Optional follow-up: bump or refine **register** row if you track NEON-6 explicitly there (not required if table stays high-level). |
## Blocking issues ## Blocking issues
_None identified in the design or code paths reviewed._ _None identified in the design or code paths reviewed._
**Process (merge gate):** Ensure **all** server/client/doc files for NS-18 and the test-harness/logging changes are **committed** on the branch so GitHub CI and reviewers see one coherent diff. **Process (merge gate):** Ensure **all** server/client/doc files for NEON-6 and the test-harness/logging changes are **committed** on the branch so GitHub CI and reviewers see one coherent diff.
## Suggestions ## Suggestions
1. **`HorizontalReach`:** ~~Consider guarding **`radius < 0`**~~ **Done**`ArgumentOutOfRangeException.ThrowIfNegative` + unit test. 1. **`HorizontalReach`:** ~~Consider guarding **`radius < 0`**~~ **Done**`ArgumentOutOfRangeException.ThrowIfNegative` + unit test.
2. **`InteractionRequestClient`:** ~~Optional **`_busy`** flag~~ **Done** — ignores **E** while HTTP in flight. 2. **`InteractionRequestClient`:** ~~Optional **`_busy`** flag~~ **Done** — ignores **E** while HTTP in flight.
3. **Postgres + `postgres.runsettings`:** ~~Call out in PR description~~ **Done**`server/README.md` (**Contributors / PRs**) and `docs/plans/NS-18-implementation-plan.md` (reviewer note under PR draft). 3. **Postgres + `postgres.runsettings`:** ~~Call out in PR description~~ **Done**`server/README.md` (**Contributors / PRs**) and `docs/plans/NEON-6-implementation-plan.md` (reviewer note under PR draft).
## Nits ## Nits

View File

@ -0,0 +1,55 @@
# Code review — NEON-16 idle / rest stability (Godot client)
**Date:** 2026-04-05
**Scope:** Branch `NEON-16-idle-vibration-fix` vs merge-base `3283f6d04c8724f6536ca591a2a4c8ca06ea7b61` (tracked as `origin/main` at review time). Issue **NEON-16**; no PR URL supplied.
**Base:** `origin/main` @ `3283f6d04c8724f6536ca591a2a4c8ca06ea7b61`
**Follow-up:** Review **suggestions** below are **done** (strikethrough + **Done.**); **nits** remain optional.
## Verdict
**Approve with nits**
## Summary
This branch delivers **NEON-16** by hardening the prototype **`CharacterBody3D`** idle path (`player.gd`): dual **`floor_max_angle`** behavior, rim/straddle detection, idle nudges, bump proximity escape, **`floor_block_on_wall`**, **`NavigationAgent3D.avoidance_enabled = false`**, and **physics interpolation off** at project + player node level. It fixes **compound internal-edge** issues by spawning each QA bump as its **own `StaticBody3D`** (`random_floor_bumps.gd`), adds **Jolt** in **`project.godot`**, and reduces **z-fighting** via capsule **mesh Y lift** (visual-only vs collision shape). **`client/README.md`** and **`server/README.md`** (bump height copy) are updated. **Server contracts** (`MoveCommand` / `PositionState`) are not changed; risk is localized to client feel and QA geometry. Manual QA by the author reports **no remaining jitter** in exercised poses.
## Documentation checked
| Document | Result |
|----------|--------|
| `docs/plans/NEON-16-implementation-plan.md` | **Matches****Resolution**, checked AC, **Shipped note**, updated **Open questions** (`015ca13`). |
| `docs/plans/NEON-8-implementation-plan.md` | **Matches** at intent level (client nav visual, server authority unchanged); no contract drift observed in the diff. |
| `docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | **Matches.** Prototype client locomotion and reconciliation surface remain consistent; changes are polish under “movement-loop” / QA, not authority or wire shape. |
| `docs/decomposition/modules/module_dependency_register.md` | **N/A** for code behavior; cited as the hub for module IDs. No register **Status** change required for this client-only jitter fix. |
| `docs/decomposition/modules/client_server_authority.md` | **N/A** (no change to who owns `PositionState` or move validation). |
| `client/README.md` | **Matches** NEON-16 call to document the pattern and manual steps (idle 10+ s, bumps). |
## Blocking issues
*(none)*
## Suggestions
**All done.**
1. ~~**Close the loop in `docs/plans/NEON-16-implementation-plan.md`:**~~ **Done.** Check off acceptance criteria; add **Resolution** (shipped mitigations) and **Shipped note** under technical approach — `015ca13`.
2. ~~**DRY bump collision constants** (`BUMP_COLLISION_*` aligned across `player.gd` / `random_floor_bumps.gd`):~~ **Done.** `client/scripts/random_floor_bump_collision_constants.gd` + preload from both scripts; README references — `015ca13`, doc sync `87d8ee0` (file later renamed off **`ns19_*`** identifiers).
3. ~~**Future character yaw** (`_snap_capsule_upright()` full identity basis):~~ **Done.** `TODO(NEON-16 follow-on)` on `_snap_capsule_upright()` in `player.gd`; plan **Open questions**`015ca13`.
## Nits
- **`get_tree().get_nodes_in_group(...)`** on **`random_floor_bump_mesh`** each idle tick is fine at **two** bumps; if the group grows, cache references after spawn or guard cost.
- ~~**Commit history:**~~ **Done.** Branch history squashed to a **single** **`NEON-16:`** commit before merge; no stray **`fix:`** / **`docs:`** subjects on the slice.
## Verification
Per **`NEON-16-implementation-plan.md`** and **`client/README.md`**:
1. Run **`main.tscn`** with server as in NEON-4/NEON-8 README steps.
2. **Idle 10+ s** at spawn and after a move (no click goal); confirm **no visible vibration** on slab and on **random green bumps** (new positions each run).
3. **Walking**, **arrival**, **`snap_to_server`** on cold boot; **NEON-7** reject UX (pedestal / far pad) unchanged.
4. **Obstacle / descend** flows per NEON-8 tradeoff (multi-click around tall obstacle if needed).
5. Spot-check **bump rim**, **cylinder side**, and **floorbump** contact (prior jitter loci).
**Automated:** None required for NEON-16 (no Godot harness in repo).

View File

@ -1,7 +1,7 @@
# Code review: NS-23 client path-follow (`NS-23-client-path-follow`) # Code review: NEON-8 client path-follow (`NEON-8-client-path-follow`)
**Date:** 2026-04-05 **Date:** 2026-04-05
**Scope:** Branch `NS-23-client-path-follow` vs `origin/main` (full NS-23 slice: client nav, wiring, docs, small server remark). **Scope:** Branch `NEON-8-client-path-follow` vs `origin/main` (full NEON-8 slice: client nav, wiring, docs, small server remark).
**Base:** `origin/main` **Base:** `origin/main`
**Follow-up:** Design and docs were aligned after the initial review; blocking items and suggestions below are **done** unless marked otherwise. **Follow-up:** Design and docs were aligned after the initial review; blocking items and suggestions below are **done** unless marked otherwise.
@ -16,17 +16,17 @@ The branch adds **`NavigationRegion3D`** / **`NavigationAgent3D`**, sync nav bak
**`player.gd`** intentionally skips nav waypoints when **`_auth_walk_goal.y < global_position.y - DESCEND_GOAL_Y_MARGIN`**, so for typical picks (floor **Y** vs mid-capsule origin) the client often **bee-lines in xz** toward the goal. That matches the **locked prototype tradeoff** (smooth bumps; multi-click around obstacles when needed). **`player.gd`** intentionally skips nav waypoints when **`_auth_walk_goal.y < global_position.y - DESCEND_GOAL_Y_MARGIN`**, so for typical picks (floor **Y** vs mid-capsule origin) the client often **bee-lines in xz** toward the goal. That matches the **locked prototype tradeoff** (smooth bumps; multi-click around obstacles when needed).
**Idle optimization:** skipping **`move_and_slide()`** when there is no walk goal, velocity is zero, and **`is_on_floor()`** is a reasonable jitter mitigation; residual vibration is tracked in **[NS-24](https://neon-sprawl.atlassian.net/browse/NS-24)** (Tech Debt). **Idle optimization:** skipping **`move_and_slide()`** when there is no walk goal, velocity is zero, and **`is_on_floor()`** is a reasonable jitter mitigation; residual vibration is tracked in **[NEON-16](https://neon-sprawl.atlassian.net/browse/NEON-16)** (Tech Debt).
## Documentation checked ## Documentation checked
| Document | Result (at closure) | | Document | Result (at closure) |
|----------|---------------------| |----------|---------------------|
| `docs/plans/NS-23-implementation-plan.md` | **Matches** after **Locked — prototype movement tradeoff**, updated AC, tests, and **NS-24** link. | | `docs/plans/NEON-8-implementation-plan.md` | **Matches** after **Locked — prototype movement tradeoff**, updated AC, tests, and **NEON-16** link. |
| `docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | **Matches** — snapshot notes single-click obstacle detours not guaranteed. | | `docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | **Matches** — snapshot notes single-click obstacle detours not guaranteed. |
| `docs/decomposition/modules/client_server_authority.md` | **Matches** — server owns **`PositionState`**; client presentation path valid. | | `docs/decomposition/modules/client_server_authority.md` | **Matches** — server owns **`PositionState`**; client presentation path valid. |
| `docs/decomposition/modules/module_dependency_register.md` / `documentation_and_implementation_alignment.md` | **N/A** deep diff. | | `docs/decomposition/modules/module_dependency_register.md` / `documentation_and_implementation_alignment.md` | **N/A** deep diff. |
| `client/README.md` | **Matches** — tradeoff, manual steps, **Known issue** + **NS-24** link. | | `client/README.md` | **Matches** — tradeoff, manual steps, **Known issue** + **NEON-16** link. |
| `server/README.md` | **Matches** — client nav presentation; detours not guaranteed on one click. | | `server/README.md` | **Matches** — client nav presentation; detours not guaranteed on one click. |
## Blocking issues ## Blocking issues
@ -41,7 +41,7 @@ The branch adds **`NavigationRegion3D`** / **`NavigationAgent3D`**, sync nav bak
1. ~~**`DESCEND_GOAL_Y_MARGIN`:**~~ **Done.** Comment added in `client/scripts/player.gd` (surface pick Y vs mid-capsule; bumps vs obstacles). 1. ~~**`DESCEND_GOAL_Y_MARGIN`:**~~ **Done.** Comment added in `client/scripts/player.gd` (surface pick Y vs mid-capsule; bumps vs obstacles).
2. ~~**`_skip_move_when_idle_on_floor`:**~~ **Done.** Doc comment above the helper (`is_on_floor()` / last `move_and_slide()`; static floors). 2. ~~**`_skip_move_when_idle_on_floor`:**~~ **Done.** Doc comment above the helper (`is_on_floor()` / last `move_and_slide()`; static floors).
3. ~~**NS-24:**~~ **Done.** Out of NS-23 scope in plan; **[NS-24](https://neon-sprawl.atlassian.net/browse/NS-24)** linked from **`NS-23-implementation-plan.md`** and **`client/README.md`**. 3. ~~**NEON-16:**~~ **Done.** Out of NEON-8 scope in plan; **[NEON-16](https://neon-sprawl.atlassian.net/browse/NEON-16)** linked from **`NEON-8-implementation-plan.md`** and **`client/README.md`**.
## Nits ## Nits
@ -51,5 +51,5 @@ The branch adds **`NavigationRegion3D`** / **`NavigationAgent3D`**, sync nav bak
- `cd server/NeonSprawl.Server && dotnet test` (no logic change expected, sanity). - `cd server/NeonSprawl.Server && dotnet test` (no logic change expected, sanity).
- Godot **F5** with server: boot snap; click floor; bump flow; gray **Obstacle** may need **multiple clicks** per docs. - Godot **F5** with server: boot snap; click floor; bump flow; gray **Obstacle** may need **multiple clicks** per docs.
- Confirm NS-19 reject targets still show rejection UX (`reasonCode` / label). - Confirm NEON-7 reject targets still show rejection UX (`reasonCode` / label).
- `gdlint` / `gdformat` on touched GDScript if CI does not already run on the branch. - `gdlint` / `gdformat` on touched GDScript if CI does not already run on the branch.

View File

@ -1,55 +0,0 @@
# Code review — NS-24 idle / rest stability (Godot client)
**Date:** 2026-04-05
**Scope:** Branch `NS-24-idle-vibration-fix` vs merge-base `3283f6d04c8724f6536ca591a2a4c8ca06ea7b61` (tracked as `origin/main` at review time). Issue **NS-24**; no PR URL supplied.
**Base:** `origin/main` @ `3283f6d04c8724f6536ca591a2a4c8ca06ea7b61`
**Follow-up:** Review **suggestions** below are **done** (strikethrough + **Done.**); **nits** remain optional.
## Verdict
**Approve with nits**
## Summary
This branch delivers **NS-24** by hardening the prototype **`CharacterBody3D`** idle path (`player.gd`): dual **`floor_max_angle`** behavior, rim/straddle detection, idle nudges, bump proximity escape, **`floor_block_on_wall`**, **`NavigationAgent3D.avoidance_enabled = false`**, and **physics interpolation off** at project + player node level. It fixes **compound internal-edge** issues by spawning each QA bump as its **own `StaticBody3D`** (`random_floor_bumps.gd`), adds **Jolt** in **`project.godot`**, and reduces **z-fighting** via capsule **mesh Y lift** (visual-only vs collision shape). **`client/README.md`** and **`server/README.md`** (bump height copy) are updated. **Server contracts** (`MoveCommand` / `PositionState`) are not changed; risk is localized to client feel and QA geometry. Manual QA by the author reports **no remaining jitter** in exercised poses.
## Documentation checked
| Document | Result |
|----------|--------|
| `docs/plans/NS-24-implementation-plan.md` | **Matches****Resolution**, checked AC, **Shipped note**, updated **Open questions** (`015ca13`). |
| `docs/plans/NS-23-implementation-plan.md` | **Matches** at intent level (client nav visual, server authority unchanged); no contract drift observed in the diff. |
| `docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | **Matches.** Prototype client locomotion and reconciliation surface remain consistent; changes are polish under “movement-loop” / QA, not authority or wire shape. |
| `docs/decomposition/modules/module_dependency_register.md` | **N/A** for code behavior; cited as the hub for module IDs. No register **Status** change required for this client-only jitter fix. |
| `docs/decomposition/modules/client_server_authority.md` | **N/A** (no change to who owns `PositionState` or move validation). |
| `client/README.md` | **Matches** NS-24 call to document the pattern and manual steps (idle 10+ s, bumps). |
## Blocking issues
*(none)*
## Suggestions
**All done.**
1. ~~**Close the loop in `docs/plans/NS-24-implementation-plan.md`:**~~ **Done.** Check off acceptance criteria; add **Resolution** (shipped mitigations) and **Shipped note** under technical approach — `015ca13`.
2. ~~**DRY bump collision constants** (`BUMP_COLLISION_*` aligned across `player.gd` / `random_floor_bumps.gd`):~~ **Done.** `client/scripts/random_floor_bump_collision_constants.gd` + preload from both scripts; README references — `015ca13`, doc sync `87d8ee0` (file later renamed off **`ns19_*`** identifiers).
3. ~~**Future character yaw** (`_snap_capsule_upright()` full identity basis):~~ **Done.** `TODO(NS-24 follow-on)` on `_snap_capsule_upright()` in `player.gd`; plan **Open questions**`015ca13`.
## Nits
- **`get_tree().get_nodes_in_group(...)`** on **`random_floor_bump_mesh`** each idle tick is fine at **two** bumps; if the group grows, cache references after spawn or guard cost.
- ~~**Commit history:**~~ **Done.** Branch history squashed to a **single** **`NS-24:`** commit before merge; no stray **`fix:`** / **`docs:`** subjects on the slice.
## Verification
Per **`NS-24-implementation-plan.md`** and **`client/README.md`**:
1. Run **`main.tscn`** with server as in NS-16/NS-23 README steps.
2. **Idle 10+ s** at spawn and after a move (no click goal); confirm **no visible vibration** on slab and on **random green bumps** (new positions each run).
3. **Walking**, **arrival**, **`snap_to_server`** on cold boot; **NS-19** reject UX (pedestal / far pad) unchanged.
4. **Obstacle / descend** flows per NS-23 tradeoff (multi-click around tall obstacle if needed).
5. Spot-check **bump rim**, **cylinder side**, and **floorbump** contact (prior jitter loci).
**Automated:** None required for NS-24 (no Godot harness in repo).

View File

@ -16,11 +16,11 @@ dotnet run
- Default URL is printed by Kestrel (see `Properties/launchSettings.json`, typically `http://localhost:5253`). - Default URL is printed by Kestrel (see `Properties/launchSettings.json`, typically `http://localhost:5253`).
- Check `GET /health` for a JSON heartbeat. - Check `GET /health` for a JSON heartbeat.
## Position persistence (NS-17) ## Position persistence (NEON-5)
When **`ConnectionStrings:NeonSprawl`** is set to a valid PostgreSQL connection string, the server uses the **`player_position`** table (relational columns `pos_x``sequence`, not JSONB). DDL lives in [`server/db/migrations/V001__player_position.sql`](../db/migrations/V001__player_position.sql); the app applies that script on startup and on first store use (idempotent `CREATE TABLE IF NOT EXISTS`). The configured dev player (`Game:DevPlayerId` / `Game:DefaultPosition`) is seeded once at host startup, matching the in-memory stores constructor seed (not on every HTTP request). When **`ConnectionStrings:NeonSprawl`** is set to a valid PostgreSQL connection string, the server uses the **`player_position`** table (relational columns `pos_x``sequence`, not JSONB). DDL lives in [`server/db/migrations/V001__player_position.sql`](../db/migrations/V001__player_position.sql); the app applies that script on startup and on first store use (idempotent `CREATE TABLE IF NOT EXISTS`). The configured dev player (`Game:DevPlayerId` / `Game:DefaultPosition`) is seeded once at host startup, matching the in-memory stores constructor seed (not on every HTTP request).
If **`ConnectionStrings:NeonSprawl`** is missing or blank, behavior matches NS-15/NS-16 (**in-memory** only). The **test** projects `postgres.runsettings` sets `ConnectionStrings__NeonSprawl` for the test host so Postgres integration tests run in CI/Rider/`dotnet test`; non-Postgres tests use `InMemoryWebApplicationFactory` and do not require a live DB. Remove or rename `postgres.runsettings` (and the `RunSettingsFilePath` property) if you want those three tests to **skip** again when the variable is unset. If **`ConnectionStrings:NeonSprawl`** is missing or blank, behavior matches NEON-3/NEON-4 (**in-memory** only). The **test** projects `postgres.runsettings` sets `ConnectionStrings__NeonSprawl` for the test host so Postgres integration tests run in CI/Rider/`dotnet test`; non-Postgres tests use `InMemoryWebApplicationFactory` and do not require a live DB. Remove or rename `postgres.runsettings` (and the `RunSettingsFilePath` property) if you want those three tests to **skip** again when the variable is unset.
**Environment variables** (typical — same mapping as other ASP.NET Core config): **Environment variables** (typical — same mapping as other ASP.NET Core config):
@ -41,9 +41,9 @@ Match credentials with [root `docker-compose.yml`](../../docker-compose.yml) for
**Local Docker (optional automation):** When **not** in CI (`CI`, `GITHUB_ACTIONS`, `GITLAB_CI`, `TF_BUILD`), the Postgres test harness tries to connect first. If the server is unreachable, it runs **`docker compose up -d`** from the repo root (same compose file as [root `docker-compose.yml`](../../docker-compose.yml)), waits for the DB, runs tests, then runs **`docker compose down`** **only if** it started Compose itself. If Postgres was already listening (existing container or native install), tests do **not** stop your database afterward. Initialization is **async** (no sync-over-async on the xUnit sync context) so **Rider** / **Visual Studio** do not leave Postgres tests stuck **Pending** after Compose starts. **Local Docker (optional automation):** When **not** in CI (`CI`, `GITHUB_ACTIONS`, `GITLAB_CI`, `TF_BUILD`), the Postgres test harness tries to connect first. If the server is unreachable, it runs **`docker compose up -d`** from the repo root (same compose file as [root `docker-compose.yml`](../../docker-compose.yml)), waits for the DB, runs tests, then runs **`docker compose down`** **only if** it started Compose itself. If Postgres was already listening (existing container or native install), tests do **not** stop your database afterward. Initialization is **async** (no sync-over-async on the xUnit sync context) so **Rider** / **Visual Studio** do not leave Postgres tests stuck **Pending** after Compose starts.
## Position state (NS-15) ## Position state (NEON-3)
Authoritative player position is served over HTTP whether the backing store is **in memory** or **PostgreSQL** (NS-17). The HTTP JSON shape is versioned with top-level `schemaVersion` (see XML docs on `PositionStateResponse` in the server project). Path `{id}` is **trimmed** and matched **case-insensitively** against stored players; `playerId` in the JSON body echoes the path segment from the request. Stored player ids use **invariant lowercase + trim** in Postgres for lookup parity. Authoritative player position is served over HTTP whether the backing store is **in memory** or **PostgreSQL** (NEON-5). The HTTP JSON shape is versioned with top-level `schemaVersion` (see XML docs on `PositionStateResponse` in the server project). Path `{id}` is **trimmed** and matched **case-insensitively** against stored players; `playerId` in the JSON body echoes the path segment from the request. Stored player ids use **invariant lowercase + trim** in Postgres for lookup parity.
Example (dev player id defaults to `dev-local-1` in `appsettings.json`): Example (dev player id defaults to `dev-local-1` in `appsettings.json`):
@ -51,7 +51,7 @@ Example (dev player id defaults to `dev-local-1` in `appsettings.json`):
curl -s http://localhost:5253/game/players/dev-local-1/position curl -s http://localhost:5253/game/players/dev-local-1/position
``` ```
Sample response (default spawn matches Godot capsule and NS-18 walk-in range demo): Sample response (default spawn matches Godot capsule and NEON-6 walk-in range demo):
```json ```json
{"schemaVersion":1,"playerId":"dev-local-1","position":{"x":-5,"y":0.9,"z":-5},"sequence":0} {"schemaVersion":1,"playerId":"dev-local-1","position":{"x":-5,"y":0.9,"z":-5},"sequence":0}
@ -59,13 +59,13 @@ Sample response (default spawn matches Godot capsule and NS-18 walk-in range dem
Unknown player ids return **404**. Full zone sync / replication is still out of scope for these spikes. Unknown player ids return **404**. Full zone sync / replication is still out of scope for these spikes.
## Move command (NS-16, NS-19) ## Move command (NEON-4, NEON-7)
**`POST /game/players/{id}/move`** with JSON body applies a v1 **MoveCommand**: authoritative position **snaps** to `target` immediately; `sequence` in responses increases by one per successful apply. **`POST /game/players/{id}/move`** with JSON body applies a v1 **MoveCommand**: authoritative position **snaps** to `target` immediately; `sequence` in responses increases by one per successful apply.
**Client navigation (NS-23):** The Godot prototype uses a **baked navigation mesh** only for **presentation**; the client may follow waypoints when active, but **automatic obstacle detours on a single click are not guaranteed** (tradeoff for smooth movement on stepped geometry). The server does **not** simulate navmesh; it validates **straight-line** step limits (NS-19) from the **last authoritative position** to the **requested target**. Cheating or divergent paths are out of scope for this slice. **Client navigation (NEON-8):** The Godot prototype uses a **baked navigation mesh** only for **presentation**; the client may follow waypoints when active, but **automatic obstacle detours on a single click are not guaranteed** (tradeoff for smooth movement on stepped geometry). The server does **not** simulate navmesh; it validates **straight-line** step limits (NEON-7) from the **last authoritative position** to the **requested target**. Cheating or divergent paths are out of scope for this slice.
**NS-19 validation (reject-only):** Before applying, the server checks the move against **`Game:MovementValidation`**. Limits use **horizontal** distance on **X/Z** only and **|ΔY|** separately (see `MoveCommandValidation`). Unknown players return **404** before validation. **NEON-7 validation (reject-only):** Before applying, the server checks the move against **`Game:MovementValidation`**. Limits use **horizontal** distance on **X/Z** only and **|ΔY|** separately (see `MoveCommandValidation`). Unknown players return **404** before validation.
| Config key | Meaning | Default (see `appsettings.json`) | | Config key | Meaning | Default (see `appsettings.json`) |
|------------|---------|-----------------------------------| |------------|---------|-----------------------------------|
@ -85,14 +85,14 @@ curl -s -X POST http://localhost:5253/game/players/dev-local-1/move \
``` ```
- **200** — body matches **`PositionStateResponse`** (same shape as `GET``/position`). - **200** — body matches **`PositionStateResponse`** (same shape as `GET``/position`).
- **400** — malformed command (missing/invalid body or wrong `schemaVersion`) **or** move rejected by NS-19 rules. Rejection responses are JSON **`MoveCommandRejectedResponse`**: `schemaVersion` (`1`) + **`reasonCode`** (`horizontal_step_exceeded`, `vertical_step_exceeded`, `out_of_bounds`). Malformed requests return **400** with **no** rejection body. - **400** — malformed command (missing/invalid body or wrong `schemaVersion`) **or** move rejected by NEON-7 rules. Rejection responses are JSON **`MoveCommandRejectedResponse`**: `schemaVersion` (`1`) + **`reasonCode`** (`horizontal_step_exceeded`, `vertical_step_exceeded`, `out_of_bounds`). Malformed requests return **400** with **no** rejection body.
- **404** — unknown player id. - **404** — unknown player id.
See XML on `MoveCommandRequest`, `PositionStateResponse`, and `MoveCommandRejectedResponse` in the server project. See XML on `MoveCommandRequest`, `PositionStateResponse`, and `MoveCommandRejectedResponse` in the server project.
For a **PR/Jira-ready** contract blurb (formatted JSON + field table), see the [NS-15 implementation plan — Pull request description](../../docs/plans/NS-15-implementation-plan.md#pull-request-description-jira-ac) (GET shape) and [NS-16 implementation plan](../../docs/plans/NS-16-implementation-plan.md#pull-request-description-jira-ac--draft-for-merge) (MoveCommand + sequence semantics). NS-19: [NS-19 implementation plan](../../docs/plans/NS-19-implementation-plan.md). For a **PR/Jira-ready** contract blurb (formatted JSON + field table), see the [NEON-3 implementation plan — Pull request description](../../docs/plans/NEON-3-implementation-plan.md#pull-request-description-jira-ac) (GET shape) and [NEON-4 implementation plan](../../docs/plans/NEON-4-implementation-plan.md#pull-request-description-jira-ac--draft-for-merge) (MoveCommand + sequence semantics). NEON-7: [NEON-7 implementation plan](../../docs/plans/NEON-7-implementation-plan.md).
## Interaction (NS-18) ## Interaction (NEON-6)
**`POST /game/players/{id}/interact`** with a versioned **InteractionRequest** body asks whether the given player may use a prototype interactable **now**. The server reads **authoritative** `PositionState` from `IPositionStateStore` (same id rules as position APIs: trim + ordinal case-insensitive match). **Horizontal reach** uses **X and Z only**; **Y is ignored** for distance (floor-plane prototype). See `HorizontalReach` in `NeonSprawl.Server/Game/World/` and `PrototypeInteractableRegistry` in `Game/Interaction/`. **`POST /game/players/{id}/interact`** with a versioned **InteractionRequest** body asks whether the given player may use a prototype interactable **now**. The server reads **authoritative** `PositionState` from `IPositionStateStore` (same id rules as position APIs: trim + ordinal case-insensitive match). **Horizontal reach** uses **X and Z only**; **Y is ignored** for distance (floor-plane prototype). See `HorizontalReach` in `NeonSprawl.Server/Game/World/` and `PrototypeInteractableRegistry` in `Game/Interaction/`.
@ -121,7 +121,7 @@ curl -s -X POST http://localhost:5253/game/players/dev-local-1/interact \
When **`allowed` is `true`**, **`reasonCode` is omitted** (clients must not require it on success). Unknown player never returns this JSON — use **404**. When **`allowed` is `true`**, **`reasonCode` is omitted** (clients must not require it on success). Unknown player never returns this JSON — use **404**.
Contract details and PR blurb: [NS-18 implementation plan](../../docs/plans/NS-18-implementation-plan.md). Contract details and PR blurb: [NEON-6 implementation plan](../../docs/plans/NEON-6-implementation-plan.md).
## Solution ## Solution