From 7ac6b66b514ba40f0c597e3409701f7b03e2bc47 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sat, 4 Apr 2026 23:45:54 -0400 Subject: [PATCH] NS-19: ignore vertical walkable surfaces in ground ray pick --- client/scripts/ground_pick.gd | 11 +++++++++++ server/README.md | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/client/scripts/ground_pick.gd b/client/scripts/ground_pick.gd index fd2b819..7bef4c8 100644 --- a/client/scripts/ground_pick.gd +++ b/client/scripts/ground_pick.gd @@ -1,10 +1,15 @@ extends Node3D ## NS-16: walkable ground ray pick. Emits world target for MoveCommand; camera wired from main. +## NS-19: ignores hits on **steep** surfaces (e.g. pedestal **walls**) so players cannot chain small +## vertical steps up a vertical face; only **floor-like** normals (high dot with UP) count. ## (No `class_name` so headless/CI can load the project before `.godot` import exists.) signal target_chosen(world: Vector3) +## Minimum `hit_normal.dot(Vector3.UP)` to accept a pick (0 = vertical wall, 1 = flat floor). +const MIN_WALKABLE_NORMAL_DOT_UP: float = 0.82 + ## Fallback when `Viewport.get_camera_3d()` is null (e.g. some editor setups). Set from `main.gd` ## in `_ready`. var fallback_camera: Camera3D @@ -36,6 +41,12 @@ func _try_pick(screen_pos: Vector2) -> void: var collider: Variant = hit.get("collider") if not _collider_is_walkable(collider): return + var hit_normal: Variant = hit.get("normal", Vector3.ZERO) + if hit_normal is not Vector3: + return + var nrm: Vector3 = (hit_normal as Vector3).normalized() + if nrm.dot(Vector3.UP) < MIN_WALKABLE_NORMAL_DOT_UP: + return var hit_pos: Variant = hit.get("position") if hit_pos is Vector3: target_chosen.emit(hit_pos as Vector3) diff --git a/server/README.md b/server/README.md index e728f22..d62b873 100644 --- a/server/README.md +++ b/server/README.md @@ -72,7 +72,7 @@ Unknown player ids return **404**. Full zone sync / replication is still out of | `Game:MovementValidation:DistrictBoundsEnabled` | Axis-aligned box on **target** (inclusive min/max) | `false` | | `Game:MovementValidation:DistrictMinX` … `DistrictMaxZ` | District extents when enabled | ±12 / Y −2…24 | -**Manual QA (Godot `main.tscn`):** **Green bumps** (~0.15 m rise) stay under default `MaxVerticalStep` from the dev spawn; **orange reject pedestal** top is ~2.5 m above floor so a floor-level click yields **`vertical_step_exceeded`**; **far pad** is beyond default horizontal reach for **`horizontal_step_exceeded`**. +**Manual QA (Godot `main.tscn`):** **Green bumps** (~0.15 m rise) stay under default `MaxVerticalStep` from the dev spawn; **orange reject pedestal** top is ~2.5 m above floor so a floor-level click yields **`vertical_step_exceeded`**; **far pad** is beyond default horizontal reach for **`horizontal_step_exceeded`**. The client’s **`ground_pick.gd`** only accepts ray hits whose surface **normal** is mostly **upward** (`MIN_WALKABLE_NORMAL_DOT_UP`), so **vertical faces** on the pedestal do not emit move targets—this complements server per-step limits and prevents “stair-stepping” up walls. Request body (example):