COWMODSКаталог модов

BS-AntiCheat

Movement, combat, world, inventory and packet-level cheat detection that's actually tuned not to flag your legit players. Install it, and your builders, miners and PvPers keep playing , while cheaters get caught.

Автор: boondocksulfur1,5 тыс.1обновлён 24 окт. 2026 г.
УправлениеТехнологииУтилиты

Описание

✨ Why BSAntiCheat?

  • 🧊 Folia-ready — runs natively on Folia and Paper from the same jar (verified against a real Folia server).
  • 🎯 Calm by default — the fiddly, false-positive-prone heuristics ship off; the reliable checks are on. No flag-storm on day one.
  • 🔧 Fully calibratable — 25+ thresholds tunable live with /bsac reload, no restart.
  • 📡 Transaction-based lag compensation — real ping/pong round-trip timing, not the coarse getPing(), so high-ping players aren't punished.
  • 🪶 Tiny — ~355 KB, no bundled bloat.
  • 🌍 Bedrock & legacy aware — auto-exempts Geyser/Floodgate and (opt-in) ViaVersion clients whose physics differ.

🛡️ What it detects

CategoryChecks
MovementSpeed, Fly, Hover, Teleport, GroundSpoof, Elytra/Riptide speed
CombatReach, KillAura (angle + multi), AimSnap, Criticals, AutoBlock
WorldNuker, FastPlace, Scaffold, FastBreak
VehicleBoat-Fly, per-type vehicle speed
PacketAutoClicker, BadPackets, Timer, crash protection, packet-flood
XRayore thresholds, ore/stone ratio, rare-ore combos, restricted zones
Inventory (opt-in)InventoryMove, ChestStealer, FastUse, BowSpam, AutoTotem

Movement is checked every tick (no sampling), ground state is server-authoritative (not the spoofable client flag), and grace windows handle knockback, teleports, joins and elytra landings so normal play stays clean.


⚙️ Enforcement

A violation-level (VL) system with decay drives configurable punishment tiers — run any console commands (warn, kick, ban, custom) at VL thresholds, with optional setback. Ships in report-only mode; flip the switch when you're ready.


🔌 Requirements

  • Paper or Folia 1.21.x
  • PacketEvents — required for the packet-level checks (AutoClicker, BadPackets, Timer, Crasher, PacketFlood) and lag-transaction system. Everything else works without it.

Optional: LuckPerms (group whitelist), PlaceholderAPI, Geyser/Floodgate, ViaVersion.


🚀 Quick start

  1. Drop the jar in plugins/ (plus PacketEvents).
  2. Start the server.
  3. Tune config.yml, apply with /bsac reload, and enable punishments when you're happy.

Calibrate for your server: set debug_mode: true, keep punishments off, watch the debug values while playing, tune anticheat.thresholds.*, then reload. Done.


📋 Commands & permissions

/bsac, /movealerts, /xrayalerts, /acsilent, /acwhitelist, /xrayores — admin/manage permissions, all with aliases. PlaceholderAPI: %bsanticheat_total%, %bsanticheat_vl_<check>%.


Actively developed. Prediction-level combat detection and reworked opt-in heuristics are on the roadmap. Feedback and bug reports welcome!

Последние версии

Все версии →
release

BS-AntiCheat 1.1.2

1.1.2
24 окт. 2026 г.126Minecraft: 1.21, 1.21.1, 1.21.2, 1.21.3, 1.21.4, 1.21.5, 1.21.6, 1.21.7, 1.21.8, 1.21.9, 1.21.10, 1.21.11, 26.1, 26.1.1, 26.1.2, 26.2, 26.3folia, paper, purpur
Скачать
Что нового
## [1.1.2] - 2026-10-24 ### Added - `alerts.notify_min_vl`: chat and Discord alerts only from this violation level on (default 1 = every alert); everything is still logged and listed in `/movealerts`. ### Fixed - Saving the whitelist, ore exclusions or silent mode no longer overwrites edits made to config.yml by hand since the last reload. - Placed ores are remembered in the chunk and survive restarts, so mining one's own ores is never counted as X-Ray. - PacketFlood/Crasher no longer log exempt players; no log entries are lost at shutdown while a database write is running. - `/bsac test` completes every check type. - Piston elevators that lift a player any height within one tick no longer raise TELEPORT. - Climbing on after an elytra glide ends in mid-air no longer raises FLY (free fall); hovering is still caught.
release

BS-AntiCheat 1.1.1

1.1.1
9 окт. 2026 г.640Minecraft: 1.21, 1.21.1, 1.21.2, 1.21.3, 1.21.4, 1.21.5, 1.21.6, 1.21.7, 1.21.8, 1.21.9, 1.21.10, 1.21.11, 26.1, 26.1.1, 26.1.2, 26.2, 26.3folia, paper, purpur
Скачать
Что нового
## [1.1.1] - 2026-10-09 ### Fixed - Timer no longer flags a connection that slows down and then catches up, even when every tick is only slightly late: late ticks are owed back by the backlog, and a backlog draining at network speed keeps its catch-up window open. - AutoClicker no longer reads a held button as 26 CPS while a packet backlog drains. - Fly/Speed no longer flag a player whose flight ended in mid-air (leaving creative, /fly off, a world forcing survival on join) until they land, at most 30 seconds.
release

BS-AntiCheat 1.1.0

1.1.0
7 окт. 2026 г.104Minecraft: 1.21, 1.21.1, 1.21.2, 1.21.3, 1.21.4, 1.21.5, 1.21.6, 1.21.7, 1.21.8, 1.21.9, 1.21.10, 1.21.11, 26.1, 26.1.1, 26.1.2, 26.2, 26.3folia, paper, purpur
Скачать
Что нового
## [1.1.0] - 2026-10-07 ### Added - Minecraft 26.1–26.3 support: new attack and punch packets (PacketEvents 2.14), spear reach, Lunge, wind charges, bouncy blocks and geysers, new mounts and physics attributes. - New checks: NoFall, Sprint (hunger/blindness/omni-sprint), Mace smash, CrystalAura/AnchorAura, PingSpoof and extended BadPackets. ### Fixed - Closed detection bypasses: ender pearl/chorus teleport grace, Timer pauses, packet bundling, oscillating or slowly sinking Fly/BoatFly, speed pulsing, dig-packet AutoClicker suppression, spoofed ground flag for InventoryMove, uncapped ping allowance. - Fewer false positives: Jesus at shores and piers, flight/gamemode changes, fast-moving reach targets, vein-miner bursts (Nuker/FastPlace), team-fight KillAura, X-Ray in overgrown caves and dense veins, knockback in cobwebs/powder snow, riptide while gliding. - Violation levels now survive a relog; punishment commands run in order on the correct Folia thread. - PerformanceAnalyzer migration no longer imports Discord or silent-player settings into fresh installs; foreign keys from earlier imports are removed. - Discord alerts share one rate limit per webhook and retry on HTTP 429; fallback log is flushed periodically. ### Changed - Timer check uses the client tick-end packet; lag detection uses a short window (per region on Folia). - Update notice shows clickable Modrinth and CurseForge links to operators on join. - Validation for all numeric config values; `clear --db` deletes by player UUID. - Console messages are English throughout; `/acsilent` category names come from the language files. - Various smaller thread-safety, memory and performance fixes. ### API - `ViolationEvent` now fires on the thread owning the player (region thread on Folia), one tick later than before.
release

BS-AntiCheat 1.0.6

1.0.6
7 сент. 2026 г.350Minecraft: 1.21, 1.21.1, 1.21.2, 1.21.3, 1.21.4, 1.21.5, 1.21.6, 1.21.7, 1.21.8, 1.21.9, 1.21.10, 1.21.11, 26.1, 26.1.1, 26.1.2, 26.2, 26.3folia, paper, purpur
Скачать
Что нового
## [1.0.6] - 2026-09-07 Live alerts from two production servers, worked case by case, and then a review pass over what that work left behind. Every false-positive entry below started as an alert somebody looked at and said "that is not what happened" — and in most of them the check was not merely mistuned but measuring the wrong quantity. Three of the fixes here replace a model rather than a threshold, and two proposals were built, measured against the servers' own history, and then thrown away because the data did not support them. ### Fixed — false positives - **Water movement is judged by water physics.** `isSwimming()` is only true in the horizontal swim *pose*. A player wading upright, surfacing, or carried by Dolphin's Grace is in water without ever entering it, fell through to WALKING, and was measured against the 0.4 dry-land cap — while the Depth Strider and Dolphin's Grace handling sat in a branch that was never reached. Live case: twelve SPEED alerts of 0.42–2.12 b/t, all labelled "walking", from a player with Depth Strider III and Dolphin's Grace. The fix applies the water cap as a **floor on the on-foot cap**, and deliberately does not reclassify the movement: the type gates far more than the speed limit — Step, Spider, GroundSpoof and the hover checks all require an on-foot type — so routing every in-water player to SWIMMING would have switched those off for anyone standing in a puddle. That was the first attempt, and it is the reason for the extra test that holds it shut. - **Holding the drop key is not clicking.** Every item dropped rides on a PLAYER_DIGGING packet and is accompanied by an arm swing, so holding Q emits one per tick and read as 26 CPS. Only the *mining* actions of that packet were excluded. The drop actions now suppress the swing too, through a deliberately short 200 ms window — mining's five-second safety cap would have turned one dropped item into a five-second free pass for a real clicker. - **A thick vein counts as one find, not as its block count.** The per-ore X-Ray threshold and the combined rare-ore count are block counts, and the hidden-ore test makes that worse rather than better: every block of a vein after the first is exposed by breaking its neighbour, so an entire deposit reads as hidden. The `xray_min_veins` gate was meant to prevent exactly this and could not, because the number it guarded stayed block-based. Live case: two fat deepslate veins produced 13 of 14 "hidden" diamonds and flagged an OP who had moved 3400 blocks of stone that hour. Each deposit now contributes at most `xray_max_count_per_vein`. - **Strip mining is recognised by its shape.** A new veto stands the counting checks down when the digging looks like a branch mine *and* the ore came out of the level it runs through. Calibrated against 4.5 months of this server's block log: honest strip miners hold one Y level to a standard deviation of 1.1–1.2, run 75–89 % of their digging as straight corridors, and take their ore from a 3.0–3.6 block band around them. The ore-band requirement is what keeps it from becoming a hiding place — without it a player could strip-mine honestly for ten minutes and have the veto cover the detours that were worth hiding. - **The Timer check no longer punishes a bad connection.** A link recovering from a stall delivers its backlog faster than one packet per tick but never all at once, so no single gap reaches PACKET_GAP_MS and the stall grace never engages while the balance climbs the whole way. Live case: a player at 1275–1444 ms round trips reached a balance of 779 ms and was flagged. Neither the height nor the duration of that excursion separates it from a hack — what does is that a catch-up drains and **plateaus** while a hack keeps gaining. The balance must now still be growing at the end of the excursion, and the required duration is extended by the measured round trip. The window is stretched, never the limit: a cheat that answers transaction pings late to fake latency then only delays its own flag instead of escaping it. - **A ballistic arc is not hovering.** The hover check counted any sample whose vertical speed stayed inside a ±0.08 band and never asked whether that speed was *changing* — but the apex of a slow arc sits inside the band for many samples in a row. Live case: dy ran +0.067 to −0.051 monotonically across all ten samples, with no potion, no gravity modifier and no teleport behind it (all three checked, not assumed). A hover *holds* its vertical speed; gravity keeps taking it away. `checkSustainedAscent` already judged motion this way — it asks whether a climb decays — and the hover check now does too. - **Combat checks have a teleport and join grace.** Every other check family had one: MovementChecker keeps recentTeleport and recentJoin, PacketChecker keeps graceUntil. Combat had none, so a hit landing while a teleport was still in flight was measured against a position no client involved had yet. It applies to **both** sides — being teleported yourself desyncs your view of everyone, and a target arriving next to you desyncs theirs. - **The same swing is no longer counted several times.** One hit does not always produce one event: item plugins that add their own damage fire EntityDamageByEntityEvent repeatedly with identical geometry. Measured here: of 327 hit groups, 83 arrived twice, 76 three times and 4 four times — same attacker, same victim, same aim angle to the degree. Every streak requirement in the combat checks was defeated by that, so a `reach_violations` of 3 was satisfied by a **single** over-reach hit. Deduplicated within one tick, before any streak is touched; weapon cooldowns put genuine hits hundreds of milliseconds apart. - **Ranged item abilities are no longer judged as melee hits.** This was the largest source of REACH alerts and it is not a threshold problem. MythicLib delivers ability damage as `LivingEntity.damage(amount, player)`, which Bukkit reports as ENTITY_ATTACK with the player as damager — the exact shape of a melee hit, from wherever the ability reaches. Live case: a sword whose right-click fires a VOID_ZAPPER beam with `length: 10.0`; all 17 of that player's REACH alerts (4.14 to 9.37 blocks) fell inside that range, and the server had 21 such right-click abilities configured. A real melee hit is announced by the client with an INTERACT_ENTITY/ATTACK packet; ability damage has none, because the player never attacked anything. Damage without that packet is now skipped by **all** combat checks, not just reach: aim angle and multi-target are equally meaningless for a beam, which hits what it covers rather than what is aimed at, and which reaching three targets at once is exactly the "3 targets in 250 ms" that was being reported. - **Reach latency is modelled as a distance, not a percentage.** `pingSlack` scales a limit, which is right for a speed (blocks per tick × slack) and wrong for a distance: what latency costs here is however far the target travelled while the hit was in flight, and that is speed × time. Measured in a four-way PvP session at 373–1519 ms round trips — the old model allowed 4.7–5.5 blocks while a sprinting pair separates by 4.2 to 17.0 in one round trip, and the alerts ran to 14.61, a figure the multiplicative model cannot produce and ordinary play can. The allowance is now additive and capped, and above `reach_max_ping_ms` the check **stands down** rather than compensating further: past that the server cannot tell where either player was, and a hole that grows with latency is one a cheat can open by answering pings late. - **Muted alert categories survive a disconnect.** Persistent preferences are read from the config once, at startup, so anything dropped from memory on quit was gone for the rest of the server's uptime. `cleanup()` matched the stored entry with an exact-string test, which only ever recognised the "everything muted" form — a player who had muted individual categories counted as non-persistent and got those alerts back on rejoin. - **The reach check's ping ceiling no longer switches off the rest of combat.** Above `reach_max_ping_ms` reach stands down, which is right — but the exit was a `return` out of the event handler, so it took KillAura's aim angle and multi-target count with it. Neither becomes less answerable as latency rises: an aim 90 degrees off the target is off however long the packet took, and the multi-target count is a question about time, not distance. On a link like the one behind the timer case above (1275-1444 ms) that is KillAura switched off permanently, and a cheat can put itself there deliberately by answering transaction pings late — the same trick the timer check is explicitly hardened against. - **A punishment tier no longer fires twice.** The crossing test compared the level before and after the violation, and decay runs on every flag — so a VL sitting at exactly 1.0 is 0.9999 a moment later, floors back to 0, and the next violation "crosses" the same tier a second time. With a kick in the tier that is a duplicate; with a ban or tempban it is a second punishment for a violation already punished. Tiers are now tracked as served and re-arm when the level has decayed to zero. - **A configured tier no longer fires one violation late.** Levels were truncated, and decay runs on every flag and every read — so three violations a few seconds apart total 2.93 and `floor` made that a 2. Every threshold therefore acted one violation later than configured; for a kick tier of 25 that is the 26th alert. The same truncation made a fresh violation read as *"no violations"* in `/bsac info` a millisecond after it was counted, which is exactly when an admin looks after an alert. Levels are rounded now: the Nth violation reaches level N, while one that has genuinely decayed away still reads as gone. Verified live on mc-test — before the fix the notify tier needed three flags and the kick four, after it two and three. - **The API event can no longer swallow a punishment.** `ViolationEvent` was fired unguarded on the punishment's critical path, so anything that made the scheduling throw took the tier below it with it — violation counted, nobody punished, nothing in the log. - **Punishment commands run on the target's own region thread.** A kick, ban or tempban reaches into that player's state, and on Folia an entity may only be touched from the region that owns it; the global region is not that region. On Paper both are the main thread, so nothing changes there. - **A rejected punishment command is reported.** `dispatchCommand`'s return value was discarded, so a typo in a tier, or a punishment plugin that had not loaded, looked exactly like a tier that never fired. Tiers that run are now logged, and an empty `tiers` section is called out the first time a level would have used it. - **A hover run that once decayed can flag again.** The drop is measured against the run's first sample and that reference never moved, so a run which exceeded `fly_hover_max_drop` once stayed silent for its whole length. Entering the still band from a rise (+0.06, then held at -0.01) keeps a player inside the ±0.08 band indefinitely with a permanent drop of 0.07: a hover descending 0.2 blocks a second that could never be flagged. Exceeding the drop now RESTARTS the run instead of only withholding the verdict, which costs the ballistic case nothing — it keeps falling out of the band anyway. - **An edited punishment tier is no longer restored beside the admin's own.** The reported case: "kick messages can't be edited, the plugin sets them back". The VL threshold is part of the KEY, so changing `"40"` to `"10"` looks to the config merge like key 40 was lost — and it put the shipped tier back, default kick message and all, next to the edited one, where it kept firing. Everyone changes that threshold, because 40 is out of reach for a per-check VL that decays every five minutes. Sections whose keys are the admin's data (`punishments.tiers`, `xray_thresholds`) are now left alone once they exist; a config that predates a section entirely still gets it in full. ### Added - **The plugin can punish without borrowing another plugin's commands.** A tier entry of `@kick [reason]` is carried out directly, and `@notify [text]` tells the holders of `bsanticheat.admin` and nobody else. `@kick` keeps every colour format in its reason — a dispatched command hands its text to whichever plugin owns `/kick`, and vanilla's passes it through as plain text — needs no such plugin installed, and behaves the same on Paper and Folia because it runs on the player's own region. With no reason given it uses `punishment.kick_default` from the language file, so the message is translated rather than copied into every server's config. Anything without an `@` is still run as a console command, so `/tempban` and friends are unaffected. - **The shipped tiers are staggered**: VL 12 notifies the admins, VL 25 kicks. Previously a single step at 40 — reachable only under sustained cheating, so switching punishments on appeared to do nothing at all. The low step costs nothing when an alert turns out to be a false positive, and nothing bans automatically; that decision stays with a human. - **`/bsac test <player> <CHECK> [count]`** raises a violation level by hand. There was no way to reach the punishment ladder without actually cheating — Speed and Fly need a modified client, Nuker and FastPlace need rates no hand achieves — so an admin who configured tiers could not find out whether they worked until somebody tripped them for real. It runs the real `flag()` path, so levels, decay, tier matching and the commands behave exactly as they will in earnest; which also means it really punishes, and logs who asked for it. Capped at 50 per call. - **Every common colour format, mixable in one line.** Only `&0`-`&f` was ever translated, so `&#9863E7` and `<#9863E7>` reached the player as literal text. Messages now accept the classic codes, hex as `&#RRGGBB` and as `&x&R&R&G&G&B&B`, and MiniMessage (`<#9863E7>`, `<red>`, `<bold>`, `<gradient:…>`) — in any combination. The legacy rule that a COLOUR clears bold/italic is preserved, so existing language files render as before, and tags that are not colours are left alone so `/bsac <reload|info|version>` still reads correctly. One parser, in `Messages`, reached through `LanguageManager` — which is why no call site had to change. - **PlaceholderAPI placeholders are resolved in the plugin's own messages.** The expansion that PUBLISHES violation levels has existed since 1.0.0; nothing ever RESOLVED anybody else's placeholders. Alerts and punishment commands now do, against the player the message is about, and are unchanged on servers without PlaceholderAPI. ### Changed - **ChestStealer is off by default.** The check judges the interval between container clicks, and vanilla shift-drag — hold shift, hold the button, sweep across slots — fires one InventoryClickEvent per slot crossed at 10–40 ms spacing. That is the exact band it watches, so one hand movement reads as a burst of inhuman clicking. Measured: a player sorting storage for two hours produced 96 alerts, and 88 % of their fast clicks landed on a slot *adjacent* to the previous one — a mouse path, not a click rate. Running total over the check's life: 97 alerts, 0 real findings. No threshold fixes that: widen the floor past 40 ms and the check is dead, keep it below and it keeps catching sweeps. A ChestStealer's actual signature is a container standing **empty** a fraction of a second after it was opened, which is a question about container state and open duration rather than click spacing. The code is kept for that rewrite. An alert type that is wrong every time teaches everyone to ignore it, and then a real one goes unnoticed. - `/bsac reload` now re-applies `transaction_interval_ticks`. The ping task's period is fixed when the task is created, so reloading the config alone left the old interval running. - Teleports now reset **all** movement streaks, not only speed and fly. Every one of them is built from deltas against a position the teleport just invalidated. ### Fixed — correctness - `DatabaseManager` uses a bounded queue instead of a `ConcurrentLinkedQueue` guarded by a size check. That check ran on every logged violation and `size()` walks the whole queue — harmless while it is short, but it is only ever long during a database outage, i.e. exactly when every violation would pay for an O(n) scan. - `AsyncConfigSaver` no longer loses saves. The pending flag was checked while the saving flag was still set, so a request arriving in that gap found a save in progress, returned without scheduling anything, and was never picked up by anyone. - `FallbackLogger` drains into a batch before writing. Polling straight into the writer meant a failure part-way through — a full disk, a revoked permission, precisely the situations this file exists for — had already removed those entries from the queue. - `CombatChecker` uses a concurrent deque for its recent-target history. On Folia an EntityDamageByEntityEvent runs on the **victim's** region thread, so one attacker hitting entities in two regions reaches it from two threads at once. - `WorldChecker` no longer dereferences a rate window that a concurrent disconnect may have removed. - `UpdateChecker`'s fallback applies the same guard as the loop above it, instead of dereferencing a missing field on exactly the malformed entry that loop skipped. - `Exemptions.via` is volatile — the packet checks read it from Netty threads, which share no happens-before edge with the enable that wrote it. - Merged config keys arrive with the comments that ship above them. `mergeDefaults` only ever copied the value, so an admin updating the plugin got every new switch as a bare line while a fresh install got the reasoning — worst for exactly the keys that were calibrated against live data. - A `cheststealer_detection` left ON is reported at startup. The merge only ADDS keys, so a changed default never reaches a server that already has the line; the value is the admin's decision, so it is named rather than overruled. - All 33 `String.format` calls use `Locale.ROOT`. Previously mixed, so number formatting depended on the host JVM's locale. ### Diagnostics - `[REACH-DEBUG]` and `[TIMER-DEBUG]`: these two checks recorded nothing at all, which is why a 7.26-block reach alert and a 779 ms timer alert could not be taken apart afterwards. Reach logs from 80 % of its limit, not only on the flag. - `[COMBAT-DEBUG]` reports damage skipped as a plugin ability. - `[HOVER-DEBUG]` carries the absolute height and the measured drop. - The AutoClicker line carries the connection's current packet rate. A burst of arrivals and a fast hand both raise the click count, and on 2026-08-25 twelve AutoClicker alerts landed on a player who was mining continuously for 25 minutes — ten of them in the exact seconds Paper disconnected him with "You are sending too many packets". No threshold is drawn from this number yet; it exists so the next such case is decidable rather than arguable. - X-Ray logs the capped count and which veto fired. ### Investigated, not shipped Two proposals were built out, measured against 4.5 months of the server's own block log, and dropped. Both are recorded because the measurements are the useful part. - **A longer spoil window for X-Ray.** The false positive came from a player who dug for minutes and then spent one minute extracting, so the spoil that explained the ore had expired by the time the ore was counted. Widening the window fixes that case and does not separate anything: an X-Ray user tunnels straight from vein to vein and digs just as much, so a wider window hands *them* the searching exemption too. Reverted; the config keys remain with the reasoning attached, so the idea is not re-invented. - **A long-horizon ore-to-stone ratio.** The intuition was that honest mining has long dry stretches and X-Ray does not. Measured over 32 half-hour windows from four players, the honest maximum is 0.089 while a tunnelling X-Ray user computes to 0.04–0.13 — the two overlap, and a threshold high enough to avoid false positives (0.10) sits above where a typical cheat lands. A directedness proxy (what share of digging ends near ore) was tried next and separated no better: every honest player fell in a narrow 0.18–0.29 band, and an X-Ray tunnel produces the same, because only the tail of a corridor is near its ore. The exonerating side *is* calibratable and shipped (the strip-mining veto above). The incriminating side is not, on this data: there is no confirmed X-Ray case on either server to calibrate against, and any threshold picked without one is a guess wearing numbers. ### Tests 96 → 182. New suites for the mining-shape veto, the drop key, the timer balance, the melee tracker, the combat grace, alert-preference persistence, the reach check's ping ceiling, the colour and placeholder handling, and the punishment path — which had none at all, because every scenario elsewhere replaces the ViolationManager with a counter to keep MockBukkit's missing region scheduler out of the way. That is how a tier could fire twice unnoticed. Every behavioural fix was counter-checked by reverting it and confirming the new test fails. That caught four tests that passed for the wrong reason and proved nothing: two where a different guard was doing the work, one where the profile was one sample short of its threshold, and one whose "disabled" setting still satisfied the condition it was meant to break.
release

BS-AntiCheat 1.0.5

1.0.5
20 авг. 2026 г.82Minecraft: 1.21, 1.21.1, 1.21.2, 1.21.3, 1.21.4, 1.21.5, 1.21.6, 1.21.7, 1.21.8, 1.21.9, 1.21.10, 1.21.11, 26.1, 26.1.1, 26.1.2, 26.2folia, paper, purpur
Скачать
Что нового
## [1.0.5] - 2026-08-20 A code review pass, then two days of live alert data with `debug_mode` on. 1.0.4 fixed the elytra speed check's assumption that one move event equals one tick; this release finds the same assumption in the two places it was left standing, and closes several holes of the same shape — a check whose evidence never expired, a guard that covered less ground than it claimed, state that was only maintained on the paths that reached the end of the method, and a rate that measured the network rather than the player. ### Fixed — false positives - **A move event is no longer treated as one tick.** The ground speed and vertical checks compared a per-event delta against a per-tick threshold (0.4 blocks walking, 3.5 vertical). A move event is not reliably one tick: a client on a poor connection delivers one event carrying several ticks of travel, and judging that as a single tick multiplies the apparent speed by however many ticks were bundled. This is exactly the mistake 1.0.4 removed from the elytra check, where live data caught it as a 110→100 b/s "over-speed" curve from a routine rocket flight; the ground checks carried it unchanged. The delta is now divided by the ticks it actually spans, clamped at one so a sub-tick event is never scaled *up*. - **The catch-up move after a packet gap is no longer judged.** Per-tick scaling handles the ordinary case but cannot rescue the pathological one: a stalled connection flushing seconds of backlog produces a single delta large enough for the TELEPORT threshold, which no division makes innocent. A gap over 400 ms now leaves that one move unjudged — the movement counterpart of the rule the Timer check has had since 1.0.4. - **Vehicle speed is measured over real elapsed time.** `VehicleChecker` derived blocks per second from one move event × 20, the same assumption again, and a boat crossing loading chunks on an ice highway is precisely the case that breaks it. Speed is now averaged over a 250 ms window, so distance and elapsed time grow together. - **AutoClicker measures the click rate, not the packet arrival rate.** There are two ways to read a rate off arm-swing packets, and both fail upwards — in different situations. Counting arrivals in a sliding second puts the network in charge of the answer: a connection that delivers a tick's swings in bundles makes the count read whatever the bundling lines up with. Taking the typical interval instead is immune to that, but an interval is not a rate: it spans the whole consistency window, so a short fast burst fills the window with burst intervals and reports the speed *inside* the burst as though it were sustained. Both were observed in the field. A player placing blocks was flagged at 26 CPS while the interval median sat at exactly 50.0 ms — one server tick, the held-button cadence — through the entire run-up; the MAD of 49 ms against that median describes a bimodal arrival pattern, half the intervals near 0 ms and half near 100 ms, which is bundling and which also broke the held-button exclusion, since that wants the spread tight. Separately, three alerts of 29–30 CPS had arrival counts for the same second of 5, 8 and 10 — a handful of quick clicks in a row, which is an ordinary thing to do. The rate is therefore the **smaller of the two** estimates. Neither can fall below the true rate, so the minimum is safe in both directions: bundling is capped by the median, a burst is capped by how many clicks actually arrived, and genuinely sustained fast clicking raises both and is still caught. Note that raising `autoclicker_max_cps` does not help against either artefact — the run-up went through 22, 23, 24 and 25 without pausing. - **Rising is no longer counted as hovering.** The sustained-hover check counted every sample that was not *falling*, which made a climb indistinguishable from hanging in the air. Live data: four alerts fired while the player was moving UP at 0.12–0.20 b/t in a Trial Chamber — a Breeze, a wind charge or a Wind Burst mace throws a player upwards for far longer than the 2 s knockback grace lasts, and the tail of that arc is a slow climb with nothing underneath. Hovering now means what the name says: vertical movement inside one tick of gravity (±0.08 b/t). Falling resets the counter as before; rising does too. - **Nuker and FastPlace evidence now expires.** Both ask for several one-second windows over the rate limit before flagging, so that one bundled window — a vein miner, a burst of place packets — is not evidence. But the counter could only be reset by flagging: a window that stays *under* the limit produces no event on that path at all. The count therefore never came down, and unrelated bursts spread across a session added up until the third one flagged. The windows must now fall within 10 s of each other to count as consecutive, which is what "the rate was held up" was supposed to mean. `CombatChecker` already solved this the same way; these two were the ones that had not. - **The unloaded-chunk guard now covers the neighbourhood it reads.** It checked the chunk the player stands in, but the checks behind it read the player's *neighbours*: the ground scan samples all four footprint corners, the fall-slowing and liquid exemptions look sideways, and Spider/Jesus look at the walls. On a chunk border those reach into the next chunk, where an unloaded result reads as "nothing below the player" — and forces a synchronous chunk load from inside a movement handler, which is what Folia forbids. The guard now covers one block of margin in every direction, and the vertical checks stand down (dropping their streaks rather than resuming them across the blind spot) when it is not satisfied. ### Added - **Sustained ascent (opt-in, off by default).** Narrowing the hover band leaves a slow steady climb unwatched, so `anticheat.sustained_ascent_detection` covers it — by the one thing that separates thrown from flown. A ballistic rise sheds about one tick of gravity of vertical speed every tick and ends within a second or two; a climb that does not decay is not one anything threw. It ships off because it is a heuristic with no live data behind it yet: turn on `debug_mode`, watch the `[ASCENT-DEBUG]` decay values on your own server, then enable it. ### Fixed — other - **The plugin no longer overwrites a `config.yml` it did not change.** The config was saved on every shutdown regardless, which made the plugin the last writer of a file it had not edited. An admin who edits a threshold while the server runs and does not run `/bsac reload` had that edit silently replaced by the stale in-memory value at the next stop — on a server with a scheduled restart twice a day, within hours, with nothing in the log to explain it. Saving is now tied to the plugin actually having something of its own to write: the whitelist and ore-exclusion commands, and the validator repairing an invalid value. Everything else on disk stays the admin's. `/bsac reload` is still what makes an edit take effect on a running server; it just no longer costs you the edit if you forget it. - **Log rows carry the time of the violation.** The `ts` column was left to `CURRENT_TIMESTAMP`, which stamps the moment the row is INSERTed. Entries are batched and flushed every 30 s, so every alert in a batch shared one timestamp, up to half a minute after the fact — visible in the data as clusters of rows on the same second. That is exactly the column needed to line an alert up against the server log. The detection time is now recorded with the entry and written explicitly, in the same UTC format the default produced, so existing rows and queries are unaffected. The fallback logger, which receives whole batches at once during a database outage, had the same problem and takes the same timestamp. - **Setback no longer teleports to a stale position.** The movement handler returned early for whitelisted, bypassing, OP-exempt, creative and spectator players without recording where they were. The last known position is what a setback teleports to, so a spell in creative froze it at the point of entry: the first violation after returning to survival sent the player back there, possibly thousands of blocks and many minutes ago. Every exempt path now keeps the baseline current. (Only reachable with `punishments.setback` enabled, which is off by default.) - **The PacketFlood alert reports the rate it measured.** It reported the configured limit plus one — the same number every time, from which neither the severity of a logged flood nor a sensible limit could be read afterwards. - **Suspending checks under lag is announced.** Below `lag_exempt_tps` nearly every check backs off, so a server sitting at 17 TPS runs with the anticheat effectively switched off. It now logs when it stands down and when it resumes, after five confirming samples so a server hovering on the threshold does not log every second. ### Diagnostics `debug_mode` said nothing about the checks whose alerts most needed explaining, so they were instrumented: - **ChestStealer** had no debug output at all. It now logs the interval, click type, slot and streak for every counted container click, including the pairs below the physical floor that are deliberately ignored. - **The hover check** only logged on `getLogger().fine()`, which the default log level drops. The counting path now logs on INFO: vertical speed, what the ground scan found below the feet, the on-ground flag, the pillar grace and the tick span of the sample. - **AutoClicker** logs both rate estimates side by side (`cps=` and `arrivals=`), so a future alert shows at a glance which one is driving it. - **The X-Ray "OP is still being checked" notice** fired on every block broken — 2843 identical lines in one debug session, burying what the mode was turned on for. Once per player now. ### Build - **`maven-compiler-plugin` 3.11.0 → 3.14.0.** 3.11.0 cannot drive a JDK 25 toolchain: its incremental-compile scan throws `CompilerException: ConcurrentModificationException` before javac runs, so `mvn clean package` failed outright on any machine whose default JDK had moved past 21. The bytecode target is unchanged — `release` is still 21. ### Tests 74 → 96, each behavioural fix verified to go red against the behaviour it replaces. The AutoClicker fixtures reproduce the live statistics exactly — median 50.0 ms with a MAD of 49.0 ms for the bundled case, the 33 ms cadence for the burst — and assert the rates that follow (20, not 26; 8, not 30) while a genuinely sustained 30 CPS still flags. Ascent is covered as a pair: a ballistic arc raises nothing, genuine hanging still raises FLY, and the opt-in ascent check fires on a climb that never slows while ignoring one that does. Config ownership is covered byte for byte — Bukkit's `saveConfig()` rewrites YAML in its own style, so "was the file written" is directly observable against a fixture that is the shipped default verbatim, including an edit made without a reload having to survive the next shutdown. The rate-streak lapse is tested as a pure function with the clock passed in, so no test has to sleep for the length of the window it tests. Not covered end to end: the vehicle speed window, which needs a ridden vehicle MockBukkit cannot supply — the same gap `PacketChecker` has. ### Investigated, not changed — elytra over-speed Seven alerts of 204–311 b/s against the 140 ceiling, one player, one flight of roughly 1300 blocks. The hypothesis was the same class of bug as the rest of this release: a stalled client flushing its backlog delivers real distance with almost no real time attached, and the window that closes afterwards reports the lot. It did not survive a test — a stall poisons a single window, and the low-speed window the stall itself produces resets the streak before it can reach the three consecutive windows the check wants. Repeating the stall does not get there either. The data cannot settle it. Under 1.0.4 the `ts` column was the flush time, so all seven rows carry the same batch write and the only bound on the flight is "somewhere inside a 30 s window": 1300 blocks in 30 s is ordinary, in 4.5 s it is not. The geometry cannot break the tie either — the distance between alert positions and the reported speed both derive from the same 250 ms windows, so they agree whether or not the elapsed time was measured correctly. No change was made. A stall guard on the elytra path would have been a fix for an unproven cause, and a 3 s unjudged window after every 400 ms packet gap is something a cheat can ask for on purpose. The detection-time fix above is what makes this answerable next time; until then the elytra path at least has an end-to-end test that it fires on sustained over-speed, which nothing verified before.
release

BS-AntiCheat 1.0.4

1.0.4
15 авг. 2026 г.113Minecraft: 1.21, 1.21.1, 1.21.2, 1.21.3, 1.21.4, 1.21.5, 1.21.6, 1.21.7, 1.21.8, 1.21.9, 1.21.10, 1.21.11, 26.1, 26.1.1, 26.1.2, 26.2folia, paper, purpur
Скачать
Что нового
## [1.0.4] - 2026-08-15 False-positive fixes derived from live alert data on the Rattenkolonie server. Over one hour of ordinary play, one player produced 54 alerts across six checks — every single one of them wrong. Each fix below is tied to the alert pattern that exposed it. ### Fixed — false positives - **Towering up is no longer Fly.** Placing a block under your own feet and landing on it produces exactly the hover signature the check hunts for: the fresh block catches the player before gravity shows, so the fall the check waits for never comes, and at the apex of each jump the previous solid block has already dropped out of the support scan. A block placed within 3 blocks below and 1.5 sideways of the player's feet now suppresses the hover counter for 1.5s. The vertical-burst and GroundSpoof checks stay armed — pillaring produces neither a 3.5-block jump nor a player genuinely high above ground. *Live data: 31 hover alerts climbing Y 91→302, against 752 clay blocks placed and removed over the same Y range in the same minutes.* - **Cobwebs, powder snow and honey walls are no longer Fly.** All three slow a descent below `-0.08` blocks/tick, which the hover check does not count as falling, so the counter keeps climbing while the player is in fact sinking. `isSupportive()` does accept cobweb and powder snow as footing, but `supportDepth()` only ever scans DOWNWARD from the feet — a web holding the player at body height above a cave, or a honey block on the wall beside them, is never seen, and `clearlyAirborne` becomes true. Both halves of the hover signature, from standing in a mineshaft. Slow Falling was already exempt; these do the same thing physically and now are too. *Live data: two hover alerts at Y −14, each within a few blocks of a cobweb the player broke in the same second.* - **Riptide momentum is no longer Speed.** Vanilla clears `isRiptiding()` after the ~0.5s animation while the player is still travelling several blocks per tick, dropping them into the walking check at a 0.4 b/t cap. The post-glide grace is now 3s for riptide (elytra keeps 1.5s, where momentum drops fast). *Live data: SPEED alerts of 1.52 / 1.03 / 0.72 b/t — a decay curve — at the same second and coordinates as RIPTIDE alerts.* - **Elytra/Riptide speed is measured over real elapsed time.** The check derived b/s from a single move event × 20, assuming one event equals one tick. It does not: a packet gap while flying over loading chunks delivers one event carrying several ticks of travel, and multiplying it by 20 invents speed that was never flown. Speed is now averaged over a 250 ms window, so distance and elapsed time grow together and a gap is harmless. *Live data: a tidy 110→100 b/s "over-speed" curve from a routine rocket flight.* - **Elytra and Riptide ceilings raised to what vanilla actually does.** Riptide III launches at 3 blocks/tick = 60 b/s by design, but the ceiling was 50; rocket-assisted elytra dives legitimately reach 100–120 b/s against a ceiling of 100. Now 75 and 140. - **Holding the mouse button is no longer AutoClicker.** The held-button exclusion was keyed off the CPS count (accepted 18-22), and flagging began at 23 — two ranges meeting without overlap. But CPS is counted in a sliding window over packet ARRIVAL times: the client sends exactly one swing per tick (20/s), and network jitter bunches those arrivals so the window reads 21-23 while nothing about the clicking changed. Held swings then fell into the gap between "recognised as held" and "flagged". The exclusion now identifies a held button by the rate its swings arrive at — the median interval sits at one tick (50 ms) however much individual arrivals jitter — instead of by how many land in a window. A hand clicking at 23 CPS has a ~43.5 ms interval and is still not excluded, so closing the false positive did not open a hiding place. Median and MAD are used rather than mean and standard deviation, because a single pause between swings shifts a mean and explodes a deviation while leaving the median where it is. *Live data: four alerts, all reading exactly "23 CPS (Max: 22)", with no block broken at the time — swinging with nothing in reach, which the `START_DIGGING`-based mining exclusion never covered.* - **`autoclicker_max_cps` raised from 22 to 25.** Butterfly clicking reaches 20-25 CPS by hand, so the old cap sat inside human range. - **Pistons no longer read as Speed, Fly, GroundSpoof or InventoryMove.** A piston displaces a player without applying velocity — it fires no `PlayerVelocityEvent` — so the knockback immunity every other check relies on never engaged. The player is simply somewhere else next tick, which reads as movement they made themselves. Piston elevators, flying machines and door mechanisms all produce it. A new `PistonTracker` records where pistons fired into a bounded ring buffer; the checks consult it only on their would-flag paths, so a clock circuit costs nothing but an append. - **Timer no longer fires on a stalled connection.** A gap in the packet stream means the client stopped sending; what follows is it flushing the backlog, and every queued packet credits a full tick with no real time attached, so the balance climbs by the whole backlog at once. The existing sustained-excursion rule does not help — an eight-second backlog is not paid off in a few hundred milliseconds either. A gap over 400 ms now discards the balance and leaves the catch-up unjudged for 3 s. *Live data: two TIMER alerts, one of 8284 ms, inside the minutes a player was timing out and reconnecting.* - **InventoryMove no longer fires on momentum.** Vanilla friction needs several ticks to bring a sprint below the 0.15 threshold, and the player steers none of them. Judging now waits 1 s after the container opens, and skips airborne players entirely — walking off a ledge carries horizontal speed the whole way down with no key input behind it. *Live data: three alerts measuring 0.150 / 0.165 / 0.278 against a 0.15 threshold.* - **Slime launchers no longer read as Speed.** The slime grace was computed inside the vertical block, so only the fly checks ever saw it — but a slime launcher throws a player sideways just as readily, and the bounce needs no key input either. - **Dismounting at speed is no longer Speed.** Leaving a galloping horse or an ice boat hands the player its momentum; the grace that covers elytra landings now covers dismounts too. - **Nuker and FastPlace require repeated windows.** Both flagged on a single one-second window over the limit. Plugins that break several blocks per action (vein miners, custom tools) fire a burst of events inside one tick, and instamining with Efficiency V and Haste reaches ~20 blocks/s by hand — close enough to the cap that one bundled window crosses it. A cheat holds the rate up across windows; a burst does not. - **KillAura multi-target requires a streak.** Reach and aim angle each needed three suspicious hits; hitting several targets did not, so one burst flagged outright — and a crowded team fight legitimately puts three players within reach inside 250 ms. - **AimSnap now requires the flick to be fast in time, not just in packet order.** The check compares three consecutive rotation packets, which should span ~2 ticks, but nothing bound how far apart they actually arrived. A packet gap handed it three rotations seconds apart — and turning to look at something and back is ordinary over a second. Capped at 150 ms. - **FastUse reads the item's own use time.** The 600 ms floor assumed vanilla eat times; custom consumables that are legitimately quicker were flagged for being what they are. The floor is now the lower of the configured value and what the item actually needs. - **Mining outside the overworld is accounted for.** `STONE_TYPES` — the spoil that forms the ratio's denominator and decides whether a player counts as searching — listed only overworld rock. Netherrack was absent, so in the nether the ratio check could never run at all and `stoneMined` stayed at zero however much was dug. Ancient debris has a threshold of 3, is a rare ore, sits buried in netherrack (so it reads as hidden) and generates in scattered singles (so it passes the vein requirement): hunting it produced alerts with nothing able to account for the rock moved to find it. Netherrack, basalt, blackstone, soul sand/soil, end stone, sandstone and dripstone now count as spoil. The per-ore thresholds and the combined rare-ore count knew nothing about spoil, so a tunnel through ore-rich rock could pass a per-minute threshold on luck alone — copper (40), coal (30) and iron (25) all sit within reach of a good vein or two — with nothing in either check able to see the hundreds of blocks of stone that explain it. Someone moving that much stone is visibly searching, which is the opposite of what X-Ray is for. Above `XRAY_MIN_STONE_FOR_RATIO_CHECK` stone in the window, checks 1 and 3 now stand down and the ore-to-stone ratio decides; below it, they decide and the ratio stands down. The two cover disjoint cases instead of overlapping, and nothing falls between them. The broken-block record this relies on is size-capped like the placed-block one; at several hundred blocks a minute per player, waiting for the 5-minute cleanup would let it reach tens of thousands of entries in between. *Trade-off, stated plainly: a player who digs spoil as camouflage now only has to beat the ratio. With ten hidden diamonds that means ~67 stone at the default 0.15 — they have to genuinely dig, but it is reachable. Live data from the source server shows every real tunnelling minute at a rare-ore ratio of 0.000 across up to 378 blocks of stone, so the 0.15 default has a great deal of slack in it and is worth revisiting.* - **X-Ray counts only ore that was hidden.** This is the flaw underneath all three X-Ray checks, and it inverts their central assumption. The detector treats "much ore, little stone" as suspicious — but X-Ray tells someone where ore is that they *cannot see*, and acting on that means **digging to it**, which produces stone. Clearing an open cave means taking ore off walls that were visible all along and digging *nothing*. The player who never touches stone is the one doing it legitimately; the ratio points the wrong way. Ore is now recorded with whether it was exposed to open space when broken, and the thresholds, the ore-to-stone ratio and the combined rare-ore count all judge hidden ore only. Faces the player opened themselves inside the window do not count as exposure, so a tunnel dug straight to a concealed vein still counts against them. Off via `xray_require_hidden: false`. *Live data: three X-Ray alerts against a player who had entered an untouched cave system — 283 lava blocks, amethyst geodes and creeper explosions across the same coordinates, and no one had ever mined there. 45 ore against 12 stone: the signature of a cave, not a cheat.* - **X-Ray counts deposits, not blocks.** The per-ore threshold looked only at how many ore blocks were broken in the window, which says nothing about how they were found: one thick vein produces the same number as a dozen scattered ones. Copper and redstone veins run past 20 blocks, a lucky pair of overlapping diamond veins reaches ten, and any vein-miner style tool takes a whole vein in a single action — all of them crossed a threshold without anyone knowing anything they should not. What X-Ray actually provides is knowing where several SEPARATE deposits are without searching for them, so the ore must now also come from at least `xray_min_veins` distinct veins (blocks within 2 on every axis, linked transitively, count as one). The vein count is computed only for an ore that already crossed its threshold, so normal mining pays nothing for it. *Checked against live data: the three X-Ray alerts of 2026-08-15 drew from 4, 4 and 3 distinct veins and still fire — this narrows what counts as evidence without blunting it.* - **Vertical checks skip unloaded chunks.** A block scan in a chunk the server has not got in memory finds nothing and reports "airborne" for a player standing on solid ground — and reading it would force a synchronous chunk load from a movement handler, which Folia forbids outright. ### Added - **A test suite (74 tests).** The project had none, so every fix in this release was verified by compiling it and reasoning about it — which is how several of them nearly shipped wrong. `mvn test` now runs JUnit 6 with MockBukkit, both as unit tests over the decision logic and as **scenarios driven through real event sequences** — a player walking, towering, being shoved by a piston, emptying a cave, digging a tunnel. That level matters because it is where the live false positives happened: no single sample was wrong, a counter simply never reset. Every scenario is written as a pair — the false positive that must fall silent, and the detection that must survive it — because an exemption is only worth having if what it exempts is still caught. Scenarios cover: - **AutoClicker** — the held-button cadence: jitter must not break the exclusion, and a hand clicking at 23 CPS must not slip into it. - **X-Ray deposits** — one thick vein counts once, scattered finds count separately. The suite replays real coordinates from the 2026-08-15 alerts, so a change that quietly stops detecting them fails the build rather than the server. - **X-Ray visibility** — ore on a cave wall is visible; ore behind a tunnel the player just dug is not. This is the distinction the whole detector now rests on. - **Fly exemptions** — cobwebs, powder snow and honey walls are exempt, open air is not. - **Pistons** — displacement is covered near the piston, not across the map or into another world, and a clock circuit cannot grow the buffer without bound. - **Latency slack**, which widens nearly every threshold in the plugin. Making this testable meant lifting six functions out of their event handlers into package-private, state-free forms (`isHeldButton`, `countVeins`, `wasVisible`, `isInFallSlowingBlock`, `median`, `medianAbsoluteDeviation`), and answering air and full blocks from the material alone in `isSupportive` before consulting `isPassable()` — which is also one fewer block-state lookup per ground scan. No behaviour changed. **Verified by mutation:** each of the twelve fixes in this release was broken on purpose and the suite confirmed to go red — the cobweb exemption, the pillar exemption, the piston and slime exemptions, the Nuker/FastPlace/KillAura streaks, the InventoryMove grace, and all four X-Ray rules (visibility, veins, the searching gate, netherrack as spoil). A green suite that cannot go red is worth nothing: two scenarios passed at first for the wrong reason and were only exposed this way — a cobweb placed at foot level registers as footing and never reaches the exemption, and a mock player defaults to not being on the ground, which silently disabled the InventoryMove check entirely. Not covered: anything inside `PacketChecker` end to end (AutoClicker, Timer, AimSnap), which needs PacketEvents objects MockBukkit cannot supply. Their decision logic is unit tested instead. - `anticheat.speed_thresholds.elytra_bps` and `riptide_bps` — the two ceilings were compiled in, so a server whose item plugins grant faster flight had no way to adjust them. - `anticheat.thresholds.nuker_violations`, `fastplace_violations` and `killaura_multi_violations` — the streak requirements added above. - `anticheat.xray_min_veins` — how many separate deposits an over-threshold ore count must come from. - `anticheat.xray_require_hidden` — count only ore that was still buried when broken.
release

BS-AntiCheat 1.0.3

1.0.3
10 авг. 2026 г.46Minecraft: 1.21, 1.21.1, 1.21.2, 1.21.3, 1.21.4, 1.21.5, 1.21.6, 1.21.7, 1.21.8, 1.21.9, 1.21.10, 1.21.11, 26.1, 26.1.1, 26.1.2, 26.2folia, paper, purpur
Скачать
Что нового
## [1.0.3] - 2026-08-05 Full code-review release: correctness fixes found by auditing every source file against its own documentation, plus hot-path performance work and dead-code removal. No behaviour change for the default configuration except where a check was previously too weak. ### Fixed — false positives ### Fixed — movement - **Standing on entities is no longer GroundSpoof/Fly.** The ground scan only ever looked at blocks, so a player standing on a boat, a minecart, a horse or another player's head had "nothing below them" and was flagged. An entity check now runs — but only when the block scan came up empty, so it costs nothing during normal play. - **Lily pads are no longer Jesus.** The water-surface test accepted any non-water block at foot level, so walking across lily pads (and standing on a boat on water) read as walking on water. The feet block must now be air specifically. - **Jump Boost is exempt from the fly checks.** It raises both jump height and time spent rising, which fed the hover counter. The Step check had always excluded it. - **Depth Strider is compensated.** Without it a player with Depth Strider III swims at roughly walking speed and pushes against the swimming cap. ### Fixed — inventory - **InventoryMove had no knockback grace.** Any server-applied velocity (arrow or trident hit, wind charge, explosion, jump pad) moves a player with a GUI open without any key input. MovementChecker has always had this grace; this check was missing it. - **InventoryMove ignored plugin-granted flight.** A player flying via EssentialsX `/fly` is in survival gamemode, so the creative exemption never applied — they drifted with a GUI open and were flagged. Now exempt, as is the Velocity check for the same reason. - **ChestStealer had no lag guard.** After a lag spike the queued clicks all arrive in one tick and look like 0 ms intervals — an instant flag. Every other check already backed off under lag. ### Fixed — combat and vehicles - **Reach honours the `entity_interaction_range` attribute.** Item plugins grant long-reach weapons by raising it; judging those hits against the flat config value flagged players for using their own gear. - **Vehicle teleports are not speed violations.** `VehicleMoveEvent` carries no teleport flag, so a Multiverse portal or a plugin repositioning a ridden horse produced one enormous "movement". Implausible single-tick jumps now reset the streak instead. - **A Speed potion on a mount raises its ceiling.** A fast horse under Speed II otherwise blew past the flat per-type limit. ### Changed - **`autoclicker_min_signals` now defaults to 3 (was 2).** Markers 1 and 2 both measure jitter and rise together, so a human clicking steadily trips both — 2 was not the safe middle it appeared to be. Marker 3 (does the player ever pause?) is the independent signal. Documented in `config.yml`. ### Fixed — effects, items and attributes - **Checks now read the game's own attributes instead of hand-rolled multipliers.** The Speed potion was compensated by a hard-coded formula, so every *other* legitimate way a player gets faster was invisible: attribute modifiers from custom gear and item plugins, datapacks, mount/armour buffs — and `EssentialsX /speed`, which bypasses the attribute system entirely via `setWalkSpeed()`. The speed checks now derive their ceiling from `movement_speed` and `getWalkSpeed()`. Verified numerically identical for potions (Speed I → 1.2x, Speed II → 1.4x, exactly as before), so nothing was loosened for vanilla play. Vanilla applies the sprint boost as a `movement_speed` modifier, which is divided back out so the separate sprint threshold is not inflated by 30%. - **Sneaking uses the `sneaking_speed` attribute** — what Swift Sneak actually modifies — instead of a fixed 0.3, so any item or plugin granting faster crouching is respected. The enchantment is still read as a floor. - **Jump strength scales the vertical fly allowance**, step height is taken from the `step_height` attribute, and **reduced `gravity` exempts the hover check** — hanging in the air without descending is exactly what that check flags, and low gravity makes it legitimate. - **Body `scale` is respected**: a resized player has a proportionally wider hitbox (the ground scan now scans that width) and reaches proportionally further (reach ceiling). - **Swimming reads `water_movement_efficiency`**, the attribute vanilla maps Depth Strider onto, so items and plugins granting the same effect without the enchantment are covered. The enchantment lookup remains as a floor. ### Changed — Criticals is now opt-in - **`criticals_detection` now defaults to `false`.** Checking the vanilla rules against the implementation showed the condition ("critical **and** on the ground **and** fall distance zero") is a contradiction: the server only awards a critical when the player is airborne and falling, and `isCritical()` reports that same server-side decision. It can therefore never fire on legitimate vanilla combat — only on damage events synthesised by other plugins, i.e. it flags players for using custom gear. It also does not catch the real Criticals cheat, which fakes micro-falls so the crit is computed while the player is reported airborne. A working implementation needs per-tick vertical movement from the packet layer; until then the check is off rather than silently wrong. Consistent with the 30 days of production data available, where it never fired once. ### Fixed — the plugin now really is optional-dependency safe - **BSAntiCheat did not load at all without PacketEvents**, despite `plugin.yml` listing it only as a `softdepend` and the README promising the packet checks would simply switch off. The main class named PacketEvents types directly (a `PacketListenerCommon` field, and passing `PacketChecker` where a `PacketListener` is expected), so the JVM had to resolve them while *linking* the main class — long before the runtime guard in `onEnable` could run. Anyone installing the plugin without PacketEvents got a startup error instead of the documented degradation. All PacketEvents references now live in a single `PacketIntegration` class; loading *that* is what fails on such a server, inside the caller's `try/catch(Throwable)`. Verified by loading the main class against a classpath with and without PacketEvents. ### Fixed — correctness - **AutoClicker consistency analysis now actually exists.** `config.yml` documented seven options for it (`autoclicker_consistency`, `_min_samples`, `_min_cps`, `_max_deviation_ms`, `_max_cv`, `_max_outlier_ratio`, `_min_signals`) and the alert text was translated in both languages — but no code ever read them, so enabling it did nothing. Implemented as documented: three robotic markers (absolute jitter, jitter relative to click rate, share of human pauses), flagged when at least `min_signals` hold. Still opt-in; calibrate with `debug_mode`, which now logs sd/cv/outlier values. - **XRay ore/stone ratio was measured against a stale stone count.** The stone deque was only trimmed to the time window when stone was broken or by the 5-minute cleanup task, so a player who mined stone and then switched to pure ore mining kept an inflated denominator for minutes — silently weakening the check. It is now trimmed at read time. - **`/movealerts clear <player> --db` deleted only 3 of ~30 log types** (speed, fly, teleport), leaving every other check's rows behind while reporting success. It now deletes everything that is not XRay-owned, derived from a single shared constant so adding a check can no longer make it stale again. - **Log deletion could hit the wrong player.** The queries built `LIKE` patterns from raw names, and `_` is a single-character wildcard in SQL — clearing alerts for `A_B` also matched `AxB`. Patterns are now escaped (`ESCAPE '\'`). - **KillAura multi-target raised the violation level several times per incident.** Unlike every other check it did not clear its evidence after flagging, so each further hit in the window re-flagged and punishment tiers were reached far too fast. - **Setback is Folia-safe.** It was the only synchronous `teleport()` left in the plugin and ran inside a `PlayerMoveEvent` handler despite `folia-supported: true`; it now uses `teleportAsync()`. - **Setback no longer teleports to a stale position.** The grace windows (teleport, knockback, join, glide) returned early *before* the last-known-position bookkeeping, so a setback after a 2-second knockback window sent the player back to where they stood before it. - **An explicitly granted `bsanticheat.bypass` now works for OPs.** All three checks were guarded by `!isOp()`, so granting the permission to an OP-admin silently did nothing. The permission defaults to `false`, so OPs still never receive it implicitly. ### Performance - Ground detection does **one** scan per movement packet instead of two. The hover and GroundSpoof checks differ only in the depth they accept, so `supportDepth()` now returns the distance to the nearest support and both read it — ~35 fewer block lookups per movement packet per player. The slime/bubble-column lookups are shared the same way. - Whitelist, restricted-world and XRay-exempt-world lookups are cached as sets. They were hit on every movement, hit and block break, and each call allocated a fresh `ArrayList`. - **New `anticheat.transaction_interval_ticks`** (default `2`). The latency system sent a ping packet to every player every tick — 20 extra packets/second/player unconditionally. The default halves that; raise it further on high-population servers. Requires a restart. ### Changed - Velocity's climbable check uses the game's `CLIMBABLE` tag instead of a hand-written material list that missed the `*_PLANT` vine variants. - Alert numbers format with `Locale.ROOT`, so values no longer render as `4,20` on servers whose JVM runs in a comma-decimal locale. - The Discord queue processor releases its slot under the same lock that starts it, and in a `finally` — an alert queued during hand-off could previously wait for the next alert, and an exception would have wedged the queue permanently. - The PerformanceAnalyzer config migration resolves its path from the plugin data folder instead of the process working directory. - Gson is declared in `pom.xml` (`provided`) instead of being used via a transitive paper-api dependency. - Removed dead code: `db/TimeUnit`, nine unused accessors, a write-only tracking set in the movement alert manager, the unused German enum labels on `MovementType`, and the unused `general.prefix` language key. The five near-identical packet flag paths were merged into one.
release

BS-AntiCheat 1.0.2

1.0.2
16 июл. 2026 г.47Minecraft: 1.21, 1.21.1, 1.21.2, 1.21.3, 1.21.4, 1.21.5, 1.21.6, 1.21.7, 1.21.8, 1.21.9, 1.21.10, 1.21.11folia, paper, purpur
Скачать
Что нового
## [1.0.2] - 2026-07-10 XRay calibration release: live data showed legitimate beacon (Haste II) + Efficiency V deepslate branch mining still tripping the per-ore thresholds and the ore/stone ratio (10.45% vs. the 10% limit). - **Per-ore thresholds raised** for the deepslate-level ores: gold 10 → 15, redstone 10 → 20, lapis 8 → 15, diamond 6 → 10, emerald 4 → 6. - **Ore/stone ratio 0.10 → 0.15** — cave/deepslate miners legitimately exceed 10%. - **Combined rare-ore threshold 8 → 12** and now configurable (`xray_rare_combined_threshold`). - **New `xray_exempt_worlds` option:** skip XRay entirely in listed worlds (resource/farm worlds that get reset). Prefer raised thresholds over full exemption — cheaters mine in exactly those worlds. - **Threshold alerts now name the exceeded ores** (e.g. `[DIAMOND_ORE x11 (max 10)]`) in the alert and DB log — previously a logged alert couldn't be diagnosed or tuned against. --- ## [1.0.1] - 2026-07-09 False-positive elimination release, driven by live alert data from a production server (503 GroundSpoof / 314 Timer / 150 Speed / 62 Fly alerts — all confirmed false positives). **Verified in production:** deployed 2026-07-09; the prior baseline of ~200 false alerts per day (~1000 over 5 days) dropped to zero in the first 24 hours of normal play. ### Movement - **GroundSpoof/Fly ground detection rewritten:** all ground tests now check the four hitbox-footprint corners (not just the centre column — sneaking over an edge no longer flags), include the block at foot level itself (standing on trapdoors, slabs, carpets, snow layers), and use real collision (`isPassable`) instead of `isSolid`, plus explicit support for powder snow, scaffolding tops, cobwebs, climbables and liquids. - **Fly at ladders fixed:** climbing state now uses the game's own `isClimbing()` logic, and climbable blocks below the feet count as support (exiting the top of a ladder no longer accumulates hover violations). - **Speed on ice fixed:** the ice multiplier now survives sprint-jumps (3-block down-scan plus a 2.5s ice-momentum memory) — ice-road running no longer flags. - **Swift Sneak supported:** the sneak speed cap scales with the enchantment level (level 3 = 0.75x walking) instead of the fixed vanilla 0.3x. - **Server-applied velocity grants knockback immunity** (`PlayerVelocityEvent`): projectile knockback (punch bows, snowballs), wind charges, fishing-rod pulls and jump-pad plugins no longer trigger Speed/Fly. - **Slime-bounce grace:** high bounces are exempt during the whole rise (3s launch memory), not only while slime is within 2 blocks below. ### Combat - **Reach and KillAura (angle) now require 3 suspicious hits within a 10s window** (configurable via `reach_violations`/`killaura_angle_violations`) instead of flagging a single hit, and Reach is ping-compensated (same sqrt scaling as movement, fed by the transaction-latency system). - **AutoBlock default off:** vanilla allows attacking while an offhand shield is raised, so the check flags ordinary sword+shield play. Opt-in only. ### Packet & world - **Timer clock-drift leak:** real time is counted at 101%, absorbing benign client clock drift (~0.5% fast clocks accumulated ~10ms/s and periodically crossed the 200ms limit). - **FastBreak measures the expected dig time at dig start AND end and uses the lenient one** — landing from a jump or a haste beacon kicking in mid-dig no longer flags. - **Scaffold angle measured to the clicked block** (nearest hitbox point) instead of the placed block's centre — placing a block at your own feet (95–113° off aim in live data) no longer counts. - **Boat ice momentum:** the boat speed ceiling and Boat-Fly keep the ice multiplier for 3s after ice contact — bumps/gaps/ramp launches on ice roads no longer flag. - **InventoryMove exempts momentum and shoves:** sliding on ice with a GUI open and being pushed by nearby entities (mob crowds) no longer count. ### Infrastructure & calibration - **Lag detection reacts to spikes:** `isLagging` now also checks the 5-second MSPT average (`getAverageTickTime`) — the 1-minute TPS average barely moves during short spikes, which are exactly what distorts movement deltas. - Calibration from live data: `autoclicker_max_cps` 20 → 22 (legit butterfly clicking peaked at 21), `nuker_max_breaks_per_second` 15 → 25 (instamine reaches ~20/s), XRay coal 20 → 30 / iron 15 → 25 / copper 15 → 40 (1.18+ giant veins), XRay ratio check needs 60 mined stone (was 20) so cave miners aren't judged on tiny samples. Note for existing installations: config values already present in your `config.yml` are kept on update — apply the new calibration defaults (`autoclicker_max_cps`, `nuker_max_breaks_per_second`, `xray_thresholds`, `autoblock_detection`) manually or delete the keys so the new defaults merge in.
release

BS-AntiCheat 1.0.0

1.0.0
3 июл. 2026 г.27Minecraft: 1.21, 1.21.1, 1.21.2, 1.21.3, 1.21.4, 1.21.5, 1.21.6, 1.21.7, 1.21.8, 1.21.9, 1.21.10, 1.21.11folia, paper, purpur
Скачать
Что нового
## [1.0.0] - 2026-07-02 First public release. Live-tested and false-positive-calibrated on a real server, verified on both Paper and Folia. ### Detections - **Movement:** Speed, Fly (vertical burst + sustained hover), Teleport, GroundSpoof, Elytra/Riptide speed ceiling. - **Combat:** Reach (measured to the hitbox surface), KillAura (aim angle + multi-target), AimSnap (robotic snap-back rotation), Criticals, AutoBlock. - **World:** Nuker, FastPlace, Scaffold, FastBreak (per-block dig time vs. `Block#getBreakSpeed`). - **Vehicle:** Boat-Fly and per-type vehicle speed via `VehicleMoveEvent` (movement checks don't fire while riding, so this closed a real gap). - **Packet-level** (needs PacketEvents): AutoClicker, BadPackets, Timer, crash protection (oversized book/sign packets are cancelled), packet-flood — the last two stay active even under server lag. - **XRay:** per-ore thresholds, ore/stone ratio, combined-rare-ore, restricted-world zones, player-placed-ore exclusion. - **Inventory (opt-in):** InventoryMove, ChestStealer, FastUse, BowSpam, AutoTotem. ### Accuracy & internals - **No sampling:** every movement is checked (not every 10th), so a cheat can't hide between samples. - **Server-authoritative ground check** (bounding-box vs. the world) instead of the spoofable client flag — hardens Fly/hover against NoFall/Fly spoofing. - **Transaction-latency system:** a per-tick ping/pong measures true round-trip latency; movement, vehicle and elytra lag compensation use it instead of the coarse `getPing()`. - Grace windows for teleport, knockback, join/respawn/world-change and **elytra landings** (residual glide momentum no longer flags as Speed). - AutoClicker excludes mining swings; Reach measured to the hitbox surface; XRay resets its evidence after each flag (no VL spirals); Creative/Spectator exempt. ### Platform & integrations - **Folia support** via a Paper/Folia scheduler abstraction — verified against a real Folia 1.21.11 server with zero scheduler exceptions; behaviour on Paper is unchanged. - **Bedrock exemption** (`exempt_bedrock_players`) via Geyser/Floodgate detection. - **Legacy-client exemption** (`exempt_legacy_clients`, opt-in) via ViaVersion. - LuckPerms group whitelist, PlaceholderAPI (`%bsanticheat_total%`, `%bsanticheat_vl_<check>%`), Discord webhook alerts, bStats, update checker. ### Enforcement & operations - Violation-level system with decay and configurable punishment **tiers** (console commands), optional setback; report-only by default. - **25+ calibration thresholds** in `config.yml`, all tunable live via `/bsac reload`. - SQLite logging with a plaintext fallback logger; silent mode; bilingual (EN/DE); auto-migrating config. ### Defaults & notes - The false-positive-prone movement micro-heuristics — **NoSlow, Jesus, Spider, Step** — and **Velocity/AntiKnockback** ship **off by default** (opt-in). They need further hardening; the reliable checks are enabled out of the box. - `autoclicker_max_cps` defaults to 20 (skilled humans reach ~17–20; autoclickers exceed it). ### Notable fixes since the internal extraction - Correct shading: sqlite-jdbc/slf4j are `provided` by Paper (jar shrank from ~11 MB to ~355 KB). - Thorns damage no longer flags the victim as KillAura; sweep hits no longer cause bogus reach/angle flags. - DatabaseManager: no silent log loss (batch overflow, failed inserts, shutdown drains fully). - Thread-safety fixes (config/language published atomically); non-blocking command player lookups; Discord webhook timeouts. --- ## Origin Extracted from the PerformanceAnalyzer plugin (v2.3.4) as a standalone anti-cheat: movement and XRay detection with lag compensation, LuckPerms whitelist, Discord webhooks, SQLite logging, silent mode and bilingual support. Everything above builds on that base.