Frostyh Plugins

Plugins / SentinelAC

SentinelAC

25 checks, an AI-assisted combat score that learns on your server, and evidence on every flag.

Get SentinelAC

$13.99 on BuiltByBit, list price on 16 October 2026. BuiltByBit sales can lower it, the listing shows the current price.

Buy on BuiltByBit

Also in Complete Collection: Every Plugin, PvP Server Bundle, SMP Starter Bundle.

Try it live

SentinelAC runs on a public demo server, set up so you can use it yourself. Join, try it, then decide.

Address: try.zeropvp.xyz:25570 (Java Edition 1.21.11)

In Minecraft: Multiplayer, Add Server, paste the address, Join. Type /demo go sentinelac to land at its booth, or right-click the compass and pick it. The world resets every night.

Overview

SentinelAC

A second pair of eyes for your staff that shows exactly what it saw, without kicking your legit players

SentinelAC 2.0 runs 25 checks across combat, movement, world and player actions, and adds an AI-assisted combat score: a small statistical model that learns what normal fighting looks like on YOUR server and scores every fight against it. Every flag comes with its evidence (the numbers it measured, the score, the last rotations and a replay), so staff decide on facts.

Out of the box it only alerts. When you do switch actions on, certain-only mode lets only flags that a normal client cannot produce on any connection run a command, and only after several separate flags on a healthy server.

Live reach alerts in staff chat: distance, the allowed maximum, ping, TPS and the AI score.

Latest version
2.0.0
Server
Paper or Purpur, Java 21
BuiltByBit
SentinelAC on BuiltByBit
What changed in 2.0.0

The biggest update so far: from 7 checks to 25, an AI-assisted detection layer that runs on your own server, evidence for every flag, and a "certain only" mode for automatic actions. Nothing punishes by default, as before.

AI-assisted detection (new)

New checks

Stronger existing checks

Fewer false flags

Staff tools

Your existing config.yml

  • While a player fights, SentinelAC records what the server can see: how the camera turns every tick (mouse steps, smooth curves, dead stops after a snap), the timing of every click, the distance and angle of every hit, hits per swing, target switches and movement beyond the movement prediction.
  • Every 20 hits these become 15 measurements, compared with what is normal on YOUR server (learned from players who were not flagged) and combined by a small statistical model into a score from 0 to 100%. One strange number on its own never reaches the alert line; several have to agree.
  • Everything runs on your server. There is no cloud service and no network call.
  • The score shows in every alert, in /sac profile (measurement by measurement), in the evidence and in the Discord posts. Several high-scoring fights within 10 minutes raise the new "ai" check.
  • It never runs a command on its own unless you set ai.may-punish: true.
  • Training: /sac ai train on while only trusted players play, /sac ai train off when /sac ai status shows a few hundred fights. It keeps learning slowly from clean players afterwards. /sac ai label <player> cheater|legit teaches it from a confirmed case. /sac ai reset starts again. What it learned is kept in plugins/SentinelAC/ai-model.yml.
  • Combat: killaura (two targets in one client tick, hits on someone behind you, hits far off aim, attacks without an arm swing, silent rotations), criticals, velocity (anti-knockback), hitbox (expanders).
  • Movement: step, jesus (walking on water or lava), phase, noslow, elytra (holding height and speed without a boost), vehicle (boat and mount speed, boat fly), badpackets (several moves in one tick, blink, sprinting while starving).
  • World: fastbreak, nuker, scaffold and tower, fastplace, ghosthand (breaking or opening through walls).
  • Player: fastuse, inventory (moving while clicking in an inventory, chest stealers).
  • Reach is now lag-compensated: measured against every position the target held inside both players' latency window, so a laggy hit on a sprinting player is no longer counted as reach. New tiers: "impossible" (no ping explains it, the hit is refused) and "pattern" (half of the last 30 hits past 3.05 blocks).
  • Speed has a per-tick movement prediction that catches strafe and low-hop modules that stay under the old limit, and a check for sprinting backwards.
  • Timer also measures the client's own tick packets against the clock.
  • NoFall also catches clients that claim to stand on the ground while falling.
  • AutoClicker also looks at the shape of the click timing, which catches randomised autoclickers.
  • Every flag is CERTAIN (something a normal client cannot do on any connection) or HEURISTIC, and alerts say which.
  • New exemptions: ping spikes, a server that just froze, elytra and riptide momentum after they end, leaving a vehicle, lowered gravity, and Bedrock players (Geyser and Floodgate) for the checks listed in gate.bedrock-skips.
  • Most new checks fill a buffer that drains over time and flag only when it is full. Movement checks can set the player back first (setback: true), and world checks refuse the block first (cancel: true), before any flag.
  • actions.certain-only (on in a new config.yml): only CERTAIN flags can run an action, and only after several separate flags spread over time, on a healthy server, with no ping spike, teleport or join just before. A held action tells staff why.
  • /sac profile <player>: the AI score feature by feature, every level, the latest evidence, client brand and connection.
  • /sac evidence <player>: the latest flags, with the numbers, the AI score and the last rotations on hover and the recording one click away.
  • /sac alerts off|certain|normal|verbose|debug: pick how much you see.
  • /sac debug <player>: every buffer step and dropped flag of one player in your chat.
  • /sac testmode: checks run and alert, but nothing is cancelled, set back or run.
  • /sac timings: how much of each tick the plugin costs.
  • Punishment ladders per check (checks.<name>.punish): first offence runs the first command, the second the next, remembered in offences.yml. /sac pardon <player> clears it.
  • Discord posts now include the AI score and the confidence.
  • Keeps working without changes. New settings use their defaults from the plugin, and the new checks only alert.
  • Your actions keep working as before: an old config.yml has no actions.certain-only line, so any flag still counts. Add "certain-only: true" under actions to switch to certain-only mode.
  • Your old alert line has no AI score in it, so the score is added at its end. Delete config.yml (or copy the new messages) to get the new alert line and the new help text.
  • Bedrock players are no longer judged by the speed check (and the other checks in gate.bedrock-skips). Remove "speed" from that list to change it back.
  • Performance: in testing the whole plugin cost well under one percent of a tick, and the AI scoring runs on its own background thread.

Features

Why server owners pick it

What makes it different

Works with

New in v2.0.0

A player's profile: the AI-assisted score and every feature behind it.

Checks

Every check is a switch; a check that is off never flags and never acts.

Combat (7)

Movement (11)

World (5)

Player (2)

On top of these, several high AI scores within ten minutes raise the separate ai check, which is always HEURISTIC.

Why it does not flag your legit players

Hover an alert for the evidence, click it for the replay.

What you get

A CERTAIN flag: 4.80 blocks, impossible above 4.00 at the measured ping.

Replay

Staff mode

Inventory audit

The staff panel

The staff panel: checks running, actions on or off, players with a violation level.

FAQ

Which servers does it run on?

Paper or Purpur 1.21.11 or a newer 1.21.x, with Java 21.

Do I need other plugins?

No. It runs on its own.

Is there DRM or a licence key?

No. There is nothing to activate and no licence server to reach, and the code is not obfuscated.

Does the AI part send data anywhere?

No. The model runs on your server and learns from your server; there is no cloud service and no network call. The only thing that ever leaves the server is a Discord alert, and only if you paste a webhook address.

Will it ban people on its own?

Not unless you tell it to. Actions are off by default, certain-only mode limits them to flags a normal client cannot produce, and the AI check never acts unless ai.may-punish is true.

Does it catch every cheat client?

No anticheat does. SentinelAC covers 25 checks plus the AI score, and the alerts show what it measured so your staff can judge the rest.

How long does setup take?

Drop the jar into /plugins and restart. The setup guide above has 4 short steps.

Can I try it before I buy?

Yes. This page has a test server with the plugin installed.

Where do I get help?

In the active support Discord, in the discussion tab on this page, or in the documentation.

Can alerts go to Discord?

Yes. Paste a channel webhook address into discord.webhook-url and run /sac reload.

Updates

Released on September 15, 2026 and updated 7 times since. The latest version is v2.0.0 (October 18, 2026). Every update is free, new Minecraft versions included.

Also by Frostyh

If this plugin helps your server, a short review on this page would mean a lot. Thank you!

Installation

Requirements

Server software
Paper, or a Paper fork such as Purpur. Spigot and CraftBukkit are not supported: the plugin uses Paper-only APIs.
Minecraft version
plugin.yml api-version 1.21; built against Paper API 1.21.11
Java
21 or newer
Required plugins
none
Optional plugins
none

Steps

  1. Download SentinelAC.jar from BuiltByBit. There is no licence key to enter.
  2. Stop the server and put the jar into the plugins folder.
  3. Start the server. The plugin creates plugins/SentinelAC/config.yml with the defaults shown under Configuration.
  4. Edit config.yml to suit your server.
  5. Apply the changes with /sac reload (Reload the config), or restart the server.

Setup guide

  1. Drop SentinelAC.jar into /plugins and restart. Give staff sentinelac.alerts.
  2. Run /sac ai train on while only trusted players are fighting, and /sac ai train off once /sac ai status shows a few hundred fights.
  3. Play normally for a few days and watch the alerts. If a legit player keeps tripping one check, raise that check's tolerance or alert-at.
  4. When the alerts match what your staff would have said anyway, set actions.enabled to true and put your ban or kick command under the levels you want. Keep certain-only on.

SMP: fly, speed, nofall, nuker and the world checks matter most; leave actions off and let staff spectate on alert.

Duel / FFA / KitPvP: reach, killaura, autoclicker, aim and the AI score are the ones that decide fights. A temp ban is still a human decision.

Networks: run it on the game servers, not the lobby, and put the server name into discord.server-name so staff know where to jump.

Commands

From plugin.yml. "Not set" in the permission column means plugin.yml attaches no permission to the command itself; the nodes the plugin checks are listed under Permissions.

CommandAliasesUsageDescriptionPermission
/sac/sentinel, /sentinelac/sac alerts|vl|profile|evidence|ai|debug|testmode|timings|replay|replays|staff|freeze|inv|ec|find|reset|pardon|exempt|checks|testalert|reloadSentinelAC staff commands.not set

Subcommands in detail

/sac alerts [off|certain|normal|verbose|debug]   Your own alert level.
/sac vl [player]         Current violation levels of a player, with their ping.
/sac profile <player>    The AI score feature by feature, every level, evidence, client and connection.
/sac evidence <player>   The latest flags with the numbers, the AI score and the rotations.
/sac ai status|train on|off|label <player> cheater|legit|reset|save   (admin) The AI-assisted layer.
/sac debug <player>      (admin) Every buffer step and dropped flag of one player.
/sac testmode            (admin) Checks run and alert, nothing is cancelled, set back or run.
/sac timings             (admin) What each check costs per tick.
/sac replay <player> [n] Watch the seconds before their flag, where it happened.
/sac replay stop · /sac replays
/sac staff               Staff mode: vanish, tools, your gear kept safe.
/sac freeze <player>     Hold somebody where they stand.
/sac inv <player> · /sac ec <player>   Read-only copies of inventory and ender chest.
/sac find <item> [name]  Who is holding it, and where they keep it.
/sac reset <player>      (admin) Clear a player's levels.
/sac pardon <player>     (admin) Clear their punishment ladder.
/sac exempt <player>     (admin) Toggle exemption for a player.
/sac checks              Which checks are on, and whether actions are enabled.
/sac testalert           (admin) Send a sample alert, and one to Discord.
/sac reload              (admin) Reload the config.

Permissions

From plugin.yml.

NodeDefaultDescriptionChildren
sentinelac.alertsoperatorsSee alerts and use /sac alerts and /sac vl.none
sentinelac.adminoperatorsReset, pardon, exempt, debug, testmode, timings, AI training and labels, testalert, reload.none
sentinelac.staffoperatorsStaff mode, freeze, inventory inspection and the item audit.none
sentinelac.staff.seeoperatorsSee staff who are vanished.none
sentinelac.bypassnobody until grantedNever checked.none

Placeholders

This plugin registers no PlaceholderAPI placeholders.

Configuration reference

The default files as they ship in the jar, with their comments. The plugin writes them to plugins/SentinelAC/ on first start.

config.yml

Top-level keys:

KeyDefaultComment above it
test-modefalseTest mode: every check runs and alerts as normal, but nothing is cancelled, nobody is set back and no action command runs (the console says what would have run). /sac testmode toggles it.
timingssection, 1 keyA timing log of the main-thread cost of each part of the plugin, in the console every this many minutes (0 = only when you ask with /sac timings).
menutrueBare /sac opens the staff panel instead of printing the help.
replaysection, 11 keysReplay: the seconds before a flag, played back to a staff member as a stand-in wearing the suspect's skin walking the exact path that was recorded. Nothing is written to disk and only the watching staff member sees any of it.
staffsection, 2 keysStaff mode: vanish, a hotbar of tools, and the staff member's own inventory kept safe on disk so a restart in the middle of a session does not cost them their gear.
menu-hides-clean-playerstrueThe panel lists only players with a violation level. Off lists everyone online.
gatesection, 7 keysnone
movementsection, 4 keysnone
alertssection, 4 keysnone
discordsection, 6 keysAlerts in a Discord channel, for when no staff are online. Make a webhook in the channel's settings (Integrations > Webhooks), paste its address here and /sac reload. Empty = off, and then nothing is ever sent anywhere. /sac testalert sends one post to...
actionssection, 4 keysnone
aisection, 13 keysA statistical model of how YOUR players fight, running on your server. No data leaves it: there is no cloud service and no network call. How it works, plainly: 1. While a player fights, it records numbers the server can see: how their camera turns every
checkssection, 26 keysnone
themesection, 11 keysEvery message below is written against these names, not against hex codes: <accent>Open</accent> rather than <#ef5350>Open</#ef5350>. Change a colour here and it changes on every screen at once. Ordinary MiniMessage still works, so <gradient:#a:#b> and...
messagessection, 169 keysnone
review-remindertrueOne thank-you line with the BuiltByBit review link: once in the console after install and once in chat for the first operator who joins. It never repeats and sends nothing anywhere. false turns it off.
plugins/SentinelAC/config.yml
# SentinelAC - an honest anticheat.
#
# Every check adds to a violation level (VL) that decays every second. At alert-at, staff with
# sentinelac.alerts see a message. Commands under actions run when a VL is reached, but ONLY when
# actions.enabled is true. Out of the box nothing is punished automatically: watch the alerts on
# your own server for a few days, then decide what you trust.
#
# Ping, low TPS, a frozen server, teleports, knockback, elytra, riptide, water, ladders, vehicles,
# creative and (for the checks listed in gate.bedrock-skips) Bedrock players are exempt before any
# check runs. Staff can toggle a player with /sac exempt <player>; sentinelac.bypass skips everything.
#
# Every flag has a confidence. CERTAIN means a vanilla client cannot produce it on any connection
# (two targets hit inside one client tick, a hit on someone behind every look you sent, a reach no
# ping explains, a client clock forty seconds fast). HEURISTIC is everything a bad connection, a mod
# or an unusual player could also produce. With actions.certain-only only CERTAIN flags act.
#
# No anticheat catches every client. This one aims for the best coverage it can get while keeping
# false flags very rare, which is why so much of it alerts and so little of it acts.

# Test mode: every check runs and alerts as normal, but nothing is cancelled, nobody is set back and
# no action command runs (the console says what would have run). /sac testmode toggles it.
test-mode: false

# A timing log of the main-thread cost of each part of the plugin, in the console every this many
# minutes (0 = only when you ask with /sac timings).
timings:
  log-every-minutes: 0

# Bare /sac opens the staff panel instead of printing the help.
menu: true

# Replay: the seconds before a flag, played back to a staff member as a stand-in wearing the
# suspect's skin walking the exact path that was recorded. Nothing is written to disk and only the
# watching staff member sees any of it.
replay:
  enabled: true
  seconds: 20              # how much movement is kept per player
  sample-every-ticks: 2    # 2 = ten samples a second
  record-at-vl: 5          # a flag at or above this level saves a recording
  min-seconds-between: 20  # do not save a second recording of the same player within this
  keep-per-player: 3
  keep-minutes: 20         # recordings older than this are dropped
  speed: 1.0               # 0.5 is half speed, 2.0 is double
  trail: true              # draw the path in particles for the watcher
  teleport-viewer: true    # put the staff member beside the start of the recording
  tell-staff: true         # say in chat when a recording was saved

# Staff mode: vanish, a hotbar of tools, and the staff member's own inventory kept safe on disk so a
# restart in the middle of a session does not cost them their gear.
staff:
  vanish-on-enter: true
  # Commands a frozen player may still run, so they can answer you.
  frozen-may-use: [msg, r, reply, tell, w, discord]
# The panel lists only players with a violation level. Off lists everyone online.
menu-hides-clean-players: true

gate:
  max-ping: 300        # above this, nothing flags
  min-tps: 17.5        # below this, nothing flags
  grace-ms: 3000       # after join, teleport, respawn or world change
  timing-max-ping: 250 # above this, timing checks (click rhythm, packet order) stand down
  ping-spike-ms: 150   # a ping jump this big marks the connection unsteady for a few seconds
  stall-grace-ms: 4000 # after the server itself froze for over a second, nothing flags for this long
  # Bedrock players (Geyser + Floodgate, found by their Floodgate UUID or the Floodgate API) are not
  # judged by these checks: their aim, clicks and ticks come from Geyser, not from a Java client.
  # "all" skips every check for them.
  bedrock-skips: [aim, killaura, hitbox, autoclicker, ai, badpackets, timer, criticals, velocity, inventory, scaffold, noslow, speed, elytra]

movement:
  velocity-grace-ms: 2500   # after knockback / explosion / plugin velocity
  damage-grace-ms: 1500
  aerial-grace-ms: 2500     # after an elytra glide or a riptide ends, the momentum keeps going
  vehicle-grace-ms: 1500    # after leaving a vehicle

alerts:
  console: true
  console-every-flag: false # every flag in the console, not only those past alert-at (on in test mode)
  min-interval-ms: 1000     # per player, so a run of flags is one line not twenty
  # Staff pick their own level with /sac alerts <level>: off, certain (only CERTAIN flags), normal
  # (past alert-at, the default), verbose (every flag) or debug (verbose plus every AI-scored fight).
  # What clicking an alert line in chat runs, as the staff member who clicked. {player} and {check}
  # are filled in. The default plays back the seconds before the flag. "/tp {player}" or
  # "/sac vl {player}" work too; "" makes alerts plain text. Hovering always shows the numbers.
  click-command: "/sac replay {player}"

# Alerts in a Discord channel, for when no staff are online. Make a webhook in the channel's
# settings (Integrations > Webhooks), paste its address here and /sac reload. Empty = off, and then
# nothing is ever sent anywhere. /sac testalert sends one post to check it.
discord:
  webhook-url: ""
  per-player-seconds: 30    # one post per player per this many seconds, so a fight is one message
  actions: true             # also post when a configured action (kick, ban command) runs
  server-name: ""           # shown on every post, for networks with several servers
  alert-title: "{player} failed {check} (VL {vl})"
  action-title: "Action taken: {player} reached {check} VL {vl}"

actions:
  enabled: false
  repeat-after-ms: 60000    # an action fires again only after this, even if the VL stays high
  # Certain only: only CERTAIN flags can run an action, the level compared is the one built from
  # CERTAIN flags alone, and the gate below must agree. Recommended. (A config.yml from before 2.0
  # has no such line and keeps the old behaviour, where any flag counts.)
  certain-only: true
  gate:
    min-flags: 3            # this many separate certain flags of the same check...
    min-spread-seconds: 3   # ...spread over at least this long, not one burst...
    window-seconds: 120     # ...inside this window
    min-tps: 18.5           # and the server healthy
    quiet-seconds: 10       # and no ping spike, teleport or join in this many seconds
  # Besides the "actions" map on each check (a command at a level), each check can have a
  # punishment ladder: the first time it is reached the first line runs, the second time the second,
  # and so on (remembered in offences.yml; /sac pardon <player> clears it). Several commands on one
  # rung are joined with " && ". Example, under checks.reach:
  #   punish:
  #     at-vl: 10
  #     ladder:
  #       - "kick {player} Unfair advantage ({check})"
  #       - "tempban {player} 7d Unfair advantage ({check}) && broadcast {player} was removed"
  #       - "ban {player} Unfair advantage ({check})"
  # Placeholders: {player} {uuid} {check} {vl} {detail} {ai}

# ------------------------------------------------------------------------------------------------
# AI-ASSISTED DETECTION
# ------------------------------------------------------------------------------------------------
# A statistical model of how YOUR players fight, running on your server. No data leaves it: there
# is no cloud service and no network call.
#
# How it works, plainly:
#  1. While a player fights, it records numbers the server can see: how their camera turns every
#     tick (steps of a mouse, smooth curves, dead stops after a snap), the timing of every click
#     (how even, whether there are the pauses and doubles a hand makes), the distance and angle of
#     every hit, how often a swing hits, how often the target switches, and how far their movement
#     goes beyond the movement prediction.
#  2. After twenty hits (or at the end of a fight with ten) those numbers become fifteen features
#     and are compared with what is normal ON THIS SERVER: the median and spread of each feature,
#     learned from fights of players who were not flagged. Only the suspicious direction counts
#     (very even clicks count, messy ones do not) and no feature can count more than six spreads.
#  3. A small logistic model with shipped weights combines them into a score from 0 to 100%. It is
#     tuned so that ONE strange number, however strange, stays under the line: it takes several
#     measurements agreeing. The line is calibrated: once 200 clean fights have been seen it is
#     raised to the score only target-false-alarm of your own clean fights reach.
#  4. Several high fights within period-minutes raise the "ai" check (always HEURISTIC). The score
#     is in every alert, every piece of evidence, /sac profile and the Discord posts.
# It never acts on its own unless may-punish is true.
#
# Training: /sac ai train on while only trusted players play (an event, staff, regulars) makes it
# learn every fight as normal and raise no AI flags. /sac ai train off when you have a few hundred
# fights (/sac ai status shows the count). It keeps learning slowly from clean players afterwards.
# Labels: /sac ai label <player> cheater (after you have confirmed one) teaches it from their recent
# fights; "legit" does the opposite. /sac ai reset starts again from the shipped baselines.
ai:
  enabled: true
  training: false
  may-punish: false          # true lets the "ai" check run its actions (actions.enabled still needed)
  window-hits: 20            # a fight is scored every this many hits
  min-hits: 10               # a fight that ends with fewer is not scored
  flag-score: 0.90           # never alert below this score, however the calibration comes out
  target-false-alarm: 0.005  # the calibrated line: half a percent of clean fights may reach it
  windows-needed: 2          # this many fights over the line...
  period-minutes: 10         # ...within this long raise the "ai" check
  learn: true                # keep learning from clean fights after training
  clean-minutes: 10          # a player with any flag in this many minutes is not learned from
  prior-strength: 30         # how many fights the shipped baseline is worth against what is learned
  # Optional: override the weight of a feature (0 turns it off). Keys: click_cv, click_kurtosis,
  # click_outliers, click_entropy, gcd_off, rot_jerk, snap_stop, aim_error, aim_error_sd, aim_lock,
  # accuracy, reach_p90, snap_hit, switch_rate, move_residual. Example: gcd_off: 0
  weights: {}

checks:
  # Flight is measured against gravity itself. Airborne vertical speed in Minecraft is
  # v = (v - 0.08) * 0.98 every tick, with no exception for a player in open air, so it has to fall
  # away tick after tick. Hovering holds it at zero, ascending holds it positive and a slow glide
  # holds it just under zero: all three are the same violation and all three are caught.
  #
  # A jump, a wind charge, an explosion and a crystal all decay exactly as the formula says, so a
  # launch is one violating move and the move after it clears the streak. A stalled connection
  # delivers several ticks of falling as one move, which looks like falling FASTER, never like being
  # held up, so lag cannot manufacture this.
  fly:
    enabled: true
    gravity-streak: 10        # consecutive moves with no gravity: half a second of flight
    gravity-window: 60        # and the window the second tier measures over
    gravity-window-hits: 50   # how much of that window has to be gravity-defying, for a client that bobs
    weight-gravity: 4.0
    alert-at: 6
    decay-per-second: 0.5
    actions:
      "20": ["kick {player} Unfair advantage ({check})"]
  # Two tiers. The peak: horizontal distance per move against a sprint-jump with your speed
  # attribute and effects. The prediction (2.0): per client tick, movement is last tick's times the
  # friction of the block plus at most one tick of acceleration (plus 0.2 on a sprint-jump), so any
  # movement beyond the larger of the ground and air answers is speed the game did not give. That
  # catches strafe and low-hop modules that stay under the peak. Also sprinting backwards.
  speed:
    enabled: true
    base-per-tick: 0.62     # sprint-jump peak on flat ground at speed 0.1
    tolerance: 1.15
    ice-multiplier: 1.8
    streak: 6               # consecutive too-fast ticks before a flag
    weight: 1.0
    prediction-threshold: 0.04  # blocks per tick beyond the prediction that count as suspicious
    prediction-slack: 0.02
    weight-prediction: 1.0
    omni-ticks: 10              # ticks of sprinting away from where you look
    weight-omni: 1.0
    buffer: 12                  # suspicious ticks needed (the buffer drains by buffer-decay a second)
    buffer-decay: 4
    setback: false
    alert-at: 6
    decay-per-second: 0.5
    actions:
      "25": ["kick {player} Unfair advantage ({check})"]
  # Landing after a real drop with no fall damage, and (2.0) a client claiming to stand on the
  # ground while falling with nothing under it, which is how most nofall modules work.
  nofall:
    enabled: true
    min-drop: 4.0
    weight: 2.0
    weight-spoof: 1.5
    buffer: 3
    buffer-decay: 0.5
    alert-at: 4
    decay-per-second: 0.25
    actions: {}
  # Lag-compensated (2.0): measured against every position the victim held inside both players'
  # latency window, so a laggy hit on a sprinting target is not charged as reach. Three tiers:
  # past range + tolerance (heuristic), past what both pings can explain + impossible-margin
  # (CERTAIN, and the hit is refused), and half of the last thirty hits past 3.05 (pattern).
  reach:
    enabled: true
    tolerance: 0.35         # on top of the player's entity interaction range (3.0 vanilla)
    high-ping-bonus: 0.25   # extra when ping is above 100
    weight: 1.0
    impossible-margin: 1.0
    cancel-impossible: true
    pattern: true
    pattern-share: 0.5
    weight-pattern: 2.0
    alert-at: 5
    decay-per-second: 0.3
    actions:
      "20": ["kick {player} Unfair advantage ({check})"]
  # Too many clicks, too even a rhythm, and (2.0) the wrong shape: forty click gaps with no human
  # tails (negative kurtosis, not one outlier) at a real clicking pace, which is what randomised
  # autoclickers produce.
  autoclicker:
    enabled: true
    max-cps: 22
    rhythm-samples: 16
    rhythm-max-deviation-ms: 4.0
    weight-cps: 1.0
    weight-rhythm: 1.5
    stats-samples: 40
    stats-min-cps: 8
    stats-max-kurtosis: -0.9
    stats-max-outliers: 0
    weight-stats: 1.5
    alert-at: 5
    decay-per-second: 0.3
    actions: {}
  aim:
    enabled: true
    window: 40
    snap-degrees: 25.0
    snaps: 4
    weight-snap: 1.0
    weight-invalid: 5.0
    alert-at: 5
    decay-per-second: 0.3
    actions: {}
  # Moves per second over a window, and (2.0) the balance of the client's own tick-end packets
  # against the clock: a client running fast drifts ahead and keeps drifting, a lag spike only
  # bunches ticks up. Past balance-certain-ms (longer than any connection survives a stall) it is
  # CERTAIN.
  timer:
    enabled: true
    window-seconds: 4
    max-ratio: 1.12
    weight: 1.0
    balance-alert-ms: 5000
    balance-certain-ms: 40000
    balance-step-ms: 2000
    weight-balance-certain: 3.5
    alert-at: 4
    decay-per-second: 0.3
    actions:
      "15": ["kick {player} Unfair advantage ({check})"]

  # ---- 2.0: combat ------------------------------------------------------------------------------
  # Two entities attacked inside one client tick (CERTAIN), a hit on someone behind every look
  # direction sent in the latency window (CERTAIN), a hit far off aim, an attack without an arm
  # swing, and silent rotations (the look jumps to the target for the attack's tick and back).
  killaura:
    enabled: true
    suspicious-degrees: 70.0
    impossible-degrees: 95.0
    weight-multi: 3.0
    weight-noswing: 1.0
    silent-snap-degrees: 20.0
    silent-return-degrees: 1.0
    silent-count: 3
    weight-silent: 2.0
    buffer: 4
    buffer-decay: 0.5
    alert-at: 3
    decay-per-second: 0.3
    actions: {}
  # A critical hit from a player who has not gone up or down for six client ticks (standing on
  # flat ground, where no critical exists). Uses positions, not the client's on-ground flag.
  criticals:
    enabled: true
    still-ticks: 6
    weight: 2.0
    buffer: 2
    buffer-decay: 0.2
    alert-at: 4
    decay-per-second: 0.3
    actions: {}
  # Anti-knockback: on the tick a knockback lands, a vanilla client moves along it by most of its
  # strength. Walls, water, webs and crowds excuse it.
  velocity:
    enabled: true
    min-strength: 0.25
    min-taken: 0.35
    weight: 1.5
    buffer: 3
    buffer-decay: 0.1
    alert-at: 3
    decay-per-second: 0.2
    actions: {}
  # Hitbox expanders: forty swings whose median passes more than 0.22 blocks outside the target.
  hitbox:
    enabled: true
    sample: 40
    median-miss: 0.22
    miss-share: 0.65
    weight: 3.0
    alert-at: 3
    decay-per-second: 0.1
    actions: {}

  # ---- 2.0: movement -----------------------------------------------------------------------------
  # setback: true puts the player back where every movement check last agreed with them as soon as
  # the buffer is half full, before any flag. An honest player gets one rubber band at worst.
  step:
    enabled: true
    tolerance: 0.05
    weight: 2.0
    buffer: 2
    buffer-decay: 0.2
    setback: true
    alert-at: 4
    decay-per-second: 0.3
    actions: {}
  jesus:
    enabled: true
    moves: 10
    weight: 2.0
    buffer: 2
    buffer-decay: 0.2
    setback: true
    alert-at: 4
    decay-per-second: 0.3
    actions: {}
  phase:
    enabled: true
    weight: 2.0
    buffer: 2
    buffer-decay: 0.2
    setback: true
    alert-at: 4
    decay-per-second: 0.3
    actions: {}
  noslow:
    enabled: true
    grace-ms: 350
    max-speed: 0.13
    ticks: 7
    weight: 1.0
    buffer: 3
    buffer-decay: 0.3
    setback: false
    alert-at: 3
    decay-per-second: 0.3
    actions: {}
  # Gliding that holds its height and speed for two seconds without a firework, wind charge or
  # riptide (the glide physics always pays with one or the other), or faster than 4 blocks a tick.
  elytra:
    enabled: true
    window-ticks: 40
    min-drop: 1.0
    max-speed: 4.0
    boost-grace-ms: 3500
    weight: 2.0
    buffer: 2
    buffer-decay: 0.1
    setback: false
    alert-at: 4
    decay-per-second: 0.3
    actions: {}
  vehicle:
    enabled: true
    max-speed-boat: 1.2
    max-speed-boat-ice: 4.2
    max-speed-mount: 1.4
    fly-moves: 10
    weight: 1.5
    buffer: 5
    buffer-decay: 1.0
    alert-at: 3
    decay-per-second: 0.3
    actions: {}
  # Several movement packets inside one client tick, blink (a silence then a burst of held ticks),
  # and sprinting with six food points or fewer.
  badpackets:
    enabled: true
    weight: 1.0
    weight-blink: 0.5
    blink-gap-ms: 1000
    blink-burst: 15
    hungry-sprint-moves: 20
    buffer: 4
    buffer-decay: 0.5
    alert-at: 4
    decay-per-second: 0.3
    actions: {}

  # ---- 2.0: world --------------------------------------------------------------------------------
  # cancel: true refuses the block break or placement first; flags come only past the buffer.
  fastbreak:
    enabled: true
    ratio: 0.9              # under this share of the game's own break time...
    latency-ms: 50          # ...minus this, is too fast
    weight: 1.0
    buffer: 3
    buffer-decay: 0.1
    cancel: true
    alert-at: 3
    decay-per-second: 0.2
    actions: {}
  nuker:
    enabled: true
    max-per-second: 22
    max-angle: 100.0
    weight: 2.0
    buffer: 3
    buffer-decay: 0.5
    cancel: true
    alert-at: 4
    decay-per-second: 0.3
    actions: {}
  scaffold:
    enabled: true
    face-tolerance: 0.4
    look-margin: 0.25
    tower-min-ms: 300
    bridge-max-pitch: 40.0
    bridge-blocks: 6
    weight: 1.5
    buffer: 3
    buffer-decay: 0.2
    cancel: true
    alert-at: 3
    decay-per-second: 0.3
    actions: {}
  fastplace:
    enabled: true
    max-per-second: 24
    weight: 1.0
    buffer: 3
    buffer-decay: 0.5
    cancel: true
    alert-at: 3
    decay-per-second: 0.3
    actions: {}
  ghosthand:
    enabled: true
    weight: 1.5
    buffer: 3
    buffer-decay: 0.05
    cancel: true
    alert-at: 3
    decay-per-second: 0.1
    actions: {}

  # ---- 2.0: player -------------------------------------------------------------------------------
  fastuse:
    enabled: true
    ratio: 0.75
    latency-ms: 100
    bow-min-ms: 650
    weight: 1.5
    buffer: 2
    buffer-decay: 0.05
    alert-at: 3
    decay-per-second: 0.1
    actions: {}
  # Inventory move (clicking slots while the client still reports a movement key held: a screen
  # always releases them; also what auto-armour does while running) and chest stealers.
  inventory:
    enabled: true
    move-held-ms: 400
    first-click-ms: 40
    steal-clicks: 6
    steal-max-gap-ms: 70
    steal-max-deviation-ms: 4.0
    weight: 1.5
    buffer: 2
    buffer-decay: 0.05
    alert-at: 3
    decay-per-second: 0.1
    actions: {}
  # The AI-assisted layer as a check (see the ai section above). Always heuristic.
  ai:
    enabled: true
    weight: 2.0
    alert-at: 2
    decay-per-second: 0.02
    actions: {}

# ------------------------------------------------------------------------------------------------
# THEME
# ------------------------------------------------------------------------------------------------
# Every message below is written against these names, not against hex codes: <accent>Open</accent>
# rather than <#ef5350>Open</#ef5350>. Change a colour here and it changes on every screen at
# once. Ordinary MiniMessage still works, so <gradient:#a:#b> and <#ff0000> are fine anywhere.
theme:
  accent: "#ef5350"     # your colour: headings and anything the player is meant to click
  accent2: "#f6a09f"    # a lighter accent, for a second level of emphasis
  title: "#f4f7ff"      # screen titles
  text: "#c2ccdd"       # ordinary prose
  muted: "#6b7a94"      # hints, separators, anything the eye should skip
  value: "#ffffff"      # the numbers and names the player opened the screen to read
  good: "#57e08a"
  warn: "#ffc857"
  bad: "#ff6b6b"
  small-caps: false     # ᴇхᴀᴍᴘʟᴇ - a garnish; whole menus in it read badly
  # Real Minecraft item pictures beside the buttons and in the chest screens. Drawn from the
  # client's own textures, so there is nothing to install and no resource pack: it needs a
  # 1.21.9 or newer client, and older ones simply see the buttons as words.
  item-icons: true

messages:
  prefix: "<#c0392b><bold>Sentinel</bold></#c0392b> <muted>»</muted> "
  alert: "<value>{player}</value><text> failed </text><#e74c3c>{check}</#e74c3c><text> (VL {vl}, {confidence}) </text><muted>{detail} · {ping}ms · {tps} tps · {ai}</muted>"
  # Added after the alert line of a config.yml from before 2.0, which has no {ai} in it.
  alert-ai-suffix: "<muted> · {ai}</muted>"
  # Hovering over an alert line in chat. The second one is used when alerts.click-command is set.
  alert-hover: "<value>{player}</value><text> · {check} · VL {vl}</text><newline><muted>{detail}</muted><newline><text>ping {ping}ms · server {tps} tps</text>"
  alert-hover-click: "<value>{player}</value><text> · {check} · VL {vl}</text><newline><muted>{detail}</muted><newline><text>ping {ping}ms · server {tps} tps</text><newline><newline><accent>Click: {command}</accent>"
  action-held: "<warn>Held an action for </warn><value>{player}</value><warn> ({check}): </warn><text>{reason}.</text>"
  test-would-run: "<warn>[test mode]</warn><text> would run: </text><value>{command}</value>"
  debug-line: "<muted>[debug] {player} {check}: {line}</muted>"
  debug-on: "<text>Debug output for </text><value>{player}</value><text> on. Same command again to stop.</text>"
  debug-off: "<text>Debug output for </text><value>{player}</value><text> off.</text>"
  testmode-on: "<warn>Test mode on.</warn><text> Checks alert, but nothing is cancelled, set back or run.</text>"
  testmode-off: "<good>Test mode off.</good><text> Cancels, setbacks and actions work again.</text>"
  timings-reset: "<text>Timings reset.</text>"
  usage-alerts: "<text>/sac alerts [off|certain|normal|verbose|debug]</text>"
  alerts-level: "<text>Your alerts: </text><value>{level}</value>"
  usage-pardon: "<text>/sac pardon <player> clears their punishment ladder.</text>"
  pardoned: "<text>Punishment ladder of </text><value>{player}</value><text> cleared.</text>"
  usage-ai: "<text>/sac ai status · /sac ai train on|off · /sac ai label <player> cheater|legit · /sac ai reset · /sac ai save</text>"
  ai-training-on: "<warn>AI training on.</warn><text> Every fight is learned as normal play and no AI flags are raised. Turn it off with /sac ai train off.</text>"
  ai-training-off: "<good>AI training off.</good><text> Fights are scored against what was learned.</text>"
  ai-labelled: "<text>Learned from </text><value>{count}</value><text> recent fights of {player} as </text><value>{label}</value><text>.</text>"
  ai-label-none: "<text>{player} has no scored fights yet to learn from.</text>"
  ai-reset: "<text>The AI model is back to the shipped baselines and weights.</text>"
  ai-saved: "<text>The AI model was written to ai-model.yml.</text>"
  ai-window: "<muted>[ai] {player}: {line}</muted>"
  profile-head: "<value>{player}</value><text> · {ping}ms · client {brand} · Bedrock {bedrock} · tick packets {ticks}</text>"
  profile-ai: "<text>AI-assisted score: </text><value>{score}</value><text> latest, {smoothed} smoothed, {windows} fights, alert line {line}</text>"
  profile-ai-none: "<text>AI-assisted score: no fight scored yet ({windows}).</text>"
  profile-feature: "<muted> - </muted><text>{feature} </text><value>{value}</value><muted> (normal {normal} ± {spread}) z {z}, +{points}</muted>"
  profile-levels: "<text>Levels: </text><value>{levels}</value>"
  profile-evidence: "<muted> - {ago}s ago </muted><text>{check} [{confidence}] </text><muted>{detail}</muted>"
  profile-offences: "<text>Punishment ladder: </text><value>{offences}</value>"
  evidence-head: "<text>Evidence for </text><value>{player}</value><text> ({count} kept, newest first). Hover for the numbers, click to watch.</text>"
  evidence-row: "<muted>{ago}s </muted><#e74c3c>{check}</#e74c3c><text> [{confidence}] VL {vl} </text><muted>{detail}</muted>"
  evidence-hover: "<value>{check}</value><text> · {confidence} · +{weight} · VL {vl}</text><newline><muted>{detail}</muted><newline><text>ping {ping}ms · server {tps} tps</text><newline><text>AI-assisted {ai}: {top}</text><newline><muted>last rotations (yaw/pitch per sample): {rotations}</muted><newline>{replay}"
  evidence-replay: "<accent>Click to watch the recording.</accent>"
  evidence-no-replay: "<muted>No recording for this flag.</muted>"
  replay-expired: "<text>That recording is no longer kept.</text>"
  menu-home-ai-on: "<text>AI-assisted detection: </text><value>watching</value><text>, {windows} fights scored, alert line {line}</text>"
  menu-home-ai-training: "<warn>AI-assisted detection is training:</warn><text> {windows} fights so far, no AI flags.</text>"
  menu-home-ai-off: "<text>AI-assisted detection is off.</text>"
  menu-player-ai: "<text>AI-assisted: </text><value>{ai}</value><muted> ({top})</muted>"
  menu-player-clean: "<muted>{count} checks at zero.</muted>"
  menu-profile: "<white><icon:stats></white><value>Profile of {player}</value>"
  menu-profile-tip: "<text>The AI-assisted score feature by feature, the levels and the latest evidence.</text>"
  menu-profile-title: "<value>{player}</value><text> · profile</text>"
  menu-evidence: "<white><icon:search></white><text>Evidence in chat</text>"
  menu-evidence-tip: "<text>Every recent flag with its numbers on hover and its recording on click.</text>"
  menu-debug: "<white><icon:settings></white><text>Debug {player}</text>"
  menu-debug-tip: "<text>Every buffer step and dropped flag of this player in your chat. Click again to stop.</text>"
  testalert-discord: "<text>Test alert shown above and posted to Discord. If it does not arrive, the console says why.</text>"
  testalert-no-discord: "<text>Test alert shown above. Discord is off: set discord.webhook-url in config.yml.</text>"
  help: "<text>/sac alerts [level] · /sac vl [player] · /sac profile <player> · /sac evidence <player> · /sac ai · /sac debug <player> · /sac testmode · /sac timings · /sac replay <player> · /sac replays · /sac staff · /sac freeze <player> · /sac inv <player> · /sac ec <player> · /sac find <item> · /sac reset <player> · /sac pardon <player> · /sac exempt <player> · /sac checks · /sac testalert · /sac reload</text>"
  no-permission: "<bad>You cannot do that.</bad>"
  unknown-player: "<bad>Unknown player: </bad><value>{name}</value>"
  alerts-on: "<value>Alerts on.</value>"
  alerts-off: "<value>Alerts off.</value>"
  vl: "<value>{player}</value><text> ({ping}ms): </text><value>{levels}</value>"
  reset: "<text>Levels of </text><value>{player}</value><text> reset.</text>"
  exempt-on: "<value>{player}</value><text> is now exempt.</text>"
  exempt-off: "<value>{player}</value><text> is checked again.</text>"
  checks: "{checks}  <text>actions: {actions}</text>"
  reloaded: "<value>Config reloaded.</value>"

  # The staff panel. Every line here is a screen title, a button label or a tooltip.
  menu-title: "<value>Sentinel</value>"
  menu-close: "<white><icon:close></white><text>Close</text>"
  menu-back: "<white><icon:back></white><text>Back</text>"
  menu-back-tip: "<text>Back to the screen before.</text>"
  menu-word-on: "<good>on</good>"
  menu-word-off: "<bad>off</bad>"
  menu-home-checks: "<text>Checks running: </text><value>{on} of {all}</value>"
  menu-home-actions-on: "<bad>Actions are on: configured commands run by themselves.</bad>"
  menu-home-actions-off: "<text>Actions are off. Alerts only, nobody is punished automatically.</text>"
  menu-home-flagging: "<text>Players with a violation level: </text><value>{count}</value>"
  menu-players: "<white><icon:players></white><#c0392b>Players</#c0392b>"
  menu-players-tip: "<text>Who is flagging, worst first.</text>"
  menu-players-title: "<value>Players</value>"
  menu-players-clean: "<text>Nobody has flagged anything.</text>"
  menu-player-line: "<value>{player}</value><text> · {check} VL {vl} · {ping}ms</text>"
  menu-player-open: "<white><icon:players></white><value>{player}</value>"
  menu-player-open-tip: "<text>Every check on {player}.</text>"
  menu-player-title: "<value>{player}</value>"
  menu-player-head: "<text>{player} · {ping}ms · server {tps} tps</text>"
  menu-player-check: "<text> - {check}: </text><value>VL {vl}</value><text> ({state})</text>"
  menu-reset: "<white><icon:dot></white><value>Reset levels</value>"
  menu-reset-tip: "<text>Back to zero on every check.</text>"
  menu-exempt-on: "<good>■</good> <value>{player} is exempt</value>"
  menu-exempt-off: "<muted>■</muted> <text>{player} is checked</text>"
  menu-exempt-tip: "<text>Exempt players are never flagged.</text>"
  menu-checks: "<white><icon:check></white><#c0392b>Checks</#c0392b>"
  menu-checks-tip: "<text>Turn a single check on or off.</text>"
  menu-checks-title: "<value>Checks</value>"
  menu-checks-intro: "<text>A check that is off never flags and never acts.</text>"
  menu-check-tip: "<text>Click to switch this check.</text>"
  menu-check-readonly: "<text>Admins only.</text>"
  menu-alerts-on: "<good>■</good> <value>My alerts on</value>"
  menu-alerts-off: "<muted>■</muted> <text>My alerts off</text>"
  menu-alerts-tip: "<text>Whether you see the alert messages.</text>"
  menu-actions-on: "<bad>■</bad> <value>Actions on</value>"
  menu-actions-off: "<muted>■</muted> <text>Actions off</text>"
  menu-actions-tip: "<text>On means the configured commands run by themselves. Watch the alerts first.</text>"
  menu-reload: "<white><icon:reload></white><text>Reload config</text>"
  menu-reload-tip: "<text>Read config.yml again.</text>"
  switch-on: "<white><icon:yes></white><good><bold>ON</bold></good>  <value>{title}</value>"
  switch-off: "<white><icon:no></white><bad><bold>OFF</bold></bad> <text>{title}</text>"

  # Replay.
  usage-replay: "<text>/sac replay <player> [n] · /sac replay stop · /sac replays</text>"
  replay-saved: "<text>Recording kept: </text><value>{player}</value><text> {check}, {seconds}s. </text><value>/sac replays</value>"
  replay-none: "<text>Nothing recorded for </text><value>{player}</value><text>.</text>"
  replay-playing: "<value>{player}</value><text> · {check} · {seconds}s from {ago}s ago. </text><value>/sac replay stop</value>"
  replay-stopped: "<text>Replay stopped.</text>"
  replay-world-gone: "<bad>That recording is from {world}, which is not loaded.</bad>"
  replay-ghost-name: "<text>{player}</text>"
  replay-label: "<#e74c3c><bold>REPLAY</bold></#e74c3c> <value>{player}</value><text> · {check} VL {vl} · {time}s of {total}s · {ping}ms · {state}</text>"
  replay-ground: "on ground"
  replay-air: "<warn>in the air</warn>"
  replays-header: "<text>Recordings kept: </text><value>{count}</value>"
  replays-row: "<text> - </text><value>{player}</value><text> {check}, {seconds}s, {ago}s ago</text>"
  menu-replays: "<white><icon:replay></white><#c0392b>Replays</#c0392b> <muted>({count})</muted>"
  menu-replays-tip: "<text>Watch the seconds before a flag.</text>"
  menu-replays-title: "<value>Replays</value>"
  menu-replays-intro: "<text>Every recording is the movement the checks saw, played back to you alone.</text>"
  menu-replays-off: "<text>Replay is switched off in config.yml.</text>"
  menu-replays-empty: "<text>Nothing has been recorded yet.</text>"
  menu-replay-line: "<value>{player}</value><text> · {check} VL {vl} · {seconds}s · {ago}s ago</text>"
  menu-replay-play: "<white><icon:replay></white><value>Watch {player}</value> <muted>({check})</muted>"
  menu-replay-play-tip: "<text>Play the {seconds} seconds back where they happened.</text>"
  menu-replay-last: "<white><icon:replay></white><value>Watch the last flag</value>"
  menu-replay-now: "<value>Record the last seconds now</value>"
  menu-replay-last-tip: "<text>Play back what {player} just did.</text>"
  menu-replay-stop: "<white><icon:delete></white><text>Stop watching</text>"
  menu-replay-stop-tip: "<text>Remove the stand-in and stop the replay.</text>"

  # Staff mode and the inventory audit.
  usage-freeze: "<text>/sac freeze <player></text>"
  usage-inv: "<text>/sac inv <player> · /sac ec <player></text>"
  usage-find: "<text>/sac find <item> [name contains]</text>"
  unknown-item: "<bad>No item called </bad><value>{item}</value>"
  staff-on: "<value>Staff mode on.</value><text> Your things are kept safe until you leave it.</text>"
  staff-off: "<text>Staff mode off. Everything you had is back.</text>"
  staff-recovered: "<text>You were still in staff mode from before the restart.</text>"
  staff-tool-inspect: "<value>Inspect</value>"
  staff-tool-inspect-lore: "<text>Right-click a player to look in their inventory.</text>"
  staff-tool-freeze: "<accent2>Freeze</accent2>"
  staff-tool-freeze-lore: "<text>Right-click a player to hold them where they stand.</text>"
  staff-tool-random: "<value>Visit somebody</value>"
  staff-tool-random-lore: "<text>Right-click to jump to a random player.</text>"
  staff-tool-vanish: "<good>Vanish</good>"
  staff-tool-vanish-lore: "<text>Right-click to disappear.</text>"
  staff-tool-unvanish: "<text>Come back</text>"
  staff-tool-unvanish-lore: "<text>Right-click to be seen again.</text>"
  staff-tool-exit: "<bad>Leave staff mode</bad>"
  staff-tool-exit-lore: "<text>Right-click to get your own inventory back.</text>"
  frozen: "<bad><bold>You are frozen.</bold></bad> <text>Staff are talking to you. Do not log out.</text>"
  frozen-reminder: "<bad>You are frozen.</bad>"
  unfrozen: "<good>You can move again.</good>"
  froze: "<value>{player}</value><text> is frozen.</text>"
  unfroze: "<value>{player}</value><text> can move again.</text>"
  visiting: "<text>Dropped in on </text><value>{player}</value><text>.</text>"
  nobody-to-visit: "<text>Nobody else is online.</text>"
  audit-title: "<value>Who has {item}</value>"
  audit-header: "<text>{total} of </text><value>{item}</value><text> across {players} player(s) online.</text>"
  audit-row: "<value>{player}</value><text> · {total} · {where}</text>"
  audit-none: "<text>Nobody online is carrying {item}.</text>"
  audit-open: "<white><icon:check></white><value>Look at {player}</value>"
  audit-open-tip: "<text>Open a copy of {player}'s inventory.</text>"
  audit-goto: "<white><icon:teleport></white><text>Go to {player}</text>"
  audit-goto-tip: "<text>Teleport to them.</text>"
  audit-inv-title: "<muted>{player}'s inventory</muted>"
  audit-ender-title: "<muted>{player}'s ender chest</muted>"
  menu-staff-on: "<good>■</good> <value>Staff mode on</value>"
  menu-staff-off: "<muted>■</muted> <text>Staff mode off</text>"
  menu-staff-tip: "<text>Vanish, the staff hotbar, and your own inventory kept safe.</text>"

# One thank-you line with the BuiltByBit review link: once in the console after install and once
# in chat for the first operator who joins. It never repeats and sends nothing anywhere. false turns it off.
review-reminder: true

Config guide

The annotated summary from the store page. Where it and the default file above differ, the file is what the plugin ships.

gate:
  max-ping: 300          above this nothing flags
  min-tps: 17.5          below this nothing flags
  grace-ms: 3000         after join / teleport / respawn / world change
actions:
  enabled: false         turn on only once you trust the alerts on YOUR server
  certain-only: true     only CERTAIN flags can run an action
  gate:
    min-flags: 3         separate certain flags of the same check...
    min-spread-seconds: 3
    window-seconds: 120  ...inside this window, on a healthy server
ai:
  enabled: true
  may-punish: false      the "ai" check never acts unless this is true
  flag-score: 0.90
discord:
  webhook-url: ""        empty = nothing is ever sent anywhere
checks:
  reach:
    enabled: true
    alert-at: 5
    actions:
      "20": ["kick {player} Unfair advantage ({check})"]

Placeholders in actions: {player}, {uuid}, {check}, {vl}, {detail}, {ai}. Each check has its own thresholds; the file explains every one in a comment. An existing config.yml keeps working: the new checks only alert, and certain-only mode is added by one line.

FAQ

Which servers and versions does it run on?

It needs Paper or a Paper fork such as Purpur, because it uses Paper-only APIs (the dialog API for its menus, Adventure text components, Paper player profiles). Spigot and CraftBukkit are not supported. plugin.yml declares api-version 1.21, the jar is built against Paper API 1.21.11, and it needs Java 21 or newer.

Does it need other plugins?

No other plugin is required (plugin.yml has no hard dependencies).

How do I apply config changes?

Edit plugins/SentinelAC/config.yml and run /sac reload (reload the config). A restart works too.

Does it run on Folia, or for Bedrock players?

Not tested on Folia, and plugin.yml does not declare folia-supported. Bedrock players (through Geyser) have not been tested either.

What should I not expect from it?

No anticheat catches every client, and this one does not claim to. SentinelAC is built to stay quiet when nothing is wrong, to show its evidence when something is, and to punish only what you configured. It is a tool for your staff, not a replacement for them.

What licence do I get?

The licence terms are the BuiltByBit EULA shown on the listing. There is no licence key to enter, and the plugin never contacts a licence server.