diff --git a/client/README.md b/client/README.md index c6dba71..96f5f5c 100644 --- a/client/README.md +++ b/client/README.md @@ -33,14 +33,14 @@ Elevated walkables use **distinct albedo tints** in `scenes/main.tscn` so screen | `ObstacleB` | `occluder` | Default gray | 3 × 3 | (−10, 5) | Second occluder (wider angle from spawn) | | `ObstacleC` | `occluder` | Default gray | 2 × 4 | (5, −12) | Tall occluder, south quadrant | | `ObstacleD` | `occluder` | Default gray | 3 × 2 | (−8, −16) | Far-south occluder | -| `TerracePlatformA` | `walkable` | **Teal** | 10 × 10, h=0.3 m | (13, −14) | Large SE pad; single-step ascent from floor | +| `TerracePlatformA` | `walkable` | **Teal** | 10 × 10, h=0.3 m | (13, −14) | Large SE pad; **three darker-teal approach treads per cardinal** (`TerracePlatformA_Approach` / `PAS_*`) bridge the lip for nav + physics | | `TerraceStepB` | `walkable` | **Gold** | 6 × 3, h=0.3 m | (−15, 8.5) | NW approach step; bridges floor → TerracePlatformB | | `TerracePlatformB` | `walkable` | **Violet** | 6 × 6, h=0.6 m | (−15, 13) | Upper NW terrace; reached via TerraceStepB | | `TerracePlatformC` | `walkable` | **Green** | 8 × 8, h=0.3 m | (14, 14) | NE pad; single-step ascent from floor | ### Height variation notes -- **Single-step terraces (A, C):** 0.3 m rise — within `agent_max_climb = 0.35`, so the nav mesh routes directly from floor onto the pad; one click suffices. **`TerracePlatformA`** (teal) sits **12 mm** above the main floor slab in the scene to reduce coplanar internal edges with Jolt; nav rebakes at startup. +- **Single-step terraces (A, C):** 0.3 m rise — within `agent_max_climb = 0.35`, so the nav mesh routes directly from floor onto the pad; one click suffices. **`TerracePlatformA`** (teal) sits **12 mm** above the main floor slab and has **cardinal walkable treads** (`TerracePlatformA_Approach`) so movement does not rely on a bare vertical box lip; nav rebakes at startup. - **Two-level terrace B:** floor → `TerraceStepB` (0.3 m) → `TerracePlatformB` (additional 0.3 m). Each transition is within climb limit, so the agent can route the full ascent in a single click-to-move target anywhere on the platform. - **Occluders** are plain `StaticBody3D` with no `walkable` tag; the nav bake excludes their top surfaces, so they remain true obstacles for routing. diff --git a/client/scenes/main.tscn b/client/scenes/main.tscn index f8ca0ab..d80e02a 100644 --- a/client/scenes/main.tscn +++ b/client/scenes/main.tscn @@ -135,6 +135,21 @@ size = Vector3(8, 0.3, 8) [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, 0.45) + +[sub_resource type="BoxShape3D" id="BoxShape3D_pa_stair_ns"] +size = Vector3(9.2, 0.104, 0.45) + +[sub_resource type="BoxMesh" id="BoxMesh_pa_stair_we"] +size = Vector3(0.45, 0.104, 9.2) + +[sub_resource type="BoxShape3D" id="BoxShape3D_pa_stair_we"] +size = Vector3(0.45, 0.104, 9.2) + [node name="Main" type="Node3D" unique_id=1358372723] script = ExtResource("1_main") @@ -266,6 +281,140 @@ surface_material_override/0 = SubResource("Mat_terrace_platform_a") transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.15, 0) 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.125) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_S1" unique_id=3100011] +mesh = SubResource("BoxMesh_pa_stair_ns") +surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_S1" unique_id=3100012] +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.675) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_S2" unique_id=3100021] +mesh = SubResource("BoxMesh_pa_stair_ns") +surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_S2" unique_id=3100022] +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.225) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_S3" unique_id=3100031] +mesh = SubResource("BoxMesh_pa_stair_ns") +surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_S3" unique_id=3100032] +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, -7.875) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_N1" unique_id=3100041] +mesh = SubResource("BoxMesh_pa_stair_ns") +surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_N1" unique_id=3100042] +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, -8.325) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_N2" unique_id=3100051] +mesh = SubResource("BoxMesh_pa_stair_ns") +surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_N2" unique_id=3100052] +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.775) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_N3" unique_id=3100061] +mesh = SubResource("BoxMesh_pa_stair_ns") +surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_N3" unique_id=3100062] +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.875, 0.052, -14) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_W1" unique_id=3100071] +mesh = SubResource("BoxMesh_pa_stair_we") +surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_W1" unique_id=3100072] +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.325, 0.156, -14) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_W2" unique_id=3100081] +mesh = SubResource("BoxMesh_pa_stair_we") +surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_W2" unique_id=3100082] +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, 7.775, 0.26, -14) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_W3" unique_id=3100091] +mesh = SubResource("BoxMesh_pa_stair_we") +surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_W3" unique_id=3100092] +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, 19.125, 0.052, -14) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_E1" unique_id=3100101] +mesh = SubResource("BoxMesh_pa_stair_we") +surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_E1" unique_id=3100102] +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, 18.675, 0.156, -14) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_E2" unique_id=3100111] +mesh = SubResource("BoxMesh_pa_stair_we") +surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_E2" unique_id=3100112] +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, 18.225, 0.26, -14) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_E3" unique_id=3100121] +mesh = SubResource("BoxMesh_pa_stair_we") +surface_material_override/0 = SubResource("Mat_terrace_platform_a_ramp") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="World/NavigationRegion3D/TerracePlatformA_Approach/PAS_E3" unique_id=3100122] +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) diff --git a/docs/plans/NEON-29-implementation-plan.md b/docs/plans/NEON-29-implementation-plan.md index b818586..5738cda 100644 --- a/docs/plans/NEON-29-implementation-plan.md +++ b/docs/plans/NEON-29-implementation-plan.md @@ -113,6 +113,7 @@ No new automated GDScript tests are added for this story — there is no new GDS - **Lip stuck (floor→platform / platform→floor):** QA screenshots for this were on **`TerracePlatformA`** (10 × 10 large SE pad, **0.3 m** single step — not the NW **Terrace B** step/platform pair). Step assist required **`vel_h.dot(want) ≤ 0.52`**, so a **head-on** push into the vertical face (dot ≈ 1) **never** triggered the lift. **Removed** that gate. For **platform→floor**, **`floor_block_on_wall = false`** only when the nav goal is **below** the feet **and** **`is_on_wall()`** or a **wall-ish slide normal** exists, so open deck walking stays stable. - **Teal pad still stuck (follow-up):** Step assist used **slide normals only**; Jolt often exposes the lip via **`is_on_wall()`** / **`get_wall_normal()`** instead — re-added a **directional** wall check (`wnh.dot(want) < -0.02`). Descend relax now also triggers on **shallow floor normal** (`get_floor_normal().dot(UP) < 0.992`) and slightly wider vertical-ish slide band. **`TerracePlatformA`** root **Y = 0.012 m** breaks **coplanarity** with the main floor without the larger gap that hurt **TerraceStepB** approach. - **Teal cardinal both ways (follow-up):** **Climb:** **`FLOOR_SNAP_MOVING` (0.32 m)** was still active while **`_step_assist_wallish_blocks`** was true, snapping the capsule back to the lower floor each tick and cancelling lifts — cap snap to **`WALK_STEP_ASSIST_SNAP`** in that state; **bigger per-tick lift** (`WALK_STEP_ASSIST_DELTA` **0.16**) and **cooldown 1** tick. **Descend:** reintroduced **`DESCEND_LIP_SNAP_CAP`** when clearly above the lower goal, plus **`descending_stall`** (`horiz_dist` not trivial, **`vh < 0.55`**) to relax **`floor_block_on_wall`** when Jolt reports neither wall nor shallow floor at a cardinal lip. +- **Teal pad — geometry wins:** Script-only mitigations still failed in practice; **`TerracePlatformA_Approach`** adds **twelve** `walkable` **`StaticBody3D`** treads (three per cardinal) with **~0.104 m** rise each, **darker teal** albedo, flush to the platform lip — nav + `CharacterBody3D` use **sloped contact** instead of fighting a **single vertical face**. ## Open questions / risks