New

dot-physics

Collision layers, engine profiles and surfaces as one shared layout — named masks instead of bit arithmetic, profiles named for how they feel, a surface table, and casts written against layer names.

dot-physics does not simulate anything — Godot does that. It decides what Godot is told, and puts those decisions somewhere a person can read them: a mask is mask_of([&"world", &"prop"]) rather than 1 << 3 | 1 << 7.

Requires dot-core.

Usage

var physics := DotPhysicsWorld.new()
physics.profile  = DotPhysicsProfile.arcade_shooter()   # 128 Hz, gravity 20
physics.layout   = DotPhysicsLayout.shooter_3d()        # sixteen named layers
physics.surfaces = DotPhysicsSurfaceSet.standard()      # eleven materials
add_child(physics)

var res := physics.setup()
if not res.ok:
    push_error(res.error.message)

physics.classify(player_body, &"player")                # layer and mask, by name

var hit := physics.query().shot_3d(world, muzzle, muzzle + dir * 100.0)
if not hit.is_empty():
    var surface: DotPhysicsSurface = hit["surface"]     # what it is made of
    audio.play(surface.impact_sound)

Layouts

shooter_3d Sixteen layers: player and NPC clip volumes, debris that touches only the world, a query-only hitbox layer for lag-compensated hit registration.
sandbox_3d The shooter’s, plus held_prop and frozen_prop. A held prop does not push the player holding it.
platformer_2d Side-on. One-way platforms are their own layer, so a projectile is not stopped by a surface the player walks through.
top_down_2d No gravity, many bodies, players who pass through each other.
custom Build your own.

A layout declares each collision relation once — “debris collides with world” — and build() closes it symmetrically, because a table whose halves are written by hand drifts. A layer name that does not exist is refused at build time: a typo contributes no bit, so the pair silently never collides.

Profiles

Named for how they feel, since that is the only thing about them anybody can reason about. Each is a DotConfig, so --physics-tick-rate 128 retunes a dedicated server with no rebuild.

arcade_shooter 128 Hz, gravity 20 m/s², interpolation off, characters not pushed by bodies.
grounded 64 Hz, gravity 12, sixteen solver iterations, interpolation on.
sandbox Twenty-four iterations so a stack of crates holds; an aggressive sleep threshold so two hundred settled props cost nothing.
floaty_platformer Long jumps, generous slopes.
top_down No gravity at all.

Surfaces

DotPhysicsSurfaceSet.standard() is eleven materials carrying friction, restitution, density, sheet thickness, movement multipliers and dot-audio / dot-fx ids.

The lookup is the hard half. A ray hits a collider and nothing in Godot’s result says it hit concrete, so for_collider() tries node metadata, then groups, then the node’s name — the last an honest guess, because a level built by a mapper is the case where nothing else is available.

This is not dot-player-controller's surface table

That one holds multipliers on player movement; this one is what the solver is told about a rigid body. A game wants both, they are read by different code at different times, and merging them would break one.