[3.1.1] - fabric parity
worked on bringing up fabric support for menus, visualizations, etc., and preparing 26.2 support against 26.2-pre2 releases
In the process, I generified several components, particularly networking and schematic transformation. This should make it easier to add in-house neoforge support by minimizing reimplementation.
Added
- Region-specific schematic paste now works on Fabric, with a single shared schematic translator across every platform (ADR-058). A schematics/<region>.schem file on a Fabric backend now pastes at the arrival location instead of logging the "no schematic paster is installed (NoOpSchematicPaster)" S-004 fallback on every teleport. The decode/plan and paste orchestration now live once in the platform-neutral WorldBlockSchematicPaster (rtp-api), driven by two RTPWorld primitives (default no-op): setBlocks(List<BlockDelta>) (block-state writes) and restoreBlockEntities(List<PlacedBlockEntity>) (container-inventory restore). The shared Sponge .schem decoder and placement planner produce the block list, and each adapter supplies only the native writes. The deobf MC 26.1/26.2 Fabric world wrappers (V26_1_R1FabricRTPWorld, V26_2_R1FabricRTPWorld) implement setBlocks via the vanilla BlockStateParser + ServerLevel.setBlock on the server thread (S-005-clean, per-block skip auditing per S-004). Bukkit/Paper/Folia were collapsed onto the same single translator: the former BukkitSchematicPaster is removed and its native block-write + container-restore logic moved into a shared BukkitBlockWriter backing BukkitRTPWorld/FoliaRTPWorld's setBlocks/restoreBlockEntities, so no platform carries a bespoke SchematicPaster. Container/block-entity contents restore on Bukkit/Paper/Folia; Fabric stays block-states-only this pass (its restoreBlockEntities is the default no-op). Covered by WorldBlockSchematicPasterTest (2) and BukkitBlockWriterChestTest.
- Live-updating region biome map during a world scan. The admin region-biomes chart now re-renders ~1 Hz from the region's saved biome data (joining the MSPT sparkline and bad-locations map on the live-refresh path), so an admin can watch the biome map fill in as the scan records biomes without re-issuing the command. ChartSpec.Kind.REGION_BIOMES was added to MapDispatch.LIVE_REFRESH_KINDS; RegionBiomesResolver is pure and re-runnable per tick (no chunk I/O, S-005-clean - it reads only the in-memory persisted biome cache via MemoryShape.biomeAt). Covered by a new MapDispatchTest.regionBiomes_bindsLive case.
- Biomes row in the admin Visualizations menu. The /rtp admin Visualizations chart-kind picker now lists a "Biomes" row alongside "Bad locations" and "Pipeline health"; clicking it opens the per-region picker and paints the REGION_BIOMES chart. The typed /rtp visualization biomes [region=<name>] leaf and VisualizationDispatch.paintBiomes were already wired - only the menu surface entry (VisualizationsSubmenuBuilder.KIND_ROWS) was previously withheld.
- Map chart rendering on Fabric (rtp-fabric-ADR-015; MULTI_PLATFORM_PLAN.md Step K). /rtp visualization charts (bad-locations heatmap, region-shape, live sparkline) now render to a real vanilla filled-map on Fabric instead of bottoming out on NoopMapBinding (... no concrete MapBinding installed). New platform-neutral FabricMapBinding + FabricMapCanvas (rtp-fabric-common, no net.minecraft.* binding) paint the maps-api logical palette into a 128x128 ARGB buffer and hand it to three new NM-free FabricVersionAdapter seams (renderMapChart / releaseMapChart / supportsMapCharts). Implemented on the MC 26.2 carrier (V26_2_R1FabricVersionAdapter): allocates a MapItemSavedData/MapId per chart, matches each pixel to the nearest vanilla MapColor, pushes a full-canvas ClientboundMapItemDataPacket, and delivers a FILLED_MAP item; live charts refresh at ~1 Hz. Installed in RTPFabricMod only when the active carrier supports it (other MC lines stay on NoopMapBinding with the localized mapBindingMissing message); per-viewer state released on disconnect (REQ-RTP-MAP-003).
- Written-book menu on the deobf MC 26.x Fabric carriers (rtp-fabric-ADR-012 §4; book-menu parity for 26.x). Completes the Fabric book-menu un-defer started for 1.21+: V26_1_R1FabricVersionAdapter and V26_2_R1FabricVersionAdapter now implement openBookMenu(Object, FabricBookSpec) with a typed, Mojang-named port of the 1.21.11 carrier (build a WRITTEN_BOOK ItemStack with a WrittenBookContent data component, send a transient hotbar-slot ClientboundContainerSetSlotPacket -> ClientboundOpenBookPacket(MAIN_HAND) -> slot revert, never mutating the server-side inventory). Per-fragment hover/click is built via the version-local V26_*FabricLegacyText.parseInteractive(...) (reflective HoverEvent/ClickEvent record-carrier probing). On the deobf MC line compile mappings equal runtime mappings, so the typed binding links (one 26.x ctor difference handled: WrittenBookContent's title is Filterable<String>, not Filterable<Component>). /rtp menu, /rtp config, the admin panel, and the pickers now open the interactive book modal on 26.x as well, instead of the chat fallback; the renderer still degrades to ChatMenuRenderer on 1.20.x and on any book-dispatch failure.
- Written-book menu renderer on Fabric (rtp-fabric-ADR-012 §4 un-defer; MULTI_PLATFORM_PLAN.md Step I Session 3). /rtp menu, /rtp config, the admin panel, and the param/shape/vert pickers now open an interactive written-book modal on 1.21+ Fabric servers, matching the Paper book UX, instead of scrolling past in chat. New platform-neutral FabricBookMenuRenderer (rtp-fabric-common, no net.minecraft.* binding) translates the MenuModel into a fully-formatted FabricBookSpec (placeholders + colour codes via RTPServerAccessor.format; click commands via the shared MenuActionToCommand) and dispatches through a new FabricVersionAdapter.openBookMenu(Object, FabricBookSpec) SPI. The per-carrier overrides (v1_21_R1, v1_21_R5, v1_21_R11) build a WRITTEN_BOOK ItemStack with a WrittenBookContent data component (fragments built by FabricLegacyText.parseInteractive(..., ClickKind.RUN)), send a transient ClientboundContainerSetSlotPacket for the held hotbar slot, send ClientboundOpenBookPacket(MAIN_HAND), then revert the slot, so the server-side inventory is never mutated. The renderer transparently falls back to the existing ChatMenuRenderer on 1.20.x and the deobf 1.26.x carrier (where openBookMenu keeps its default false), when the viewer is offline, or when the book dispatch throws. Wired in RTPCmdFabricRoot in place of the bare ChatMenuRenderer. Covered by FabricBookMenuRendererTest (6 cases). v26_1_R1 book support remains a tracked follow-up.
- Early/experimental MC 26.2 Fabric adapter (rtp-fabric-ADR-014). New rtp-fabric-v26_2_R1 submodule, cloned from v26_1_R1 (deobfuscated MC line, Loom 1.15 unobf, JDK-25 toolchain), pinned to the 26.2-pre-2 pre-release (loader 0.19.2, fabric-api 0.150.1+26.2). RTPFabricMod#adapterFqnFor dispatches the 26.2 line (startsWith("26.2"), which matches both the pre-release friendly string and eventual 26.2.x finals) to V26_2_R1FabricVersionAdapter; the adapter bytecode is merged into both the full and rtp-lite jars post-remap via a dedicated fabric262Bytecode configuration. JDK-25-gated (!excludeJdk25); the pinned versions and dispatch matcher are to be re-verified when 26.2 ships final.
Fixed
- Written-book menu now actually opens on the deobf MC 26.x Fabric carriers (rtp-fabric-ADR-012 §4). After V26_1_R1/V26_2_R1FabricVersionAdapter gained openBookMenu, /rtp menu//rtp config/the admin panel still fell back to the chat renderer on 26.x because FabricBookMenuRenderer recognised only the common FabricRTPPlayer (instanceof) when resolving the target player. On the deobf 26.x runtime the online player is a per-version sink (V26_1_R1FabricRTPPlayer / V26_2_R1FabricRTPPlayer) that implements RTPPlayer directly and does not extend the common FabricRTPPlayer, so the renderer silently degraded to chat - the book never opened. New FabricBookOpener capability interface (rtp-fabric-common): each Fabric player exposes a single openBookMenu(FabricBookSpec) method (delegating to the active FabricVersionAdapter with its own ServerPlayer, which stays private to the player) and the renderer now just calls opener.openBookMenu(spec) instead of extracting and passing a raw ServerPlayer. Implemented on the common FabricRTPPlayer (1.20.x / 1.21.x) and both 26.x players, so the book opens on 26.x. 1.20.x is unaffected (its carrier keeps the openBookMenu SPI default false/ and the renderer falls back to chat).
- /rtp admin, /rtp config, and the chat menu now render clickable text on the deobf MC 26.x Fabric carriers. On 26.x the written-book modal is not yet wired (the carrier keeps openBookMenu's default false), so the menu falls back to the chat renderer - but FabricServerAccessor's rich-text path could only attach hover/click to the common FabricRTPPlayer; for the per-version 26.x sink (V26_1_R1FabricRTPPlayer / V26_2_R1FabricRTPPlayer) it degraded to a plain, dead-text message, so every menu//rtp info line arrived unclickable. New FabricInteractiveMessageSink capability interface (rtp-fabric-common): a per-version player that implements it builds the hover/click Component on its own deobf-linkable runtime, and FabricServerAccessor.sendInteractiveMessage / sendMessageAndSuggest route through it instead of the plain fallback. Both 26.x carriers implement it via a new parseInteractive(...) (plus reflective, mapping-agnostic HoverEvent/ClickEvent builders) added to their version-local legacy-text parsers. 1.20.x/1.21.x are unaffected (they use the common FabricRTPPlayer path, and 1.21+ opens the book).
- Fabric rtp scan no longer abandons chunks that need generating. FabricRTPWorld#getOrLoadChunk (both the obf and unobf carriers) bounded each live chunk load with an adapter-owned LIVE_LOAD_DEADLINE_MS of 4 s. Vanilla cold-start generation on a never-before-explored coordinate routinely takes 5-10 s under early-server scan/pre-fill load, so the deadline fired and rejected the in-progress generation as a nullChunk before vanilla finished - even though ScanTask's full-load path already waits 30 s. The per-chunk deadline is now 30 s, matching the scan wait so generation runs to completion while still bounding a genuinely stuck coordinate; the bridge-dispatch (server-shutdown drop) watchdog is unchanged.
- /rtp scan cancel (and pause) no longer freeze the server or spam timeout exceptions. ScanTask.setCancelled(true) and pause() drained outstanding chunk-load futures with an unbounded inFlightGate.acquire(MAX_PENDING_CHUNKS) on the calling (command / main) thread. If a candidate chunk was mid-generation, the drain blocked until each in-flight load finished or hit the per-future 30 s orTimeout, stalling the server for up to many seconds, and every elapsed timeout still logged a Chunk generation exception WARNING during the cancel - flooding the console. The drain is now bounded (tryAcquire with a 5 s ceiling) and first actively cancels every tracked in-flight chunk future, so cancellation returns promptly without waiting out the timeouts (the cancelled / paused flag already guards late callbacks from mutating saved state). The full-load completion handler now suppresses the timeout / null-chunk WARNING once the task is cancelled or paused, eliminating the log spam.
Removed
- Dead Mojmap-decoupling wrappers and their SPI methods on the Fabric version adapter (rtp-fabric-ADR-007). Four FabricVersionAdapter methods had no live callers anywhere in the tree - blockKey, biomeKeyAt, hasChunk, and airState - so they were deleted from the interface and from every per-version adapter implementation (v1_20_R1, v1_21_R1, v1_21_R5, v1_21_R11, v26_1_R1, v26_2_R1) plus the test stub. With those gone, the RTPBlockHandle, RTPBlockStateHandle, and RTPRegistryKey wrapper records became unused and were removed. The still-live chunk-load / ticket seam is unchanged: RTPLevelHandle (parameter of requestFullChunkAsync / getChunkFull / applyTicket / releaseTicket) and RTPChunkHandle (their return) remain. No behaviour change.
- bStats pipeline_latency_buckets chart. The chart bucketised avgPipelineMs, the mean of every TeleportPipelineTask completion. Because the histogram sample window starts at task construction (ADR-032), it includes at-rate queue-wait / configured-delay time for queued teleports (only the slow-teleport audit is gated to immediate/unqueued teleports per ADR-053). Bucketising that mean misrepresents pipeline latency across the bStats fleet, so the chart was dropped (registration in RTPCostMetricsCharts, BStatsChartIds.PIPELINE_LATENCY_BUCKETS, and the pipelineLatencyBucket helper removed). The honest per-teleport latency signal remains operator-local via /rtp info (mean + P50..P99 percentiles, plus the immediate/unqueued-gated slow-teleport audit).
[3.1.0] - 2026-05-31
Added
- German (de) locale shipped across all config/message files; full locale parity maintained.
- Region-specific schematic paste platforms (ADR-058) with new PasteOptions API and RegionVerifierRegistry footprint claim checks (S-003). New prefabs updated to use it (skyblock, oneblock, high-performance, multi-world).
- Pluggable bare-/rtp root action (ADR-056, REQ-API-F-006). New RootActionRegistry SPI (RTPAPI.hooks().rootAction()) lets an addon take over a bare /rtp (e.g. open a GUI menu) instead of teleporting; subcommands are never routed through it. Shipped with an example menu addon.
- Platform-agnostic addon SPI (ADR-057). New RTPAddon lifecycle interface + AddonRegistry (RTP.addons, ServiceLoader-based) so an addon jar runs unchanged on Bukkit/Paper/Folia, Fabric, and proxy JVMs. RTP_ExampleAddon ported with zero org.bukkit.* imports.
- Optional PvP / combat-tag gate (ADR-055). Off by default (safety.yml#pvpCheckEnabled=false). When enabled, /rtp is refused for a player currently in combat, using either a bound PvPCombatStateRegistry provider (combat-tag plugins such as PvPManager / SimpleCombatLog / CombatLogX) or RTP's built-in native damage tracker. The native tracker is now fed on every supported platform: Bukkit/Paper/Folia via an EntityDamageByEntityEvent listener and Fabric via ServerLivingEntityEvents.AFTER_DAMAGE (both resolve the projectile shooter / primed-TNT igniter as the aggressor and stamp victim/aggressor per pvpTagVictim / pvpTagAggressor). The gate is consulted at the /rtp pre-dispatch surface before the player is enrolled, fails open (a broken external provider never blocks a teleport), audits every refusal at WARNING (REQ-RTP-S-004), and uses the new configurable messages.yml#pvpInCombat string (REQ-RTP-F-013). Six safety.yml knobs: pvpCheckEnabled, pvpCombatTagSeconds, pvpOnCombat, pvpSource, pvpTagVictim, pvpTagAggressor.
- Bundled combat-tag plugin integrations for the PvP gate (ADR-055). New reflection-based soft-depend adapters for PvPManager, CombatLogX, and Simple Combat Log (rtp-plugin/.../softdepends/pvp/); PvPIntegrations.setup binds the first enabled plugin (priority PvPManager, then CombatLogX, then Simple Combat Log) to PvPCombatStateRegistry via RTPAPI.hooks().pvpCombatState(), gated on isPluginEnabled(...) like the claim integrations. When none is installed (or pvpSource: NATIVE), the native combat tracker answers; a reflective/API-version failure disables the adapter for the session and treats the player as not-in-combat (REQ-RTP-S-004). Wired in both the full and rtp-lite bootstraps. Covered by PvPCombatAdapterTest.
- uniquePlacements is now an integer chunk radius (was boolean; defaults to 0 = off). N >= 1 clears a (2N-1)x(2N-1) chunk square around each used spot so placements spread out; legacy true/false still coerce to 1/0. Retired spots tagged with a dedicated uniquePlacement fail cause.
- Config getters tolerate boolean<->int cross-typing, so a legacy true/false on a now-numeric knob (or 0/1 on a boolean knob) resolves instead of throwing.
- Optional emergency-platform block-restoration timeout (ADR-060). New safety.yml knob platformRestoreSeconds (-1 = disabled default; 0 = restore on first loaded pulse; > 0 = after that many seconds). Restores are chunk-load-aware (never force-loads, S-005), Folia-safe, and persisted across restarts.
- /rtp info now surfaces generation outcomes (REQ-RTP-OBS-007, ADR-052): success/failure rate and a per-cause rejection breakdown, via six new placeholders and three messages.yml lines.
- Live-updating bad-locations map during a world scan: the admin "Region shape" chart now re-renders ~1 Hz from the in-memory bad-keys cache so scan progress is visible without re-issuing the command.
- New bStats charts: language_selection, MSPT-p99 by platform/game-version/plugin-version, and aggregate generation-outcome (success rate + top failure cause). All privacy-safe (bucketised / whitelisted categories).
- (Pro) Numeric range predicates in the safety-list grammar (ADR-017 amendment). safety.yml::unsafeBlocks / airBlocks now accept block-state comparisons (LAVA[level<=3], FIRE[age>=10], *[level>=8]); multiple bounds AND together. rtp-lite ignores tag/predicate tokens.
tested a little more on paper and folia. fabric can run decently but it's gonna be behind on the menu system for a little bit, I'll get to it when someone needs it.
fixed a lack of cooldown/delay check on folia
added visualizations for region mapping and mspt/heap
visualization framework is the last component I needed to feel ready to switch to more day to day upkeep and feature extension rather than feature implementation.
This one was exceedingly difficult, there are still gaps but I've created a multi-server test harness to work through it.
RTP 3.0.0-beta.4 is out, and it's the release where network mode becomes a real feature instead of plumbing. If you run a Velocity network with multiple RTP backends, this is the upgrade you've been waiting for. Plus: a curated admin panel, prefab configs, safety fixes, and the usual round of Fabric improvements.
Network mode is here
Beta.3 shipped the wire (heartbeats, transports, reservation tokens). Beta.4 makes it usable end-to-end - a player on any backend in your network gets the sameexperience they'd get on a single server, with proper queueing, fair load distribution, and a tamper-resistant control plane.Code:/rtp
- Shared cross-server waitlist. One unified
queue across every backend in the network. Players see their real position (Code:/rtpplaceholder) regardless of which backend they're on, get notified when it's their turn, and can't double-enrol by hopping servers. Backed by Redis with atomic Lua scripts and cross-proxy single-leader election (RedisLeaderLease), or an in-memory reference impl for single-proxy / testing setups.Code:[position]- Lobby load balancing (most-kept). Flip
on your lobby backend and bareCode:network.yml::routing.lobbyMode: truenow routes each player to the peer with the most pre-generated locations ready to serve - instead of slamming everyone onto whichever backend sorted lex-smallest. Three interacting bugs that broke this in beta.3 (zeroed kept-counts, ignored "not accepting" flags, missed local decrements) are all fixed.Code:/rtp- HMAC-signed transport. Every BackendHeartbeat / ProxyHeartbeat / ReservationToken row on both Redis and SQL transports is now signed and verified with constant-time compare. Tampered or unsigned legacy rows are dropped with a warning. SQL deployments self-heal on the next heartbeat (idempotent
migration, MySQL/MariaDB dialect quirks handled).Code:ADD COLUMN- Multi-proxy ready. Two Velocity proxies in front of two Paper backends is the acceptance target. No proxy-to-proxy chatter, no singleton assumptions - proxies coordinate purely through the shared transport.
If you've been holding off on running RTP across a network because of fairness or trust concerns, now's the time to try it. See docs/dev/MULTI_SERVER_PLAN.md and docs/admin/proxies/ for the setup walkthrough.
Other highlights
- Admin panel + curated prefabs.
opens a permission-gated panel with five bundled prefab configs (survival-default, low-performance, high-performance, folia-tuned, multi-world). Apply via menu orCode:/rtp admin- single-use confirmation nonce, automaticCode:/rtp admin prefab {list,apply,confirm,rollback}backups, best-effort hot reload. See docs/admin/PREFABS.md.Code:.bak.<epoch>- Declarative chart composition bridge. First concrete MapBinding (BukkitMapBinding + Folia override) with single-use chart-spec tokens, so
can offer ephemeral map-rendered charts (heatmaps, region coverage, time series).Code:/rtp info
Fixes worth calling out
- Fabric: permission-granted on-event teleports and effects now actually fire (LP-Fabric node-set discovery + op fallback).
- Safety: L3 backlog mid-air placement regression closed - promotion re-runs the vertical adjustor against the loaded chunk rather than trusting the placeholder Y.
- Config:
and biome names now accept either bare orCode:safety.yml::unsafeBlocks-namespaced form (modded namespaces preserved verbatim).Code:minecraft:- YAML database: race in YamlFileDatabase.setValue(String, Map) that flattened top-level keys is fixed.
Gaps specific to this release
- Fabric does not yet have the menu system.
, the curated front page, the newCode:/rtp menupanel, and the chart/map bridge are Bukkit/Paper/Folia only this cycle. Fabric operators still configure RTP via YAML and the existing Brigadier command tree.Code:/rtp admin- Fabric is not on the network yet. The cross-server waitlist, lobby load balancing, reservation tokens, and signed Redis / SQL transports are Bukkit/Paper/Folia backends only. Fabric backends can't join a proxy network this cycle.
- Many of the new config values are still English in non-English locales. Admin panel / prefab strings, menu reflector hovers, chart-spec menu labels, waitlist
/ alreadyQueued, backlogRefillThreshold and friends, new network.yml comments - all render in English until a native speaker translates them. If you'd like to help, open a PR editing your locale's file under rtp-plugin/src/main/resources/lang/<lang>/, or drop suggestions in an issue / Discord ticket.Code:[position]
Notes
- Still a beta - back up your world and your plugins/RTP/ before upgrading.
- Full notes: CHANGELOG.md in the repo.
- Issues / feedback welcome on GitHub or the Discord.
Hey folks, quick rundown of what's landed in RTP since the last update:
Menus
Effect fixes
- New interactive /rtp menu framework with a curated front page (Teleport / Region picker / Biome picker / Help) and a dedicated admin panel (Config editor, Server info, Full diagnostics, Memory tracker snapshot, Scan control, Reload, Browse all commands).
- Permission-gated rows that auto-hide; every label and hover is configurable via messages.yml.
In-house YAML reader/writer
- Folia dispatch hardened for PotionEffect and FireworkEffect (closes the "firework never spawns / Asynchronous effect add" symptom).
- Effect permission tokens with skipped inputs no longer break the rest of the chain.
Metrics (mostly working)
- rtp-core now uses our own zero-dependency YAML reader/writer/AST with block-comment preservation and atomic saves, replacing simpleyaml. Gives us tighter control over comments, key order, and locale parity.
New languages
- /rtp info gained a live health block: queue depth, pending teleports, average pipeline latency, and heap usage. Seven new placeholders (queueDepth, pendingTeleports, avgPipelineMs, heapUsedMb, heapMaxMb, etc.) usable in messages.yml.
- bStats now reports eleven new privacy-safe charts (platform, assembly variant, DB backend, region count, cache pool health, TPS/MSPT/pipeline-latency/memory/queue/chunk-load histograms).
- Per-platform TPS bindings still being polished, but the pipeline is functional.
Other notable additions
- Four new locale trees since beta.2: Italian (it), Polish (pl), Portuguese (pt), and Russian (ru). Locale set is now 11: de, es, fr, it, ja, ko, nl, pl, pt, ru, zh.
- Large batch of comment-block translations across every locale, plus a Chinese messages key-rename pass and a cleanup of inline/trailing comments that had baked into quoted values.
Velocity / multi-server
- L3 backlog cache (ADR-028) has a new backlogRefillThreshold hysteresis knob.
- New pregeneratedPreference performance knob to bias selection toward already-generated chunks.
- New muted teal / lavender color palette across menus and system messages.
As always, bug reports and feedback are welcome.
- Infrastructure only. The rtp-proxy module (rtp-proxy-common, rtp-proxy-velocity, rtp-proxy-bungee) is in place along with the MULTI_SERVER_PLAN and a stack of ADRs. Not functional yet - no cross-server teleports - this is groundwork for the next round.
working towards stability and full utilization of the engine
- started running and testing on fabric
- due to the lack of permissions on fabric, added effects yaml interpretation under
effects, similar toregions.- fixed effects permission parsing
- merged RTP_Glide into efffects-api as a
glideeffect. refer to docs for usage- added a few more runtime metrics tracked internally to the plugin, like memory usage, tps, mspt
- added new vertical adjustor
fixed, i.e. a fixed (static, unchanging) configuredylevel.- beyond basic kept and unkept caches, added level 3
backlogcache for pre-filter binned by region file for efficient cpu utilization under load.- added optional pregen preference configuration (percentage), using bounded rejection sampling
- added early design doc for next-step proxy support
- cleaned up extraneous / completed / replaced files from docs
