Compare commits

..

15 Commits

Author SHA1 Message Date
don 707129b38e Merge pull request 'feature/ns-14-click-to-move' (#4) from feature/ns-14-click-to-move into main
Reviewed-on: don/neon-sprawl#4
2026-03-29 22:09:30 -04:00
don 64092566a4 fix(client): reliable NS-14 click-to-move without NavigationMesh
- Drive movement with horizontal steering + move_and_slide toward click goal
- Remove NavigationRegion3D / NavigationAgent3D; obstacle interaction is slide
- main.gd: raycast to walkable, Player.set_move_goal; viewport camera + mouse
- Scene: explicit physics layers/mask on floor, obstacle, player
- Docs: Godot 4.6 baseline, README + NS-14 plan + tech_stack; Godot .import/.uid

Made-with: Cursor
2026-03-29 22:08:25 -04:00
don 343592541a feat(client): NS-14 click-to-move with NavigationAgent3D
- Main scene: floor, obstacle, nav mesh, camera, CharacterBody3D + agent
- Click-to-move on walkable group; pick mask avoids avatar self-hit
- player.gd drives path following; README and plan AC updated

Made-with: Cursor
2026-03-29 21:19:52 -04:00
don 2aabcb969a chore(cursor): require explicit user approval before git commit
Add commit-and-review rule; align story-kickoff and git-workflow with it.

Made-with: Cursor
2026-03-29 21:19:49 -04:00
don 9a9f4ab3ad docs: add NS-14 click-to-move implementation plan
Made-with: Cursor
2026-03-29 21:09:43 -04:00
don 353fa23d29 chore(cursor): rename project rules from .mdc to .md
Update cross-references in testing-expectations and story-kickoff.

Made-with: Cursor
2026-03-29 21:04:08 -04:00
don 80082a1eac Add Cursor rules for story kickoff, authority, styles, and testing.
Introduce project rules: Jira story kickoff and docs/plans workflow, server
authority boundaries, GDScript and C# style guides, and testing expectations
including integration tests when persistence is in scope. All use
alwaysApply where requested so they stay in context.

Made-with: Cursor
2026-03-29 20:48:51 -04:00
don cbf15fc8bb chore: retarget server to .NET 10 and update stack docs
- NeonSprawl.Server: net8.0 -> net10.0
- README, tech_stack, server README, vision plan: .NET 8 -> .NET 10

Made-with: Cursor
2026-03-29 19:49:45 -04:00
don f5305a447b Document git workflow: docs on main, code on branches
- README: Git workflow section for humans
- Cursor rule (always apply) so the agent follows the same split

Made-with: Cursor
2026-03-29 19:38:36 -04:00
don dbcb326a2e a 2026-03-29 19:36:47 -04:00
don 764c832b74 Merge branch 'docs/decomposition-follow-up' into main 2026-03-29 19:36:30 -04:00
don 31bce2b344 Lock client scripting to GDScript; document in README and tech stack
- State GDScript as the locked choice for Godot client code
- Summary tables and client README note standard (non-.NET) Godot build
- Server/client contract via Protobuf or JSON spike, not shared C# on client

Made-with: Cursor
2026-03-29 19:36:18 -04:00
don 91bd18e69e Merge pull request 'docs/decomposition-follow-up' (#3) from docs/decomposition-follow-up into main
Reviewed-on: don/untitled-cyberpunk-mmo#3
2026-03-29 17:25:34 -04:00
don 63a7ccb8e2 Merge pull request 'Document tech stack, README, and align prototype critical path with modules.' (#2) from docs/decomposition-follow-up into main
Reviewed-on: don/untitled-cyberpunk-mmo#2
2026-03-29 17:05:35 -04:00
don 063974ca93 Merge pull request 'Update module dependency register and README with new epic documentation and refined next actions. Added SecurityTierZoneFlags module and clarified dependencies in the planning documents.' (#1) from docs/decomposition-follow-up into main
Reviewed-on: don/untitled-cyberpunk-mmo#1
2026-03-29 16:50:02 -04:00
22 changed files with 561 additions and 20 deletions

View File

@ -0,0 +1,30 @@
---
description: Server-authoritative simulation; client sends intents; transport-only networking; no client-trusted outcomes.
alwaysApply: true
---
# Architecture — authority & client boundaries (Neon Sprawl)
Canonical background: [`docs/architecture/tech_stack.md`](docs/architecture/tech_stack.md). This rule is the **non-negotiable slice** for day-to-day implementation.
## Where truth lives
- **Authoritative game state and rules** live on the **C# / ASP.NET Core** server (zones, combat resolution, inventory, economy, progression, anything audit-worthy).
- The **Godot** client is responsible for **input, presentation, camera, UI, and local feel** (e.g. interpolation, cosmetic prediction only if explicitly designed)—not for deciding final outcomes of gameplay systems.
## Network shape
- **WebSocket** or **TCP** is **transport only**. Do not put authoritative simulation in Godots **high-level multiplayer** templates or patterns that imply peers co-own game state.
- Prefer a clear boundary: client emits **intents** (e.g. `MoveIntent`, `UseAbilityIntent`); server validates, simulates, persists as needed, and responds with **state deltas or snapshots**. Names are examples—follow whatever contracts exist in-repo.
## What the client must not “decide”
- Do not treat the client as source of truth for **loot, trades, crafting results, currency, or anti-cheat-sensitive** behavior. Those paths belong on the server with validation and, where required, **database transactions** (see tech stack doc).
## Exceptions
- **Prototype-only, client-local** behavior is allowed when a Jira story or plan explicitly scopes it (e.g. **no server yet**, “client-only milestone”) and docs/README call out that it is **temporary** until authoritative sync exists. Do not silently expand client-only shortcuts into permanent architecture.
## When unsure
- Default to **server validates + owns outcome**; add a short note in the story plan or PR if you introduce a deliberate exception.

View File

@ -0,0 +1,21 @@
---
description: Never git commit without explicit user instruction; user reviews before any commit
alwaysApply: true
---
# Commits and review (Neon Sprawl)
## No commits without explicit instruction
- Do **not** run **`git commit`** (or equivalent, e.g. committing via tools) unless the user **explicitly** asks—clear wording such as “commit this”, “make a commit”, or “commit with message …”.
- Phrases like “implement X”, “begin work”, or “open a PR” are **not** implicit permission to commit.
- **Default after edits:** leave changes **uncommitted** (working tree or staged only if the user asked to stage). Summarize what changed and where so the user can review in the Git / diff UI, then wait for commit instructions.
## Review before commit
- The user should **review** the diff before anything is committed. The agents job is to make the changes visible (uncommitted) and explain them; the user decides when to commit.
- If the user asks to commit, use a message that matches repo conventions; still follow [git workflow](git-workflow.md) (branch vs `main`, doc-only vs code).
## Scope
- Applies to **all** commits the agent might make, including **documentation-only** changes (e.g. `docs/plans/`, README), not only application source.

View File

@ -0,0 +1,45 @@
---
description: C# naming and layout aligned with Microsoft coding conventions and .NET idioms.
globs: "**/*.cs"
alwaysApply: true
---
# C# style (Neon Sprawl)
Follow Microsofts **[C# Coding Conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions)** and **[C# identifier rules](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/identifier-names)**. Prefer clarity and consistency with existing server code.
## Naming
- **Types** (classes, structs, records, interfaces, enums, delegates): `PascalCase`.
- **Interfaces:** prefix with `I` (e.g. `IPlayerSession`).
- **Methods, properties, events, public fields:** `PascalCase`.
- **Parameters, local variables:** `camelCase`.
- **Private instance fields:** `camelCase` (no leading underscore), unless an existing file consistently does otherwise—then match the file. If a parameter or local shadows a field, use `this.` or rename for clarity.
- **Static fields:** `camelCase` for private/internal static fields; `PascalCase` for `public static` members (including `readonly`/constants) per Microsoft guidance; stay consistent within a project.
- **Async methods:** suffix with `Async` (e.g. `LoadProfileAsync`).
## Layout and syntax
- **Braces:** opening brace `{` on a **new line** for types and members (Allman-style), per common Microsoft examples.
- **Indentation:** **4 spaces** per level; no tabs unless the file already uses tabs—never mix.
- **`var`:** use when the type is obvious from the right-hand side; use explicit types when it improves readability or for literals where the type matters.
- **File-scoped namespaces** (`namespace X;`) for new single-namespace files when the SDK/version allows.
- **Pattern matching / nullability:** prefer modern C# features where they simplify code; honor **nullable reference type** annotations when the project enables them.
## Members
- Prefer **expression-bodied** members only when they stay one clear idea.
- **LINQ:** favor readability over micro-chains; break complex queries across lines.
- **Exception handling:** catch specific exceptions; avoid empty `catch`; log or rethrow with context when appropriate.
## `Program.cs` and minimal APIs
- Top-level statements and minimal APIs are fine for small apps; extract registration/build logic into extension methods or dedicated types when the file grows.
## Documentation
- Use **`///` XML doc comments** on public APIs (types and members) when behavior or contracts are not obvious.
## Tooling
- Prefer fixes that satisfy built-in **.NET analyzers** / `dotnet format` (if adopted) rather than fighting IDE warnings without reason.

View File

@ -0,0 +1,45 @@
---
description: GDScript naming, formatting, and Godot-idiomatic patterns (Godot docs style guide).
globs: "**/*.gd"
alwaysApply: true
---
# GDScript style (Neon Sprawl)
Follow the [Godot GDScript style guide](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html). Prefer clarity and consistency with existing scripts in the repo.
## Naming
- **Functions, variables, signals:** `snake_case`.
- **Classes** (`class_name`, custom resources): `PascalCase`.
- **Constants** (`const`): `ALL_CAPS` when they are true fixed values; otherwise match Godot/node conventions used nearby.
- **“Private” / internal:** leading underscore `_like_this` for members meant as implementation detail (not enforced by the language).
## Types and API
- Add **static types** on parameters, locals, and return values where reasonable (`-> void`, `var x: int`, etc.).
- Prefer **`@export`** (Godot 4) over legacy `export` syntax.
- Use **`@onready`** for node references set after the scene tree is ready when that pattern fits.
## Formatting
- **One statement per line**; break long lines for readability (Godot line length is flexible—aim for ~100 characters unless a longer string is clearer).
- **Indentation:** use **tabs** for new Godot work (editor default). Do not mix tabs and spaces in the same file; match the file if it already uses spaces.
- **Blank lines:** separate functions with two blank lines; sparing single blank lines inside functions for logical groups.
- **Trailing commas** in multi-line collections/function args when it improves diffs.
## Structure
- **`class_name`** only when the type must be referenced globally; otherwise anonymous `extends` is fine.
- Order loosely: `extends``class_name``signals``enums``const``export/@export``onready` → other vars → `_ready` / lifecycle → public methods → private helpers.
- Virtual overrides (`_ready`, `_process`, `_physics_process`, etc.): keep small; extract helpers with leading `_`.
## Comments
- Use `#` comments; document non-obvious **why**, not what the next line literally does.
- Doc comments: Godot 4 supports `##` above declarations for the in-editor help—use for public-ish APIs when helpful.
## Anti-patterns
- Avoid `get_node()` string paths repeated everywhere; use `@onready`, exports, or groups when scale demands it.
- Prefer typed collections and enums over “magic” string/int state when it stays readable.

View File

@ -0,0 +1,14 @@
---
description: Branch only for code; documentation commits on main
alwaysApply: true
---
# Git workflow (Neon Sprawl)
**Agent:** Never run `git commit` without explicit user instruction; the user reviews diffs first. See [commit-and-review](commit-and-review.md).
- **Documentation-only work** — Commit directly on **`main`**. This means Markdown (`.md`) under `docs/`, root and nested `README.md` files, `neon_sprawl_vision.plan.md`, and other prose docs. No feature branch required.
- **Code or implementation changes** — Use a **branch** (e.g. `feature/…`, `fix/…`), then merge to `main` when ready. 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).

View File

@ -0,0 +1,42 @@
---
description: When starting Jira story work, load issue + repo context first; no implementation code until a plan exists under docs/plans/.
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.
## 1. Load Jira context
- Fetch the issue when possible (e.g. Atlassian MCP `getJiraIssue`), or use a URL / pasted description the user provides.
- Capture summary, description, acceptance criteria, and anything explicitly **out of scope**.
## 2. Load codebase context (read-only)
- Open only what the story implies: entry scenes/scripts, related modules, configs, tests/CI, and relevant [`docs/`](docs/) files.
- **No edits** and **no new implementation or test files** in this phase.
## 3. No implementation yet
- Do not add or change source, Godot scenes/project, game data pipelines, or automated tests until step 4 is done **and** the user has moved past planning in chat (e.g. explicit go-ahead to implement).
## 4. Planning document
- Add **`docs/plans/{JIRA_KEY}-implementation-plan.md`** (example: `docs/plans/NS-14-implementation-plan.md`). Create `docs/plans/` if it does not exist.
- **Do not** `git commit` the plan unless the user explicitly asks; see [commit-and-review](commit-and-review.md). When the user commits plan-only changes, use **`main`** per [git workflow](git-workflow.md).
**Required sections** in that file:
- Story reference (key, title, link if available)
- Goal, scope, and out-of-scope (from Jira)
- Acceptance criteria checklist (from Jira)
- Technical approach (concise)
- **Files to add** (paths)
- **Files to modify** (paths and one-line rationale each)
- **Tests** — what will be added or changed; if none, say why (e.g. no harness yet, manual verification only)
- Open questions / risks (if any)
## 5. After the plan
- Implement only after the user confirms. For code, scenes, data, or CI changes, use a **branch** and follow [git workflow](git-workflow.md).

View File

@ -0,0 +1,30 @@
---
description: Unit vs integration tests; integration when data persistence/mutation exists; C# xUnit; Godot manual until harness.
alwaysApply: true
---
# Testing expectations (Neon Sprawl)
## C# server
- **Non-trivial logic** (validation, simulation rules, serialization boundaries, idempotency, security-sensitive paths) should have **automated tests** once a test project exists in the repo.
- **Default framework:** **xUnit** for new test projects unless the repo already standardizes on something else—stay consistent with existing `*.Tests` projects.
- **No test project yet:** It is acceptable to ship a small change without a suite only while the server is mostly scaffolding. When you add the **first** testable module, **add a `NeonSprawl.Server.Tests` (or equivalent) project** and start covering that area; do not leave new core logic permanently untested.
- **Bug fixes:** Prefer a **regression test** when the fix is non-obvious or has broken before.
## Integration testing (data manipulation)
- Once the server **reads or writes durable state** (e.g. **PostgreSQL** via EF Core, Dapper, or similar; migrations; transactional inventory/progression/economy paths), expect **integration tests** that exercise the **real persistence boundary**, not only mocked repositories.
- Cover at least: **happy path**, **constraints / failures** (unique keys, FKs, expected rollbacks), and **idempotent or retry-safe** behavior where the design requires it.
- **How to run them:** Prefer a project-standard approach (dedicated test database, **Testcontainers**, or CI service container) documented in `server/` README or `docs/`—do not leave integration tests machine-only without notes.
- **Spike exception:** A story may defer integration tests only when persistence is explicitly **out of scope**; call that out in the plan and add tests when data manipulation lands.
## Godot client (GDScript)
- Until the project adopts a harness (**GUT**, Godot unit tests, or CI export checks), rely on **manual verification** for gameplay and scene changes.
- For non-trivial client work, note **how you verified** (steps or scenario) in the PR or story plan so reviewers can repeat it.
## Cross-cutting
- If **CI** is added later, treat its **required** steps (e.g. `dotnet test`, schema validation) as gates: fix failures or update workflows/docs intentionally—do not silence checks without a recorded reason.
- When a story is **explicitly client-only or spike** (no server), match that scope in tests too (manual client checks only), and call out temporariness per [architecture authority](architecture-authority.md).

View File

@ -0,0 +1,7 @@
{
"plugins": {
"atlassian": {
"enabled": true
}
}
}

View File

@ -6,8 +6,8 @@
| Area | Choice | | Area | Choice |
|------|--------| |------|--------|
| Client | **Godot 4.x** | | Client | **Godot 4.x** (GDScript) |
| Game server | **C# / .NET 8** (ASP.NET Core), **PostgreSQL**, **Protobuf** (JSON OK for earliest spike) | | Game server | **C# / .NET 10** (ASP.NET Core), **PostgreSQL**, **Protobuf** (JSON OK for earliest spike) |
| Content | **JSON/YAML** + **JSON Schema** in CI | | Content | **JSON/YAML** + **JSON Schema** in CI |
Full rationale and constraints: [`docs/architecture/tech_stack.md`](docs/architecture/tech_stack.md). Full rationale and constraints: [`docs/architecture/tech_stack.md`](docs/architecture/tech_stack.md).
@ -16,6 +16,11 @@ Full rationale and constraints: [`docs/architecture/tech_stack.md`](docs/archite
Epic-level breakdown: [`docs/decomposition/README.md`](docs/decomposition/README.md). Epic-level breakdown: [`docs/decomposition/README.md`](docs/decomposition/README.md).
## Git workflow
- **Documentation** (Markdown under `docs/`, README files, `neon_sprawl_vision.plan.md`): work and commit **directly on `main`**.
- **Code and implementation** (server/client source, Godot project, `content/` data, Docker/build/CI config): use a **branch**, then merge to `main` when ready. Mixed doc + code changes follow the branch rule.
## Repository layout (prototype scaffold) ## Repository layout (prototype scaffold)
| Path | Purpose | | Path | Purpose |
@ -44,4 +49,4 @@ Connection (dev): host `localhost`, port `5432`, database `neon_sprawl`, user `n
### Run the client ### Run the client
Open the [`client/`](client/) folder in **Godot 4.2+** and run the main scene (see [`client/README.md`](client/README.md)). Open the [`client/`](client/) folder in **Godot 4.6** and run the main scene (see [`client/README.md`](client/README.md)).

View File

@ -1,10 +1,30 @@
# Neon Sprawl — Godot client # Neon Sprawl — Godot client
Open this **`client/`** directory as a project in **Godot 4.2+** (4.x recommended). Open this **`client/`** directory as a project in **Godot 4.6** (4.x compatible). Use the **standard** Godot build (not the .NET build)—client code is **GDScript** (`.gd`).
- Main scene: `scenes/main.tscn` (bootstrap `scripts/main.gd`). - Main scene: `scenes/main.tscn` (bootstrap `scripts/main.gd`).
- Networking will use **WebSocket** or **TCP** to the C# server; authoritative logic stays on the server per [`docs/architecture/tech_stack.md`](../docs/architecture/tech_stack.md). - Networking will use **WebSocket** or **TCP** to the C# server; authoritative logic stays on the server per [`docs/architecture/tech_stack.md`](../docs/architecture/tech_stack.md).
## Movement prototype (NS-14)
The main scene includes a **client-only** click-to-move demo:
- **Left-click** walkable ground (the large floor) to move the capsule avatar; **WASD is not required**.
- Movement is **direct horizontal steering** plus **`move_and_slide()`**: the capsule walks toward the click and **slides along** the center crate instead of pathfinding around it. (A hand-authored `NavigationMesh` was not reliable across Godot versions; **NavigationAgent3D** can return later for routed paths.)
- 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.
This behavior is **temporary**: when authoritative movement and `MoveCommand` / `PositionState` exist, the client will follow server state instead of driving navigation locally.
### Manual check
1. Run the main scene (**F5**).
2. Click the floor: the avatar walks to the point.
3. Click behind the center crate: the avatar **slides** against the crate (no nav mesh path around it in this build).
### Clicks still ignored?
In the **Game** dock, **Input** must be active (not the **2D** / **3D** scene-picking tools) so events go to the game. If Input is on and clicks still do nothing, pick rays must use the **viewports current camera** and **mouse position** (the script does this so the embedded Game view matches the ray).
## First run ## First run
1. Install [Godot 4.x](https://godotengine.org/download). 1. Install [Godot 4.x](https://godotengine.org/download).

View File

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://clvcqwwuhaity"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -1,13 +1,23 @@
; Godot 4.x project — import this folder in the Godot project manager. ; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5 config_version=5
[animation]
compatibility/default_parent_skeleton_in_mesh_instance_3d=true
[application] [application]
config/name="Neon Sprawl" config/name="Neon Sprawl"
config/description="Neon Sprawl — prototype client (isometric MMO; planning docs in repo root)." config/description="Neon Sprawl — prototype client (isometric MMO; planning docs in repo root)."
run/main_scene="res://scenes/main.tscn" run/main_scene="res://scenes/main.tscn"
config/features=PackedStringArray("4.2", "Forward Plus") config/features=PackedStringArray("4.6", "Forward Plus")
config/icon="res://icon.svg" config/icon="res://icon.svg"
[display] [display]
@ -15,6 +25,10 @@ config/icon="res://icon.svg"
window/size/viewport_width=1280 window/size/viewport_width=1280
window/size/viewport_height=720 window/size/viewport_height=720
[dotnet]
project/assembly_name="Neon Sprawl"
[rendering] [rendering]
textures/canvas_textures/default_texture_filter=0 textures/canvas_textures/default_texture_filter=0

View File

@ -1,6 +1,72 @@
[gd_scene load_steps=2 format=3 uid="uid://cyberpunkmmo_main"] [gd_scene format=3 uid="uid://dg2g1nd82lyxm"]
[ext_resource type="Script" path="res://scripts/main.gd" id="1_main"] [ext_resource type="Script" uid="uid://bh04b3iify0hd" path="res://scripts/main.gd" id="1_main"]
[ext_resource type="Script" uid="uid://1jimgt3d4bjj" path="res://scripts/player.gd" id="2_player"]
[node name="Main" type="Node3D"] [sub_resource type="BoxMesh" id="BoxMesh_floor"]
size = Vector3(20, 0.2, 20)
[sub_resource type="BoxShape3D" id="BoxShape3D_floor"]
size = Vector3(20, 0.2, 20)
[sub_resource type="BoxMesh" id="BoxMesh_obstacle"]
size = Vector3(2, 2, 2)
[sub_resource type="BoxShape3D" id="BoxShape3D_obstacle"]
size = Vector3(2, 2, 2)
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_player"]
radius = 0.4
height = 1.0
[sub_resource type="CapsuleMesh" id="CapsuleMesh_player"]
radius = 0.4
height = 1.0
[node name="Main" type="Node3D" unique_id=1358372723]
script = ExtResource("1_main") script = ExtResource("1_main")
[node name="World" type="Node3D" parent="." unique_id=1042212190]
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="World" unique_id=201819776]
transform = Transform3D(0.866025, -0.353553, 0.353553, 0, 0.707107, 0.707107, -0.5, -0.612372, 0.612372, 0, 6, 0)
shadow_enabled = true
[node name="Camera3D" type="Camera3D" parent="World" unique_id=1124088856]
transform = Transform3D(0.819152, -0.40558, 0.40558, 0, 0.707107, 0.707107, -0.573576, -0.579228, 0.579228, 12, 10, 12)
current = true
fov = 50.0
[node name="Floor" type="StaticBody3D" parent="World" unique_id=1800743112 groups=["walkable"]]
collision_layer = 1
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/Floor" unique_id=152652175]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.1, 0)
mesh = SubResource("BoxMesh_floor")
[node name="CollisionShape3D" type="CollisionShape3D" parent="World/Floor" unique_id=409532142]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.1, 0)
shape = SubResource("BoxShape3D_floor")
[node name="Obstacle" type="StaticBody3D" parent="World" unique_id=1638845763]
collision_layer = 1
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/Obstacle" unique_id=750473628]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
mesh = SubResource("BoxMesh_obstacle")
[node name="CollisionShape3D" type="CollisionShape3D" parent="World/Obstacle" unique_id=1344302688]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
shape = SubResource("BoxShape3D_obstacle")
[node name="Player" type="CharacterBody3D" parent="." unique_id=352931696]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5, 0.9, -5)
collision_layer = 2
collision_mask = 1
script = ExtResource("2_player")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Player" unique_id=1695755590]
shape = SubResource("CapsuleShape3D_player")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Player" unique_id=2027034386]
mesh = SubResource("CapsuleMesh_player")

View File

@ -1,4 +1,45 @@
extends Node3D extends Node3D
func _ready() -> void: ## NS-14: client-only click-to-move. Provisional until authoritative movement sync (server / MoveCommand).
print("Neon Sprawl client bootstrap (Godot). Connect to game server TBD.")
@onready var _camera: Camera3D = $World/Camera3D
@onready var _player: CharacterBody3D = $Player
func _input(event: InputEvent) -> void:
if event is InputEventMouseButton:
var mb := event as InputEventMouseButton
if mb.button_index == MOUSE_BUTTON_LEFT and mb.pressed:
_try_set_move_target(get_viewport().get_mouse_position())
func _try_set_move_target(screen_pos: Vector2) -> void:
var cam: Camera3D = get_viewport().get_camera_3d()
if cam == null:
cam = _camera
var origin: Vector3 = cam.project_ray_origin(screen_pos)
var ray_dir: Vector3 = cam.project_ray_normal(screen_pos)
var to: Vector3 = origin + ray_dir * 2000.0
var query := PhysicsRayQueryParameters3D.create(origin, to)
query.collision_mask = 1
var space: PhysicsDirectSpaceState3D = get_world_3d().direct_space_state
var hit: Dictionary = space.intersect_ray(query)
if hit.is_empty():
return
var collider: Variant = hit.get("collider")
if not _collider_is_walkable(collider):
return
var hit_pos: Variant = hit.get("position")
if hit_pos is Vector3:
_player.set_move_goal(hit_pos as Vector3)
func _collider_is_walkable(collider: Variant) -> bool:
var n: Node = collider as Node
while n:
if n.is_in_group("walkable"):
return true
n = n.get_parent()
return false

View File

@ -0,0 +1 @@
uid://bh04b3iify0hd

View File

@ -0,0 +1,30 @@
extends CharacterBody3D
## NS-14: click-to-move using horizontal steering + move_and_slide (not NavigationAgent3D).
## Obstacles are handled by physics sliding, not pathfinding — see client README.
const MOVE_SPEED: float = 5.0
const ARRIVE_EPS: float = 0.35
var _goal: Vector3
func _ready() -> void:
_goal = global_position
func set_move_goal(world_pos: Vector3) -> void:
_goal = world_pos
_goal.y = global_position.y
func _physics_process(_delta: float) -> void:
var to_goal: Vector3 = _goal - global_position
to_goal.y = 0.0
if to_goal.length() <= ARRIVE_EPS:
velocity = Vector3.ZERO
else:
velocity = to_goal.normalized() * MOVE_SPEED
velocity.y = 0.0
move_and_slide()

View File

@ -0,0 +1 @@
uid://1jimgt3d4bjj

View File

@ -4,16 +4,18 @@
**Constraints from product vision:** Solo operator, server-authoritative critical paths, fixed isometric 3D client, tab-target combat, data-driven skills/items/recipes/quests, optional PvP zones, eventual scale-out (not day one). **Constraints from product vision:** Solo operator, server-authoritative critical paths, fixed isometric 3D client, tab-target combat, data-driven skills/items/recipes/quests, optional PvP zones, eventual scale-out (not day one).
**Server runtime (locked):** **C# / .NET 8** — primary author has ~10 years C# experience; authoritative game logic, persistence, and ops stay in this stack. **Server runtime (locked):** **C# / .NET 10** — primary author has ~10 years C# experience; authoritative game logic, persistence, and ops stay in this stack.
**Other operator skills:** **JavaScript** and **TypeScript** remain useful for content tooling, client-side scripting in Godot (GDScript on client for fast iteration), and one-off scripts—**not** the authoritative server. **Client scripting (locked):** **GDScript** in Godot—fast iteration, native engine integration; server remains the only **C#** game-logic surface.
**Other operator skills:** **JavaScript** and **TypeScript** remain useful for content tooling and one-off scripts—**not** the authoritative server.
## Summary ## Summary
| Layer | Choice | Role | | Layer | Choice | Role |
|--------|--------|------| |--------|--------|------|
| Game client | **Godot 4.x** | Rendering, input, camera, local prediction (if any), UI, telemetry emit | | Game client | **Godot 4.x** + **GDScript** | Rendering, input, camera, local prediction (if any), UI, telemetry emit |
| Game server | **C# / .NET 8** (ASP.NET Core) | Authoritative simulation, zones, combat/crafting resolution, persistence boundaries | | Game server | **C# / .NET 10** (ASP.NET Core) | Authoritative simulation, zones, combat/crafting resolution, persistence boundaries |
| RPC / messages | **Protobuf** (or **JSON** for earliest spike only) | Versioned contracts between client and server | | RPC / messages | **Protobuf** (or **JSON** for earliest spike only) | Versioned contracts between client and server |
| Primary database | **PostgreSQL** | Characters, inventory, progression, economy ledger, idempotent transactions | | Primary database | **PostgreSQL** | Characters, inventory, progression, economy ledger, idempotent transactions |
| Cache / presence (when needed) | **Redis** | Sessions, rate limits, hot read models—**defer** until second scaling pain | | Cache / presence (when needed) | **Redis** | Sessions, rate limits, hot read models—**defer** until second scaling pain |
@ -22,11 +24,13 @@
## Client: Godot 4 ## Client: Godot 4
The [`client/`](../../client/) Godot project targets **4.6** (`config/features` in `project.godot`). Older 4.x may work but 4.6 is the baseline for editor and manual testing.
**Why** **Why**
- Strong fit for solo iteration: fast reload, small install, no revenue cap. - Strong fit for solo iteration: fast reload, small install, no revenue cap.
- 3D isometric camera (orthographic or perspective), zoom, and **no rotation** are straightforward in a single-camera rig. - 3D isometric camera (orthographic or perspective), zoom, and **no rotation** are straightforward in a single-camera rig.
- **GDScript** for rapid client prototyping; **Godot C#** available if you want client/server language alignment in hot paths. - **GDScript (locked)** for all client gameplay and UI code—quick reload, examples and addons match; share contracts with the server via **Protobuf** or **JSON** (spike), not shared C# assemblies.
- Networking: treat **WebSocket** or **TCP** as transport only; **authoritative logic stays on the ASP.NET Core server**, not in Godots high-level multiplayer templates, to avoid fighting engine assumptions. - Networking: treat **WebSocket** or **TCP** as transport only; **authoritative logic stays on the ASP.NET Core server**, not in Godots high-level multiplayer templates, to avoid fighting engine assumptions.
**Prototype discipline** **Prototype discipline**
@ -42,7 +46,7 @@
- **Unreal** for this phase unless art direction *requires* it. - **Unreal** for this phase unless art direction *requires* it.
## Server: C# (.NET 8) ## Server: C# (.NET 10)
**Why** **Why**

View File

@ -0,0 +1,82 @@
# NS-14 — Implementation plan
## Story reference
| Field | Value |
|--------|--------|
| **Key** | NS-14 |
| **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) |
## Goal, scope, and out-of-scope
**Goal:** On the Godot client only, prove click-to-move locomotion and the input → world-target path before any networking or server authority.
**In scope**
- Placeholder avatar in the prototype scene (`main.tscn` or a dedicated movement test scene; default choice: extend `main.tscn` so F5 still runs the prototype).
- Ground pick via camera raycast; on valid hit, command movement to that point.
- Flat prototype terrain and simple obstacles sufficient to validate stopping behavior.
- Brief note in README and/or scene that this is **temporary** until authoritative movement sync exists.
**Out of scope (per Jira)**
- Game server, persistence, `MoveCommand` / wire protocol, Protobuf contracts.
## Acceptance criteria checklist
- [ ] Clicking walkable ground moves the avatar to the destination without requiring WASD (mouse-only locomotion for this prototype).
- [ ] Movement stops at obstacles when using navigation; **or** if using a direct move-to approach instead, the limitation is documented (README and/or plan).
- [ ] README or an in-editor scene note states this behavior is provisional until authoritative sync lands.
## Technical approach
1. **Scene setup (`main.tscn`)**
- Add a **Camera3D** suitable for ground picking (angled downward at a `StaticBody3D` floor with collision).
- Add a **CharacterBody3D** placeholder (e.g. `CapsuleMesh` + `CollisionShape3D`) as the avatar.
- Add at least one **obstacle** (`StaticBody3D`) so “stop at obstacles” is observable.
- Add a **NavigationRegion3D** covering the walkable floor; bake a navigation mesh from the floor (and subtract or exclude obstacles per Godot 4 workflow).
2. **Movement model**
- Prefer **NavigationAgent3D** on the avatar: set `target_position` from the raycast hit on the ground; each frame move the body toward the agents next path position (e.g. `velocity` + `move_and_slide`, or equivalent Godot 4 pattern). This satisfies the AC path that expects stopping at obstacles via navigation.
- Fallback only if navigation proves unnecessarily heavy for the first slice: direct `move_toward` / velocity toward the click point with `move_and_slide` and document that there is no pathfinding.
3. **Input**
- `_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.
## Files to add
| Path | Purpose |
|------|--------|
| None required initially | If `main.gd` grows unwieldy, optionally extract `scripts/player_navigation.gd` (or similar) attached to the avatar—only if it keeps the scene readable. |
*(Default: implement in existing `scripts/main.gd` + scene edits unless script size forces a split.)*
## Files to modify
| Path | Rationale |
|------|-----------|
| `client/scenes/main.tscn` | Add camera, terrain, obstacles, navigation region, avatar, and wire scripts. |
| `client/scripts/main.gd` | Raycast-on-click, navigation target updates, and any per-frame movement driver if kept on root. |
| `client/README.md` | Document prototype movement, temporary nature, and manual verification steps. |
| `client/project.godot` | Only if needed (e.g. input map, layer names, or feature flags)—prefer minimal diffs. |
## Tests
- **Automated:** None for this story; the repo has no Godot test harness yet ([testing expectations](../../.cursor/rules/testing-expectations.md)).
- **Manual verification:**
1. Open `client/` in Godot 4.2+, run main scene.
2. Click on the floor: avatar moves to the point without using WASD.
3. Click a destination behind an obstacle: avatar path avoids or stops appropriately per navigation behavior.
4. Confirm README/scene note mentions provisional client-only movement.
## Open questions / risks
- **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.

View File

@ -118,7 +118,7 @@ These decisions heavily affect architecture and content pipelines and should be
**Decision record:** [`docs/architecture/tech_stack.md`](docs/architecture/tech_stack.md) **Decision record:** [`docs/architecture/tech_stack.md`](docs/architecture/tech_stack.md)
- **Client:** Godot 4.x — isometric 3D presentation, intents to server, no client-authoritative economy or combat outcomes. - **Client:** Godot 4.x — isometric 3D presentation, intents to server, no client-authoritative economy or combat outcomes.
- **Game server:** **C# / .NET 8** (ASP.NET Core)—authoritative simulation, PostgreSQL, Protobuf (JSON acceptable for earliest spike). Locked: operator primary language for backend. - **Game server:** **C# / .NET 10** (ASP.NET Core)—authoritative simulation, PostgreSQL, Protobuf (JSON acceptable for earliest spike). Locked: operator primary language for backend.
- **Content:** JSON/YAML + schema validation in CI; shared definitions drive skills, items, recipes, quests. - **Content:** JSON/YAML + schema validation in CI; shared definitions drive skills, items, recipes, quests.
- **Plan B:** Unity client if the art pipeline warrants it; **server remains C#** for continuity. - **Plan B:** Unity client if the art pipeline warrants it; **server remains C#** for continuity.

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>NeonSprawl.Server</RootNamespace> <RootNamespace>NeonSprawl.Server</RootNamespace>

View File

@ -1,6 +1,6 @@
# Game server (`NeonSprawl.Server`) # Game server (`NeonSprawl.Server`)
ASP.NET Core **.NET 8** host for authoritative simulation (prototype: HTTP + health only). ASP.NET Core **.NET 10** host for authoritative simulation (prototype: HTTP + health only).
## Run ## Run