# iamar world recipes

How to engineer an iamar world that looks good and runs smoothly on every visitor's GPU.
Each recipe says what it is for, when to use it, the steps, the defaults, how the
kit checks it and where it comes from. `techniques.json` beside this file is the
source of truth (this page is generated from it). The browser applies recipes
marked *automatic*. Builders can apply *manual* recipes with today's format/tools.
A *planned* recipe has no supported package field or tool yet: skip it, report it
as unavailable when relevant, and never invent a field. Planned recipes do not
block validation, signing, or completion.

## Geometry

### Distance LOD chain  (`lod_chain`, kit: automatic)

Far objects use much lower-poly versions, so the triangle budget goes to what is near.

**When:** Every model larger than a crate, and every merged area mesh.

1. Model the full-detail version in Blender.
2. Make LODs at about 50% and 20% of the triangles (Decimate, planar for buildings, collapse for organic shapes).
3. Far away, swap to an impostor card (see impostor).
4. Let the engine pick by screen size.

**Defaults:** `lod_ratios` = [1.0, 0.5, 0.2], `lod_threshold_pixels` = 4.0

**Check:** perf: triangles per view in the busiest and the widest views.

**Cost:** More disk and memory per model; none at run time.

**From:** oakhaven b22d441 (distance LODs for merged props), project.godot mesh_lod threshold_pixels=4 (higher visibly breaks baked buildings); the browser applies it to every world (scripts/kit/world_techniques.gd, off with manifest techniques: {"lod_chain": false})

### Impostor cards far away  (`impostor`, kit: manual)

Distant trees, buildings and mountains become a flat textured card: a few triangles instead of thousands.

**When:** Anything regularly seen beyond about 80-150 m.

1. Give the far object "impostor": <metres> in scene.json (about 80-150).
2. Run `iamar-world impostors <folder>` (renders 8 views of the model into a small atlas; needs a display), then `iamar-world tiers` so lower tiers get smaller atlases.
3. The browser swaps model and card at that distance; many cards of one model are drawn together.

**Defaults:** `start_m` = 120

**Check:** preview the widest view; perf triangles drop.

**Cost:** A texture per model.

**From:** docs/WORLD_TIERS.md (impostor cards), oakhaven 802210a/906af53

### Merge static meshes per area  (`merge_static`, kit: automatic)

Many small props become one mesh per area and material: far fewer draw calls.

**When:** Props that never move: fences, crates, barrels, shelves, small furniture.

1. Nothing to do in a world package: the browser merges static pieces that look the same (same material colour, texture and settings) per 96 m area into one mesh with its own LOD chain (scripts/kit/world_techniques.gd, off with manifest techniques: {"merge_static": false}).
2. Help it: give props that belong together the same material (one shared colour atlas) instead of a material per model.
3. Impostor cards and custom shaders keep their own draw.

**Defaults:** `cell_m` = 96

**Check:** perf: draw calls per view under budget.

**Cost:** One bigger mesh per area; culling is coarser.

**From:** oakhaven scripts/world/prop_batch.gd (PropBatch, keeps per-instance transforms for the bake export), 'Fewer prop draw calls'; world packages: oakhaven lakeside village 446 -> 126 draw calls

### Instance repeated props  (`instancing`, kit: automatic)

One mesh drawn many times in a single call (MultiMesh): grass, flowers, rocks, trees.

**When:** The same model placed more than about 10 times.

1. Place one model many times with an instance list instead of copies.

**Check:** perf: draw calls.

**Cost:** Instances share one LOD choice per batch.

**From:** oakhaven scripts/world/prop_batch.gd MultiMesh; the browser applies it to every world (scripts/kit/world_techniques.gd, off with manifest techniques: {"instancing": false})

### Thin far foliage  (`foliage_thinning`, kit: automatic)

Grass and flowers fade out with distance; trees are split into cells so culling works.

**When:** Any ground cover or forest.

1. Split foliage into cells (e.g. 30-45 m).
2. Reduce density with distance and stop at a visibility range.

**Defaults:** `visibility_end_m` = 90, `margin_m` = 8

**Check:** perf in open views.

**Cost:** Far ground looks a little barer.

**From:** oakhaven b22d441 (thinned far foliage, split tree cells), prop_batch.gd visibility_range_end; the browser applies it to every world (scripts/kit/world_techniques.gd, off with manifest techniques: {"foliage_thinning": false})

### Simple collision shapes  (`collision_proxy`, kit: manual)

Physics uses boxes and capsules, not the visual triangles.

**When:** Everything except floors and stairs people walk on.

1. Use collision: box for houses you do not enter, rocks, crates.
2. Use mesh only where people walk.
3. Props are obstacles, not ground.

**Check:** validate warns about mesh collision on big decorative models.

**Cost:** None.

**From:** docs/WORLD_FORMAT.md 4.4 collision; oakhaven 'props are obstacles, not ground'

## Visibility

### Occluders on big walls and buildings  (`occluders`, kit: planned)

Things hidden behind a castle wall or a hall are not drawn at all.

**When:** Large solid walls, buildings and hills between areas.

1. Add simple occluder boxes inside big solid shapes.
2. Turn occlusion culling on.

**Check:** perf: draw calls behind walls drop.

**Cost:** A little CPU per frame.

**From:** oakhaven b22d441 (castle and garden occluders), project use_occlusion_culling=true

### No live shadows on tiny props  (`tiny_no_shadow`, kit: automatic)

Small props do not cast real-time shadows (the bake already darkens under them).

**When:** Props smaller than about 1 m.

1. Set cast_shadow false on small props.

**Defaults:** `max_size_m` = 1.0

**Check:** perf: shadow draw calls.

**Cost:** None visible.

**From:** oakhaven b22d441; the browser applies it to every world (scripts/kit/world_techniques.gd, off with manifest techniques: {"tiny_no_shadow": false})

### A pool of real lights  (`light_pool`, kit: automatic)

Only the few lamps nearest the camera get a real light; the rest glow through their material.

**When:** Towns and halls with many lamps.

1. Register every lamp with its position.
2. Each moment, give the nearest few a real light.
3. Place each light from its fixture's transform (never a separately typed position).

**Defaults:** `real_lights` = 6

**Check:** night screenshots: glows sit in the fixtures.

**Cost:** Far lamps light nothing around them (until night lamps are baked).

**From:** oakhaven scripts/world/light_pool.gd (POOL=6), d5a0e98 Buildings.light_at; the browser applies it to every world (scripts/kit/world_techniques.gd, off with manifest techniques: {"light_pool": false})

## Lighting

### Blender Cycles light bake (sky and sun)  (`bake_sky_sun`, kit: manual)

Sky light and sunlight with soft shadows are baked into a texture, so static glTF scenery is lit for free without double-lighting.

**When:** Static architecture and big props that you author in Blender; primitives and reused assets stay live-lit.

1. Generate a second, non-overlapping lightmap UV set and cap the image size.
2. Bake the combined sky and sun lighting into an image in Blender Cycles.
3. Feed the baked image to an Emission or Background material before glTF export so Blender writes KHR_materials_unlit; the browser then shows the bake once instead of lighting it twice.
4. Keep moving characters, creatures, doors and flames on ordinary Principled materials.
5. Bake interiors separately from shells, and rebake after any geometry change.

**Defaults:** `texel_m` = 0.1, `max_size` = 4096

**Check:** preview the static model from lit and shadowed sides; confirm the bake is visible once and no surface is black.

**Cost:** Bake time and one lightmap texture per object.

**From:** kit/AGENT_KIT.md baked-lighting gotcha and Blender glTF KHR_materials_unlit

### Baked ambient occlusion in vertex colours  (`bake_ao_vertex`, kit: planned)

Characters, animals and props get soft contact darkness baked in, so they sit in the world.

**When:** Anything that moves (so it cannot use a lightmap) and small props.

1. Cast hemisphere rays per vertex (on worker threads) and store the result in vertex colour.

**Check:** preview: no floating look.

**Cost:** A short bake at load or build time.

**From:** oakhaven AnimalKit (hemisphere-ray AO, ~0.53 s for all kinds), dragon

### Keep lightmaps lossless  (`lightmaps_lossless`, kit: planned)

Lightmaps are not VRAM-compressed: compression pads them to a multiple of 4 and shifts the light.

**When:** Every lightmap.

1. Import lightmap PNGs with lossless compression.

**Check:** screenshot diff after import changes.

**Cost:** More VRAM than compressed (BC7 lightmaps need padding plus a UV scale: graphics list).

**From:** oakhaven 00b844a perf pass

### Live light only for what moves  (`live_only_moving`, kit: planned)

Characters get live shadows only where the bake has sun; lamps stay live via the light pool.

**When:** Always.

1. Use the bake for static things; live light and shadow only for characters and moving objects.

**Check:** perf: shadow cost.

**Cost:** None.

**From:** oakhaven bake.gd notes

### Bevel and normal maps from high-poly  (`bevel_normals`, kit: planned)

Bevels and fine detail are baked from a high-poly version into normal maps, so low-poly meshes read as carved.

**When:** Buildings, furniture, weapons.

1. Model or bevel a high-poly version.
2. Bake its normals onto the game mesh in Blender.

**Check:** preview close-ups.

**Cost:** A normal map per material.

**From:** Graphics list item 3 (planned); kit/blender/starter_build.py uses real bevels meanwhile

## Materials

### One shared wind  (`shared_wind`, kit: planned)

Grass, flowers, trees and flags all sway from one wind value: one uniform, no per-object scripts.

**When:** Any vegetation or cloth.

1. Drive vertex-shader sway from one global wind value.

**Check:** perf: no per-frame scripts for sway.

**Cost:** None.

**From:** oakhaven 'One shared wind for grass, flowers, trees and flags'

### Time-of-day palettes, haze and weather  (`atmosphere`, kit: manual)

Sky, fog, haze and glow follow the time of day; rain and storms; wetness as one shader parameter.

**When:** Outdoor worlds.

1. Use the browser's environment settings (scene.environment) rather than custom per-object effects.

**Check:** preview at several hours.

**Cost:** Small.

**From:** oakhaven 'Atmosphere and weather', Mats.set_wetness

## Textures

### VRAM texture compression and mipmaps  (`texture_compression`, kit: planned)

Textures are BC7 (desktop) and ETC2/ASTC (Apple Silicon) with mipmaps: about half the video memory.

**When:** Every texture except lightmaps.

1. Import with VRAM compression (high quality) and mipmaps on; enable ETC2/ASTC import for macOS.

**Check:** perf: VRAM in the busiest view.

**Cost:** More disk than lossless.

**From:** oakhaven 00b844a (457 -> 250 MB on Amber), 887bd83 (ETC2/ASTC)

### Right-sized textures and atlases  (`texture_sizes`, kit: manual)

Small textures for small or far things, atlases to share one material across many props.

**When:** Every material.

1. Pick the smallest size that looks right at the closest normal view.
2. Share atlases between props in one area.

**Check:** validate: texture budget.

**Cost:** Authoring time.

**From:** WORLD_FORMAT budgets

### Converted-model cache  (`gltf_cache`, kit: automatic)

Converted glTF scenes are cached, so a second visit opens faster.

**When:** Automatic in the browser.

1. Nothing to do as a builder.

**Cost:** Disk cache.

**From:** oakhaven scripts/worlds/world_cache.gd

## Loading

### Stream terrain in chunks  (`streaming`, kit: planned)

Only the terrain near the player is built and refined.

**When:** Worlds larger than a few hundred metres.

1. Split terrain into chunks and build near ones first.

**Defaults:** `chunk_m` = 90

**Check:** frame-time metrics while walking.

**Cost:** Code complexity.

**From:** oakhaven scripts/world/terrain.gd CHUNK=90

### Build progressively, in time slices  (`progressive_build`, kit: planned)

Heavy building work runs in small slices across frames (and on worker threads) so the game never freezes.

**When:** Big worlds and any build step over a few milliseconds.

1. Split work into slices of about 10 ms per frame.
2. Commit meshes on worker threads.
3. Hide the first heavy frames behind the world-switch fade and progress bar.

**Defaults:** `slice_ms` = 10

**Check:** perf: no frame over 50 ms after load.

**Cost:** Things appear over a few seconds.

**From:** oakhaven town.gd slice_usec=10000, 'take the load stutters off the main thread', 00b844a progress bar

### Physics interpolation  (`physics_interp`, kit: automatic)

Movement is smooth at any frame rate.

**When:** Always.

1. Keep physics interpolation on (project setting).

**Cost:** None.

**From:** oakhaven project.godot physics_interpolation=true

## Quality

### Quality presets and auto-tune  (`quality_presets`, kit: manual)

Fast / Balanced / higher presets with a first-run auto-tune; worlds must look pleasant on the lowest.

**When:** Every world.

1. Design for the low tier first, then add detail that lights up on better GPUs.

**Check:** perf on Amber-class hardware (HD 5750, 1 GB) at 30 fps.

**Cost:** Testing several tiers.

**From:** oakhaven presets and _auto_tune; GPU tiers item (planned)

## Feel

### Responsive movement  (`movement_feel`, kit: automatic)

Jump grace (coyote time and a jump buffer), step-up only on steep collisions, floor snap, an eased camera that never clips into walls and fits rooms indoors.

**When:** Built into the browser; keep walkable slopes and doorways reasonable.

1. Keep doorways at least 2.2 m high, stairs shallow, walkable slopes under 40 degrees.

**Defaults:** `jump_grace_s` = 0.15, `floor_snap_m` = 0.9

**Check:** walk every path and portal.

**Cost:** None.

**From:** oakhaven player.gd JUMP_GRACE, floor_snap_length, c17e70d

## Network

### Lean networking  (`network`, kit: automatic)

Fast unreliable 15 Hz state, reliable events, Opus voice by distance, rarest-first parallel piece download, peers sharing pieces, per-peer rate limits.

**When:** Built into the browser.

1. Keep packages small so first visits are fast; nothing to do in world data.

**Cost:** None.

**From:** oakhaven scripts/net/*, scripts/shell/world_fetcher.gd, world_swarm.gd

## Measurement

### Measure on the low-end machine  (`measure`, kit: manual)

Every change is measured before and after on Amber-class hardware, with fixed-view screenshots compared.

**When:** Always.

1. Run perf in the busiest view and the widest view.
2. Compare fixed-view screenshots before and after.
3. Report the numbers.

**Check:** perf, preview.

**Cost:** Time.

**From:** oakhaven tools/perf_probe.gd, autotest frame-time metrics

## Art

### Cloud shadows  (`cloud_shadows`, kit: planned)

Slow-scrolling cloud shadows drift over the land: one texture sample in the terrain and baked shaders, big sense of life.

**When:** Outdoor worlds.

1. Enable the browser's cloud-shadow layer in scene.environment (density, speed, scale).

**Defaults:** `density` = 0.4, `speed_m_s` = 3.0

**Check:** preview over 10 s.

**Cost:** One texture sample.

**From:** Graphics ideas 2026-10-01 #1

### Terrain texture blending by slope and height  (`terrain_blend`, kit: planned)

Grass, dirt, rock and gravel chosen by slope and height with soft height-blended edges; triplanar rock on cliffs so nothing stretches.

**When:** Any terrain.

1. Assign terrain layers with slope and height ranges.
2. Use triplanar mapping on steep faces.

**Check:** preview cliffs and hills.

**Cost:** A few texture samples.

**From:** Graphics ideas #2

### Ground clutter  (`ground_clutter`, kit: planned)

Pebbles, fallen leaves, clover and mushrooms near paths and trees, instanced with LODs.

**When:** Wherever people walk outdoors.

1. Scatter small instanced props by rules (near paths, under trees, by water).
2. Thin them with distance.

**Defaults:** `visibility_end_m` = 40

**Check:** perf: draw calls stay in budget.

**Cost:** Instanced; small.

**From:** Graphics ideas #3

### Worn paths  (`path_wear`, kit: planned)

Darker trampled edges, cart ruts and puddle hollows along roads, painted into vertex colour or a terrain mask.

**When:** Roads and busy paths.

1. Paint wear along path splines into the terrain mask.

**Check:** preview roads.

**Cost:** Nearly free.

**From:** Graphics ideas #4

### Anisotropic filtering  (`anisotropic`, kit: automatic)

Ground textures stay sharp at grazing angles instead of blurring toward the horizon.

**When:** Ground, roads, floors.

1. Import ground textures with anisotropic filtering (4x-8x).

**Defaults:** `level` = 8

**Check:** preview long roads.

**Cost:** Nearly free.

**From:** Graphics ideas #5

### Per-instance colour variation  (`instance_tint`, kit: planned)

Every tree, bush, rock, roof and wall gets a slight random tint and brightness shift so copies stop looking copied.

**When:** Every repeated model.

1. Give each placement a small random tint (hue +-4%, value +-8%) through instance colour.

**Defaults:** `hue` = 0.04, `value` = 0.08

**Check:** preview a forest and a street.

**Cost:** Free.

**From:** Graphics ideas #6

### Smooth LOD transitions  (`lod_crossfade`, kit: planned)

A dithered crossfade between LOD levels, so trees and buildings stop visibly popping.

**When:** Every LOD chain.

1. Enable dithered LOD fade on LOD meshes.

**Defaults:** `fade_m` = 4.0

**Check:** walk toward a forest.

**Cost:** A little fill rate during the fade.

**From:** Graphics ideas #7

### Backlit leaves  (`leaf_translucency`, kit: planned)

Leaves and grass glow softly when the sun is behind them (wrap and transmission light in the foliage shader).

**When:** All foliage.

1. Use the foliage material with translucency on.

**Defaults:** `translucency` = 0.35

**Check:** preview against the low sun.

**Cost:** A few shader instructions.

**From:** Graphics ideas #8

### Grass that parts  (`grass_interaction`, kit: automatic)

Grass and flowers bend away from the player, townsfolk and horses as they walk through.

**When:** Grassy areas.

1. Nothing to do as a builder once the browser feature exists; use the standard grass material.

**Check:** walk through grass.

**Cost:** Small.

**From:** Graphics ideas #9

### Feet that follow the ground  (`foot_ik`, kit: automatic)

Foot IK plants feet on slopes and stairs instead of floating or sinking.

**When:** All characters (browser feature).

1. Keep stairs and slopes as real collision so feet can find them.

**Check:** walk up stairs and hills.

**Cost:** Small CPU per nearby character.

**From:** Graphics ideas #10

### Characters look at each other  (`look_at`, kit: automatic)

Heads turn toward nearby players and whoever they are talking to.

**When:** NPCs and players (browser feature).

1. Give NPCs a look_at radius if they should notice visitors.

**Defaults:** `radius_m` = 6

**Check:** talk to an NPC.

**Cost:** Tiny.

**From:** Graphics ideas #11

### Lively faces  (`faces`, kit: manual)

Eye highlights, blinking and varied idle animations so crowds do not move in sync.

**When:** All characters.

1. Use the built-in bodies, or give custom characters eye highlights and several idles.

**Check:** preview NPC close-ups.

**Cost:** Tiny.

**From:** Graphics ideas #12

### Soft ground shadows at distance  (`blob_shadows`, kit: automatic)

A simple soft shadow under every character and animal far away, where live shadows are off.

**When:** All characters and animals (browser feature).

1. Nothing to do for built-in characters; custom creatures declare a shadow radius.

**Check:** preview a crowd at distance.

**Cost:** One decal-like quad each.

**From:** Graphics ideas #13

### Lit windows at night  (`lit_windows`, kit: planned)

Windows glow warmly from inside, some flicker like candlelight, turning on and off through the evening (emissive only, no real lights).

**When:** Every building with windows.

1. Give window panes the window-glow material (it follows the time of day).

**Check:** night preview.

**Cost:** Free (emissive).

**From:** Graphics ideas #14

### Lived-in interiors  (`interior_dressing`, kit: manual)

Clutter, rugs, shelves, hanging herbs and dust motes in sunbeams, so insides feel lived in.

**When:** Every interior people can enter.

1. Dress each room with merged clutter props and a few motes in light shafts.

**Check:** preview every room.

**Cost:** Merged props; small.

**From:** Graphics ideas #15

### Ambient creatures  (`ambient_creatures`, kit: planned)

Flocks of birds crossing the sky, crows on roofs, fish jumping, a few cats and dogs, laundry blowing in the wind.

**When:** Outdoor and town worlds.

1. Declare ambient life in scene.json (birds, fish, pets) with counts per area.

**Check:** preview over a minute.

**Cost:** A few cheap animated meshes.

**From:** Graphics ideas #16
