
Detects swastikas that players build from blocks on Paper servers and reports them to staff for review.
What it does
Vistructum watches block changes and scans whole worlds with two small neural networks. When it finds a swastika, it stores a finding and alerts staff in chat. Staff review the finding in a menu and mark it as confirmed or as a false alarm.
Vistructum never kicks, bans, or rolls back on its own. Every decision stays with your staff.
Features
- Live check: Detects symbols shortly after players finish building them, including rotated, mirrored, irregular, and carved variants from 5×5 blocks upward.
- Fullscan: Scans whole worlds daily or on demand, including the Nether and the End, with a progress bar for staff. It checks the surface and also finds symbols built underground or hidden inside terrain.
- Review menu: Lists open findings with preview images. The detail view shows the scene, the builder's face, location, probability, and source.
- Chat alerts: Staff get a message with [Open] and [TP] buttons for every new finding.
- Local or remote inference: Runs the models inside the server process, or on a separate sidecar with a local fallback.
- Model updates: Downloads new models from GitHub Releases automatically.
- Plugin API: Query findings, submit reviews, start scans, and listen to events from your own plugin.
- Stateless design: All state lives in SQLite. After a restart, running scans continue where they stopped.
Modes
- Live check: The core records every placed and broken block in SQLite. Every few seconds it groups nearby changes into clusters. A cluster that has been quiet for
tracking.quiet-secondsbecomes a mask and goes to the mask model. - Fullscan: The scanner reads the region files directly, without loading chunks on the server, and walks the world in 256×256 tiles. For each tile, it runs two checks:
- Surface: Samples the surface height and brightness and sends the tile to the fullscan model.
- Volume: Finds clusters of blocks that are rare in their surroundings, keeps the flat or evenly extruded ones, and sends their outlines to the mask model.
- Findings: Both paths hand detections to the same reporter. It removes duplicates, fires
FindingCreateEvent, stores the finding, and firesFindingCreatedEvent. - UI:
vistructum-uiuses only the public API. You can replace it with your own plugin.
Requirements
- Paper 1.21.4
- Java 21
Installation
Plugin jars
- Download
vistructum-<version>.jarandvistructum-ui-<version>.jarfrom Releases. - Copy both jars into
plugins/. - Set
pack.public-urlinplugins/vistructum-ui/config.ymlto an address your players can reach. The defaulthttp://localhost:8765/pack.zipworks only on the machine that runs the server. - Restart the server.
On the first start, Paper downloads SQLite JDBC and ONNX Runtime through the libraries entry in plugin.yml.
Docker Compose
docker compose build
docker compose up -d server
The server service runs Paper 1.21.4 with both plugins, ViaVersion, and ViaBackwards. It exposes port 25565 for players and port 8765 for the resource pack.
To run inference on a separate container, start the sidecar and set inference.mode: remote:
docker compose up -d sidecar
Commands and permissions
| Command | Description |
|---|---|
/vis, /vis review [count] |
Opens the review list |
/vis show <id> |
Opens a finding |
/vis status |
Shows the inference mode, loaded models, tracked changes, open findings, and running scans |
/vis tp <id> |
Teleports to a finding |
/vis confirm <id> |
Marks a finding as confirmed |
/vis falsealarm <id> |
Marks a finding as a false alarm |
/vis scan [world|stop] |
Starts a fullscan or stops all scans |
| Permission | Default | Grants |
|---|---|---|
vistructum.staff |
op | Chat alerts, list, detail view, and reviews |
vistructum.admin |
op | vistructum.staff and /vis scan |
Configuration
| File | Content |
|---|---|
plugins/vistructum/config.yml |
Inference mode, model updates, sidecar address, live check timing, daily fullscan worlds and scan threads, retention |
plugins/vistructum-ui/config.yml |
Resource pack port and public URL, map previews in the list |
plugins/vistructum-ui/messages.yml |
All chat and menu texts in MiniMessage format |
The Configuration wiki page describes every key.
Usage statistics
Vistructum sends anonymous usage data to bStats: server and player counts, the inference mode, the model update setting, whether the daily scan is on, the number of scan worlds, and whether vistructum-ui runs. It sends no findings, reviews, or player names. To turn this off for all plugins, set enabled: false in plugins/bStats/config.yml.
Plugin API
Add vistructum-api as a provided dependency and declare depend: [vistructum] in your plugin.yml.
Vistructum.get().findings()
.find(FindingQuery.open().world("world").limit(10))
.thenAccept(page -> page.items().forEach(finding -> staff.sendMessage("#" + finding.id())));
Every future completes on the main thread. The Plugin API wiki page covers setup, threading, and events.
Building
mvn package
| Module | Output |
|---|---|
vistructum-api |
Public API, bundled into the core jar |
vistructum-inference |
Model inference shared by the core and the sidecar |
vistructum-core |
vistructum-core/target/vistructum-<version>.jar |
vistructum-ui |
vistructum-ui/target/vistructum-ui-<version>.jar |
vistructum-sidecar |
vistructum-sidecar/target/vistructum-sidecar.jar |
ml/ contains model training and the Python reference implementation. The plugins don't need Python at runtime. See Building for details.
Contributing
Read Contributing before you open a pull request.
License
Vistructum is licensed under the GNU General Public License v3.0 only. You can use it on any server, including commercial ones. If you distribute a modified version, you must publish its source code under the same license.
Pinned Versions
- R1.21.4–1.21.11
Pages
Members
1Owner