Compatibility
Minecraft: Java Edition
Platforms
Tags
Creators
Details
ViewDistanceTweaks
Sets view and simulation distance per world, on its own, based on what the server can handle at that moment.
The number in server.properties has to be safe enough for your worst minute, which means it's too low for every other minute. Ten people on at 3am and you're saving distance nobody asked you to save. Sixty people at an event and that same number is why the TPS meter is on the floor.
This moves it for you. It watches MSPT and how many chunks are actually loaded, gives distance back while there's headroom, and takes it away before players notice. Each world has its own floor and ceiling, so the Nether can sit at 6 while your main world runs at 16.
Drop the jar in and restart. The defaults do something sensible without any setup.
Requirements
- Paper 1.21.x / 26.x or a fork (Purpur, Pufferfish, Leaves)
- Folia works, tested on 1.21.11
- PlaceholderAPI - optional, only if you want the placeholders
- EssentialsX - optional, only to read AFK state if you turn AFK throttling on
It loads on Spigot and Bukkit, but a few parts need Paper's API and switch themselves off with a line in the console if it isn't there: per-player distances, pre-generation and pre-loading. The distance engine itself still works, it just falls back to a coarser tick-time reading.
How it decides
MSPT is sampled every tick. Every ticks-per-check (30 seconds by default) each world is looked at once and the answer is up, down, or leave it alone.
Four modes:
proactive- you give it a chunk budget (how many ticking and non-ticking chunks the server should carry in total) and it solves for the distance that fitsreactive- ignores chunk counts, just watches MSPT against two thresholdsmixed- runs both and takes whichever is more pessimistic. Right answer for most servers, and the defaultoff- never touches view or simulation distance. For servers that only want the pre-generation and pre-loading parts
On top of the mode itself:
It won't repeat an increase that already hurt. A linear regression over the last 30 minutes of chunk-count/MSPT pairs predicts what the next step up would cost. If that lands above the decrease threshold, the increase doesn't happen. That's what breaks the usual raise-lag-drop-raise loop.
It watches the spread, not just the average. A server can average a comfortable 30ms while single ticks bounce between 8 and 55, and nobody enjoys playing on that. Standard deviation across Paper's last 100 tick samples catches it, so above variance-threshold the server counts as overloaded even though the average looked fine.
Down can be faster than up. max-increase-step and max-decrease-step are separate. 1 and 4 is a common pair: climb one notch at a time, bail out in one go. revert-increase-if-overloaded also re-checks right after an increase and rolls it straight back if it hurt.
Simulation first, view second. With prioritize-simulation-distance on, view distance isn't touched until simulation distance is already at the world minimum. Simulation is the one that costs TPS, view is the one players see, so spend the cheap one first.
Safety nets
MSPT isn't the only thing worth reacting to. Everything here except the memory safeguard is off by default.
- Memory safeguard - free heap under
min-free-percentblocks any increase, whatever MSPT says. A server can idle at 20ms and still be one ring of chunks away from spending every tick in GC. On by default at 15%. - Chunk generation safeguard - counts chunks genuinely being generated, not ones read off disk. Past a per-second rate it stops increases and can clamp view distance until generation calms down. This is the someone-just-flew-an-elytra-into-fresh-terrain case.
- AFK throttling - a player standing at spawn doesn't need 16 chunks. Their personal distances drop to the AFK values and what they free up goes back into the budget. Uses EssentialsX AFK state if it's installed so
/afkcounts immediately, otherwise Paper's idle timer. - Density scaling - twenty players at one farm load nearly the same chunks, but the proactive formula assumes twenty separate rings and starves the world for nothing. When enough players sit close together the world gets bonus distance, scaled by how much of its population is in that group.
- Complexity weighting - an ocean chunk is free, a chunk with a 500-hopper sorter can eat a tick by itself, and
(2d+1)²counts both as one. Samples a bounded number of chunks around players and derives an average multiplier, so a redstone-heavy world gets a smaller budget than one full of water. - Emergency relief - last resort. World is at minimum sim and view, MSPT is still over the line, nothing left to take. So it sheds thunderstorms and the daylight cycle instead. Both come back when MSPT recovers, on
/vdt disable, and on shutdown. The world's original weather and gamerule state are remembered on the way in. - Startup grace - holds a low distance for the first few minutes after a restart. Keeps a cold server from getting hit at max distance while it's still finding its feet.
Per-player distances
Two permission families set a distance for one player without touching the world:
viewdistancetweaks.fixed.<n> view distance
viewdistancetweaks.simfixed.<n> simulation distance
Values 2 to 32, highest wins if someone holds several, and taking the permission away hands them back to the world value without a relog. Both have to be switched on in the config first. They need different prefixes, otherwise one node would set both at once.
The view distance one is client-side and costs the server nothing. The simulation one genuinely does cost, so keep it for staff instead of selling it as a donor perk.
/vdt status --fixed shows who currently holds what, and whether it came from a permission or the AFK throttle.
Presets and profiles
Two different things with annoyingly similar names, sorry about that.
/vdt preset <small-server|large-network|performance-first> writes into your config.yml. It's a starting point if you don't want to tune forty values by hand, not a final answer.
/vdt profile <name> lays a temporary overlay over whatever is configured, defined under profiles: in the config. Nothing is written to disk and normal puts it all back. It's there so you can move to event settings for two hours without editing config.yml in the middle of an event. max-view-distance-cap and max-simulation-distance-cap are hard ceilings applied after every other rule, so a cap of 8 really is 8.
Pre-generating terrain
Generating a chunk is the most expensive thing a server does, and the one everything else here can only react to. Building the terrain in advance removes the cause: what already exists gets read from disk, which is cheap, and the chunk-generation safeguard never has to trip.
Turn on idle-pregeneration and it runs whenever the last player logs off, pausing the moment somebody joins. Or drive it yourself:
/vdt pregen start world 500 centred on the world spawn
/vdt pregen start world 500 --here centred on where you're standing
/vdt pregen status progress, rate and ETA
/vdt pregen stop pause for good, progress kept
/vdt pregen cancel --confirm throw the progress away
Three things keep it from becoming the problem it solves. It throttles against the same rolling MSPT average the distance engine uses, so the two never disagree about whether the server is busy. It stops rather than slows when free heap runs low, because a slower path to an OutOfMemoryError is still an OutOfMemoryError. And it writes progress to disk, so a crash or a nightly reboot picks up where it left off.
It also excludes its own chunks from the chunk-generation safeguard. Without that, starting a run would make the plugin think players were outrunning the generator and clamp everyone's view distance. The plugin fighting itself.
Watch the radius. The workload is (2*radius+1)², so 250 is about 251,000 chunks and 1000 is over four million. Match it to your world border rather than picking a round number.
Predictive pre-loading
Loads chunks along the path of a fast-moving player so arriving somewhere doesn't stall the tick.
Worth being clear about what this is: it makes chunks ready server-side. Nobody sees further, because the client still only receives what its own view distance covers. What you notice is the absence of a hitch, not a wider horizon.
It follows the movement vector rather than where the player is looking. During elytra flight the view swings around constantly while the flight path stays straight, and it's the path that decides which chunks the server will need. Look direction can be blended in at half weight, but it's off by default.
Generation is off by default too and should stay that way. With it on, this stops being a loader and becomes a second, uncontrolled terrain generator driven by whoever is flying fastest, which is the exact load spike the rest of the plugin exists to contain. Unexplored terrain is what pre-generation is for.
Configuration
Click to expand the full config.yml
# ============================================================
# ViewDistanceTweaks - LucasTHCR Edition
# Compatible with Paper 1.21.* - 26.*+
# ============================================================
# Plugin version - DO NOT CHANGE
version: 2
# Master switch. The plugin manages sim/view distance out of the box with the
# defaults below; set this to false if you want to configure everything first
# (or use /vdt disable in-game).
enabled: true
# ============================================================
# UPDATES
# ============================================================
# Tell you when a newer release exists? One async request on startup, it never
# blocks the server, and nothing is downloaded. Turn it off if the machine has
# no outbound internet access. /vdt update always checks on demand regardless.
# (This was called update-checker before 2.8.3; the old name still works.)
update-notify: true
# Installs new releases on its own. OFF by default, you decide each update.
# Set enabled to true if you would rather not think about it again.
#
# This only ever touches ViewDistanceTweaks. No other plugin is looked at, let
# alone replaced: the only file it can write is named after this plugin's own
# jar.
#
# The running jar is never modified. The download goes into Bukkit's update
# folder (plugins/update/ by default, see update-folder in bukkit.yml) and the
# server installs it on the next START. So: downloaded now, active after your
# next restart. No plugin can safely swap its own jar underneath a running
# server, and this one does not pretend otherwise.
#
# Before anything is written, the download is verified against the SHA-512 hash
# Modrinth publishes for that file. On a mismatch it is discarded, not installed.
#
# Needs update-notify above to stay true, since this acts on its result.
# You can always fetch an update on demand with /vdt update download, whatever
# this is set to.
auto-update:
enabled: false
# Only accept releases that list the Minecraft version this server runs.
# Leaving this true is strongly recommended. false lets the plugin install a
# build that was never tested against your server version.
require-matching-game-version: true
# Which Modrinth release channels may be installed: release, beta, alpha.
# Add "beta" if you want fixes early and accept the risk that comes with it.
accepted-release-types:
- release
# ============================================================
# IDLE PRE-GENERATION (optional, off by default)
# ============================================================
# Builds terrain ahead of time, so players never wait for it. Generating a chunk
# is the most expensive thing a server does and the one this plugin can otherwise
# only react to. Terrain that already exists is read from disk instead, which is
# cheap, and the chunk-generation safeguard never has to trip at all.
#
# Three rules keep it from becoming the problem it is meant to solve:
# * It throttles against the same rolling MSPT average the distance engine uses,
# not an instant reading, so the two never disagree about whether the server
# is busy.
# * It STOPS on low memory rather than slowing down. A slower path to an
# OutOfMemoryError is still an OutOfMemoryError.
# * It writes progress to disk, so a crash or a nightly reboot resumes exactly
# where it left off instead of silently losing hours of work.
#
# It also excludes its own chunks from the chunk-generation safeguard. Without
# that, starting a run would make the plugin think players were outrunning the
# generator and clamp everyone's view distance.
idle-pregeneration:
enabled: false
# Start automatically once the last player logs off. Set "world" to the world
# you want built out; leave it empty and nothing starts on its own, but
# /vdt pregen still works.
start-when-empty: true
world: ""
# Radius around the world spawn, in chunks. 250 chunks is a 4000-block radius.
# Mind the squaring: the workload is (2*radius+1)^2 chunks, so 250 is about
# 251,000 chunks and 1000 is over four million. Match this to your world border
# rather than picking a round number.
radius: 250
# Refuses any /vdt pregen start above this, so a mistyped radius cannot queue
# a run that would take weeks.
max-radius: 5000
# How many chunk requests may be in flight at once, and how many ticks to wait
# between batches. Higher = faster and heavier. The defaults are deliberately
# gentle; raise them on a machine with headroom.
chunks-per-batch: 4
tick-interval: 5
# Pause while the server is above this MSPT. Well below decrease-mspt-threshold
# on purpose: pre-generation is optional work and should yield long before the
# distance engine has to start defending TPS.
pause-above-mspt: 35.0
# Stop entirely while free heap is under this percentage. Higher than
# memory-safeguard's own floor because mass generation allocates far faster
# than a distance increase does.
min-free-memory-percent: 30.0
# Pause an AUTOMATIC run as soon as anyone logs in, and continue when the server
# empties again.
#
# This does not apply to a run started with /vdt pregen: somebody who typed the
# command while standing in the world is not asking to be told to log off first.
# Manual runs stay throttled by pause-above-mspt and min-free-memory-percent, so
# they still cannot run the server into the ground.
pause-when-players-online: true
# ============================================================
# PREDICTIVE PRE-LOADING (optional, off by default)
# ============================================================
# Loads chunks along the path of a fast-moving player, so arriving somewhere does
# not stall the tick.
#
# To be clear about what this is: it makes chunks ready SERVER-side. It does not
# let anyone see further. The client still receives only what its own view
# distance covers. The visible result is the absence of a hitch, not a wider
# horizon.
#
# Prediction follows the movement vector, not the look direction. During elytra
# flight the view swings around constantly while the flight path stays straight,
# and it is the path that decides which chunks the server will need.
predictive-preloading:
enabled: false
# Ticks between passes, and the speed a player must sustain to qualify.
# 0.8 blocks per tick is roughly sprint speed; elytra flight is far above it.
tick-interval: 10
min-speed-blocks-per-tick: 0.8
# How far along the path to look, and the cap on chunk requests per pass across
# all players together.
lookahead-chunks: 4
max-chunks-per-cycle: 8
# Skip a pass while the server is above this MSPT. Pre-loading is a comfort
# feature and must be the first thing to go under pressure.
pause-above-mspt: 40.0
# Mix the look direction into the prediction at half weight. Helps when players
# are about to turn, adds noise during sustained travel. Off by default.
blend-look-direction: false
# Allow pre-loading to GENERATE terrain that does not exist yet.
# Leave this false. With it on, this stops being a loader and becomes a second,
# uncontrolled generator driven by whoever is flying fastest, the exact load
# spike the rest of the plugin exists to contain. Use idle-pregeneration for
# unexplored terrain instead.
allow-generation: false
# ============================================================
# ADJUSTMENT
# ============================================================
# Adjustment mode:
# proactive - Adjusts distances before performance suffers
# (based on chunk count targets)
# reactive - Reacts to MSPT changes
# mixed - Combines both; prioritises reduction when overloaded (recommended)
# off - Disables automatic sim/view distance management entirely. Sim/view
# distance is left exactly as-is; only /vdt simulationdistance|viewdistance
# (temporary overrides) can still change it. Everything else keeps working
# independently: idle pre-generation, predictive pre-loading, AFK
# throttling, per-player fixed distances, MSPT tracking. Use this if you
# only want the pre-generation/pre-loading subsystems and nothing else.
adjustment-mode: mixed
# How many ticks after plugin start should be waited before the first check runs?
# 2400 = 2 minutes, useful to let the server stabilise after a restart.
start-delay: 2400
# How many ticks between checks for whether distances need to be adjusted?
# 600 = every 30 seconds
ticks-per-check: 600
# How many consecutive checks must confirm an increase before it is applied?
# Higher value = more conservative (recommended: 8-15)
passed-checks-for-increase: 10
# How many consecutive checks must confirm a decrease?
# 1 = immediate reaction when overloaded (recommended)
passed-checks-for-decrease: 1
# Should a line be logged to the console when distances are changed?
log-changes: true
# Should a message be sent to players with the permission viewdistancetweaks.notify?
# Useful for admins who want to follow changes live.
announce-changes-in-chat: false
# Format of the announce message. Placeholders:
# {world} - world name
# {oldSim} - old simulation distance
# {newSim} - new simulation distance
# {oldView} - old view distance
# {newView} - new view distance
# Colours with & are supported.
announce-format: "&7[VDT] &f{world}: Sim &e{oldSim}&7→&a{newSim} &f| View &e{oldView}&7→&a{newView}"
# ============================================================
# PROACTIVE MODE
# ============================================================
proactive-mode-settings:
# Maximum number of ticking chunks globally (across all worlds).
# Formula: (2*sim+1)^2 * number of players
# Example: 20 players, Sim 8 -> (17)^2 * 20 = 5780
global-ticking-chunk-count-target: 5780
# Maximum number of non-ticking chunks globally (View minus Sim).
# Formula: ((2*view+1)^2 - (2*sim+1)^2) * number of players
# Example: 20 players, View 12, Sim 8 -> (625 - 289) * 20 = 6720
global-non-ticking-chunk-count-target: 6720
# NEW in 2.6: what a world with ZERO players should be driven to.
# min - drop it to its configured minimum (recommended, default)
# max - drive it to its configured maximum (behaviour before 2.6)
# keep - leave whatever it currently has
# No chunks tick while a world is empty, so "max" costs nothing right up until
# someone walks in, at which point they land on a fully maxed-out world and
# max-decrease-step can only walk it back down one notch per check. That is the
# exact load spike startup-grace exists to prevent, hence "min" as the default.
empty-world-target: min
# NEW in 2.7: weigh chunks by what is actually IN them instead of counting
# them all the same. An ocean chunk costs almost nothing; a chunk holding a
# 500-hopper sorting system can dominate a tick on its own, yet the plain
# (2*d+1)^2 formula treats both as one unit, so a redstone-heavy world gets
# the same budget as a world full of water.
#
# Inspecting chunks is itself work, so this SAMPLES a bounded number of
# loaded chunks per world (round-robin) and derives an average multiplier,
# refreshed once per ticks-per-check. It never scans every chunk.
use-complexity-weighting: false
# How many loaded chunks to sample per world per refresh. Higher = more
# accurate but more work. 40 is plenty for a stable average.
complexity-sample-chunks: 40
# Base cost of an ordinary chunk before any tile entities are counted.
complexity-default-weight: 1.0
# Surcharge per tile entity of that material. Names are Bukkit Material
# names. Anything not listed adds nothing.
complexity-weights:
HOPPER: 0.5
SPAWNER: 2.0
CHEST: 0.05
TRAPPED_CHEST: 0.05
BARREL: 0.05
FURNACE: 0.1
BLAST_FURNACE: 0.1
SMOKER: 0.1
BREWING_STAND: 0.1
BEACON: 0.5
CONDUIT: 0.5
PISTON: 0.1
STICKY_PISTON: 0.1
# ============================================================
# REACTIVE MODE
# ============================================================
reactive-mode-settings:
# Below which MSPT should an increase of the distance be considered?
increase-mspt-threshold: 40.0
# Above which MSPT should a decrease of the distance be considered?
# A tick lasts at most 50ms (= 20 TPS); players notice lag from ~47ms.
decrease-mspt-threshold: 47.0
# If true: when the server is overloaded, simulation distance is reduced FIRST.
# View distance is only reduced once simulation distance has already hit its
# configured minimum for that world. Simulation distance has a bigger impact
# on TPS, while reducing view distance causes visible chunk flicker for
# players, so this avoids touching view distance unless it's really needed.
# If false: simulation and view distance are reduced together (old behavior).
# Default: false
prioritize-simulation-distance: false
# Maximum number of steps simulation/view distance may change by in a single
# check cycle, in EACH direction. Setting these to 1 gives the old behavior
# (always +/-1 per check). Higher values let the plugin react faster to big
# swings in server load, at the cost of bigger visible/perf jumps per step.
# max-increase-step - limits how much distance can grow in one check
# max-decrease-step - limits how much distance can shrink in one check
# Default: 1 for both (safe, gradual behavior)
max-increase-step: 1
max-decrease-step: 1
# After increasing simulation/view distance, re-check MSPT shortly after
# (see below). If the server is immediately overloaded as a result, the
# increase is rolled back on the next check instead of waiting through the
# normal passed-checks-for-decrease delay. This prevents the "jump straight
# to max, then crawl back down one step at a time" problem.
revert-increase-if-overloaded: true
mspt-tracker-settings:
# Over how many ticks is the MSPT average calculated?
# 1200 = 1 minute. Higher = more stable but slower to react.
collection-period: 1200
# MSPT prediction: Prevents increasing distances
# even though we know from history that this would cause lag.
mspt-prediction:
enabled: true
# How many minutes of history should be stored for the prediction?
history-length: 30
# Minimum number of data points before the prediction takes effect.
# Prevents wrong decisions immediately after server start.
min-samples: 5
# MSPT VARIANCE: a server can have a perfectly fine *average* MSPT while
# still spiking badly tick-to-tick (e.g. redstone clocks, chunk generation
# bursts, plugin schedulers). Average-only checks miss this. This tracks
# how spread out the recent tick times are (standard deviation) and can
# treat a high-variance server as if it were overloaded, even if the
# average MSPT is still below decrease-mspt-threshold.
mspt-variance:
enabled: true
# How many of the most recent tick samples (from Paper's getTickTimes())
# are used to compute the standard deviation. getTickTimes() holds up to
# 100 samples (~5s at 20 TPS); this just controls how many of those are used.
sample-window: 100
# Standard deviation (in ms) above which the server is considered
# "unstable" and treated as overloaded for decrease purposes, even if
# average MSPT is fine. Typical stable servers sit around 1-4ms stddev;
# noticeable stutter usually starts around 8-10ms+.
variance-threshold: 8.0
# Minimum number of samples required before variance is evaluated at all.
# Prevents false positives right after server start.
min-samples: 20
# If true, high variance ALSO blocks increases (in addition to forcing
# decreases), even if average MSPT looks fine. Recommended: true.
block-increase-on-high-variance: true
# Standard deviation is symmetric. It measures how spread out recent ticks
# are around the average, regardless of direction. A server bouncing between
# 4ms and 18ms (avg ~10ms, still totally fine) can trip the same stddev
# threshold as one bouncing between 40ms and 55ms (avg ~47ms, actually
# struggling). Variance is only treated as overload once the average MSPT
# is at least this high. Below it, variance is ignored even if it exceeds
# variance-threshold above.
min-avg-mspt-to-trigger: 25.0
# ============================================================
# PER-PLAYER FIXED VIEW DISTANCE (optional)
# ============================================================
# Lets specific players (e.g. mods, streamers) keep a fixed CLIENT-SIDE view
# distance regardless of what the plugin sets for the world. This only affects
# what that individual player receives. It does NOT change the world's
# server-side view distance or the chunk budget the rest of this plugin manages.
#
# Grant a permission "<permission-prefix><value>" to set that player's fixed
# view distance, e.g. with permission-prefix "viewdistancetweaks.fixed." :
# viewdistancetweaks.fixed.20 -> that player always gets view distance 20
# If a player has multiple such permissions, the highest value is used.
# Valid values are 2-32; anything higher is clamped to 32. Note that a player
# never sees more chunks than their own client render-distance setting allows.
# Taking the permission away again hands the player back to their world's
# normal, plugin-managed view distance, no relog needed.
per-player-fixed-view-distance:
# Must be true for the viewdistancetweaks.fixed.<value> permissions to do
# anything. Works independently of the "enabled" master switch at the top.
enabled: false
# Must be lowercase-safe; a trailing "." is added automatically if missing.
permission-prefix: "viewdistancetweaks.fixed."
# ============================================================
# PER-PLAYER FIXED SIMULATION DISTANCE (optional, NEW in 2.6)
# ============================================================
# Exactly the same idea as the fixed view distance above, but for simulation
# distance (Paper's Player#setSimulationDistance). Simulation distance controls
# how far around that player the world actually TICKS: mobs, redstone, crops.
#
# Careful: unlike view distance this genuinely costs server performance, so
# handing it out widely will hurt. It is meant for a handful of staff members,
# not for a donor rank.
#
# viewdistancetweaks.simfixed.8 -> that player always gets simulation distance 8
#
# Valid values are 2-32; anything higher is clamped to 32. If a player holds
# several such permissions, the highest value is used. Taking the permission
# away hands the player back to their world's normal distance, no relog needed.
# Use a DIFFERENT permission-prefix than the view-distance feature above,
# otherwise a single permission would set both values at once.
per-player-fixed-simulation-distance:
enabled: false
permission-prefix: "viewdistancetweaks.simfixed."
# ============================================================
# AFK PLAYER THROTTLING (optional, NEW in 2.7)
# ============================================================
# A player standing still at spawn does not need the same distances as one
# flying an elytra. When they go AFK their personal distances drop to the
# values below, and the chunks they free up go straight back into the global
# budget for players who are actually playing.
#
# AFK state comes from Paper's own idle timer. If EssentialsX is installed its
# AFK state is used instead, so someone who typed /afk counts immediately
# instead of having to wait out afk-time-minutes.
#
# Note: an explicit viewdistancetweaks.fixed.<n> permission WINS over this
# throttle. If you want staff throttled while AFK too, don't give them that
# permission.
afk-player-throttling:
enabled: false
afk-time-minutes: 5
afk-simulation-distance: 4
afk-view-distance: 6
use-essentials-if-present: true
# ============================================================
# CHUNK GENERATION SAFEGUARD (optional, NEW in 2.7)
# ============================================================
# mspt-variance notices the stutter that terrain generation causes, but not the
# cause. This counts chunks that are genuinely being GENERATED (not merely read
# from disk) and reacts to the cause directly.
#
# Generating a chunk runs the whole terrain pipeline and is what actually
# freezes the main thread when someone crosses unexplored terrain at speed.
chunk-generation-safeguard:
enabled: false
# Newly generated chunks per second, per world, above which the safeguard trips.
chunks-per-second-threshold: 15
# Block distance increases while generation is running. Growing view distance
# mid-generation feeds the exact thing that is stalling the server and causes
# the rubber-banding this is meant to avoid.
lock-increase-during-gen: true
# Additionally clamp view distance to this value while generating.
# -1 disables the hard clamp and only uses lock-increase-during-gen above.
emergency-view-distance: -1
# ============================================================
# DENSITY-BASED SCALING (optional, NEW in 2.7)
# ============================================================
# 20 players scattered across a world load 20 separate rings of chunks.
# 20 players standing around the same community farm load almost the SAME
# chunks. The proactive formula assumes the first case and therefore starves a
# clustered world of view distance it could easily afford.
#
# When enough players sit within cluster-radius chunks of each other, this
# grants bonus distance proportional to how much of the world's population is
# in that cluster.
density-based-scaling:
enabled: false
# Radius in chunks within which players count as "together"
cluster-radius: 8
# Minimum players in one cluster before any bonus is granted
cluster-player-threshold: 5
# Maximum bonus distance, reached when everybody is in the same cluster
max-bonus: 4
# ============================================================
# EMERGENCY RELIEF (optional, NEW in 2.7)
# ============================================================
# Last resort. When a world already sits at its MINIMUM simulation and view
# distance and the server is STILL over decrease-mspt-threshold, there is
# nothing left to take away from distances. Rather than let the server keep
# sinking, shed two cheaper-to-lose sources of load instead: thunderstorms
# (extra light and weather updates) and the day/night cycle.
#
# Both are restored automatically once MSPT drops below recover-below-mspt,
# and on plugin disable. The world's ORIGINAL weather and doDaylightCycle
# setting are remembered, so a world that deliberately had the cycle off
# keeps it off afterwards.
emergency-relief:
enabled: false
clear-weather: true
freeze-time: true
# Deliberately lower than decrease-mspt-threshold, otherwise relief would
# flap on and off around the same value that triggered it.
recover-below-mspt: 40.0
# ============================================================
# MEMORY SAFEGUARD (NEW in 2.7)
# ============================================================
# Distances don't only cost CPU. Every extra ring of chunks stays resident in
# the heap. A server can sit at a perfect 20ms MSPT and still be one increase
# away from spending every tick in garbage collection, at which point MSPT
# reacts far too late to be useful as the only signal.
#
# While free heap is below min-free-percent, ALL increases are blocked
# regardless of MSPT. Decreases always stay allowed.
# Measured against the -Xmx ceiling, not the currently allocated heap.
memory-safeguard:
enabled: true
min-free-percent: 15.0
# ============================================================
# PROFILES (optional, NEW in 2.7)
# ============================================================
# Named overlays for a handful of tuning values, switchable in-game with
# /vdt profile <name>, no config editing mid-event. "normal" is always
# available and means "the values configured above".
#
# Supported keys inside a profile:
# decrease-mspt-threshold, increase-mspt-threshold,
# max-increase-step, max-decrease-step,
# passed-checks-for-increase, passed-checks-for-decrease,
# global-ticking-chunk-count-target, global-non-ticking-chunk-count-target,
# max-view-distance-cap, max-simulation-distance-cap
#
# The two *-cap keys are hard caps applied AFTER every other rule, so a cap of
# 8 genuinely means 8 and cannot be undone by never-reduce-below-players.
profiles:
event-mode:
decrease-mspt-threshold: 42.0 # react to load much earlier
max-view-distance-cap: 8 # hard ceiling for the duration of the event
passed-checks-for-increase: 20 # and be very reluctant to grow again
night-mode:
increase-mspt-threshold: 45.0 # be generous while the server is quiet
max-increase-step: 2
# ============================================================
# STARTUP GRACE PERIOD (optional)
# ============================================================
# If enabled, the plugin forces a low, fixed sim/view distance immediately when
# the plugin starts (before start-delay/ticks-per-check even apply), instead of
# whatever distance the world already had (which could be near max after a
# config change or a manual override, and cause instant overload/crash on a
# cold-started server). Once duration-ticks has passed, normal proactive/
# reactive/mixed adjustment takes back over as usual, and max-increase-step
# still limits how fast it's then allowed to climb back up.
startup-grace:
enabled: false
simulation-distance: 4
view-distance: 6
# 6000 ticks = 5 minutes
duration-ticks: 6000
# ============================================================
# WORLD SETTINGS
# ============================================================
# Any worlds not listed here inherit the "default" settings.
# You can override each world with its exact name.
world-settings:
default:
simulation-distance:
# Exclude the world completely from management?
# Chunks are still counted for global calculations (unless chunk-weight: 0).
exclude: false
min-simulation-distance: 4
max-simulation-distance: 12
view-distance:
exclude: false
min-view-distance: 6
max-view-distance: 16
# How much chunk "budget" does this world weigh?
# 1.0 = normal. 0.5 = half as heavy (e.g. End, many empty chunks).
# 0.0 = world has no influence on global calculations.
chunk-weight: 1.0
# NEW: Override how often THIS world is checked, independent of the global
# ticks-per-check above. Leave unset (or <= 0) to inherit the global value.
# Useful for a busy overworld that needs quick reactions vs. a quiet, mostly
# empty nether/end that doesn't need checking every 30 seconds.
# ticks-per-check: 200
# NEW: Never lower the distance below the current player count in this world.
# Useful for small servers with few players.
never-reduce-below-players: false
# NEW: This world gets 50% more chunk budget in proactive calculations.
# Useful for the main world on servers with multiple dimensions.
priority-world: false
# Example: Nether world
# world_nether:
# simulation-distance:
# min-simulation-distance: 4
# max-simulation-distance: 8
# view-distance:
# min-view-distance: 4
# max-view-distance: 10
# chunk-weight: 0.8
# priority-world: false
# Example: End world (many empty chunks -> lower weight)
# world_the_end:
# simulation-distance:
# min-simulation-distance: 4
# max-simulation-distance: 10
# view-distance:
# min-view-distance: 6
# max-view-distance: 14
# chunk-weight: 0.5
# Example: Main world with priority
# world:
# simulation-distance:
# min-simulation-distance: 6
# max-simulation-distance: 12
# view-distance:
# min-view-distance: 8
# max-view-distance: 16
# chunk-weight: 1.0
# priority-world: true
messages.yml has every string players and staff see, with & colours and {placeholder} substitution. It's copied into the data folder on first start. Every key also has a fallback inside the jar, so an older messages.yml keeps working instead of rendering blank lines, and anything missing gets listed in the console once at load.
Distances are in chunks, timings are in ticks, so ticks-per-check: 600 is 30 seconds. The --duration flag on the two override commands is the exception, that one takes minutes.
An older config.yml gets topped up on start with whatever a newer build added, and your previous file is kept as config.yml.bak. Nothing existing is rewritten.
Commands
| Command | What it does |
|---|---|
/vdt status [--weight] [--fixed] |
Live state per world: distances, chunk counts, MSPT, variance, plus a line for each subsystem that is actually switched on. --weight adds chunk weight and complexity multiplier, --fixed lists per-player overrides |
/vdt simulate [world] |
Dry run. Shows what the next check would decide and the reason for it, without changing anything |
/vdt history |
The last 20 applied changes, each with the reason it was made. Answers "why is view distance 6 right now" after the fact |
/vdt mspt [debug] |
MSPT, TPS and standard deviation. debug adds the raw Paper tick samples |
/vdt viewdistance <n> [world] [--duration <min>] |
Manual override. Without --duration it holds until the next reload |
/vdt simulationdistance <n> [world] [--duration <min>] |
Same, for simulation distance |
/vdt profile [name] |
Switch tuning overlay, or show which one is active |
/vdt preset <name> |
Write one of the bundled presets into config.yml |
/vdt pregen <start|pause|resume|stop|cancel|status> |
Pre-generate terrain. start <world> [radius] [--here]; stop keeps progress, cancel --confirm discards it |
/vdt enable / /vdt disable |
Master switch without editing the config file |
/vdt reload |
Re-read config.yml and messages.yml, drop manual overrides |
/vdt update |
Ask Modrinth whether there is a newer version |
Permissions
Everything defaults to OP, except the two numeric families at the bottom, which are never granted automatically.
| Node | Grants |
|---|---|
viewdistancetweaks.command.vdt |
Access to /vdt at all |
viewdistancetweaks.vdt.command.status |
/vdt status, /vdt history and /vdt mspt |
viewdistancetweaks.vdt.command.simulate |
/vdt simulate |
viewdistancetweaks.vdt.command.viewdistance |
/vdt viewdistance |
viewdistancetweaks.vdt.command.simulationdistance |
/vdt simulationdistance |
viewdistancetweaks.vdt.command.preset |
/vdt preset |
viewdistancetweaks.vdt.command.profile |
/vdt profile |
viewdistancetweaks.vdt.command.pregen |
/vdt pregen |
viewdistancetweaks.vdt.command.reload |
/vdt reload, enable, disable, update |
viewdistancetweaks.notify |
Receives the in-chat announcement when a distance changes |
viewdistancetweaks.fixed.<n> |
Fixed view distance for that player |
viewdistancetweaks.simfixed.<n> |
Fixed simulation distance for that player |
Placeholders
Needs PlaceholderAPI. Everything is prefixed %viewdistancetweaks_.
Server-wide
| Placeholder | Value |
|---|---|
mspt% |
Current average MSPT |
tps% |
Current TPS |
mspt_stddev% |
Tick-to-tick standard deviation in ms |
mspt_color% |
&a / &e / &c against the two MSPT thresholds |
tps_color% |
&a / &e / &c against 19 and 15 TPS |
mspt_stddev_color% |
&a / &e / &c against variance-threshold |
mode% |
Active adjustment mode |
enabled% |
true or false |
chunk_count% |
All loaded chunks across every world |
ticking_chunk_count% |
Ticking chunks across every world |
nonticking_chunk_count% |
Non-ticking chunks across every world |
sim_distance% |
Simulation distance of the first world |
view_distance% |
View distance of the first world |
Per world, append the world name:
| Placeholder | Value |
|---|---|
sim_distance_<world>% |
Current simulation distance |
view_distance_<world>% |
Current view distance |
min_sim_distance_<world>% |
Configured minimum |
max_sim_distance_<world>% |
Configured maximum |
min_view_distance_<world>% |
Configured minimum |
max_view_distance_<world>% |
Configured maximum |
chunk_count_<world>% |
Loaded chunks in that world |
ticking_chunk_count_<world>% |
Ticking chunks in that world |
nonticking_chunk_count_<world>% |
Non-ticking chunks in that world |
chunk_weight_<world>% |
Configured chunk weight |
Support
Made and maintained by LucasTHCR.
Questions, bug reports and feature ideas go to dc.gg/paperstream. I read everything there, and a bug report with your config.yml and the output of /vdt status saves us both a round of guessing.
License
Copyright (C) 2026 LucasTHCR
ViewDistanceTweaks is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
Short version: use it, study it, change it, pass it on. Anything you distribute that's built on it has to be GPLv3 too and has to ship its source.
Releases up to and including 2.6-RELEASE were published under the MIT License; copies obtained under those terms stay governed by them.


