# The ground of your own scene (world.scene_ground) ## What it is for The character kits (`character.first_person`, `character.third_person`), NPCs, navigation and placement ask `world.ground` where the ground is. The world generators serve it with the world they build. When your game brings its own terrain or level (an engine heightmap, a level mesh, floors and platforms), `world.scene_ground` serves `world.ground` from those colliders instead: the height at a point is the first static collider a ray cast straight down meets through the physics port. It generates and draws nothing, so you no longer install a world generator you do not use. ## Turn it on ```text gripforge_gamekit_install { project_id: "gkp_…", id: "world.scene_ground" } gripforge_gamekit_configure { project_id: "gkp_…", id: "world.scene_ground", config: { "bounds": [-3000, -2500, 3000, 2500], "rayTop": 800, "rayLength": 1200 } } ``` It requires `game.engine`, `physics.port` and the physics of the host. Remove the other provider of `world.ground` (a world generator) from the same scene: two providers are reported as an overlap. ## First example With a relief mesh loaded by the game and the config ```json { "cellSize": 1, "tileCells": 8, "edgeTolerance": 2 } ``` the first `groundY(3, 4)` casts its own ray and answers the height of the mesh there; it also asks for the tile around the point. One tick later the tile is filled, and every `groundY` inside it is read from the grid, within a centimetre of what a ray gives. Off the mesh, `groundY` answers `null`. ## Settings and defaults | Setting | Type | Default | Meaning | |---|---|---|---| | `bounds` | `[minX, minZ, maxX, maxZ]` | `[-1024, -1024, 1024, 1024]` | limits of the world in metres, read by `bounds()`, `extent()` and `inside()`; `groundY` answers outside them too | | `rayTop` | number | `1000` | height the downward rays start from: above everything that may be ground | | `rayLength` | number | `2000` | length of a ray; nothing below `rayTop − rayLength` is ground | | `spawn` | `[x, y, z]` | `[0, 0, 0]` | where the player starts | | `spawnYaw` | number | `0` | direction the player faces, in radians | | `spawnOnGround` | boolean | `true` | put the spawn on the ground under it | | `cache` | boolean | `true` | answer from the height grid where it is known; off: one ray per call | | `cellSize` | number 0.1 to 16 | `1` | metres between two height samples; match your terrain's resolution | | `tileCells` | integer 2 to 64 | `16` | cells along a tile edge | | `maxTiles` | integer | `1024` | tiles kept; the least recently read go first | | `edgeTolerance` | number | `0.5` | above this height difference inside a cell, the point casts its own ray (a step, a wall) | | `raysPerTick` | integer | `1024` | rays per game tick to fill tiles | | `prefetchRadius` | number | `32` | tiles filled ahead around the player, in metres | | `maxSkips` | integer 0 to 16 | `4` | characters, moving bodies and ignored colliders a ray goes through | | `ignore` | list of strings | `[]` | prefixes of entity ids that are never ground (roofs, foliage, triggers) | | `invalidateOn` | list of topics | `[]` | topics after which the whole cache is dropped | | `watchColliders` | boolean | `true` | drop the tiles under a registered static collider that came, went or moved | | `watchEvery` | integer | `10` | ticks between two looks at the colliders | The first static collider from the top is the ground: a roof or a bridge over a point is its ground unless its id is in `ignore`. A tile costs `(tileCells + 1)²` rays once and about 1 KiB of memory (16 cells). ## Services, events - `world.ground` (service): `groundY(x, z)` returns the height under the point or `null`; `bounds()` returns `{ min, max }` (y from `rayTop − rayLength` to `rayTop`); `extent()` the larger side; `inside(p)`; `nearestRoadPoint(p)` returns the point on the ground (there are no roads); `defaultSpawn()` returns `{ position, yaw }`. - `world.scene_ground` (service): `cast(x, z)` casts one ray now, the cache bypassed; `invalidate(area?)` drops the tiles over `[minX, minZ, maxX, maxZ]` or `{ at, radius }`, or all of them, and returns how many; `stats()` returns `tiles`, `filled`, `queued`, `inFlight`, `bytes`, `calls`, `cached`, `exact`, `rays`, `dropped`, `moving`, `colliders`. - `world.ready` (event): once at mount, with the `bounds`. - `ground.changed` (event): cached heights were dropped, with `reason` (`colliders`, `engine.scene.entered`, `invalidate` or the topic), `area` (or `null` for all) and `tiles`. When your game changes its own geometry in a way the physics port does not see (an engine terrain edited in place), emit one of the `invalidateOn` topics or call `invalidate` with the area. ## Hooks and modding The hook `ground.spawn` is asked once per scene, before `defaultSpawn()` answers, with `{ position, yaw }` (the position already on the ground). Both fields are mutable; it is not cancellable. ```json { "id": "north_start", "hook": "ground.spawn", "set": { "yaw": 3.14 } } ``` A scene whose world names `world.scene_ground` sets `bounds`, `spawn` and `spawnYaw` in its `world.config`. Every scene change drops the cache. Most settings are moddable; the kit has no collection and saves nothing. ## Engines One shared implementation: the web runs it as a kit module, Godot, Unity and Unreal in the native kit library, where the rays are the engine's own, so the colliders of the scene your game loaded itself are the ground. ## When it does not do what you expect - `groundY` is `null`: nothing static is under the point between `rayTop` and `rayTop − rayLength`, or only ignored entities, characters and moving bodies (more than `maxSkips`). - The ground is the top of a roof: add its id prefix to `ignore`. - A slope looks smoothed near an edge: lower `edgeTolerance` or `cellSize`. - The height is stale after the game changed its terrain: emit an `invalidateOn` topic or call `invalidate`. - `null` right after a teleport with the physics on its own thread: the tile fills on the next ticks. ## Limits One height per point (the first static collider from the top); no water, roads or materials; colliders the game changes outside the physics port are not seen until it says so. ## Versions Kit `world.scene_ground` 1.0.0, requires `game.engine@^1`, `physics.port@^1` and `physics.rigidbody@^1`.