-
-
Notifications
You must be signed in to change notification settings - Fork 0
Ordering and Composition
WorldgenLib gives each seam its own ordered delegate list. An order value is
compared only with other registrations on the same seam. The host sorts by
ascending order and uses registration order as the stable tie breaker.
Register during StartServerSide. At InitWorldGen, each list freezes into an
immutable snapshot for the generation hot path. A late registration throws
InvalidOperationException.
Use the named constants instead of magic numbers:
| Constant | Value | Purpose |
|---|---|---|
BeforeVanillaMin |
-100 |
Lower bound for input rewrites |
BeforeVanillaMax |
-1 |
Last position before the canonical pass |
Vanilla |
0 |
Reserved for the WorldgenLib vanilla-equivalent chain |
AfterVanillaMin |
1 |
First normal post-vanilla effect |
AfterVanillaMax |
100 |
Last normal post-vanilla effect |
FinalOverrideMin |
1000 |
Terminal owner for a complete replacement |
The constants are ranges, not locks. WorldgenLib accepts any finite order, so consumers should document their chosen offset and keep it stable between releases.
The audited starting offsets are:
| Consumer or effect | Recommended offset |
|---|---|
| Rivers-Mod terrain effects | AfterVanillaMin + 50 |
| VSRiverGen terrain effects | AfterVanillaMin + 50 |
| Watersheds erosion and stream effects | AfterVanillaMin + 60 |
| Terra Prety normal terrain effects | AfterVanillaMin + 50 |
| Terra Prety post-placement carving | AfterVanillaMin + 70 |
| Terra Prety BlockLayers filter | AfterVanillaMin + 60 |
These values are coordination defaults. They do not make two algorithms compatible when both claim the same semantic ownership.
Prefer the smallest hook that expresses the effect:
| Need | Preferred seam |
|---|---|
| Rewrite border taper for one chunk | Step 0 |
| Change landform weights or octave inputs | Step 2 |
| Add vertical displacement | Step 4 |
| Select fresh, salt, or ice water | Step 5 |
| Modify solid or air threshold | Step 7 |
| Carve or fill after placement | Step 10 |
| Persist data once all columns finish | TerrainFinalize |
| Change one map layer's output | The matching map step |
| Wrap a vanilla map generator | RegisterMapGenerator |
| Change map padding | RegisterMapPadding |
| Replace one complete region pass | Full map-region adapter |
| Modify BlockLayers raise | Raise modifier |
| Suppress sea-level rise for a column | Sea-level filter |
| Replace an entire BlockLayers pass | Full BlockLayers adapter |
An atomic hook receives the current result and should make a bounded change.
For example, a threshold hook returns its input unchanged outside the river
channel. A terminal adapter has ownership of the full request and must return
true only after it has handled that request. Terminal adapters need explicit
coordination because one complete-pass owner can prevent the canonical pass
from running.
Rivers-Mod and VSRiverGen implement similar river effects. They are alternative providers by default. Running both can create duplicate landforms and carving, even if their orders are deterministic. Select one provider or define a consumer-level policy for their shared state.
Watersheds and Terra Prety can share the pipeline when each effect is split into its map, terrain, and BlockLayers hooks. The order must reflect the intended meaning. A map smoothing wrapper that must receive the vanilla generator goes before the canonical map chain. An erosion transform that should see the finished vanilla height inputs goes after the corresponding terrain step.
Full adapters are migration bridges for algorithms that cannot yet be split. Do not register a full adapter and an equivalent set of atomic hooks for the same effect. That runs the effect twice or makes the result depend on which terminal callback claims the request first.
Registration validates the owner ID, finite order, and non-null handler. During generation, a thrown exception disables all entries owned by that mod in the affected hook list and the host logs a warning. A non-finite numeric result is handled the same way for numeric hooks. Other mods continue to run.
This boundary does not make shared data safe. If a consumer stores mutable data
in ChunkContext.CustomData, it must provide its own synchronization or
partitioning. Per-column callbacks run in parallel and their arrival order is
not a composition contract.
Use the world seed, coordinates, and stable inputs. Avoid process-global random state, wall-clock values, unordered shared mutation, and dependence on callback arrival order. If two hooks need to exchange data, use namespaced request data, registered region maps, or a finalization hook with a clearly defined owner.