Natural Generation and Pre-loading (Generation Director / Density / Regen)
Previous: Placement Profiles (generators/*.yml) · Next: Procedural Generation (Palette / Sub-structure Variants) Related: Ruin Template · Full config.yml Reference · Command Reference
A placement profile governs "where a single ruin can land on its own"; the generation director governs "of all these ruins across the world, who, where, and how often one appears." It is the scheduling brain that turns recipes into anchors: all generatable ruins go through a weighted competitive draw, are constrained by global density (per-region cap, minimum spacing), can live-tick around players in already-explored worlds, and can let cleared ruins fade and regenerate over time to free up slots.
Tip
Generation director on / off are two world views:
- On (recommended): a pile of ruins compete for one slot, density is uniformly controllable, and the world will "grow alive."
- Off (backward-compatible): falls back to the old logic of "each template rolls independently by its own
generation.probability"—each spawns on its own, no global density. An old server whose config has nodirectorsection automatically takes this path.
🖼️ [Image placeholder] A scheduling pipeline diagram of "new chunk / live world trigger → generation director draws a template → density + spacing validation → landing-spot placement" · suggested
assets/director-pipeline.png
1. Two Layers of Switches: the generation Master Switch + the director Sub-switch
generation:
enabled: true # natural generation master switch (off = only /qr spawn manual placement)
grid-bucket-size: 512 # spatial index bucket size
max-per-tick: 4 # max candidate chunks processed each time (throttling, anti-lag)
max-millis-per-tick: 3 # ★millisecond budget per tick: yield this tick on timeout (more pressure-resistant than a fixed count)
period-ticks: 5 # interval (ticks) of the candidate-processing task
director:
enabled: true # director takes over: weighted draw + global density (old server with no this section = auto fallback to old logic)
...| Field | Default | Meaning |
|---|---|---|
generation.enabled | true | The natural-generation master gate. Turning it off stops the entire natural generation; only /qr spawn / /qr stage manual placement remains. It also constrains the generation director—if the master switch is off, the director won't generate even if it's on. |
grid-bucket-size | 512 | Spatial index bucket size (for fast queries of anchor positions); generally left alone. |
max-per-tick | 4 | How many candidate chunks the candidate-processing task handles at most each time (throttling). |
max-millis-per-tick | 3 | Millisecond budget per tick: yields this tick once processing exceeds this cumulative time, more resistant to generation spikes and prevents freezing the server than a fixed count. |
period-ticks | 5 | The run interval (ticks) of the candidate-processing task. |
⚙ Candidates aren't placed on the spot but queued and landed across frames: a trigger (new chunk / live world) only stuffs "world + chunk + template" into the candidate queue, and a background task processes it throttled by
max-per-tick/max-millis-per-tick/period-ticks. When the queue is full (the default cap is fairly large) it discards the excess candidates and warns once a minute—at that point raisemax-per-tick/max-millis-per-tickor lowerdirector.live.chance.
2. Full Generation Director Fields
director:
enabled: true
notify-admins: true
chunk-chance: 80
live:
enabled: true
interval: 10m
chance: 0.25
min-radius: 80
max-radius: 220
density:
region-size: 512
max-per-region: 3
min-spacing: 48
regen-hours: 24
weights: []2.1 Top Level
| Field | Default | Meaning |
|---|---|---|
enabled | true | On = director takes over (weighted draw + global density); off = falls back to each template rolling its own probability. |
notify-admins | true | When any ruin generates naturally, whisper the coordinates + ID to online admins (qinhruins.admin). A killer tool for debugging and finding ruins; turn it off if it's too noisy. |
chunk-chance | 80 | The probability denominator for a new chunk to attempt generation: each newly loaded chunk has a 1/N chance to initiate one generation attempt. 0 = exploration doesn't actively spawn, relying purely on live below. Lower it to spawn more (e.g. 40), raise it to spawn less. |
2.2 Live World live (already-explored worlds also grow new ruins)
chunk-chance only triggers on "new chunks," and an old map explored long ago no longer has new chunks. live lets the generation director periodically attempt generation around online players within already-loaded chunks too, so "old worlds also grow new ruins."
| Field | Default | Meaning |
|---|---|---|
enabled | true | Live-world master switch. |
interval | 10m | How often to attempt around online players (this is the "refresh interval"; for debugging change to 1m / 30s; minimum 20 seconds). |
chance | 0.25 | The probability per player per attempt (higher = spawns more often). |
min-radius | 80 | Pick sites within this ring around the player (avoiding right at the feet, which is too close and jarring). |
max-radius | 220 | Generally slightly larger than view distance—so when the player walks closer, the new ruin is "already right there." |
⚙ Site selection is only in loaded chunks: each time,
livecomputes a random point within the[min-radius, max-radius]ring for players who hit the probability; if the chunk containing that point is not loaded, it's skipped (it doesn't force-load, to avoid lag), and only if loaded does it initiate a candidate. So ruins tend to appear at the edge of the player's activity range.
2.3 Global Density density (the world's overall ruin density)
| Field | Default | Meaning |
|---|---|---|
region-size | 512 | The side length (blocks) of the grid square used for density statistics. |
max-per-region | 3 | At most how many ruins per region-size×region-size square (0 = unlimited). |
min-spacing | 48 | The minimum spacing between any two ruins, a face-to-face fallback (0 = unlimited). |
⚙ How density and spacing are judged: before placement the generation director first counts how many non-recycled anchors already exist in the square containing the landing spot, and rejects if it reaches
max-per-region; then it checks whether there's any other anchor within themin-spacingrange. If the profile / template wrotemin-distance-others, its own value is used (which may be lower than the global fallback); only if unwritten (0) is the globalmin-spacingused.min-distance-sameis the extra spacing "from the same kind of ruin," to prevent same-type clustering.
2.4 Fade-and-Regen regen-hours
regen-hours: 24 # a cleared ruin is snapshot-restored after 24h when no one is nearby, freeing a density slot- A cleared (
CLEARED) ruin, after more thanregen-hourshours and with no player nearby (within about 64 blocks), is snapshot-restored to the original terrain by the generation director, its anchor turned toRECYCLED, freeing a density slot so the world grows new ruins. 0= never fades (cleared ruins stay in the world permanently as landmarks).- ⚠️ Actually restoring blocks requires turning on
cleanup.snapshot-restore(see §4). When snapshots aren't on,regen-hoursonly frees a density slot and recycles the anchor record, but does not restore blocks.
2.5 Weights weights
weights:
[]
# - { template: ancient_tower, weight: 30 } # the larger the number, the more common
# - { template: sunken_crypt, weight: 10 }- Leave empty (
[]) = automatically convert weights from each template'sgeneration.weight/generation.probability—no need to register each one here, and newly added ruins automatically take part in the draw. - When you want to centralize and force-control the lineup, explicitly list templates + weights here; once you write a non-empty
weights, only the listed templates take part in the draw (unlisted ones don't spawn).
⚙ The draw process: at a candidate spot, first take all templates that are "
generation.enabledand world / dimension matching" → find the batch with the highest priority (smallestpriority) → among that batch, draw one by weighted roulette. With weights left empty, a template's weight =generation.weight(used when >0), otherwise converted into an integer weight fromprobability'snumerator/denominator.
3. Generation Director On vs Off Comparison
| Dimension | Director On | Director Off (backward-compatible) |
|---|---|---|
| Who is chosen | a pile of ruins compete by weight for one | each template independently rolls its own probability |
| Trigger | new chunk (chunk-chance) + live world (live) | each template rolls its own on a new chunk |
| Global density | uniformly constrained by density.region-size/max-per-region/min-spacing | no global density (only each template's own min-distance-*) |
| Live world | old maps grow new ruins too | none (no more spawning once explored) |
| Fade-and-regen | regen-hours fades after clearing to free slots | none |
| Suits | the vast majority of servers (controllable density, world grows alive) | minimalism, wanting to fully replicate old behavior, or an old server with un-migrated config |
4. Snapshots and Regeneration (cleanup)
cleanup:
snapshot-restore: true # snapshot the original terrain before generating a structure, restore on removal/fade
max-snapshot-volume: 500000 # snapshot volume cap (block count): skip the snapshot above this value (removal doesn't restore but doesn't lag). 0 = unlimited (dangerous)| Field | Default | Meaning |
|---|---|---|
snapshot-restore | true | Snapshot the original terrain before generating a structure, and restore the blocks when /qr remove or regen-hours fades it. Turning it off saves disk, but removal / fade won't restore blocks. |
max-snapshot-volume | 500000 | Snapshot volume cap (block count). Synchronously snapshotting an oversized structure (in the millions) would freeze the main thread and get the server killed by the watchdog → skip the snapshot above this value (removal doesn't restore but doesn't lag). 0 = unlimited (dangerous). |
🔗
regen-hoursworks together withsnapshot-restore: to make a cleared ruin "truly fade, with the terrain restored as before," both must be on—regen-hours>0(set the fade timing) +snapshot-restore: true(a snapshot saved at generation time is what makes restoration possible). Turning onregen-hourswithout snapshots only frees a slot and won't restore the blocks.
5. Debug Commands
| Command | Purpose |
|---|---|
/qr why <template> | Diagnostics: under your feet, lists item by item the pass / fail + reason of each landing-spot check for that template (switch / world / dimension / coordinate box / height / ground / water / biome / flatness / spacing / density). Use it first thing when "it's configured but won't generate." |
/qr gentest | Initiates about 25 generation attempts near you using the director's live-world algorithm, reporting how many times each of "succeeded N / blocked by density / no template drawn / placement failed" occurred. See the actual hit rate. |
/qr scatter <radius> <amount> | Batch pre-generate around you: radius 100–20000, amount 1–200. Scatters points within the radius and attempts placement until the target amount is filled or attempts run out. Fill an area with ruins in one go to see the effect (admin broadcasts are silenced during generation). |
/qr spawn <template> | Bypass all site-selection checks and directly place a specified ruin under your feet (manual placement, not constrained by the generation director / density). |
⚙ Both
/qr gentestand/qr scatterrequire the generation director to be on (director.enabled: true); with it off they prompt that the director is not enabled./qr whyand/qr spawnare not subject to this.
🖼️ [Image placeholder] A screenshot of the item-by-item check list (green checks / red crosses) output by
/qr why ancient_tower· suggestedassets/qr-why.png
For full commands and permissions see Command Reference; for troubleshooting routines see Diagnostics and Troubleshooting.
6. Tuning Quick Reference
| I want | Change |
|---|---|
| More ruins overall | Lower chunk-chance, raise live.chance, raise density.max-per-region |
| Fewer / sparser ruins overall | Raise chunk-chance, lower live.chance, lower max-per-region, raise min-spacing |
| Old maps keep growing new ruins too | Turn on live.enabled, shorten live.interval |
| A certain ruin more common / rarer | Adjust its template's generation.weight (or explicitly give it a weight / spawn-chance in weights) |
| Periodically clear cleared ruins to free slots | regen-hours: 24 + cleanup.snapshot-restore: true |
| No spawning near the spawn point | Add spawn-region { exclude: true } to the profile / template (see Placement Profiles §7) |
| Fully replicate the old "each spawns its own" | director.enabled: false |
7. World Pre-generation (Chunky) and Anti-lag Advice ⚠️ Must-read
QR's ruins follow chunks: a generation attempt is triggered only when a new chunk loads (chunk-chance) or a player is active (live). So "pre-generate the world first, then let players in to explore" gives the best experience—with chunks already formed, ruins are already in place before players reach them, entering the map doesn't lag, and /qr scatter can also blanket an area with ruins in one go.
7.1 What Plugin to Use for Pre-generation
We recommend Chunky (free, lightweight, mainstream, natively compatible with Paper), currently the most universal world pre-generation plugin. A common workflow:
/chunky world world # select the world to pre-generate
/chunky center 0 0 # set the center (usually the spawn point)
/chunky radius 3000 # set the radius (blocks)—★this number directly decides the time cost and lag
/chunky start # start pre-generating (runs in the background, can /chunky pause)There are also long-standing options like WorldBorder; the principle is the same. This section's notes apply to all of them.
7.2 ⚠️ Pre-generation Will Be Very Laggy and Slow—That's the Cost of Pre-generation Itself, Not a QR Problem
🔴 Important understanding: the lag, TPS drop, and long duration during the pre-generation phase come from the server frantically generating vanilla terrain chunks (Chunky doing its work), not from QinhRuins. QR's own ruin placement is frame-split and throttled (
max-millis-per-tick/ candidate queue, see §1); it yields this tick on timeout and won't freeze the server. Please make sure server owners and players understand this, and don't blame the pre-generation lag on QR.
7.3 Anti-lag Checklist for Server Owners (follow this and you'll basically be fine)
- Give notice in advance: during pre-generation the server will noticeably lag and TPS will drop, so notify players in advance via group chat / announcement, and agree on a low-population time slot (e.g. late night) to run it.
- Mention the resource world specially: if you're pre-generating / resetting the resource world, be sure to notify players in advance that "the resource world is about to close / reset" so everyone can move their belongings and building materials out, avoiding property loss and complaints.
- If the hardware is weak, shrink the radius: with average server hardware, don't blanket too much at once. First run a batch at
radius 1000~2000and watch the load; enlarge only if it holds up; don't jump straight toradius 10000and choke the server to death. - Run in batches: split a large map into several runs—after this batch finishes and TPS stabilizes, run the next. Chunky supports
/chunky pause//chunky continuefor resumable progress, and even continues after a restart. - Lighten QR's load before pre-generating: Chunky loads a massive number of new chunks in a short time, and for each new chunk QR initiates a generation attempt by
chunk-chance, so the candidate queue may be instantly flooded (the console warns once a minute). Two approaches:- The worry-free approach: temporarily set
generation.enabledtofalseduring pre-generation (or setdirector.chunk-chancevery high and turnlive.enabledoff), and once pre-generation finishes, turn it back on and use/qr scatter <radius> <amount>to scatter ruins into the already-laid area manually and controllably, in one go. - The hands-off approach: keeping the defaults is fine too—QR's
max-millis-per-tickself-throttles and won't freeze the server; ruin placement is just dragged out by throttling during pre-generation, and excess candidates are discarded when the queue is full (which doesn't affect laterlivetop-up spawning).
- The worry-free approach: temporarily set
- Don't forget after pre-generation: change the temporarily adjusted
generation.enabled/chunk-chance/liveback to normal values and/qr reload.
7.4 Recommended Order for Pre-generation + QR
① Announce to players (especially say "about to close/reset" for the resource world)
② (Optional) temporarily generation.enabled: false, /qr reload, to lighten QR's load
③ Chunky batch pre-generation: start with a small radius → watch TPS → enlarge / run the next batch
④ Pre-generation finished, TPS stabilized
⑤ Set QR generation config back, /qr reload
⑥ /qr scatter <radius> <amount> to lay ruins into the generated area in one go (or hand it to chunk-chance/live to grow slowly)
⑦ /qr near / /qr gentest spot-check, open the server🖼️ [Image placeholder] Chunky pre-generation progress + concurrent TPS curve, annotated "lag comes from terrain generation, not QR" · suggested
assets/pregen-chunky.png
Next Steps
- A single ruin's own landing-spot rules → Placement Profiles
- The template-side
generation.weight/priority/spawn-chance→ Ruin Template §5 - Stitching multiple small structures into a large ruin / sub-structure variants → Procedural Generation
- All config.yml fields → Full config.yml Reference