Skip to content

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 no director section 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

yaml
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)
    ...
FieldDefaultMeaning
generation.enabledtrueThe 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-size512Spatial index bucket size (for fast queries of anchor positions); generally left alone.
max-per-tick4How many candidate chunks the candidate-processing task handles at most each time (throttling).
max-millis-per-tick3Millisecond 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-ticks5The 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 raise max-per-tick / max-millis-per-tick or lower director.live.chance.


2. Full Generation Director Fields

yaml
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

FieldDefaultMeaning
enabledtrueOn = director takes over (weighted draw + global density); off = falls back to each template rolling its own probability.
notify-adminstrueWhen 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-chance80The 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."

FieldDefaultMeaning
enabledtrueLive-world master switch.
interval10mHow often to attempt around online players (this is the "refresh interval"; for debugging change to 1m / 30s; minimum 20 seconds).
chance0.25The probability per player per attempt (higher = spawns more often).
min-radius80Pick sites within this ring around the player (avoiding right at the feet, which is too close and jarring).
max-radius220Generally 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, live computes 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)

FieldDefaultMeaning
region-size512The side length (blocks) of the grid square used for density statistics.
max-per-region3At most how many ruins per region-size×region-size square (0 = unlimited).
min-spacing48The 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 the min-spacing range. If the profile / template wrote min-distance-others, its own value is used (which may be lower than the global fallback); only if unwritten (0) is the global min-spacing used. min-distance-same is the extra spacing "from the same kind of ruin," to prevent same-type clustering.

2.4 Fade-and-Regen regen-hours

yaml
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 than regen-hours hours and with no player nearby (within about 64 blocks), is snapshot-restored to the original terrain by the generation director, its anchor turned to RECYCLED, 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-hours only frees a density slot and recycles the anchor record, but does not restore blocks.

2.5 Weights weights

yaml
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's generation.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.enabled and world / dimension matching" → find the batch with the highest priority (smallest priority) → 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 from probability's numerator/denominator.


3. Generation Director On vs Off Comparison

DimensionDirector OnDirector Off (backward-compatible)
Who is chosena pile of ruins compete by weight for oneeach template independently rolls its own probability
Triggernew chunk (chunk-chance) + live world (live)each template rolls its own on a new chunk
Global densityuniformly constrained by density.region-size/max-per-region/min-spacingno global density (only each template's own min-distance-*)
Live worldold maps grow new ruins toonone (no more spawning once explored)
Fade-and-regenregen-hours fades after clearing to free slotsnone
Suitsthe 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)

yaml
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)
FieldDefaultMeaning
snapshot-restoretrueSnapshot 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-volume500000Snapshot 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-hours works together with snapshot-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 on regen-hours without snapshots only frees a slot and won't restore the blocks.


5. Debug Commands

CommandPurpose
/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 gentestInitiates 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 gentest and /qr scatter require the generation director to be on (director.enabled: true); with it off they prompt that the director is not enabled. /qr why and /qr spawn are 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 · suggested assets/qr-why.png

For full commands and permissions see Command Reference; for troubleshooting routines see Diagnostics and Troubleshooting.


6. Tuning Quick Reference

I wantChange
More ruins overallLower chunk-chance, raise live.chance, raise density.max-per-region
Fewer / sparser ruins overallRaise chunk-chance, lower live.chance, lower max-per-region, raise min-spacing
Old maps keep growing new ruins tooTurn on live.enabled, shorten live.interval
A certain ruin more common / rarerAdjust its template's generation.weight (or explicitly give it a weight / spawn-chance in weights)
Periodically clear cleared ruins to free slotsregen-hours: 24 + cleanup.snapshot-restore: true
No spawning near the spawn pointAdd 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:

text
/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)

  1. 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.
  2. 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.
  3. 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~2000 and watch the load; enlarge only if it holds up; don't jump straight to radius 10000 and choke the server to death.
  4. 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 continue for resumable progress, and even continues after a restart.
  5. 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.enabled to false during pre-generation (or set director.chunk-chance very high and turn live.enabled off), 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-tick self-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 later live top-up spawning).
  6. Don't forget after pre-generation: change the temporarily adjusted generation.enabled / chunk-chance / live back to normal values and /qr reload.
text
① 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