Scenery

Dress a ground with trees, rocks, bushes and cliff walls placed from a seed, away from routes and clearings: layers, keep-out zones, live coverage, the scenery.cover hook.

Source: module-kits:kits/world_scenery/usage/en.mdView Markdown source

Also read offline — CLI: grip-forge docs world-sceneryMCP tool: gripforge_docs { "topic": "world-scenery" }

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:

{ "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 #

<!-- test: kits/game_engine/host/tests/world_scenery.rs::scenery_keeps_off_the_routes_and_the_clearings --> A 120 m ground, one route along X from -50 to 50, one clearing of radius 10 at [0, 30], and two layers:

{ "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 #

SettingTypeDefaultMeaning
seedinteger 0-9999997Seed of the placement.
densitynumber 0-41Multiplier of every scatter count.
layersarray (max 48)cliffs, trees, rocks, bushes, flowersWhat to place.
keeparray (max 800)noneGround kept free of scatter and line pieces.
coveragenumber 0-11Share 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.<slot> (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:

{ "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.