Quick answer: Move PhysicsServer3D / direct space state queries to _physics_process. For per-body forces, use the RigidBody3D _integrate_forces override. Direct state outside the physics step is invalid.
Custom raycast in _process. Editor warning, sometimes a crash. Direct state queries are only valid during the physics step.
The Symptom
Crash or assertion: “PhysicsDirectBodyState3D not in valid state.” Or queries return nonsense (zero positions, infinite velocities). Always reproduces on the second physics frame.
The Fix
extends Node3D
func _physics_process(_delta: float) -> void:
var space := get_world_3d().direct_space_state
var q := PhysicsRayQueryParameters3D.create(global_position, global_position + Vector3.DOWN * 5)
var hit := space.intersect_ray(q)
if hit:
print("Hit at ", hit.position)
direct_space_state is fresh and valid only inside _physics_process.
RigidBody3D Custom Forces
extends RigidBody3D
func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:
# state is valid here
var v := state.linear_velocity
state.linear_velocity = v * 0.99 # custom drag
state.apply_central_force(Vector3.UP * 9.8 * mass)
_integrate_forces receives the live PhysicsDirectBodyState3D.
If You Must Query in _process
Cache the latest physics state in a member variable during _physics_process. Read the cache from _process. Stale by up to one physics tick but doesn’t crash.
Verifying
Move queries to _physics_process. Crash disappears. Hit data is consistent. Frame-time impact is minimal because _physics_process runs at a fixed rate.
“Physics queries in physics callbacks. Direct state stays valid.”
Related Issues
For physics interpolation jitter, see interpolation. For Area3D detection, see Area3D.
_physics_process for queries. State holds.