makepad/skills/makepad-aaa-graphics-builder/SKILL.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

8.2 KiB
Raw Permalink Blame History

name description
makepad-aaa-graphics-builder Raise Makepad game visuals to a premium bar: meshes, materials, shader overrides in the splash DSL, lighting and sun, shadows, baked AO, skinning, particles and fireworks, sky and fog, post look, render budget and the visual scorecard. Use for premium, AAA, polished, high-fidelity, showcase, 'less basic', art direction, and performance-versus-quality work on makepad-game-render.

Makepad AAA Graphics Builder

Make it look authored. The bar is not "3D objects appear" — it is that every visible surface was decided by someone.

Where the seam is

makepad-game-render owns structure; the splash DSL owns look. The engine shader decides where a spark is and how long it lives; a script_mod! override decides what it looks like. Keep gameplay-relevant constants out of the DSL and look constants out of Rust. This is what lets a generated game restyle the sky without being able to break the simulation.

script_mod! {
    mod.widgets.MyView = set_type_default() do mod.widgets.MyViewBase {
        draw_cube     += { light_dir: vec3(0.35, 0.8, 0.45) }
        draw_terrain  += { light_dir: vec3(0.35, 0.8, 0.45) }
        draw_skinned  += { light_dir: vec3(0.35, 0.8, 0.45) }
        draw_firework += { /* pixel/shape override for the look only */ }
    }
}

Set light_dir consistently across every draw type. A skinned character lit from a different direction than the terrain it stands on is the single most common "this looks wrong and I can't say why" defect in this engine.

The renderer

Modules in libs/game/render: renderer, scene, stage, shaders, geometry, model, skin, shadow, shadow_mesh, particles, firework, sun, bake, ao, hud, thermometer.

Draw types: DrawGameCube, DrawGameAlpha, DrawGameTerrain, DrawGameSkinned, DrawGameSky, DrawGameShadow, DrawGameFirework, DrawGameTexture, collected in GameDraws.

Frame shape:

let scene = scene_state(&world, &rig, /* .. */);
set_pass_camera(cx, &self.pass, &scene);
renderer.set_models(models);         // Vec<ModelInstance>
renderer.set_particles(particles);
renderer.set_fireworks(fireworks);
renderer.draw_scene_full(cx, &mut draws, &world, &scene /* .. */);

Adaptive quality is already built in — do not write a second one:

let changed = renderer.report_frame_ms(ms);
renderer.set_refresh_hz(72.0);
renderer.set_shadow_budget(24);
renderer.quality(); renderer.quality_level(); renderer.quality_reason();
renderer.frame_p90_ms();
renderer.set_stage(Stage::/* .. */);   // StageMode

Models: load_model(..), model_is_loaded(id), model_bounds(id), model_triangles(id), model_collider_parts(id). ModelInstance::new(key, vertices, indices, transform) with .with_tint(..) and .with_texture(..).

Art direction first

Decide these four before authoring anything, and write them into artifacts/game-progress.md. Changing them later invalidates every asset choice made under the old ones.

  1. Palette — 3 dominant + 2 accent colours. makepad-game-assets gives Palette, Spread, VarietyParams; the script route has find_palette.
  2. Key light — direction, colour, and time of day. Low sun (1525° elevation) produces long shadows and reads as authored; noon light is the default-looking default.
  3. Sky and fogSkyConfig, SunConfig, DrawGameSky. Fog colour must agree with the sky at the horizon or the world looks pasted onto a backdrop.
  4. Scale — player height in world units, decided in gameplay, obeyed here. Kit pieces at inconsistent scale are instantly legible as wrong.

Lighting

sun.rs and SunConfig. The engine's cube/terrain/skinned shaders take sun_color, sun_sky, sun_ground and fog_color as uniforms, not instance data — they are identical across a batch and were moved off the instance stream for a 27% saving. Do not push them back per-instance.

Three-part model: directional key (the sun), hemispheric ambient (sun_sky above / sun_ground bounce below), and fog for depth. Colour them separately — a warm key with a cool sky ambient is most of the difference between "rendered" and "lit". Grey ambient flattens everything.

Shadows: shadow, shadow_mesh, set_shadow_budget(n). Shadow meshes use the same packed 6-float vertex. Budget casters — shadow every object and the frame dies; shadow only the hero and it floats. Prioritise: player, anything moving near the player, tall silhouettes.

Baked AO: bake.rs, ao.rs, BakeSettings, bake_settings() / set_bake_settings(), dynamic_shade(p). AO in contact regions is the cheapest single upgrade to perceived quality — it is what stops props reading as stickers on the ground. The ao_render dev example software-rasterises props so baked AO can be judged by eye.

Materials and variety

The library is CC0 and shared, so out-of-the-box scenes look like everyone else's. Differentiate with:

  • TintModelInstance::with_tint, driven by Palette / VarietyParams / Spread. A crowd of one prop in five palette-consistent tints reads as authored; a crowd of identical props reads as a placeholder.
  • Different members of one cast — never clone the same character across a crowd (find_cast).
  • Procedural texturemakepad-game-gen::texgen for tiling detail, with DrawGameTexture.
  • Scale and rotation jitter — seeded, small (±10% scale, full yaw). Free, and it destroys the grid-stamped look.

Particles and VFX

particles.rs (ParticleSystem, ParticleInstance), sim-side ParticleKind / ParticleRequest / ParticleSpec / EmitterAnchor, and firework.rs (FireworkSystem, FireworkInstance). Script verbs: particles, burst, particles_stop, dust, beam.

Effects read when they are short, bright, and layered: a flash (12 frames), a burst (0.2 s), a lingering trace (0.51 s). One long uniform puff reads as fog. Drive emission from real events — take_mover_impacts() — not from a polling timer.

The firework shader override in arcade_view.rs is the house precedent for a look override: real fireworks are symmetric, so pick one uniform radial angle per spark, then add swirl and fizzle so sparks look like burning matter rather than points on a sphere.

Budgets

apps/arcade/BUDGETS.md is authoritative. What actually costs:

  • Not the sim. 100 movers + 50 bodies + 65×65 terrain = 0.038 ms/tick against a 16.6 ms budget.
  • Draw calls and CPU skinning are. Skinning is CPU-side, so the whole skinned buffer re-uploads every frame. The Knight at 3716 verts was 238 KB/frame at 64 B/vertex, and 89 KB at the packed 24 B.
  • Vertex attributes here are f32-only — no u8/u16/i16 attribute types. Compression means bit-packing into f32 lanes and unpacking in-shader with the unpack2f16 / unpack4u8 builtins (available on Metal/GLSL/HLSL/WGSL).
  • geom.GameMeshVertex is the shared packed layout: position 3 exact floats, octahedral normal in 1 lane, uv as 2×f16 in 1 lane, colour as 4×unorm8 in 1 lane = 6 floats / 24 B. Use it.
  • Quest is bandwidth-bound before ALU-bound. Cut bytes per frame first, shader instructions second.
  • Measure from RenderStats::instance_floats, not by counting fields by hand.

Order of visual return

When time is limited, this order gives the most visible improvement per hour:

  1. Key light direction + colour (low sun)
  2. Contact shadows / baked AO
  3. Palette discipline and tint variety
  4. Sky/fog agreement at the horizon
  5. Silhouette clarity of the hero
  6. Ground detail — texture or geometry, not flat colour
  7. Impact VFX on every hit
  8. Post look and grading

Verification

Screenshot every state at target aspect ratio and look at them. Score with references/visual-scorecard.md: premium means nothing below 2 and an average ≥ 2.3. Read frame_p90_ms() before and after any performance claim. cargo run -p makepad-arcade --example bigworld_probe --release gives headless render evidence.

Required reading

  • references/visual-scorecard.md — the 10 categories, anchors, and how to score.
  • references/shader-cookbook.md — DSL overrides, packing, the builtins.
  • references/lighting-and-materials.md — sun, ambient, fog, AO, tint variety.
  • references/render-budget.md — what costs, how to measure, what to cut first.