ADR-066 - Foreign Config Importer (rtp config import <plugin>)¶
Status: Accepted Date: 2026-06-13
Context¶
Operators evaluating RTP against competing random-teleport plugins (BetterRTP, EzRTP, JakesRTP, and others) face a switching cost: every region center, radius, cooldown, economy price, and biome/block filter has to be re-authored by hand in RTP's config tree. RTP currently has no foreign-config import path. The only migration machinery that exists is internal: locale-file migration in ConfigParser and teleportData.yml migration in RTP.java. There is nothing that ingests an admin's existing third-party config.
This ADR scopes a one-shot migration aid, not a runtime compatibility layer. It is deliberately distinct from the separately-roadmapped BetterRTP API compatibility shim (which keeps emulating a competitor's public API at runtime to absorb the inventory-GUI menu ecosystem). The importer reads a competitor's YAML once, writes RTP's own files, and then RTP owns the config; the competitor plugin need not be installed, enabled, or linked.
Two facts establish that the importer is platform-neutral and can therefore live in rtp-core rather than a Bukkit-family module:
- In-house YAML parser.
RtpYamlConfig/RtpYamlSection(rtp-core/.../common/configuration/yaml/) parse and write YAML with noorg.bukkit.*coupling;ConfigParseralready uses them. Reading a competitor's YAML off disk needs no platform API. - Polygon shape.
Polygon extends Square(rtp-core/.../selectors/memory/shapes/Polygon.java, ADR-034) can replicate any region outline beyond circle/square/rectangle as an admin-authored vertex list, so a competitor's arbitrary region shapes are translatable.
This crosses module boundaries and introduces a new command, so it is D-005-gated and recorded here before implementation.
Decision¶
Add a one-shot, explicit, non-destructive foreign-config importer behind a new command verb, built on a generic source seam.
Command grammar (parallel to rtp prefab)¶
rtp config import- no source argument: auto-detect. Probe the registered sources' expected on-disk locations. If exactly one source's config is present, proceed against it. If zero or more than one are present, list the candidates and require an explicit source (no silent guess).rtp config import <plugin>- explicit source; dry-run preview (default). Lists every key it would map, every approximation, every deferred mapping, and every dropped key. Writes nothing.rtp config import <plugin> confirm- performs the writes after the preview, mirroring theprefabapply->confirmtwo-phase UX. Every RTP file touched is backed up via the existingprefab<file>.yml.bak.<epochMillis>mechanism, pruned byperformance.yml#prefab.bakRetention.<plugin>is aCommandParameterwhose suggestions come from the registered importer keys (commands-api wire grammar; bareconfigsubcommand ->import-> typed/literal source; no free positionals).- Sources at launch:
betterrtp,ezrtp,jakesrtp, extensible to any viable competitor.
Seam shape¶
- A
ConfigImporterSPI keyed by source name, declaring:sourceName(), a detection probe (expected on-disk path(s)),preview()returning a list of mapping outcomes, andapply()performing the backed-up writes. - A registry resolves importers by key and powers auto-detection.
BetterRtpConfigImporteris the first concrete implementation;ezrtp/jakesrtpfollow the same contract.
Module placement¶
rtp-coreholds theConfigImporterSPI + registry, theRtpYamlConfig-based readers, and the mapping/translation logic. It is platform-neutral because it only reads foreign YAML off disk and writes RTP's own config through existing core machinery - there is noorg.bukkit.*dependency.- The platform adapter /
rtp-plugincontributes only the thin command-surface binding (locating competitor plugin directories, dispatching the verb) if any platform-specific path resolution is required.
Translation contract¶
The importer targets each competitor's latest config schema. Translation is honest and lossy: every approximation and every dropped key is logged (S-004-style audit), never silently swallowed. Each mapped key is classified as one of:
MAPPED- clean 1:1 (or near-1:1) translation.APPROXIMATED- semantics differ; logged with the approximation made.DEFERRED- recognized, but the RTP target feature has not landed yet (see sequencing rule).DROPPED- genuinely meaningless under RTP (e.g. internal competitor bookkeeping); reported, not carried.
Known mappings (BetterRTP as the worked example):
| Foreign key | RTP target | Outcome |
|---|---|---|
Shape: square / circle / rectangle |
matching RTP shape | MAPPED |
| arbitrary / custom outlines | Polygon (ADR-034) vertex list |
APPROXIMATED |
CenterX / CenterZ |
region center |
MAPPED |
MaxRadius / MinRadius |
region radius / minRadius |
MAPPED |
Cooldown / Delay |
teleportCooldown / teleportDelay (seconds) |
MAPPED |
MaxAttempts |
performance.yml#maxAttempts (default 32) |
MAPPED |
PreloadRadius |
performance.yml#viewDistanceSelect / viewDistanceTeleport |
MAPPED |
| per-world enable list | worlds / regions | MAPPED |
Price / economy |
economy.yml |
MAPPED |
| biome / block blocklists | safety.yml filters |
APPROXIMATED (key shape differs) |
SetAsRespawn |
persistent spawn-anchor flag (parity feature) | DEFERRED until parity lands |
LockAfter |
cooldown usage cap + reset window (parity feature) | DEFERRED until parity lands |
Sequencing rule for parity-dependent keys¶
SetAsRespawn and LockAfter map onto RTP features that are planned for parity but not yet implemented. The importer maps a foreign key only if its RTP target exists:
- While the target feature is absent, the importer reports the key as
DEFERRED("recognized, target not yet available"), notDROPPED. - When each parity feature ships, its importer row flips from
DEFERREDtoMAPPEDin the same change - no permanent unmapped residue.
This lets the seam ship first and the two mappings activate as their targets arrive, without blocking this ADR on the parity work.
Safety / non-destructiveness¶
- Explicit command only; never a silent startup auto-overwrite.
- Dry-run preview is the default; writes require
confirm. - Every touched RTP file is backed up (reusing the
prefabbackup + retention machinery). - Refuse to clobber a customized RTP config without explicit confirmation.
Alternatives Considered¶
| Alternative | Why Rejected |
|---|---|
| Silent auto-import on startup | Destructive and surprising; can overwrite a populated config without consent. Violates the non-destructive constraint. |
| Live competitor API shim (read competitor files / emulate its API at runtime) | Different goal (absorbing the menu-plugin ecosystem), heavier, and a permanent runtime coupling. Tracked as its own ROADMAP item; this importer is a one-shot aid. |
Importer in rtp-plugin / an addon |
Unnecessary now that file-only reading + the in-house YAML parser prove platform-neutrality. rtp-core keeps a single implementation shared across every platform. |
| BetterRTP-only, no generic seam | Operators migrate from several plugins; a per-source seam (EzRTP, JakesRTP, ...) costs little extra and avoids a rewrite for the second source. |
| Drop unsupported keys silently | Dishonest about lossiness; operators must know what was not carried over. Hence the logged APPROXIMATED / DEFERRED / DROPPED classification. |
Consequences¶
- Positive: One cross-platform importer serves all competitors; materially lowers the switching cost; reuses the
prefabbackup machinery and thePolygonshape; no runtime dependency on any competitor plugin; honest, auditable translation. - Negative / Trade-offs: The two parity-dependent keys cannot transfer until
SetAsRespawn/LockAfterland. Auto-detection needs a clear "pick one" UX when multiple sources are present. - Schema drift is a non-issue in practice. The "track each competitor's evolving schema" concern is largely moot for the launch sources: BetterRTP and EzRTP have not seen a release in years, so their config schemas are effectively frozen and the importer can pin a stable mapping. EzRTP is the only mild caveat (it models faction/claim-anchored centers differently), but its schema is likewise dormant, so a one-time mapping is sufficient.
References¶
- ROADMAP: "BetterRTP config importer (one-shot migration aid)" and the parity items "cooldown usage cap (
LockAfter)" and "persist RTP destination as a permanent spawn anchor (SetAsRespawn)" indocs/dev/ROADMAP.md. - In-house YAML parser:
RtpYamlConfig/RtpYamlSection(rtp-core/.../common/configuration/yaml/); see ADR-025. - Region shapes / Polygon: ADR-034.
- Backup + two-phase
apply/confirmUX prior art: thertp prefabflow andperformance.yml#prefab.bakRetention. - Distinct runtime shim: the "BetterRTP API compatibility shim" ROADMAP item; external-hook policy ADR-026.
- Process: D-005 (Propose Before Implementation).