All articles
Tutorials · 6 min read

Reproducible scattering: seeds, determinism and version-controlling a scattered level

Why procedural scatter needs to be deterministic, covering how seeds make randomness repeatable, why baking to native instances makes a scattered level diffable and mergeable, and how to keep a team's foliage from silently changing under source control.

Coloured prayer flags strung across a mossy, fern-covered forest floor

In short: Procedural scatter is production-safe only when identical inputs create identical placement on every machine and build. Store a seed, keep parameters versioned, and bake approved results into native engine instances. That makes foliage changes intentional, reviewable, and reproducible instead of letting a live random system silently rearrange the level.

Ferns carpeting a forest floor in repeating but varied clumps This looks random, but for a team it must be repeatable random: the same on your machine, your teammate's, and the build server. Determinism is what turns a pretty scatter into something you can actually ship and maintain.

Why determinism matters

If a scatter regenerates differently each time it's opened or built, your level silently changes: collision moves, hidden objects appear, playtests stop being reproducible. Deterministic scatter always produces the exact same result from the same inputs.

Randomness that isn't repeatable is a bug factory in a level. If opening the scene on a different machine, or rebuilding it next week, reshuffles where every rock and bush lands, then a spot that was clear is now blocked, a sightline you tuned is broken, and a playtest result can't be trusted because the level under test isn't the level you shipped. Determinism, meaning the same inputs always produce the same output, is what makes a procedural scatter safe. It's the difference between "the computer placed some plants" and "the level has a defined, stable state I can rely on."

Seeds: making randomness repeatable

A seed is the starting number for a random sequence. The same seed produces the same "random" placement every time. Expose and save the seed, and your scatter is reproducible; leave it to true randomness and it's a different level on every run.

Computers don't do true randomness. They run deterministic sequences that look random, each sequence defined by a starting number called the seed. Feed the same seed and you get the same sequence, hence the same placement, every single time. This is the key to reproducible scattering: the tool exposes a seed, you save it with the scene, and the forest is now a pure function of (seed + parameters + surface). Change the seed to explore a different arrangement you like, then lock it. Anything that instead pulls from a genuinely unseeded random source, such as the clock or a fresh roll each open, produces a level that's different every time it loads, which is unusable for production.

The version-control problem

A live procedural scatter stored as "a system + parameters" is a black box to source control. You can't diff it, can't review it, and two people editing it produces an unmergeable conflict. Teams need the scatter in a form Git or Perforce can actually track.

Here's where scatter meets the reality of a team. If your forest exists only as "a scatter system with these settings," source control can't see what it produced, only that some parameters changed. You can't review a placement change in a pull request, you can't tell what moved, and if two artists touch the same scatter, you get a merge conflict no tool can resolve meaningfully. A scattered level that only lives inside a live generator is invisible to the exact systems teams rely on to collaborate safely, which is why "it's all procedural" can quietly become "no one can safely edit the forest."

Small mushrooms scattered across the forest floor Each of these is an instance with a position, rotation and scale. When the scatter is baked to native instances, those become real, inspectable data, and the forest stops being a formula and becomes something you can review line by line.

Baking makes it diffable

Bake the scatter down to native engine instances: real placed objects with concrete transforms. Now it's ordinary scene data: diffable, reviewable, mergeable, and identical on every machine because it's stored, not regenerated.

The resolution to both determinism and version control is the same move: bake. Instead of shipping a live generator, you bake the scatter to plain native engine instances, actual placed meshes with concrete positions, rotations and scales stored in the scene. This collapses every problem at once. It's deterministic by definition (it's stored data, not a re-roll). It's diffable (source control sees the concrete transforms that changed). It's reviewable (a lead can see exactly what moved in a pull request). And it costs the engine nothing unusual at runtime, because it's just instanced meshes. A workflow that paints density with rules and then bakes to native instances, which is exactly how Numivo works, gives you the authoring speed of procedural scatter and the stability of hand-placed content, with none of the black-box fragility.

Keeping a team's foliage stable

Lock seeds, bake before committing, and treat the baked result as the source of truth. Re-scatter deliberately (bump the seed, review the diff), never accidentally, so the forest only changes when someone means it to.

The practical discipline for a team is short. Lock the seed so the scatter is reproducible. Bake before you commit, so what's in source control is the concrete result everyone shares, not a recipe that re-rolls per machine. Treat the baked instances as the source of truth, the thing you review and ship. And when you do want to change the forest, do it deliberately: adjust parameters or seed, re-bake, and review the diff like any other change. The goal is a forest that changes only when a human decides it should, and whose every change is visible and reviewable, the same standard you'd hold any other part of the level to. Procedural for speed, baked for stability: that's the whole trick.

Field numbers worth stealing

  • Determinism rule: same inputs, same output, always, or your level drifts
  • A seed makes randomness repeatable: expose it, save it, lock it
  • A live procedural scatter is a black box to Git/Perforce: undiffable, unmergeable
  • Bake to native instances for diffable, reviewable, mergeable, identical everywhere
  • Team habit: lock seed, bake, commit; re-scatter deliberately, never by accident

Mini-FAQ

Can't I just keep the scatter procedural and never bake? For a solo quick project, maybe. For a team or anything you'll maintain, no. You lose diffing, reviewing and safe merging, and you risk silent drift. Baking is what makes procedural scatter production-safe.

What exactly should I put under source control, the settings or the result? The baked result is the source of truth; keep the settings/seed too so you can re-generate deliberately. Committing only the settings means source control can't see what actually changed in the level.

Two artists edited the same forest, how do we merge? With baked instances, it's ordinary scene data and your normal merge tools apply. With a live generator, it's often unresolvable, which is the core argument for baking before commit and coordinating who owns a given scatter.

Does baking cost me the ability to tweak later? No. Keep the seed and parameters, and re-baking is a deliberate step whenever you want to change the arrangement. You get iteration and stability, as long as re-scattering is an intentional, reviewed action rather than something that happens on its own.

Procedural scattering earns its place only when it's reproducible. Lock your seeds, bake to native instances, and treat that baked result as the level's truth, and your forest becomes something a whole team can build on: fast to author, stable to ship, and safe to change only when someone means to.