# Lighting and materials ## The three-part model **Key (sun).** `SunConfig`, `sun.rs`, `DrawGameSky`. Direction and colour. Elevation 15–25° gives long shadows and modelled forms; noon light flattens everything and is what an unconsidered scene looks like. Commit to a time of day. **Ambient (hemispheric).** `sun_sky` above, `sun_ground` bounce below, interpolated by the normal's Y. Grey ambient is the flatness default. Colour the two ends: a warm key with cool sky ambient (or the reverse at sunset) is most of the distance between "rendered" and "lit". **Fog.** `fog_color`, `fog_density`. Provides depth ordering and aerial perspective. **Fog colour must agree with the sky at the horizon**, or distant geometry sits on a visibly different backdrop. All four are shader **uniforms**, not instance data (see `shader-cookbook.md`). `fog_density` is the one per-instance exception, because shadow draws switch it off individually. ## Consistency `light_dir` must be identical across `draw_cube`, `draw_alpha`, `draw_terrain`, `draw_skinned`, `draw_models`. Set them together in one `script_mod!` block. Inconsistent light direction is the most-missed defect in this engine because each surface looks fine alone. ## Shadows `shadow.rs`, `shadow_mesh.rs`, `set_shadow_budget(n)`, `shadow_budget()`. Shadow meshes use the same 6-float packed vertex. Budget by importance: 1. The player — always. 2. Anything moving within the player's immediate area. 3. Tall objects whose silhouette defines the space. 4. Everything else — baked AO instead. Shadowing everything kills the frame; shadowing nothing makes props float. Adaptive quality lowers the budget automatically under load — read `quality_reason()` before concluding your shadows "stopped working". ## Baked AO `bake.rs`, `ao.rs`, `BakeSettings`, `bake_settings()` / `set_bake_settings()`, `dynamic_shade(pos)`. **Contact darkening is the cheapest large upgrade to perceived quality.** It is specifically what stops props reading as stickers laid on the ground. Bake it for static props; use `dynamic_shade` for moving ones. The `ao_render` dev example software-rasterises props so baked AO can be judged by eye without a `Cx` or the app — use it, because AO is a judgement call that a number does not settle. That example is why the crate carries a `jpeg-encoder` dev-dependency and no more. ## Materials without a PBR pipeline There is no material graph here. Differentiation comes from four levers: **Tint.** `ModelInstance::with_tint(vec4)`. Drive from `Palette` / `Spread` / `VarietyParams` in `makepad-game-assets`, or `find_palette` on the script route. Five palette-consistent tints across a crowd reads as authored; one tint reads as placeholder. **Texture.** `with_texture(idx)` and `DrawGameTexture`. Generate tiling detail with `makepad-game-gen::texgen` when the library has nothing — procedural texture is free of download weight and is deterministic from a seed. **Jitter.** Seeded, small: ±10% scale, full yaw rotation, slight position offset. This destroys the grid-stamped look at zero cost. Seeded, so it replicates. **Cast variety.** For characters, use different members of ONE cast (`find_cast`) — same skeleton, so animation is shared, but the crowd is not clones. ## Palette Three dominant + two accent. Rules that hold across genres: - **Reserve accents for gameplay meaning.** If pickups are the only saturated orange in the game, the player learns orange without being told. - **Separate by value, not only hue.** A screenshot converted to greyscale should still show foreground, midground, background as distinct bands. This is the test most scenes fail. - **Desaturate with distance** toward the fog colour — aerial perspective, free depth. - The CC0 library is shared, so out-of-the-box colours look like every other Kenney project. Tint discipline is what makes it yours. ## Skinned characters `skin.rs` — `SkinnedModel`, `PoseBuffer`, `SkinnedBatch`, `SkinnedDraw`. Skinning is **CPU-side**, so the whole skinned vertex buffer re-uploads every frame. That makes skinned characters the most expensive visual thing in the scene by bandwidth: the Knight is 3716 verts, 238 KB/frame unpacked, 89 KB packed. Consequences: - Cap the number of on-screen skinned characters and prove the cap holds in the worst wave. - Distant characters do not need full skinning — swap to a static or lower-rate pose. - Share one cast per scene so one animation set serves everyone. ## Checklist before scoring - [ ] Sun elevation committed and not noon - [ ] `light_dir` identical across every draw type - [ ] Sky ambient and ground bounce coloured, not grey - [ ] Fog colour matches the sky at the horizon - [ ] Player always casts a shadow - [ ] Contact AO under every static prop - [ ] No two adjacent props identical in tint, scale and yaw - [ ] Greyscale screenshot still separates fore/mid/background - [ ] Accent colour reserved for gameplay-relevant objects