Fabric 26.2 · Server-side

StaffCore Handbook

Everything the mod does, where to find it, and what it costs a player when you use it. Written for the people who will actually be running it at 2am.

Minecraft
26.2
Modules
18
Screens
21
Commands
One /staff tree
Permission nodes
40
Client mod
Not needed

Orientation

The idea

StaffCore is panel-first. Every tool lives behind /staff, in a chest menu you can operate without remembering a single command.

That is the whole design argument. Moderation happens under time pressure, usually while someone is actively causing the problem, and a staff member who has to recall the argument order of /tempban is a staff member who is not watching the griefer. So the panel is the primary interface and the commands are the escape hatch — for free text, for speed, and for people who prefer typing.

Three consequences worth knowing up front:

  • Nothing irreversible happens without a confirm screen. Bans, rollbacks, history wipes and inventory restores all show you what is about to happen and make you click again.
  • Punishment reasons are presets, not free text. A chest menu has nowhere to type, and a fixed vocabulary makes history comparable across a whole staff team. The command form is there when you genuinely need something bespoke.
  • Punishments work on offline players, and so does the inventory view. Both resolve a name through the server's own records rather than requiring the target to be online — which is usually the entire point.
  • Every command starts with /staff. One word to remember, and the rest is tab-complete.

No client mod, no resource pack, nothing for players to install. Everything you see is a normal chest GUI and normal chat, rendered server-side.

The hub

The panel

/staff opens a six-row menu split into three bands, in the order a shift actually runs: what you switch on about yourself, what you do to other people, and what you do to the whole server.

You

Staff Mode · Vanish · Staff Chat · Alerts · Command Spy · Return

People

Players · Reports · Punish · Inventories · Notes · History · Security Check

Server

Server Control · Grief Log · Analytics · Item Scanner · Discord status

Info

Your record · Live server status · A help card listing the equivalent commands

Anything you lack permission for is drawn as an iron-bars "Locked" bar naming the node you need, rather than hidden. Staff can see what exists and ask for it, instead of concluding the mod is broken.

How the screens connect

/staff  Staff Panel
 ├─ Players ─────────── Player List ─┬─ left-click  → whatever you came in for
 │                                   └─ right-click → the player's file
 ├─ Player file ──┬─ Punish ─── Duration ─── Reason ─── Confirm
 │                ├─ History          paged, wipe sits behind a confirm
 │                ├─ Notes            paged, delete sits behind a confirm
 │                ├─ Inventory        live, read-only or editable
 │                ├─ Snapshots ────── Snapshot view
 │                └─ Security check   paged findings
 ├─ Reports ─────────── claim / resolve / teleport
 ├─ Server control ──── chat lock · clear · broadcast · maintenance · TPS
 ├─ Grief log ───────── paged block history ─── Rollback (preview → confirm)
 └─ Analytics ───────── staff leaderboard

The player list is the junction. Whichever door you came in through — Punish, Notes, Inventories — a right-click on any head opens that player's full file, so you are never more than one click from everything else you might need.

Your shift

On duty

Staff Mode

Clocking on does four things at once: it stashes your real inventory, hands you the staff toolset, grants flight, and vanishes you. Clocking off reverses all four.

Your inventory goes to the database, not to memory. That was the one genuine data-loss risk in the mod: a restart while anyone was clocked on used to destroy their items outright. Now clocking on writes the stash before clearing anything — if the write fails, staff mode refuses to start rather than emptying an inventory it cannot put back — and the stash is cleared only after a successful hand-back. The failure mode is a duplicate restore attempt, never a loss.

Disconnecting on duty now keeps you on duty. A staff member who crashes mid-incident comes back with their tools, and their real inventory is still waiting.

The duty gamemode is creative by default, because that is what being on duty actually needs: fly to the report, phase through the base you are inspecting, and put back the block you just proved was griefed. staffModeGameMode defaults to survival. Creative was the old default, and it is the reason four separate containment layers exist: staff tools are refused on drop, stripped on death, destroyed if they reach the ground, and swept every two seconds — all of it work to stop an unlimited item source leaking into the economy. Survival removes the source instead of containing it, and every layer stays as defence in depth.

Very little is lost. Flight comes from being on duty rather than from creative, so staff still fly to a report; noclip is its own toggle; and putting back a griefed block is what rollback is for. Creative is still available and costs three things: staff become an unlimited item source, so anything they hand out is outside the economy and indistinguishable from a duplication bug; they stop looking like ordinary players to an anti-cheat, so their own behaviour goes unchecked; and the containment layers become load-bearing rather than belt-and-braces. spectator is the least intrusive and stops the staff toolset working — the tools are items used by right-clicking, and vanilla drops interactions for spectators before they reach any of this mod's code. Whichever you pick, the gamemode somebody was in before clocking on is stored and handed back when they clock off, so changing this strands nobody. On duty you are also invulnerable, vanished or not, so investigating a mob farm does not make you part of the incident. Set staffModeInvulnerable to false if you would rather staff stayed killable.

Clocking off really does end it Flight and invulnerability are worked out from who is currently claiming them — your gamemode, duty, vanish — rather than restored from a copy taken earlier. That matters because staff mode and vanish set the same two fields and normally overlap, so a remembered "what it was before" is whichever of them wrote last. Worth knowing what invulnerability actually costs you if it lingers: vanilla treats an invulnerable player as one no mob can see, so they are not merely unhurt — nothing ever comes at them. Rejoining would have hidden it, because the game rebuilds both fields from your gamemode on login.

Movement

The panel carries a Movement control: noclip and a fly-speed cycle (1× / 2× / 4× / 8×).

Noclip is spectator That is not a shortcut — it is the only thing that works. The client runs its own collision, so a server-side "ignore physics" flag is simply ignored: you walk into the wall on your own screen and the server pulls you back. Vanilla's own check for passing through blocks is are you a spectator, so that is what the toggle switches to. Your duty gamemode is remembered and restored when you switch it off.

Spectator normally means you cannot click anything, including this mod's own menus — vanilla drops container clicks for spectators before they ever reach the menu. StaffCore carves out an exception for its own screens, so the panel, the player file and every other tool keep working while you noclip. Real chests stay untouchable, as they should.

Vanish

Vanish is not invisibility. It is the absence of every trace a player leaves.

When you vanish, everyone without staff.vanish sees "<name> left the game" — the real vanilla message, same translation key, same yellow. Un-vanishing sends the matching join line. A staff member who simply blinks out tells every observant player that vanish exists and somebody is watching; one who leaves is indistinguishable from somebody who actually did. Staff who can see through vanish get the truth instead.

The fake message and the real one have to be kept from stacking, and getting that wrong is very visible. Logging off while vanished must not print a second "left the game" on top of the one your vanish already sent, and rejoining must not print "joined the game" or put you back in the tab list. Both took two attempts to get right, and both failures came from the same mistake — assuming an ordering instead of checking it.

The leave line. Suppression is driven by a record of who to stay quiet about, and that record was being cleared from Fabric's disconnect event. Fabric fires that event from the head of the connection teardown, which is before the server builds the "left the game" message — so the state was gone before the message it was meant to suppress existed. The cleanup now happens on the far side of the broadcast instead, driven by the player-list removal that vanilla performs immediately after it.

The tab list. Joining broadcasts your list entry to every client. The old fix sent that entry and then chased it with a removal packet a tick later. That is a race, and a race is exactly the wrong guarantee for a feature whose entire job is that nobody notices — it works most of the time, which is worse than not working at all, because you stop checking. The entry is now simply never sent to clients that should not have it. Same technique as the entity tracker: refuse the decision at the source rather than undo its consequences.

What is covered

LeakHow it is closed
Seen at allThe tracker answers vanilla's own broadcastToPlayer with "no", so you are never paired with an unauthorised client
Join / leave linesSuppressed, and replaced with convincing fakes on vanish
Tab listEntry withheld at login, and removed on vanish
Player countExcluded from getPlayerCount
/listFiltered for anyone who cannot see vanished — names and count both
Server-list pingBoth the online count and the hover sample — the ping sees zero of you
Command selectors (@a, @r, /msg)Filtered for anyone who cannot see vanished
Sound — walking, breaking, placing, usingSuppressed at Level#playSound
Chest and shulker lid animationsOpen/close events refused
Sculk sensors, shriekers, the wardenNo game events emitted
Mobs noticing youTarget assignment refused outright
Entity collisions — boats, carts, mobsNot pushable, and pushes nothing
Item, XP, arrow and trident pickupRefused
Sleep countExcluded, so night still passes
Pressure plates and tripwiresNot counted, so nothing clicks open
Mob spawning, phantoms, proximity AIFiltered out of getNearestPlayer
Death and advancement announcementsSuppressed while vanished
Trial spawners and vaultsNot detected, so chambers stay unarmed
Mob-spawning chunk eligibilityNot counted, so farms run as if nobody is there
Sweeping-edge splashNever lands, so no particles in mid-air
Arrows, fireworks, bobbers, pearlsDiscarded rather than flown
DamageInvulnerable while on duty
Surviving a reconnectPersisted, and restored before the join line is built

Not yet covered Dispensers equipping armour onto a vanished player — the predicate that picks the target is an inlined lambda, effectively impossible to target safely, and the payoff is a dispenser occasionally putting a helmet on somebody invisible. Not worth a fragile injection.

Raw chunk loading is half-done by default, and it is worth knowing why. DistanceManager#addPlayer raises the spawn counter and the chunk-loading ticket in one call. Only the spawn half is skipped out of the box — that is the part anyone can actually observe, and skipping the other half would leave you staring into unloaded void unable to do the job vanish exists for. Set vanishLoadsChunks to false to skip both, and read the trade in the Config section before you do: it lands on the next chunk boundary you cross, not the instant you toggle vanish.

Map integrations — BlueMap, Dynmap, Squaremap — cannot be done from here at all: each needs that mod present as a compile dependency and an API call into it. They are a separate compatibility layer, not a mixin.

Freeze

Pins a player where they stand. Every tick, a frozen player who has drifted more than a few centimetres is snapped back, and every ten seconds they get a reminder not to log out. The small dead zone is deliberate — snapping on any movement fights the client's own prediction and produces rubber-banding; a little slack reads cleanly as "you cannot walk".

Return

Every staff teleport records where you came from, so /back puts you exactly where you were, in the right dimension.

Staff mode

The toolset

Staff mode replaces your hotbar with nine fixed tools. Slots 1, 2, 8 and 9 act when you right-click the air; slots 3 through 6 act on whichever player you right-click; slot 7 acts on whichever block you right-click.

1Staff PanelNether Star
2Online PlayersCompass
3InspectBook
4FreezePacked Ice
5InventoryChest
6PunishNetherite Axe
7Inspect BlockStick
8VanishEye of Ender
9Jump ToEnder Pearl

Jump To teleports you to a random player who is not staff. Staff are excluded on purpose: the point of the tool is landing somewhere something might be happening, and staff cluster at spawn.

Inspect Block is the CoreProtect-style inspector: left-click or right-click any block to open the grief log centred on it — who broke it, who placed it, who opened it and when. Left-click is the one to reach for on a chest, since right-clicking one would open it.

On a container it opens the item history instead — who took what out and who put what in, each row showing the actual stack that moved. That is almost always the real question, because the chest is usually still standing and the diamonds are not. A button switches across to the block view.

The tools cannot leave staff mode. They refuse to be dropped, they are stripped from your inventory before death drops run, any that reaches the ground is destroyed rather than collected, and a two-second sweep removes one from anybody not on duty and tells staff. A loose staff tool is a permanently circulating item, so every route out is closed rather than just the obvious one.

Per player

A player's file

One screen holding everything StaffCore knows about somebody and everything you can do to them. It opens from the player list, the Inspect tool, or /lookup <player>.

The file is keyed on a profile, not a live player, so it works for someone who has already logged off. Actions that genuinely need them present — teleport, freeze, live inventory — are drawn greyed with the reason stated, rather than silently missing.

The header alone tells you: online or offline, health, dimension, coordinates, punishment count, note count, and an explicit warning line for any ban or mute currently in force. Two status cards below spell out the active ban and mute in full — type, reason, who issued it, when, and what is left to run.

What you can do from here

Punish

Straight into the ladder, with their record on screen beside it.

History

Every punishment they have ever taken.

Notes

Read and write staff notes.

Inventory

A live view of what they are carrying.

Snapshots

Frozen copies of that inventory.

Security check

Run the automated checks on them.

Freeze

Lock or release them.

Teleport / Bring

Go to them, or pull them to you.

Lift ban / mute

Revoke what is currently in force, behind a confirm.

Enforcement

Punishments

You pick what they did. The ladder picks what happens.

This is the change that matters most to a staff team. Choosing a punishment from scratch every time produces drift — the new moderator who bans for swearing, the veteran who lets their friends off with a warning — and neither is visible until somebody complains. Here the server decides once, in config, that a second instance of chat abuse is a one-day mute, and every staff member applies that identically.

The rung comes from how many times this player has already been done for that specific offence, so escalation is per-offence rather than one global counter: three warnings for spam do not make their first griefing report a permanent ban. The target's prior record sits on the same screen as the decision.

OffenceIconLadder — 1st / 2nd / 3rd / 4th
Hacking / CheatingNetherite Sword Permanent ban — there is no version of "a bit of cheating" worth a second chance.
GriefingTNT Ban 7d → Ban 30d → Ban permanent
Chat Abuse / ToxicityNote Block Mute 1h → Mute 1d → Mute 7d → Ban 7d
Disrespecting StaffShield Warn → Mute 1h → Mute 1d
AdvertisingOak Sign Mute 1d → Ban 7d → Ban permanent
SpammingPaper Warn → Mute 30m → Mute 6h
Exploiting a BugRedstone Torch Ban 7d → Ban permanent
ScammingGold Ingot Ban 14d → Ban permanent
Inappropriate Name / SkinPlayer Head Kick → Ban permanent
Ban EvasionBarrier Permanent ban

The flow

PickOffence
ReviewConfirm

Two clicks for the common case. The icon tells you the rung before you press it — how many priors they have, what applies now, and what it escalates to next time.

"Something else" drops into the old manual ladder — pick rung, then duration, then reason — for anything the offences do not cover. It is deliberately not the default route.

ManualRung
PickDuration
PickReason
ReviewConfirm

Offences, their ladders, their icons and their descriptions all come from config/staffcore.json. Add an offence there and a new button appears; change a duration and every staff member's next punishment follows it.

Nothing is written anywhere until you press Confirm. Abandoning the flow at any point leaves no trace.

What happens on confirm

One code path handles every punishment, whether it came from the panel or a command: record it, enforce it, announce it, mirror it to Discord. Staff hear a thunderclap when a ban lands — deliberately the loudest thing in the mod. By default bans and mutes are announced to the whole server; set publicPunishmentBroadcast to false to keep them to staff.

Lifting and history

/unban and /unmute, or the Lift buttons on a player's file, deactivate what is in force and record who lifted it. History keeps everything — spent, expired and lifted entries stay in the list, dimmed, with the name of whoever revoked them.

Destructive Wipe history deletes every record for a player, including active bans and mutes — which lifts them. It needs staff.history.clear and a confirm, and it cannot be undone.

Records

Notes

The informal half of a player's record — the things that are not punishments but that the next person on shift needs to know. Every note carries its author and a timestamp.

Notes cannot be edited, only deleted. That is deliberate: the value of a note six months later is that it says what somebody actually thought at the time. Deleting one needs staff.notes.remove and goes through a confirm showing the note you are about to lose.

Writing a note needs free text, so it is a command: /notes <player> add <text>. The notes screen tells you so rather than leaving you hunting.

Player-facing

Reports

Open to everyone /report <player> <reason> is the one command in the mod that is not staff-only, and it stays that way even on a server with no permissions plugin installed.

Filing a report pings every subscribed staff member with a bell, mirrors to Discord, and drops into a queue. A per-reporter cooldown (60 seconds by default) stops spam, and a second report against someone already in the queue is merged — the reporter still gets thanked, because their report is real information, but staff attention is not split across duplicates.

The queue

Open reports first, oldest at the top, then claimed ones. Claiming is the whole point of the screen: an unclaimed queue is two staff teleporting to the same player while a third report ages out. One click claims, the claimant's name goes on the icon, and clicking your own claimed report resolves it behind a confirm.

  • Left-click — claim it, or resolve it if it is already yours
  • Right-click — open the reported player's file
  • Shift-click — teleport straight to them

The other side

Appeals

A banned player with nowhere to argue has exactly one move left, and it is an alt.

The ban screen now carries your Discord invite (set discordInvite in config; leave it empty to hide the line). Players who are muted rather than banned can also file in game with /appeal <text> — reachable while muted on purpose, because a mute stops you talking in chat, it is not meant to stop you contesting the mute.

Appeals land in a queue beside reports. Accepting one lifts the punishment it was made against — an appeal that is "accepted" but leaves the ban in place is worse than no appeal system at all. Rejecting closes it with your name attached, so a second appeal arrives carrying the history of the first.

  • Left-click — accept, and lift their ban and mute
  • Right-click — reject; the punishment stands
  • Shift-click — open the player's file first

Either way the player is told the outcome the moment they are next online.

Who is who

Identity & logs

Every connection records the account, the name and the address it came from. From that fall three things staff repeatedly need and previously had to guess at.

Linked accounts

Other accounts that look like they might be the same person, banned ones pulled to the top and marked. On join, a link to a banned account raises a staff alert automatically.

Two kinds of link are found. An exact address match is the strong one. A range match — the same /24, or /64 on IPv6 — catches what exact matching cannot see at all: a home address that moved within its provider's block between one session and the next, and so stopped matching itself. That is not exotic. It is what happens when a router reboots, and it was most of what "a VPN defeats it" really meant.

Each link carries a confidence out of a hundred and the reasoning that produced it. Repeated use of the shared address counts for; a single stray connection counts against. The signal that does most of the work is whether the two accounts have ever been online at the same moment: one person switching between accounts never can be, and two siblings routinely are. Neither is proof, but they point in opposite directions, and a screen that only said "same address" could not tell you which one you were looking at.

Read as a lead A shared address is not proof. Siblings, flatmates, schools and mobile networks all put different people behind one, and under CGNAT a single address can be every customer of a provider.

Nothing here bans anybody automatically, and there is no setting that makes it. There used to be one, and it was removed rather than defaulted off: an automatic ban on this evidence is wrong some fraction of the time, the people it is wrong about are strangers who did nothing, and it would be wrong while nobody was watching. Everything else in this handbook says alt detection is a lead and not a verdict; acting on it automatically was the one place that said otherwise.

A range match means "somewhere in this provider's block", which on a mobile network can be a city — which is why it scores far lower than an exact match. Links below altMinConfidence are not alerted at all, since an alert staff learn to dismiss teaches them to dismiss the ones that matter.

Session replay

Watching where somebody went, from inside their own path. Off by default, and the most invasive thing in this mod.

/staff replay <player> [timespan] reconstructs a window from the position history and puts you in spectator at the start of it, moving along the route the player took. pause, resume, speed 0.2516, restart and exit do what they say. A sidebar carries the time, the coordinates, the speed and how far through you are.

It only shows what was recorded. Switching positionTracking on does not fill in the past — a replay of last week on a server that started recording yesterday is empty, and says so rather than showing an incomplete path as though it were the whole one.

The world is put back too A replay of the world as it is now is quietly the wrong world: every hole the player made is already open when they arrive at it, and a rollback somebody ran since has erased the lot. So the blocks that player broke or placed in the window are painted back to how they were and released one at a time as the clock reaches each one — a wall stands until they break it, a tower is absent until they build it. Only the positions that changed are touched; everything else is the real world, because everything else did not change. Nothing is written: these are block packets sent to your client alone.

After a change has happened you see the world as the server actually has it. So a block that was broken and later restored by a rollback is drawn coming down and then standing again — which is the truth about both events rather than a guess about one.

Gaps are skipped, and announced Nothing is recorded while a player stands still, so a session with an hour of AFK in it has an hour between two samples. Played at real speed that is an hour of watching nothing; cut silently it is a teleport that reads as suspicious movement. So a gap is held for a moment, then jumped, with a chat line naming how long it was. What is being hidden is time, and hiding time without saying so is how a replay becomes misleading evidence.

Not an anti-cheat Two samples a second says where somebody went. It says nothing reliable about how they moved between them — the smooth motion you watch is this mod interpolating between two readings half a second apart. Anything you think you see about movement mechanics is a property of the interpolation, not of the player. Raising positionSampleHz does not change that; it multiplies the storage cost and buys detail the playback throws away again.

staff.replay is its own permission node and is in the admin group by default, not moderator. Everything else here shows what somebody did; this shows where they were, continuously, whether or not they did anything. A server that wants its moderators to have it can grant it — the default should not be the permissive one for this.

The config updates itself New settings are added to config/staffcore.json on the first boot after an upgrade, with their defaults, and the server log names each one. Anything you have set is left exactly as it is — there is never a reason to delete the file to pick up new options, and deleting it would reset every choice you have made.

Settings this build no longer recognises are removed, and that is logged as a warning naming them. If you are downgrading, keep a copy first.

What is stored about a person, and for how long

Two categories here are personal data rather than a record of conduct. They are worth being exact about.

WhatWhereKept for
Connection address, and its /24 range connections connectionRetentionDays, default 90 days
Address on each join and leave session_log connectionRetentionDays, default 90 days
Where a player was, twice a second while they were moving — position, facing and world, to about three centimetres position_run, position_log positionRetentionDays, default 7 days. Off unless you switch it on.
Username and UUID throughout For as long as the record they belong to

Everything else StaffCore records — punishments, notes, blocks broken, items moved, commands run — is conduct on the server. That belongs to the server in a way somebody's home address does not, and it follows the retention setting for its own feature.

Position history is off by default positionTracking is false on a new install, and that is a judgement rather than caution about the code. Every other row in this database exists because a player did something. A position row exists because the player existed. Turning it on is a decision to keep a record of where the people on your server go, and it should be a decision somebody makes rather than something that starts happening because they installed a moderation tool.

It is also, by a wide margin, the largest thing this mod can write — see Session replay for what it buys. /staff status reports how much of it is on disk, whether tracking is currently on or off.

Measured cost, at the default 2 Hz: about 160 KB per player-hour of continuous movement, and about 80 KB for an hour in which the player is actually moving half the time — nothing is written while somebody stands still. Twenty players online for four hours a day, moving half of it, is roughly 45 MB a week, which the seven-day retention window then keeps flat. Raising positionSampleHz multiplies all of these by the same factor.

Nothing about it is nudged towards being on. There is no alert telling you the replay would have helped, and no prompt when a case is opened. If you do not need it, leaving it alone is the supported configuration.

Exports withhold both /staff export redacts address columns and writes no position rows at all — the file and its header line are still there, so a withheld table can be told from a missing one. /staff export personal confirm includes both, and says how many samples that is before it does. addresses still works as the old name for the same flag.

Addresses are hashed by default hashConnectionAddresses is on, so what lands on disk is a salted SHA-256 digest rather than an address. This costs nothing, because nothing here needs to read an address — only to know whether two accounts used the same one, which is an equality test and survives hashing exactly. Both matching passes still work: exact matching compares the address hash, range matching compares a separately hashed prefix.

The salt is generated once per server and kept in the database, not the config file — it is not a setting, and changing it would silently unlink every account from every other. Salting matters because IPv4 is 32 bits: an unsalted digest of every possible address can be built in seconds, which would make it an encoding of an address rather than a hash of one. It is not magic either — somebody holding both the database and the salt can still test a guess.

Turning it on converts anything already stored, so the table cannot end up half in each form. That matters more than it sounds: two accounts sharing an address would otherwise stop matching because one row predates the change, and alt detection would get quietly worse with nothing to say why.

Export redacts addresses /staff export writes one CSV per table, and address columns come out as [redacted]. An export is nearly always wanted for the punishment history or the grief log; the addresses only came along because they share a database. /staff export addresses confirm includes them, and says first whether they are hashes or readable.

Session log

Every join and leave with a timestamp and address — the fastest way to answer "were they even online when this happened?"

Death log

Cause, killer, world and coordinates for every death, with a click to teleport to the spot. An argument about lost items becomes something you can check.

Ender chest

Its own screen, because the ender chest is where anything worth hiding ends up: it follows the player, survives death, and is invisible to a normal inventory check. Read-only unless you hold security.invsee.edit, and opening it in edit mode is announced to staff chat just like the main inventory.

Evidence

Inventories

/staff invsee opens a genuinely live window: the slots are the player's real inventory, not a copy, so what you see is what they are holding at that instant. Main storage, hotbar, armour and offhand are all laid out in the same geometry every time.

Offline players work too. If the target is not on the server, their inventory is decoded straight out of their save file and shown in the same layout. Those views are always read-only — writing NBT back under somebody who might reconnect mid-edit is a corruption risk no amount of care makes safe — and the header says so rather than leaving you to wonder why the slots will not move.

Edit mode Read-only unless you hold security.invsee.edit. The difference is visible before you touch anything — the title says so, the header item says so, and the frame turns red. Opening in edit mode is announced to staff chat.

Reaching into another player's inventory is the most abusable thing in this mod. The fix for that is daylight, not a permission node on its own — and opening in edit mode now also takes a snapshot, so what was there is a matter of record rather than of recollection. That protects the staff member as much as the player: "it was already missing" is a claim that needs evidence pointing either way.

The footer carries the rest of the file without leaving the screen: the ender chest, snapshots, freeze, a security check, the contraband vault and the punish ladder. The ender chest link is new in both directions — the ender chest screen has always linked back here, but there was no way in, and the one button on this screen that looked like an ender chest was the snapshot button.

Snapshots

Frozen copies of an inventory, capped per player and configurable. They settle the arguments a live view cannot: what somebody had ten minutes ago, before they emptied a shulker into lava.

Several are taken automatically: the instant before a player dies, when they log out, before a staff member opens their inventory to edit, and before a rollback debits them. Automatic captures are ranked below deliberate ones, so they are discarded first when the cap bites — switching them on can never cost you a snapshot you would otherwise have kept.

The death one is the piece of evidence that cannot be reconstructed afterwards — and you can force one from the inventory view or a security check. Restoring a snapshot onto a player takes a snapshot of what they currently have first, so an accidental restore is itself undoable, and the player is told it happened.

A snapshot is the whole inventory — storage, hotbar, armour, offhand and the equipment slots past them. Somebody who died in full enchanted netherite has all of it, not just what was in the nine slots along the bottom, and restoring puts every piece back in the slot it came out of.

Dying takes a snapshot, always

It fires the instant before the death lands — before a single item has hit the ground — so it holds the full kit: armour worn, offhand, hotbar, everything in storage. It is not behind a config switch and it cannot be turned off, because it is the one capture that can never be taken late. It is also ranked as evidence rather than routine, so the per-player cap discards ordinary captures before it ever discards a death.

That makes reviving somebody who lost a kit to a bug, a bad chunk or a lava accident a two-click job: open their file, Snapshots, pick the one labelled On death, restore.

Restoring a death snapshot does not duplicate the drops

Restoring what somebody had when they died, while what they dropped is still lying on the ground, would hand them a second set. So the restore sweeps the death site first: anything from the snapshot still lying there is picked up and counted as already returned, and only what is genuinely gone gets recreated.

The recreated items are rebuilt from the stored stack, so enchantments, trims, custom names, durability and everything else come back exactly as they were. Someone who died in lava and lost eight of their eleven items gets three real originals off the floor and eight faithful copies — and no twenty-two-item windfall.

Restoring a death: destroyed, dropped, or in somebody's pocket

Restoring hands the player everything the snapshot held, which is right whatever became of the originals — but it means every copy still in the world has to be accounted for exactly once, or the items exist twice.

What happened to the itemsWhat the restore does
Destroyed — burned in lava, despawned, gone Replacements are handed over. Nothing is duplicated, because the originals do not exist and nobody is charged for them.
Still lying there Swept off the ground first. The chunks are loaded to do it, so this works at any distance — items in an unloaded chunk never despawn, so without that they would sit there forever as a second copy.
Picked up by somebody else Taken back off them, out of their inventory or ender chest, and booked against their next login for whatever they no longer have.
Picked up by the owner Left alone. Collecting your own death drops is not theft, and the restore clears their inventory first anyway, so nothing survives twice.

Nobody is ever charged more than went missing. Somebody who pocketed a full stack at a death where five were unaccounted for owes five. That cap is what makes it safe to search widely: deathDropSweepRadius is deliberately larger than the pile, because water pushes drops about and people wander while collecting, and a wider circle can find more of what belongs to the death without being able to take anything that does not.

Working the slots

In edit mode the window behaves like any other container. Click to pick up and place, and shift-click to move a stack across — from their inventory into yours, or from yours into theirs. Armour and offhand are excluded from shift-click targets, so a stack never lands somewhere it would silently equip.

Confiscation is a deliberate act, not a shortcut. Pick the stack up and drop it on the bundle in the footer, which vaults it and logs it. Only stacks picked up from the target's own slots can be vaulted this way — dropping something of your own on the bundle is refused, because confiscating an item off a player who never had it is not a thing that should be one misclick away.

Snapshots are kept in the database and survive a restart, so an incident that spans one is still answerable. They are pruned by the per-player cap and by retention, with deliberate captures outliving automatic ones.

Automated checks

Security checks

Three checks, run against one player, answering "is this person's stuff possible?" Every finding is a flag, never an action.

FindingSeverityMeans
ILLEGAL_ITEMImpossibleBedrock, a spawner, a barrier, a portal frame — not obtainable in survival.
OPERATOR_ITEMImpossibleCommand, structure or jigsaw block, debug stick. A serious finding.
OVERSTACKImpossibleA stack larger than the item's own limit.
ILLEGAL_ENCHANTImpossibleAn enchantment above its vanilla maximum level.
STAFF_TOOLImpossibleA staff-mode tool held outside staff mode. Removed automatically.
MININGSuspiciousScored x-ray confidence, with the evidence attached.

Contraband watch

Every two seconds, online players are checked for items that cannot exist in survival — bedrock, spawners, barriers, command blocks, over-stacked stacks — and staff are alerted the moment one appears, with coordinates. Each player-and-item pair alerts once, so somebody carrying contraband around for an hour does not generate an alert every two seconds.

Waiting for somebody to run a manual check means the first person to notice a duplicated spawner is whoever it gets used on. The scan also recurses into shulkers and bundles and covers the ender chest, because hiding contraband one layer deeper is the obvious response to learning that inventories get scanned.

World containers are checked too. The watch only ever saw what somebody was carrying, so the way to keep a banned item was simply not to carry it — leave it in a chest and nothing noticed until it came back out. Chests, barrels and shulkers are now checked as they are opened and as they are closed, which costs nothing extra: the grief log already copies the contents of every container anyone opens, and the check rides along on a copy it has taken anyway. A staff tool found sitting in one goes to the vault, exactly as it would if it were found on a player.

X-ray

The x-ray check reads the grief log rather than watching players, and it will not fire at all until the dig reaches a volume of at least 512 blocks of rock.

A single ratio is a bad detector, so it is not one. Four independent signals are scored into a confidence percentage with the evidence attached:

  • Ore fraction — the blunt signal, kept but no longer decisive.
  • Directness — filler blocks dug between one vein and the next. This is the signal that actually separates a lucky player from a guided one: luck changes what you find, not how far you tunnel to reach it.
  • Beelines — veins reached by a near-straight run of breaks.
  • Ancient debris — never generates exposed, so every one found had to be tunnelled to blind.

Ore a player placed themselves is excluded from their own score. Placing a block and breaking it again is an ordinary thing to do — moving a vein, decorating, testing silk touch — and every one of those breaks used to read as a find. It also meant anybody could inflate their own ore fraction to whatever they liked with a stack of iron ore and a wall, which is a poor property for a detector to have.

A background sweep scores online players every five minutes and alerts above 55%, re-alerting only when the score climbs. That matters because x-ray has no victim: nobody files a report saying somebody else found diamonds too easily. Staff who are clocked on are skipped; staff mining on their own time are not, because staff cheat too.

Retuned, and this time measured These numbers were once moved on the grounds that the detector was too quiet. That is an argument about visibility, and any bar can be lowered until a feature speaks — so it was replaced with a measurement. Scoring two hundred generated honest sessions and forty guided ones showed the old settings were not quiet at all: the worst honest pattern scored 66 against an alert line of 55, so ordinary strip mining and ordinary diamond hunting at y=-54 both tripped it. The line is now 65 with a floor of 200, which sit in the gap between the two populations: worst honest 50, weakest cheat 81. The run is recorded in docs/decisions.md.

xrayNoticeConfidence (55) is the other half. A score above it but below the alert line produces a quiet line to staff that says plainly it is not a finding. Without it there is no way to tell "nobody is cheating" from "this has never once run", and for a long time it was the second.

When it never says anything

Silence is the detector's normal state, which makes it impossible to tell a quiet server from a broken feature. /staff xray <player> [hours] always answers — confidence, sample size, the alert threshold, and every signal that contributed — and takes a plain name, so it works on somebody who has already logged off.

It now ends with "Why you have not been alerted" and names the one reason that applies: not enough mining yet, a score under the line, or the sweep being switched off. It also reminds you that ore a player placed themselves is excluded — which is the one that catches people out, because seeding a wall with ore and mining it back is the natural way to try to test this and proves nothing at all.

One real bug lived here and is now fixed: the sweep used to skip everyone holding staff.gui, and with no permissions plugin installed that node falls back to operator level. The person testing the detector was therefore the one player it was guaranteed never to look at.

The screen has no punish shortcut on a flag, on purpose. "The scanner said so" is not a reason. The route to acting is back through the player's file, where their record is visible alongside. /itemscanner sweeps every online player at once and reports who is worth a look.

Evidence

Contraband & the vault

Taking somebody's items is the most contestable thing staff do. It should be the easiest thing to undo.

Confiscation used to mean deletion. That makes every mistake permanent and unprovable at once: the item is gone, the player insists it was legitimate, and there is nothing left to look at. Everything taken now goes to the vault instead — a real stack, held, with who it came off, who took it, when and why.

Each row offers exactly two endings. Give it back puts the stack straight into the owner's inventory. Destroy is deliberate, confirmed, and keeps the record — a destroyed item still leaves a row saying it existed and who ended it.

Giving something back no longer requires the owner to be online. It cannot be pushed into their save file — writing player data underneath somebody who might reconnect mid-write is a corruption risk no amount of care makes safe — so the decision is booked instead of the delivery. The row moves to awaiting return, and the item is handed over the moment they next log in. Until then the queued return can be cancelled and the item put back in the vault.

The screen pages straight from the database rather than reading a capped block, so there is no ceiling on what it can show, and the filter in the footer walks through held, awaiting return, returned and destroyed. That last pair matters: the table has always kept a row after an item was handed back or ended, and this is what finally makes that history something you can look at.

Editing the rules by pointing at things

The contraband lists were always config, which meant editing them required knowing that a spawner is minecraft:spawner and that the trial variant is a different id again. The rules screen makes the item the rule: click one in your own inventory to ban it, click it in the grid to allow it again.

Nothing is ever consumed. The click reads the item's type and leaves the stack exactly where it was — a rules screen that ate a shulker box to learn what a shulker box is would be a poor trade.

Two lists, two tabs. Impossible in survival is the ordinary case, usually a leak from a staff member. Operator tooling is reported separately because a command block on a player is a different kind of problem. Each list pages, so a list longer than one screenful is edited here like any other — it used to refuse past forty-five entries and send you to the JSON file, because saving would have quietly dropped whatever did not fit.

Four ways to take something

Confiscation used to be one button that took whatever the scan disliked, and nothing else. That is the right tool when the scan is right and the wrong one every other time — so there are now four, and all of them end in the vault rather than in deletion.

GestureTakes
Drop a stack on the bundleExactly what is on your cursor. Pick an item out of their inventory and put it in the vault, which is the gesture you were already reaching for.
Shift-click a slotThat one stack, without picking anything up. Shift-clicking in these screens used to do nothing at all.
Confiscate, left-clickEverything the security scan flags, across inventory and ender chest.
Confiscate, right-clickEverything they are carrying, full stop — for when you already know what is going on and just need it held.

The bundle icon appears on both the inventory and the ender chest screens, and with an empty cursor it opens the vault — so it means the same thing whichever direction you approach it from: this is where confiscated items live.

The guard rails Every route takes a snapshot first, records the item against the player it came from, and announces itself in staff chat. The two bulk routes add a confirm screen naming what is about to go. Nothing is ever deleted — it is all one click from being handed back, which is the entire reason the vault exists.

The drop-on-the-bundle route only accepts a stack picked up out of one of their slots. Anything else — something from your own bags, a drag, a hotbar swap, a double-click gather — is refused with an explanation rather than recorded under their name. A vault row is a statement about what was taken off somebody, and one naming the wrong person is worse than one that never existed, so an unrecognised gesture costs you a click instead.

Repair

Grief log & rollback

Every block broken and placed is logged with who, what, where, which world and when — and so is every container opened, because most "griefing" reports are actually theft and a log that only records broken blocks cannot answer who emptied the chest.

Reading the log

The screen has four controls that decide what you are looking at, and they sit together along the bottom rather than scattered around it.

ControlWhat it does
Radius8 to 96 blocks. This used to be fixed at 24 — too small for a razed build and too wide for one stolen chest — and the rollback button used the same fixed number, so neither could be aimed.
Time windowDoubles on left-click, halves on right-click.
ShowingCycles: everything except mining · everything · broken only · placed only · containers only. There used to be only a mining-noise toggle, so "just show me what was broken" — the commonest thing anyone wants — could not be asked for.
Re-centreMoves the search to where you are standing, keeping the radius, window and filters you have set. It used to reopen the screen and reset all three.

Repeats are collapsed. Tearing down a wall writes one row per block, so a page could be twenty-eight lines all saying the same thing while the row you came for sat on page four. Consecutive identical actions by one player, in one place, within a couple of minutes now show as a single row with a count — "Steve broke 28× oak planks" — and the rest of the page is free for everything else.

Right-clicking a row filters to that player; the header says who you are scoped to and clicking it clears the filter. Shift-clicking a row previews a rollback of that player at that spot.

The inspector answers about one block

Clicking a block with the inspector now shows the history of that block: every break, place, open, take and put at that exact coordinate, newest first, with who did it and when. Block changes and item movements are merged into one timeline, because the story of a stolen chest is both — opened, emptied, then broken.

It used to open an area log centred on whatever you clicked. That answers "what happened around here", which is a different and much noisier question; on a base wall it buries the block you pointed at under a hundred neighbouring rows. Widening out is still one click away.

Rows that have already been rolled back are shown and marked rather than hidden, since "this was already put back" is usually what somebody is trying to find out.

/staff inspect holds the mode on, so any block you click reports its history whatever is in your hand and without the block breaking or the chest opening underneath you. The stick still works and is faster for one question; an investigation is usually a stretch of time — the chest, then the wall behind it, then the floor — and that is what a mode is for. It is deliberately not saved across a restart, so it can never quietly eat your first pickaxe swing.

Searching the log

The area screen answers "what happened near me, recently", which is the right question most of the time and the wrong one the moment you already know what you are looking for. /staff search takes key:value filters and queries both logs at once:

/staff search player:Steve action:break time:2d
/staff search block:minecraft:spawner radius:200
/staff search Steve container:yes

player: action: block: time: radius: world: and container: are the filters; a bare word is read as a player name, since that is what people type first. Leaving out radius: searches the whole world rather than guessing one. An unrecognised filter is reported rather than ignored — silently dropping half a query and returning confident results would be worse than refusing.

/staff purge <age> [player] deletes history early. Retention already runs daily and unattended; this is for the other reason to delete, which is that somebody asked. It previews what it would remove and does nothing until you add confirm.

Preview before you write

Rollback is the only irreversible thing in the mod — it overwrites whatever is standing there now. The GUI has always shown a preview first; the commands did not, which meant the fastest way to run one was also the only way to run one blind. /staff preview mirrors the rollback tree exactly and writes nothing, reporting the block changes, container stacks and any entries naming blocks that no longer exist, then printing the command that would apply it.

The Grief Log screen shows activity within 24 blocks of wherever you are standing, newest first. It is paged on the server — twenty-eight rows and a count per query, read off the main thread and handed back on it — so opening it on a busy world cannot become the cause of the lag you opened it to investigate.

Mass-grief detection alerts staff when somebody breaks an implausible number of blocks in a short window (120 in 20 seconds by default). The threshold sits well above normal mining on purpose: a detector that fires on every strip-miner is one staff learn to ignore.

  • Left-click an entry — teleport to that exact spot
  • Shift-click — roll back everything that player did in this radius
  • Right-click — filter the list to just that player
  • The TNT — roll back the whole area, whoever did it
  • The clock — double or halve the time window
  • The compass — re-centre on your current position

How rollback behaves

Works by player when you know who did it, and by area when several accounts hit one build and naming them one at a time is hopeless. Either way it previews first: you see how many changes will be undone before anything is written. Replay runs newest-first and inverts each action — a logged break puts the block back, a logged place clears it. Order matters: somebody who broke a wall and then built on the rubble has to be undone in the opposite order to how they did it.

Rolled-back entries are marked rather than deleted, so running the same rollback twice is a no-op and the audit trail survives.

Containers roll back too. Contents are snapshotted when a player opens a chest and diffed when they close it, so the log records exactly which stacks left and which arrived. A rollback puts them back — into the slot they came from where it is free, anywhere free otherwise. Putting a looted chest back and leaving it empty looks like the problem was fixed when it was not.

Snapshot-and-diff rather than hooking every click: it misses nothing, needs no special cases for shift-clicking or drag-distribution, and costs one array copy per chest opened instead of work on every slot interaction.

A chest that was broken rather than opened is restored full. That one used to be a silent deletion, and it is worth being precise about why. Breaking a chest scatters its contents as items; breaking a shulker hands you one item with everything inside it. The open/close diff never saw any of that, because nobody opened anything — so rollback put the block back empty, debited the griefer one chest, and destroyed the contents. It looked like a repair and it was a deletion. The contents are now recorded at the moment of the break, restored to the slots they were in, and charged to whoever broke it. A shulker is the one exception to the charge: its contents ride inside the item, so debiting the box already takes them and counting them again would bill the same items twice.

Undoing a rollback

Rollback is the most destructive thing here and used to be the only destructive thing with no way back. A ban is revoked, a confiscation handed back, a vaulted item is one click from its owner — but a rollback aimed at the wrong radius overwrote whatever was standing there and that was simply the end of it. The predictable result is staff who hesitate to repair grief at all, which costs a server more than the occasional bad call does.

Every rollback now writes a restore point before it touches anything, recording the blocks it is about to overwrite and the contents of any container it is about to replace.

Doing it from the panel

  1. Open the Grief Log and click Rollback history in the top-right corner. The badge on it counts how many rollbacks can still be taken back.
  2. Each row is one rollback: who ran it, how many changes, the centre and radius, and how long ago. Rows already undone stay on the list, greyed, showing who undid them — because "has somebody already fixed this?" is the second question people ask.
  3. Left-click a row to teleport to where it happened, so you can look before deciding.
  4. Shift-click to undo it. A confirm screen names what is coming back; nothing is written until you accept.

Doing it from chat

/staff rollback undo          # reverse the most recent rollback
/staff rollback undo list     # see what is available, with ids
/staff rollback undo 14       # reverse a specific one

The bare form is the one that matters in practice. When somebody watches a repair land on the wrong build they want it back in the next few seconds, not after looking up a number.

Worth understanding Undo puts back exactly what was recorded. It does not attempt to merge with anything built there since — if somebody has rebuilt in the meantime, undoing overwrites their work in turn. That is deliberate: a restore point that silently declined to restore, or tried to guess which changes to keep, would be far worse than one that does precisely what it says and tells you it did.

Blocks whose type no longer exists — a mod removed since — are skipped and counted separately, exactly as they are in a rollback.

Restore points are kept for rollbackPointRetentionDays, seven by default; set it to 0 to record nothing and disable undo entirely. This is a safety net for a mistake noticed shortly after it is made, not an archive — a week is long enough for somebody to log in and find their build missing. Undoing also un-retires the log rows the rollback consumed, so the grief log stops claiming a repair that has since been reversed and the same rollback can be run again if it turns out it was right after all.

Rollback no longer duplicates items. Restoring a block the griefer broke while they keep the drop is a duplication exploit — every rollback quietly printed items. Restored blocks now have their original drops reclaimed along four routes, in order of how certain each one is: ground items in the area, then the offender's own inventory, then chests they filled during the same window, and finally a debt booked against their next login if they have already logged off.

The last two matter more than they sound. Carrying the haul away and stashing it used to be a complete defence, and so did simply logging out — between them that was most of what made rollback a duplication exploit in practice. Following the loot needs no new tracking: the container log already records every stack put into every container, by whom and when, so it knows the difference between a chest with cobblestone in it and a chest this person put cobblestone into. Somebody else's chest is never touched.

What is still out of reach is loot handed to another player. And a container that has no room cannot take its stack back — that row is left un-retired so clearing space and running it again works, and the count of what would not fit is reported rather than left for you to notice.

Confirm required Anything built on top of the reverted blocks is overwritten. Entries referring to blocks that no longer exist — a mod removed since — are skipped and counted separately in the preview. The preview also lists what is going back, not just how many changes: "412 changes" tells you the size of the operation and nothing about whether it is the right one, where "38 oak planks, 2 chests, 64 diamonds" tells you at a glance whether you have the radius you meant. And if it turns out you did not, /staff rollback undo puts it back.

The preview is also drawn in the world. While the confirm screen is up, the blocks a rollback would restore appear around you as ghosts — visible to you and nobody else, with nothing written to the server. A list tells you what is coming back; standing in the build and watching the missing wall reappear tells you where, which is the question that actually decides whether the radius is right. It clears when you confirm, cancel, close the window, or after forty-five seconds, and since the server's own copy never changed, the worst a stuck preview can do is show you a ghost that a chunk reload fixes.

Where the loot is chased

Putting items back is only half a repair. If the person who took them still has a copy, the repair has printed items — and the better the restore gets, the more attractive stealing becomes, because the victim is made whole and the thief keeps the haul. Both the block rollback and the chest theft undo run the same five checks, in this order:

  1. The ground, at the scene and again around the offender, who may well have walked off before dropping it.
  2. Their inventory, armour and offhand included.
  3. Their ender chest — for a long time the safest place on the server to put stolen goods, because nothing looked there.
  4. Chests they filled during the same window, read out of the container log so somebody else's chest is never touched. The container this operation has just refilled is excluded, or the debit would undo the restore it is supposed to pay for.
  5. Their next login. Whatever is still missing is booked as a debt rather than written off, so spending or destroying the loot delays paying for it instead of avoiding it. This applies whether they are online or not.

Taking items out of somebody's inventory on the strength of a log query takes a snapshot first, so there is a before-picture whether or not the query was right.

Every chest, and the ones that drive away

Double chests count as one container from either half. A log row lands on whichever block was clicked, so anything reading a single coordinate would see half the history and present it as all of it. The inspector, the container log and the theft undo all read both halves.

That includes copper chests, in every weather state and waxed or not. Copper halves pair by tag rather than by being the same block — an oxidised half and a fresh one are one chest as far as the game is concerned — so the pairing is decided by asking the block, not by comparing it.

Chest boats and container minecarts are watched too. They hold items exactly as a chest does, and until recently none of the container hooks saw them at all, because every one of them hung off block interaction. That made a chest minecart the best place on the server to keep anything you did not want logged. Takes and puts are now recorded the same way.

One limit follows from what they are: they move. The log records where the vehicle was when it was opened, which makes that position a description of the scene rather than an address. Thefts from a chest boat are attributable and visible; a positional rollback of one is not meaningful and is not offered.

When a creeper does it

Only player block-breaking used to reach the log, which left the commonest damage on most servers invisible. A creeper took out a wall and a chest, and there was nothing to look at and nothing to put back — the area read as though nothing had happened, because as far as the log was concerned nobody had done anything.

Explosions are now recorded before the blocks go, contents included, so an exploded chest comes back with what was in it. Rollback treats the damage like any other break.

Attribution follows responsibility. Where a player is genuinely behind it — lighting TNT is griefing done with a tool — it goes on their record exactly as breaking the blocks by hand would. Otherwise the creature is named with a # prefix, as #creeper or #ghast. Minecraft names cannot contain that character, so a mob entry can never be confused for an account, and nothing tries to reach into a creeper's inventory or bill it on its next login.

A blast is capped at explosionLogCap blocks (512 by default). A creeper takes out a few dozen; a TNT cannon can level thousands, and writing every one buries the incident the log exists to make readable.

Fire, and what it eats

Lighting the first block is a placement and was always in the log. Everything the fire then ate was not, so a wooden build could burn to nothing and leave a single flint-and-steel in the record and no damage at all.

Every block fire burns away is now recorded, contents included, and rolls back like any other break. It is attributed to #fire rather than to whoever struck the flint: fire spreads, and by the twentieth block the person who lit it is a guess dressed as a fact. Their ignition is in the log as a placement of minecraft:fire, at a time and place you can line up against the damage yourself — which is evidence, where naming them automatically would be an inference on somebody's record.

When somebody owes items

A rollback takes back what the offender gained by it, or the restore prints items. If they cannot pay — offline, or they spent the loot — the shortfall is booked against their next login rather than written off, and they are told about it when they join.

That debt can outlive the reason for it: a rollback run while testing, or one aimed at the wrong player, leaves somebody nagged on every login with no way to settle it. So it can be written off.

CommandDoes
/staff owedEveryone who owes anything, worst first.
/staff owed <player>Item by item, with the reason each was booked.
/staff owed forgive <player>Shows what would go. Add confirm to write it off.

A debt is not a sentence, so it expires

The reason to take items back is that a rollback would otherwise duplicate them: the wall goes back up, so whoever knocked it down must not still be holding the blocks. That reasoning holds while they still have them.

Once a sweep of the ground, their inventory, their ender chest and the chests they filled has come up empty for debtExpiryDays (7 by default), the likeliest explanation is that the items no longer exist. Charging at that point takes items earned honestly since, to repay ones that were never duplicated. So the debt is written off. Set it to 0 to keep debts forever.

Undoing a rollback cancels what it left owing, for the same reason: the blocks are broken again, so nobody gained anything from a repair that no longer stands.

Deliveries never expire. An item waiting to go back to its owner is their property, and losing it would be theft rather than mercy.

Reaching items somebody has already taken

Looking for items only works while they are findable, and on a real server they usually are not. A chunk unloads the moment nobody is standing near it, and an item in an unloaded chunk is on disk rather than in memory — invisible to any search, and it will not despawn either, because entities do not tick while their chunk is unloaded. Anything already in somebody's pocket is invisible by definition.

So StaffCore records who picks what up, and recovery asks the log instead of searching the world. A scene ten thousand blocks away with nobody near it works exactly like one underfoot, and a passer-by who pocketed half a death pile is charged for exactly what they took — the debt moves off the offender rather than being collected twice.

The owner is always excluded: picking your own drops back up is not theft. Rows are marked settled once acted on, so a second rollback over the same ground cannot charge for it again. Kept for pickupLogRetentionMinutes (three hours by default) — this is the highest-volume table in the mod and only has to outlive the gap between a scene being looted and staff putting it right. Turn it off with logItemPickups, at the cost of recovery going back to searching the ground nearby.

Items you picked up at the scene

Arriving at a grief site and clearing the floor is a reflex, and it quietly breaks the arithmetic: those drops came out of the container being restored, so once it is refilled they exist twice. The ground sweep cannot find them — they are in a pocket, not on the floor — and the offender cannot be charged for them either, because they genuinely do not have them.

So the reclaim checks whoever ran the rollback, last, and only for what is still missing after the offender has paid everything they can. Bounded by the shortfall and by the item types being restored, applied only to the person who chose to run the command and never to a bystander, and a snapshot is taken first. You are told when it happens. Turn it off with rollbackReclaimsFromStaff.

Breaking blocks in creative costs nothing

A block broken in creative drops no item, so rolling it back charges its breaker nothing. Without that, staff who build in creative would be billed for their own work the moment anybody rolled the area back — and since the items were never received, paying would mean losing real ones.

Entries logged before this was recorded are treated as survival. Under-charging a griefer is the smaller wrong.

Somebody emptied a chest

This is the most common report on most servers, and until now the log answered only half of it. Click the chest with the inspector and the container log shows every stack that has moved through it — who opened it, what they took, what they put back, and when. That names the thief. It did not give the items back.

Now it does. On any take row:

ActionWhat happens
Shift-click a take Undoes that player's takes from this container, inside the log window. Everything they lifted goes back in the chest and comes off them.
Put everything back The footer button. Undoes every take from this container in the window, whoever made it, and debits each thief for their own share.

What it does and does not touch

Only takes are undone. Stacks somebody put in are left exactly where they are — a thief who dumped their cobblestone in the chest on the way past does not get it handed back, and an owner who filled their own chest does not have it emptied by a theft undo. That asymmetry is the whole point: this reverses the taking, not the visit.

The items come off the thief the same way rollback collects loot: out of their inventory if they are online, and booked against their next login if they are not. Logging off is not an escape. Anything they have already spent or given away is counted as still owed rather than quietly written off, and the chest is refilled regardless — the owner should not wait on the thief's balance.

A chest with no room takes what fits and reports the rest, exactly as rollback does. Clear space and run it again.

Server-wide

Server control

Switches that everyone online feels the moment you press them, which is exactly why the two that change what other players can do go through a confirm.

ControlIconEffect
Chat lockBarrier Silences everyone without control.chat. Confirmed on the way in, instant on the way out.
Clear chatSponge Pushes visible history off screen. Staff with command spy on keep their view — usually they still need to read what was said.
BroadcastGoat Horn A server-wide message with a chime. Free text, so it is /broadcast.
MaintenanceIron Door Refuses new joins from anyone without control.maintenance, and disconnects everyone already on who lacks it. Swaps the server-list MOTD to a bold gold maintenance notice, and puts the real one back afterwards.
Command spySculk Sensor Shows you commands other players run. Your own are never echoed back at you.

Clearing the server

Blocking new logins alone was half a feature: everybody already online stayed online, and whatever you turned maintenance on to fix carried on being played through. Switching it on now announces the change, then disconnects every player without control.maintenance.

The disconnect screen is the same component the login gate shows, so somebody kicked and somebody refused at the door read one explanation rather than two different ones — including your Discord invite, when discordInvite is set.

The maintenance notice

Turning maintenance on also swaps the server-list MOTD, because a refusal at the login screen only reaches players who already clicked connect — the MOTD is what everybody else actually reads. The real MOTD is captured the first time it is replaced and restored when maintenance ends, so repeated toggles can never leave the notice stuck on.

"maintenanceMotd": "§6§lSERVER IN MAINTENANCE
§7Please wait while we update things — back shortly."

Legacy formatting codes work: §6 gold, §l bold, §r reset, §7 grey, and splits the two lines the server list gives you. Nothing is written to server.properties, so a crash mid-maintenance heals itself — the next start reads your real MOTD back.

TPS watchdog

Samples every thirty seconds and alerts staff when TPS crosses below the configured floor — then once more when it recovers. It is edge-triggered on purpose: a watchdog that repeats every sample is a watchdog people switch off.

Coordination

Staff chat & alerts

Staff chat

/sc <message> for one line, or /staffchat to toggle the channel on and route everything you type there. Messages carry a quiet experience-orb ping so they sit under conversation rather than over it.

One handler governs all chat, in a fixed order: a mute beats everything including the staff channel, then staff chat, then the global lock. That ordering is the point — a muted player must not be able to reach staff chat, and a chat lock must not silence the staff coordinating through it.

If you are the only person online holding staff.chat, StaffCore says so after your message. A staff channel with no audience looks identical to a broken one otherwise — you type, you see your own line, and nothing tells you whether it went anywhere. Every send is also logged to console with a recipient count.

Alerts

Reports, security flags, performance warnings and staff actions. Subscription is opt-out: anyone with staff.alerts receives everything until they turn it off, because the failure mode of a quiet alert system is that nobody notices the thing it was built to catch.

Punishments are the deliberate exception — they are already broadcast by the punishment system, and hearing the same event twice trains people to ignore both. They still reach Discord.

The team

Analytics

Per-staff totals — punishments issued, reports claimed, commands logged, last activity — plus server-wide counts, ranked by punishments issued.

Three numbers that are not volume sit alongside them, because counting actions on its own rewards whoever does the most of them:

  • Seen through — reports they claimed that actually reached a conclusion. Claiming a queue and leaving it claimed is the failure mode a raw count cannot see at all.
  • Typical response — the median gap between a report arriving and them acting. It separates somebody working the queue as it fills from somebody clearing it once a week.
  • Later revoked — how many of their punishments were lifted afterwards. The closest thing the database holds to "was this call right".

Read it as a prompt, not a score. These are better than a bare volume ranking and they are still not a performance review — no number here can tell you whether a ban was the right call, only whether somebody else undid it. The staff member with two bans and forty resolved reports is doing the harder job. Click a name to open their file and read the actual work.

Anyone with no logged command for two weeks is flagged as quiet. Bear in mind the command log only records StaffCore commands, so somebody who works mostly through the panel will look quieter than they are — the screen says so itself.

Outbound

Discord

Set discordWebhookUrl in the config and punishments, reports and alerts mirror into a channel. Leave it empty and the bridge is simply off; the panel shows which state you are in.

It is fire-and-forget by design — a slow or dead webhook can never stall a tick, and failures are logged once rather than retried. Payloads are built as real JSON, so a player name containing a quote cannot break the message, and @everyone is stripped whatever a staff message contains.

Reference

Command list

Everything lives under /staff. Tab-complete the one word and the whole tree is there, and nothing StaffCore adds can collide with another mod's /ban or /kick. Two commands deliberately stay at the root: /report, because it belongs to players rather than staff, and /sc, because it is typed dozens of times a shift.

/staff <name> with no subcommand opens that player's file. Brigadier tries every literal first, so /staff ban Notch still hits the ban subcommand — only a bare name falls through to the file.

Durations are 30m, 6h, 7d, 1h30m or perm. Anything unparseable is refused, not silently treated as zero.

CommandNodeDoes
/staffstaff.guiOpen the panel
/staff <player>staff.guiOpen that player's file
/staff modestaff.modeClock on or off
/staff vanishstaff.vanishToggle invisibility
/staff freeze <player>staff.freezeLock or release a player
/staff warn <player> <reason…>staff.punish.warnLogged warning
/staff kick <player> <reason…>staff.punish.kickDisconnect once
/staff mute <player> <reason…>staff.punish.mutePermanent mute
/staff tempmute <player> <dur> <reason…>staff.punish.muteTimed mute
/staff ban <player> <reason…>staff.punish.banPermanent ban
/staff tempban <player> <dur> <reason…>staff.punish.banTimed ban
/staff unban <player>staff.punish.revokeLift a ban
/staff unmute <player>staff.punish.revokeLift a mute
/staff history <player>staff.historyPrint their record
/staff history <player> clearstaff.history.clearWipe their record
/staff notes <player> [list]staff.notes.viewRead notes
/staff notes <player> add <text…>staff.notesWrite a note
/staff notes <player> remove <n>staff.notes.removeDelete note n
/staff chatstaff.chatToggle the staff channel
/staff say <message…>staff.chatOne-off staff message
/staff alertsstaff.alertsToggle your alerts
/staff reportsreport.viewOpen the queue
/staff goto <player>staff.tpTeleport to them
/staff bring <player>staff.tpherePull them to you
/staff tppos <x> <y> <z>staff.tpposTeleport to coordinates
/staff backstaff.tpReturn to your last position
/staff invsee <player>security.invseeInventory view — works offline
/staff lookup <player>staff.guiOpen their file
/staff seccheck <player>security.checkRun the checks
/staff scansecurity.itemscannerSweep everyone online
/staff xray <player> [hours]security.checkScore one player on demand, offline included
/staff replay <player> [timespan]staff.replayWatch their session back from inside it. Needs positionTracking on, and only covers what was recorded after you switched it on
/staff replay pause · resume · speed <0.25–16> · restart · exitstaff.replayPlayback controls
/staff rollback <player> <radius> [minutes]grief.rollbackUndo their block changes
/staff spycontrol.spyToggle command spy
/staff clearchatcontrol.chatPush chat history off screen
/staff lockchatcontrol.chatSilence non-staff
/staff unlockchatcontrol.chatReopen chat
/staff broadcast <message…>control.broadcastServer-wide announcement
/staff maintenancecontrol.maintenanceKicks non-staff, blocks joins, swaps the MOTD
/staff enderchest <player>security.enderchestLive ender chest view
/staff logs <player>staff.logsJoins, leaves and deaths
/staff alts <player>staff.altsAccounts sharing an address
/staff appealsstaff.appealsAppeal queue
/staff rollback area <radius> [minutes]grief.rollbackUndo everyone's changes here
/staff rollback undo [id | list]grief.rollbackUndo a rollback — restore points kept seven days
/staff search <query…>grief.searchQuery both logs — player: action: block: time: radius:
/staff purge <age> [player] [confirm]grief.purgeDelete log history early — previews unless you confirm
/staff inspectgrief.inspect.modeHold inspect mode; any block click shows its history
/staff perms [list | set | unset]staff.permsBuilt-in groups, when no permissions mod is installed
/staff stats <player>analytics.statsOne staff member's totals
/staff reloadstaff.reloadRe-read the config and permission groups from disk
/staff statusstaff.reloadModules, TPS, storage state
/staff backupstaff.reloadTake a coherent copy of the database now
/staff exportstaff.reloadDump every table to CSV, one file each
/staff selfteststaff.reloadProve the mod works, not just that it started — see Health checks
/staff panelstaff.guiOpen the panel, same as bare /staff
/staff vaultsecurity.vaultThe contraband vault — what was taken, and what became of it; security.vault.destroy to destroy an item for good
/staff contrabandsecurity.vaultThe contraband rules; security.contraband.edit to change them
/staff nbtsecurity.invseeRead the held item’s real component data
/staff anticheatsecurity.checkBridge state and the newest findings
/staff anticheat <player>security.checkEverything one player has been flagged for
/staff anticheat test <player> [confidence]staff.reloadPush a synthetic finding through the whole pipeline, to prove an integration works
/staff owedgrief.rollbackWho still owes items from a rollback or theft undo
/staff owed <player>grief.rollbackWhat one player owes, item by item
/staff owed forgive <player> [confirm]grief.rollbackWrite a debt off — previews unless you confirm
/report <player> <reason…>report.useEveryone File a report — stays at the root
/appeal <text…>appeal.useEveryone File an appeal — works while muted
/sc <message…>staff.chatShortcut for /staff say

Reading the placeholders

Everything in angle brackets is something you type; everything in square brackets is optional and has a sensible default if you leave it out. The names are not decoration — each one expects a particular kind of value, and the commonest reason a command "does nothing" is a placeholder filled in with the wrong sort of thing.

PlaceholderWhat goes there
<player> A player name. Tab-completes for anyone online. Offline players work too for punishments, appeals and lookups — the name is resolved against the server’s own cache, so it has to be someone who has joined at least once. Not a UUID.
[player] The same, but optional. Left out, the command applies to everyone, or to you, depending on the command — each row in the table above says which.
<dur> How long a punishment lasts: a number and a unit, with no space. 30s, 10m, 2h, 7d, 2w. Several can be run together — 1d12h is a day and a half. Anything it cannot parse becomes permanent, deliberately: a typo that silently expires a ban an hour early is worse than one you have to undo.
<age> The same format, read as "older than this". /staff purge 30d deletes log entries more than thirty days old.
<reason> Free text to the end of the line. No quotes needed. Shown to the player and kept on their record, so write it for them to read.
<text…>
<message…>
Free text to the end of the line. The ellipsis means it swallows everything after it, so this is always the last argument.
<radius> Blocks out from where you are standing, as a whole number. A rollback radius is a square, not a circle, and it reaches from bedrock to sky — it is horizontal distance only.
[minutes]
[hours]
How far back to look, as a plain number with no unit letter. /staff xray Bob 6 is six hours. Left out, each command uses its own default from the config.
<x> <y> <z>
<world>
Block coordinates as whole numbers, and a dimension id such as minecraft:overworld. Tab-complete offers the worlds that actually exist.
<n> A plain count — how many rows to show or act on.
<name>
<group>
A permission group name as written in staffcore-permissions.json. Tab-completes from the groups you actually have.
[confirm] The literal word confirm. Without it the command previews what it would do and changes nothing. This is the only safety net on the destructive commands, and it is opt-in on purpose — typing a word is cheap next to undoing a purge.
[list] The literal word list, to show what exists instead of changing it.

Placeholders in the config

One config value takes markup rather than a value: maintenanceMotd is what the server list shows while maintenance is on, and it accepts the game’s own formatting codes. §6 is gold, §c red, §7 grey, §l bold and §r resets back to plain. A literal splits the two lines the server list gives you. Nothing else in the config is a template — every other value is a number, a boolean, or a plain string used as-is.

Reference

Permissions

Install fabric-permissions-api alongside LuckPerms and StaffCore picks it up automatically. That is still the best answer, and it wins outright wherever it is present.

Without one, config/staffcore-permissions.json answers instead. It is written with the starter groups below on first run, and holds node lists — with @other to inherit from another group and staff.* wildcards — plus a map of players to groups. Assign somebody with /staff perms set <player> <group>, and /staff reload re-reads the file after a hand edit.

operatorsBypass starts on, so writing this file cannot lock you out before you have put yourself in it. Turn it off once your staff are assigned and op stops being a way around the groups. Only then do ranks actually mean anything on a server without LuckPerms — before this, every node fell back to op level, which made a trainee helper exactly as powerful as an admin.

Op level remains the last resort, for a node no group mentions. report.use is the exception to all of it: it resolves default-true, so /report stays open to everyone rather than quietly becoming admin-only.

Suggested groups

These ship as the defaults in that file.

Helper

staff.gui · staff.mode · staff.vanish · staff.freeze · staff.tp · staff.chat · staff.alerts · staff.notes* · staff.history · report.view · security.invsee

Moderator

Helper, plus staff.punish.* · staff.tphere · staff.tppos · security.check · security.itemscanner · control.spy · staff.alts · staff.logs · grief.inspect · grief.search

Admin

Moderator, plus staff.punish.revoke · staff.history.clear · security.invsee.edit · grief.rollback · grief.purge · control.* · security.* · analytics.stats · staff.reload · staff.perms

Two nodes are declared but not yet wired to anything: grief.rollback.regen and proxy.admin. They are reserved for chunk regeneration and a proxy companion. Granting them today does nothing.

Reference

Icon key

Chosen so a screen reads at a glance without reading a word. Player heads carry the real skin, and anything currently switched on gets an enchantment shimmer — state is visible in peripheral vision.

ToolItemToolItem
Staff ModeDiamond ChestplatePunishNetherite Axe
VanishEye of EnderWarnPaper
FreezePacked IceKickIron Boots
Staff ChatOak SignMuteNote Block
AlertsBellBanNetherite Axe
Command SpySculk SensorTemp-banIron Axe
Teleport / ReturnEnder PearlTimedClock
Bring HereLeadPermanentBedrock
PlayersPlayer HeadLift punishmentTotem of Undying
ReportsPaperLift muteJukebox
NotesWritable BookWipe historyLava Bucket
HistoryBookBroadcastGoat Horn
InventoryChestClear chatSponge
SnapshotsEnder ChestChat lockBarrier
SecuritySpyglassMaintenanceIron Door
Item ScannerHopperServer statusBeacon
Grief LogTNTAnalyticsMap
Broke a blockIron PickaxePlaced a blockBricks
DiscordAmethyst ShardHelpKnowledge Book
Confirm / CancelLime / Red ConcreteLockedIron Bars

Menu frames are black stained glass. The invsee frame turns red in edit mode.

Reference

Sound key

One voice across the whole suite: navigation is quiet and high, state changes are pitched — up for on, down for off — and anything that lands on another player is loud and low. Every sound is sent to a single client, so a vanished admin clicking through menus is silent to everyone else.

MomentSoundPitch
Menu opens / closesblock.barrel.open / close1.5
Button pressui.button.click1.1
Into a sub-menu / back outblock.amethyst_block.chime1.4 / 0.9
Page turnitem.book.page_turn1.1
Toggle on / offnote_block.pling / bass1.8 / 0.7
Small win — note saved, report claimedentity.experience_orb.pickup1.3
Big win — punishment landed, rollback doneentity.player.levelup1.4
Refusedentity.villager.no1.0
Something brokeblock.anvil.land1.9
Staff mode on / offblock.beacon.activate / deactivate1.6
Vanish on / offentity.enderman.teleport1.6 / 0.8
Invsee opensblock.ender_chest.open1.3
Alert or new reportblock.note_block.bell1.5
Staff chat messageentity.experience_orb.pickup1.9, quiet
A ban lands — heard by all staffentity.lightning_bolt.thunder1.4
Target: frozen / unfrozenentity.player.hurt_freeze / chime
Target: warned / mutednote_block.pling / bass, low0.6 / 0.5

Setup

Config

Written with defaults on first run to config/staffcore.json. The file carries a configVersion: when a default changes in a way existing servers should inherit, the value is moved only if it still holds the old default, so anything you deliberately set is left alone and every change is logged. /staff reload re-reads it without a restart.

{
  "discordWebhookUrl": "",            // empty = bridge off
  "requireReason": true,
  "publicPunishmentBroadcast": true,  // false = announce to staff only
  "presetReasons":   [ … ],           // becomes the buttons in the Reason menu
  "presetDurations": [ { "label": "7 days", "spec": "7d" }, … ],
  "reportCooldownSeconds": 60,
  "rootAliases": false,              // /ban, /vanish etc. at the root, if free
  "xrayRatioThreshold": 0.12,         // ore fraction that trips the heuristic
  "xraySampleFloor": 200,             // blocks needed before it fires at all
  "tpsAlertFloor": 17.0,
  "defaultRollbackMinutes": 60,

  "rollbackReclaimsDrops": true,      // without this, every rollback duplicates items
  "rollbackChasesBankedLoot": false,  // follow loot stashed in chests outside the radius
  "rollbackPointRetentionDays": 7,    // how long a rollback stays undoable; 0 disables undo
  "watchContraband": true,            // alert the moment a player is seen holding one
  "watchContainers": true,            // check chests as they are opened and closed
  "scanEnderChests": true,
  "maxSnapshotsPerPlayer": 10,        // 0 keeps every one
  "snapshotRetentionDays": 30,
  "altSubnetMatching": true,          // also link accounts sharing an address range
  "connectionRetentionDays": 90,      // personal data; 0 keeps forever
  "hashConnectionAddresses": true,    // matching still works; the plaintext goes
  "altMinConfidence": 40,             // below this, a link is not worth an alert
  "vanishLoadsChunks": true,          // false = vanish leaves no trace, and you fly into void

  // Shown in the server list while maintenance mode is on
  // \n splits the two server-list lines; §6 gold, §l bold, §7 grey.
  "maintenanceMotd": "§6§lSERVER IN MAINTENANCE\n§7Please wait while we update things — back shortly."
}

The punish flow is entirely preset-driven — add a reason or a duration here and a new button appears, no code change.

vanishLoadsChunks is the one worth thinking about before you change it. Left on — the default — a vanished admin still holds chunks open around them, so they can see and fly through the world; spawning is suppressed either way, so farms behave as though nobody is there. Turned off, vanish becomes complete and leaves no trace on the server at all, at the cost that chunks nobody else is holding open will not load for you. Reasonable on a busy server. A bad idea on a quiet one.

Either way the change lands on the next chunk boundary you cross rather than the instant you toggle vanish, because that is when chunk tickets are registered. In practice you will not notice — anyone who cares about chunk loading is moving.

Operations

Where data lives

SQLite at <world>/staffcore.db, opened when the server starts, with write-ahead logging on. Schema changes are numbered migrations recorded in the file itself, so an existing install upgrades in place, once, in order, and never has to be deleted to move forward. If it fails to open, every feature that needs it degrades quietly and the rest of the mod carries on.

TableHolds
punishments, notes, reports, appealsThe enforcement record
block log, container logWho broke it, and who emptied it
command logStaff commands, for the spy and the analytics
connections, session log, death logAddresses, sessions and deaths
snapshots, snapshot itemsInventory snapshots and their contents
staff state, stashVanish, freeze, duty state and on-duty inventories
contraband vaultItems taken off players and kept
pending actionsItems owed to somebody who was offline when staff decided

Survives a restart:

  • Vanish state
  • Freeze anchors — logging out no longer clears a freeze
  • Staff mode, including the duty gamemode you came from
  • The on-duty inventory stash
  • Inventory snapshots, and everything in the vault
  • Anything owed to a player who was offline when staff decided it

In memory only, and cleared by a restart:

  • /back return points
  • Mass-grief burst counters
  • Inspect mode — deliberately, so it cannot eat your first pickaxe swing after a restart

Keeping it safe

The database is the evidence. A punishment record with a hole in it is worse than no record, because somebody will act on the half that survived. Four things stand between you and that:

WhatWhenWhy it is that way
TransactionsEvery multi-step write Restoring a snapshot, draining what is owed to somebody joining, retiring rolled-back rows and purging a player all touch several tables. Each either lands whole or not at all, so a crash in the middle cannot leave items handed over but the row still owing them.
Integrity checkEvery boot A corrupt file is moved aside with a timestamp and the newest good backup takes its place. If there is no backup it starts clean. It never fails the boot — a broken staff database must not be the reason nobody can play.
BackupsEach start, plus /staff backup Taken with SQLite's own coherent-copy command, not a file copy — copying the file by hand while the write-ahead log is live can give you something missing its most recent writes. Rotated to databaseBackups.
Export/staff export One CSV per table in a timestamped folder. A backup is for restoring; an export is for reading — which is what a data request, an audit, or a spreadsheet actually needs. The table list comes from the database itself, so nothing is left out by accident.

Security

Anti-cheat bridge

StaffCore does not detect cheating. It gives whatever does the detecting somewhere useful to say so.

An anti-cheat that logs to console is a tool nobody reads. The finding that changes a decision is the one a staff member sees next to everything else they know about that player — the punishment history, the alt links, the mining report. A flag on its own is ambiguous. A flag alongside three others is not.

What arrives

Every provider has its own vocabulary. These are the four things they all say:

KindMeans
Detection The provider's own checks flagged something. An opinion, and often a provisional one.
Cloud detection A shared-intelligence service flagged this account, usually from behaviour on other servers.
Mitigation The provider intervened — cancelled a movement, rolled something back, slowed something down.
Punishment The provider punished the player itself.

These stay separate on purpose. A detection is an opinion; a punishment already happened. Collapsing them is how staff end up either ignoring everything or punishing somebody twice for one offence.

Noise control

Detections below antiCheatAlertConfidence (60 by default) are recorded but not announced. Anti-cheats flag on thresholds that expect to be wrong sometimes, and an alert channel that cries wolf is one staff learn to scroll past. Everything is still on the player's file either way. Mitigations and punishments always announce — those already happened to somebody.

Findings are kept for antiCheatRetentionDays (30 by default). A year-old flag on a player who has been fine since is not evidence of anything, and this is the chattiest source of rows in the database.

It never punishes StaffCore will not ban, mute or kick on the strength of a detection, and there is no setting to make it. Acting on an anti-cheat flag stays a human decision. What the bridge changes is that the human sees it.

Wiring one up

StaffCore cannot compile against an anti-cheat it does not ship with, so there is one supported integration point and it is a plain method call. Any mod, bridge or script does this:

StaffCore.antiCheat().report(new AntiCheatEvent(
        "Polar",                       // which provider said it
        player.getUUID(),              // or null if you only have a name
        player.getName().getString(),
        AntiCheatEvent.Kind.DETECTION, // or CLOUD_DETECTION, MITIGATION, PUNISHMENT
        "Speed",                       // the provider's own name for the check
        82,                            // confidence 0-100, or -1 if it has none
        "moved 4.2 blocks in one tick",
        System.currentTimeMillis()));

There is a shorter form taking a name instead of a UUID, which resolves the account itself. Either way the call is safe to make from another mod's event thread: a malformed event is dropped with a warning rather than thrown back at you, because an exception there becomes somebody else's crash report.

What is and is not automatic

StaffCore detects whether Polar, Grim, Vulcan or Matrix are installed and says so in the boot log. It does not subscribe to their events automatically. Subscribing means calling methods StaffCore cannot see at compile time, and guessing at another mod's internals produces code that compiles, runs, quietly catches its own failure and does nothing — which is worse than not trying, because it looks like it worked.

So the honest position: if the boot log says a provider was detected, point that provider's alert hook at the call above and findings will flow. Until you do, nothing from it reaches StaffCore. The log tells you which of the two you are in.

Reference

Every module

StaffCore is nineteen modules behind one panel. Each owns one job, keeps its own data, and can be switched off without taking anything else with it. This is the whole list, in roughly the order a shift uses them.

ModuleWhat it does
Staff Mode
staff_mode
Clocking on. Stashes your survival inventory, hands you the staff toolset, switches you to the duty gamemode (creative by default) and gives it all back on the way out. The stash survives a restart, and a failed restore never consumes it.
Vanish
vanish
Being invisible properly. Entity tracking, tab list, player count, /list, selectors, the server-list sample, sounds and waypoints all agree that you are not there. Optionally stops you loading chunks. Survives a restart and a reconnect.
Freeze
freeze
Pinning a player where they stand, with an on-screen explanation. Anchored to a position, so logging out and back in does not shake it off.
Teleport
teleport
Going to a player, bringing one to you, teleporting to coordinates, and /back to return. Return points are memory-only and cleared by a restart.
Punishments
punishment
Warn, mute, kick, temp-ban and ban, from a preset offence ladder or set by hand. Timed punishments expire on their own; revoking one keeps the record and marks it revoked rather than deleting it.
Reports
report
/report for players, a claim-and-resolve queue for staff, and a teleport straight to the subject. Claiming is recorded, which is what makes follow-through measurable later.
Appeals
appeal
The other side of a punishment. Works while muted, on purpose. Accepting an appeal lifts the punishment it refers to in the same action.
Notes
notes
Staff-only notes on a player — the things that are not punishments but that the next person on shift needs to know.
Inventory
inventory
Live inventory and ender chest views, editable behind their own permission, plus snapshots. Snapshots capture every slot including armour and offhand, are taken automatically before a death, a logout, a staff edit and a rollback debit, and restore without duplicating anything still lying on the ground.
Security
security
The item checks: contraband, over-stacked stacks, enchantments above the vanilla ceiling, staff tools loose in the world, and the x-ray heuristic. Findings go to the vault rather than being deleted.
Grief Log
grief
Who broke it, who placed it, who opened it and who emptied it. Block inspector, area and per-block history, search, rollback with a rendered preview, rollback undo, and chest-theft undo. Rolled-back blocks return with their orientation and their contents.
Identity
identity
Addresses, sessions, deaths, and the alt links built from them. Keeps stored names current when a player renames, so an account cannot drop out of search by changing its name.
Ban Evasion
evasion
Screens joins against banned accounts on the same address or range, alerts staff, and can auto-ban on an exact match only. Everything else is a lead for a human.
Anti-cheat bridge
anticheat
Normalises findings from an installed anti-cheat — detections, cloud detections, mitigations and punishments — into StaffCore alerts and onto the player's file. Never punishes on its own.
Server Control
control
Chat lock, clear chat, broadcast, maintenance mode with its own server-list message, command spy, and the TPS watchdog.
Staff Chat
staff_chat
A private channel for staff, on /sc because it is typed dozens of times a shift.
Alerts
alerts
The one place every notification goes through — punishments, reports, security findings, suspicious mining, performance, staff actions — so routing and rate-limiting live in one place instead of nineteen.
Analytics
analytics
The staff leaderboard, and a per-person record: total actions, what kind, follow-through, overturn rate, response time, tools used and recent activity.
Discord
discord
Outbound webhook for punishments, reports and alerts. One direction only — see Known limits.

Switching one off

Every module has an enable flag in config/staffcore.json. A disabled module stops running, stops recording and disappears from the panel; what it already wrote stays in the database. Nothing else changes — modules reach each other through the registry and handle a missing neighbour rather than assuming one.

Operations

Checking the mod is healthy

StaffCore hooks into the game in thirty-one places. Most of them are allowed to fail without stopping the server, which is the right trade — and it means a broken feature looks exactly like a feature nobody used today. These are the three ways to tell the difference.

1. The startup line

Every boot prints one line. On a healthy server it reads like this:

[StaffCore] Health check: 19/19 hooks present, 15 verified applied.

Hooks present is the number of places in the game StaffCore expects to find. If that first number is lower than the second, an update has moved or removed something and the affected features are off — each one is named on its own line underneath.

Verified applied is the stricter question: not just “is the target still there”, but “did our code actually get injected into it”. It is normally lower than the first number, and that is not a fault. Some hooks cannot be positively confirmed from inside the running game, so the check reports only what it can prove and stays quiet about the rest. A check that cried wolf on a healthy server would teach everybody to ignore it.

What to act on A number lower than expected on the left of “hooks present”, or any line beginning [StaffCore] Hook unavailable. Those name a feature that is genuinely not working. A low “verified” count on its own is not a fault.

2. The Diagnostics screen

/staffServer Status opens it, or type /staff status for the same information in chat. Every hook is listed with a coloured state:

StateColourWhat it means
WorkingLime Confirmed attached and running. Nothing to do.
UnverifiedYellow The target is there and the hook loaded, but attachment could not be positively confirmed from inside the game. Normal — most hooks sit here. Not a fault on its own.
Target missingRed The method or class StaffCore hooks no longer exists. Almost always a Minecraft or Fabric update. The feature behind it is off.
Not appliedRed The target exists but our code did not attach — usually another mod hooking the same place, or an injection point that moved. The feature behind it is off.

Each row names the feature it belongs to, so a red entry tells you what stopped working rather than leaving you to map a class name onto a menu.

3. The self test

/staff selftest is the difference between “it started” and “it works”. The health check proves our hooks attached; it cannot prove the code behind them runs. A malformed query, a table missing a column, a command that never registered — all three start cleanly, pass the health check, and fail the first time somebody uses them.

The self test uses the mod and reports eight things:

CheckProves
storageThe database is open and at the expected schema version. A version of 0 means migrations never ran.
modulesHow many modules are enabled.
commandsThe /staff tree registered, and how many subcommands are on it.
contrabandEvery entry in your contraband list resolves to a real item — and names the ones that do not. This is the check that would have caught minecraft:spawn_egg, an id that never existed and therefore never flagged a single spawn egg.
xray scoringThe detector still scores an obvious session as obvious, against your own thresholds.
backupA backup actually writes a file.
exportAn export actually writes CSVs, and how many.
configWhich config version is loaded, and the duty gamemode.

A failing check names what is broken rather than just failing, and the server keeps running either way — everything that still works is unaffected. Continuous integration runs the same checks on a real server on every change, which is what makes this the layer that catches things before a release does.

4. Module and storage state

Hooks are only half of it. /staff status also reports:

  • Modules — which are enabled. A module switched off in the config is off deliberately; one missing from the list entirely is a bug.
  • Storage — whether the database opened. If it did not, every feature that needs it degrades quietly and the rest carries on, so this is worth reading rather than assuming.
  • TPS — the server’s own tick rate, for context.
  • Anti-cheat bridge — which providers, if any, are wired up.

A two-minute check after an update

After any Minecraft, Fabric or StaffCore update, this is the whole routine:

  1. Read the startup line. 14/14, or whatever the current total is, on both sides of the slash.
  2. If it is short, read the named lines under it — they say which features are affected.
  3. Open /staff → Server Status and confirm nothing is red.
  4. Run /staff selftest. Eight checks, all passing.
  5. Run the manual checks in Known limits — vanish with a second account, break and roll back a chest. Those cover the behaviour no automatic check can see.

The build also runs this automatically: continuous integration boots a real server on every change and fails if any hook is missing, and a scheduled job repeats it against the newest Minecraft snapshot so a breaking change surfaces before a release reaches anybody’s server.

Honesty

Known limits

Stated plainly rather than papered over.

Recovery and evidence

  • Rollback only reaches the area and window you give it. That is the scope you asked for, and widening it silently would be worse than not widening it. What is followed beyond the radius is the offender's own loot: ground drops are reclaimed, a live inventory is debited, chests they filled during the same window are emptied back, and whatever an offline offender still owes is collected the next time they log in. Loot handed to somebody else is gone.
  • Container rollback still needs somewhere to put things. A full chest cannot take the stack back. The log row is left un-retired so retrying after clearing space works, and the number of stacks that would not fit is now reported rather than left for you to spot.

Detection

  • Alt detection is a lead, never a verdict. It links accounts by exact address and by address range, scores each link out of a hundred from how often the address was shared and whether the two accounts have ever been online at the same moment, and shows its reasoning on the screen. A shared household still trips it. A determined evader with a clean VPN still beats it. Only an exact address match can ever ban anybody automatically.
  • The x-ray check is a heuristic. It flags, it never acts, and by default it will not commit to a verdict below a 512-block volume of rock. It no longer counts ore a player placed themselves, but it is still inference drawn from a block log. Its thresholds are measured against generated mining patterns, not against real player data — docs/decisions.md records exactly what that does and does not establish.
  • Analytics cannot read judgement. Follow-through, response time and overturn rate sit beside the raw counts now, so the leaderboard is no longer purely a volume ranking — but no number here can tell you whether a ban was the right call. Read it as a prompt to go and look at someone's work.

Not built

  • Discord is outbound only. Punishments, reports and alerts go out through a webhook; nothing comes back. Two-way needs a gateway connection, a bot token and roughly 10 MB of JDA, which belongs in a separate jar.
  • No map integrations. BlueMap, Dynmap and Squaremap would each need that mod present as a compile dependency.

Structural

  • Almost every mixin is non-fatal. Exactly one (the login gate behind bans and maintenance) is required; the rest use defaultRequire: 0, so an update that moves an injection point costs you that feature rather than your server. The startup check asks two questions of each hook — is the target still there, and did our code actually get injected into it — so a mixin that quietly fails to apply is named in the log instead of passing as healthy. The second question is answered conservatively: it reports only what it can positively establish, and stays silent on anything it cannot, because a check that fires on a healthy server teaches everybody to ignore it.
  • Some of it is still only verified by construction. Every 26.2 API change was read out of the real jar with javap, and the mod now proves rather more of itself at runtime: continuous integration boots a real server and runs the self test, so storage, migrations, the command tree, the contraband list, the detector, backups and exports are all confirmed working on every change. What still is not covered is the behaviour that needs a live client and a second account — vanish being seen, the maintenance kick landing, an inventory screen redrawing. Those are reasoned about and checked by hand.
  • The tests cover logic and wiring, not visibility. The automated suite runs the headless half — storage and its migrations, corruption recovery, rollback arithmetic, the x-ray scoring, the contraband list against the real item registry, log parsing — and continuous integration then boots a real server, checks every hook, and runs the self test against a real database. What is still not automated is the half that needs two clients standing in a world: a test per visibility leak (tab list, selectors, sound, waypoints), and killing the server mid-write to prove every state transition survives it. Minecraft's own test framework runs server-side and cannot drive two connected clients, so this is a real gap rather than an oversight, and claiming coverage that is not there would be worse than the gap itself. The manual checks below cover the same ground by hand and are worth running after any Minecraft or Fabric update.
  • There are more mixins now, not fewer. Thirty-one classes, thirty of them non-fatal. The vanish rework did remove the fragile one — a hook that reached into a package-private inner class by name — and replaced it with vanilla's own public methods, then added more of those. Each is individually sturdier and each is one more thing a Minecraft update can move. Reducing the count was on the list and did not happen; what happened instead was reducing how badly any one of them can fail, which the startup line reports every boot.

Checks worth running by hand

These are the ones automation does not cover, and they are the ones most likely to break after a Minecraft or Fabric update. Each needs two accounts and a couple of minutes.

CheckExpected
Vanish, have the other account watch you, then unvanish They see you again without moving. Needing to walk away and back means entity tracking has gone stale — the exact bug the vanish rework was for.
Vanish and check the server list from outside Player count and the hover sample both exclude you. So does /list and an @a selector.
Fill a shulker, place it, break it, roll the break back The block returns with its contents, and the person who broke it keeps neither.
The same with a double chest, clicking each half in turn Both halves come back either way round.
Ban an alt account and read the disconnect screen it gets Reason, length in words, a real end date with its zone, how to appeal, Reference: #id and an Appeal code in groups of four. This screen is the only channel left to a banned player, and what they do with it is photograph it.
Punish somebody while another staff member is online, then run /staff op P-<id> The record, and a line naming who else was connected. "Nobody else was connected" is a finding, not a blank.
Type /staff ban with no player, having just looked somebody up It names them and offers a click-to-fill, rather than printing a usage line for a shape you have just demonstrated you know.
Type a name one character off a real one Refused, with the candidates listed as things to click. It must never pick the closest.
Log in as staff with a report open and a case unresolved Up to three lines, each a link, none of them reading zero. With nothing waiting, nothing is said.
Punish somebody from the panel and click "Something else?" The screen closes and hands over the command with the target and the chosen duration already in it. A preset must never be the only route.
Preview a rollback covering spawn, then run it without previewing The preview warns; running it cold is refused and points at the preview.
Have two staff preview overlapping rollbacks at once The second is refused by name, so there is somebody to go and talk to.
Decoy blocks: are they visible to a cheating client, and does the resync arrive Follow docs/manual-checks/decoy-visibility.md — a numbered five-minute script with the config to set, what to look at, and pass/fail for each claim. Not yet run. Until it is, the decoy layer is packet-verified rather than client-verified, and the measured false-positive rate says the retirement rule holds rather than that decoys work.
Run /staff xray <player> replay, fly the tunnel, then /staff xray exit You arrive at the entrance in spectator and vanished, the dig is drawn in tinted glass with ore lit, the sidebar shows the odds and both fractions, and exiting puts you back exactly where you were in the gamemode you left in. Nobody else sees any of it.
Start a replay, disconnect without exiting, rejoin You come back at your own position in your own gamemode. The drawn blocks are gone — a fresh client was sent the honest chunks — and the sidebar is gone with them.
Start a replay, fly through a portal The replay ends within five seconds and puts you back, rather than leaving a tunnel drawn over a different world.
Start a replay and stop the server without exiting On restart the log names the open sessions; rejoining restores you. This is the case that strands somebody if the record is only in memory.
Kill the server process mid-snapshot It boots, and the snapshot is either whole or absent — never half.

Recently closed

Things that used to sit on this list, and what replaced them.

WasNow
Rollback ignored offline offenders and banked lootChests they filled are emptied back; the rest is collected on next login
Container rollback failed silently on a full chestThe deferred count is reported, and the rows still wait for a retry
The vault only returned items to online playersAn offline return is queued and delivered on next login, and can be cancelled meanwhile
The vault listed the newest five hundred itemsPaged straight from the database; returned and destroyed rows are reachable too
Snapshots were fixed at ten per playermaxSnapshotsPerPlayer, or 0 to keep every one
The contraband watch never saw world containersChests are checked as they are opened and closed; a staff tool found in one goes to the vault
Ender chests were described as swept but were notThey are now, behind scanEnderChests
The rules screen refused to edit past forty-five entriesIt pages
Vanished players always loaded chunksvanishLoadsChunks, defaulting to the old behaviour
Every node fell back to op level without a permissions modstaffcore-permissions.json — real groups, and /staff perms
Ledger parity: no search, no purge, no inspect toggle/staff search, /staff purge, /staff inspect
Rolling back a double chest restored only the half you clickedBoth halves resolve as the one container they are, from either block
Shift-clicking a stack in the inventory view did nothing usefulIt moves stacks between the target's inventory and your own, the way a chest does
Inspect mode broke the block and told you nothingIt reports the block's history, and the break is cancelled twice over
Restoring a death snapshot while the drops were still lying there doubled everythingThe drops are swept up first; only what is genuinely gone gets recreated
You could see a theft in the container log but not undo itShift-click a take to undo that thief, or put the whole chest back in one click
Chest boats and container minecarts were not watched at allTakes and puts are logged the same as a chest, so they stop being the quiet place to stash things
Copper double chests read as two unrelated singlesPairing asks the block instead of comparing it, so every weather state and waxed variant works
A rollback debt stood forever and survived undoing the rollbackIt expires, and undoing the rollback cancels it
Snapshots showed empty armour and offhand slots, and restoring one gave none backEvery slot is read back, not just the first thirty-six — the items were always stored, only the reader truncated them
Rolled-back chests came back single and facing the wrong wayThe full block state is recorded, so orientation and double-chest pairing return as they were
A thief who dropped the loot, banked it or hid it in their ender chest kept it after a rollbackAll five routes are chased, and anything still missing is owed against their next login
On a brand-new server, snapshots, punishment reasons and alt detection wrote to columns that were never createdFresh and upgraded databases are now checked against each other for exactly this
The contraband list named minecraft:spawn_egg, which is not an item — so none of the eighty-eight spawn eggs were ever flagged#spawn_eggs covers every one, existing configs are migrated, and the self test fails on any entry matching nothing
Inventory and ender chest views were a photograph taken when the screen openedThey redraw twice a second, so watching somebody empty a shulker shows it happening
No way to see what an item was actually carryingMiddle-click any slot, or /staff nbt for the one in your hand
The x-ray check flagged strip mining, the commonest honest technique there isIt looks for short detours off the tunnel that end on ore, which is the thing strip mining never does
X-ray counted all ore alike, over one windowBroken down by ore type and weighted by rarity, across four spans of time at once
A renamed player dropped out of every searchStored names are brought up to date on their next join
Permission assignments listed raw UUIDsResolved to names, with the key kept alongside
Anti-cheat findings lived in console and nowhere staff would lookNormalised into alerts and onto the player's file — see Every module
Block-log writes queued at shutdown were droppedThe writer drains before the database closes