Skip to content

Instantly share code, notes, and snippets.

@ollieread
Created May 3, 2026 17:17
Show Gist options
  • Select an option

  • Save ollieread/71a7a6dec003210bc0016a35ea7b5d42 to your computer and use it in GitHub Desktop.

Select an option

Save ollieread/71a7a6dec003210bc0016a35ea7b5d42 to your computer and use it in GitHub Desktop.

Cartograph Test Rig

A Docker Compose sandbox for exercising the Cartograph Minecraft plugin against real server software, with simulated player traffic via Mineflayer bots.

The rig supports six platforms (Paper, Spigot, Folia, NeoForge, Velocity, BungeeCord) across two Minecraft version lines (1.21.x on Java 21, 26.1.x on Java 25). Plugin telemetry can be inspected locally via a tiny Node sink, or pointed at any HTTP endpoint via CARTOGRAPH_API_URL.

Quick start

cp .env.example .env
./gradlew -p ../cartograph-plugin installToTestrig    # populates plugins/
bin/tail-telemetry &                                   # local telemetry sink
bin/up paper 1.21                                      # start a topology
bin/bots idle 5                                        # spawn 5 idle bots

Within a minute you should see boot, heartbeat, and player join/leave events in the sink.

Topologies

Command Bring-up
bin/up paper 1.21 Paper 1.21.4 standalone backend on :25565
bin/up paper 26.1 Paper 26.1 standalone backend on :25565
bin/up spigot 1.21 Spigot 1.21.4 standalone backend on :25565
bin/up spigot 26.1 Spigot 26.1 standalone backend on :25565
bin/up folia 1.21 Folia 1.21.4 (experimental channel) on :25565
bin/up neoforge 1.21 NeoForge 1.21.4 standalone server on :25565
bin/up velocity 1.21 Velocity proxy on :25577 + 2 Paper backends (network-internal)
bin/up velocity 26.1 Velocity (Java 25) on :25577 + 2 Paper backends
bin/up bungee 1.21 BungeeCord proxy on :25578 + 2 Spigot backends
bin/up bungee 26.1 BungeeCord (Java 25) on :25578 + 2 Spigot backends

bin/up folia 26.1 and bin/up neoforge 26.1 are intentionally rejected. Folia has not released a 26.x experimental build, and NeoForge 26.x is beta-only upstream — the plugin's policy is to support stable releases only. These topologies will be added when the upstream stable releases ship.

bin/down brings down whichever topology is up. bin/reset additionally prunes volumes for a clean slate.

Bot scenarios

bin/bots command What it does
bin/bots idle 5 5 bots, idle for 300s — heartbeat & presence
bin/bots active 8 600 8 bots, random walks + chat for 600s
bin/bots churn 20 300 20 bots, rapid join/leave loop for 300s
bin/bots soak 3 1800 3 bots, slow wander for 30 minutes

bin/bots auto-detects the active topology and connects to the proxy (when present) or directly to the backend.

Telemetry sinks

There are two ways to receive telemetry:

1. Local Node sink (default). bin/tail-telemetry [port] runs a tiny HTTP server that pretty-prints every payload to stdout and replies 204 No Content. Set CARTOGRAPH_API_URL=http://host.docker.internal:8000/api/telemetry in .env (already the default).

2. Real platform. Point CARTOGRAPH_API_URL at a running Cartograph platform instance, a staging URL, or a tunnel.

Test scenarios

The reference scenarios from the test rig brief, with concrete commands:

Scenario Topology Bots What it verifies
Boot event on startup bin/up paper 1.21 none Boot event arrives with token + version metadata
Heartbeat cadence bin/up paper 1.21 none Heartbeats every 60s
Player join/leave events bin/up paper 1.21 bin/bots idle 5 Join on connect, leave on disconnect
Buffered event flush bin/up paper 1.21 bin/bots churn 20 300 Time-threshold flush under load
Session duration bin/up paper 1.21 bin/bots soak 3 1800 Accurate session durations
Graceful shutdown bin/up paper 1.21 bin/bots idle 2 600 Shutdown event fires on bin/down
Spigot parity bin/up spigot 1.21 bin/bots active 5 300 Same telemetry as Paper
Folia thread safety bin/up folia 1.21 bin/bots active 10 300 No region-thread violations
NeoForge mod loading bin/up neoforge 1.21 bin/bots idle 3 300 Mod loads, full telemetry fires
Velocity proxy + backends bin/up velocity 1.21 bin/bots active 8 300 Three distinct tokens, correct routing
BungeeCord proxy + backends bin/up bungee 1.21 bin/bots active 8 300 Same as Velocity for Bungee
26.1 — supported platforms bin/up <paper|spigot|velocity|bungee> 26.1 bin/bots idle 3 300 Plugin works on Java 25 / MC 26.1
Auth failure handling bin/up paper 1.21 (with bogus token) none Plugin handles 401 without crashing
Multi-topology side-by-side bin/up velocity 1.21 && bin/up bungee 1.21 bin/bots idle 2 300 per topology Both proxy nets coexist on different ports

Configuration (.env reference)

Var Used by Notes
CARTOGRAPH_API_URL every plugin-running container Telemetry destination
TOKEN_BACKEND standalone backend API key for single-server topologies
TOKEN_VELOCITY_PROXY velocity-proxy Distinct per-node token
TOKEN_VELOCITY_BACKEND_{1,2} Velocity backends One per backend
TOKEN_BUNGEE_PROXY bungee-proxy
TOKEN_BUNGEE_BACKEND_{1,2} BungeeCord backends
SERVER_TYPE / SERVER_VERSION / SERVER_IMAGE_TAG / PLUGINS_VERSION_DIR / PLUGINS_TYPE_DIR every container Set by bin/up arguments
SERVER_MEMORY / PROXY_MEMORY resource limits
BOT_* bot orchestrator Defaults overridden by bin/bots arguments

Troubleshooting

First Spigot boot is very slow. The itzg/minecraft-server image runs BuildTools to compile Spigot from source on first boot. Subsequent boots are fast because the result is cached in the data volume. bin/reset will clear that cache.

Bots exit immediately with unsupported_protocol. Mineflayer protocol support for 26.1 may not have shipped yet. Run the topology without bots and observe boot/heartbeat events directly. See bots/README.md.

Plugin doesn't connect to the sink. Check from inside the container:

docker exec $(docker compose -p cartograph-testrig ps -q backend) \
  sh -c 'apk add --no-cache curl >/dev/null 2>&1 || true; \
         curl -v "$CARTOGRAPH_API_URL"'

Linux hosts may need extra_hosts: ["host.docker.internal:host-gateway"] added to each compose service if they don't already have it via Docker Desktop.

${CARTOGRAPH_*} literals appear in the container's plugin config. That means REPLACE_ENV_VARIABLES=TRUE isn't being honored, or REPLACE_ENV_VARIABLE_PREFIX is misspelled. Confirm both env vars are set on the relevant service and that the file is under /data/plugins/ (Bukkit), /data/config/ (NeoForge), or /server/ (proxies).

Where do the JARs come from? Either run ./gradlew -p ../cartograph-plugin installToTestrig to populate every plugins/<platform>/<version>/ directory, or drop a JAR by hand. See plugins/README.md.

Architecture

Directory layout

cartograph-testrig/
├── docker-compose.yml             # base: shared network only
├── compose/                       # one file per topology, plus overrides
├── plugins/<platform>/<line>/     # JAR drop directories (gitignored)
├── config/                        # plugin config templates with ${VAR}
├── proxy-config/                  # Velocity/BungeeCord server-side configs
├── bots/                          # Mineflayer orchestrator (TS + Docker)
├── tail-telemetry/                # Node debug sink + Jest test
├── bin/                           # POSIX shell helpers
└── docs/superpowers/{specs,plans}/  # design & plan docs

Per-Minecraft-version JAR subdirs

Plugin JARs are version-specific (cartograph-paper-1.21-*.jar differs from cartograph-paper-26.1-*.jar). A flat plugins/bukkit/ would force swapping JARs every time you switch MC line. The per-version subdirectory layout means both lines can stay populated, and bin/up simply mounts the matching one.

Per-server-type Bukkit subdirs

The Paper, Spigot, and Folia JARs all declare themselves as name: Cartograph in plugin.yml, so mounting all three into /data/plugins at once causes an "Ambiguous plugin name" error on server boot. To avoid this, the Bukkit tree splits one level deeper by server type: plugins/bukkit/<line>/{paper,spigot,folia}/. bin/up exports PLUGINS_TYPE_DIR (paper | spigot | folia) which the compose files use to mount only the matching subdirectory. NeoForge, Velocity, and BungeeCord don't have this issue (each platform produces a single, uniquely-named JAR).

Two Bukkit configs

config/bukkit/ is the standalone-server config. config/bukkit-backend/ is identical except for flags.proxy-backend: true, which tells the Cartograph plugin to skip player join/leave tracking and report the node's type as BACKEND. Without this flag, backends behind a Velocity or BungeeCord proxy emit duplicate join events alongside the proxy.

Env-var flow

bin/up exports a small set of vars, plus the TOKEN_* values from .env. Compose interpolates them into service env. The itzg/* images, seeing REPLACE_ENV_VARIABLES=TRUE and REPLACE_ENV_VARIABLE_PREFIX=CARTOGRAPH_, substitute ${CARTOGRAPH_*} placeholders inside the synced config files on startup. End result: each container sees its own API key inside config.yml.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment