PVDC dynamically limits the view distance of the individual player who is moving quickly into newly generated terrain. Other players keep their own normal distance. Once pressure stops, the affected player recovers gradually.
This fork targets Minecraft 26.2, Paper, Folia, and Java 25.
It uses Paper's native schedulers on both server types and declares
folia-supported: true.
Each online player owns an independent state machine:
NORMAL -> EXPLORING -> PRESSURED -> RECOVERING -> NORMAL
PVDC samples horizontal speed and recent chunk-boundary crossings on that
player's EntityScheduler. It correlates those signals with a passive cache of
nearby ChunkLoadEvent#isNewChunk events. It never calls isChunkGenerated,
getChunkAt, loadChunk, or any other API that could probe storage, load, or
generate terrain.
- Ordinary movement, AFK players, and fast travel through generated terrain do
not enter
PRESSURED. - Sustained fast travel near server-observed new generation first applies the medium cap and can then apply the minimum cap.
- Quiet confirmation, a recovery delay, and stepped recovery prevent distance flapping.
- Teleport, world change, respawn, death, reconnect, and configuration reload reset movement history before reevaluation.
The passive approach intentionally reacts after the first new chunks have actually appeared. Paper 26.2 has no public arbitrary-chunk existence query that both distinguishes ungenerated terrain and guarantees no chunk load or disk read.
Only PlayerDistanceController writes distance APIs. Controllers cannot fight
each other. The effective view distance is the minimum of:
- the player's saved preference or an administrator override;
- the
pvdc.maxdistance.<n>permission cap, unless bypassed; - the exploration-state cap, unless
pvdc.dynamic-mode-bypassis granted; - the optional per-player ping cap;
- the AFK cap, unless
pvdc.bypass-afkis granted.
The result is clamped to config.yml's global min-distance and
max-distance. Exploration uses its own min/medium/max values from
dynamic-mode.yml; it never reads ping-mode bounds.
dynamic-mode.yml contains every exploration threshold:
enabled: true
sample-interval-ticks: 20
distance:
min: 4
medium: 8
max: 32
movement:
speed-blocks-per-second: 8.0
chunk-crossings-window-seconds: 8
chunk-crossings-threshold: 3
generation:
new-chunk-window-seconds: 10
# Accepted range: 0..8 (larger values are clamped).
radius-chunks: 2
medium-threshold: 2
minimum-threshold: 5
hysteresis:
exploring-confirm-samples: 2
pressure-confirm-samples: 2
quiet-confirm-samples: 3
recovery-delay-seconds: 10
recovery-interval-seconds: 3
recovery-step-chunks: 1Worlds inherit the defaults and can disable or override any exploration value:
worlds:
default-enabled: true
overrides:
world_nether:
enabled: true
distance:
min: 4
medium: 7
max: 24
world_the_end:
enabled: falseSimulation distance is gameplay-affecting, so it is independent and disabled by default. Send distance is also independent and disabled by default:
simulation-distance-control:
enabled: false
minimum: 2
send-distance-control:
enabled: false
minimum: 2When enabled, these optional controls never exceed the value captured when the player session attached. Disabling either control restores that captured value.
Use /pvdc reload after configuration changes. Reloading restarts each
player's sampling task and resets exploration history. /pvdc dynamic on|off
updates and persists the exploration feature toggle without creating duplicate
tasks.
pvdc.dynamic-mode-bypass: bypass exploration caps.pvdc.bypass-afk: bypass the AFK cap.pvdc.bypass-maxdistance: bypasspvdc.maxdistance.<n>caps.pvdc.reload: reload all PVDC configuration.- Existing set/get/reset, ping-mode, and online-admin permissions remain in
plugin.yml.
LuckPerms changes are dispatched back to each affected player's entity scheduler. PlaceholderAPI reads immutable runtime snapshots only. Geyser is detected reflectively and remains optional.
Available placeholders are %pvdc_chunks%, %pvdc_base_chunks%,
%pvdc_afk%, and %pvdc_state%. The first, AFK, and state placeholders also
accept an online-player suffix, for example %pvdc_state_playername%.
Build with Maven 3.9+ and JDK 25:
.\mvnw.cmd clean package
The JUnit harness covers:
- low-speed activity remaining at normal distance;
- high-speed travel through generated terrain without a penalty;
- medium and minimum caps during sustained new-terrain exploration;
- isolation between hundreds of independently evaluated players;
- gradual recovery and anti-flapping hysteresis;
- lifecycle reset behavior;
- world enable/disable/override inheritance and invalid-bound clamping;
- passive generation-cache concurrency and expiry;
- offline preference ordering, field preservation, and shutdown flushing;
- controller priority/composition;
- architecture contracts preventing chunk-probe APIs, legacy schedulers, and multiple distance writers.
The packaged plugin is produced under target/. Server startup smoke tests
should use clean Paper 26.2 and Folia 26.2 directories and confirm the log
contains PVDC 3 started with native Paper/Folia entity schedulers. with no
thread-ownership exception.
Normal /pvdc reload and graceful server shutdown are supported. Third-party
hot-unloaders should invoke disable from the console/global context. If another
plugin disables PVDC from a Folia region thread, PVDC deliberately does not
block that region waiting for remote regions; it logs a warning, and a remote
player can retain the last applied distance until reconnecting.