Mud and blood
Usage guide of world.fluids: editable mud and blood patches that take footprints, wheel ruts and splashes; the fluid_surfaces documents, settings and defaults, service, events, the fluid.enter hook, saves and engines.
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.
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):
{ "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 #
<!-- test: kits/game_engine/host/tests/world_fluids.rs::a_walker_leaves_prints_in_the_active_patch --> 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.
{ "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,revisionandactiveof a patch (null if unknown).fields()andheights()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 ascontact().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. Changeweight(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, andfluid.enteredis not emitted, until it leaves the patch and comes back.
{ "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: raisesimulationRangeormaxActiveSurfaces),autoContactsis off, or afluid.entertap 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 notenabled.- Prints vanish too fast or never: change
recoverySecondsandviscosity.
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.