Fix: revive the walk-through flora rustle — a fourth orphaned presentation layer

Track 1. Before building any new "world responds to you" work, I checked what
already existed, because today's score was three presentation systems that were
present, enabled, and produced nothing. This is the fourth.

AmbientMotionSystem's rustle layer (flora parting as the local player brushes
past) was dead in the shipping arena via THREE independent gates, each a
leftover of the deleted biomes:

  1. Root gate required a scene root literally named "BaseBiome" or
     "ExpeditionBiome*". Both went with the expedition, so the loop matched
     nothing at all. The seabed flora live under
     Env_SeabedKit/Seabed_Flora/FloraClump_N.
  2. Name regex was pure LAND flora: bush|grass|flower|fern|cactif|clover...
     Nothing in a seabed matches that. Added seabed terms first; the land terms
     stay only so an older scene still works.
  3. Renderer gate tested for a renderer ON the matched object. The natural sway
     unit is the CLUMP ROOT, which carries no renderer of its own - only
     children - so the test rejected exactly the object we want to rotate. Now
     accepts a renderer anywhere beneath, and skips a child whose parent already
     matched so the clump tilts once instead of every stalk tilting out of its
     socket.

Verified live: AmbientMotionSystem._flora.Count 0 -> 4 (the four clump roots).

304/304 EditMode green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-08 17:50:13 -07:00
parent b00926dc6e
commit 13d591e789
@@ -24,8 +24,12 @@ namespace ProjectM.Client
[UpdateInGroup(typeof(PresentationSystemGroup))] [UpdateInGroup(typeof(PresentationSystemGroup))]
public partial class AmbientMotionSystem : SystemBase public partial class AmbientMotionSystem : SystemBase
{ {
// 2026-08-08: seabed terms FIRST. The old list was pure LAND flora (bush/grass/flower/fern/cactus)
// from the deleted meadow + arid biomes, so it matched NOTHING in the shipping seabed arena and the
// whole walk-through rustle layer was dead. The land terms are kept only so an old scene still works.
static readonly System.Text.RegularExpressions.Regex FloraName = new System.Text.RegularExpressions.Regex( static readonly System.Text.RegularExpressions.Regex FloraName = new System.Text.RegularExpressions.Regex(
"bush|grass|flower|fern|groundcover|ground_cover|wildflower|clover|reed|succulent|cactus", "floraclump|flora_|kelp|frond|seagrass|seaweed|coral|anemone"
+ "|bush|grass|flower|fern|groundcover|ground_cover|wildflower|clover|reed|succulent|cactus",
System.Text.RegularExpressions.RegexOptions.IgnoreCase); System.Text.RegularExpressions.RegexOptions.IgnoreCase);
struct Flora struct Flora
@@ -79,14 +83,22 @@ namespace ProjectM.Client
{ {
RestoreFlora(); RestoreFlora();
_flora.Clear(); _flora.Clear();
// 2026-08-08: scan EVERY root. The old gate required a root literally named "BaseBiome" or
// "ExpeditionBiome*" — both went with the expedition, so this loop matched nothing and the rustle
// layer silently did nothing in the shipping arena. The seabed flora live under
// Env_SeabedKit/Seabed_Flora/FloraClump_N.
foreach (var root in scene.GetRootGameObjects()) foreach (var root in scene.GetRootGameObjects())
{ {
if (root.name != "BaseBiome" && !root.name.StartsWith("ExpeditionBiome")) continue;
foreach (var t in root.GetComponentsInChildren<Transform>(false)) foreach (var t in root.GetComponentsInChildren<Transform>(false))
{ {
if (!FloraName.IsMatch(t.gameObject.name)) continue; if (!FloraName.IsMatch(t.gameObject.name)) continue;
if (t.gameObject.name.Contains("LOD")) continue; // prop roots only; LOD children follow if (t.gameObject.name.Contains("LOD")) continue; // prop roots only; LOD children follow
if (t.GetComponent<LODGroup>() == null && t.GetComponent<MeshRenderer>() == null) continue; // The sway unit is the CLUMP ROOT, which carries no renderer of its own — only children.
// The old renderer-on-self test rejected exactly the object we want to rotate, so accept a
// renderer anywhere beneath. Skip anything whose PARENT already matched, so we tilt the
// clump once instead of tilting every stalk out of its socket.
if (t.parent != null && FloraName.IsMatch(t.parent.gameObject.name)) continue;
if (t.GetComponent<LODGroup>() == null && t.GetComponentInChildren<MeshRenderer>() == null) continue;
// 1.5b: ground-flush carpet props (Flowers_Flat, Grass_*_Plane) must NEVER root-tilt — // 1.5b: ground-flush carpet props (Flowers_Flat, Grass_*_Plane) must NEVER root-tilt —
// any rotation lifts their edges off the ground; they stay fully static (shader wind only). // any rotation lifts their edges off the ground; they stay fully static (shader wind only).
if (t.gameObject.name.IndexOf("Flat", System.StringComparison.OrdinalIgnoreCase) >= 0 if (t.gameObject.name.IndexOf("Flat", System.StringComparison.OrdinalIgnoreCase) >= 0