Beta

STYGION Keystone b0.1.0

The number went backwards on purpose. What shipped as 1.0.1 was this mod's name around thirteen thousand carried-over lines of the one before it. That code is gone and everything it did is written again, so this is the first version of the mod that is actually the mod — and calling it 1.0.2 would have said the opposite. The files under the old number are withdrawn; nothing updates from them, because there is nothing they could honestly update into.

Everything 1.0.1 could do, written again from nothing — and this time on all four builds instead of one. Restarts, announcements, the entity cleaner, vote rewards, playtime ranks, skin restore, the Discord bridge and tickets were in 1.0.1 as thirteen thousand carried-over lines of the previous mod. That code was deleted whole on 2 September 2026 and every feature was written again against this mod's own foundations. Nothing you had is missing; each of them is listed below with what it gained.

Why it went rather than being tidied: it kept its own settings files, its own storage and its own second set of ranks beside the ones in the database, and it answered to Minecraft's operator level instead of Keystone's permissions. Its /keystone collided with the real one — the console reload could not be run at all, and the permission gate on /keystone was being dropped on the way in. None of that is fixed by wiring it up more carefully, and carried code that runs is code nobody ever rewrites.

One thing did not come back, and it is the only thing you lose: it ran on one loader and one Minecraft version, because that was where those lines compiled. What replaces it runs on Fabric and NeoForge, on 1.21.1 and on 26.2, and every one of those four is booted and driven by a real server before a release is allowed out.

The editor was rebuilt behind the same door. Five screens were drawn against that code's own back end and stopped answering when it went. They come back one at a time, each with the feature under it and each reading a route that is in the published interface rather than this page's private one — so an addon, a chat bot and the assistant see exactly what the page draws. Back: Operations, Settings — with every change recorded and one press to undo it — and Console, with chat beside it. New: Performance, for what this machine actually holds, and Assistant. Alongside the ones that never went: the players on the server, the groups and what they may do, the keys this server has issued, and the status and live feed in the header. Still to return: the overview and the community screen, and they arrive with what goes under them rather than as a tab that answers nothing.

One command says how many players this machine holds. /keystone test 20 measures the server doing nothing first, then places test players in groups and waits after each one — so asking for more than the machine can take gives you an answer instead of an out-of-memory kill. It ends with a sentence you can act on: how many it was comfortable with, what gave first, and whose code the time went into, by mod. --to 200 climbs until something gives and reports the ceiling; --for 5m holds; --at 128 -320 measures the place where everything on your server actually happens rather than wherever you were standing.

And it can bring its own load, because a quiet world measures a quiet world. --load 400 turns on a fixed amount of extra work per player — scattered block reads, allocation, block updates, arithmetic — so the answer is how much headroom this box has beyond what your pack already costs. It is deliberately ours and not built out of your pack's blocks: a load made of a pack's contents costs a different amount in every pack, which would make the one number meant to compare two servers the number that differs most between them. What one unit does is versioned, so a run from today and a run from next year are never quietly averaged together after it changes.

The verdict now names the wall instead of the symptom. "Tick time was what gave" is true of a machine out of processor, a machine out of memory, a machine spending its time collecting garbage and a machine waiting on a disk — and those are four different things to buy. The run says which, and why it thinks so.

And it says what a player costs. "Every ten players cost about 4 ms a tick; at 40 it was using 22 ms of the 50 a tick allows, leaving 28." A threshold tells you about the box you have; a slope lets you work out the one you are thinking of buying.

Each step of the ramp reports arriving and standing separately. Somebody joining makes the server load the world around them, which costs far more than ticking it afterwards. Reported as one number, "it holds sixty" can hide a four-hundred-millisecond stall at every join — the thing players actually feel, and a different fault with a different fix.

The test players walk, and that is not a detail. A player who stands still costs a server a loaded chunk and almost nothing else — nothing to send, no tracking to redo, no chunk border ever crossed — so a test made of statues tells you the machine holds more people than it does, which is the one direction a number like this must never be wrong in. Each of them now walks a small circle around where it was placed: far enough to cross a chunk border every few seconds, small enough that the amount of world loaded is still the amount you asked for. The run says how far they walked between them, so a walk that stops working cannot pass for one that does.

It says which world your time is going into. Alongside "62% of the late time was this mod", the report now carries the share of world-ticking time each dimension took — 94% overworld, 3% nether, 3% end. No profiler can answer that question, because the call that ticks a dimension looks identical in all of them, and "two thirds of your tick is a Nether nobody has visited since April" is something you can act on this evening. It also counts everything standing on the server by type at its worst second, not only inside the three busiest chunks: four hundred entities spread evenly over a hundred chunks never shows up as a hotspot and is still four hundred entities being ticked.

Runs record what they need to be comparable at all: the loader, the Minecraft version, the whole mod list with versions, and what kind of disk your world is on — read out of the mount table and the kernel rather than measured, and left out entirely when it cannot be told. Not the boot drive: a server booted off an SSD with its world on a spinning disk behaves like the spinning disk.

Nothing is written to your world. Saving is off for the length of a run, so the blocks the load touches exist in memory and nowhere else — a crash in the middle cannot leave any of it behind. Where somebody real is nearby, the load runs read-only and the report says so. Every test player is removed at the end, however the run ended, and only the ones the test placed: bots you left standing on purpose to hold a chunk open are not its to tidy away.

The run is readable rather than only recorded. A new Performance screen in the editor draws it — tick time second by second, each step of the ramp, the busiest chunks by what is actually in them, the world it ran in and the machine it ran on. When it finishes you are asked, in chat, whether to put it in a Discord channel or send it to the public figures at stygion.eu, with an "always" beside each so you are asked once rather than every time. Nothing leaves by itself, and the place those runs will go is still being built — until it exists, choosing to send marks the run and nothing goes anywhere.

Every settings change can be undone. Who changed what, when, and from which surface — the console, the editor, the interface or the assistant — with the value it had before. One press puts it back, in the editor or with /keystone set undo, because the person fixing a server at two in the morning is usually on SSH. It undoes that change rather than restoring the whole file as it was then, so the afternoon's other work survives; and undoing is itself a change, so pressing it twice puts things back again. A credential is recorded as having been replaced and never as what it was replaced with — the alternative would be a table holding every token this server has ever had, for the convenience of undoing a paste.

Ask the server what is wrong with it. /keystone ask why did it stutter at eight? — and the answer is read off the numbers rather than guessed at. It works the same in the editor, on the same conversation: a thread you start in game is the thread you carry on in the browser, and it is still there after a restart. Bring your own model and point it anywhere, including at one running on your own machine. Nothing routes through STYGION, there is no key of ours, and a blank address simply means no assistant rather than something broken.

It cannot change your server, and that is not a promise — it is how it is built. The assistant reaches this mod through the same interface an outside program uses, holding a key whose scopes you choose. Anything that reads runs; anything that changes something never does. It becomes a proposal — what it would set, and what that is now — and you get a question in chat with two buttons, or a line in the editor, or the same decision as a command from the console. On yes it happens and is written down as having been done because you said so.

The console is worth looking at again, and /keystone assistant incident reads it for you. Nothing had kept a console line since the old code went, so the editor could not show you one and nothing could read one. The mod now keeps the last few hundred lines of console and of chat in memory — the log file on disk is still the whole of it — and the editor's Console screen is back, with chat beside it. Hand that stretch to the assistant and it says what looks wrong and which lines it is going by, which is the job nobody has time for at the moment it would help.

Nothing secret goes to the model. Every value your settings hold as a credential is removed from anything sent, wherever it turns up — including a console line printed months ago — as is anything shaped like a token or an address whoever wrote it. That is a layer everything passes through rather than a rule somebody has to remember.

A record of who changed what. The mod never had one. Every change made over the interface, and every refusal, is now written down with the key that made it and the person it was made for, kept for a year, readable at /v1/audit. What is never written is the body of a request: a log of those would be a second copy of every password this server has ever been told.

Load tests now say what each mod's things cost. Alongside "62% of the time over budget was this mod", a run counts how many things each mod has standing on the server — every id carries the name of the mod that registered it — and divides one by the other. 34% of the late time, 3,400 block entities, about 0.01% each is a number a pack maker can act on, and one a percentage on its own cannot give: a mod is a bigger share of a smaller pack without doing anything differently. It says out loud what it is: where time was spent rather than what caused it, over budget rather than in total, and an observation about that run.

An assistant could never write anything through the MCP interface. Tool names left the verb out, so reading the settings and changing them were both offered as a tool called settings — two entries with one name, and every call reached the first. Reads keep the plain name; everything else carries its verb.

A fixed leak: your Discord bot token was readable over the interface. Every setting a module declares was answered in full by GET /v1/settings, and the bot token is one of them — so any key holding settings.read, a scope that exists so somebody can be shown the configuration, could read a credential that takes over your bot. The comment printed beside that setting in your own config file had promised the opposite since the day it was written. Settings now have a kind for credentials: the schema says whether one is set and never what it is set to, the value stays in the file on your machine, and a test refuses to let the next credential we add be answered with. Nothing you have to do — but if that token has ever been given to a key you did not write yourself, roll it.

/keystone bots places marked test players on their own, without a measurement around them: hold a chunk loaded overnight, keep a farm ticking, see what one more player costs.

Restarts are back, and they decide for themselves whether they are worth it. Schedule them at times of day, on an interval, or once after start — and then a restart at six because it is six does not happen: if memory and ticks are healthy and the server has not been up long, it is skipped and the console says so. When it is worth doing and somebody is playing, it waits for the world to empty, up to a limit you set, then counts down on a boss bar and disconnects whoever is left. An empty server restarts immediately and in silence.

And the server comes back on a machine with nothing watching it. There is no /restart in Minecraft — a mod can only stop a server — so Keystone looks for whatever would start it again (systemd with a Restart=, a hosting panel, our own) and, finding nothing, arranges its own return before it stops. Never both, so nothing ever starts twice. The old mod called the equivalent of /stop and relied on somebody's hosting; on a plain machine that was a server that stayed down until a person noticed.

Underneath them is one timetable, and everything that happens on a clock will join it: one countdown wherever it comes from, no two heavy jobs on the same minute, one timeline of what is coming — /keystone operations, or GET /v1/operations — and one record of what ran. Run one by hand with /keystone operations restart 5m, call it off with cancel, and it takes exactly the same road as the clock, warnings and all.

A fix worth naming on its own: two modules can no longer strip each other's permissions off a command. Handing Brigadier two /keystone trees merges them and drops the second one's requirement, which is how 1.0.1's carried-over half left /keystone status open to every player. Trees are merged inside the mod now, before the game sees them, keeping every gate — and two modules that disagree about a permission are refused at start rather than quietly resolved.

Nobody is turned away with "Kicked by an operator." any more. Every screen a player can be shown — banned, banned until a date, kicked, maintenance, full, full because the last places are held back — is a line you write, with the reason, who did it, when it ends, how long that is from now, and where to appeal filled in. /keystone screens <name> shows one exactly as a player reads it. Bans are read from the server's own lists, so this works today, before Keystone has a ban system of its own.

Maintenance mode closes the server with a reason, disconnects everybody who is not on the list, and tells a new arrival the same thing in the same words. Reserved places keep the last few seats for whoever holds keystone.join.reserved — by turning an ordinary player away a little earlier, never by letting a rank past something. Nothing in either can admit somebody past a ban.

The entity cleaner merges before it deletes. Stacks of the same item lying near each other become one, which usually ends it there and costs a player nothing; only if that was not enough does a countdown start and anything get removed. Items, experience and spent arrows by default, never mobs, named and tamed and anything the game refuses to despawn protected, and /keystone clean says what a sweep would take before you trust it with anything. A server with nothing to clean is never counted down at.

The world backs itself up, once you say where to. Off until then, because where a backup goes is the one decision nobody else can make for you — put it on a different disk if you have one, since a copy beside the world survives exactly the failures that do not matter. A week of them costs about one. A backup is a whole world you can copy out and run — not a base plus a chain of changes to replay — and the files that did not change since the last one are shared with it instead of copied again, so seven days of a 5 GB world take about 7 GB rather than 35. Every world is written and flushed first and saving stays off until the copy is finished, because copying a live world means copying a region file in the middle of a write. Saving comes back on whatever happens, including when the backup fails: the alternative is a server that looks fine and loses everything on its next crash.

What is kept thins out instead of being cut off. Everything for a day, one a day for a week, one a week for three months — all three are settings. A backup does not start at all if it would leave the disk under the free space you asked for, which is the failure that otherwise takes the world down with it.

Putting one back happens at the only moment it can. A world cannot be swapped underneath a server that is playing, so asking for a restore writes down which one you want and the swap happens at the very start of the next boot, before the game opens the world. What it replaces is moved aside and never deleted — being right about which backup somebody wanted is not something to bet a world on.

Ids are chosen, not typed. The two lists the entity cleaner protects — what it never removes, and which items it never touches — were boxes where you wrote minecraft:boats, saw nothing go wrong, and found out the evening a boat was swept up. Your server already knows every id it loaded, vanilla and everything the modpack brought, so the editor searches that instead: type three letters, click the thing, and it is in the list with the mod it came from beside it. The search runs on the server, because minecraft:item alone is over a thousand entries before a pack adds anything.

An id whose mod is switched off today stays in the list rather than being quietly dropped. A pack changes underneath a settings file and that line comes back when the mod does.

A fix worth naming: saving a list in the editor used to ruin it. A list was handed to the page the way Java writes one — [minecraft:boat, minecraft:villager] — went into the box with its brackets, and came back through the comma split as [minecraft:boat and minecraft:villager]. Nobody typed anything wrong; the setting was broken by pressing save. The console never had this, because it has always written a list out as a list.

Announcements say things to the people who have not done them yet. The previous mod put a line in chat every thirty minutes in order, whether or not anybody was there, which is how a server teaches people to scroll past the one notice that mattered. Here a notice carries who it is for and where it lands — chat, the action bar or a title — the same one never comes up twice running, and nothing is said to an empty server. Three audiences today: everybody, people without a rank, and admins. A notice addressed to a condition nothing can answer is refused when the settings are read, with the list of the ones that exist.

And a notice can be addressed to somebody who has not voted today, or who has never linked their Discord. Those are the two most useful conditions there are, and until now neither existed — the module that says things could not ask the modules that know what a vote and a link are. Now it asks all of them once, after everything has started, and each answers for itself. A module you have switched off simply stops being a condition.

Both answer from memory rather than from the database. The notice is checked against everybody on the server each time it goes out, and a query per player would be a hundred database reads on the tick every half hour.

Everything the mod does is reachable from outside it, and that is not a side door. A local HTTP API on loopback, behind keys that carry scopes, with an OpenAPI document the mod writes out of its own routes so it cannot describe something that is not there. An event stream you can watch with three lines of EventSource. An MCP surface, so an assistant can be pointed at a server. A caller with no key is told the route does not exist, exactly as a made-up path is — there is no way to learn what exists by asking without one.

keystone-api is published for addons, with no dependencies at all: writing one costs a line in a build file and nothing on your classpath. A module somebody else wrote is a module in every sense — it declares itself, gets its own tables and its own settings file, registers commands and API routes, and is switched off the same way ours are. Modules talk through events and never by importing each other, which is what keeps one of them being off from breaking another.

Everybody on an offline-mode server was Steve, including the people who bought the game. A server in offline mode never asks Mojang anything, so it has no skin for anybody — and offline mode is exactly what a server runs when it wants players without a bought account. Keystone now looks a joining player's name up once and, when it belongs to a real account, puts that account's skin on them. /skin <name> borrows somebody else's, /skin <link> wears a picture of their own, and /skin reset goes back. A link has to be signed by something Mojang trusts before any client will load it, so that third one names the service it goes through and sends it the image and nothing else — no name, no address, no id. All of it applies without anybody reconnecting.

Discord is back, and this time it costs no libraries at all. The old bridge brought fifteen jars into every build — a Discord library, three JSON parsers, two HTTP stacks and four Kotlin standard libraries — each one named separately so that resolving them could not drag a second logger onto a classpath where the game insists on one. Discord is HTTPS, JSON and one WebSocket, and this mod had all three already. What is written is what the mod actually uses, and it queues, retries and reads Discord's own rate limits rather than waiting to be told off.

What that gives you today is chat, both ways. What is said in game appears on Discord under the player's own name and face rather than as a wall of lines from a bot, and what is said on Discord appears in game. A mention arrives as a name instead of eighteen digits, a custom emoji as its name, and **bold** as the word it was around — Minecraft's formatting has a reset but no memory, so putting markup back would drop the colour of everything after it. Going the other way, a * somebody types stays a *, and nothing anybody says in the game can ping a channel or a role. Joining and leaving are announced, and the channel's topic keeps who is online and how long the server has been up.

And the game and Discord can now be proven to be the same person. /discord link gives you a code, /link on Discord spends it, and from then on the server knows which Discord account belongs to which player. The direction is deliberate and does not reverse: on a server in offline mode anybody can connect wearing anybody's name, so a Discord command claiming to be a player proves nothing — a code that only ever appeared on one screen, in the game, does. Codes last minutes, work once, and have no letter in them that can be mistaken for another when somebody types it out.

A server can require that link before anybody gets in — and whoever is turned away reads their code on the disconnect screen itself, which is the screen they were already looking at. There is nothing to look up first and no chat to reach, which is what makes it usable rather than a chicken-and-egg. If the bot is not set up, the door stays open and the log says why, because a missing token should not lock a server out of itself.

None of it can slow the game down. Nothing here runs on the server thread: what happens in the world becomes a call in a queue that one thread drains in order, and a Discord that is unreachable fills that queue and then drops its oldest messages rather than growing without limit or making anybody wait. The rest of the bridge — roles, bans, the console, alerts and reports — is next.

Bans are Keystone's own now, and they end by themselves. Minecraft's ban list has no expiry worth the name, so "banned for a week" has always meant a ban plus something else remembering to lift it — a second answer to "is this person banned", kept somewhere else, by something that has to still be running in a week. There is one ledger instead: /keystone ban <player> [7d] <reason>, /keystone unban, /keystone bans, and /keystone history <player>, which is the one that matters the second time somebody is in trouble. Nothing is ever deleted from it — a lifted ban stays, with who lifted it. The server's own ban list still counts and is still read; it is an input, not the store.

And you can finally name somebody who is not standing in front of you. Until now every administrative command in the mod could only address a player who was online at that moment, which is exactly backwards for the one command that needs it most: people are banned after they leave, not while they are watching. The mod now remembers everybody it has seen — the account, the name, the first and last day — case-insensitively, following a rename, and saying so when two accounts have worn one name rather than picking between them quietly.

A banned player reads that they are banned, even during maintenance. Which of two modules happens to start first used to decide which screen somebody got; now a refusal can say it is the specific one and is asked before the general ones. The ban screens moved with the feature: they are in moderation.toml now, not operations.toml, because a ban screen sitting in the operations file tells a server owner that bans are configured there.

A ban can now be a ban in both places — and that is two switches, not one. ban.game-to-discord carries a ban in the game out to Discord; ban.discord-to-game carries one the other way. Turn on either, neither, or both — both is a ban that means it wherever it started. They are separate because the offences are: spamming a channel and griefing a build are not the same thing, and a server owner is entitled to treat the two places as two places.

Who may ban from Discord is a list you write, not Discord's own Ban Members permission. Moderating a chat and having power over a game server are different amounts of trust, and the first is usually handed to more people than the second. With the roles set, /ban <player> <reason> [for] and /unban <player> work at the bot, answer only to whoever typed them, and land in the same ledger as a ban typed in the console.

A mirrored ban does not bounce back. A ban carried out to Discord arrives back a second later as a Discord ban, and asking to ban somebody already banned is answered rather than written down twice. Closing the loop at the ledger rather than by remembering what we just sent is what makes it hold when two things happen at once.

The server's console can go to a Discord channel — for watching a machine you are not sitting at, where a crash at two in the morning is either something you see happen or something you find out about the next day. Batched a few seconds at a time, because a line per message is fifty messages a second on a starting server and Discord answers that with a rate limit that becomes a ban on the whole bot. A feed that falls behind drops its oldest lines rather than showing you the past. Nothing is tapped at all unless a channel is set: switching it off costs the server nothing, not even the hook.

And no password can ever end up in it. /login and streaming the console arrived in the same week and each is perfectly reasonable alone; together they would put the first password anybody types into a channel the whole team reads, and nobody would have noticed, because neither feature is wrong. So masking happens once, in the core, for every surface that writes a line down — the console feed, the log, and whatever audit trail comes next. The command still shows, only its arguments go: /login *** still says somebody logged in, which is what a record is for. Keys this mod issued are masked wherever they turn up, including inside a stack trace, and a server can add the command another mod uses.

The server says when it is struggling, and when it is fine again. Ticks under a number you choose, memory over a percentage you choose — announced in a Discord channel, with a role pinged if you name one. Three rules keep it from becoming the channel everybody mutes: a reading has to last before anybody is told, the same thing is not said twice inside a quiet period, and coming back is announced too, because an alert that is never followed by "it is fine now" leaves somebody wondering on the drive home and unread the second time.

A rank and a Discord role can follow each other — and not symmetrically. Going out, Discord follows the game: a rank given or ended puts the role on or takes it off, including when it ends on its own date. Coming in, a role is a grant like any other — written into the same ledger every other rank lives in, with Discord named as its source, and removed when the role is.

That asymmetry is the whole point. The obvious version, where an added role simply means "they have the rank now", breaks the first time somebody's VIP expires: the rank ends, the role comes off, a moderator puts it back a month later, and that player has VIP forever, off the ledger, invisible to the command that lists what somebody holds. And a rank already held from a shop or a quest is never overwritten, because a second entry from a second source would outlive the first one's expiry date.

A server in offline mode can ask who somebody is. /register once, /login after, /password to change it — and until somebody has said who they are they stay exactly where they joined, cannot be hurt, and nothing they say reaches anybody. Held rather than moved to a lobby, because a lobby is a second world to keep and a teleport is a place to remember and get wrong after a crash.

It is off unless you switch it on, and it stays off on a server that checks with Mojang — asking there is asking twice, and it says so in the log rather than silently doing nothing. Passwords are PBKDF2-HMAC-SHA512 with a salt each and a cost you can raise later: the cost is stored beside each password, so raising it locks nobody out and old ones are quietly rewritten the next time they are typed. Coming back from the same place inside a day does not mean typing it again; from a different one it does.

The password rule is length and nothing else. Asking for a capital and a digit is how every server that has ever asked ends up full of Password1.

No IP address is written down by this mod, anywhere. Not in the login record, not in the message asking whether it was you, not beside a vote. An address exists while somebody is connecting and becomes a place: a one-way token under a key your server makes for itself and keeps in its own folder. The same machine reads as the same place, which is the only thing anything asked of it, and nobody — us included — can turn a place back into an address.

It is also the better version. Somebody is joining as you, from 89.24.1.14 asks a player to recognise four numbers about their own house; from somewhere you have never connected from is the same warning in a form they can actually answer. Your server learns a place only when somebody has proved they are themselves, so an attempt that was not them never teaches it anything.

/ban, /pardon, /kick and /banlist now do what Keystone does. The command a moderator's fingers already know has to be the one that works — the alternative is two ban lists and somebody who is right about which one they used only most of the time. A ban typed as /ban lands in the ledger, ends on its own date and shows the screen you wrote, and /ban somebody 7d spam works, which vanilla has never been able to do. Switchable off, for a server that would rather keep the game's own.

A kick is written down too. It has nothing to expire and stands against nobody, but "how many times has this person been kicked this week" is what decides whether the next one is a ban, and a kick nobody wrote down cannot answer it.

And a held player can no longer run commands. Somebody frozen at spawn who could not speak could still run every command every other mod on the server added, as whoever they claimed to be. The list of what works before you have said who you are is short and closed — anything not on it is refused, rather than a list of things to block that the next mod adds one more to.

You can have a server that asks who you are without anybody having a password to forget. Somebody linked to a Discord account gets a message the moment anybody joins under their name — who, where from, and two buttons. Press yes and you are in. Press no and that connection is closed, the attempt is written down, and the name is held for a quarter of an hour so it cannot simply be tried again.

The thing worth being exact about is why this works at all, because it looks backwards: being linked does not prove who you are. An offline server derives a player's id from their name, so somebody typing your name arrives holding your id and therefore your link, and nothing about the link tells the two of you apart. What tells you apart is that the message goes to the person who owns the Discord account. The link is not the proof — it is the address the question is sent to.

It falls back the way it should. Direct messages closed? The question can go to a channel you name, and there it carries no address at all, because a channel is not a private message; only the person it is about can press the button. No bot, or not linked? A password, exactly as before. Nothing to reach them with and no password either? They are told so, rather than left frozen for two minutes waiting for a message that is never coming.

A password is remembered for a day and a Discord confirmation is not, which is deliberate rather than an oversight. Typing a password every evening is a tax; pressing one button is not — and half of what makes this worth having is that a login you did not make reaches you every time, not only the first time this week.

A name that belongs to somebody who bought the game cannot be worn here by anybody else. Off by default, because it is a decision about who your server is for. Switched on, Keystone asks Mojang once whether a name is taken and refuses it to anybody who has not already set a password here — which closes the actual hole in offline mode rather than a theoretical one: without it, "offline" means anybody can walk in wearing anybody's name, yours included.

There is one way out and it is a person, not a setting: /keystone accounts allow <name> lets one real owner through once, and the permit is spent the moment they set a password, because from then on the password is the better proof. Without that, protecting the name would also lock out the one person it belongs to.

And Mojang being down never turns into a name being free. Those two answers look identical to anything that returns "nothing" and they are not remotely the same: one means a stranger may wear that name, the other means nobody can say. Keystone keeps them apart, answers from what it already learned about a name when Mojang cannot be reached, and lets people in rather than out when it truly does not know — a mod that fails closed here is a mod that turns somebody else's outage into your server being shut.

/keystone accounts attempts <name> says who has been refused at that door and when. It is deliberately not in the punishment ledger: on an offline server a name is an id, so six attempts at impersonating somebody would have appeared as six marks against the person they were aimed at.

Ranks people earn by playing are back, and an hour is no longer the only thing you can earn one with. Write the promotions as lines and the server hands them over the moment somebody has done enough:

vip      -> played >= 24h
explorer -> visited >= 20, walked >= 100000
hunter   -> killed:minecraft:zombie >= 500, killed >= 2000

Commas mean and; two lines pointing at the same rank mean or. Everything counted is countable in a condition — time, distance, deaths, chat lines, commands, and every mob, block and biome by its own id, so killed:alexsmobs:bunfungus works on a modded server without Keystone having heard of the mod. A line that does not make sense is named in the log at start and skipped, never guessed at.

Two clocks, and the server decides which one a rank reads. One counts time connected and the other counts time not standing still, and both are always kept — so turning the setting off does not quietly rewrite what everybody has earned, it only changes what the number is used for. Out of the box an hour is an hour however it was spent; a server that does not want Veteran earned by a laptop left on overnight turns count-idle-time off and gets the second clock.

A promotion is a grant like any other, written into the same ledger as a bought rank with its own reason on it. Which matters at the other end: when a shop subscription lapses and takes its rank back, the one somebody spent forty hours earning is a different row and stays exactly where it is.

/playtime says how long you have been here, how much of it you were moving, how far you have walked, how many places you have been, and what you are closest to earning next, as a percentage. /playtime <somebody> answers for anybody the server has ever seen, online or not. /keystone progress <player> adds the breakdown — what they have actually been fighting and mining — and /keystone progress top <counter> is the board for any of it.

Nothing here writes to the database on the tick. Counting happens in memory, reaches the table every thirty seconds and when somebody leaves, and a crash costs whoever was on the last half minute of their evening. A mod that inserts a row per block broken is the mod a server owner removes in the second week.

And the server can now be asked what is in the modpack. GET /v1/catalog lists the registries this server has loaded, what is in any one of them, and the tags over it — every entry with its id, the mod it came from, its translation key and the best name a dedicated server can give it. Paged and searchable, because a big pack has tens of thousands of items. It is what turns "which mob does this rank need" into something you click in the editor rather than type, and it is the same reading STYGION Workbench needs of a pack it did not build.

The tab list says something now. A header and a footer the server writes, with the numbers filled in — {online}, {max}, {tps}, {player}, {rank}, {world} — and the rank's own prefix in front of each name. It is where a server's address, its Discord or what is running this week belongs, and there is no layout imposed on it: the first owner who wants their own line there is right.

And the list is sorted by rank where Minecraft has a notion of order — from 1.21.2 on, the client sorts by a number the server sends, so the rank's weight is that number and the list reads top down. On 1.21.1 there is no honest way to do it: the only trick available is scoreboard teams, which takes over name colours, nameplate visibility and friendly fire from every other mod using them. So there the list keeps its usual order, the ranks are still in front of the names, and this is said out loud rather than half-done.

The columns, the faces and the badges in the mod's own type still need the client mod and are not imitated here (S-03).

Tickets are back, and this time the ticket is not a Discord channel. It is a row in the mod, and Discord, the game and the API are three windows onto it. Which means a server with no bot has working tickets, a deleted channel is not a deleted ticket, and "what did we agree in March" does not depend on a chat server's retention.

What somebody can open is yours to write, and each kind has a team:

help   -> support
bug    -> devs
report -> moderation

The group is one of this server's own permission groups, and the ticket goes to whoever in it is carrying the fewest open ones. A report of griefing is then not in the same queue as "how do I make a piston", and nobody has to sort them by hand at two in the morning — which is when reports arrive.

An answer reaches the person who asked, in chat when they are here and the moment they next join when they are not. A reply nobody sees is a ticket that gets opened twice. /ticket shows the whole conversation, /ticket reply adds to it, and the team has /tickets for the queue, /tickets take, /tickets reply and /tickets close.

With a bot, each ticket gets its own channel that only its team and the person who opened it can see, carrying what is said in both directions. When the ticket closes the channel is deleted rather than archived: the conversation is already kept in the mod, and an archive is a thousand dead channels in a year against a limit Discord actually enforces.

A limit per person and a short wait between two, so the queue cannot be flooded — and somebody else's ticket answers exactly like one that does not exist, so nobody can count the queue by guessing numbers.

Vote rewards are back, and both protocols are spoken at once. Vote sites disagree about which one they use — the modern signed one and the original encrypted one — and supporting one of them is a server whose rewards work on some of the places it is listed, with the owner finding out from a player rather than from a log. Keystone answers both on the same port. The token and the key pair are made the first time it starts rather than being a setup step somebody gets wrong at midnight, and /keystone votes keys prints what a site asks for.

The port stays shut until you write an address, because it is the only thing Keystone ever opens to the world; everything else here listens on loopback or reaches outward. And nothing unsigned gets through it: a payload has to carry a signature this server's token produces and answer the challenge this server just sent, or decrypt with this server's own key. The mod this replaces accepted plain text on that port, which made a vote reward something anybody who could reach it could hand themselves, under any name they liked.

A vote that arrives while somebody is away is kept and paid when they next join — once, and in one line rather than ten. That is most votes: people vote from a phone, from work, from a list of sites at breakfast, and paying only whoever happens to be standing in the world is how this feature earns its reputation for not working.

What a vote is worth is lines you write:

give {player} minecraft:diamond 3
perk vote-fly for 1d
rank vip for 7d

A plain line is a command run as the console, which covers anything any mod on the server can do. perk and rank go into the same ledger as a bought rank instead — so for 7d ends by itself, and nobody has to remember to take it back.

And voting is something people talk each other into. A streak counts the days in a row and can pay for reaching one; a vote party is a goal the whole server counts towards, and when it lands everybody online gets something. Neither is decoration: a vote reward on its own is collected once and forgotten. The streak survives today not having a vote in it yet, rather than dropping to zero every midnight and climbing back an hour later.

/vote says where to vote, how many times you have, and how the party is doing. /keystone votes says what has arrived, what is still owed, and what this server tells the sites. It is all on the API too.

Two threads writing to the database at once could quietly write half of each. Everything shares one connection and a transaction on it is three operations in a row — off, write, commit — so a second thread committing in the middle of the first ended its transaction early and wrote the half that existed. Nothing threw and nothing was logged. It is serialised now, and a test with eight writers at once fails without the fix.

A fix worth naming: the local API was throwing away everything in a link. Anything asked over a GET — which page of a list, which counter a board is for — was read from the request body, and a GET has no body. So a route that documented parameters answered as though none had been sent: ask for one registry, get the list of all of them. That is worse than an error, because it is a valid answer to a different question, and nothing failed to make it visible. The address is read now, and a test pins it.

All four builds are downloadable now, on Fabric and NeoForge, for 1.21.1 and 26.2. Only NeoForge 1.21.1 went out before, because the other three would have been the mod with none of those features in it; they hold the same thing again, and every one of them boots a real dedicated server in the release run before it is published.