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.
5.4 KiB
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
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:
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
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 aFlowFieldwhen 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 aBrain.
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-mathtranscendentals, notstd, for anything crossing the wire.- Seeded
rngfrommakepad-game-gen, never thread RNG. - Iterate deterministically ordered containers. A
HashMapiteration 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:
#[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.