/rtp command — world/region selection¶
Scope of this diagram. This chart covers only the /rtp (and alias /wild) command path from dispatch through SelectionAPI.getRegion(player) to the point where the teleport pipeline (diagram 01) or the public queue (diagram 02) takes over. Related-but-separate behavior paths are intentionally out of scope here and documented elsewhere:
- onEvent auto-teleport (join / respawn / …) — different entry point (
OnPlayerJoin,OnPlayerRespawn, etc.) gated byrtp.onevent.<event>andonEventParsing. SelectionAPI.tempRegion(...)— addon/command extensibility that bypasses the world/region override loops by overridingRegionKeysdirectly against a base region./rtp scan— a separate long-lived worker with its own throttle; see diagram 05.- Pipeline stages after
Start LOAD— see diagram 01. - Cache replenishment driving the "Cache has verified location?" decision — see diagram 02.
flowchart TD
Start([Player runs /rtp<br/>or /wild]) --> CmdParse
%% 1. Command layer decision
subgraph CommandLayer [Command Layer — commands-api + Bukkit dispatch]
CmdParse{Valid args?}
CmdParse -- No --> MsgBad[msgBadParameter<br/>messages.yml<br/>REQ-RTP-F-013 / S-007]
CmdParse -- Yes --> BusyCheck{RTP busy?<br/>inFlightCalculations}
BusyCheck -- Yes --> MsgBusy[msgBusy<br/>messages.yml<br/>REQ-RTP-F-013 / S-007]
BusyCheck -- No --> ResolvePlayer
end
%% 2. World + Region selection (SelectionAPI.getRegion(player))
subgraph WorldResolution [World resolution — WorldKeys.requirePermission / override loop]
ResolvePlayer[player.getLocation.world.name] --> WorldPerm{worldParser<br/>requirePermission?}
WorldPerm -- No --> PickRegion
WorldPerm -- Yes --> WorldHasPerm{player has<br/>rtp.worlds.<name>?}
WorldHasPerm -- Yes --> PickRegion
WorldHasPerm -- No --> WorldOverride[worldName :=<br/>WorldKeys.override<br/>default: default]
WorldOverride --> LoopGuardW{worldName seen<br/>before?}
LoopGuardW -- Yes --> ThrowW[IllegalStateException<br/>infinite override loop]
LoopGuardW -- No --> WorldPerm
end
subgraph RegionResolution [Region resolution — RegionKeys.requirePermission / override loop]
PickRegion[regionName :=<br/>WorldKeys.region] --> RegionPerm{regionParser<br/>requirePermission?}
RegionPerm -- No --> RegionReady
RegionPerm -- Yes --> RegionHasPerm{player has<br/>rtp.regions.<name>?}
RegionHasPerm -- Yes --> RegionReady
RegionHasPerm -- No --> RegionOverride[regionName :=<br/>RegionKeys.override]
RegionOverride --> LoopGuardR{regionName seen<br/>before?}
LoopGuardR -- Yes --> ThrowR[IllegalStateException<br/>infinite override loop]
LoopGuardR -- No --> RegionPerm
end
%% 3. Queue vs unqueued split
RegionReady[Region resolved] --> UnqPerm{player has<br/>rtp.unqueued?}
UnqPerm -- Yes --> CachePop{Cache has<br/>verified location?}
UnqPerm -- No --> CachePop
CachePop == Yes ==> StartLOAD([Start LOAD stage<br/>see diagram 01]):::success
CachePop -- No --> UnqPerm2{has rtp.unqueued?}
UnqPerm2 -- Yes --> AdHoc[Spawn async search now<br/>SETUP stage]:::async
UnqPerm2 -- No --> EnqueuePublic[Park in public queue<br/>wait for selectionAPI pulse<br/>see diagram 02]:::async
%% 4. Per-region behavior is DATA, not code (see prose below)
ShapeChoice[RegionSettings<br/>shape / vert / cacheCap /<br/>worldBorderOverride / price]:::data
RegionReady --> ShapeChoice
%% Color legend: green = happy path into LOAD, red = config-bug throws / bad-param, blue = async/queue work, yellow = data-driven settings
classDef success fill:#b7e4b7,stroke:#1f6b1f,stroke-width:2px,color:#0b2a0b;
classDef fail fill:#f4b7b7,stroke:#8a1f1f,color:#3a0b0b;
classDef async fill:#cfe2ff,stroke:#1f4e8a,color:#0b1f3a;
classDef data fill:#fff2b3,stroke:#8a6d1f,color:#3a2f0b;
class MsgBad,MsgBusy,ThrowW,ThrowR fail
How to read this chart:
- The decision tree is almost entirely data-driven. Nothing in
rtp-corehardcodes "if world == X, use shape Y". Behavior is the product ofworld.yml,region.yml,messages.yml, plus per-player permission nodes. - Two nested override loops — world-level (
WorldKeys.override) and region-level (RegionKeys.override) — protect against infinite loops via aSet<String> attemptedguard, throwingIllegalStateExceptionon cycle detection. This is the only place in the normal flow that throws rather than attributes a failure, because it is a configuration bug, not a teleport failure. - Permission nodes involved (non-exhaustive):
rtp.worlds.<worldName>— gates access to a world; failure falls through the worldoverride.rtp.regions.<regionName>— gates access to a region; failure falls through the regionoverride.rtp.unqueued— bypasses the public queue and forces an ad-hoc async search. Dangerous on large servers; see ADR-001 on why the search is still bounded.rtp.effect.<stage>.*— decorates pipeline stages with sounds/particles/etc. (seedocs/admin/EVENTS_AND_EFFECTS.md).rtp.onevent.<event>— opts the player into auto-teleport on lifecycle events (join / respawn / …). RequiresonEventParsing: true.RegionSettings(shape,vert,cacheCap,activeChunkCap,price,spatialResolution,worldBorderOverride) is the only source of truth for per-region behavior. To change a region's shape or cache size, editregion.yml; never fork code per region.- Busy / invalid messages are configurable (S-007, REQ-RTP-F-013). All user-visible strings come from
messages.yml; never hardcode them in a command or adapter. tempRegion(...)(SelectionAPI) lets an addon or command spawn a one-shot region by overriding specific keys against a named base region (default:default). It is the supported extensibility point for custom per-call behavior — prefer it over constructingRegiondirectly.