diff --git a/.cursor/settings.json b/.cursor/settings.json
index 32c28fc..fc38bf8 100644
--- a/.cursor/settings.json
+++ b/.cursor/settings.json
@@ -2,6 +2,9 @@
"plugins": {
"atlassian": {
"enabled": true
+ },
+ "linear": {
+ "enabled": true
}
}
}
diff --git a/client/README.md b/client/README.md
index 0493964..abfd7a0 100644
--- a/client/README.md
+++ b/client/README.md
@@ -48,7 +48,7 @@ Elevated walkables use **distinct albedo tints** in `scenes/main.tscn` so screen
### Designer / QA limits
- Floor bounds: **±22.5 m** in X and Z from world origin. Clicks outside the `NavigationRegion3D` boundary (beyond the floor edge) will be rejected by `NavigationAgent3D` path queries.
-- The `MoveRejectPedestal` (orange box) top is ~2.5 m above the floor and will be rejected by the server's `MaxVerticalStep = 1.25 m` check — the only remaining move-rejection criterion. Horizontal distance is never a rejection reason.
+- The `MoveRejectPedestal` (orange box) top is ~2.5 m above the floor and will be rejected by the server's `MaxVerticalStep` (**2.2 m** by default) — the only remaining move-rejection criterion with default server config. Horizontal distance is never a rejection reason when `HorizontalStepEnabled` is false.
- New obstacles are tagged `"occluder"` so the NEON-27 occlusion policy and NEON-30 click-through both apply automatically.
## Authoritative movement (NEO-7, NEO-11)
@@ -71,7 +71,7 @@ With the game server running ([`server/README.md`](../server/README.md)), each v
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` / NEO-9 walk demo).
4. **Left-click** the floor: the client **POST**s the target, then **GET**s position; the capsule **walks** in **XZ** straight toward that target (nav path only for **vertical** routing when you are already near the goal column on **flatter** floor). Server-rejected clicks show the reject label and do **not** start a path.
-5. Click the **pedestal top** (orange box at ~(7.5, 0, −6.5)) to confirm **`vertical_step_exceeded`** rejection — the top is ~2.5 m above floor, which exceeds `MaxVerticalStep`. The `MoveRejectFarPad` (blue pad at (9, 9)) is a normal walkable surface; clicking it from any distance should succeed.
+5. Click the **pedestal top** (orange box at ~(7.5, 0, −6.5)) to confirm **`vertical_step_exceeded`** rejection — the top is ~2.5 m above floor, which exceeds default **`MaxVerticalStep` (2.2 m)**. From the **physics ramp test block** top (**2 m**), clicking the floor should **succeed**. The `MoveRejectFarPad` (blue pad at (9, 9)) is a normal walkable surface; clicking it from any distance should succeed.
6. After a move (or at spawn), **stand idle** with no click goal for **10+ seconds** and confirm the capsule does **not** visibly vibrate or drift in **x/z** on flat floor, then repeat on 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.
diff --git a/client/project.godot b/client/project.godot
index f42beef..5ab0b4b 100644
--- a/client/project.godot
+++ b/client/project.godot
@@ -67,8 +67,8 @@ dev_toggle_occluder_obstacle={
[physics]
common/physics_ticks_per_second=120
-3d/default_gravity=9.81
3d/physics_engine="Jolt Physics"
+3d/default_gravity=9.81
[rendering]
diff --git a/client/scenes/main.tscn b/client/scenes/main.tscn
index 1aab92b..6fafa7e 100644
--- a/client/scenes/main.tscn
+++ b/client/scenes/main.tscn
@@ -6,13 +6,11 @@
[ext_resource type="Script" uid="uid://ds5fkbscljkxi" path="res://scripts/position_authority_client.gd" id="4_auth"]
[ext_resource type="Script" uid="uid://bv0xprp660hib" path="res://scripts/interaction_request_client.gd" id="5_ix"]
[ext_resource type="Script" uid="uid://n3p4f3fky3nv" path="res://scripts/interaction_radius_indicators.gd" id="6_rad"]
-[ext_resource type="Script" uid="uid://b8x7c4m3r5t8t2" path="res://scripts/isometric_follow_camera.gd" id="7_iso_cam"]
+[ext_resource type="Script" uid="uid://caaj3ohikfdx2" path="res://scripts/isometric_follow_camera.gd" id="7_iso_cam"]
[ext_resource type="Resource" path="res://resources/isometric_zoom_bands.tres" id="8_zoom_bands"]
[ext_resource type="Resource" path="res://resources/isometric_occlusion_policy.tres" id="9_occ_pol"]
[sub_resource type="NavigationMesh" id="NavigationMesh_district"]
-vertices = PackedVector3Array()
-polygons = []
geometry_collision_mask = 1
cell_size = 0.15
cell_height = 0.15
@@ -21,12 +19,12 @@ agent_height = 1.0
agent_radius = 0.4
agent_max_climb = 0.35
-[sub_resource type="StandardMaterial3D" id="Mat_floor_slab"]
-albedo_color = Color(0.5, 0.5, 0.52, 1)
-
[sub_resource type="BoxMesh" id="BoxMesh_floor"]
size = Vector3(45, 0.2, 45)
+[sub_resource type="StandardMaterial3D" id="Mat_floor_slab"]
+albedo_color = Color(0.5, 0.5, 0.52, 1)
+
[sub_resource type="BoxShape3D" id="BoxShape3D_floor"]
size = Vector3(45, 0.2, 45)
@@ -63,24 +61,6 @@ size = Vector3(2, 2, 2)
[sub_resource type="BoxShape3D" id="BoxShape3D_obstacle"]
size = Vector3(2, 2, 2)
-[sub_resource type="BoxMesh" id="BoxMesh_marker"]
-size = Vector3(0.22, 0.35, 0.22)
-
-[sub_resource type="StandardMaterial3D" id="Mat_marker_base"]
-albedo_color = Color(0.15, 0.4, 1, 1)
-
-[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
-
-[sub_resource type="StandardMaterial3D" id="Mat_player_capsule"]
-specular_mode = 2
-albedo_color = Color(0.42, 0.52, 0.7, 1)
-
[sub_resource type="BoxMesh" id="BoxMesh_obstacle_b"]
size = Vector3(3, 3, 3)
@@ -99,101 +79,146 @@ size = Vector3(3, 2, 3)
[sub_resource type="BoxShape3D" id="BoxShape3D_obstacle_d"]
size = Vector3(3, 2, 3)
-[sub_resource type="StandardMaterial3D" id="Mat_terrace_platform_a"]
-albedo_color = Color(0.15, 0.62, 0.58, 1)
-
-[sub_resource type="StandardMaterial3D" id="Mat_terrace_b_step"]
-albedo_color = Color(0.96, 0.72, 0.15, 1)
-
-[sub_resource type="StandardMaterial3D" id="Mat_terrace_platform_b"]
-albedo_color = Color(0.58, 0.32, 0.72, 1)
-
-[sub_resource type="StandardMaterial3D" id="Mat_terrace_platform_c"]
-albedo_color = Color(0.22, 0.68, 0.38, 1)
-
[sub_resource type="BoxMesh" id="BoxMesh_terrace_a"]
size = Vector3(10, 0.3, 10)
+[sub_resource type="StandardMaterial3D" id="Mat_terrace_platform_a"]
+albedo_color = Color(0.15, 0.62, 0.58, 1)
+
[sub_resource type="BoxShape3D" id="BoxShape3D_terrace_a"]
size = Vector3(10, 0.3, 10)
+[sub_resource type="BoxMesh" id="BoxMesh_pa_stair_ns"]
+size = Vector3(9.2, 0.104, 1)
+
+[sub_resource type="StandardMaterial3D" id="Mat_terrace_platform_a_ramp"]
+albedo_color = Color(0.1, 0.48, 0.45, 1)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_pa_stair_ns"]
+size = Vector3(9.2, 0.104, 1)
+
+[sub_resource type="BoxMesh" id="BoxMesh_pa_stair_we"]
+size = Vector3(1, 0.104, 9.2)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_pa_stair_we"]
+size = Vector3(1, 0.104, 9.2)
+
[sub_resource type="BoxMesh" id="BoxMesh_terrace_b_step"]
size = Vector3(6, 0.3, 3)
+[sub_resource type="StandardMaterial3D" id="Mat_terrace_b_step"]
+albedo_color = Color(0.96, 0.72, 0.15, 1)
+
[sub_resource type="BoxShape3D" id="BoxShape3D_terrace_b_step"]
size = Vector3(6, 0.3, 3)
[sub_resource type="BoxMesh" id="BoxMesh_terrace_b_platform"]
size = Vector3(6, 0.6, 6)
+[sub_resource type="StandardMaterial3D" id="Mat_terrace_platform_b"]
+albedo_color = Color(0.58, 0.32, 0.72, 1)
+
[sub_resource type="BoxShape3D" id="BoxShape3D_terrace_b_platform"]
size = Vector3(6, 0.6, 6)
[sub_resource type="BoxMesh" id="BoxMesh_terrace_c"]
size = Vector3(8, 0.3, 8)
+[sub_resource type="StandardMaterial3D" id="Mat_terrace_platform_c"]
+albedo_color = Color(0.22, 0.68, 0.38, 1)
+
[sub_resource type="BoxShape3D" id="BoxShape3D_terrace_c"]
size = Vector3(8, 0.3, 8)
-[sub_resource type="StandardMaterial3D" id="Mat_terrace_platform_a_ramp"]
-albedo_color = Color(0.1, 0.48, 0.45, 1)
-
-[sub_resource type="BoxMesh" id="BoxMesh_pa_stair_ns"]
-size = Vector3(9.2, 0.104, 1.0)
-
-[sub_resource type="BoxShape3D" id="BoxShape3D_pa_stair_ns"]
-size = Vector3(9.2, 0.104, 1.0)
-
-[sub_resource type="BoxMesh" id="BoxMesh_pa_stair_we"]
-size = Vector3(1.0, 0.104, 9.2)
-
-[sub_resource type="BoxShape3D" id="BoxShape3D_pa_stair_we"]
-size = Vector3(1.0, 0.104, 9.2)
+[sub_resource type="BoxMesh" id="BoxMesh_pc_stair_ns"]
+size = Vector3(7.2, 0.104, 1)
[sub_resource type="StandardMaterial3D" id="Mat_terrace_platform_c_ramp"]
albedo_color = Color(0.12, 0.48, 0.22, 1)
-[sub_resource type="BoxMesh" id="BoxMesh_pc_stair_ns"]
-size = Vector3(7.2, 0.104, 1.0)
-
[sub_resource type="BoxShape3D" id="BoxShape3D_pc_stair_ns"]
-size = Vector3(7.2, 0.104, 1.0)
+size = Vector3(7.2, 0.104, 1)
[sub_resource type="BoxMesh" id="BoxMesh_pc_stair_we"]
-size = Vector3(1.0, 0.104, 7.2)
+size = Vector3(1, 0.104, 7.2)
[sub_resource type="BoxShape3D" id="BoxShape3D_pc_stair_we"]
-size = Vector3(1.0, 0.104, 7.2)
+size = Vector3(1, 0.104, 7.2)
+
+[sub_resource type="BoxMesh" id="BoxMesh_tsb_stair_ns"]
+size = Vector3(6.2, 0.104, 1)
[sub_resource type="StandardMaterial3D" id="Mat_terrace_b_step_ramp"]
albedo_color = Color(0.62, 0.45, 0.06, 1)
-[sub_resource type="BoxMesh" id="BoxMesh_tsb_stair_ns"]
-size = Vector3(6.2, 0.104, 1.0)
-
[sub_resource type="BoxShape3D" id="BoxShape3D_tsb_stair_ns"]
-size = Vector3(6.2, 0.104, 1.0)
+size = Vector3(6.2, 0.104, 1)
[sub_resource type="BoxMesh" id="BoxMesh_tsb_stair_we"]
-size = Vector3(1.0, 0.104, 3.2)
+size = Vector3(1, 0.104, 3.2)
[sub_resource type="BoxShape3D" id="BoxShape3D_tsb_stair_we"]
-size = Vector3(1.0, 0.104, 3.2)
+size = Vector3(1, 0.104, 3.2)
+
+[sub_resource type="BoxMesh" id="BoxMesh_tpb_stair_ns"]
+size = Vector3(6.2, 0.104, 1)
[sub_resource type="StandardMaterial3D" id="Mat_terrace_platform_b_ramp"]
albedo_color = Color(0.35, 0.18, 0.42, 1)
-[sub_resource type="BoxMesh" id="BoxMesh_tpb_stair_ns"]
-size = Vector3(6.2, 0.104, 1.0)
-
[sub_resource type="BoxShape3D" id="BoxShape3D_tpb_stair_ns"]
-size = Vector3(6.2, 0.104, 1.0)
+size = Vector3(6.2, 0.104, 1)
[sub_resource type="BoxMesh" id="BoxMesh_tpb_stair_we"]
-size = Vector3(1.0, 0.104, 6.2)
+size = Vector3(1, 0.104, 6.2)
[sub_resource type="BoxShape3D" id="BoxShape3D_tpb_stair_we"]
-size = Vector3(1.0, 0.104, 6.2)
+size = Vector3(1, 0.104, 6.2)
+
+[sub_resource type="BoxMesh" id="BoxMesh_physics_ramp_test_floor"]
+size = Vector3(24, 0.2, 45)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_physics_ramp_test_floor"]
+size = Vector3(24, 0.2, 45)
+
+[sub_resource type="BoxMesh" id="BoxMesh_physics_ramp_test_block"]
+size = Vector3(4, 2, 4)
+
+[sub_resource type="StandardMaterial3D" id="Mat_physics_ramp_test"]
+albedo_color = Color(0.95, 0.42, 0.38, 1)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_physics_ramp_test_block"]
+size = Vector3(4, 2, 4)
+
+[sub_resource type="BoxMesh" id="BoxMesh_physics_ramp_test_walk"]
+size = Vector3(4.5, 0.15, 6.5)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_physics_ramp_test_walk"]
+size = Vector3(4.5, 0.15, 6.5)
+
+[sub_resource type="BoxMesh" id="BoxMesh_physics_ramp_test_steep"]
+size = Vector3(2.5, 0.15, 4.5)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_physics_ramp_test_steep"]
+size = Vector3(2.5, 0.15, 4.5)
+
+[sub_resource type="BoxMesh" id="BoxMesh_marker"]
+size = Vector3(0.22, 0.35, 0.22)
+
+[sub_resource type="StandardMaterial3D" id="Mat_marker_base"]
+albedo_color = Color(0.15, 0.4, 1, 1)
+
+[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
+
+[sub_resource type="StandardMaterial3D" id="Mat_player_capsule"]
+specular_mode = 2
+albedo_color = Color(0.42, 0.52, 0.7, 1)
[node name="Main" type="Node3D" unique_id=1358372723]
script = ExtResource("1_main")
@@ -231,8 +256,8 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.1, 0)
shape = SubResource("BoxShape3D_floor")
[node name="PrototypeTerminal" type="StaticBody3D" parent="World/NavigationRegion3D" unique_id=1700001 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/PrototypeTerminal" unique_id=1700002]
mesh = SubResource("BoxMesh_terminal")
@@ -242,8 +267,8 @@ surface_material_override/0 = SubResource("Mat_terminal")
shape = SubResource("BoxShape3D_terminal")
[node name="MoveRejectPedestal" type="StaticBody3D" parent="World/NavigationRegion3D" unique_id=1900021 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.5, 0, -6.5)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/MoveRejectPedestal" unique_id=1900022]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.25, 0)
@@ -255,8 +280,8 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.25, 0)
shape = SubResource("BoxShape3D_move_reject_pedestal")
[node name="MoveRejectFarPad" type="StaticBody3D" parent="World/NavigationRegion3D" unique_id=1900031 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9, 0.06, 9)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/MoveRejectFarPad" unique_id=1900032]
mesh = SubResource("BoxMesh_move_reject_farpad")
@@ -266,8 +291,8 @@ surface_material_override/0 = SubResource("Mat_move_reject_farpad")
shape = SubResource("BoxShape3D_move_reject_farpad")
[node name="Obstacle" type="StaticBody3D" parent="World/NavigationRegion3D" unique_id=1638845763 groups=["occluder"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6, 0, 5)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/Obstacle" unique_id=750473628]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
@@ -278,8 +303,8 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
shape = SubResource("BoxShape3D_obstacle")
[node name="ObstacleB" type="StaticBody3D" parent="World/NavigationRegion3D" unique_id=3000001 groups=["occluder"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -10, 0, 5)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/ObstacleB" unique_id=3000002]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
@@ -290,8 +315,8 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
shape = SubResource("BoxShape3D_obstacle_b")
[node name="ObstacleC" type="StaticBody3D" parent="World/NavigationRegion3D" unique_id=3000004 groups=["occluder"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, -12)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/ObstacleC" unique_id=3000005]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
@@ -302,8 +327,8 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
shape = SubResource("BoxShape3D_obstacle_c")
[node name="ObstacleD" type="StaticBody3D" parent="World/NavigationRegion3D" unique_id=3000007 groups=["occluder"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8, 0, -16)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/ObstacleD" unique_id=3000008]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
@@ -314,8 +339,8 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
shape = SubResource("BoxShape3D_obstacle_d")
[node name="TerracePlatformA" type="StaticBody3D" parent="World/NavigationRegion3D" unique_id=3000010 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 13, 0.012, -14)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA" unique_id=3000011]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.15, 0)
@@ -329,8 +354,8 @@ shape = SubResource("BoxShape3D_terrace_a")
[node name="TerracePlatformA_Approach" type="Node3D" parent="World/NavigationRegion3D" unique_id=3100000]
[node name="PAS_S1" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach" unique_id=3100001 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 13, 0.052, -20.4)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_S1" unique_id=3100011]
mesh = SubResource("BoxMesh_pa_stair_ns")
@@ -340,8 +365,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp")
shape = SubResource("BoxShape3D_pa_stair_ns")
[node name="PAS_S2" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach" unique_id=3100002 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 13, 0.156, -19.95)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_S2" unique_id=3100021]
mesh = SubResource("BoxMesh_pa_stair_ns")
@@ -351,8 +376,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp")
shape = SubResource("BoxShape3D_pa_stair_ns")
[node name="PAS_S3" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach" unique_id=3100003 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 13, 0.26, -19.5)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_S3" unique_id=3100031]
mesh = SubResource("BoxMesh_pa_stair_ns")
@@ -362,8 +387,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp")
shape = SubResource("BoxShape3D_pa_stair_ns")
[node name="PAS_N1" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach" unique_id=3100004 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 13, 0.052, -6.5)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_N1" unique_id=3100041]
mesh = SubResource("BoxMesh_pa_stair_ns")
@@ -373,8 +398,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp")
shape = SubResource("BoxShape3D_pa_stair_ns")
[node name="PAS_N2" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach" unique_id=3100005 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 13, 0.156, -7.5)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_N2" unique_id=3100051]
mesh = SubResource("BoxMesh_pa_stair_ns")
@@ -384,8 +409,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp")
shape = SubResource("BoxShape3D_pa_stair_ns")
[node name="PAS_N3" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach" unique_id=3100006 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 13, 0.26, -8.5)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_N3" unique_id=3100061]
mesh = SubResource("BoxMesh_pa_stair_ns")
@@ -395,8 +420,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp")
shape = SubResource("BoxShape3D_pa_stair_ns")
[node name="PAS_W1" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach" unique_id=3100007 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6.5, 0.052, -14)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_W1" unique_id=3100071]
mesh = SubResource("BoxMesh_pa_stair_we")
@@ -406,8 +431,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp")
shape = SubResource("BoxShape3D_pa_stair_we")
[node name="PAS_W2" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach" unique_id=3100008 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.5, 0.156, -14)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_W2" unique_id=3100081]
mesh = SubResource("BoxMesh_pa_stair_we")
@@ -417,8 +442,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp")
shape = SubResource("BoxShape3D_pa_stair_we")
[node name="PAS_W3" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach" unique_id=3100009 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.5, 0.26, -14)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_W3" unique_id=3100091]
mesh = SubResource("BoxMesh_pa_stair_we")
@@ -428,8 +453,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp")
shape = SubResource("BoxShape3D_pa_stair_we")
[node name="PAS_E1" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach" unique_id=3100010 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15.5, 0.052, -14)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_E1" unique_id=3100101]
mesh = SubResource("BoxMesh_pa_stair_we")
@@ -439,8 +464,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp")
shape = SubResource("BoxShape3D_pa_stair_we")
[node name="PAS_E2" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach" unique_id=3100013 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 16.5, 0.156, -14)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_E2" unique_id=3100111]
mesh = SubResource("BoxMesh_pa_stair_we")
@@ -450,8 +475,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp")
shape = SubResource("BoxShape3D_pa_stair_we")
[node name="PAS_E3" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach" unique_id=3100014 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 17.5, 0.26, -14)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_E3" unique_id=3100121]
mesh = SubResource("BoxMesh_pa_stair_we")
@@ -461,8 +486,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp")
shape = SubResource("BoxShape3D_pa_stair_we")
[node name="TerraceStepB" type="StaticBody3D" parent="World/NavigationRegion3D" unique_id=3000013 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0, 8.5)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerraceStepB" unique_id=3000014]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.15, 0)
@@ -474,8 +499,8 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.15, 0)
shape = SubResource("BoxShape3D_terrace_b_step")
[node name="TerracePlatformB" type="StaticBody3D" parent="World/NavigationRegion3D" unique_id=3000016 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0, 13)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB" unique_id=3000017]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.3, 0)
@@ -487,8 +512,8 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.3, 0)
shape = SubResource("BoxShape3D_terrace_b_platform")
[node name="TerracePlatformC" type="StaticBody3D" parent="World/NavigationRegion3D" unique_id=3000019 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14, 0, 14)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformC" unique_id=3000020]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.15, 0)
@@ -502,8 +527,8 @@ shape = SubResource("BoxShape3D_terrace_c")
[node name="TerracePlatformC_Approach" type="Node3D" parent="World/NavigationRegion3D" unique_id=3150000]
[node name="PCS_S1" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach" unique_id=3150001 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14, 0.052, 8.6)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach/PCS_S1" unique_id=3150011]
mesh = SubResource("BoxMesh_pc_stair_ns")
@@ -513,8 +538,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_c_ramp")
shape = SubResource("BoxShape3D_pc_stair_ns")
[node name="PCS_S2" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach" unique_id=3150002 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14, 0.156, 9.05)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach/PCS_S2" unique_id=3150021]
mesh = SubResource("BoxMesh_pc_stair_ns")
@@ -524,8 +549,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_c_ramp")
shape = SubResource("BoxShape3D_pc_stair_ns")
[node name="PCS_S3" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach" unique_id=3150003 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14, 0.26, 9.5)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach/PCS_S3" unique_id=3150031]
mesh = SubResource("BoxMesh_pc_stair_ns")
@@ -535,8 +560,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_c_ramp")
shape = SubResource("BoxShape3D_pc_stair_ns")
[node name="PCS_N1" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach" unique_id=3150004 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14, 0.052, 19.4)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach/PCS_N1" unique_id=3150041]
mesh = SubResource("BoxMesh_pc_stair_ns")
@@ -546,8 +571,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_c_ramp")
shape = SubResource("BoxShape3D_pc_stair_ns")
[node name="PCS_N2" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach" unique_id=3150005 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14, 0.156, 18.95)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach/PCS_N2" unique_id=3150051]
mesh = SubResource("BoxMesh_pc_stair_ns")
@@ -557,8 +582,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_c_ramp")
shape = SubResource("BoxShape3D_pc_stair_ns")
[node name="PCS_N3" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach" unique_id=3150006 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14, 0.26, 18.5)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach/PCS_N3" unique_id=3150061]
mesh = SubResource("BoxMesh_pc_stair_ns")
@@ -568,8 +593,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_c_ramp")
shape = SubResource("BoxShape3D_pc_stair_ns")
[node name="PCS_W1" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach" unique_id=3150007 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.5, 0.052, 14)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach/PCS_W1" unique_id=3150071]
mesh = SubResource("BoxMesh_pc_stair_we")
@@ -579,8 +604,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_c_ramp")
shape = SubResource("BoxShape3D_pc_stair_we")
[node name="PCS_W2" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach" unique_id=3150008 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.95, 0.156, 14)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach/PCS_W2" unique_id=3150081]
mesh = SubResource("BoxMesh_pc_stair_we")
@@ -590,8 +615,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_c_ramp")
shape = SubResource("BoxShape3D_pc_stair_we")
[node name="PCS_W3" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach" unique_id=3150009 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9.5, 0.26, 14)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach/PCS_W3" unique_id=3150091]
mesh = SubResource("BoxMesh_pc_stair_we")
@@ -601,8 +626,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_c_ramp")
shape = SubResource("BoxShape3D_pc_stair_we")
[node name="PCS_E1" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach" unique_id=3150010 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 19.4, 0.052, 14)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach/PCS_E1" unique_id=3150101]
mesh = SubResource("BoxMesh_pc_stair_we")
@@ -612,8 +637,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_c_ramp")
shape = SubResource("BoxShape3D_pc_stair_we")
[node name="PCS_E2" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach" unique_id=3150013 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 18.95, 0.156, 14)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach/PCS_E2" unique_id=3150111]
mesh = SubResource("BoxMesh_pc_stair_we")
@@ -623,8 +648,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_c_ramp")
shape = SubResource("BoxShape3D_pc_stair_we")
[node name="PCS_E3" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach" unique_id=3150014 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 18.5, 0.26, 14)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformC_Approach/PCS_E3" unique_id=3150121]
mesh = SubResource("BoxMesh_pc_stair_we")
@@ -636,8 +661,8 @@ shape = SubResource("BoxShape3D_pc_stair_we")
[node name="TerraceStepB_Approach" type="Node3D" parent="World/NavigationRegion3D" unique_id=3160000]
[node name="TSB_S1" type="StaticBody3D" parent="World/NavigationRegion3D/TerraceStepB_Approach" unique_id=3160001 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0.052, 5.6)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerraceStepB_Approach/TSB_S1" unique_id=3160011]
mesh = SubResource("BoxMesh_tsb_stair_ns")
@@ -647,8 +672,8 @@ surface_material_override/0 = SubResource("Mat_terrace_b_step_ramp")
shape = SubResource("BoxShape3D_tsb_stair_ns")
[node name="TSB_S2" type="StaticBody3D" parent="World/NavigationRegion3D/TerraceStepB_Approach" unique_id=3160002 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0.156, 6.05)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerraceStepB_Approach/TSB_S2" unique_id=3160021]
mesh = SubResource("BoxMesh_tsb_stair_ns")
@@ -658,8 +683,8 @@ surface_material_override/0 = SubResource("Mat_terrace_b_step_ramp")
shape = SubResource("BoxShape3D_tsb_stair_ns")
[node name="TSB_S3" type="StaticBody3D" parent="World/NavigationRegion3D/TerraceStepB_Approach" unique_id=3160003 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0.26, 6.5)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerraceStepB_Approach/TSB_S3" unique_id=3160031]
mesh = SubResource("BoxMesh_tsb_stair_ns")
@@ -669,8 +694,8 @@ surface_material_override/0 = SubResource("Mat_terrace_b_step_ramp")
shape = SubResource("BoxShape3D_tsb_stair_ns")
[node name="TSB_W1" type="StaticBody3D" parent="World/NavigationRegion3D/TerraceStepB_Approach" unique_id=3160004 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -19.5, 0.052, 8.5)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerraceStepB_Approach/TSB_W1" unique_id=3160041]
mesh = SubResource("BoxMesh_tsb_stair_we")
@@ -680,8 +705,8 @@ surface_material_override/0 = SubResource("Mat_terrace_b_step_ramp")
shape = SubResource("BoxShape3D_tsb_stair_we")
[node name="TSB_W2" type="StaticBody3D" parent="World/NavigationRegion3D/TerraceStepB_Approach" unique_id=3160005 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -19.05, 0.156, 8.5)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerraceStepB_Approach/TSB_W2" unique_id=3160051]
mesh = SubResource("BoxMesh_tsb_stair_we")
@@ -691,8 +716,8 @@ surface_material_override/0 = SubResource("Mat_terrace_b_step_ramp")
shape = SubResource("BoxShape3D_tsb_stair_we")
[node name="TSB_W3" type="StaticBody3D" parent="World/NavigationRegion3D/TerraceStepB_Approach" unique_id=3160006 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -18.5, 0.26, 8.5)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerraceStepB_Approach/TSB_W3" unique_id=3160061]
mesh = SubResource("BoxMesh_tsb_stair_we")
@@ -702,8 +727,8 @@ surface_material_override/0 = SubResource("Mat_terrace_b_step_ramp")
shape = SubResource("BoxShape3D_tsb_stair_we")
[node name="TSB_E1" type="StaticBody3D" parent="World/NavigationRegion3D/TerraceStepB_Approach" unique_id=3160007 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -10.5, 0.052, 8.5)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerraceStepB_Approach/TSB_E1" unique_id=3160071]
mesh = SubResource("BoxMesh_tsb_stair_we")
@@ -713,8 +738,8 @@ surface_material_override/0 = SubResource("Mat_terrace_b_step_ramp")
shape = SubResource("BoxShape3D_tsb_stair_we")
[node name="TSB_E2" type="StaticBody3D" parent="World/NavigationRegion3D/TerraceStepB_Approach" unique_id=3160008 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -11, 0.156, 8.5)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerraceStepB_Approach/TSB_E2" unique_id=3160081]
mesh = SubResource("BoxMesh_tsb_stair_we")
@@ -724,8 +749,8 @@ surface_material_override/0 = SubResource("Mat_terrace_b_step_ramp")
shape = SubResource("BoxShape3D_tsb_stair_we")
[node name="TSB_E3" type="StaticBody3D" parent="World/NavigationRegion3D/TerraceStepB_Approach" unique_id=3160009 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -11.5, 0.26, 8.5)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerraceStepB_Approach/TSB_E3" unique_id=3160091]
mesh = SubResource("BoxMesh_tsb_stair_we")
@@ -737,8 +762,8 @@ shape = SubResource("BoxShape3D_tsb_stair_we")
[node name="TerracePlatformB_Approach" type="Node3D" parent="World/NavigationRegion3D" unique_id=3170000]
[node name="TPB_N1" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach" unique_id=3170001 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0.052, 18.75)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_N1" unique_id=3170011]
mesh = SubResource("BoxMesh_tpb_stair_ns")
@@ -748,8 +773,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_b_ramp")
shape = SubResource("BoxShape3D_tpb_stair_ns")
[node name="TPB_N2" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach" unique_id=3170002 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0.156, 18.3)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_N2" unique_id=3170021]
mesh = SubResource("BoxMesh_tpb_stair_ns")
@@ -759,8 +784,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_b_ramp")
shape = SubResource("BoxShape3D_tpb_stair_ns")
[node name="TPB_N3" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach" unique_id=3170003 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0.26, 17.85)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_N3" unique_id=3170031]
mesh = SubResource("BoxMesh_tpb_stair_ns")
@@ -770,8 +795,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_b_ramp")
shape = SubResource("BoxShape3D_tpb_stair_ns")
[node name="TPB_N4" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach" unique_id=3170004 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0.364, 17.4)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_N4" unique_id=3170041]
mesh = SubResource("BoxMesh_tpb_stair_ns")
@@ -781,8 +806,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_b_ramp")
shape = SubResource("BoxShape3D_tpb_stair_ns")
[node name="TPB_N5" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach" unique_id=3170005 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0.468, 16.95)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_N5" unique_id=3170051]
mesh = SubResource("BoxMesh_tpb_stair_ns")
@@ -792,8 +817,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_b_ramp")
shape = SubResource("BoxShape3D_tpb_stair_ns")
[node name="TPB_N6" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach" unique_id=3170006 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0.572, 16.5)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_N6" unique_id=3170061]
mesh = SubResource("BoxMesh_tpb_stair_ns")
@@ -803,8 +828,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_b_ramp")
shape = SubResource("BoxShape3D_tpb_stair_ns")
[node name="TPB_W1" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach" unique_id=3170007 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -20.75, 0.052, 13)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_W1" unique_id=3170071]
mesh = SubResource("BoxMesh_tpb_stair_we")
@@ -814,8 +839,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_b_ramp")
shape = SubResource("BoxShape3D_tpb_stair_we")
[node name="TPB_W2" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach" unique_id=3170008 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -20.3, 0.156, 13)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_W2" unique_id=3170081]
mesh = SubResource("BoxMesh_tpb_stair_we")
@@ -825,8 +850,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_b_ramp")
shape = SubResource("BoxShape3D_tpb_stair_we")
[node name="TPB_W3" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach" unique_id=3170009 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -19.85, 0.26, 13)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_W3" unique_id=3170091]
mesh = SubResource("BoxMesh_tpb_stair_we")
@@ -836,8 +861,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_b_ramp")
shape = SubResource("BoxShape3D_tpb_stair_we")
[node name="TPB_W4" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach" unique_id=3170010 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -19.4, 0.364, 13)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_W4" unique_id=3170101]
mesh = SubResource("BoxMesh_tpb_stair_we")
@@ -847,8 +872,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_b_ramp")
shape = SubResource("BoxShape3D_tpb_stair_we")
[node name="TPB_W5" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach" unique_id=3170013 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -18.95, 0.468, 13)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_W5" unique_id=3170111]
mesh = SubResource("BoxMesh_tpb_stair_we")
@@ -858,8 +883,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_b_ramp")
shape = SubResource("BoxShape3D_tpb_stair_we")
[node name="TPB_W6" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach" unique_id=3170014 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -18.5, 0.572, 13)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_W6" unique_id=3170121]
mesh = SubResource("BoxMesh_tpb_stair_we")
@@ -869,8 +894,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_b_ramp")
shape = SubResource("BoxShape3D_tpb_stair_we")
[node name="TPB_E1" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach" unique_id=3170015 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.25, 0.052, 13)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_E1" unique_id=3170131]
mesh = SubResource("BoxMesh_tpb_stair_we")
@@ -880,8 +905,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_b_ramp")
shape = SubResource("BoxShape3D_tpb_stair_we")
[node name="TPB_E2" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach" unique_id=3170016 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.7, 0.156, 13)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_E2" unique_id=3170141]
mesh = SubResource("BoxMesh_tpb_stair_we")
@@ -891,8 +916,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_b_ramp")
shape = SubResource("BoxShape3D_tpb_stair_we")
[node name="TPB_E3" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach" unique_id=3170017 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -10.15, 0.26, 13)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_E3" unique_id=3170151]
mesh = SubResource("BoxMesh_tpb_stair_we")
@@ -902,8 +927,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_b_ramp")
shape = SubResource("BoxShape3D_tpb_stair_we")
[node name="TPB_E4" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach" unique_id=3170018 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -10.6, 0.364, 13)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_E4" unique_id=3170161]
mesh = SubResource("BoxMesh_tpb_stair_we")
@@ -913,8 +938,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_b_ramp")
shape = SubResource("BoxShape3D_tpb_stair_we")
[node name="TPB_E5" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach" unique_id=3170019 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -11.05, 0.468, 13)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_E5" unique_id=3170171]
mesh = SubResource("BoxMesh_tpb_stair_we")
@@ -924,8 +949,8 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_b_ramp")
shape = SubResource("BoxShape3D_tpb_stair_we")
[node name="TPB_E6" type="StaticBody3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach" unique_id=3170020 groups=["walkable"]]
-collision_mask = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -11.5, 0.572, 13)
+collision_mask = 3
[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_E6" unique_id=3170181]
mesh = SubResource("BoxMesh_tpb_stair_we")
@@ -934,6 +959,55 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_b_ramp")
[node name="CollisionShape3D" type="CollisionShape3D" parent="World/NavigationRegion3D/TerracePlatformB_Approach/TPB_E6" unique_id=3170182]
shape = SubResource("BoxShape3D_tpb_stair_we")
+[node name="PhysicsRampTestFloorExtension" type="StaticBody3D" parent="World/NavigationRegion3D" unique_id=4201001 groups=["walkable"]]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 34.5, 0, 0)
+collision_mask = 3
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/PhysicsRampTestFloorExtension" unique_id=4201002]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.1, -0.19204712)
+mesh = SubResource("BoxMesh_physics_ramp_test_floor")
+surface_material_override/0 = SubResource("Mat_floor_slab")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="World/NavigationRegion3D/PhysicsRampTestFloorExtension" unique_id=4201003]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.1, 0)
+shape = SubResource("BoxShape3D_physics_ramp_test_floor")
+
+[node name="PhysicsRampTestBlock" type="StaticBody3D" parent="World/NavigationRegion3D" unique_id=4201010 groups=["occluder"]]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 40, 0, 0)
+collision_mask = 3
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/PhysicsRampTestBlock" unique_id=4201011]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+mesh = SubResource("BoxMesh_physics_ramp_test_block")
+surface_material_override/0 = SubResource("Mat_physics_ramp_test")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="World/NavigationRegion3D/PhysicsRampTestBlock" unique_id=4201012]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+shape = SubResource("BoxShape3D_physics_ramp_test_block")
+
+[node name="PhysicsRampTestWalkRamp" type="StaticBody3D" parent="World/NavigationRegion3D" unique_id=4201020 groups=["walkable"]]
+transform = Transform3D(1, 0, 0, 0, 0.948683, -0.316228, 0, 0.316228, 0.948683, 40, 1, 5)
+collision_mask = 3
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/PhysicsRampTestWalkRamp" unique_id=4201021]
+mesh = SubResource("BoxMesh_physics_ramp_test_walk")
+surface_material_override/0 = SubResource("Mat_physics_ramp_test")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="World/NavigationRegion3D/PhysicsRampTestWalkRamp" unique_id=4201022]
+shape = SubResource("BoxShape3D_physics_ramp_test_walk")
+
+[node name="PhysicsRampTestSteepRamp" type="StaticBody3D" parent="World" unique_id=4201030]
+transform = Transform3D(-0.6, -0.8, 0, 0.8, 0.6, 0, 0, 0, 1, 42.75, 1, 0)
+collision_layer = 1
+collision_mask = 3
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="World/PhysicsRampTestSteepRamp" unique_id=4201031]
+mesh = SubResource("BoxMesh_physics_ramp_test_steep")
+surface_material_override/0 = SubResource("Mat_physics_ramp_test")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="World/PhysicsRampTestSteepRamp" unique_id=4201032]
+shape = SubResource("BoxShape3D_physics_ramp_test_steep")
+
[node name="InteractionMarkers" type="Node3D" parent="World" unique_id=1700003]
script = ExtResource("6_rad")
@@ -994,12 +1068,6 @@ grow_horizontal = 2
autowrap_mode = 3
[node name="PlayerPositionLabel" type="Label" parent="UICanvas" unique_id=9000003]
-layout_mode = 0
-anchors_preset = 1
-anchor_left = 0.0
-anchor_top = 0.0
-anchor_right = 0.0
-anchor_bottom = 0.0
offset_left = 8.0
offset_top = 8.0
offset_right = 220.0
@@ -1010,4 +1078,6 @@ theme_override_colors/font_color = Color(0.92, 0.95, 0.98, 1)
theme_override_colors/font_outline_color = Color(0.08, 0.08, 0.1, 1)
theme_override_constants/outline_size = 6
theme_override_font_sizes/font_size = 15
-text = "x: —\ny: —\nz: —"
+text = "x: —
+y: —
+z: —"
diff --git a/client/scripts/ground_pick.gd b/client/scripts/ground_pick.gd
index 3f48957..d52b798 100644
--- a/client/scripts/ground_pick.gd
+++ b/client/scripts/ground_pick.gd
@@ -1,6 +1,8 @@
extends Node3D
## NS-16: walkable ground ray pick. Emits world target for MoveCommand; camera wired from main.
+## Flat tops on `occluder` bodies (horizontal normal) are valid destinations; vertical occluder
+## faces still punch through so ground behind can be picked.
## NS-19: reject vertical faces (pedestal). Stepped ray: steep walkable hits (bump slopes) advance
## along the ray so the next hit can be flat floor beyond — fixes “stuck” when leaving plateau.
## (No `class_name` so headless/CI can load the project before `.godot` import exists.)
@@ -64,6 +66,17 @@ func _try_pick(screen_pos: Vector2) -> void:
break
var hit_collider: Variant = hit.get("collider")
if _collider_is_occluder(hit_collider):
+ var hit_normal_o: Variant = hit.get("normal", Vector3.ZERO)
+ if hit_normal_o is not Vector3:
+ break
+ var nrm_o: Vector3 = (hit_normal_o as Vector3).normalized()
+ var ndot_o: float = nrm_o.dot(Vector3.UP)
+ if ndot_o >= MIN_WALKABLE_NORMAL_DOT_UP:
+ var top_pos: Variant = hit.get("position")
+ if top_pos is Vector3:
+ chosen = top_pos as Vector3
+ have_chosen = true
+ break
var occluder_pos: Variant = hit.get("position")
if occluder_pos is not Vector3:
break
diff --git a/client/scripts/main.gd b/client/scripts/main.gd
index 05faf9a..eee2139 100644
--- a/client/scripts/main.gd
+++ b/client/scripts/main.gd
@@ -7,6 +7,10 @@ extends Node3D
## (see `isometric_follow_camera.gd`).
## Prototype: two random short bumps (sibling StaticBody3D under NavigationRegion3D; see
## `random_floor_bumps.gd`) before nav bake.
+## Prototype: `PhysicsRampTest*` (+X floor extension, block, gentle ramp on **+Z**, steep on **+X**).
+## Steep ramp: **2 m / 1.5 m** rise/run (~53°) so slope exceeds `Player` `floor_max_angle` (~50°) and is not
+## walkable; gentle ramp stays shallower than `NavigationMesh.agent_max_climb`. Steep is under `World`
+## only (not nav source geometry).
## Prototype HUD: world `CharacterBody3D` position in `UICanvas/PlayerPositionLabel`
## (updated in `_physics_process` so it matches physics ticks).
diff --git a/client/scripts/player.gd b/client/scripts/player.gd
index 67cffd6..80c46f9 100644
--- a/client/scripts/player.gd
+++ b/client/scripts/player.gd
@@ -53,6 +53,10 @@ const WALK_PEEL_PROBE_FALSE_FRAMES: int = 2
## already inside [member ARRIVE_EPS] → never [code]has_goal=false[/code], full [member MOVE_SPEED] path
## steer flips forever (looks like “idle” jitter; NEON-16 traces).
const WALK_COLUMN_NEAR_ARRIVE_VERT: float = 0.36
+## Only apply [member WALK_COLUMN_NEAR_ARRIVE_VERT] when vertical error is already in this band — avoids
+## treating a **plaza / block** drop (feet still >20 cm above goal) as “column stair wobble” while
+## [code]horiz_dist[/code] is inside column steering (NEON-29-style skate + slow float after XZ).
+const WALK_COLUMN_NEAR_ARRIVE_GATE_VERT_ERR: float = 0.22
## If [code]vlat && ncol[/code] and net displacement stays below this for [member WALK_COLUMN_STUCK_FRAMES],
## clear the walk goal — path lookahead limit cycle with no progress (backup if widened arrival is not enough).
const WALK_COLUMN_STUCK_FRAMES: int = 28
@@ -70,6 +74,9 @@ const FLOOR_SNAP_MOVING: float = 0.32
const FLOOR_SNAP_IDLE: float = 0.11
## Below this floor-normal dot up, idle tick uses **moving** `floor_max_angle` (rim).
const IDLE_RIM_MIN_FLOOR_UP_DOT: float = 0.968
+## Uniform walkable slopes (e.g. physics QA ramp ~18°): stable idle when not tread+riser ridged.
+## Below ~cos(28°): treat as too steep for this fast-path (leave corrective / rim logic).
+const IDLE_SLOPE_STABLE_MIN_UP_DOT: float = 0.88
## Stable flat idle support: skip corrective idle motion when support is effectively level.
## 0.998 (~3.6°): 0.999 still saw open-floor HUD drift — Jolt [code]get_floor_normal()[/code] often
## reports ~0.9991–0.9992 on a flat box, so the old 0.999 gate rarely latched the idle anchor and
@@ -112,15 +119,25 @@ const WALK_SUPPORT_PROBE_MIN_UP_DOT: float = 0.42
## on violet stairs. Must stay **below** ~0.25 m so a gold→floor (~0.3 m) void still rejects the slab hit
## (no horizontal skate off the deck).
const WALK_CONTINUATION_MAX_BELOW_FEET: float = 0.108
-## If capsule feet are farther above the click’s surface Y than this, always use walk gravity + zero
-## moving snap (rays can still read “support” while [method CharacterBody3D.is_on_floor] hugs a lip).
-## ~0.42 m: **TerracePlatformB** top (~0.6 m) → floor (0) qualifies; **TerraceStepB** (~0.3 m) → floor does
-## not — shallow drops still rely on [method _walk_has_close_floor_probe_below].
-const WALK_DEEP_DESCENT_FEET_ABOVE_GOAL: float = 0.42
+## If capsule feet are farther above the click’s surface Y than this, always arm ledge peel (bypass
+## probe streak). Keep this **just above** a single QA tread drop (~0.104 m) so stair treads still use
+## [method _walk_has_close_floor_probe_below]; use a **small** value so tall drops (block ~2 m, terrace
+## ~0.6 m) never exit this “deep” band until feet are almost on the goal surface — otherwise the last
+## ~40 cm reverted to probe-only, lip [code]is_on_floor()[/code] + full snap could **horizontal skate**
+## with almost no peel until XZ arrival.
+const WALK_DEEP_DESCENT_FEET_ABOVE_GOAL: float = 0.12
## [method get_gravity] can be ~0 with missing/odd project defaults; ledge peel + airborne walk still need a pull.
const WALK_FALLBACK_GRAVITY_Y: float = -9.81
## Multiplier on project gravity for walk ledge peel, walk-airborne, and idle-airborne (arcade descent).
const PLAYER_VERTICAL_ACCEL_MULTIPLIER: float = 9.0
+## Scale [method _apply_walk_post_slide_ledge_peel] integration when feet are still far above a **lower**
+## goal (block / terrace lip). Keeps peel sub-linear time from feeling like a hover; tread-sized drops stay ~1×.
+const WALK_LEDGE_PEEL_BOOST_SEP_1: float = 0.28
+const WALK_LEDGE_PEEL_BOOST_SEP_2: float = 0.75
+const WALK_LEDGE_PEEL_BOOST_SEP_3: float = 1.35
+const WALK_LEDGE_PEEL_BOOST_MUL_1: float = 1.65
+const WALK_LEDGE_PEEL_BOOST_MUL_2: float = 2.75
+const WALK_LEDGE_PEEL_BOOST_MUL_3: float = 4.25
## CapsuleShape3D in scene: height = 1.0 (cylinder portion), radius = 0.4.
## Total half-height from body origin to physical bottom = 0.5 + 0.4 = 0.9
## (`CAPSULE_HALF_HEIGHT + PLAYER_CAPSULE_RADIUS`).
@@ -598,7 +615,15 @@ func _apply_walk_post_slide_ledge_peel(
_walk_ledge_peel_vy = 0.0
return
# Debounce was computed pre-slide; do not re-probe here (post-slide rays re-flicker on treads).
- _walk_ledge_peel_vy += _player_vertical_accel(delta).y * delta
+ var sep_below_feet: float = feet_y - _auth_walk_goal.y
+ var peel_boost: float = 1.0
+ if sep_below_feet > WALK_LEDGE_PEEL_BOOST_SEP_3:
+ peel_boost = WALK_LEDGE_PEEL_BOOST_MUL_3
+ elif sep_below_feet > WALK_LEDGE_PEEL_BOOST_SEP_2:
+ peel_boost = WALK_LEDGE_PEEL_BOOST_MUL_2
+ elif sep_below_feet > WALK_LEDGE_PEEL_BOOST_SEP_1:
+ peel_boost = WALK_LEDGE_PEEL_BOOST_MUL_1
+ _walk_ledge_peel_vy += _player_vertical_accel(delta).y * delta * peel_boost
var dy: float = _walk_ledge_peel_vy * delta
if absf(dy) > 1e-9:
# Raw [code]global_position[/code] edits can be ignored/overwritten vs Jolt sync after [method move_and_slide].
@@ -765,6 +790,15 @@ static func idle_support_is_stable(
if not on_floor:
return false
var ridged: bool = idle_slide_contacts_are_ridged(slide_normals)
+ var up_dot: float = floor_normal.dot(Vector3.UP)
+ # Single-surface inclines (walk ramps): flatter than [member STABLE_IDLE_FLOOR_MIN_UP_DOT] but not
+ # stair lip geometry — latch stable idle instead of endless corrective [method _physics_idle_tick].
+ if (
+ not ridged
+ and up_dot >= IDLE_SLOPE_STABLE_MIN_UP_DOT
+ and up_dot < STABLE_IDLE_FLOOR_MIN_UP_DOT
+ ):
+ return true
# Entering stable idle uses [member STABLE_IDLE_FLOOR_MIN_UP_DOT] (0.998). On stair treads,
# [code]get_floor_normal()[/code] often sits ~0.996–0.997 with a riser contact — never latches,
# so idle stays in corrective [method _maybe_idle_rim_settle_nudge] + [method _physics_idle_tick]
@@ -772,7 +806,7 @@ static func idle_support_is_stable(
var required_up: float = min_flat_up_dot
if ridged and min_flat_up_dot >= STABLE_IDLE_FLOOR_MIN_UP_DOT - 1e-6:
required_up = STABLE_IDLE_FLOOR_HOLD_MIN_UP_DOT
- if floor_normal.dot(Vector3.UP) < required_up:
+ if up_dot < required_up:
return false
# Post-stop **ridged** slides (tread + riser) used to force unstable idle for the entire
# `loose_ticks` window even when the **floor** is level — e.g. idle on a flat approach tread
@@ -782,7 +816,7 @@ static func idle_support_is_stable(
if (
loose_ticks > 0
and ridged
- and floor_normal.dot(Vector3.UP) < IDLE_RIM_MIN_FLOOR_UP_DOT
+ and up_dot < IDLE_RIM_MIN_FLOOR_UP_DOT
):
return false
return true
@@ -915,7 +949,12 @@ func _maybe_idle_rim_settle_nudge() -> bool:
var shallow_floor: bool = (
is_on_floor() and get_floor_normal().dot(Vector3.UP) < IDLE_RIM_MIN_FLOOR_UP_DOT
)
- if not shallow_floor and not _idle_ridged_floor_contacts():
+ var ridged: bool = _idle_ridged_floor_contacts()
+ # Uniform walkable ramps (one dominant sloped floor, no tread+riser pair) still read as “shallow”
+ # vs [member IDLE_RIM_MIN_FLOOR_UP_DOT] — horizontal rim nudges slide along the ramp every tick → jitter.
+ if shallow_floor and not ridged:
+ return false
+ if not shallow_floor and not ridged:
return false
var h := Vector3.ZERO
if shallow_floor:
@@ -1117,7 +1156,12 @@ func _physics_process(delta: float) -> void:
_auth_walk_goal.y, global_position.y, CAPSULE_HALF_HEIGHT + PLAYER_CAPSULE_RADIUS
)
var vert_ok: float = VERT_ARRIVE_EPS
- if is_on_floor() and _walk_vert_route_latched and _walk_nav_column_steering:
+ if (
+ is_on_floor()
+ and _walk_vert_route_latched
+ and _walk_nav_column_steering
+ and vert_err < WALK_COLUMN_NEAR_ARRIVE_GATE_VERT_ERR
+ ):
vert_ok = maxf(VERT_ARRIVE_EPS, WALK_COLUMN_NEAR_ARRIVE_VERT)
if horiz_dist <= ARRIVE_EPS and vert_err <= vert_ok:
velocity = Vector3.ZERO
@@ -1171,7 +1215,14 @@ func _physics_process(delta: float) -> void:
# path only when vertical routing is latched **and** column hysteresis says so **and** we are
# on floor — mid-air column steering flips XZ velocity each tick against `move_and_slide`
# (NEON-16: `on_floor=false` + `vlat && ncol` while falling).
- if needs_vertical_routing and _walk_nav_column_steering and is_on_floor():
+ # On **shallow ramps** (dot below [member IDLE_RIM_MIN_FLOOR_UP_DOT]), path waypoints sit on a
+ # narrow ribbon — lookahead flips 180° each tick → violent oscillation (debug: alternating ±vel).
+ if (
+ needs_vertical_routing
+ and _walk_nav_column_steering
+ and is_on_floor()
+ and get_floor_normal().dot(Vector3.UP) >= IDLE_RIM_MIN_FLOOR_UP_DOT
+ ):
_set_horizontal_velocity_from_nav_path_or_goal(want_goal_h)
else:
_set_horizontal_velocity_toward(_auth_walk_goal, want_goal_h)
diff --git a/server/NeonSprawl.Server/Game/PositionState/MovementValidationOptions.cs b/server/NeonSprawl.Server/Game/PositionState/MovementValidationOptions.cs
index 2d80cc3..94d64ed 100644
--- a/server/NeonSprawl.Server/Game/PositionState/MovementValidationOptions.cs
+++ b/server/NeonSprawl.Server/Game/PositionState/MovementValidationOptions.cs
@@ -11,7 +11,7 @@ public sealed class MovementValidationOptions
public double MaxHorizontalStep { get; set; } = 18.0;
/// Max absolute delta on Y in one move (meters). Inclusive at the limit.
- public double MaxVerticalStep { get; set; } = 1.25;
+ public double MaxVerticalStep { get; set; } = 2.2;
/// When true, target must lie inside the axis-aligned district box (inclusive min/max).
public bool DistrictBoundsEnabled { get; set; }
diff --git a/server/NeonSprawl.Server/appsettings.json b/server/NeonSprawl.Server/appsettings.json
index 90ef224..18e0bca 100644
--- a/server/NeonSprawl.Server/appsettings.json
+++ b/server/NeonSprawl.Server/appsettings.json
@@ -16,7 +16,7 @@
"MovementValidation": {
"HorizontalStepEnabled": false,
"MaxHorizontalStep": 18,
- "MaxVerticalStep": 1.25,
+ "MaxVerticalStep": 2.2,
"DistrictBoundsEnabled": false
}
}
diff --git a/server/README.md b/server/README.md
index f63f2db..63f378d 100644
--- a/server/README.md
+++ b/server/README.md
@@ -70,11 +70,11 @@ Unknown player ids return **404**. Full zone sync / replication is still out of
| Config key | Meaning | Default (see `appsettings.json`) |
|------------|---------|-----------------------------------|
| `Game:MovementValidation:MaxHorizontalStep` | Max XZ displacement per command (m); inclusive at limit | `18` |
-| `Game:MovementValidation:MaxVerticalStep` | Max absolute Y delta per command (m); inclusive | `1.25` |
+| `Game:MovementValidation:MaxVerticalStep` | Max absolute Y delta per command (m); inclusive | `2.2` |
| `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** are **two random short cylinders** each run (~**8–14 cm** rise) and 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.
+**Manual QA (Godot `main.tscn`):** **Green bumps** are **two random short cylinders** each run (~**8–14 cm** rise) and stay under default `MaxVerticalStep` from the dev spawn; **orange reject pedestal** top is ~**2.5 m** above floor so a floor-level click still yields **`vertical_step_exceeded`** (default `MaxVerticalStep` is **2.2 m**); the **physics ramp test block** top is **2 m** so clicking the floor from it is **accepted**. **Far pad** is beyond default horizontal reach for **`horizontal_step_exceeded`** when horizontal limits are enabled. 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):