Commit Graph

829 Commits

Author SHA1 Message Date
Ludovic Marie 09e179804c Merge pull request #680 from ludovic111/perf/mod-content-memory
🤖 Agent: merged by Ludovic's AI agent. Parity 291/291 on the head; combined with main locally: workspace clippy clean, client/world tests pass, mob_ai/save_roundtrip/spawner PASS. ATM10 registry 249 → 125 MB, vanilla 46 → 24 MB.
2026-10-17 19:41:47 +02:00
Ludovic Marie 900e0af776 Merge commit 'HEAD@{1}' into HEAD 2026-10-17 16:51:55 +02:00
Ludovic Marie eda6e4b510 Merge feat/wide-block-states (with origin/main) into perf/mod-content-memory
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:32:46 +02:00
Ludovic Marie f49af8d995 Launcher releases: count every crate the launcher builds in
LAUNCHER_PATHS and launcher.yml's push paths now list horizon-bench,
horizon-friends, horizon-mods and horizon-port-pack too, the launcher's
path packages per cargo metadata. A change there (like the horizon-mods
dependency that left launcher/Cargo.lock stale) then builds and releases
the launcher instead of going unnoticed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 14:59:01 +02:00
Ludovic Marie 002f14ce53 Merge pull request #692 from ludovic111/feat/locator-bar
Locator bar for vanilla clients on Horizon servers (waypoint packets as vanilla sends them)
2026-10-17 14:56:09 +02:00
Ludovic Marie 732e77a7ad Merge pull request #694 from ludovic111/feat/server-usercache-2
usercache.json, and console names resolved through it as vanilla does (stacked on #589)
2026-10-17 14:56:05 +02:00
Ludovic Marie 851d34f762 Merge pull request #589 from ludovic111/feat/server-online-profiles
Online mode: players named in commands are their accounts
2026-10-17 14:56:01 +02:00
Ludovic Marie 3e31144362 Merge pull request #690 from ludovic111/fix/mob-float
Mobs float in water instead of bouncing
2026-10-17 14:19:03 +02:00
Ludovic Marie 78e3206133 Merge pull request #673 from Online5277/fix/render-distance-settings-cap
Allow render distances up to 64 chunks in settings
2026-10-17 14:13:30 +02:00
Ludovic Marie 98dc7b17b7 Merge pull request #608 from rdbtCVS/perf/versioned-behaviours-cache
Cache resolved block behaviours in versioned binary snapshots
2026-10-17 11:33:47 +02:00
Ludovic Marie b859b22111 Keep usercache.json and find console players through it, as vanilla
Port CachedUserNameToIdResolver to the dedicated server, in place of the
in-memory name cache: usercache.json in the universe folder is loaded at
startup (bad entries skipped, a bad date warned about), players are added
when they join and kept for a month, and the file keeps the 1000 most
recently used, newest first, as Gson writes it.

The console's op, deop, ban, pardon and whitelist find players by name
through it, in any case. A name it doesn't hold is asked of the profile
service (profiles::find_by_name) on offline servers too, as vanilla does;
one the service doesn't know is the offline player of the lower-case name
on an offline server, and "That player does not exist" on an online one.

tools/test_user_cache_parity.py runs vanilla's server and Horizon's with a
mock profile service and compares the replies, the names looked up and the
files; the dedicated-server harness now compares usercache.json too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:33:02 +02:00
Ludovic Marie ed9a25ad49 Measure where content mods' memory goes; drop the registry's second copy
- perf_bench.py samples the physical footprint on macOS as well as
  RSS. RSS counts the pages the allocator freed and marked reusable,
  so the six mods' "+1.1 GB" is about 0.5 GB of footprint.
- examples/resource_memory.rs: live heap, peak, RSS and footprint after
  each resource loader, for a prepared or a content-mod root.
- BlockRegistry indexes ids by a hash of the state, checked against
  the states, instead of keeping every state twice: -22 MB live for
  vanilla, -124 MB for ATM10's 42 mods (193,762 states).
- docs/modpacks.md: the per-loader breakdown and the next cut (the
  per-state tables parsed whole at startup).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:44:05 +02:00
Ludovic Marie 171fbeb8ac Send vanilla clients the locator bar on dedicated and LAN servers
Port 26.3's ServerWaypointManager for the players of a served world: each
player who receives waypoints gets ClientboundTrackedWaypointPacket for
every other player who transmits one, as a block (in view), a chunk (one
chunk out) or an azimuth (really far), with the update thresholds,
breaking rules and team colours vanilla uses. Crouching, invisibility and
a carved pumpkin or mob head hide a player; spectators see everyone and
are seen by no one; the locator_bar game rule turns it all off and on.

tools/test_locator_bar_parity.py runs vanilla's server and Horizon's with
two vanilla-codec players and compares every waypoint packet each receives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:14:34 +02:00
Ludovic Marie 62f96e9d65 Mobs float in water instead of bouncing
FloatGoal's constructor lets the navigation float, so Mob.jumpInLiquid
adds 0.04F a tick; Horizon set that flag only for a few species, and
every other FloatGoal user (pigs, cows, wolves, cats, horses, witches,
illagers...) got the non-floater 0.3 and bounced out of the water.
Register it from the goal (and from the constructors that set it:
allay, armadillo, camel, goat, parrot, wither), and add 0.04 as the
float vanilla adds.

Mobs that drop back into water now splash (Entity.doWaterSplashEffect
from updateFluidInteraction, skipped on the first tick), with their
swim splash sound. The new mob_ai deep_water scenarios also caught:
horses register addBehaviourGoals last (none for the skeleton horse, no
mount panic for the zombie horse), RandomStandGoal waits the horse
ambient interval (400 for every AbstractHorse), and the cat's sit-on-
block goal draws its delay only for tame cats once the last ran out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:42:46 +02:00
Ludovic Marie 09c039c423 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-17 09:14:21 +02:00
Ludovic Marie bc69b88c5d Merge pull request #587 from ludovic111/feat/server-online-mode
Dedicated server: online mode, and refused logins in the log
2026-10-17 09:14:16 +02:00
Cosmic 310047c225 Allow render distances up to 64 chunks in settings and saved options 2026-10-17 02:55:27 -04:00
Ludovic Marie 4410c3ba44 Merge pull request #656 from LookImEcho-Rick/codex/fix-multiplayer-world-options
Fix multiplayer pause menu and World Options permissions
2026-10-17 08:39:44 +02:00
Ludovic Marie d85a16daf8 Merge main into feat/server-online-mode
lan/conn.rs imports: keep this branch's AtomicU32 and main's Mutex/PoisonError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 08:34:34 +02:00
Ludovic Marie ef3cd64475 Merge pull request #607 from rdbtCVS/perf/shared-block-json-20261017
perf: reuse decoded gameplay and behaviour facts at startup
2026-10-17 08:16:01 +02:00
Ludovic Marie 0a69d6b7bc Merge main and #607 into perf/versioned-behaviours-cache
level/data.rs, level/mod.rs: load, load_with_behaviour_json and
load_with_gameplay all go through one cached() helper (try the snapshot,
build and store on a miss), so #607's client path keeps this cache.
Resolution from contrib-review-1's validated integration, without #601.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 08:15:49 +02:00
Ludovic Marie f0295c1671 Merge remote-tracking branch 'pr/607' into HEAD 2026-10-17 08:01:45 +02:00
Ludovic Marie 9665a7c5b0 Merge pull request #609 from LookImEcho-Rick/fix/563-translucent-block-outline
fix(rendering): correct selection outline on translucent blocks
2026-10-17 08:01:13 +02:00
Ludovic Marie 4a8355047e Merge main into perf/versioned-behaviours-cache
Main's startup now falls back to vanilla resources when packs fail; the
behaviours snapshot folder is set just before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 06:42:38 +02:00
LookImEcho-Rick aeaccb0413 Fix multiplayer pause menu and World Options permissions 2026-10-16 23:39:36 -05:00
Ludovic Marie c5851e77aa horizon-bots: --explore-speed, for an explore pass with equal work
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:42:27 +02:00
Ludovic Marie 1a68d8ddf0 Merge main into fix/furnace-on-server
lan_actions.rs: clear the old body's potions (main), then respawn with
the tick context (this branch). hub.rs: keep main's effect and chunk
tracking reset before this branch's vanilla respawn sequence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:35:16 +02:00
Ludovic Marie 3cd26c6d0b server_bench: --gamerule, more game rules for both servers
Horizon has no locator bar yet, so equal-work runs turn vanilla's off
(--gamerule locator_bar=false): its waypoint updates cost every move
a pass over every other player.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:17:33 +02:00
Ludovic Marie c8003301bc horizon-bots: vanilla's chunk batch sizing, chunk-gated movement and chunk accounting
The bots acknowledge chunk batches with ChunkBatchSizeCalculator's
desired chunks per tick, from how long each batch takes to arrive,
instead of a fixed 25. A bot moves only while it holds the chunk it
stands in, as vanilla's client stops ticking its player in a chunk it
hasn't received, so no bot outruns a server's chunk sending. Each bot
keeps the chunks it holds, so the report separates chunks that entered
its view from chunks sent again while it still held them, and counts
the moves the server refused and the ticks it waited for its chunk.

server_bench.py runs one pass per behaviour (walk, explore), spawns
every bot on the world spawn itself (respawn_radius 0) so both servers
see the same paths, can run the bots on another machine (--bots-via,
--bots-bin, --connect) and the servers under nice, and reports new,
dropped and re-sent chunks per second, pull-backs and the server's
"moved too quickly" lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:17:33 +02:00
Ludovic Marie 5725ee0efa Give the shared-JSON profiler its own name
Every PR of the startup batch adds a different tools/profile_client_startup.py,
so any two of them conflict. This one becomes tools/profile_shared_json.py; its
guide and tools/profile_gameplay_decode.py follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:13:10 +02:00
Ludovic Marie bf851bfea1 Merge pull request #648 from ludovic111/feat/modpack-bench
Content mods: refuse more block states than chunks hold; the pack against NeoForge, measured
2026-10-17 05:01:42 +02:00
Ludovic Marie 02dce1093b Give the behaviour profiler its own name
Every PR of the startup batch adds a different tools/profile_client_startup.py,
so any two of them conflict. This one measures only Behaviours::load, so it
becomes tools/profile_behaviour_cache.py, and --cache-dir passes the snapshot
folder to the example so warm loads can be measured.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 04:58:28 +02:00
Ludovic Marie b0e31eb14f Merge pull request #632 from ludovic111/feat/neoforge-oracle-harness
Per-mod parity harness against a dev-only NeoForge oracle
2026-10-17 04:58:05 +02:00
Ludovic Marie 2822d4e1b7 Content mods: refuse more block states than chunks hold; the pack against NeoForge, measured
Loading the eight carried-over mods in the game (not only the probe) showed
two limits real packs reach. Block textures past 2048 layers are another PR;
this one handles block states and measures a pack.

- Chunk sections store a block state in 16 bits. Vanilla 26.3 has 35,723
  states; Macaw's four mods add 36,154, the eight 41,389. Past 65,536 ids
  wrapped onto other blocks (a trapdoor became a hanging sign).
  mod_content::build now refuses a set of mods past MAX_STATES and names the
  states per mod; the game then starts without content mods, as for any
  refused content.
- tools/mod_port_harness.py probes each mod on its own against vanilla, as a
  player adding it would load it.
- tools/perf_bench.py --game neoforge: Minecraft 1.21.1 on NeoForge 21.1.259
  with --mod-jars and --deps, driven by a dev-only NeoForge port of the bench
  mod (mods/horizon/src/neoforge-bench); --port-work loads a work folder's
  carried-over mods into Horizon. tools/neoforge_oracle.py installs the
  client once under .local/neoforge-oracle (the installer, then the vanilla
  libraries it inherits, sha1-checked) and builds the driver. A NeoForge
  start that crashes before the driver loads (FML reading fml.toml while
  creating it) is retried once.

Measured (docs/modpacks.md): six ported mods in Horizon against the same
mods on NeoForge, Mac mini M4 Pro, 2 games per row on a loaded machine. The
world is ready 3.1x sooner at 8 and 16 chunks, the flights run 1.7x and
3.6x the frames, and Horizon's RSS is not lower (+1.1 GB with the mods).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 04:55:20 +02:00
Ludovic Marie e7f7236c34 Open furnaces, blast furnaces and smokers on a server
Right-clicking a furnace as a guest did nothing: the host only handed
chests, barrels, hoppers, dispensers and shulker boxes over. It now opens
the furnace's own menu (minecraft:furnace, blast_furnace or smoker),
runs the guest's clicks through the host's AbstractFurnaceMenu (fuel slot,
take-only result, shift-clicks by recipe and fuel, a new input restarts
cooking) and keeps the client's progress arrows in step with
ContainerSetData. The dedicated server harness now opens a furnace and
smelts an ore in it on both servers.

Fixes #540

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 04:17:54 +02:00
rdbtCVS ef633b252f test: run parity from worktrees and document startup profiles 2026-10-17 02:37:44 +01:00
rdbtCVS 73ac737ccf perf: release startup decode buffers and profile parsing tradeoffs 2026-10-17 02:26:56 +01:00
Ludovic Marie 66d187ed06 Respawn on a server in vanilla's order, so vanilla clients leave the loading screen
A vanilla 26.3 client that died on a Horizon server stayed on its loading
screen after pressing Respawn (#566): ClientPacketListener.handleRespawn
starts a LevelLoadTracker that waits for the level info's "level chunks
load start" game event and the chunk under the player, and Horizon's
respawn sent neither the level info nor the chunk cache centre.

The hub's respawn now follows PlayerList.respawn: the entities paired with
the dead player are removed (one packet each), its chunks are dropped
without ForgetLevelChunk (as PlayerChunkSender does for a dead player) and
sent again, the abilities, then Respawn (with the last death location),
the teleport to the spawn facing its direction, the default spawn,
difficulty, experience, post effects, the level info, the permission
level, the chunk cache centre and the inventory. A dying guest also gets
the death animation event and no health update while dead, as vanilla's
ServerPlayer.die does.

Guests' commands also ran at the host's permission level: an operator on
a dedicated server could not run operator commands (and LAN guests ran
the host's level). They now run at the guest's own level, as the command
tree they are sent says.

The dedicated server harness covers it: after trading, the vanilla-codec
client is made an operator by the console, runs /kill, respawns, and the
packets from the death screen to the respawned inventory are compared
with vanilla's, then the level must load where it respawned. The client
also stops sending a movement packet beside each teleport acceptance
during the respawn: the 26.3 client sends the acceptance alone, and
vanilla's server kicks a second position in the same tick.

Fixes #566

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 03:11:16 +02:00
rdbtCVS cd61e11b56 perf: intern gameplay projection strings and verify resource overrides 2026-10-17 01:42:34 +01:00
rdbtCVS cf837118e9 perf: defer behaviour construction and test fresh resource loads 2026-10-17 01:18:24 +01:00
LookImEcho-Rick 5d614f1cf6 fix(rendering): correct selection outline on translucent blocks 2026-10-16 19:03:48 -05:00
rdbtCVS 78791b79ce Cache resolved block behaviours across launches 2026-10-17 01:03:21 +01:00
rdbtCVS a01d47f28f perf: share decoded block facts during client startup 2026-10-17 01:02:09 +01:00
Ludovic Marie ae22d5dd96 Online mode: players named in commands are their accounts
An online-mode server checks players by their account's UUID, so op,
whitelist, ban and pardon now resolve a name as vanilla's
CachedUserNameToIdResolver does: a player on the server, then one seen
since the start (in memory; vanilla also keeps usercache.json), then
the services' profiles/getByName endpoint through the discovery
document. A name with no account is "That player does not exist", with
vanilla's "Couldn't find profile with name" warning. Offline servers
keep the offline profiles.

horizon-server does the same while the server is stopped (op,
whitelist add, and the players init writes), and removes entries by
the name already on the list. `init --offline` (or HORIZON_ONLINE=false)
sets online-mode off; init's note now warns about offline servers only.

The online-mode harness adds a sixth login: `whitelist add` for the
player who joined earlier needs no lookup and lets them in, and an
unknown name is looked up and refused, on vanilla's server and on
Horizon's (43 checks).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 01:31:19 +02:00
Ludovic Marie e7003e7d49 Dedicated server: online mode, and refused logins in the log
online-mode=true now authenticates players as vanilla's
ServerLoginPacketListenerImpl does. The server makes a 1024-bit RSA key
pair at startup ("Generating keypair"), sends it with a random challenge,
checks the challenge and decrypts the shared secret from the client's key
packet, encrypts the connection with AES/CFB8, and asks the session
service's verify endpoint (hasJoined, through the services discovery
document) whether the player joined with the server's hash, from the
"User Authenticator #N" thread. A verified profile keeps its UUID and
properties, so skins show in the player list; an unverified one is
refused with "Failed to verify username!", and an unreachable service
with authservers_down. The offline-mode warnings are only printed in
offline mode.

Refused logins are logged as vanilla logs them: "Disconnecting
<name> (/<address>): <reason>" and "<name> (/<address>) lost
connection: <reason>", with log-ips respected.

horizon-protocol gains RSA key generation (Miller-Rabin primes,
e = 65537), the X.509 encoding of the public key and PKCS#1 v1.5
decryption. test_online_mode_parity.py runs its logins against Horizon's
server too, plus a verified player the whitelist refuses, and compares
the two servers' log lines about the player.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 01:31:18 +02:00
Ludovic Marie 32f5f01e22 Per-mod parity harness against a dev-only NeoForge oracle
The harness the modpack design calls for, for data-tier mods:

- tools/neoforge_oracle.py installs a NeoForge 21.1.259 server (ATM10's)
  under .local/neoforge-oracle, builds the exporter in
  mods/horizon/src/neoforge-oracle (our code, never shipped), runs it
  headless with the user's jars and writes every block (class chain,
  states, per-state hardness, light, tool need, map colour, sounds,
  outline and collision; drops for hand, diamond tools, shears and silk
  touch; placement; BlockSetType and WoodType), item, recipe and tag of
  the mods' namespaces, plus the vanilla blocks for templates. About 30 s.
- examples/mod_port_probe.rs builds the content root from the carried
  mods and dumps what Horizon's loaders make of the same things.
- tools/mod_port_harness.py compares them per mod and writes
  <work>/mods/<id>/harness/result.json. 1.21.1 sound events map to 26.3's
  through vanilla blocks both have, and vanilla items newer than 1.21.1
  are not counted against a recipe.

On ATM10's own jars (MIT): Macaw's Trapdoors passes everything; Doors,
Fences, Paths, Glassential, Factory Blocks and Cloud Glass pass every
drop, recipe, tag and placement and fail only on what API 0.2 cannot say
(shapes and sound events of their own, blocks of classes no vanilla block
shares). Jars, facts and results stay in the work folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:27:20 +02:00
Ludovic Marie 820936b975 horizon-bots and tools/server_bench.py: the first server benchmark against vanilla
horizon-bots is a load client on horizon-protocol for any 26.3 server: offline login, configuration,
keep-alives, pings, teleports, chunk-batch acknowledgements, and movement at 20 Hz. The bots play in
creative and fly above their spawn point, so they need no world of their own and no server refuses their
moves; idle, walk, explore and mixed behaviours are deterministic from a seed.

tools/server_bench.py runs the same bots on Horizon's server (horizon-server run) and on the pinned vanilla
jar, each fresh from the same server.properties, and measures MSPT from each server's own /tick query,
RSS and CPU of the process tree, the chunks the bots receive, and the start time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:21:19 +02:00
Cosmic b27e4b9323 Skip Rust verify steps for content-only pull requests 2026-10-16 11:46:49 -04:00
Ludovic Marie b8f1011cb1 Merge pull request #474 from ludovic111/fix/lan-name-tags-through-walls
Name tags show through walls
2026-10-16 13:03:23 +02:00
Ludovic Marie 9c87672a0c Merge pull request #473 from ludovic111/fix/lan-trial-guests
LAN: trial spawners and vaults see the guests
2026-10-16 13:03:15 +02:00