Commit Graph

497 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 9403347d13 Merge pull request #732 from ludovic111/docs/website-screenshots
🤖 Agent: merged by Ludovic's AI agent: images, captions and LEGAL.md only; verify passed; screenshots reviewed at full size.
2026-10-17 19:40:35 +02:00
Ludovic Marie c12e1dcd91 Website and README: fresh in-game and menu screenshots from today's Horizon
Six scenes from one fixed-seed world (seed 8675309): a plains village among
savanna hills, sunset over it and the river, moonrise over its lit square, a
crimson forest with a zombified piglin, tropical fish over a warm-ocean coral
reef, and survival at dusk with a zombie and the full HUD. Plus Horizon's own
title, Singleplayer, Video options and pause screens.

Captured with horizon-ctl screenshot from the offscreen client (fullscreen
2560x1286 on the Mac mini, default graphics settings, GUI scale Auto), scenes
cropped to 16:9 and menus to 16:10 (left-aligned) and scaled to the old sizes
(1600x900, 1280x800). The scene sources are JPEGs in docs/media again, the
names scripts/assets.mjs already reads; the README's WebP copies are the
rebuilt website media. Alt texts and LEGAL.md follow the new pictures.

The launcher screenshots are unchanged: the Mac mini's session is locked, so
no window capture works.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:38:37 +02:00
Ludovic Marie a6592eb188 Merge main into feat/wide-block-states
lan/chunks.rs: #706's per-section block entity lists carry u32 states.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 17:31:24 +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 a60519eb63 Merge origin/main into feat/wide-block-states
The LAN chunk encoder's section cache (#623) and dimension range (#668)
are kept; a cached section's wire bytes use the registry's palette width,
set once per process before any encoder exists, so the cache stays keyed
on the section alone. The sculk scan's palette check takes the u32 state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:02:53 +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 3135e1e3d1 Merge pull request #681 from ludovic111/fix/carry-unreadable
Carry: leave out the files the content loader would refuse
2026-10-17 12:39:21 +02:00
Ludovic Marie ed3ad72588 Merge pull request #653 from ludovic111/feat/port-pack-fetch
horizon-port-pack: a library, fetch and port; CurseForge zips name their mods
2026-10-17 12:39:13 +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 1f1d5a5aa4 Carry: leave out the files the content loader would refuse
Vanilla skips a resource with an invalid path, a data file that does not
parse, one at a time; Horizon's content loader refuses the whole mod
set. carry now keeps them in outside/ with the reason (report
`unreadable`): upper-case paths, JSON that does not parse, shared
tags with "replace": true. A byte-order mark is dropped.

ATM10's 42 carried mods: 19 such files in 7 mods (Mekanism Tools'
ry_UA lang, CTOV's empty tag, Towns and Towers' replacing tags, broken
template pools, recipes and a lang file), and the 42 load as carried.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:53:46 +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 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
Ludovic Marie 4d1596fe3b Load ATM10's 42 carried mods: room past 8,192 textures and tags
With the wide state ids, ATM10's 42 data-tier mods (193,762 states)
reach the world once these loaders stop refusing them:

- Block sprites: eight texture arrays of 2048 layers instead of four
  (9,790 block textures for the 42).
- Per-state tables read as NBT: the table limits cover 2^20 states, and
  the collision table in level data uses them.
- Worldgen block tags: 2^20 blocks instead of 8,192 (mineable/pickaxe
  holds every stone block of a pack).
- As vanilla's loaders do, a blockstate definition that does not bind,
  a missing or broken block model of a mod, and a recipe that does not
  parse are logged and skipped instead of dropping every mod (or
  stopping the game).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:12:41 +02: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 6b0b34098e Benchmark: Horizon's server against vanilla 26.3 on equal work
Walk and explore passes at 1 to 100 bots on the ThinkPad (bots on the Mac
mini), vanilla with the locator bar off, plus an explore pass at a rate both
servers keep up with. The README has the method, the tables, the profile of
what remains at 25 walking bots and the earlier runs; docs/server.md section
9 and the Scale row summarise it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 08:09:50 +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 ee104603dd horizon-port-pack: a library, fetch and port; CurseForge zips name their mods
The launcher's "Import modpack" needs the pipeline as a library and a way to
get the jars a pack names without redistributing anything.

- The steps move into the library (pipeline.rs) with a line callback, so the
  command line and the launcher run the same code; main.rs is a thin CLI.
- fetch: the jars of the portable mods (content, data tier) into
  <work>/jars/, the player's own downloads: copied from a folder they have
  (by SHA-1, or a CurseForge app instance's minecraftinstance.json), else
  from the pack's own link (.mrpack hosts only) or the same file on
  Modrinth's public CDN found by SHA-1, kept only when the hash matches.
  What only CurseForge has is listed as needing a CurseForge API key.
  <work>/fetch.json records each file.
- port: every step after the import for the portable mods (fetch, the
  inventory again with the jars read, carry, report).
- A CurseForge zip whose overrides hold Crash Assistant's modlist.json (All
  the Mods 10 does) takes its jar names, mod ids and SHA-1s instead of bare
  project ids, so Modrinth answers for most files.
- inventory goes on offline when Modrinth cannot be reached.

All the Mods 10 8.3's CurseForge zip: 496 files named; of the 54 data-tier
content mods 42 download from Modrinth's CDN (84 MB) and 12 need the key or
the player's instance; 42 carried over (partial: the harness is a dev step).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 07:00:45 +02:00
LookImEcho-Rick aeaccb0413 Fix multiplayer pause menu and World Options permissions 2026-10-16 23:39:36 -05: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 1e514f8539 Merge pull request #633 from ludovic111/feat/port-pack-report
horizon-port-pack report: coverage per mod, and ATM10 measured
2026-10-17 04:58:09 +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 303c8d4452 Merge pull request #631 from ludovic111/feat/port-pack-oracle-carry
horizon-port-pack carry: templates and numbers from the NeoForge oracle, 1.21.1 loot upgraded
2026-10-17 04:58:00 +02:00
Ludovic Marie 03ca95e8af Merge pull request #630 from ludovic111/feat/mod-content-real-mods
Content mods: load real carried-over mods (large tables, convention tags, mod recipes)
2026-10-17 04:57:55 +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
Cosmic 8fa5c38663 Add an optional Fullbright extra 2026-10-16 21:42:01 -04:00
LookImEcho-Rick 5d614f1cf6 fix(rendering): correct selection outline on translucent blocks 2026-10-16 19:03:48 -05: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 0560bbf876 horizon-port-pack report: coverage per mod, and ATM10 measured
`report <work>` writes coverage.json and coverage.md: per mod ported,
partial, not yet or not needed with the reason, and the totals by class,
tier and licence family. It reads the inventory, each mod's carry report
and its harness result: ported is a green harness with nothing left out
(blocks with a class of their own are counted and named); partial lists
the failing checks, the blocks left out and what the templates miss; not
yet names the API tier or the dependency it waits for; not needed covers
libraries, performance mods and pack tooling. Unclassified mods are not
yet, never not needed.

`import` also reads Crash Assistant's modlist.json, the list ATM10
publishes in its repository (mod ids, names, versions, Modrinth hashes,
the loader entry), and the inventory uses its mod ids and names when
there is no jar.

ATM10 (496 files, the eight mods we downloaded carried and checked):
ported 1, partial 7, not yet 367, not needed 121.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:28:43 +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 886def4360 horizon-port-pack carry: templates and numbers from the NeoForge oracle, 1.21.1 loot upgraded
Carrying over seven real ATM10 mods (Cloud Glass, Factory Blocks,
Glassential, Macaw's Doors, Fences, Paths and Trapdoors) showed what the
guesses missed:

- Loot tables: 1.21.1's `conditions` lists, `functions` and
  `condition`/`function` kinds are now 26.3's single `condition`,
  `modifier` and `type`; silk-touch and shears tool matches become
  `minecraft:tool/can_silk_touch` and `can_shear`. Before, every drop
  condition was ignored (glass dropped itself by hand, upper door halves
  dropped doors, double slabs dropped one).
- With the oracle's facts (<work>/oracle/facts.json, from
  tools/neoforge_oracle.py in the next PR), every registered block gets
  the vanilla template of the same vanilla class and states that matches
  its shapes, sounds, BlockSetType and WoodType best, and the mod's own
  hardness, blast resistance, light, tool need, friction, map colour and
  sounds. Items get stack size and rarity. The carry report lists what a
  template does not reproduce and why a block was left out.
- Convention tags a recipe uses (c:dyes/red) are written from the
  oracle's tags, vanilla and own members only; the jar's own c tags stay
  inside the mod.
- Without the oracle, blockstates that omit properties their models
  ignore (powered, waterlogged) still find their template, and names pick
  between fences and panes, doors and trapdoors.
- `version = "${file.jarVersion}"` is read from MANIFEST.MF.
- `--resources <prepared>` limits templates to blocks Horizon has.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:25:54 +02:00
Ludovic Marie ddc115e969 Content mods: load real carried-over mods (large tables, convention tags, mod recipes)
Loading seven real NeoForge mods carried over by horizon-port-pack (about
1,000 blocks) broke the content root in four places:

- The per-state NBT tables (cached collision, occlusion, movement, worldgen
  block properties and support) passed the NBT reader's 1M-node cap once
  mod states were appended. They are trusted prepared tables with a row
  per state, so they now read and write with Limits::tables() (16M nodes,
  256 MiB) everywhere they are loaded.
- The overlay linked the base root by the path it was given, so a relative
  base gave dangling links. The base is made absolute first.
- Carried-over tags name blocks the port left out and other mods' items.
  Vanilla fails such a tag as a whole; entries naming ids nobody registers
  (or tags nobody defines) are now dropped, so the rest loads.
- Mods use the convention tags (c:dyes/red, c:glass_blocks/colorless).
  Content mods may add to data/c/tags/ as to vanilla's, and with content
  mods loaded the tag loaders read the c namespace and the mods' own.

Stonecutter and smithing recipes in mod namespaces load too (ids
<ns>:<name>), as crafting and cooking ones already did. With no content
mod nothing changes: the loaders read the minecraft namespace only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:24:34 +02:00
Ludovic Marie 9c03535011 docs/server.md: hosting a Horizon server, the design
What the dedicated server does today (measured against vanilla 26.3), the horizon-server supervisor and
its packaging, server.toml, the CLI and TUI, the web panel, backups and updates, the control API and MCP,
the bot benchmark plan against vanilla and Paper, the working version for 2026-10-23, and the PRs that
build it (#551 to #556).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 22:22:56 +02:00
Ludovic Marie 673ac84724 horizon-port-pack carry: a jar's data and assets as a content mod
carry <work> [<mod id>...] turns each content mod with a jar into a folder
content mod (API 0.2) in <work>/mods/<id>/carry/, for local use:

- assets/<id>/, data/<id>/ and data/minecraft/tags/ are copied; other
  namespaces and vanilla data go to outside/ for review; no class file is
  copied.
- Vanilla-type recipes get 26.3 ingredients ("id", "#tag", lists);
  custom types and NeoForge conditions are kept and listed.
- Item models get the items/ client definitions 1.21.4 introduced.
- Each block is declared from a vanilla template guessed from its
  blockstate properties; unknown property sets are listed. Items
  without a block are declared from minecraft:stick.

The test builds a small NeoForge jar, carries it over, and reads the
result with horizon-mods' own Content reader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:25:28 +02:00
Ludovic Marie 85858610bf horizon-port-pack: import a modpack and classify its mods
A CLI for the first steps of docs/modpacks.md's pipeline:

- import: a Modrinth .mrpack, a CurseForge zip or a Prism / MultiMC
  instance (or a folder of jars) to pack.json: Minecraft and loader
  versions, each file with its hash and sides, and the overrides.
- inventory: Modrinth metadata by SHA-1 (cached in modrinth.json, so
  reruns work offline), and the jars when the user's instance has them
  (neoforge.mods.toml / mods.toml / fabric.mod.json and a file census,
  without decompiling). Each mod gets a class and API tier from the
  reviewed ATM10 table, then rules on categories, sides and the census,
  and a dependency layer. Writes inventory.json and inventory.csv.

Only metadata is downloaded; jars are never fetched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:25:04 +02:00
Ludovic Marie 746c888cd3 horizon-mods: content mods (API 0.2), blocks and items declared from vanilla templates
A .hmod may carry assets/ and data/ beside mod.wasm, or instead of it. The
manifest needs api = "0.2" and the new registries capability ("add blocks
and items to your worlds"). Content mods have no WIT interface: code still
links API 0.1.

horizon_mods::content::Content reads a mod's content and checks it:
- plain resource paths only;
- 65,536 files, 16 MiB each, 512 MiB in all;
- files in the mod's own namespace, plus additions to vanilla tags;
- blocks and items declared in data/<id>/horizon/{block,item}/*.json, each
  copying a vanilla template with typed overrides (hardness, resistance,
  light, tool, friction, map colour, sounds; stack size, rarity), unknown
  keys refused.

Mods::load refuses an enabled mod whose content fails these checks, with
the reason on its row. A content-only mod is on with no engine and no hooks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:23:35 +02:00
Ludovic Marie a06367edf1 docs: modpacks design, with ATM10's real mod list classified
docs/modpacks.md: how any pack (ATM10 down to small Fabric packs) gets to
Horizon: import (.mrpack, CurseForge zip or instance, Prism, packwiz),
inventory and classification, dependency order, mechanical carry-over of
data and assets, one agent per mod for code with a per-mod harness against
a dev-only NeoForge/Fabric oracle, and a coverage report. Also the mod API
gaps against 0.1, the registries 0.2 design (template blocks through the
overlay root), licence handling, how "10x" is measured against NeoForge,
and the scope for 2026-10-23.

docs/modpacks/atm10-mods.csv: ATM10's 495 jars from the pack's own
crash_assistant modlist (2026-10-15), with Modrinth metadata looked up by
hash: class, API tier, licence, sides. Metadata only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:08:23 +02:00
Ludovic Marie 81243f7897 Release notes: drop the sprint-jumping known issue
Nobody could reproduce it, and no issue tracks it, yet every release since
2026-10-14 repeated it. Players noticed (rdbt on Discord).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 14:29:46 +02:00
Ludovic Marie fc59063ada Keep held keys held through wait on the control socket
`wait` ended with release_agent_keys like every other task, so
`key w down` + `wait 40` + `wait 20` walked for the first wait only.
A wait holds nothing: it leaves keys and buttons alone. They stay held
until `key ... up`, `release` or `stop`. Documented in horizon-ctl's
key/wait help (and so the MCP tools) and docs/agent-control.md.

Fixes #469

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 10:56:48 +02:00
Ludovic Marie 3cfb7b8053 Mods' commands in the chat's command tree (#456)
A mod's commands ran but the chat box parsed against vanilla's tree only,
so `/coords` showed red as unknown and never completed. The game now keeps
the chat's tree as vanilla's plus a literal per running mod's command (every
permission level, optional greedy `args`), rebuilt when mods start, reload
or stop.

Fixes #456

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 06:48:56 +02:00
Ludovic Marie 9b9b23bf3a Merge pull request #436 from ludovic111/feat/shaderpack-client
horizon-client: run a Java pack's full-screen passes over the world
2026-10-16 05:31:32 +02:00
Ludovic Marie 953cdc947f Merge pull request #418 from ludovic111/feat/shaderpack-passes
horizon-shaderpack: plan a Java pack's full-screen passes like Iris
2026-10-16 05:31:28 +02:00
Ludovic Marie 4de37ac0e4 Merge pull request #415 from ludovic111/feat/shaderpack-properties
horizon-shaderpack: read shaders.properties like Iris
2026-10-16 05:31:23 +02:00
Ludovic Marie 1bf51d786f Merge pull request #413 from ludovic111/feat/shaderpack-translate
Java shader packs (2): translate programs to WGSL (syntax-tree legaliser + naga)
2026-10-16 05:31:19 +02:00
Ludovic Marie 64a82acdab Merge pull request #405 from ludovic111/feat/shaderpack-preprocess
Java shader packs (1): horizon-shaderpack crate, Iris's preprocessor and options
2026-10-16 05:31:14 +02:00
Ludovic Marie 2862f17e86 horizon-client: run a Java pack's full-screen passes over the world
A folder holding shaders/, or a .zip, in shaderpacks/ now loads from the
Shader Packs screen: every planned begin, prepare, deferred, composite
and final pass is translated and drawn each frame over Horizon's world,
with Iris's formats, clears and buffer flips.

The targets keep OpenGL's layout (the translator flips gl_Position and
remaps its depth), the world enters as OpenGL depth, and the pack gets
OpenGL matrices. Textures share four samplers by kind (Metal allows 16
per stage), and isnan/isinf test bits (Metal's fast math folds x != x).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 00:22:39 +02:00
Ludovic Marie 5350f3465b Merge pull request #424 from ludovic111/test/lan-e2e
tools/lan_e2e.py: a LAN host and guest, checked from both sides
2026-10-16 00:10:22 +02:00
Ludovic Marie 08c4805e10 Release train: pad the same-day counter to two digits
GitHub's releases page sorts a day's tags as text, so v2026.10.15.9 showed
above v2026.10.15.18 (reported by rdbt on Discord). Tags now count .01, .02...
Every reader (release_train.tag_key, the launcher's compare_versions) compares
the parts as integers, so older unpadded tags still order correctly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 23:17:09 +02:00