# Scenery for your ground (world.scenery) ## What it is for `world.scenery` places Library models over the ground of a game: clumps of trees, rocks, bushes and flowers, walls of cliffs lining the routes, and one fitted piece in each box of a built level. The placement is a pure function of a seed, so the same seed gives the same pieces at the same spots. The kit only computes placements; the renderer draws each layer with instancing, so hundreds of trees cost a few draw calls. A layer is placed only when its slot has a model bound. It keeps off three things: the routes of `lanes.routes` (a kit such as `moba.lanes`), the clearings of `world.clearings` (`{ at: [x, z], radius }`: bases, camps, buildings), and the ground of `keep`. It reads the bounds of `world.ground`. ## Turn it on In `gripforge.game`, install the kit after `world.ground`: ```json { "id": "world.scenery", "config": { "seed": 12, "density": 1.3 } } ``` It requires `world.ground` and `game.engine`. Bind models to its slots (`scenery_tree`, `scenery_rock`, `scenery_bush`, `scenery_flower`, `scenery_cliff`, `scenery_architecture`, `scenery_landmark`); several models on one slot (or variants such as `scenery_tree:oak`) are mixed. A slot with no model places nothing, so a game can start with trees only. ## First example A 120 m ground, one route along X from -50 to 50, one clearing of radius 10 at `[0, 30]`, and two layers: ```json { "slot": "cliff", "mode": "line", "height": 5, "clearance": 11, "gap": 5.2 } { "slot": "tree", "mode": "scatter", "count": 40, "height": 7, "clearance": 9, "gap": 3.2 } ``` The same seed returns the same list. The cliffs stand exactly 11 m from the route on both sides, the 40 trees stay at least 9 m from it and out of the clearing, and another seed gives another forest. ## Settings and defaults | Setting | Type | Default | Meaning | |---|---|---|---| | `seed` | integer 0-999999 | `7` | Seed of the placement. | | `density` | number 0-4 | `1` | Multiplier of every scatter count. | | `layers` | array (max 48) | cliffs, trees, rocks, bushes, flowers | What to place. | | `keep` | array (max 800) | none | Ground kept free of scatter and line pieces. | | `coverage` | number 0-1 | `1` | Share of the pieces shown at start. | A layer is `{ slot, mode, height, clearance, gap, count?, cluster?, vary?, openings?, volumes? }`: - `mode: "scatter"`: `count` pieces in clumps (`cluster` 0-1), at least `gap` metres apart. - `mode: "line"`: one piece every `gap` metres on both sides of every route, `clearance` metres from it, turned along the route; `openings` (0-0.9) leaves a share out, for paths through the wall. - `mode: "volumes"`: one piece in each box of `volumes` (`{ at, size, yaw, y?, colliders? }`), scaled uniformly to stay inside its box. `colliders` are optional solid boxes relative to the volume base; omitted, the piece is decorative. - `height` is the height in metres a model is brought to. A `keep` entry is a disc (`{ at, radius }`) or a box (`{ at, size: [width, depth], yaw }`). A scene of `game.engine` may set `seed`, `density`, `layers` and `keep` in `layers["world.scenery"]`; the placements are recomputed on entry. ## Services, events, hooks - `world.scenery` (service): `placements()` lists every piece shown as `{ slot, index, x, z, yaw, height }`; `layers()` lists the layers in use; `coverage(share, slot)` sets the share (0-1) of pieces shown, of every layer or of one slot, and returns it. - `world.scenery.settings` (service): `keys()`, `get(key)`, `set(key, value)` for the player settings `coverage` and `coverage.` (0-1), saved with the other settings. - `scenery.changed` (event): `{ count }`, emitted when the share shown changed; the renderer draws `placements()` again. - `scenery.cover` (hook): `{ slot, from, share }` before the share changes. Mutable `share`; cancellable (the share stays, nothing is emitted). `slot` is empty for every layer. Loading a save does not ask. Each piece has a fixed rank: lowering the coverage hides pieces and raising it brings the same ones back in the same order, so a forest can thin out and grow back. The shares are saved once one has moved. ## Hooks and modding A game or a mod may override `seed`, `density`, `layers`, `keep` and `coverage`, and the live settings `coverage` and `coverage.*`. A hook that never lets the forest fall under a third: ```json { "id": "forest_survives", "hook": "scenery.cover", "if": [{ "field": "share", "op": "<", "value": 0.33 }], "set": { "share": 0.33 } } ``` ## Engines The placement is the same Rust logic on the web and, through the kit library, in the native player, Godot and Unity. ## When it does not do what you expect - Nothing appears for a layer: its slot has no model bound. - Pieces stand on a path: the route is not published by a kit such as `moba.lanes`, or `clearance` is too small. - The forest changes between runs: the `seed` changed; keep it fixed. - Collisions are missing: `colliders` are only used when a volume carries them, and hidden or unbound pieces have no body. ## Limits It does not draw anything, and placement is not terrain editing. Collision boxes are conservative bounds of the parts, not triangle-exact, unless a volume gives indexed meshes. ## Versions Kit 1.9.0. It provides `world.scenery` 1.1.0 and relies on `world.ground@^1` and `game.engine@^1`.