makepad/skills/makepad-gameplay-systems/references/physics-and-movement.md
Arena Agent fe21c07d84 skills: add Makepad game skills pack ported from threejs-game-skills
Nine agent skills for building Makepad/Rust games, ported from
majidmanzarpour/threejs-game-skills. Same director-routed workflow and
premium bar; runtime rewritten for this fork's game crates.

- makepad-game-director        entrypoint, routing, continuity, asset probe
- makepad-gameplay-systems     loop, movers vs rigid bodies, input, camera, netplay
- makepad-aaa-graphics-builder lighting, shaders, budget, visual scorecard
- makepad-game-ui-designer     HUD, menus, touch and XR UI
- makepad-debug-profiler       defect bisection and profiling
- makepad-qa-release           verification ladder, evidence, packaging
- makepad-3d-generator         CC0 model search, casts, procedural geometry
- makepad-image-generator      texgen textures, palettes, sky, icons
- makepad-audio-generator      sample bank, mixer, material impacts, 3D audio

Written against the real APIs in libs/game/*, libs/sim and apps/arcade:
the game.* verb table, GameRenderer adaptive quality, the packed 6-float
GameMeshVertex layout, script_mod! splash styling, makepad-test driving,
and the BUDGETS.md numbers. No paid generation API is required - the CC0
library plus seeded makepad-game-gen replaces them.

Includes install.sh (Codex/Claude), validate-skills.sh and a repo-aware
probe_assets.sh; both scripts verified against this checkout.
2026-09-05 21:18:58 +00:00

111 lines
5.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Physics and movement
## Choosing a body type
| Want | Use | Why |
| --- | --- | --- |
| Responsive player | kinematic capsule mover | direct control, no integrator lag, predictable collision response |
| Thrown crate, debris, ragdoll | rigid body | mass, spin, and stacking come free |
| Lift, door, moving platform | scripted transform | exact path, no drift |
| Vehicle | `Car` block | suspension, wheels and weight transfer already solved |
| Bullet | ray or `projectile_ccd` | discrete stepping tunnels through thin geometry |
A player on a rigid body feels floaty and snags on seams. A crate on a mover feels weightless. This choice is not a matter of taste.
## Movers
```rust
capsule_mover_step(/* world, entity, desired_motion, dt */);
apply_mover_contacts(&mut dynamics, &mut entities);
apply_sweep_pushes(&mut dynamics, &pushes);
```
The mover resolves against geometry and reports contacts. Grounding: `on_floor`, `ground_y`, `ground_normal`, plus `deck_floor_under(decks, pos, half, feet, climb)` and `box_floor_under(..)` for deck strips and boxes. The `climb` parameter is your step height — the single most feel-relevant constant on a walker. Too low and the character catches on every kerb; too high and it walks up walls.
Slopes: use `ground_normal`. Above a walkable angle, slide instead of walk, or the player climbs cliffs.
## Rigid dynamics
`RigidDynamics` owns bodies. Useful surface:
```rust
dynamics.rigid_body_of(entity_id); // Option<BodyId>
dynamics.rigid_impulse(entity_id, dv); // bool
dynamics.rigid_spin(entity_id, axis_vel);
dynamics.rigid_accel(entity_id);
dynamics.sync_baseline(entity_id, pos, orient, vel);
dynamics.take_mover_impacts(); // Vec<MoverImpact> - drive audio & decals from these
step_dynamics(&mut dynamics, &mut entities);
reconcile(/* ... */); // netplay correction
```
`take_mover_impacts()` is the correct source for impact SFX, `BulletDecal` marks and particle bursts. Detecting collisions a second time in gameplay code produces double sounds and drifting decals.
Ragdoll: `activate_ragdoll(..)`, then `ragdoll_active(id)` and `ragdoll_body_poses(id)` to drive the skinned pose. The mover→ragdoll transition must be an explicit event, not an emergent state.
## Queries
```rust
cast_ray(&dynamics, from, dir, max); // Option<QueryHit>
projectile_ccd(/* ... */);
raycast_filter(); // sensible defaults - start here
projectile_filter();
```
Filter deliberately. A raycast that hits the shooter's own capsule is the classic "my gun does nothing" bug; a ground check that hits a trigger volume is the classic "player is permanently airborne" bug.
## Terrain, water, voxels, nav
- `terrain` + `landform::LandKind` — heightfield ground. 65×65 with 100 movers and 50 bodies costs 0.038 ms/tick, so terrain resolution is a *visual* budget question, not a sim one.
- `water``WaterVolume`, `WaterWave` (`MAX_WAVES`), `WaterState`, `BuoyancyApplied`, `dispersion_speed`. Buoyancy is applied to rigid bodies, so a floating crate is a rigid body, not a mover.
- `voxel` — destructible/blocky volumes, with their own body count (`voxel_body_count`).
- `nav``NavMap`, `NavAgent`, `FlowField`. Use a `FlowField` when many agents share one target (crowds, waves); use per-agent paths when targets differ. Building N paths to one goal is the standard performance mistake in wave games.
- `LosProvider` / `NavProvider` — inject visibility and navigation into behaviour code instead of reaching into the world from a `Brain`.
## Tuning tables
Starting values for a human-scale character (player capsule ~1.8 m tall). Tune from here; do not invent from zero.
| Constant | Start | Note |
| --- | --- | --- |
| walk speed | 4.5 m/s | |
| run speed | 8.0 m/s | ≤ ~2× walk or animation desyncs |
| ground accel | 40 m/s² | reaches walk speed in ~0.11 s |
| air accel | 8 m/s² | low, but non-zero — zero air control feels broken |
| jump apex | 1.2 m | derive impulse from apex, not the reverse |
| gravity | 22 m/s² | heavier than real; real gravity feels floaty |
| fall multiplier | 1.8× after apex | the single biggest jump-feel improvement |
| coyote time | 0.10 s | |
| jump buffer | 0.12 s | |
| step height (`climb`) | 0.4 m | |
| max walkable slope | 45° | |
Vehicles: tune `CarConfig`, not raw forces. Aircraft: tune `PlaneConfig`.
## Determinism
Non-negotiable for netplay and replay:
- Fixed `TICK_DT`. Accumulate leftover frame time; never step by a variable delta.
- `makepad-game-math` transcendentals, not `std`, for anything crossing the wire.
- Seeded `rng` from `makepad-game-gen`, never thread RNG.
- Iterate deterministically ordered containers. A `HashMap` iteration order in the step function is a desync waiting for a player count above one.
## Testing
All of this is Cx-free, so it is ordinary `cargo test`. Assert on observable behaviour:
```rust
#[test]
fn mover_clears_step_but_not_wall() {
let mut w = world_with_step(0.4);
walk_forward(&mut w, 1.0);
assert!(player(&w).pos.y > 0.35, "should have climbed the 0.4 m step");
let mut w = world_with_step(1.5);
walk_forward(&mut w, 1.0);
assert!(player(&w).pos.y < 0.1, "must not climb a 1.5 m wall");
}
```
When the claim is about composition — "a walker can enter a real generated room" — test the composition. Two half-tests in two crates both pass while the walker floats above the floor; that has actually happened in this codebase, which is why `blocks` dev-depends on `gen` and `render`.