Server-side SML mod (mod reference NodeShuffle) for Satisfactory 1.1+
(>=491125), SML 3.12, UE 5.6.1-CSS. It relocates the world's resource nodes to
new, map-wide locations per save and shuffles which are active and what resource
and purity each carries — for solids, oil/liquids, and modded nodes — with
balance minimums that keep every playthrough completable.
Every unoccupied original node (vanilla and modded) is hidden whole-actor (which also removes its rock, including instanced meshes) and recorded persistently. Its resource lives on as one of our own relocated nodes, spawned at a new location. Occupied originals — any node with a miner, extractor, or portable miner — are left 100% untouched, at roll time and re-checked continuously, so the player's factory is never disturbed and the save stays safe. Hiding is reversible (nothing is destroyed), so disabling the mod restores the vanilla world.
A relocated node is spawned as its own resource-node class (resolved from the
saved NodeClassPath), not a generic stand-in. This is what makes modded nodes
behave correctly: a lithium node spawns as its Alkali class, so it keeps its
native rules (rejects a normal Miner, accepts its intended extractor). Each
spawned node carries a UNodeShuffleNodeComponent — a runtime identity marker
that also hosts the fallback rock/oil-decal and a bForceAccept flag. Identity
lives in the SaveGame layout (a FGuid per entry), not the actor class, so nodes
re-adopt by GUID or by location across reloads.
In priority order: an authored table (FNodeShuffleNodeAssets) of the game's
own node meshes/materials for vanilla resources; a look captured from the
original node for modded resources the table doesn't cover — from its paired
mesh actor (e.g. RefinedPower thorium, modded lead) or, for self-rendering node
BPs with no engine mesh-actor links, from the node's own/attached mesh
components (e.g. FicsitFarming dirt); a quartz placeholder otherwise; and
an oil decal for liquids. When a modded-class node is dealt a vanilla
resource, the assigned resource's look wins (the native mesh is hidden and our
rock is dressed) so it doesn't wear the wrong appearance.
New nodes use spawn-on-discovery: they materialize only once a player is
within range and the terrain has streamed in, so they always settle correctly on
the ground. Settling (RaycastSettle):
- Slope-fit — the rock takes the full (smoothed) slope so it beds into the hill; the node actor is tilt-clamped (~12°) so the Miner hologram gets near-vanilla geometry and can place. Hills of any steepness are fair game.
- Cliff avoidance — hits steeper than 60° re-deal to better ground.
- Water avoidance — a learned land/water map plus a depth guard and the game's water-volume test keep nodes off the seabed and out of lakes; a water-locked entry re-deals randomly within the map-wide deal box.
- Caves — the shuffle would otherwise empty caverns (it hides their originals). Roofed originals seed a budgeted trace flood-fill that maps cavern floors; nodes are then dealt onto them at their natural share, only where a Miner fits.
- Overlap/enclosure/machinery guards keep nodes off factories and out of boxed-in rock pockets.
The land/water grid (100 m cells) and cave-floor cells (8 m) are learned from
every ground probe and persisted globally (NodeShuffle_WaterGrid.json,
NodeShuffle_CaveFloors.json — the terrain is the same across saves). A snapshot
is also embedded in the mod (generated into NodeShuffleBakedData.h) and
merged on load with local knowledge winning, so a fresh install places nodes
well from the first launch and the player's own exploration keeps refining it.
The single source of truth is a UPROPERTY(SaveGame) array of FNodeShuffleEntry
on the subsystem (plus seed and layout version). It is rolled once per save
from a seeded FRandomStream and thereafter only re-applied idempotently
every ~5 s (server-only). The roll preserves balance by dealing resources and
purities from the vanilla multiset (so overall balance is retained) and
enforces a per-resource active floor (MinNodesPerResource, and a separate
floor for modded resources) — the completability guarantee. New SaveGame fields
are additive with defaults, so older saves load unchanged.
The initial roll captures the whole map's vanilla node set, not a streamed subset. This was believed the other way round until it was measured, and the belief produced two wrong investigations, so the evidence is recorded here rather than left as an assumption:
- The roll's pool comes from a live
TActorIterator<AFGResourceNodeBase>. That iterator sees only resident actors — but every level-placed resource node is resident from load. All 630 originals resolve and hide inside a single 8–11 ms frame at boot, in three independent boots, including all 8 Uranium and all 23 Bauxite nodes in biomes tens of kilometres apart. No streaming radius contains both. MinVanillaNodesForRoll = 50is therefore not a streaming gate and never was. It is a sanity floor ("a populated world exists"). Raising it fixes nothing and is explicitly rejected — seedocs/TECH-DEBT.md.- Corroboration from the game's own design: CSS built cook-time manifests
(
AFGWorldScannableDataGenerator) for item pickups, drop pods and creature spawners — precisely the things that are streamed — and built none for resource nodes. The Radar Tower takes liveAFGResourceNodeBase*pointers and has no registry behind it; it works for the same reason our scan does.
Two things remain player-presence-gated, and both by physics, not by choice: spawning a
replacement node and probing a well destination each need a downward trace against resident
terrain (RaycastSettle). Neither is affected by how the roll enumerates.
The one genuine discovery gap is other mods' nodes. Nodes SpawnActor'd at runtime by
other mods have been measured arriving minutes after boot (28 in one session) and are absent
from the first roll's pool. A re-roll re-scans live and enrols them. See docs/TECH-DEBT.md
T14.
Diagnostics: every number above is emitted per roll on the ROLLCENSUS: line, including
the live-node provenance split (ours / level-placed / runtime-spawned by other mods) and the
per-resource active counts, with a ROLLCENSUS ZERO-ACTIVE: warning naming any managed
resource that ends a roll with no active entries.
A small number of engine methods are hooked via SUBSCRIBE_UOBJECT_METHOD:
- Miner hologram acceptance — force-accept our relocated nodes so Miner buildings snap to them (never fracking or gas nodes, which would crash or must keep native rules).
- Resource scanner — strip hidden originals from the scan clusters at the source, so an emptied original doesn't leave a phantom ping or map marker.
- Portable miner dispenser — lift the spawn onto the rock surface the player aimed at instead of the terrain beneath it.
Radiation is positional data in AFGRadioactivitySubsystem. Hiding an original
removes its emitter (otherwise it would keep irradiating an empty spot), and
a relocated/restored node re-registers radiation at its new location — so
radiation moves with the shuffle instead of leaving invisible hot zones.
- A node with a miner/extractor/portable miner is never retyped, moved, purity-changed, or deactivated — enforced at roll time and re-checked each pass.
- Re-rolling is never implicit: only the edge-triggered
RerollNowone-shot toggle triggers it (live mid-session or on next load), and it clears itself. Occupied nodes are carried and pinned through a re-roll. - With
Enabled=falsethe subsystem does nothing; hidden originals return and no new nodes spawn.
NodeShuffle.Here— logs the player's position, the ground slope at their feet (and whether the cliff gate accepts it), and a 300 m census of nearby nodes and originals (state, distance, radiation).NodeShuffle.SeedHere— marks a roofed spot (cave/arch/overhang) as a cave-placement seed.
Config/AccessTransformers.ini friends ANodeShuffleSubsystem (and, for the
hooks, FNodeShuffleModule) to the engine members the design needs:
AFGResourceNode/AFGResourceNodeBase (overrides, radioactivity, mesh actor,
placement flags), AFGResourceNodeManager (registration), AFGResourceScanner
(cluster refresh + phantom-ping strip), AFGRadioactivitySubsystem (emitter
verification), AFGPortableMinerDispenser, and the Miner extractor hologram.
See the README for the full in-game config table and the optional
Configs/*.json data files (generated-node report, custom node list, rock-pattern
overrides, and the learned terrain maps).