ViewDistanceTweaks
2.16.0-RELEASEAutomatically adjusts view and simulation distance per world to match what your server can handle - more distance when there is headroom, less before players notice lag.
- Tested MC versions
- 1.21, 26.1, 26.2, 26.3
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
Spigot and Bukkit are not supported: their API can read view and simulation distance but not change it, and changing it is what this plugin does.
How it decides
MSPT is sampled every tick. Every ticks-per-check (10 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.
It can tell your worlds apart. The server publishes one MSPT figure for everything, so a single world melting down drags every other world's distance down with it - the quiet ones get punished for their neighbour. With per-world-mspt on, the tick is split between worlds and each is judged by the part it actually causes. Off by default; see below.
Per-world MSPT
One number for the whole server is the wrong shape for a per-world decision. If your nether farm is eating 30ms, the overworld didn't do anything wrong, and shrinking it doesn't help.
Turn on per-world-mspt.enabled and each world is judged by its own share of the tick instead:
fair share = 1 / number of worlds with players
attribution = this world's share / fair share (clamped, 0.5x to 2.0x by default)
judged at = server MSPT x attribution
So a world pulling exactly its weight is judged by the plain server figure, unchanged. A world pulling twice its weight is judged at twice the figure and shrinks first; a quiet one is judged at less and is left alone, or keeps growing while its neighbour gives ground.
Your thresholds keep meaning what they meant. This scales the figure the existing decrease-mspt-threshold is compared against - it does not replace it with a smaller per-world number. Nothing needs re-tuning, and on a single-world server the feature changes nothing at all.
Where the numbers come from depends on the platform:
- Paper (and forks like Purpur) estimates. There's no per-world timing to read, so the tick is split by what each world made the server walk - entities, tickable block entities, loaded chunks. Reliable for ranking worlds; it will misjudge a world that's cheap by volume and expensive by content, like a few hundred hoppers feeding each other.
- Folia measures region tick times directly, and that turns out to mean per-world MSPT does nothing there by default. A region that keeps up reports the tick rate itself, so every world reads the same and there is no split to act on; the figure only becomes informative once a region falls behind. Set
prefer-exact-source: falseto run the estimating proxy on Folia instead, which does tell worlds apart. A laggy region with no players near it isn't probed either way.
/vdt mspt shows the breakdown, which world is worst, and whether each figure is measured or estimated. /vdt simulate says so in its reason line when a world is being judged by an adjusted figure.
If the measurement is unavailable for any reason - still warming up, or the library failed to start - every world falls back to the plain server-wide figure, which is what the plugin did before this existed.
Placeholders
With PlaceholderAPI installed, everything the plugin knows is available on a scoreboard or in a chat format. The identifier is viewdistancetweaks - not vdt, which is the first thing everybody tries.
Server-wide
| Placeholder | Gives |
|---|---|
%viewdistancetweaks_mspt% | current MSPT, two decimals |
%viewdistancetweaks_tps% | current TPS, capped at 20 |
%viewdistancetweaks_mspt_stddev% | how much recent ticks vary |
%viewdistancetweaks_mode% | the active adjustment mode |
%viewdistancetweaks_enabled% | whether the plugin is managing distances |
%viewdistancetweaks_mspt_color% | &a/&e/&c against your own thresholds |
%viewdistancetweaks_tps_color% | same idea for TPS |
%viewdistancetweaks_mspt_stddev_color% | same idea for the spread |
%viewdistancetweaks_chunk_count% | loaded chunks across every world |
%viewdistancetweaks_ticking_chunk_count% | ticking chunks across every world |
%viewdistancetweaks_nonticking_chunk_count% | non-ticking chunks across every world |
Per world. Append the world name - %viewdistancetweaks_view_distance_world_nether%. Without a name, the server's first world is used.
| Placeholder | Gives |
|---|---|
%viewdistancetweaks_sim_distance_<world>% | current simulation distance |
%viewdistancetweaks_view_distance_<world>% | current view distance |
%viewdistancetweaks_min_sim_distance_<world>% | configured minimum |
%viewdistancetweaks_max_sim_distance_<world>% | configured maximum |
%viewdistancetweaks_min_view_distance_<world>% | configured minimum |
%viewdistancetweaks_max_view_distance_<world>% | configured maximum |
%viewdistancetweaks_chunk_count_<world>% | loaded chunks in that world |
%viewdistancetweaks_ticking_chunk_count_<world>% | ticking chunks in that world |
%viewdistancetweaks_nonticking_chunk_count_<world>% | non-ticking chunks in that world |
%viewdistancetweaks_chunk_weight_<world>% | configured chunk weight |
Per-world MSPT. These need per-world-mspt switched on; without it they fall back to the server-wide figure or report that the feature is off, rather than failing.
| Placeholder | Gives |
|---|---|
%viewdistancetweaks_worst_world% | the world carrying the most of the tick |
%viewdistancetweaks_worst_world_mspt% | what that world costs, in ms |
%viewdistancetweaks_world_mspt_<world>% | that world's own contribution in ms |
%viewdistancetweaks_world_mspt_share_<world>% | its share of the tick, 0-100 |
%viewdistancetweaks_per_world_mspt_state% | where the numbers come from, or why there are none |
An unknown world reads as unknown rather than as a zero, so a typo in a scoreboard is visible instead of looking like a healthy server.
Safety nets
MSPT isn't the only thing worth reacting to. Everything here except the memory and chunk generation safeguards 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 (8 by default), while MSPT is also at least
min-mspt(30 by default), it stops increases and clamps view distance to 8 until generation calms down. A single player exploring on an idle server is left alone. This is the someone-just-flew-an-elytra-into-fresh-terrain case. On by default since 2.14, after a benchmark showed the distance climbing while single ticks on fresh terrain took over a second. - 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. - Ping throttling - same idea, triggered by a bad connection instead of an idle one. A player whose ping stays above the threshold for a few checks in a row gets reduced personal distances too, so their packets stop queuing and everyone else's budget stops paying for it. If both throttles apply, the more restrictive distance wins.
- 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. - Client view distance cap - nobody sees further than their own client draws. If the highest setting among a world's players is 8, a server view distance of 16 pays for rings of chunks nobody receives. With
client-view-distance-capon, a world is capped at the highest reported client value plus a small headroom, never below its minimum and never below its simulation distance, so one player's video settings cannot slow down farms for everyone. The highest value is held for three minutes, so a player switching dimensions does not make the world jump. Off by default. - Diagnostics -
/vdt diagnose onwrites a sample every minute and every distance change with the rule behind it toplugins/ViewDistanceTweaks/diagnostics, one file per day, without player names. Send that folder along with a bug report. Off by default. - Reports -
/vdt diagnose sendshows what would be sent and, after/vdt diagnose send confirm, uploads the last 24 hours of diagnostics, anonymised (no player names, UUIDs or IPs, worlds asworld_1,world_2, no notes), to a small service run by the plugin author, to help fix problems. You get a private link and a delete key;/vdt diagnose delete <id>removes a report, and reports vanish after 30 days. A daily report at a time you choose is available (report-upload.daily, off by default). The sender's IP address is not stored. Privacy policy. - Conflict detection - if another plugin keeps overwriting a world's distances, the two would fight forever. After a few cycles of that, ViewDistanceTweaks stops managing that world, says so once in the console, and names the installed plugins that could be doing it.
/vdt doctorshows it too. A manual/vdt viewdistanceon that world takes it back. - 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, the AFK throttle or the ping 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 schedule world 500 04:00 start daily at 4 AM server time, even with players on
/vdt pregen schedule cancel drop the scheduled run
/vdt pregen status progress, rate and ETA
/vdt pregen stop pause for good, progress kept
/vdt pregen cancel --confirm throw the progress away
schedule is for a maintenance window rather than an empty server: the START trigger switches from "whenever it's empty" to "at this clock time," but the run itself still answers to pause-above-mspt, min-free-memory-percent and pause-when-players-online once it's going, same as any other automatic run. It recurs daily until cancelled, and - like other command-driven state in this plugin - is not written to config.yml, so it needs re-arming after a restart.
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. If a world border is set, a radius that would reach past it is clamped down automatically (respect-world-border in config, on by default) - so entering something too big costs a log line, not four million wasted chunks nobody inside the border can reach.
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 t
Similar projects
Plugin →VeinMiner
Mine the whole vein on mining a single ore/block. Make the tedious mining experience to something satisfying and fun! FTB Ultimine alternative
99.3MSueta
Simple Voice Chat
A working voice chat in Minecraft!
76.3MSueta
Let Me Despawn
Improves performance by tweaking mob despawn rules. Say bye to pesky unintentional persistent mobs.
26.2MSueta
Chunky
Pre-generates chunks, quickly and efficiently
20.8MSueta
VeinMiner Enchantment
Veinminer Addon - Adds vineminer enchantment to enchanting tables. Only tools with the enchantment can veinmine
18MSueta
JourneyMap
Real-time map used for mapping in-game or your browser as you explore.
14.7MSueta