Replace stacked NS-19 workarounds (underfoot rays, plateau nav skip, 3D climb/descent steering, rim escape) with one rule: set horizontal velocity toward waypoint/goal, force velocity.y = 0, move_and_slide + floor_* handle height along geometry. Server MoveCommand remains the single place for allow/deny on illegal steps. |
||
|---|---|---|
| .. | ||
| scenes | ||
| scripts | ||
| README.md | ||
| icon.svg | ||
| icon.svg.import | ||
| project.godot | ||
README.md
Neon Sprawl — Godot client
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(bootstrapscripts/main.gd). - Networking will use WebSocket or TCP to the C# server; authoritative logic stays on the server per
docs/architecture/tech_stack.md.
Script organization (repo policy)
Do not grow an all-in-one main.gd. The main scene root should compose child nodes and scripts; put picking, server sync, and similar features in dedicated scripts (typically under scripts/) and connect via signals or small APIs. Use Autoloads only when several scenes truly need the same global. Full guidance for contributors and tooling: .cursor/rules/godot-client-script-organization.md.
Authoritative movement (NS-16, NS-23)
With the game server running (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 walks along a baked navigation mesh (NavigationRegion3D + NavigationAgent3D on the player) toward that verified position instead of teleporting on the GET (NS-23). Boot sync_from_server() snaps once so spawn matches the server.
- 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), thinscripts/main.gd(nav bake on first frame, then wiring). - Scene:
scenes/main.tscn— walkableStaticBody3Dgeometry lives underWorld/NavigationRegion3D.main.gdcallsbake_navigation_mesh(false)(main-thread bake) after oneprocess_frame, then waits two **physics_frame**s soNavigationServer3Dhas a map before agents query paths. After moving floor or obstacles, re-bake in the editor if needed. - Inspector: select
PositionAuthorityClienton the main scene to setbase_url(defaulthttp://127.0.0.1:5253) anddev_player_id(defaultdev-local-1, must match serverGame:DevPlayerId).
Manual check (NS-16 + NS-23)
- From repo root:
cd server/NeonSprawl.Server && dotnet run(note the URL/port, usuallyhttp://localhost:5253). - If the port differs, set
base_urlonPositionAuthorityClientaccordingly (e.g.http://127.0.0.1:5253). - 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). - Left-click the floor: the client POSTs the target, then GETs position; the capsule walks around
Obstacletoward the authoritative target. NS-19 reject clicks still show the reject label and do not start a path. - Click the far pad or pedestal top (from spawn) to confirm
horizontal_step_exceeded/vertical_step_exceededbehavior is unchanged.
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)
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 printsallowed/reasonCode(or HTTP errors). While a request is in flight, further E presses are ignored (same pattern asPositionAuthorityClient).- Inspector: match
base_url/dev_player_idtoPositionAuthorityClientand the serverGame:DevPlayerId.
Manual check (NS-18)
- Run the game server and client as in NS-16.
- 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).
- Press E (input action
interactinproject.godot): Output should showallowed=truewhen markers glow,allowed=falsewithreasonCode=out_of_rangewhen 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)
player.gd uses NavigationAgent3D.get_next_path_position() + move_and_slide() for horizontal motion (NS-23). 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.
Manual check (remnants)
- Run the main scene (F5) with server running for movement behavior above.
- Without the server, startup sync does not apply authoritative position; behavior is undefined for this repo’s intended demo—run the server for the demo path.
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 viewport’s current camera and mouse position (the script does this so the embedded Game view matches the ray).
First run
- Install Godot 4.x.
- In the project manager, Import and select
client/project.godot. - Press F5 to run the main scene.
Godot CLI (Linux smoke test)
For agents and CI, use the official Linux x86_64 editor binary (matches config/features 4.6 in project.godot):
- Download Godot_v4.6-stable_linux.x86_64.zip from the 4.6-stable release.
- Unzip and put the binary on your
PATH, e.g.~/.local/opt/godot-4.6-stable/andln -s …/Godot_v4.6-stable_linux.x86_64 ~/.local/bin/godot. - Ensure
~/.local/binis onPATH, then fromclient/:
godot --version
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.