3.1.2
leaf26 released LeafRTP version 3.1.2 on June 13, 2026
(since 3.1.1)
Added
Cross-server
/rtpon the DB-freeproxy-directtier, as a drop-in alternative to Redis/SQL for data management (rtp-proxy-ADR-019, refining rtp-proxy-ADR-018). Building on theproxy-directregion discovery (rtp-proxy-ADR-017), a cross-server/rtp <server>:<region>issued on a lobby now moves the player and runs/rtpon the destination backend with no Redis/SQL.proxy-directis simply "another data-management transport likeredis/sql": the proxy's own in-memoryNetworkTransport+NetworkRequestQueueare the shared store, and a backend reaches them over the existing player-independent proxy-direct socket via a thin RPC. The wire carries one opcode per backend-invoked SPI method (OP_HEARTBEAT,OP_FLUSH_PENDING,OP_POLL_STATUS,OP_CANCEL,OP_FIND_RESERVATION,OP_REDEEM,OP_LIST_ACTIVE), HMAC-signed whenRTP_NET_SECRETis set; proxy-local methods (claim/dequeueReady/reaper) never leave the proxy. As a result enrolment flows through the standardNetworkEnrolmentBuffer->NetworkRequestQueue.flushPending, the proxy's existingTransportRequestTriggerSource+ dispatcher relocate the player viacreateConnectionRequest, and arrival redeem runs through the standardJoinTriggerSource(findReservation/redeem) - the same code the Redis/SQL tiers use, with no behavioural branch outside the binding factory.ProxyDirectNetworkBindingis now a full RPCNetworkTransport, plus a newProxyDirectNetworkRequestQueue(rtp-core); the proxyProxyDirectListenerdispatches each RPC onto the proxy's store. Reservation tokens are retained this pass (slimming them is a future cleanup). Covered byProxyDirectNetworkBindingTest(5) andProxyDirectListenerTest(2).Per-server
/rtppermission gate for networked backends. A qualified cross-server target/rtp <server>:<region>is now gated byrtp.servers.<server>(the destination backend) in addition to the existingrtp.regions.<region>(the region), mirroring the localrtp.regions.xmodel so operators can allow/deny teleport per backend. Enforced in theRegionParametervalidator, which also filters tab-completion, so a forged argument cannot bypass the suggestion filter.rtp.servers.*defaults to op (and is a child ofrtp.*) in both the full and liteplugin.yml.proxy-directnetwork transport: player-independent cross-server region discovery on the DB-free edition (rtp-proxy-ADR-017). A newtransport.type: proxy-directlets a backend open its OWN outbound TCP connection to the proxy companion (configuredtransport.proxies: [host:port, ...], the same mirrorable list on every backend) and publish its real region list at startup with NO player online, so cross-server/rtp region=<server>:<region>tab-completion converges with zero players on any backend and no per-server operator config - which plugin messaging cannot do (it is gated on a player connection, so a player-empty backend has no proxy connection). The Velocity proxy opens a matching TCP listener (transport.direct.enabled: true, default port25599) that ingests backend pushes into and serves the merged availability snapshot from the same store thertp:netplugin-message companion uses; frames are HMAC-SHA-256 signed whennetwork.secretEnv(RTP_NET_SECRET) is set (rtp-proxy-ADR-010), unsigned otherwise. No Redis/SQL required. The backend dial is driven by the existing heartbeat tick (RTP.scheduler, no raw backend thread); the tier is non-durable (region discovery only - durable cross-server reservations still need the SQL/Redis tier). The proxy availability store no longer fabricates an assumed-defaultregion row for a server that has not actually reported. Covered byProxyDirectNetworkBindingTest(4),ProxyDirectListenerTest(1), andVelocityProxyAvailabilityCacheTest./rtp info biomesbiome-occupancy leaderboard. A new/rtp infosubcommand reporting which biomes online players actually spend their time in, ranked by share - useful for world designers gauging where players congregate (distinct from teleport landings, which mirror biome surface area). A new in-memoryBiomeActivityTracker(RTP.biomeActivity,rtp-core) accumulates per-biome occupancy; the platform sampler periodically enumerates online players andBiomeActivityTracker.sample(...)dispatches each per-player biome read to that player's owning thread viaRTP.scheduler.runTaskForPlayer(Folia-correct: the read lands on the entity's region thread), reporting back through independent per-player futures so one stalled region never blocks the sweep. Wired on the Bukkit/Paper/Folia plugin (~30ssampler pulse); counts are in-memory only and reset on restart/reload, so behaviour is identical in the full and lite editions. Gated by the existingrtp.infopermission. NewinfoBiomeActivityHeader/infoBiomeActivityRow/infoBiomeActivityEmptymessage keys mirrored across every shipped locale. Covered byBiomeActivityTrackerTest(3). Fabric/NeoForge samplers are a tracked follow-up.spark profiler is now an optional source for
/rtp infoTPS/MSPT metrics (metrics-api-ADR-001; ADR-026 soft-hook). When the spark plugin is installed on a Bukkit/Paper/Spigot/Folia backend, RTP now merges spark's richer per-window TPS (1m/5m/15m) and mean MSPT over the native binding, instead of reading only the coarseServer#getTPS()/Server#getAverageTickTime(). spark is a pure soft-dependency reached entirely by reflection (no compile dependency onme.lucko:spark-api);softdepend: [spark]inplugin.ymlonly constrains load order so the spark API is ready when queried. NewSparkMetricsBinding(per-field merge over a native delegate - player count / soft cap / chunk backlog / db latency / Folia regions still come from the native binding) andReflectiveSparkStats(self-disables permanently on aClassNotFoundException/NoClassDefFoundErrorsuch as a trimmed lite jar, and returns the native value per-getter on a transient pre-init / not-yet-sampled state so the merge self-heals once spark warms up), wired byMetricsBindingDispatcher#wrapWithSparkIfPresenton the Paper, Spigot, and Folia paths. On Folia, spark wrapsFoliaMetricsBindingso its real per-tick MSPT is surfaced (Folia's native per-region sampler can only derive MSPT from the inter-tick interval, ~50 ms on a healthy server, so the visualizer //rtp infopreviously showed a flat, incorrect MSPT) while the per-regionfoliaRegions()samples still come from the native binding. Documented inEXTERNAL_HOOKS.md. Covered bySparkMetricsBindingTest(5) andMetricsBindingDispatcherTest.performance.yml#biomeWeightsships the full vanilla biome set pre-listed at the no-op weight1.0(ADR-062). The map previously shipped a singleplains: 1.0example; it now enumerates every vanilla overworld/nether/end biome (grouped by dimension) at1.0, so the knob doubles as an easy-to-edit reference - change a number to bias a biome, set0.0to suppress it, or append a modded biome id, without having to know each biome's exact name first. Behaviour is unchanged: an all-1.0map is identical to the prior default (unlisted biomes already defaulted to weight1.0), and weighting only takes effect underbiomeWeighted: true. When a future Minecraft version adds a biome, append it here to keep the list current. Mirrored across every shipped locale and the rtp-lite assembly.Registry-aware gray-space steering for biome weighting (ADR-062 Phase 3) (ADR-062). The
biomeWeighted/biomeWeightsdraw no longer treats a requested biome that has not yet been scanned as an implicit weight0, and a rare biome with a single recorded run no longer funnels every weighted teleport onto one coordinate. The weighted recall draw now consults the world's biome registry (RTPServerAccessor.getBiomes(world), resolved once per/rtpinPregenState): a requested biome the world can produce but that recall memory has not recorded stays reachable as "gray space" via bounded-spiral exploration (its biome confirmed by the existing downstream pipeline verification), and only a biome absent from the registry is a true0. Each recorded biome also defers a run-count-proportional share of its weight to exploration (PregenTask.grayFraction: unrecorded -> fully gray, a single recorded run -> mostly gray,>= GRAY_SPACE_MIN_RUNSdistinct runs -> fully recall-steered), and the draw explores with probability equal to the gray-space share of total weight (PregenTask.graySpaceProbability), so a higher configured weight on a thinly-recorded biome defers more to exploration and can never amplify single-run clustering. Forced recall (biomeRecallForced) opts out. Stays bounded and S-005-safe (no synchronous chunk load to discover a biome). A new cost-tiered biome-sampling SPI is scaffolded for a future cheap pre-filter (BiomeSampleCapabilityenum + defaultRTPServerAccessor.biomeSampleCapability/sampleBiomemethods, currentlyGENERATE_REQUIRED/nullon every platform); the per-version noise-sample fast path remains D-005-gated follow-up work. Covered byBiomeWeightedDrawTest(8 cases).Biome-probability weighting for biome-filtered selection (ADR-062). New
performance.ymlknobbiomeWeighted(defaultfalse, requiresbiomeRecall). When enabled, a biome-filtered/rtp(or background pre-fill) that requests several biomes steers toward each requested biome with equal probability instead of in proportion to how many recorded location runs it occupies, so a rare biome (mushroom fields, cherry grove) is no longer drowned out by a common biome's many runs. The draw operates over the existing Anvil-sourced per-region biome occupancy map (MemoryShapegetBiomeKeys/getBiomePrefixSums): a newPregenTask.drawWeightedBiome(...)picks the target biome uniformly among those present in spatial memory, then a run within it weighted by run width (keeping placement spatially uniform inside the chosen biome), then a uniform offset. It stays bounded (a finite weighted pick over a pre-mapped set, no reroll loop, no chunk I/O) and falls back to the legacy uniform-over-runs draw when disabled and to the plain bounded spiral when no occupancy data exists yet. Accuracy footing is the existing Anvil read, so steering stays correct on worlds pregenerated elsewhere or migrated across a Minecraft version (where a noise-mapgetBiomelookup would land in the wrong biome). A companionbiomeWeightsmap knob lets operators set explicit, non-equal per-biome weights: whenbiomeWeightedis on and the map is non-empty, the target biome is picked in proportion to the configured weights instead of equally (biomes absent from the map use weight1.0,0.0suppresses a biome, and an all-zero/empty set falls back to the equal-probability pick); the shippedplains: 1.0entry is a no-op example, and the equal-probability draw is just the all-equal degenerate case. Weights are parsed once per/rtpinvocation (PregenState), keeping the hot draw allocation-light. Mirrored across every shipped locale'sperformance.yml. Covered byBiomeWeightedDrawTest(6 cases)./rtp version(alias/rtp about) descriptive subcommand. A new lightweight subcommand that reports the running RTP plugin version, the platform brand, and the host server version. Unlike/rtp info, it captures no liveMetricsSnapshotand reads no runtime counters, so it is a purely descriptive identity readout rather than realtime telemetry. Registered on the Bukkit/Paper/Folia, Fabric, and NeoForge command roots (the aliasaboutresolves to the same command). Gated by a newrtp.versionpermission (defaulttrue, also a child ofrtp.*). Covered byVersionCmdTest.Basic Folia support in the free build (ADR-024 2026-06-10 amendment). The free (lite) build now runs on Folia instead of failing to enable: the lite
plugin.ymlsetsfolia-supported: true, and a new correctness-firstFoliaAwareScheduler(in the lite-bundledrtp-paper-common) routes work through paper-api's regionized scheduler statics (getGlobalRegionScheduler/getRegionScheduler/getAsyncScheduler) and the per-entity scheduler, whileBukkitRTPPlayer.setLocationperforms the teleport viaEntity#teleportAsync(reflectively, since a cross-region syncteleport()throws on Folia; failures are logged per REQ-RTP-S-004).BukkitServerProvider.resolveServerModelprefers the tunedfolia_v*adapter when present (Pro) and falls back to the Paper accessor +FoliaAwareSchedulerotherwise (lite). The tuned, throughput-optimizedrtp-foliaadapter remains a (Pro) early-access feature and is still excluded from the lite jar, so no PolyForm-licensed source enters the MIT lite binary (ADR-061).
Fixed
/rtp visualization/map charts no longer crash on the free (lite) build running on Folia. Map chart delivery dropped the chart'sFILLED_MAPitem viaWorld#dropItemon the thread that ranMapDispatch, which on Folia is the global region thread - it does not own the player's region, so Folia threwNullPointerException: ... ServerLevel.getCurrentWorldData() is nulland the chart was never delivered. The region-thread hop is now performed once at the call site:MapDispatch.paintdispatchesbinding.deliverTo(...)throughRTP.scheduler.runTask(viewerLocation, ...)(resolved viaRTP.serverAccessor.getPlayer(viewer).getLocation()), so on Folia the drop lands on the region thread owning the viewer's chunk and on Paper/Spigot it runs inline on the main thread as before. The platform map bindings (BukkitMapBinding,FoliaMapBinding) no longer reimplement anEntitySchedulerhop -deliverTosimply drops on the (now correct) calling thread, and the S-004 failure-to-mapUnavailablepath is evaluated inside the scheduled runnable. Covered byMapDispatchTest,BukkitMapBindingTest, andFoliaMapBindingTest.Metrics now install on the free (lite) build running on Folia, so bStats /
/rtp inforeport sampled data instead of UNSAMPLED. On a Folia server the lite jar'sMetricsBindingDispatcher.install()tried to load the Pro-onlyFoliaMetricsBinding(the wholertp/folia/**adapter is excluded from the MIT lite jar, ADR-061), caught theClassNotFoundException, and aborted the metrics install with a startup WARNING + stack trace - leaving the binding at NOOP. The dispatcher now resolves each platform binding through atryInstallBinding(...)helper that returns gracefully (logging at FINE, no stack trace) when a binding class is absent, falling through Folia -> Paper (Bukkit#getTPSworks on Folia) -> the raw Bukkit sampler. Lite-on-Folia therefore installsPaperMetricsBindingand metrics work out of the box. Covered byMetricsBindingDispatcherTest.(Pro) Folia TPS/MSPT metrics no longer go UNSAMPLED on an idle server.
FoliaMetricsBinding's scalar TPS/MSPT clock was fed only byFoliaRegionProcessor, which runs only while a region has queued/rtptasks; with no teleport traffic no samples were recorded and the 60s idle-eviction left/rtp infoand bStats reporting UNSAMPLED. The binding now also drives a steady, traffic-independent heartbeat sampler scheduled once per server tick on the Folia global region scheduler (RTP.scheduler.runTaskTimer), folded into the scalar aggregation (but not into the per-regionfoliaRegions()list), and self-terminating on hot-reload/uninstall. Per-region attribution fromFoliaRegionProcessoris unchanged. Covered by a newFoliaMetricsBindingTestcase.Relative coordinate tokens (
/rtp centerx=~ centerz=~) now resolve to the player's position instead of a garbage center. The BukkitCoordinateParameteradvertises the vanilla/spreadplayers-style~/-~tokens in tab-completion, but the per-call shape-override path inRTPCmd.computeparsed override values withLong.parseLong->Double.parseDouble->Boolean.valueOf; a~value fell through toBoolean.valueOf("~") == false, socenterx/centerzsilently became the booleanfalse(an effective0center) rather than the caller's coordinate. A newRTPCmd.resolveRelativeCoordinate(token, base)helper now resolves~,~<n>(e.g.~-50),-~, and-~<n>against the teleporting player's current X/Z before the generic coercion, with absolute (tilde-free) tokens unchanged and a malformed offset falling back to the player's coordinate. Covered byRelativeCoordinateResolutionTest(6 cases).Fabric and NeoForge no longer suggest
/rtpsubcommands and parameters the caller lacks permission for (commands-api-ADR-001; REQ-API-F-007). The Brigadier bridge's greedyargs-slot suggestion provider (BrigadierCommandAdapter.argsSuggestionsFor) emitted every subcommand name and everyparamName=key regardless of the caller's permissions, so on the Brigadier-dispatched platforms (Fabric, NeoForge) tab-completion advertised commands the player could not run - diverging from the BukkitTreeCommand.onTabCompletecontract, which filters by permission. The provider now gates both subcommand-literal andparamName=suggestions throughBrigadierBridgeContext.permissionCheck()(empty/null permission stays open; a throwing predicate denies and logs, mirroringapplyRequires). Sub-command literal nodes were already requirement-gated viarequires(...)and filtered out of the client command-tree packet; this closes the dynamic-suggestion leak. Covered byBrigadierSuggestionPermissionTest(4 cases).
Changed
Released jars trimmed of dead weight. Both
shadowJar(Pro) andshadowLiteJar(lite) now exclude the compile-only IntelliJ annotation classes (org/jetbrains/annotations/**,org/intellij/lang/annotations/**, CLASS retention, no runtime use) and the orphan maven metadata (META-INF/maven/**, pom/properties never read at runtime) that leaked in transitively from shaded dependencies. The Pro jar additionally stops bundling the contributor-facingdocs/architecture/**tree (per-attempt pipeline internals, chunk-ticket lifecycle, network model, etc.) - it remains available in the public repository - while the operator-facingdocs/admin/**set (Quick Start, Configuration, Commands, Hazards, Runbook, proxies) is unchanged. No code, dependency, or public-API change:shadowJar'sminimize()was deliberately not adopted because the heavy shaded libs are reflectively driven (Jedis pub/sub, lz4 variant selection) or relocated into our own namespace and the RTP modules expose an addon-facing API plus descriptor-loaded platform entry points that static reachability analysis cannot see./rtp infotrimmed to stay compact before any metrics are sampled. The live-health block previously printed a wall ofn/arows (TPS, server MSPT, average pipeline latency, the P50..P99 percentile line, and database latency) on servers where no liveMetricsBindinghad sampled yet.InfoCmdnow suppresses each of those rows until its underlying value exists: the TPS and MSPT rows render only once a live sample is recorded, the average-latency and percentile rows render only once at least one teleport pipeline has completed, and the database-latency row renders only once a query has run. The always-available rows (JVM heap, queue depth, pending teleports, generation outcomes) are unchanged. Backed by four new read-only predicates onPlaceholderProvider(hasLiveTps,hasLiveMspt,pipelineSampleCount,hasDatabaseLatency) that read the snapshot sentinels (NaN TPS/MSPT, negative database latency, zero pipeline samples). The admin-only DRM footer was condensed from up to six separate lines into a singleDRM: <source> id=<id> nonce=<nonce>line (BuiltByBit builds appendbbb=<resourceId>@<timestamp>); the DRM strings stay hardcoded in core rather than moving into the player-editablemessages.yml. Covered byInfoCmdTest.Per-batch
[TRACE] ScanTask diagline demoted from INFO to FINE. The scan diagnostic split (probeNull / adjustNull / cacheHitLoad / genscan*) had been logged atINFO, printing a line every few seconds to the default console for the full duration of a world scan. It now logs atFINE, so it stays available when an operator raises the log level to debug a scan but no longer spams a normally-configured server. No behaviour change beyond log verbosity.
Information
| Published on | June 13, 2026 |
|---|---|
| Downloads | 4 |