diff --git a/client/scripts/main.gd b/client/scripts/main.gd index bfe9e7a..2459f8f 100644 --- a/client/scripts/main.gd +++ b/client/scripts/main.gd @@ -72,6 +72,8 @@ func _on_move_rejected(reason_code: String) -> void: ## Dev smoke (NEON-28 occluder purge): **F9** frees `Obstacle` while testing; remove when no longer needed. ## After removing nav source geometry, we **rebake** — otherwise `NavigationAgent3D` keeps a stale map ## and click-to-move can stop working. +## Coroutine is started **directly** from input (not [method call_deferred]): deferred + [code]await[/code] +## often never resumes, so the rebake never finished and the nav map stayed stale. func _unhandled_key_input(event: InputEvent) -> void: if not event is InputEventKey: return @@ -80,15 +82,18 @@ func _unhandled_key_input(event: InputEvent) -> void: var obstacle := get_node_or_null("World/NavigationRegion3D/Obstacle") if obstacle: obstacle.queue_free() - call_deferred("_rebake_nav_mesh_after_obstacle_removed") + _rebake_nav_after_obstacle_removed_async() -func _rebake_nav_mesh_after_obstacle_removed() -> void: +func _rebake_nav_after_obstacle_removed_async() -> void: + await get_tree().process_frame + if not is_instance_valid(_nav_region): + return _nav_region.bake_navigation_mesh(false) + # Let NavigationServer3D apply the rebaked mesh before repathing. + await get_tree().physics_frame await get_tree().physics_frame await get_tree().physics_frame if not is_instance_valid(_player): return - var na := _player.get_node_or_null("NavigationAgent3D") as NavigationAgent3D - if na != null: - na.set_target_position(_player.global_position) + _player.sync_navigation_agent_after_map_rebuild() diff --git a/client/scripts/player.gd b/client/scripts/player.gd index 0341bf7..ed694ad 100644 --- a/client/scripts/player.gd +++ b/client/scripts/player.gd @@ -89,6 +89,14 @@ func clear_nav_goal() -> void: _nav_agent.set_target_position(global_position) +## Repath on a new nav map (e.g. after [NavigationRegion3D] rebake when geometry is removed). +func sync_navigation_agent_after_map_rebuild() -> void: + if _has_walk_goal: + _nav_agent.set_target_position(_auth_walk_goal) + else: + _nav_agent.set_target_position(global_position) + + func snap_to_server(world_pos: Vector3) -> void: global_position = world_pos velocity = Vector3.ZERO diff --git a/docs/plans/NEON-28-implementation-plan.md b/docs/plans/NEON-28-implementation-plan.md index 7f34acf..0662bda 100644 --- a/docs/plans/NEON-28-implementation-plan.md +++ b/docs/plans/NEON-28-implementation-plan.md @@ -106,7 +106,7 @@ Jira acceptance criteria (verbatim intent): **Manual verification:** Main scene — zoom bands, occlusion fade, fixed yaw UX; spot-check Slice 2 behaviors (readable motion, occlusion active). -**Freed occluder (end-to-end, editor):** Position player so `Obstacle` is between camera and player (fade active), then `queue_free` the occluder. Expect **no** recurring script errors and **no** stuck transparent materials on other meshes. **`Obstacle` lives under `NavigationRegion3D`** — after removing it, **rebake** the nav mesh (see **`main.gd` F9** dev path: `bake_navigation_mesh(false)` + refresh `NavigationAgent3D` target) or click-to-move can stop until the next full scene reload. Complements static `occluder_override_key_is_valid` tests, which do not execute the full purge path in CI. +**Freed occluder (end-to-end, editor):** Position player so `Obstacle` is between camera and player (fade active), then `queue_free` the occluder. Expect **no** recurring script errors and **no** stuck transparent materials on other meshes. **`Obstacle` lives under `NavigationRegion3D`** — after removing it, **rebake** the nav mesh (see **`main.gd` F9**: `await` a frame after `queue_free`, then `bake_navigation_mesh(false)`, then a few physics frames, then **`Player.sync_navigation_agent_after_map_rebuild()`**) or click-to-move can stop. Do **not** drive that sequence with **`call_deferred` + `await`** on the same method — the coroutine may never resume. Complements static `occluder_override_key_is_valid` tests, which do not execute the full purge path in CI. ## Open questions / risks