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.
111 lines
5.4 KiB
Markdown
111 lines
5.4 KiB
Markdown
# 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`.
|