Author ground flora species and procedural scattering
verifiedAgainst e41a048 · verifiedOn 2026-09-11.
What you will build
Section titled “What you will build”In Voxamine, open landscapes are not bare geometric voxel steps. Ground flora—such as wild clovers, delicate alpine lichens, arid sagebrush, shore reeds, and floating lily pads—decorates the world to give each biome its tactile identity (MASTERPLAN.md §13.1, §14.5).
Rather than allocating heavy GameObjects or writing decorative entries into chunk voxel memory, ground flora is purely presentational:
- It is drawn using GPU instancing (
Graphics.DrawMeshInstanced) in batches up to 1023 instances per draw call. - It streams within a bounded 3-chunk horizontal presentation radius around the active camera.
- It strictly obeys voxel geometry: each prop requires an allowed solid support block (
FloraSupport.cs), tests for vertical headroom (ClearanceHeight), and despawns the instant a player mines the ground or places a roof overhead. - It sways harmoniously under atmospheric wind currents (
AtmosphericPressureField.PrevailingWind).
You will author a data-driven species definition in SurfaceFloraCatalog.cs, configure support and clearance legality gates in VegetationPlacementRules.cs, inspect deterministic PRNG scattering in VegetationPlacementMath.cs, and verify streaming presentation in SurfaceVegetationRuntime.cs.
// Example: Authoring a hardy alpine wildflower in SurfaceFloraCatalog:new SurfaceFloraSpecies( SurfaceFloraKind.AlpineWildflower, "Alpine Wildflower", BiomeType.Mountains | BiomeType.Tundra, new[] { FloraSupport.Grass, FloraSupport.Snow }, clearanceHeight: 1, density: 0.04f, maxPerChunk: 24, FloraMeshStyle.CrossQuad, new Color(0.95f, 0.95f, 0.88f), new Color(0.96f, 0.82f, 0.32f), new Vector3(0.14f, 0.34f, 0.14f), swayAmount: 0.5f)Where this sits
Section titled “Where this sits”Ground flora sits cleanly between procedural terrain streaming and GPU presentation:
- Biome & Terrain Engine (
BiomeClassifier&ChunkStreamingRuntime) determines local biome classification and provides world block lookups. - Flora Catalogue (
SurfaceFloraCatalog&SurfaceFloraSpecies) acts as the immutable code-defined catalogue of all flora types, their biome masks, and support requirements. - Placement Rules (
VegetationPlacementRules) validates that candidate coordinates satisfy physical support, vertical clearance, and environmental adjacency. - Deterministic Math (
VegetationPlacementMath) computes seeded hash scattering and candidate priority without storing persistent state. - Presentation Runtime (
SurfaceVegetationRuntime) monitors active chunk transitions, organizes per-chunk candidate patches, evaluates wind sway fromAtmosphericPressureField, and issuesGraphics.DrawMeshInstancedcalls.
flowchart TD
subgraph TERRAIN["Terrain Generation & Streaming"]
BIOME["BiomeClassifier.Classify(x, z)\n(BiomeType, Temperature, Moisture)"]
CHUNK["ChunkStreamingRuntime\n(Voxel Block State & Palette)"]
end
subgraph CATALOG["Authoring & Rules"]
CATALOGUE["SurfaceFloraCatalog.All\n(26 immutable Species)"]
RULES["VegetationPlacementRules.CanPlace\n(Support, Clearance, Water Adjacency)"]
MATH["VegetationPlacementMath\n(Seeded Hash Scattering & Offset/Yaw Style)"]
end
subgraph RUNTIME["Presentation Runtime"]
STREAM["SurfaceVegetationRuntime\n(3-chunk radius patch streaming)"]
SWAY["AtmosphericPressureField.PrevailingWind\n(Wind angle & phase modulation)"]
REBASE["world.OriginBlockOffset\n(Floating-origin coordinate reprojection)"]
end
subgraph GPU["GPU Instancing"]
DRAW["Graphics.DrawMeshInstanced\n(FloraMeshLibrary meshes, up to 1023/batch)"]
end
BIOME --> STREAM
CHUNK --> RULES
CATALOGUE --> STREAM
CATALOGUE --> RULES
MATH --> STREAM
RULES --> STREAM
SWAY --> STREAM
REBASE --> STREAM
STREAM --> DRAW
Before you start
Section titled “Before you start”Read MASTERPLAN.md §13.1 (“World presentation”) and §14.5 (“Flora & vegetation”), then inspect:
SurfaceFloraSpecies.cs, the immutable definition contract.SurfaceFloraCatalog.cs, the registry containing all 26 ground species.VegetationPlacementRules.cs, the voxel legality validator.SurfaceVegetationRuntime.cs, the instanced drawing pipeline.
Keep the core architectural distinction clear:
| Category | Typical Examples | Representation | Persistence | Collision |
|---|---|---|---|---|
| Voxel Blocks | Stone, Dirt, Wood, Cutout Foliage Block | Voxel byte in chunk array (BlockId) |
Persisted in world save | Solid box or raycast hit |
| Ground Flora | Wildflowers, Grass clumps, Ferns, Lichen | GPU-instanced procedural mesh clump | Not persisted (procedurally derived from seed + voxels) | None (purely visual) |
The build, step by step
Section titled “The build, step by step”1. Register a Stable Flora Kind
Section titled “1. Register a Stable Flora Kind”In SurfaceFloraSpecies.cs, append your new species kind to the end of SurfaceFloraKind:
public enum SurfaceFloraKind{ GrassClump = 0, Wildflower = 1, Cactus = 2, // ... Glasswort = 22, SilicaHorsetail = 23, SulfurLichen = 24, SphagnumMoss = 25}[!IMPORTANT] Never reorder existing enum members. The integer ordinal directly salts the deterministic PRNG hash (
salt = KindSeedSalt + (int)kind * KindSeedStride). Reordering members silently scrambles the flora layout of existing worlds.
2. Define the Species in the Catalogue
Section titled “2. Define the Species in the Catalogue”In SurfaceFloraCatalog.cs, instantiate the immutable SurfaceFloraSpecies in Build():
new SurfaceFloraSpecies( SurfaceFloraKind.Glasswort, "Glasswort", BiomeType.Desert | BiomeType.Badlands, sand, clearanceHeight: 2, density: 0.045f, maxPerChunk: 18, FloraMeshStyle.SaltwortCard, new Color(0.52f, 0.60f, 0.20f), new Color(0.92f, 0.22f, 0.20f), new Vector3(0.72f, 0.96f, 0.72f), swayAmount: 0.25f, chemicalRole: FloraChemicalRole.SodiumAshPrecursor, textureResourcePath: "Flora/Glasswort")Every species specifies:
biomes: Flags mask (BiomeType.Plains | BiomeType.Forest). Every classified biome must have at least two distinct flora species.supports: One or moreFloraSupportcategories (Grass,Sand,Snow,Stone,WaterSurface).clearanceHeight: Number of continuous air blocks required above the root.density: Scatter probability in $(0, 1]$ evaluated on qualifying surfaces.maxPerChunk: Hard instance cap per chunk preventing performance spikes.meshStyle: The procedural silhouette drawn byFloraMeshLibrary(CrossQuad,BushCluster,LowCrust,LilyPadDisc, etc.).swayAmount: Wind reactivity ($0.0$ for rigid lichen and cacti, $>0.8$ for flexible tall grasses).chemicalRole: Clue indicating future chemistry harvest roles (e.g.SodiumAshPrecursor,BiogenicSilica).
3. Implement Physical Block Legality
Section titled “3. Implement Physical Block Legality”In VegetationPlacementRules.CanPlace, incoming coordinates are validated against the active palette and world voxels:
public static bool CanPlace( SurfaceFloraSpecies species, BiomeType biome, GenerationBlockPalette palette, BlockId support, System.Func<int, BlockId> cellAbove, bool hasWaterNeighbour){ if (species == null || palette == null || cellAbove == null) return false; if ((species.Biomes & biome) == BiomeType.None) return false; if (!species.SupportsBlock(support, palette)) return false; if (species.RequiresWaterAdjacency && !hasWaterNeighbour) return false;
if (species.IsWaterSurface) { return support == palette.Water && cellAbove(1).IsAir; }
for (int height = 1; height <= species.ClearanceHeight; height++) { if (!cellAbove(height).IsAir) { return false; } }
return true;}Support blocks are resolved dynamically via palette (FloraSupport.Grass $\to$ palette.Grass), ensuring flora authoring is completely independent of specific block IDs.
4. Deterministic Seed-Based Scattering
Section titled “4. Deterministic Seed-Based Scattering”In VegetationPlacementMath, evaluate deterministic placement without state:
// 1. Fair rotation of species candidate order to prevent the first species from consuming all spots:int startIndex = VegetationPlacementMath.GetSelectionStartIndex(worldX, worldZ, worldSeed, candidateCount);
// 2. Independent per-species scatter probability:bool place = VegetationPlacementMath.ShouldPlace(worldX, worldZ, worldSeed, candidate.Kind, density);
// 3. Jitter local offset, yaw rotation, and scale:VegetationVisualStyle style = VegetationPlacementMath.GetStyle(worldX, worldZ, worldSeed);Because each species salts the seed differently, species scatter independently across biomes, creating natural clusters and varied ground tapestries.
5. Stream and Render via GPU Instancing
Section titled “5. Stream and Render via GPU Instancing”SurfaceVegetationRuntime manages active chunks:
- As chunks activate within a 3-chunk Chebyshev radius around the camera, it computes candidate instances into
VegetationPatch. - In
LateUpdate, it checks still-valid legality and builds transformation matrices. - It offsets coordinates by
world.OriginBlockOffsetto maintain floating-origin invariance. - It modulates rotation by atmospheric wind:
Vector2 wind = AtmosphericPressureField.PrevailingWind;float windAngle = Mathf.Lerp(3f, 12f, clampedStorm) * species.SwayAmount;float sway = Mathf.Sin(Time.time * swaySpeed + instance.Style.WindPhase) * windAngle;Quaternion rotation = Quaternion.Euler(-wind.y * sway, instance.Style.YawDegrees, wind.x * sway);
- It issues
Graphics.DrawMeshInstanced(mesh, 0, material, matrices, count, null, ShadowCastingMode.Off, true).
Verify
Section titled “Verify”1. Execute EditMode Tests Headlessly
Section titled “1. Execute EditMode Tests Headlessly”Run the dedicated flora test suites:
/opt/unity/Editor/Unity -batchmode -nographics \ -projectPath /home/soulwax/workspace/engines/unity/minecraft/Minecraft-HD \ -runTests -testPlatform EditMode \ -testFilter VoxelSandbox.Tests.EditMode.World.SurfaceFloraCatalogTests/opt/unity/Editor/Unity -batchmode -nographics \ -projectPath /home/soulwax/workspace/engines/unity/minecraft/Minecraft-HD \ -runTests -testPlatform EditMode \ -testFilter VoxelSandbox.Tests.EditMode.World.VegetationPlacementRulesTestsVerify that all key assertions pass:
CatalogExposesAtLeastFourteenDistinctSpecies: guarantees coverage across all species.EveryClassifiedBiomeHasAtLeastTwoGroundFloraSpecies: verifies no barren biomes.EverySpeciesHonoursItsOwnInvariants: checks valid densities in $(0, 1]$, non-empty display names, and positive heights.ScatterIsDeterministicPerKindAndKindsScatterIndependently: ensures repeatable, non-colliding scattering.ChemistryFacingFloraUsesDistinctHonestBiomassOrIndicatorRoles: confirms valid 256x256 clamp textures for chemistry-facing species.
2. Inspect in Play Mode
Section titled “2. Inspect in Play Mode”- Enter Play Mode in
Gameplay.unity. - Teleport to various biomes (
/tpor fly):- Plains: Grass Clumps, Wildflowers, Clover, Tall Grass.
- Desert: Saguaro Cacti, Barrel Cacti, Sagebrush, Dead Bush, Glasswort.
- Tundra & Mountains: Lichen, Alpine Wildflower, Snow Shrub, Hardy Tuft, Sulfur Lichen.
- Jungle & Swamps: Ferns, Giant Ferns, Bromeliads, Reeds, Lily Pads, Sphagnum Moss.
- Break a dirt block beneath a wildflower: observe that the flower instantly vanishes on the next frame because its support block was destroyed.
- Build a stone overhang 1 block above a reed: observe that it despawns due to clearance violation.
Now do your own
Section titled “Now do your own”To author a new regional species (e.g. Cave Phosphor Fungi or Coastal Mangrove Tuft):
- Add Enum: Add
CavePhosphorFungitoSurfaceFloraKind. - Add Catalogue Entry: Construct the
SurfaceFloraSpeciesinSurfaceFloraCatalog.Build(). - Select Mesh Style: Choose from
FloraMeshStyleor add a new procedural silhouette inFloraMeshLibrary.cs. - Assign Textures or Colors: Set primary/secondary colors or add an alpha-clipped 256x256 card texture under
Resources/Flora/. - Add Test Fixtures: Verify biome coverage and placement assertions in
SurfaceFloraCatalogTests.cs.
Pitfalls
Section titled “Pitfalls”| Symptom | Cause | Fix |
|---|---|---|
| Flora layout shifts across the entire world after an update. | SurfaceFloraKind enum was reordered or an item inserted into the middle. |
Always append new species at the end of SurfaceFloraKind. Never change existing enum values. |
| Flora appears floating in mid-air or clipping through ceilings. | ClearanceHeight was set to $0$ or support was not checked against palette. |
Ensure ClearanceHeight >= 1 and all vertical cells are verified using cellAbove(h).IsAir. |
| Lily pads fail to spawn on water surfaces. | Support was set to FloraSupport.Grass instead of FloraSupport.WaterSurface. |
Use FloraSupport.WaterSurface and ensure IsWaterSurface handles palette.Water checks. |
| High frame-time hitching when turning the camera quickly. | Flora runtime created new Material or Mesh instances inside LateUpdate. |
Cache all procedural meshes in meshCache and materials in materialCache by FloraMeshStyle and SurfaceFloraKind. |
| Ground flora fails to align with camera during origin rebase. | World positions were computed without subtracting world.OriginBlockOffset. |
Always evaluate instance transformation matrices relative to world.OriginBlockOffset. |
User-contributed notes
Corrections, clarifications, and practical tips for this page. Anonymous is fine — a name is optional. Basic Markdown works:
**bold**,*italic*,`code`, and links.Notes policy
Notes are lightly filtered for spam and may be edited or removed. Keep them about this page — no support requests, no personal data, nothing you would not publish. Links are limited and marked
nofollow.