# Mud and blood patches in your world (world.fluids) Small patches of mud or blood that remember who walked or drove through them. Readable offline: `gripforge docs world-fluids`, or the MCP tool `gripforge_docs` (topic `world-fluids`). The other environment kits (light, weather, water, grass) are in [the environment guide](guide:world-environment). ## What it is for `world.fluids` simulates a height field per patch. A foot or a wheel pushes the surface down and piles a rim beside the print; the surface then slowly relaxes. The patch is drawn as a mesh whose heights follow the simulation (ruts, rims, wet shine). It is a local surface effect, not a volumetric fluid: the existing ground stays the collision surface, and nothing sinks or flows. Blood is not spawned by combat hits: the game places a `blood` patch where it wants one. ## Turn it on Install the kit and add patches as `fluid_surfaces` documents (in the project, or in a scene's data): ```json { "id": "world.fluids", "config": {} } { "fluid_surfaces": [{ "id": "bog", "position": [0, 0, 0], "size": [10, 8], "resolution": 64 }] } ``` It requires `game.engine`. Do not install it with `world.tracks` in the same scene: they conflict (the same-scene overlap is reported). ## First example A player walks along a patch named `bog`. The patch is active (it is near the player), the walker leaves prints, and the save remembers that the player stands in `bog`. ```json { "op": "call", "service": "world.fluids", "method": "active", "args": [] } { "op": "call", "service": "world.fluids", "method": "stats", "args": ["bog"] } ``` `active` answers `["bog"]`. After thirty steps of walking, `stats` answers a `trough` below -0.01 (the deepest print) and a positive `peak` (the rim). ## Settings and defaults | Setting | Type | Default | Meaning | |---|---|---|---| | `enabled` | boolean | `true` | turns the whole kit off (no patch, no contact) | | `autoContacts` | boolean | `true` | feet and wheels of entities leave prints on their own | | `splashes` | boolean | `true` | contacts also throw droplets | | `simulationRange` | number | `65` | metres from the player within which a patch is relaxed (20 to 120) | | `maxActiveSurfaces` | integer | `2` | how many patches are simulated at once, the nearest first (1 to 4) | A scene may override these through its `layers["world.fluids"]` entry. ## The fluid_surfaces collection At most eight patches per scene. A patch that is invalid, a duplicate id, or over the limit is left out and a warning is logged. | Key | Default | Meaning | |---|---|---| | `id` | required | unique name (1 to 80 characters) | | `position` | required | `[x, y, z]` in metres; place it just above the ground | | `size` | required | `[width, length]` in metres, each 2 to 48; 8 to 24 m patches give the best detail | | `yaw` | `0` | rotation in radians | | `preset` | `mud` | `mud` or `blood` | | `resolution` | `128` | grid cells per side: 64, 128 or 192 | | `depth` | `0.13` | visible layer thickness, 0.02 to 0.35 | | `viscosity` | `0.88` | higher keeps ruts and folds longer, 0 to 1 | | `recoverySeconds` | `180` | slow return to rest; 0 disables recovery | | `seed` | `1` | random seed of the resting surface | ## Services, events, hooks - `world.fluids` (service): `surfaces()` the patches, `active()` the ids being simulated, `contact({ x, y, z, yaw, radius, length, pressure, vx, vz, entity? })` presses the surface (answers the volume moved; 0 if a number is missing or not finite), `reset(surface?)` restores one patch or all, `interactions(after)` the recent contacts after a sequence number, `covers(x, y, z)` whether a point lies on a patch, `probe(surface, x, z)` the surface height or null outside, `stats(surface)` the volume, `peak`, `trough`, `revision` and `active` of a patch (null if unknown). `fields()` and `heights()` give the patches as plain data for a renderer. - `fluid.entered` (event): an entity made its first contact in a patch. Payload: `entity`, `surface`, `preset`. - `fluid.contact` (event, listened to): other objects may send the same contact shape as `contact()`. - `fluid.reset` (event, listened to): `{ surface? }`; omit it to reset every patch. - `fluid.enter` (hook): `{ entity, surface, preset, weight }`, asked once per stay, before the first print. Change `weight` (0 to 1) to scale the pressure of all the entity's contacts in that patch (0.3 for a light step), or cancel it: the entity leaves no print and no splash, and `fluid.entered` is not emitted, until it leaves the patch and comes back. ```json { "id": "light-feet", "hook": "fluid.enter", "match": { "preset": "mud" }, "set": { "weight": 0.3 } } ``` ## Modding A mod can override the five settings and add, patch or remove `fluid_surfaces` documents. Hooks run on the web and the native player. Without a tap the kit plays exactly as before. ## Saves The save keeps each patch (only the cells that left rest), who walked where, which patch each entity stands in, and the recent splashes. A patch that belongs to a scene is kept per scene while another scene is played. ## Engines The solver, the contact samplers and the save format are the same on the web, the native player, Godot, Unity and Unreal; each engine draws the patches with its own mesh and droplets. With `world.tracks` in the same scene the patches would be drawn over the snow prints. ## When it does not do what you expect - No print appears: the patch is not in `active()` (too far: raise `simulationRange` or `maxActiveSurfaces`), `autoContacts` is off, or a `fluid.enter` tap cancelled the entity. - A patch is missing: the log says it is invalid, a duplicate, or over eight patches. - `contact()` answers 0: a field is missing or not a finite number, or the kit is not `enabled`. - Prints vanish too fast or never: change `recoverySeconds` and `viscosity`. ## Limits A height field per patch, 48 m at most per side, eight patches per scene and four simulated at once. No flow, no buoyancy, no blood from combat. ## Versions Kit 1.3.0 (service contract 1.1.0). Hook `fluid.enter` since 1.3.0; native Godot, Unity and Unreal deliveries since 1.1.0; scene patches since 1.2.0.