Practice Core
The full guide
Everything needed to install, configure and run Practice Core — readable in full before you spend anything.
This is the guide that ships inside the product, word for word — the file README.md in your archive. It is kept in English so that what you read here is exactly what you receive.
1. Install
- Download
PracticeCore.jarfrom your client area at boteramc.com/licence. That copy is - Drop
PracticeCore.jarintoplugins/. - Start the server.
- Read the two lines it prints. On a fresh install they say what is missing and what to type:
built for you and carries your buyer id — download it again rather than passing a copy around, because a shared file names the account it came from. It runs on any server your licence covers.
[PracticeCore] No arenas.yml file: no arena is defined. Use /practice arena create <id> in game to make one.
[PracticeCore] No kits.yml file: no kit is defined. Run /practice kit preset all in game for the
bundled kits, or gear up and run /practice kit save <id> to make your own.
[PracticeCore] Practice Core 1.0.0 on v1_8_R3: 0 arena(s), 0 kit(s), 0 rating(s) loaded.
The plugin refuses to start rather than run half-configured: a wrong value in config.yml, an unknown sound name, a language file that does not exist. It always says which key, what it read and what it expected — and it reports every problem at once, so you fix them in one pass instead of one restart per mistake.
It also refuses to start on a server that is not 1.8.8/1.8.9, and says so. Nothing is loaded and no file is touched.
1b. Your licence
Paste the key from your client area at boteramc.com/licence into the plugin configuration:
licence: "BOTERA1.xxxxxxxx.xxxxxxxx"
It may be pasted with or without line breaks — a key copied out of an email works as is.
The plugin contacts one address, and here is exactly what happens. At startup and then every twelve hours, it asks boteramc.com whether the licence is still valid. It sends the key, a random number, the plugin version, and hashed fingerprints of the machine. The raw values never leave your server: they are hashed with HMAC-SHA256 before anything is sent. No IP address, no server name, no player data, nothing about your game.
If that server does not answer, nothing happens for fifteen days. Your licence is signed and verifies offline; our server can only withdraw a right, never be the condition of your startup. An outage on our side does not stop a server that paid.
Nothing is ever cut mid-match. If we revoke a licence while your server is running, the plugin says so in the console and keeps working until the next restart.
How many machines. Your licence covers a number of machines, shown in your client area — at least two by default, because almost everyone runs a test server next to the live one. A machine is recognised across ordinary hardware changes: replacing a network card does not consume a new slot. If you move to another host, release the old machine in your client area; the new one registers itself at the next startup.
If it refuses. The message says which of these it is, and what to do: no key, a key for another product, an expired licence, a revoked one, too many machines, or our server unreachable past the grace window. It is never just "invalid licence".
2. Your first five minutes
In game, as an operator:
/practice kit preset all # the 14 bundled kits, ready to play
/practice arena create ring # stand where player 1 starts, then run it again for player 2
/practice arena bounds ring # one corner, then the other (or give the six numbers)
/practice # the menu: left click = unranked, right click = ranked
That is the whole setup. Everything else is optional.
Arenas. create is run twice on purpose: the first call records the first spawn and says the arena is not usable yet, the second records the second spawn. bounds is only needed for kits that allow building (Bridge, MLG Rush, Build UHC, Spleef) — it tells the plugin what to put back after a round. Restoring is done by block delta, without WorldEdit.
Several layouts per kit. /practice layout nodebuff arranges the kit; /practice layout save keeps it as the one you play. save <name> keeps it under a name and switches to it, names <kit> lists what you have, and use <kit> <name> switches between them between rounds — a rushing layout and a defensive one on the same kit.
Upgrading an existing server keeps every layout already saved: they become the one named default, still selected. Nothing to rename, nothing to redo.
Each match sees only itself. Two arena copies sit 16 blocks apart and a view distance is typically 64, so without this a duel would see the seven duels around it — figures running across the screen, arrows crossing it, and on a Sumo kit no way to tell which one is your opponent. Players in a match see only the people in that match; the lobby sees only the lobby. Set hud.hide-other-matches: false if you would rather let people watch the arenas from the lobby.
More arenas, and when to make them. /practice arena clone ring 8 copies a finished arena eight times and registers each copy as its own arena, so eight duels can run at once on it. The copy is done a slice of blocks per tick, so the server stays playable while it runs. Measured on eight copies of a 2 574-block arena: the worst single slice took 10 ms, against the 50 ms a tick has. You can watch this yourself with /practice perf, under arena.clone.
The one cost it cannot split is the terrain itself. On ground no player has ever visited, in a world with normal terrain, generating a single chunk costs 9 ms on average and up to 36 ms — most of a tick — and nothing can cut that in half. Cloning pays one of those per tick, which is the best any plugin can promise. /practice arena prepare ring 8 moves it instead: run it with the same numbers you will clone with, at a quiet hour, and the terrain is generated then. Measured on a normal-terrain world, the clone that follows costs 0,03 ms per chunk instead of 9,1 — 304 times less. It writes no block and creates no arena; run it twice and the second run costs nothing.
If your arena world is superflat or void, a chunk costs well under a millisecond and this command buys you almost nothing. That is worth knowing before you schedule it: the gain is real, and it belongs to worlds with terrain.
One terrain job at a time: a prepare started while a clone is running is refused, and the other way round, because two of them together would put two chunk generations in the same tick. That is also why /practice arena prepare stop exists — a preparation never disturbs anyone, but it does hold that slot, so a wrong number would leave you unable to clone until it finished. Stopping keeps every chunk already generated. There is no equivalent for cloning on purpose: a clone stopped half-way would leave an arena that looks ready and is not.
Events: nine formats across every kit. /practice event open with no kit opens a window — pick a format, then a kit. Or type it: event open sumo lms.
| format | what changes | needs |
|---|---|---|
bracket | a knockout tournament in 1v1s | — |
lms | everyone in one arena, last one alive wins | — |
oitc | one hit eliminates; health does not save you | — |
tnt | the bomb goes round and goes off after event.tnt-fuse-seconds — hit someone to pass it, and you cannot pass it straight back | — |
juggernaut | one player takes event.juggernaut-hits hits to fell and eliminates in one; whoever fells them takes the title | — |
koth | hold the hill, alone, for event.koth-seconds seconds. Seconds are cumulative: being pushed off costs you the time you were off, nothing more | a hill region |
thimble | jump into the pool; the square you land on fills in. Miss the pool, or land on a taken square, and you are out | a pool region |
parkour | first to the finish wins. Falling sends you back to the start instead of eliminating you | a finish region |
corners | four corners, one of them disappears every event.corners-round-seconds seconds. Do not be standing in it | corner1…corner4 |
The last four need a named region, which you draw the same way as a goal — stand at one corner, type the command, walk to the other, type it again:
/practice arena region ring hill
/practice arena region ring (lists what that arena already has)
/practice arena region ring hill clear (removes it)
Draw the region where players stand, not just the floor. A region is a box, and a player is inside it when the block their feet are in is inside it. A corner drawn as one flat layer of floor has nobody in it — they are standing one block above. Take the lower corner at floor level and the upper corner two or three blocks up, and it behaves the way you expect.
A format whose region is missing says so at the start of the round and names the one it wants, rather than running five minutes that nobody can win. Blocks a mode removes — a corner going, a pool square filling in — are put back when the round ends, exactly like blocks a player places.
Five formats across sixteen kits is eighty events, all played by code that is already proven — instead of hand-written game modes each with its own match lifecycle to maintain and to get wrong. Every format but bracket takes the same kits a free-for-all does: won by death or by falling out.
What the last four ask of you: a named region, drawn once, and then reused by every event you run on that arena. That is the whole cost. Parkour has no checkpoints — a course with checkpoints needs an ordered list of regions, and an arena holds a named table, not a list; adding an order for one mode would be exactly the promise that cannot be built, which is the complaint this plugin was written against.
Where players end up afterwards. Exactly where they were before the match — same position, same health, same game mode, same inventory, to the block. This plugin does not own a lobby, does not teleport anyone to a spawn point of its own, and has no lobby setting: whatever hub or spawn plugin you already run keeps working, and the two never disagree about where a player belongs. A match here is a round trip.
Watching the last round again. /practice replay puts silhouettes wearing the players' skins back in the arena and walks them through the round you just played, for you alone. It is movement only — not hits, not items, not effects. That is what gets argued about after a round ("where were you when I hit you"), and calling it anything more would be promising a video recording that does not exist.
One round is kept per player, the last one, capped and dropped when they disconnect. A replay feature that records without a limit does not show up in testing; it shows up three days later when the heap is full.
Duels only — free-for-alls and events are not recorded. Six silhouettes retracing a melee would be unreadable, and saying so is cheaper than letting a player find out by typing the command after one.
A leaderboard people read without typing anything. /practice hologram add nodebuff, standing where you want it, puts the top ten of that kit in the air at your feet — name, rating and tier, refreshed every minute. remove takes it away and list shows what you have placed.
What is written to disk is the point, never the entities. The floating lines are placed at start-up and erased at shutdown, and every placement first sweeps the area for lines of ours. That is deliberate: a plugin that saves entity ids drifts from the world on the first hard stop, and six months later the spawn carries a hundred invisible armour stands nobody knows how to remove.
Groups that do not need a second group. party split draws two teams out of the party and plays them against each other — it needs an even number. party ffa needs nothing: three players, five, eight, everyone fights for themselves and the last one standing wins. Everyone is told their place, not only the winner's name, and the party is not disbanded — the same players start again with one command.
A free-for-all takes kits won by death or by falling out (Sumo, Spleef, Stick Fight, Fireball, and every ordinary kit). Kits won on hits, goals or beds are refused by name: counting hits between six players decides nobody, and a goal belongs to a side when there are six. Free-for-alls are not rated, on purpose — a rating is computed between two sides, and a wrong rating is worse than none.
Kits. kit save <id> records your current inventory — gear up, test, save. It is the flow every 1.8 server actually uses. Re-saving a kit keeps its knockback profile and win condition: you spend hours tuning a profile and one second redoing an inventory.
Ids. A kit or arena id is 1 to 32 characters from a-z, 0-9, - and _. Anything else is refused with the reason, because an id becomes a key in a .yml file and a dot, a space or a colour code does not read back the way it was written.
3. Commands
Everything lives under /practice (aliases /pr, /duel). /practice help lists them in game.
| command | who | what | ||||||
|---|---|---|---|---|---|---|---|---|
/practice | everyone | opens the menu | ||||||
/practice queue <kit> [ranked] | everyone | join a queue | ||||||
/practice leave | everyone | leave a queue, a duel, or spectating | ||||||
/practice duel <player> [kit] [bo3] [arena] | everyone | challenge someone directly, optionally on a named arena | ||||||
/practice accept <player> | everyone | accept a challenge | ||||||
/practice rematch [bo3] | everyone | replay the last opponent, same kit | ||||||
| `/practice party [invite\ | accept\ | kick\ | leave\ | disband]` | everyone | parties, for 2v2 and 3v3 | ||
/practice party chat <message> | everyone | say something to your party only | ||||||
/practice party split [kit] | everyone | the party plays against itself, teams drawn at random | ||||||
/practice party ffa [kit] | everyone | free-for-all: everyone against everyone, last one standing | ||||||
/practice spectate <player> | everyone | watch a duel | ||||||
| `/practice bot <kit> [easy\ | normal\ | hard]` | everyone | train against a bot, on the spot. Not ranked | ||||
/practice bot stop | everyone | end the training and get your inventory back | ||||||
/practice layout <kit> | everyone | arrange a kit your own way | ||||||
| `/practice layout save <name>\ | use <kit> <name>\ | names <kit>` | everyone | several layouts per kit, switch between them | ||||
/practice inv [player] | everyone | the inventories at the end of the last round | ||||||
/practice history | everyone | your recent ranked matches | ||||||
/practice replay [stop] | everyone | watch your last round again — movement only | ||||||
/practice top [kit] | everyone | the leaderboards — in a window, or as text for one kit | ||||||
/practice kit preview <kit> | everyone | see what a kit gives before queueing for it | ||||||
/practice stats [player] | everyone | rating, tier, wins, losses and streaks, per kit | ||||||
/practice lang <code> | everyone | pick your language | ||||||
/practice status | everyone | arenas, kits, duels, ratings | ||||||
| `/practice arena create\ | bounds\ | goal\ | region\ | tp\ | clone\ | prepare` | practice.admin | build and check arenas |
| `/practice hologram <add\ | remove\ | list> [kit]` | practice.admin | a leaderboard standing in the world | ||||
| `/practice kit save\ | preset\ | win\ | mode` | practice.admin | create and tune kits | |||
| `/practice kb <kit> [set …\ | reset]` | practice.admin | knockback profile, measurable | |||||
/practice season [reset <name>] | everyone / practice.admin | the ranked season, and opening a new one | ||||||
/practice config | practice.admin | every setting actually in force | ||||||
/practice perf [reset] | practice.admin | cost per operation, on the main thread | ||||||
/practice selftest [full] | practice.admin | self-diagnosis: what is broken and why | ||||||
/practice integrity [pairs] | practice.admin | what the competitive watch has seen | ||||||
/practice admin | practice.admin | the administration menu | ||||||
/practice event open [kit] [format] | practice.event | a tournament or a mini-game — the five formats are in section 2; with no kit, a window opens | ||||||
| `/practice event [start\ | join\ | leave\ | info]` | practice.event | run and join it |
French verbs work too (/practice langue, /practice revanche, /practice tournoi…).
Permissions
| node | default | grants |
|---|---|---|
practice.admin | op | creating arenas, saving kits, settings, diagnosis |
practice.event | op | opening and cancelling a tournament |
practice.priority | false | served before other players of equal waiting time; never bends the rating or ping rules |
Everything else is open to every player. A player without practice.admin is refused on all twelve administration paths, and nothing changes when they try — that is checked in game on every release, by connecting an ordinary player and reading the arena and kit counts before and after.
3b. Training against a bot
/practice bot nodebuff hard
/practice bot stop
A sparring partner that never gets bored. It appears three blocks in front of you, wearing the same kit you are given, and it fights where you stand -- no queue, no arena reserved. That last part matters on a busy server: you can practise a kit while all eight arenas are taken, and your practice never takes a slot a real duel was waiting for.
It is a player, not a monster. The bot is a real player entity as far as the server is concerned, so it has a player's hitbox, a player's reach, and it takes the knockback profile of the kit through exactly the same code a human does. Nothing is approximated. That is deliberate: a training partner with a mob's hitbox teaches reflexes that do not transfer, which is worse than no bot at all.
Three difficulties, and they are cadences rather than adjectives:
| difficulty | strikes every | that is |
|---|---|---|
easy | 12 ticks | 1,7 hits per second |
normal | 9 ticks | 2,2 hits per second |
hard | 6 ticks | 3,3 hits per second |
It backs off when it gets low instead of walking into your sword, and it circles between hits instead of standing still.
Nothing is recorded. No ELO moves, no match is archived, no leaderboard changes. A rating won against a machine would say nothing about anybody. Your inventory comes back through the same snapshot a duel uses -- not a separate path -- so ending a training, dying, disconnecting or a server restart all give you your things back.
4. Configuration
config.yml is commented key by key and never rewritten by the plugin. Edit it, run /reload or restart, and /practice config shows what is actually in force.
The parts most servers touch:
preset—competitive,casualortournament: a set of settings that hold together. Any keyduel.countdown-seconds,duel.max-duration-seconds— the two that change how a round feels.matchmaking.*— the rating window, how fast it widens, and the ping gap refused between twofeedback.*— the sounds, the titles and the player-list band. Each sound is one linehud.duel-lines,hud.queue-lines,hud.lobby-lines— **which lines the sidebar shows, and inranking.divisions— the tiers, on one line, from the lowest to the highestintegrity.*— invitation limits, the wait after repeated abandons, and the report. **Nothing in
below overrides it.
opponents. That last one matters in 1.8.
("NOTE_PLING 1.0 2.0", or "none"); an unknown name stops the server and suggests the closest matches rather than playing nothing in silence.
which order**. Write the ids you want, blank for an empty line. An id this sidebar does not know stops the server and lists the ones that exist. A line with nothing to say right now disappears on its own.
(Bronze:0, Silver:1050, ...). Rename them, add them, remove them: a rating is a number nobody says out loud, a tier is an identity.
this section bans, removes points or marks an account**: the plugin prevents what is certainly harmful, makes boosting pointless rather than punishing it, and only shows what it cannot prove.
Language files (messages_en.yml, messages_fr.yml) are created on first start and never rewritten. Translate them freely; a key you delete falls back to the one inside the jar. Drop a messages_es.yml next to them and it is loaded at startup and offered in /practice lang. A partial translation is accepted — what is missing shows in English and the log tells you how much.
Both ways of using an added language are tested against a real 1.8.9 client before release: a player picking it with /practice lang es on an English server, and the server itself running on it (language: es in config.yml). In both, the translated sentences arrive translated with their parameters substituted, everything else comes back in English, and no key name or unsubstituted {0} ever reaches the player. The log says how much is missing -- "Added language 'es' loaded from messages_es.yml (3/619 texts; the rest falls back to en)" -- because a translation that is 0.5% done and believed finished is the only way to send English to players who do not expect it.
4b. Storage: SQLite, MySQL, MariaDB or PostgreSQL
The default needs nothing installed. Out of the box the plugin keeps ratings, owed inventories and saved kit layouts in a single SQLite file in its own folder. No database to set up, no password to hold, no service to watch, no OS requirement. That is deliberate, and it is the shape of the promise: no mandatory external dependency.
The other three engines are a choice, never a requirement. Pick one if you already run a database and would rather the practice data lived in it -- for the backups you already take, or the dashboard you already have.
storage:
type: sqlite # sqlite | mysql | mariadb | postgresql
host: localhost
port: 0 # 0 = the engine's own default (3306 / 5432)
database: practice
user: practice
password: ""
ssl: false
Drivers, measured rather than assumed. The Spigot 1.8.8 jar was opened and its contents counted:
| Engine | Driver | What you must do |
|---|---|---|
sqlite | shipped by Spigot | nothing |
mysql | shipped by Spigot (Connector/J 5.1) | nothing |
mariadb | the same driver -- MariaDB speaks the MySQL protocol | nothing |
postgresql | not shipped | put the driver jar in plugins/PracticeCore/drivers/ |
This plugin never downloads anything. The only network call it makes is the licence check described in section 1b, to one address and nowhere else. If a driver is missing it says so, names the folder, and refuses to start rather than running without persistence.
One caveat, written rather than left to be discovered. The driver Spigot ships is Connector/J 5.1, which cannot authenticate against a MySQL 8 account using caching_sha2_password -- the MySQL 8 default. Either create the account with mysql_native_password, or drop a Connector/J 8 jar in that same drivers/ folder.
And the refusal says it, in those words. This was checked against a real MySQL 8 rather than assumed, and the first attempt found the guide wrong: the plugin used to print the driver's surface message and send you to check the host, the port, the database and the account -- all four of which were correct. What it prints now is:
> the account uses an authentication method the driver cannot negotiate. The driver that ships with > Spigot 1.8.8 is Connector/J 5.1, and MySQL 8 creates accounts with caching_sha2_password by > default. Two ways out, either is fine: run ALTER USER 'you'@'your-host' IDENTIFIED WITH > mysql_native_password BY '<password>'; on your database, or put a Connector/J 8 jar in > plugins/PracticeCore/drivers/. Nothing else is misconfigured: the host, the port, the > database and the password are being accepted up to this point.
The same treatment covers a wrong password, a database that does not exist and a server that does not answer -- each says which of the four it is, so you do not check the other three.
What is verified, and what is not. Three levels, and it is worth knowing which is which.
| Engine | Verified how |
|---|---|
sqlite | every test campaign runs on it, plus the clean-room install |
postgresql | a live PostgreSQL 16.15 server: the shipped archive unzipped on an empty server, the driver dropped in drivers/, the sixteen kits created, an arena built, a ranked duel played and decided, the ladder written and read back |
mariadb | two live MariaDB-backed servers behind a Velocity proxy: a cross-server ranked duel, the shared ladder read from the other server (see 4c) |
mysql | a live MySQL 8.0.46 server, both ways: an account in mysql_native_password (the whole buyer path, seven checks, no failure) and an account in caching_sha2_password (the refusal below, verified word for word) |
The PostgreSQL run used pgjdbc 42.7.2; the MySQL run used the driver Spigot ships. All four engines have now carried a real ranked duel. No claim in this document rests on an engine nobody has run.
Switching engines does not move your data: the plugin creates its schema in whichever database you point it at, and starts empty there. Export and import are your database's job, not this plugin's.
4c. Several servers, one practice
Off by default. A single server never reads any of this and pays nothing for it -- no polling, no heartbeat, no table touched.
Turned on, ratings, kit layouts and owed inventories already follow the player from server to server: they live in the shared database, and that is all it takes. What this adds is a common queue. A player on arena-1 is matched with a player on arena-2, and one of them is sent across by your proxy.
The fight never crosses. The two players end up on one server and the duel runs there exactly like a local one -- same code, same tests, same cost. Making two players fight through a network would mean synchronising positions tick by tick; that is a different product, and it would be unplayable in 1.8.
network:
enabled: true
server-id: "arena-1" # exactly the name your proxy knows this server by
poll-seconds: 2
ladder-refresh-seconds: 20
ladder-refresh-seconds is how often this server re-reads the ladder written by the others, so leaderboards and /practice stats show what happened elsewhere without a restart. It only affects what is displayed: the points of a duel are always computed from the shared database at the moment the duel ends, never from a local snapshot. Lower it if you want fresher leaderboards, raise it if your database is far away.
Two things are refused at startup, with the reason, rather than left to be discovered:
server-idempty. It is the name used to send players across, so a wrong one sends them nowhere.storage.type: sqlite. A SQLite database is a local file: two servers cannot share it, so there
would be no common queue at all -- and nothing would look broken, the queue would simply stay empty forever.
Nothing else to install. No Redis, no message bus, no plugin on the proxy. The coordination goes through the database you already configured, and the transfer uses the BungeeCord channel that BungeeCord and Velocity both listen on out of the box.
What happens when something falls over -- on a network that is the normal case, not the exception:
| what breaks | what happens |
|---|---|
| the travelling player never arrives | their appointment expires; the host stops waiting |
| the host server dies mid-pairing | the claim expires; both players go back in the queue |
| a server disappears | it stops checking in, and the others stop counting it |
None of it needs a hand. /practice status lists every server that is alive, how many players it holds and how long ago it last checked in -- which is what you want when a queue is not producing matches.
This has been run, not just written. Two Spigot servers with distinct server-ids, one shared MariaDB, one Velocity proxy in legacy forwarding mode. A player queued on arena-2 while their opponent queued on arena-1; the one who had waited least was told where they were going, the proxy moved them -- confirmed in the server's own connection log, not in the plugin's word for it -- the duel started on arena-1, was fought and decided, and the result was then read back from arena-2's leaderboard. The script that builds that rig ships in qa/network/.
The limits, plainly. Cross-server covers 1v1 queues. Parties, team queues (2v2, 3v3) and events stay on one server: they are not shared, and a party cannot span servers. Two players who queue on the same server are matched there without anyone travelling.
5. PlaceholderAPI (optional)
If PlaceholderAPI is installed, Practice Core registers a practice expansion at startup and the placeholders below resolve anywhere PlaceholderAPI is read — tab list, chat format, holograms, scoreboards from another plugin. If it is not installed, nothing changes and nothing is logged: Practice Core does not depend on it, does not ship any of its classes, and never asks for it. A server without PlaceholderAPI is the normal case.
/papi info practice lists them in game. Every token below is checked by a test that asks the plugin to resolve it, so this list cannot drift away from what actually answers.
About the player
| Placeholder | Answers |
|---|---|
%practice_state% | lobby, queue, duel, melee or spectating |
%practice_in_match% | true while fighting a duel or a melee, false otherwise |
%practice_kit% | the kit being played or queued for, as displayed (NoDebuff) |
%practice_kit_id% | the same kit as an id (nodebuff), to build another placeholder |
%practice_opponent% | the opponent's name in a 1v1, empty otherwise |
%practice_rating% | the global rating, weighted by matches played per kit |
%practice_wins% / %practice_losses% / %practice_matches% | totals across every kit played |
%practice_rating_<kit>% | the rating on that kit |
%practice_wins_<kit>% / %practice_losses_<kit>% | wins and losses on that kit |
%practice_matches_<kit>% | matches played on that kit |
%practice_streak_<kit>% / %practice_beststreak_<kit>% | current and best win streak |
About the server
| Placeholder | Answers |
|---|---|
%practice_online% | players connected |
%practice_ongoing% | matches in progress, duels and melees together |
%practice_queued% | players waiting, every queue together |
%practice_queued_<kit>% | players waiting on that kit, ranked and unranked, every team size |
%practice_topname_<rank>_<kit>% | the name at that rank on that kit's leaderboard, 1 to 10 |
%practice_toprating_<rank>_<kit>% | the rating at that rank |
Three things worth knowing before you write them into your tab list:
%practice_state%answers a stable word, not a sentence. It is an identifier meant to be read- A misspelt token stays visible.
%practice_ratign%resolves to itself rather than to an empty - The leaderboard is at most one second old. Sorting every rated player of a kit is the only
by your configuration, not shown as-is to a player — write the sentence around it, in your own language and colours. These five words are stable; a new kind of match, if one is ever added, would add a sixth rather than rename any of them. Use %practice_in_match% rather than comparing the word yourself, and your configuration keeps working.
string, so a typo shows up instead of silently blanking the line.
expensive answer here, so it is kept for a second and re-sorted after. Everything else is a table lookup or a counter the plugin already keeps — these placeholders are safe to refresh every tick.
Ranks above the tenth are not published, and a rank nobody holds yet answers empty so your line keeps its shape on a new server.
6. What it does not do
Written here rather than discovered later:
- 1.8.8 / 1.8.9 only. Not 1.9+, and ViaVersion is not required (nor supported as a target).
- No Bedrock.
- Cross-server covers 1v1 queues only (see section 4c), and only when you turn it on. Parties,
- No anti-cheat. Reach, autoclick and the rest are somebody else's job.
- The competitive watch reports, it never sanctions. Its counters live in memory and start again
team queues and events stay on a single server. A party cannot span servers.
at each restart; /practice integrity says so itself.
7. Where the numbers come from
Every performance claim about this plugin is measured on a real server with real 1.8 clients, and re-measured on the artifact that is actually released — not inherited from an older build.
Load, measured on this release:
| connected | plugin cost, 95th percentile | worst tick | TPS |
|---|---|---|---|
| 1 | 0,486 ms | 1,993 ms | 20 |
| 51 | 0,307 ms | 0,451 ms | 20 |
| 101 | 0,314 ms | 0,901 ms | 20 |
| 151 | 0,292 ms | 1,807 ms | 20 |
| 201 | 0,363 ms | 1,252 ms | 20 |
That is under one percent of a 50 ms tick at two hundred players, and -- this is the part worth reading twice -- the cost does not grow measurably with population in this range. The fitted line is flat to within the noise (0,405 ms + -0,0005 ms per player), which is another way of saying the sidebar costs what it costs per player and nothing extra for having neighbours.
These figures replace the ones published before (1,34 ms at 200 players), and the reason is not a change in the plugin: the old bench was itself the bottleneck. It held all two hundred clients in one process, and a struggling client process is time the server spends on network I/O. Spread over four processes, the same build measures three times lower. A number measured through a saturated instrument is not a number, and it is not carried forward.
Two hundred players, all queueing at once -- now measured. This was the one number this guide used to say it did not have. The bench held all two hundred clients in a single process, and that process -- not the server -- gave out during the burst. It now spreads them over four processes, and the measurement exists:
| what | measured |
|---|---|
| players connected | 201 (200 plus one operator), counted by the bench rather than assumed |
| they all queue in the same instant | 25 duels started, in all three runs |
| cost of one duel start | 10,1 / 12,0 / 13,6 ms average over three consecutive runs (15,0 / 19,9 / 22,1 at the 95th; 33 / 35 / 41 at the worst) |
| the sidebar during that burst | 0,55 / 0,52 / 0,60 ms average, 0,79 / 0,83 / 1,17 at the 95th |
| the sidebar at rest, 201 connected | 0,324 ms average, 0,445 at the 95th, 0,543 at the worst |
| TPS | 20, dipping to 19,97 from a hundred players up |
Three numbers and not one, on purpose. The three runs are the same artifact, the same machine, minutes apart -- and the duel-start cost climbs on every one of them. That drift belongs to the machine (a shared VPS that also runs a production server), not to the plugin, and printing the prettiest of the three as if it were the figure would be the kind of measurement this document refuses elsewhere. An earlier edition of this guide printed 8,4 ms from another day; it is not carried forward, because it was not measured on the artifact you are getting.
Why 25 duels and not 100, and why that is on purpose. Matchmaking runs on a budget -- matchmaking.queue-start-budget-ms, 8 ms by default -- and one duel start costs 10 to 14 ms at this population. So a pass starts one duel, and a two-hundred-player queue drains over about forty seconds rather than seizing the server for a second. That is the trade this plugin makes deliberately: the queue is slower than it could be so that nobody's game stutters. Raise that key if you would rather drain faster, and watch duel.start in /practice perf while you do.
The sidebar does not grow with population, and that is the figure worth keeping. Measured across five steps on the released artifact: 0,561 ms at the 95th with one player, 0,412 at fifty-one, 0,339 at a hundred and one, 0,336 at a hundred and fifty-one, 0,445 at two hundred and one. A least-squares fit over those five points has a negative slope -- the per-player cost is not measurable above the noise. (The bench runs on four processes for this reason: a single one was the bottleneck and was measuring itself. An older edition printed 1,344 ms at 200 players; that was a struggling bench as much as a server, and it is not carried forward either.)
The functional campaign is 458 checks across 34 sections, driven by actual 1.8.9 clients, on the exact artifact that is released -- plus fourteen more on two servers behind a proxy, seven on a live PostgreSQL install, and seven on an added language.
The ceiling of the load bench is its own max-players, not the plugin and not memory. The method and the figures are published with the release. If a number was not measured, it is not claimed.
You can reproduce the essentials yourself on your own server: /practice perf shows the cost of every operation on the main thread, and /practice selftest full says what is misconfigured and why.
For add-on authors, the public contract — four events and the mode API — is in docs/API.md.