#physics
38 items (2 guides · 36 fixes)
The method for making a procedurally-rigged NPC crumple via engine physics: author a physics skeleton in the vmdl, toggle Sandbox.ModelPhysics at runtime, and hand bone control back for stand-up. No authored collapse clips needed.
The proven architecture for driving games in s&box: raycast wheels on a single chassis Rigidbody, substepped slip-ratio/slip-angle tire physics with peaked curves, a torque-curve drivetrain, layered assists, and arcade dials on top of a sim core.
Census tris × instances before decimating; prefer BoxCollider over ModelCollider for decorative props.
A PhysicsHullFile node compiled with import_mode HullPerElement treats a material break as an element boundary. Paint two adjoining primitives with a second material and the compiler folds them into one element and builds one convex hull across both. The convex hull of a wall with a hole in it is a solid wall, so every doorway and window built the same way seals shut at once, with no geometry change in the diff. Every offline check stays green, because none of them compile the model. The defect is only visible in the compiled collision, which the editor or an in-engine walk probe can see. Fix by giving physics its own single-material copy of the source mesh, so the render mesh's material list never reaches the physics node, and pin the physics mesh to the render mesh with a lint.
On a track that crosses itself (a figure-eight, or any lap that revisits the same ground), a per-frame nearest-waypoint lookup jumps about half a lap at the crossing, because two points that sit close together can be arclength-distant. Drive lap position from a monotone cursor that only advances along the committed branch, do lateral math in a left-of-travel frame, and keep the branch-discrimination cone tighter than the branches' angular separation.
A per-run accumulator guard (budget, cooldown, or streak counter) that resets on a transient state flicker never actually fires, because the reset zeroes it faster than it can fill. Reset only on a signal that truly ends the episode (sustained air, real forward progress), never on a per-tick proxy the episode itself trips constantly.
Deriving a visual's facing from horizontal velocity causes 180-degree snaps on any momentum reversal. Lock facing to an attach-time azimuth for pendulums and oscillators.
BoxCollider is cheap and follows WorldScale; Capsule/ModelCollider don't scale with WorldScale and ModelCollider is costly on clutter.
A stationary vertical sink at a chunk-corner seam defeats eject, mantle, and even hard-recover (the anchor itself can be buried under an overhang). The terminal escape is a top-out to the chunk surface.
Jump() helpers clamp against rising velocity, eating the second impulse. Set Velocity.z directly for a reliable double jump.
Editor-embedded play mode pins at exactly 60 fps regardless of cvars. The cap is the compositor's vsync on the editor window, not the engine frame sync. Under the cap even the p50 pins to 16.67 ms: read the tail (p99, worst frame, over-budget count), not the median.
Frame fps pins at 60 in the editor regardless of physics load: implement IScenePhysicsEvents.PrePhysicsStep/PostPhysicsStep with a Stopwatch to get the true, vsync-free CPU cost of the solver step.
Turning a composite physics actor into static scenery by destroying its Rigidbody silently kills every child system that keyed its own liveness off that body. Set MotionEnabled = false instead: the body stays alive, dependents keep ticking, and a kinematic body reads zero delta-v for free.
A runtime-generated world root torn down with GameObject.Destroy() keeps rendering in edit mode because the deferred queue isn't flushed: use DestroyImmediate and sweep all matching roots.
An idempotent ground-snap jitters on curved terrain because the slope curves away within each step: ease the downward snap at a bounded glue rate.
The render node in a vmdl honours import_rotation. PhysicsHullFile and PhysicsMeshFile ignore it. So correcting an off-axis import with import_rotation fixes the picture and leaves the collision exactly where it was. The model builds clean with no error and no warning, photographs correctly, and a player walks straight through it. Bake a corrected engine-frame copy of the mesh that both the render node and the physics node read, rather than rotating one consumer with a property the other ignores.
Always handle tr.StartedSolid: ignore that frame so an overlapped body can walk free; slide-trace the wish onto the hit plane.
A library extraction seam covers only the headline behavior: every other side effect of the replaced code path silently disappears unless explicitly reproduced on the consumer side.
RenderMeshFile alone compiles clean with zero physics: ModelCollider needs PhysicsMeshFile/PhysicsHullFile in the vmdl.
CapsuleCollider and ModelCollider ignore WorldScale; BoxCollider follows it. Bake scale into import_scale instead.
Disabling a collider does nothing for a hand-integrated kinematic controller: noclip must be a movement state that skips traces, gravity, and ground-snap entirely.
A PhysicsShapeList node compiles with several tagged PhysicsHullFile children, so one vmdl can carry a compound collision volume. The per-shape tags stay inert through a scene ModelCollider.
Rotation.FromYaw alone leaves a decal flat on the floor: build a rotation that aligns the thin axis to the surface normal, then offset along it.
A multi-hull vmdl authored with per-shape collision_tags does not get those tags at runtime -- a scene ModelCollider overwrites every shape with the GameObject's own tags, so Scene.Trace.WithoutTags can't filter one hull from another. Split the hulls across separate collider GameObjects instead.
PhysicsShapeList + PhysicsJointList in the vmdl + ModelPhysics toggle. No collapse clips, no SetBoneTransform. On current builds ModelPhysics.PhysicsGroup reads NULL: enumerate ModelPhysics.Bodies directly and resolve bones by name→index.
A collision rule that keys its direction on the struck object's own velocity is correct for the object that caused the hit and silently backwards for the object that got hit. A car rolling forward at 2 m/s and rammed from behind reads as moving forward, so it dents its nose instead of its rear. Derive the direction from the negated impulse delta-v, which points from the struck face inward for both bodies.
Rigidbody API verified across multiple projects: Gravity, MassOverride, Velocity, ApplyForce, and the recipe for a dynamic pushable prop. AutoSleep lives on PhysicsBody (set-only as of 26.08.05), which is now populated on the same tick the component is created.
Rotation.FromYaw(+angle) is a LEFT (CCW) turn. Get the sign right or steering, AI, and autopilot spiral the wrong way.
FlatBox/Prop/Deco/Wire helpers plus Obstacle records as plain data beat physics queries for build validity.
1 m = 39.37 engine units: design in SI, convert once at the engine boundary, and audit every consumer.
SceneTraceResult.EndPosition on a sphere-radius sweep is the sphere's CENTRE at contact, not the contact point. Treating it as where the feet landed is off by a full radius. Derive the real contact position as from + (to - from) * Fraction in one helper. A related trap: snapping the sphere exactly tangent to the floor makes the next horizontal sweep intermittently report StartedSolid.
For a swept sphere/hull trace, HitPosition is the true surface contact point while EndPosition is the swept shape's centre at impact. Seat surface effects at HitPosition.
Model compiler rewrites upper_arm.L → upper_arm_L; physics KV3 that still says .L makes limb bodies unknown while the torso works.
The Citizen ragdoll physics rig has 18 shapes (17 capsules plus 1 cylinder) and 16 joints (12 conical ballsockets plus 4 revolute hinges). A common folk estimate lands near 9 to 13 bodies, roughly half the real count, so it undercounts per-body and joint-solve cost. Both prefab files are plain KV3 text, so you count them offline with no editor running.
A trace-based kinematic character controller has no collider component, so trigger volumes and ITriggerListener never fire against it. Use distance polling instead.
A hand-integrated trace mover has no collider component: ITriggerListener and OnTriggerEnter never fire. Use distance-polling instead.
Source engine convention: Forward = +X, Left = +Y, Right = -Y. Using Vector3.Right for '+X' silently slides geometry the wrong way.
A zero-radius Scene.Trace.Ray passes straight through coarse voxel ModelColliders and returns Hit=false. Sweep a thin sphere (.Radius(...)) and it hits.