DST worldgen
Two separate jobs, often confused:
- Options — the
overridestable on a level (hounds = "never"). Each key has a fixed vocabulary; a wrong value fails silently or crashes worldgen. →references/worldgen-overrides.md,references/worldsettings-overrides.md - Structure — levels, task sets, tasks, rooms; what actually shapes the map.
→
references/levels-tasksets-tasks.md - Verification — whether the generated world still contains every link a
boss or system needs, checked against a real save.
→
references/boss-prerequisites.md,references/save-files.md,scripts/save_inspect.lua
Find the game's scripts first
Everything here is verified against DST's shipped, unobfuscated
data/scripts/. Read the vanilla definition before overriding it — a PreInit
assignment replaces a field wholesale rather than merging, so the original
file is the only record of what you are discarding.
Typical locations:
~/.steam/steam/steamapps/common/Don't Starve Together/data/scripts # Linux
~/Library/Application Support/Steam/steamapps/common/Don't Starve Together/data/scripts # macOS
C:\Program Files (x86)\Steam\steamapps\common\Don't Starve Together\data\scripts # Windows
If you cannot find them, ask the user for the path rather than guessing at option names or values.
Part 1 — options
Two categories, and the distinction matters:
| worldgen | worldsettings | |
|---|---|---|
| category | LEVELCATEGORY.WORLDGEN |
LEVELCATEGORY.SETTINGS |
| consumed by | map/forest_map.lua during generation |
worldsettings_overrides.lua → TUNING |
| effect | terrain, which prefabs get placed, map shape | spawn rates, timers, damage, boss behaviour |
| changeable later | no — requires a new world | yes |
| example | spiders, world_size, task_set |
hounds, antliontribute |
Both go in the same overrides table on a level; AddModLevel splits them for
you (map/levels.lua:720-744).
-- modworldgenmain.lua
AddLevel(LEVELTYPE.SURVIVAL, {
id = "MYMOD_FOREST",
name = "My World",
location = "forest", -- required, no default
version = 2,
overrides = {
task_set = "MYMOD_TASKSET", -- required on every level
world_size = "small", -- worldgen
spiders = "never", -- worldgen
hounds = "never", -- worldsettings
antliontribute = "never", -- worldsettings
},
})
To change an option on a vanilla level instead of shipping your own:
AddLevelPreInit("SURVIVAL_TOGETHER", function(level)
level.overrides.hounds = "never"
level.overrides.antliontribute = "never"
end)
The vocabularies
Look the key up in the reference tables — never assume. The three common lists:
| vocabulary | values | used by |
|---|---|---|
worldgen_frequency_descriptions |
never rare uncommon default often mostly always insane |
most worldgen keys |
frequency_descriptions |
never rare default often always |
most worldsettings keys |
yesno_descriptions |
never default |
roads, terrariumchest, mutated_hounds, … |
The worldgen list is 8 values, the worldsettings list is 5 — uncommon,
mostly and insane are not valid for hounds, antliontribute or any
other worldsettings key. The tables in references/ give the exact list per
option, per world.
"none" is almost never what you want. It is not in any of the three lists
above; only a handful of keys with bespoke vocabularies accept it —
petrification (none/few/default/many/max), extrastartingitems, and the
options using the enableddisabled / ghostenabled / resetime lists
(portalresurection, basicresource_regrowth, ghostsanitydrain, …), several
of which default to "none". To switch something off, the value is
"never".
ocean_waterplant and ocean_seastack take the ocean_-prefixed vocabulary
(ocean_never … ocean_insane). ocean_otterdens, ocean_wobsterden,
ocean_shoal and ocean_bullkelp take the plain one — they live in the
worldgen animals/resources groups (map/customize.lua:303,372).
Invalid values fail three different ways — only one is loud
| the key | what happens |
|---|---|
a worldgen key whose prefabs are in MULTIPLY_PREFABS (forest_map.lua:150-166) |
hard Lua crash: MULTIPLY[v] is nil, then MULTIPLY_PREFABS[prefab](v, nil) does if mult > 1 → attempt to compare nil with number at forest_map.lua:133 |
any other worldgen key in TRANSLATE_TO_PREFABS |
MULTIPLY[v] nil → translated_prefabs[prefab] = nil → silent no-op (forest_map.lua:277-291) |
| a worldsettings key | OverrideTuningVariables(nil) returns immediately → silent no-op (worldsettings_overrides.lua:2-12) |
The crash case is unrecoverable: it errors out of worldgen_main.lua before the
5-attempt retry loop (worldgen_main.lua:418-439) can run. On the caves shard
that means worldgen never finishes, the shard never connects, and the client
hangs at init. Prefabs that trigger it: slurper_spawner, worm_spawner,
monkeybarrel_spawner, tumbleweedspawner, buzzardspawner,
wobster_den_spawner_shore, oceanfish_shoalspawner, seastack_spawner_*,
waterplant_spawner_rough, boat_otterden.
touchstone and boons index MULTIPLY directly too
(worldgen_main.lua:358-372) and crash the same way.
Regenerating the tables
references/*-overrides.md are generated, not hand-written. After a game patch:
cd "<path to data/scripts>"
luajit /path/to/scripts/dump_options.lua --format=md # or --format=tsv
It loads the game's real map/customize.lua in a stubbed environment, so the
output is whatever the current build actually accepts.
Part 2 — structure
Read references/levels-tasksets-tasks.md for the field-by-field detail. The
essentials:
Never redefine, always PreInit. AddTask and AddRoom assert on a
duplicate name (map/tasks.lua:58, map/rooms.lua:20); the mod versions
AddModTask/AddModRoom only moderror and silently drop your definition.
So to change vanilla content use AddTaskPreInit / AddRoomPreInit /
AddLevelPreInit / AddTaskSetPreInit, and to add new content use a fresh,
mod-namespaced name.
Worldgen hooks belong in modworldgenmain.lua, not modmain.lua —
worldgen runs in its own Lua state. Runtime-only additions (components,
spawners) go in modmain.lua.
A broken lock/key chain does not crash. Story:LinkNodesByKeys attaches a
random task when nothing is unlockable (storygen.lua:596-598), so the map
silently loses its intended shape. Pick locks that some included task's
keys_given actually provides — LOCKS_KEYS in map/lockandkey.lua says what
opens what.
valid_start_tasks does not pick the topology root. It only places the
player portal (storygen.lua:916-940). The root is a random task with empty
or LOCKS.NONE locks (Story:_FindStartingTask, storygen.lua:854-862).
Task-set bookkeeping must stay consistent. set_pieces[*].tasks must name
tasks actually in the set, and task-set required_prefabs must be deliverable
by some room or setpiece in it — otherwise worldgen PANICs and rerolls, or
worse, ships a world missing content. Klei's own warning sits at
map/tasksets/caves.lua:86.
Shrinking a world
In order of effect: fewer tasks in the task set (each task ≈ one biome blob) →
fewer rooms per task (fixed room_choices counts, not
math.random(SIZE_VARIATION)) → world_size = "small" →
background_node_range = {0, 1} → background_room = "BGImpassable" so ocean
eats the margins → trim contents.distributeprefabs densities.
After changing worldgen
- Host a fresh world and confirm it generates at all.
- Regenerate 3–5 seeds. Lock-chain mistakes show up as randomly shaped maps, not errors.
- Grep the server log for
moderror(dropped duplicate task/room definitions) and for the[Story Gen]lines, which name the starting task and the linker. - Ocean setpiece placement also fails silently —
Ocean_PlaceSetPieces(map/ocean_gen.lua) prints only a total like[Ocean] Placed 4 of 6 ocean set pieces.and never says which it dropped. - For a cave shard, check the master log for
Skipping portal[1] (no available shard [Caves] connected)— that means cave worldgen crashed, usually an invalid override value. - Confirm the result in the save, not just the log:
scripts/save_inspect.luacounts prefabs, gives positions and tiles, and dumps topology (references/save-files.md).
Part 3 — boss chains in a trimmed world
Several bosses are unlocked by runtime chains rather than placed by worldgen, so
cutting tasks or writing custom setpieces can break them with no error at all —
the break shows up days into a playthrough. references/boss-prerequisites.md
has the full chains with citations. The traps found so far:
- Celestial Champion — altar geometry. The three altars are socketed into
moon_fissures and only link if all are within 20 units, pairwise ≥ 2.5 apart and not near-collinear. A custom layout that places fissures can dead-end the whole chain. - Celestial Champion — moon storm hang.
StartMoonstormloops until it finds 4 connected land nodes that are neitherlunacyareanorsandstorm, with no retry cap. A world with eligible nodes but no such cluster hangs the server when the altars link. - Celestial Champion — the crown. Crab King only drops
moon_altar_crownif it was socketed with Pearl's pearl, so Pearl's island and her house materials (marble, moon rock, cookie cutters) are part of the chain. - Ancient Fuelweaver — Fossil Fragments. 8 are needed. Without spider-hole
rooms, stalagmites give one 10% roll each and do not regrow;
wormattacks = "never"also removes the worm boss's drop. - Cross-shard bosses. Scrappy Werepig (surface junk yard) only appears after
the cave Nightmare Werepig is defeated — check
shard_network.persistdata.shard_daywalkerspawner.locationin the save.