Physics
Physics in Zero has two surfaces, reached in two different ways:
Physics— a Luau global you call from inside scripts and components: forces, velocity, raycasts, overlaps, sleep control, gravity, joints, wheels, collision groups. This is the code surface.phys— a toolbox you drive from the agent tool surface (search_tools→use_tool), not a Luau global. Its verbs compose an entity + model + body + collider in one call, so you can stand up physical objects without writing a script.physis not available insideexecutecode — callingphys.spawnStatic(...)in a script fails with "attempt to index nil".
Physics simulates during play; in edit mode the world is posed, not running (the engine guide covers modes).
Making something physical (in code)
A body is static (immovable — floors, walls), dynamic (moved by forces and gravity), or kinematic (moved by your code, pushes dynamics). Build one from the component primitives: give an entity a Collider for its shape and a Physics component for its body.
local crate = entity.spawn("crate")
crate.position = { 0, 8, 0 }
crate.component.add("Collider", { shape = "box" }) -- shape sized by the entity's scale
crate.component.add("Physics", { kind = "dynamic" }) -- static | dynamic | kinematic
A Collider alone (no Physics) is a static collider fixed in world space — the right thing for ground and walls:
local ground = entity.spawn("ground")
ground.localScale = { 40, 1, 40 }
ground.component.add("Collider", { shape = "box" })
Collider also takes shape = "sphere" / "capsule" with radius / height. A dynamic body's mass comes from its collider volume by default; set Physics.mass to a value greater than zero to pin the exact total mass instead (a value of zero or below keeps the volume-derived mass).
Driving and querying (in code)
Physics.applyImpulse(id, { 0, 10, 0 }) -- one-shot push
Physics.setVelocity(id, 0, 5, 0) -- direct velocity (numeric args), or setVelocity(id, {0,5,0})
Physics.setGravity({ 0, -9.81, 0 }) -- world gravity
local hit = Physics.raycast({ 0, 5, 0 }, { 0, -1, 0 }, 100) -- origin, direction, maxDist
if hit then print(hit.entityId, hit.distance) end
local near = Physics.overlapSphere({ 0, 0, 0 }, 10) -- entity ids whose colliders overlap
A body at rest sleeps to save simulation cost. applyImpulse, setVelocity, and setAngularVelocity wake it for you; call Physics.wakeUp(id) to wake one yourself, and Physics.isSleeping(id) to check.
Joints, wheels, groups
Joint motors, wheel colliders, and collision-group filtering live on Physics (Physics.setJointMotor, Physics.addWheelCollider, Physics.setCollisionGroups). The phys toolbox composes the common constraints (fixed, revolute, prismatic, spherical, spring, rope) from the tool surface.
Rope and chain behavior is a joint, not a script: kind = "rope" with maxDistance gives a hard maximum-distance limit solved inside the physics step — slack is free, taut resists extension, and an orbiting body on a taut rope keeps its speed. A constraint solved per-frame from Luau reads state one step late and writes one step later; for a steadily rotating configuration that lag feeds energy in every solve, so a hand-rolled rope pumps an orbiting body faster and faster. Reel a rope in or out by writing maxDistance on the live joint.
The full surface
lsp.methods("Physics") lists every Physics method with its signature; search_tools surfaces the phys toolbox verbs. The model to hold: phys composes from the tool surface, Physics controls from code, and simulation happens in play.