feat(foliage): add recovering grass trails and bush contact

This commit is contained in:
Rijad Zuzo
2026-09-05 19:30:46 +02:00
parent da3bed67a0
commit 7899d36f03
21 changed files with 673 additions and 18 deletions
+43
View File
@@ -49,6 +49,49 @@ values; viewport scale restoration is guarded by the current quality owner so a
second world cannot clobber the active owner. Compatibility/mobile feature
selection never forces unsupported volumetric fog on.
## Grass trails and bush contact
`GrassInteractionController` samples the existing `grass_interactors` group:
player, NPCs, hostile creatures and animals. The player keeps one slot; other
visible actors are ranked by camera distance within the loaded actor container.
High updates at 0.08 seconds with eight actors; Balanced uses 0.12 seconds and
four. Low stops updates and immediately releases both grass and bushes.
`FoliageTrailMap` maintains a disposable 128×128 RGBA float texture covering a
64 m square around the camera. Swept footprints part grass to either side and
lower its tips; the field recovers over eight seconds after contact. Sparse
history is clipped to the moving window. The 256 KiB texture is reused with
[`ImageTexture.update`](https://docs.godotengine.org/en/stable/classes/class_imagetexture.html#class-imagetexture-method-update),
at most 3.125 MiB/s of texture data at the High update rate. The vertex shader
adds one field lookup per grass vertex; there are no blade collision bodies,
per-blade scripts, render targets or added grass draw calls.
`StylizedBerryPatch` shares the field and contact positions between its leaf
and berry MultiMeshes. Both bend in world space around the planted bush root,
including under rotated/scaled parents, and spring back faster than grass.
The five village berry placeholders now use this same amount-aware presentation
as the existing riverbank bush. Contact does not change berries, resource
amounts, navigation, targets or collision geometry.
The controller clears history after `SimulationManager.state_restored`, on
quality changes and on world disposal. Missing/recreated actors, teleports over
3 m between samples and update gaps over 0.5 seconds start a new footprint rather
than a connecting trail. Actor foot height gates deformation so bridge traffic
does not flatten grass below. Trails are visual only and never enter saves.
The exported actor, primary actor, foliage and simulation paths can be rebound
when placing the controller in another loaded scene container.
`tests/foliage_interaction_test.gd` checks sweep direction, recovery, teleport
rejection, camera movement, world isolation, restore/quality reset, the primary
actor slot and shared leaf/fruit bindings. Run `tools/capture_foliage.gd` with
the native renderer for before/contact/trail/recovery images and controller
timing in `docs/baselines/foliage_*`. That capture freezes simulation and wind, and moves
the player presentation through the actual meadow, checking the state checksum
is unchanged. CPU update timing is not a GPU or weak-PC frame-rate guarantee.
Native Metal and Compatibility both render the effect; existing particle-shader
shutdown warnings (and four Compatibility texture leaks) also reproduce in the
untouched pre-art baseline. The headless quality gate retains its exact allowlist.
## Runtime hot paths already bounded
The woodland storybook art pass adds portable Blender animal meshes and an
Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1008 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 KiB

@@ -0,0 +1,13 @@
{
"active_trail_texels": 145,
"controller_update_p50_ms": 0.307,
"controller_update_p95_ms": 0.338,
"draw_calls": 582.0,
"note": "CPU timing includes scanning, field painting and material uploads; not isolated GPU frame time.",
"profile": "High",
"renderer": "metal",
"resolution": "1600x900",
"simulation_checksum_unchanged": true,
"trail_texture_bytes": 262144,
"workload": "Actual Jajce meadow; 8 nearest actors, player presentation follows an 11 m sweep"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1000 KiB