🤖 Agent: CI green on eccfe3d9b (clippy, launcher, tests, python, verify). Telemetry server checked in its own workspace (fmt, clippy -D warnings, tests). Exact live count, the live count's history and /v1/live/history; #5235 is stacked on it.
- GET /v1/live serves exact playing, lan, versions and platforms (live::round is gone);
rare versions are still summed under "other".
- The telemetry server writes the live count down once a minute (totals only: playing,
lan, per platform) into live_minute (7 days), live_hour (90 days) and live_day (kept).
- GET /v1/live/history?range=24h|7d|30d|all serves the series ([start, average, peak]),
the range's peak, the all-time peak and the average per platform, cached a minute, on
the live count's rate limit.
- The launcher's "N playing now" opens playhorizon.dev/stats.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified: rebased on main after #5166/#5186, tools/server_admin_e2e.py passed (45 checks: the console attaches, runs say/list, shows their lines, detaches), pr_ready green, CI green.
🤖 Agent: proven on fdfa4fbfa (rebased on main 125875b84): pr_ready --quick green (1427 tests, clippy, fmt, 495 Python tests); CI green (parity 35/35 after a rerun of the known lunge_motion flake, untouched here); anticheat_e2e default set (combat with no server.toml, ground, off, prediction, timer) green; a real vanilla 26.3 client hits a cow with combat on and is never flagged; MSPT 60 bots: anticheat off 3.68/3.69, default 4.11/4.13, 0 flags.
The recent log, then every new line as it happens (the control API's event
stream), a header with the TPS, MSPT, players and RAM refreshed every second on
a terminal (an ANSI scroll region; no TUI framework), and a command line whose
lines run as console commands. Ctrl-D detaches and leaves the server running;
when stdout isn't a terminal it prints plain lines. The admin e2e attaches
through a pipe and runs two commands.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified: pr_ready green (36 harnesses affected, all passed), the e2e's 39 event checks passed on the Mac mini; CI caught a race in the stream test (an event between the headers and the first cursor read was skipped), fixed in bef1eff27, CI green.
Ludovic, 10-31: turn the anticheat on. Checks a vanilla client can't trip act by default; the
others run but only log until real players show no false positive. `[anticheat] enabled = false`,
or `enabled = false` in a check's table, turns them off.
New `combat` check, acting by default: vanilla's client attacks only from Minecraft.startAttack,
once a client tick, on the entity of the tick's hitResult, and swings (ServerboundPunchPacket) with
each attack. A client tick with more attacks than swings, or a second entity attacked in the same
tick, flags; from `setback` (2) on the attacks are dropped, `kick` 20. The swing counts before or
after the attack (older clients behind a version proxy swing first).
`timer`, `ground` and `prediction` default to setback, alert and kick 0 (log only); a log-only ground
check keeps the claim. The thresholds once trusted are in docs/anticheat.md.
horizon-bots swing after each attack as vanilla does; --no-swing and --attack A,B are the cheats for
tools/anticheat_e2e.py --check combat, which runs with no server.toml (the defaults).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`players [--json]` shows who is online, the operators, the whitelist and the
bans (a stopped server's from its files); `kick`, `ban`, `pardon`, `ban-ip`
and `pardon-ip` moderate the running server through the control API. The
admin e2e runs them too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified: pr_ready green, CI green (retargeted to main after #5163 merged), tools/server_admin_e2e.py passed with the API on server.toml's fixed [api] address and token (live difficulty change, engine.path refused, backup made, downloaded byte for byte and deleted).
🤖 Agent: verified: pr_ready green, CI green, tools/server_admin_e2e.py passed against a real dedicated server (players, op/deop, whitelist, ban/pardon by name and IP, kick, Prometheus). Base of the server-admin stack (#5165, #5166 follow).
GET /api/v1/events streams server-sent events: every log line, the
supervisor's phase, and the game's typed join, leave, kick, chat and death
events with the player's name and UUID (?types= picks some; Last-Event-ID or
?since= resumes). Without Accept: text/event-stream it answers the events
after since as JSON. The dedicated server records the typed events where it
logs them and answers `server-events SINCE` on its control endpoint; the
supervisor fetches them every 250 ms. The admin e2e streams a bot's joins,
chat (a raw chat packet), kick, leave and death.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
GET /api/v1/config shows server.toml's settings (the API token hidden);
PATCH /api/v1/config changes them as `config set` does, live where vanilla
can, but never [engine], [panel], [api] or the RCON, query and management
keys. DELETE /api/v1/backups?name= and GET /api/v1/backups/download?name=
(streamed from the file). server.toml's new [api] section fixes the API's
address and token for a program that runs elsewhere, such as a moderation
bot; loopback or tailnet only unless allow_public. Panel changes of any
method need the X-Horizon header.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
GET /api/v1/players lists who is online (address, ping, op level) and vanilla's
lists; POST players/{kick,ban,pardon,ban-ip,pardon-ip,op,deop} and
whitelist[/add|/remove] run vanilla's console command on the running game and
answer whether the list changed and the console's lines; GET
metrics/prometheus serves the latest sample as Prometheus text.
tools/server_admin_e2e.py runs it all against a real server with two bots.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_item_entities(dimension, min, max) lists the item entities whose
box meets a box (Level.getEntitiesOfClass(ItemEntity.class, box), as a
hopper's getItemsAtAndAbove asks); native_set_item_entity(dimension, id,
stack) changes one's stack or removes it when empty (ItemEntity.setItem,
discard), as a hopper's addItem does. On the level in place. API 41.
Fixes#5150. Part of #4953.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_level_random(dimension) hands a mod the level's own random
(vanilla's Level.random, level.getRandom()), the stream Horizon's ports
of vanilla draw from, for the level in place and for a parked one. None
on a client of a server and for a level that isn't open. API 41.
Fixes#5145. Part of #4953.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Lithostitched limited/guaranteed jigsaw elements beside main's YUNG rules. rdbtCVS's change request (a fallback pool's guaranteed entries were never tried) is fixed and approved at c556f5b5b. Main merged in (clean). CI green, parity included. pr_ready: only main's 8 macOS resource-pack tests fail (#5094).
Builds keep #4590's profiles, plan and timing and gain #3964's crate
check and post-build steps; post-build programs build with the chosen
profile. The strings #3855 retired stay out of the translations.
#3855 (in #3891) moved Native mods from Settings to the Mods page: the
build's time, how it ended and the Release build switch move with it, to
the instance's Native build row. The install notice #3855 removed stays
removed; the build record keeps #3891's per-mod sha256.
rdbtCVS's review: the "no build yet" skip covered every platform and every
failure (a launcher that failed four times, a script error). Now only the
macos-x64 entry skips, and only on a version list that parsed and had no
build for it (exit 3); anything else fails the step as before. Plus the
known limit in docs/native-mods.md: no Rust toolchain for Intel Macs yet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A move that lands farther than `[anticheat.prediction] tolerance` (0.03 blocks) from the replay
flags, weighing its offset in tolerances (at most 5 a tick); the setback sends the player to where
its moves last agreed. What the replay can't play yet excuses the tick and the ones after until the
moves agree again (40 ticks at most): another entity or player near (farther for a fast player), a
block change or an entity removal near the player that the client hadn't seen, a block change the
server hasn't sent, abilities the client hadn't seen, a moving piston, a chunk the client doesn't
have, a riptide launch, a teleport that keeps motion. Under an excuse the replay takes the client's
velocity, so it agrees again within a few ticks.
The replay now also nudges the player out of a block it stands in (LocalPlayer
.moveTowardsClosestSpace, worked out from the blocks as the game does for its own player), keeps
its body across setbacks, and plays the ticks that came in a burst with a teleport's accept before
starting again there, so their jump delay goes on through it.
The bot harness checks and the bench keep the prediction from flagging the bots, which move by
their own rules.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dedicated server's new `max_view_distance` (server.toml, off by default)
sends each player, past `view_distance`, the chunks the world has saved:
read on two reader threads, sent and dropped, never generated, loaded or
ticked. Bodies sent are kept five minutes (96 MB) for players walking back,
dropped when their column changes. A brake pulls the ring in by two chunks
while ticks average over 40 ms, never inside the view, and lets it out by
one every 5 s after 30 s under 25 ms; reads pause while the save's writer
is behind. The docs point at /pregen to fill the ring.
Fixes#5008
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified: pr_ready green except #4662's known test (fails on main with .local); server_knockback, dedicated_server, server_entities pass; a real Horizon client replays 638/638 ticks with offset 0.0 (effects given and cleared); anticheat stays off by default; CI tests, clippy and verify pass.