diff --git a/.cursor/rules/code-review-agent.md b/.cursor/rules/code-review-agent.md index 67a6eaa..834e216 100644 --- a/.cursor/rules/code-review-agent.md +++ b/.cursor/rules/code-review-agent.md @@ -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). -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. 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. @@ -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. 1. **Directory:** `docs/reviews/` (create the folder if it does not exist). -2. **Filename:** `YYYY-MM-DD-{slug}.md` using the **authoritative “Today’s 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 “Today’s 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): - **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) - **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`. 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. diff --git a/.cursor/rules/commit-and-review.md b/.cursor/rules/commit-and-review.md index ef1c767..a22c4ba 100644 --- a/.cursor/rules/commit-and-review.md +++ b/.cursor/rules/commit-and-review.md @@ -23,7 +23,7 @@ alwaysApply: true ## 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). ## Scope diff --git a/.cursor/rules/git-workflow.md b/.cursor/rules/git-workflow.md index 75f5337..8cad6fc 100644 --- a/.cursor/rules/git-workflow.md +++ b/.cursor/rules/git-workflow.md @@ -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). -- **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. -- **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`. diff --git a/.cursor/rules/jira-git-naming.md b/.cursor/rules/jira-git-naming.md index 065f0bd..c9ab069 100644 --- a/.cursor/rules/jira-git-naming.md +++ b/.cursor/rules/jira-git-naming.md @@ -5,27 +5,27 @@ alwaysApply: true # 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 - 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). -- **Avoid:** `feature/click-to-move` with no key, or `fix/ns-14-foo` with the key not at the start. +- **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/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-`**, 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-`**, not `feature/neon-2-…` with the key buried after a prefix. ## 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. -- **Good:** `NS-14: add direct click-to-move steering`, `NS-20: 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` +- **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: `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. ## 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`). diff --git a/.cursor/rules/story-end.md b/.cursor/rules/story-end.md index 97e1871..31d5fbe 100644 --- a/.cursor/rules/story-end.md +++ b/.cursor/rules/story-end.md @@ -5,7 +5,7 @@ alwaysApply: true # 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 @@ -16,7 +16,7 @@ When the user **ends story work** on a ticketed branch—phrases like “end sto 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) -3. **`git branch -d `** — 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 `** is OK **only with user confirmation** so local unpushed tip is not lost by mistake. +3. **`git branch -d `** — 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 `** is OK **only with user confirmation** so local unpushed tip is not lost by mistake. ## 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 - 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) diff --git a/.cursor/rules/story-kickoff.md b/.cursor/rules/story-kickoff.md index 8d88a65..0f31b2f 100644 --- a/.cursor/rules/story-kickoff.md +++ b/.cursor/rules/story-kickoff.md @@ -5,7 +5,7 @@ alwaysApply: true # 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 @@ -20,7 +20,7 @@ When the user starts work on a **Jira story** (e.g. issue key `NS-14`, phrases l ## 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. - 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 -- 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). **Required sections** in that file (do **not** ship a plan that omits any of these): diff --git a/.cursor/rules/testing-expectations.md b/.cursor/rules/testing-expectations.md index d85670a..cccaf49 100644 --- a/.cursor/rules/testing-expectations.md +++ b/.cursor/rules/testing-expectations.md @@ -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 --- @@ -23,7 +23,7 @@ alwaysApply: true ## 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. - **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. diff --git a/AGENTS.md b/AGENTS.md index 5eb55e8..f9438d4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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). -**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). diff --git a/client/README.md b/client/README.md index e0d8df4..7e06d70 100644 --- a/client/README.md +++ b/client/README.md @@ -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`**. -## 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 pick’s **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). - **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`). -### 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`). 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 server’s default position (e.g. **(-5, 0.9, -5)** per `Game:DefaultPosition` / NS-18 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. +3. Open the client in Godot and run the main scene (**F5**). The player should **snap** to the server’s 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). 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. 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. -## 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 server’s 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`**). - **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). 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. @@ -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. -## 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`). diff --git a/docs/decomposition/epics/epic_01_core_player_runtime.md b/docs/decomposition/epics/epic_01_core_player_runtime.md index dc4c63d..5582480 100644 --- a/docs/decomposition/epics/epic_01_core_player_runtime.md +++ b/docs/decomposition/epics/epic_01_core_player_runtime.md @@ -22,7 +22,7 @@ Deliver the foundational client runtime: movement, locked isometric camera, worl - Key contracts: `MoveCommand`, `PositionState`, `InteractionRequest` - Dependencies: None - 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 diff --git a/docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md b/docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md index 1153fe5..447c563 100644 --- a/docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md +++ b/docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md @@ -7,7 +7,7 @@ | **Module ID** | E1.M1 | | **Epic** | [Epic 1 — Core Player Runtime](../epics/epic_01_core_player_runtime.md) | | **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 @@ -33,7 +33,7 @@ Contract readiness is tracked in the [module dependency register](module_depende ## 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). - **Alignment:** [Documentation and implementation alignment](documentation_and_implementation_alignment.md). diff --git a/docs/decomposition/modules/documentation_and_implementation_alignment.md b/docs/decomposition/modules/documentation_and_implementation_alignment.md index 2baecdf..ff5f3d2 100644 --- a/docs/decomposition/modules/documentation_and_implementation_alignment.md +++ b/docs/decomposition/modules/documentation_and_implementation_alignment.md @@ -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). | | **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 doc’s 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 | |--------|-----------------|----------|-------------------| -| 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) | --- diff --git a/docs/plans/NS-21-implementation-plan.md b/docs/plans/NEON-14-implementation-plan.md similarity index 96% rename from docs/plans/NS-21-implementation-plan.md rename to docs/plans/NEON-14-implementation-plan.md index a3c5149..ee563ca 100644 --- a/docs/plans/NS-21-implementation-plan.md +++ b/docs/plans/NEON-14-implementation-plan.md @@ -1,13 +1,13 @@ -# NS-21 — Implementation plan +# NEON-14 — Implementation plan ## Story reference | Field | Value | |--------|--------| -| **Key** | NS-21 | +| **Key** | NEON-14 | | **Title** | Set up GDScript unit testing and cover existing client scripts | -| **Jira** | [NS-21](https://neon-sprawl.atlassian.net/browse/NS-21) | -| **Parent** | [NS-20 — Tech Debt](https://neon-sprawl.atlassian.net/browse/NS-20) | +| **Jira** | [NEON-14](https://neon-sprawl.atlassian.net/browse/NEON-14) | +| **Parent** | [NEON-13 — Tech Debt](https://neon-sprawl.atlassian.net/browse/NEON-13) | ## 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). - 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. diff --git a/docs/plans/NS-24-implementation-plan.md b/docs/plans/NEON-16-implementation-plan.md similarity index 79% rename from docs/plans/NS-24-implementation-plan.md rename to docs/plans/NEON-16-implementation-plan.md index 4680d34..b5bcfc8 100644 --- a/docs/plans/NS-24-implementation-plan.md +++ b/docs/plans/NEON-16-implementation-plan.md @@ -1,23 +1,23 @@ -# NS-24 — Implementation plan +# NEON-16 — Implementation plan ## Story reference | Field | Value | |--------|--------| -| **Key** | NS-24 | +| **Key** | NEON-16 | | **Title** | Godot client: eliminate CharacterBody3D idle / rest vibration | -| **Jira** | [NS-24](https://neon-sprawl.atlassian.net/browse/NS-24) | -| **Parent** | [NS-20 — Tech Debt](https://neon-sprawl.atlassian.net/browse/NS-20) | -| **Related** | [NS-23](https://neon-sprawl.atlassian.net/browse/NS-23) (path-follow baseline; idle jitter explicitly deferred here) | +| **Jira** | [NEON-16](https://neon-sprawl.atlassian.net/browse/NEON-16) | +| **Parent** | [NEON-13 — Tech Debt](https://neon-sprawl.atlassian.net/browse/NEON-13) | +| **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:** **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** - 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). **Out of scope** @@ -28,7 +28,7 @@ ## 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] **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). ## Resolution (shipped) @@ -43,7 +43,7 @@ 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. -**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 @@ -64,7 +64,7 @@ | 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). | ## Open questions / risks @@ -73,4 +73,4 @@ ## 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. diff --git a/docs/plans/NS-14-implementation-plan.md b/docs/plans/NEON-2-implementation-plan.md similarity index 90% rename from docs/plans/NS-14-implementation-plan.md rename to docs/plans/NEON-2-implementation-plan.md index 0c6a742..23ba600 100644 --- a/docs/plans/NS-14-implementation-plan.md +++ b/docs/plans/NEON-2-implementation-plan.md @@ -1,13 +1,13 @@ -# NS-14 — Implementation plan +# NEON-2 — Implementation plan ## Story reference | Field | Value | |--------|--------| -| **Key** | NS-14 | +| **Key** | NEON-2 | | **Title** | E1.M1: Click-to-move prototype (client-only) | -| **Jira** | [NS-14](https://neon-sprawl.atlassian.net/browse/NS-14) | -| **Parent context** | [NS-10 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NS-10) | +| **Jira** | [NEON-2](https://neon-sprawl.atlassian.net/browse/NEON-2) | +| **Parent context** | [NEON-9 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NEON-9) | ## 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. 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. - - Optional: `Editable Children` note on root node in scene or a comment in `main.gd` pointing to NS-14 / temporariness. + - 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 NEON-2 / temporariness. ## 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. - **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. diff --git a/docs/plans/NS-15-implementation-plan.md b/docs/plans/NEON-3-implementation-plan.md similarity index 87% rename from docs/plans/NS-15-implementation-plan.md rename to docs/plans/NEON-3-implementation-plan.md index f5d3e43..cde21bb 100644 --- a/docs/plans/NS-15-implementation-plan.md +++ b/docs/plans/NEON-3-implementation-plan.md @@ -1,14 +1,14 @@ -# NS-15 — Implementation plan +# NEON-3 — Implementation plan ## Story reference | Field | Value | | ------------------ | --------------------------------------------------------------------------------------- | -| **Key** | NS-15 | +| **Key** | NEON-3 | | **Title** | E1.M1: Authoritative PositionState API (in-memory) | -| **Jira** | [NS-15](https://neon-sprawl.atlassian.net/browse/NS-15) | -| **Parent context** | [NS-10 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NS-10) | +| **Jira** | [NEON-3](https://neon-sprawl.atlassian.net/browse/NEON-3) | +| **Parent context** | [NEON-9 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NEON-9) | ## 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. - Optional second test: unknown id returns **404**. 6. **Documentation** - - Extend `server/README.md` with “Position state (NS-15)”: endpoint path, example `curl`, note that state is in-memory and authoritative only at the HTTP spike layer. + - 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 @@ -88,7 +88,7 @@ ## 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. @@ -115,12 +115,12 @@ Jira NS-15 asks for stable JSON **field names** documented for reviewers outside | `position.x` | number | X coordinate. | | `position.y` | number | Y 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). ## 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. - **CI:** If no workflow runs `dotnet test` yet, local `dotnet test` still satisfies AC; consider a follow-up chore to wire CI when available. diff --git a/docs/plans/NS-16-implementation-plan.md b/docs/plans/NEON-4-implementation-plan.md similarity index 81% rename from docs/plans/NS-16-implementation-plan.md rename to docs/plans/NEON-4-implementation-plan.md index dabe1e5..b936081 100644 --- a/docs/plans/NS-16-implementation-plan.md +++ b/docs/plans/NEON-4-implementation-plan.md @@ -1,13 +1,13 @@ -# NS-16 — Implementation plan +# NEON-4 — Implementation plan ## Story reference | Field | Value | |--------|--------| -| **Key** | NS-16 | +| **Key** | NEON-4 | | **Title** | E1.M1: Submit MoveCommand → server updates PositionState | -| **Jira** | [NS-16](https://neon-sprawl.atlassian.net/browse/NS-16) | -| **Parent context** | [NS-10 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NS-10) | +| **Jira** | [NEON-4](https://neon-sprawl.atlassian.net/browse/NEON-4) | +| **Parent context** | [NEON-9 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NEON-9) | ## Goal, scope, and out-of-scope @@ -16,8 +16,8 @@ **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 rule (document in plan + XML on types):** apply movement with a single clear rule. **Planned v1 rule:** **snap** authoritative position to the command’s 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). -- **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”). +- **Server rule (document in plan + XML on types):** apply movement with a single clear rule. **Planned v1 rule:** **snap** authoritative position to the command’s 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 [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). **Out of scope (per Jira)** @@ -26,8 +26,8 @@ **Dependencies** -- [NS-15](NS-15-implementation-plan.md): in-memory `PositionState` **read** API and store. -- [NS-14](NS-14-implementation-plan.md): click-to-move scaffold. +- [NEON-3](NEON-3-implementation-plan.md): in-memory `PositionState` **read** API and store. +- [NEON-2](NEON-2-implementation-plan.md): click-to-move scaffold. ## Acceptance criteria checklist @@ -39,7 +39,7 @@ 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`). - - **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`. 2. **Store mutation** @@ -53,14 +53,14 @@ - 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. - - **`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`. - - **`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. + - **`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)`** (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. - - 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** - **`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 @@ -77,20 +77,20 @@ | `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/PositionStateApi.cs` | Map `POST /game/players/{id}/move`. | -| `server/NeonSprawl.Server/Game/PositionState/PositionStateResponse.cs` | XML: clarify **`sequence`** increments after moves (post–NS-15). | +| `server/NeonSprawl.Server/Game/PositionState/PositionStateResponse.cs` | XML: clarify **`sequence`** increments after moves (post–NEON-3). | | `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). | | `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/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. | -| `client/README.md` | NS-16 runbook and authority note. | +| `client/README.md` | NEON-4 runbook and authority note. | ## Tests - **Automated:** xUnit integration tests via `WebApplicationFactory` — `POST` then `GET` equality, `404` / `400` as above. - **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 @@ -114,9 +114,9 @@ Paste when opening the PR (adjust if shapes differ slightly in implementation). | `target` | object | Destination world position. | | `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. diff --git a/docs/plans/NS-17-implementation-plan.md b/docs/plans/NEON-5-implementation-plan.md similarity index 92% rename from docs/plans/NS-17-implementation-plan.md rename to docs/plans/NEON-5-implementation-plan.md index e99afa8..052d96a 100644 --- a/docs/plans/NS-17-implementation-plan.md +++ b/docs/plans/NEON-5-implementation-plan.md @@ -1,13 +1,13 @@ -# NS-17 — Implementation plan +# NEON-5 — Implementation plan ## Story reference | Field | Value | |--------|--------| -| **Key** | NS-17 | +| **Key** | NEON-5 | | **Title** | E1.M1: Persist character position (PostgreSQL) | -| **Jira** | [NS-17](https://neon-sprawl.atlassian.net/browse/NS-17) | -| **Parent context** | [NS-10 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NS-10) · [NS-16](NS-16-implementation-plan.md) (MoveCommand → `PositionState`) | +| **Jira** | [NEON-5](https://neon-sprawl.atlassian.net/browse/NEON-5) | +| **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 @@ -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`**). - [ ] 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 @@ -93,4 +93,4 @@ ## 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. diff --git a/docs/plans/NS-18-implementation-plan.md b/docs/plans/NEON-6-implementation-plan.md similarity index 92% rename from docs/plans/NS-18-implementation-plan.md rename to docs/plans/NEON-6-implementation-plan.md index c18a330..fe97940 100644 --- a/docs/plans/NS-18-implementation-plan.md +++ b/docs/plans/NEON-6-implementation-plan.md @@ -1,20 +1,20 @@ -# NS-18 — Implementation plan +# NEON-6 — Implementation plan ## Story reference | Field | Value | |--------|--------| -| **Key** | NS-18 | +| **Key** | NEON-6 | | **Title** | E1.M1: InteractionRequest with server-side range check | -| **Jira** | [NS-18](https://neon-sprawl.atlassian.net/browse/NS-18) | -| **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) | +| **Jira** | [NEON-6](https://neon-sprawl.atlassian.net/browse/NEON-6) | +| **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 | -**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:** **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)** @@ -33,7 +33,7 @@ The game is **3D**, but **prototype interaction and movement tuning** treat all **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 @@ -111,8 +111,8 @@ Authoritative values live in **`PrototypeInteractableRegistry.cs`**. **Godot** a ### 7. Documentation -- **`server/README.md`:** subsection **Interaction (NS-18)** — 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). +- **`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 (NEON-6). ## Files to add @@ -136,9 +136,9 @@ Authoritative values live in **`PrototypeInteractableRegistry.cs`**. **Godot** a | `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/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. | -| `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. | ## Tests diff --git a/docs/plans/NS-19-implementation-plan.md b/docs/plans/NEON-7-implementation-plan.md similarity index 89% rename from docs/plans/NS-19-implementation-plan.md rename to docs/plans/NEON-7-implementation-plan.md index 9aa2dae..ce709bf 100644 --- a/docs/plans/NS-19-implementation-plan.md +++ b/docs/plans/NEON-7-implementation-plan.md @@ -1,16 +1,16 @@ -# NS-19 — Implementation plan +# NEON-7 — Implementation plan ## Story reference | Field | Value | |--------|--------| -| **Key** | NS-19 | +| **Key** | NEON-7 | | **Title** | E1.M1: Server movement validation (speed + bounds) | -| **Jira** | [NS-19](https://neon-sprawl.atlassian.net/browse/NS-19) | -| **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) | +| **Jira** | [NEON-7](https://neon-sprawl.atlassian.net/browse/NEON-7) | +| **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) | -**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 @@ -18,7 +18,7 @@ **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** @@ -34,11 +34,11 @@ Match [NS-18](NS-18-implementation-plan.md): prototype gameplay is **on the floo **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) -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 @@ -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/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/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. | -| `docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | Implementation snapshot: NS-19 move validation + link to this plan. | +| `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: NEON-7 move validation + link to this plan. | ## Tests diff --git a/docs/plans/NS-23-implementation-plan.md b/docs/plans/NEON-8-implementation-plan.md similarity index 82% rename from docs/plans/NS-23-implementation-plan.md rename to docs/plans/NEON-8-implementation-plan.md index 821ab2f..bf5b00b 100644 --- a/docs/plans/NS-23-implementation-plan.md +++ b/docs/plans/NEON-8-implementation-plan.md @@ -1,16 +1,16 @@ -# NS-23 — Implementation plan +# NEON-8 — Implementation plan ## Story reference | Field | Value | |--------|--------| -| **Key** | NS-23 | +| **Key** | NEON-8 | | **Title** | E1.M1: Client path-follow baseline (navigation + visible locomotion) | -| **Jira** | [NS-23](https://neon-sprawl.atlassian.net/browse/NS-23) | -| **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 | +| **Jira** | [NEON-8](https://neon-sprawl.atlassian.net/browse/NEON-8) | +| **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) | -**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 @@ -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). - **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)** @@ -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. | | **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. | -| **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. | ## Locked — prototype movement tradeoff @@ -43,13 +43,13 @@ | 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. | -| **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. | -| **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). | +| **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 — [NEON-16](https://neon-sprawl.atlassian.net/browse/NEON-16) (not a blocker for this story). | ## 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] 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. ## 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. 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). 3. **Wiring (`main.gd` / `PositionAuthorityClient`)** @@ -103,18 +103,18 @@ Prototype player collider in [`main.tscn`](../../client/scenes/main.tscn): **`Ca | Path | Rationale | |------|-----------| | `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/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. | | `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 | 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. | ## Open questions / risks @@ -123,4 +123,4 @@ Prototype player collider in [`main.tscn`](../../client/scenes/main.tscn): **`Ca ## 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. diff --git a/docs/reviews/2026-03-29-NS-15.md b/docs/reviews/2026-03-29-NEON-3.md similarity index 62% rename from docs/reviews/2026-03-29-NS-15.md rename to docs/reviews/2026-03-29-NEON-3.md index aa223c7..93bae47 100644 --- a/docs/reviews/2026-03-29-NS-15.md +++ b/docs/reviews/2026-03-29-NEON-3.md @@ -1,13 +1,13 @@ -# Code review — NS-15 (authoritative position read API) +# Code review — NEON-3 (authoritative position read API) ## Review metadata | Field | Value | |--------|--------| | **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` | -| **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 -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`, 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`, 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 -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 @@ -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. -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). @@ -38,7 +38,7 @@ The NS-15 work adds an in-memory `IPositionStateStore`, `Game` configuration for ## Nits - **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 @@ -62,13 +62,13 @@ Expect HTTP 200 and JSON including `schemaVersion`, `playerId`, `position` with ## 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/*` ## 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. - **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. diff --git a/docs/reviews/2026-03-30-NS-16.md b/docs/reviews/2026-03-30-NEON-4.md similarity index 53% rename from docs/reviews/2026-03-30-NS-16.md rename to docs/reviews/2026-03-30-NEON-4.md index a0a2278..2794ae0 100644 --- a/docs/reviews/2026-03-30-NS-16.md +++ b/docs/reviews/2026-03-30-NEON-4.md @@ -1,8 +1,8 @@ -# Code review: NS-16 (MoveCommand → PositionState) +# Code review: NEON-4 (MoveCommand → PositionState) **Date:** 2026-03-30 -**Scope:** Branch `NS-16-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 +**Scope:** Branch `NEON-4-move-command-position-state` vs `origin/main` (working tree / uncommitted changes included at review time). +**Issue:** [NEON-4](https://neon-sprawl.atlassian.net/browse/NEON-4) — E1.M1: Submit MoveCommand → server updates PositionState **Base:** `origin/main` ## Verdict @@ -11,15 +11,15 @@ ## 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 | Document | Result | |----------|--------| -| `docs/plans/NS-16-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/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | **Partially matched → updated** — snapshot still said “Not yet: MoveCommand”; **updated** to reflect NS-16. | +| `docs/plans/NEON-4-implementation-plan.md` | **Matches** — endpoint, snap rule, client split, tests, README expectations align with implementation. | +| `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 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/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. | @@ -33,7 +33,7 @@ The branch delivers NS-16 end-to-end: **`POST /game/players/{id}/move`** with ve ## 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. 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:** -- 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. ## Verification diff --git a/docs/reviews/2026-03-30-NS-17.md b/docs/reviews/2026-03-30-NEON-5.md similarity index 65% rename from docs/reviews/2026-03-30-NS-17.md rename to docs/reviews/2026-03-30-NEON-5.md index 6985d64..3e42b92 100644 --- a/docs/reviews/2026-03-30-NS-17.md +++ b/docs/reviews/2026-03-30-NEON-5.md @@ -1,16 +1,16 @@ -# Code review — NS-17 (PostgreSQL position persistence) +# Code review — NEON-5 (PostgreSQL position persistence) **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 -**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 **low–medium**: 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 | |----------|------------| -| `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/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/documentation_and_implementation_alignment.md` | **Matches (post-review update)** — E1.M1 tracking row includes NS-17 and Postgres-when-configured. | +| `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 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/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. | --- @@ -44,8 +44,8 @@ The diff also includes **repo-wide C# brace style** updates in `.cursor/rules/cs ## 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). -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). +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:** [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. --- @@ -68,4 +68,4 @@ Before merge, the author should run: 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. -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`. diff --git a/docs/reviews/2026-04-04-NS-18.md b/docs/reviews/2026-04-04-NEON-6.md similarity index 72% rename from docs/reviews/2026-04-04-NS-18.md rename to docs/reviews/2026-04-04-NEON-6.md index 2d5f3ea..b9f1d8e 100644 --- a/docs/reviews/2026-04-04-NS-18.md +++ b/docs/reviews/2026-04-04-NEON-6.md @@ -1,12 +1,12 @@ -# Code review — NS-18 (InteractionRequest + related) +# Code review — NEON-6 (InteractionRequest + related) **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). ## 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 @@ -16,25 +16,25 @@ The work delivers **server-authoritative** `POST /game/players/{id}/interact` wi | 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/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | **Matches** — Snapshot documents InteractionRequest + range check and links to NS-18 / server README. | -| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | **Matches** — E1.M1 row references NS-18 and server paths. | +| `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 NEON-6 / server README. | +| `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. | | `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. | -| `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). | +| `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 NEON-6 explicitly there (not required if table stays high-level). | ## Blocking issues _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 1. **`HorizontalReach`:** ~~Consider guarding **`radius < 0`**~~ **Done** — `ArgumentOutOfRangeException.ThrowIfNegative` + unit test. 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 diff --git a/docs/reviews/2026-04-05-NEON-16.md b/docs/reviews/2026-04-05-NEON-16.md new file mode 100644 index 0000000..c1e66ff --- /dev/null +++ b/docs/reviews/2026-04-05-NEON-16.md @@ -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 **floor–bump** contact (prior jitter loci). + +**Automated:** None required for NEON-16 (no Godot harness in repo). diff --git a/docs/reviews/2026-04-05-NS-23.md b/docs/reviews/2026-04-05-NEON-8.md similarity index 81% rename from docs/reviews/2026-04-05-NS-23.md rename to docs/reviews/2026-04-05-NEON-8.md index de4d4f1..ff274b4 100644 --- a/docs/reviews/2026-04-05-NS-23.md +++ b/docs/reviews/2026-04-05-NEON-8.md @@ -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 -**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` **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). -**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 | 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/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. | -| `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. | ## 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). 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 @@ -51,5 +51,5 @@ The branch adds **`NavigationRegion3D`** / **`NavigationAgent3D`**, sync nav bak - `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. -- 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. diff --git a/docs/reviews/2026-04-05-NS-24.md b/docs/reviews/2026-04-05-NS-24.md deleted file mode 100644 index a12b222..0000000 --- a/docs/reviews/2026-04-05-NS-24.md +++ /dev/null @@ -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 **floor–bump** contact (prior jitter loci). - -**Automated:** None required for NS-24 (no Godot harness in repo). diff --git a/server/README.md b/server/README.md index f6855de..440568b 100644 --- a/server/README.md +++ b/server/README.md @@ -16,11 +16,11 @@ dotnet run - Default URL is printed by Kestrel (see `Properties/launchSettings.json`, typically `http://localhost:5253`). - 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 store’s 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** project’s `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** project’s `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): @@ -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. -## 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`): @@ -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 ``` -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 {"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. -## 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. -**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`) | |------------|---------|-----------------------------------| @@ -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`). -- **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. 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/`. @@ -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**. -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