macOS and Windows are supported platforms with dedicated documentation under docs/platform/. Replace the stale 'macOS and Windows are out of scope' sentence with a reference to docs/platform/.
Fixes#11145.
Co-Authored-By: Antigravity <[email protected]>
On Linux, Horizon asks ALSA for a 1024-frame fixed buffer to keep sound effects low-latency. When PipeWire is configured with min-quantum >= 1024 (common for Bluetooth audio or in VMs), the 1024-frame buffer repeatedly underruns with POLLERR and produces no sound.
Allow setting HORIZON_AUDIO_BUFFER=default or HORIZON_AUDIO_BUFFER=<frames> (e.g. 2048) to override the fixed buffer request and use a larger or system-default buffer. Document the environment variable in native-client.md.
Fixes#11082.
Co-Authored-By: Antigravity <[email protected]>
Keep same-machine test sockets on loopback and stage launcher-managed games
at a stable executable path per game home. Preserve runtime dependencies and
protect running installed/native versions from replacement and cleanup.
Refs #9685.
Remove the hard allocation ceiling and glass launcher slider added since the original revert. Keep read-only memory diagnostics and unrelated upstream changes.
Co-Authored-By: OpenAI Codex <[email protected]>
Keep the latest main particle culling and graphics settings alongside the shared synchronous and asynchronous entity occlusion path.\n\nCo-Authored-By: OpenAI Codex <[email protected]>
🤖 Agent: queued, verify and launcher green on 3f941752. CI never builds Windows, so the Windows-only code was checked separately: clippy -D warnings for x86_64-pc-windows-gnu is clean on horizon-threads (all targets) and horizon-client (with and without default features), with the C build scripts (mimalloc, ring) stubbed.
On Windows a RAM limit was a hard working set limit (SetProcessWorkingSetSizeEx
with QUOTA_LIMITS_HARDWS_MAX_ENABLE): Windows kept the game under it by moving
what it used least to the page file, and the game only stepped down once it
was already slow and faulting hard. Players saw a low limit make the game slow
instead of making it hold less (#6651, #6652).
Windows now does what Linux and macOS do: once a second the game compares its
working set with the limit and, past nine tenths of it, turns far terrain off
and lowers the render distance in use until it fits. The system cap, the page
fault pressure, the frozen-game check and the frame clock that fed it are
gone. The limit stays opt-in (no launcher value, no limit), and
HORIZON_MEMORY_HARD_MB is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
HORIZON_MEMORY_LIMIT_MB caps the working set, and Windows meets it by moving pages out: at 512 MB the game still committed 2.2 GB here. HORIZON_MEMORY_HARD_MB (and /ramlimit hard) is a budget of the process's commit instead. The governor sheds far terrain and render distance against it, the allocator refuses what would pass it, and a game that cannot fit ends with a crash report or a message naming the limit rather than living in the page file.
Isolate the entity occlusion cache and worker from particle visibility. Keep entity modes in the existing settings, submit inflated entity AABBs before model generation, and refine visible entities with their emitted bounds. Preserve fail-open handling and add async culling telemetry.
Co-Authored-By: OpenAI Codex <[email protected]>
Vanilla's `KeyboardHandler` branch for `key.debug.dumpDynamicTextures` takes
`TextureUtil.getDebugTexturePath(gameDirectory)` (`screenshots/debug`), calls
`TextureManager.dumpAllSheets` and shows `debug.dump_dynamic_textures` with the
folder as a clickable link. Horizon bound the key and its action was a no-op.
`hud::dump_atlas` writes the atlas as `name.png` beside `name.txt`, as
`TextureAtlas.dumpContents` does: the PNG of the pixels and one line per
sprite, sorted by name, `%s\tx=%d\ty=%d\tw=%d\th=%d%n` in texels
(`dumpSpriteNames`). The file is `minecraft_textures_atlas_gui`, vanilla's
debug name for `minecraft:textures/atlas/gui.png` (`Identifier.toDebugFileName`
replaces `/` and `:` with `_`); Horizon packs the GUI, font, particle, map and
skin sprites into that one atlas, so it stands in for the atlases vanilla dumps
apart.
Fixes#7042.
Co-Authored-By: Hermes Agent (deepseek-v4.1-flash) <[email protected]>
Replace the collision-prone four-way visibility cache with a bounded sorted cache and validate repeated occlusion proofs through frame-local ordered lookup. Keep entity visibility conservative and off block entities, and add batched particle culling modes sharing the occlusion worker.
Add a reproducible benchmark note and screenshots for Sync and Async modes.
Fixes#6684
Co-Authored-By: OpenAI Codex <[email protected]>
A soft limit that lowers render distance and then crashes the game is not
a cap, and only Windows could hold the game under a number at all. Takes out
the launcher's Maximum memory row, HORIZON_MEMORY_LIMIT_MB, /ramlimit, the
governor and the watcher thread (reverts #5800 and #5801). A Java instance's
-Xmx stays settable from the launcher CLI.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Ports main's lib.rs (block_edit, cit, lod_net, ram_limit, social_screen) into
client_lib.rs, main.rs's --player-dir logging and ram_limit into entry.rs, and
the horizon-optifine dependency into horizon-client-lib.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep native hook targets and source paths stable. Run gameplay tests in the gameplay library, and build ctl/probes from horizon-client-lib without gameplay. Update build/test selection and the fixed mixin-tool pin.
Co-Authored-By: Codex <[email protected]>
HORIZON_MEMORY_LIMIT_MB and /ramlimit cap the RAM the game holds. On
Windows a hard working set limit enforces it, so a low limit costs speed
instead of crashing. Elsewhere, and when the game is slow and faulting
hard, far terrain and the render distance in use step down and come back.
The game crashes with a report only if it can't run under the limit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Merges #3574 (and with it #3567 and #3551) for docs/api/launch.md.
- --java-home names a Java the launcher manages (Options::java_home was
there for horizon-launcher only).
- The import logs the client jar it read, which --client-jar may have
named outside the Minecraft folder.
- named_asset_store_serves_the_asset_commands: with an empty Minecraft
folder, an asset command fails without --assets-dir and copies from
the store it names.
- docs/api/launch.md and native-client.md: the named inputs, that an
earlier verified import is reused instead, the server jar rule (Horizon
never downloads it; a launcher only for an owner), --java-home.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A preparation removes the stamp before it writes anything and stamps
again at the end, so --check calls a folder being prepared, or whose
preparation stopped, not current.
- Each preparation holds .horizon-prepare.lock in the resources and in
root's .local (add_assets in the resources); a second one says it is
waiting and waits. tools/prepare_output.py leaves the lock out of the
digest.
- missing_extras is the one completeness check: --check and the
launcher's COMPLETIONS both use it.
- The Java exporters and the worker run in root's .local, so vanilla's
logs/ stays in the scratch, not where the preparer was started.
- --check's help and docs/native-client.md say what it doesn't test
(contents) and that exit 1 is either gaps or "can't check".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
--version-json, --client-jar, --libraries-dir and --assets-dir name
the files a launcher keeps in a layout of its own (instance folders,
shared libraries and assets), each replacing only what --minecraft-dir
would give; every file is still checked against research-lock.json's
pins. GameFiles gains the libraries and asset store folders, and
Options the Inputs (Default in the launcher, so it is unchanged).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
--complete prepares, copies the sounds, then fails unless --check would
pass: one command for resources with nothing missing (without the sounds
step the game is silent, which is easy to miss).
--progress json prints one JSON object a line: {"message": ...} for each
log line, {"fraction": F} as each of the preparation's 18 stages begins
and at the end, {"error": ...} on failure. prepare_with_progress takes
the fraction; prepare keeps its signature, and a test keeps STAGES equal
to the stages steps() opens.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
--output DIR puts the prepared resources elsewhere than
<root>/.local/horizon-client/<version>, for preparing and for the asset
commands (sounds, panorama, fonts, packs, languages, now one add_assets
behind them); the exporters' scratch files stay in <root>/.local.
--check exits 0 when the resources are complete and current for this
preparer and 1 otherwise, printing each gap with the command that fills
it: the manifest's schema and Minecraft version, the OUTPUT_VERSION that
made them, and the extras the launcher completes (packs, panorama,
Unifont, languages, sounds). A preparation now stamps its resources with
its OUTPUT_VERSION (horizon-prepare.json), which the output digest
leaves out, so the stamp needs no new OUTPUT_VERSION.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An owner with no Minecraft on the computer no longer needs to find
"Download and prepare": Play on a version not prepared yet prepares it
(from the player's install when there is one, else downloaded from
Mojang for the signed-in owner, Java included) and then starts the game,
as the official launcher does. Without an owner Play stays off.
With no install found, the Resources row offers the download alone and
the Minecraft folder row says none is needed. The CLI's not-prepared
error and the READMEs stop asking for a local install and Java.
The download path itself (download_for_owner, mojang.rs) is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each frame the game hands the presence thread its status (menus; single
player, multiplayer or hosting a world with its party size; the
dimension; the game mode), and only a change wakes it. The switch
"Discord Rich Presence" (on by default, discordPresence in
horizon-extras.txt) sits beside Controller Settings. Dedicated servers
never start it. tools/discord_presence_e2e.py runs real games against a
fake Discord: title, world, Nether and quit on the server thread and
in-frame, a LAN host (both paths) and guest, the switch, and a Discord
started after the game.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Drop the Horizon menu style (options_horizon.rs, front.rs, the live title
backdrop and its post grade, the launcher's menu-style setting, CLI and MCP
tool). Every screen is vanilla's. Horizon's own settings hang off vanilla's
Options the way mods add theirs: Horizon Settings... and Mods... take the
Telemetry/Credits slots, and Horizon Settings opens Graphics, Advanced
Graphics, Shader Packs, Controller Settings, Extras and Mods, each a vanilla
options list with tooltips. Play with friends moves behind vanilla's Friends
buttons: the pause menu's opens Play with Friends (share, copy, stop), the
title screen's opens Join a Friend, and Direct Connection takes a full code.
Old files load as before: horizon.txt's menuStyle line and
horizon-graphics.txt's level line are ignored.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A remote client (a LAN guest, or the game on its server thread) threw away
the light in each LevelChunkWithLight and relit the column and its
neighbours itself. It now applies the sky and block layers as
ClientPacketListener.applyLightData does, and relights only block changes,
as vanilla's client does.
The hub now sends ClientboundLightUpdatePacket for the light sections that
changed in a column to the players for whom it is on the tracked border
(ChunkHolder.broadcastChanges), so light from blocks a guest doesn't track
reaches it.
horizon-ctl gains 'light X1 Y1 Z1 X2 Y2 Z2', and lan_e2e a 'light' check
comparing the guest's light with the host's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
wait-for screen NAME|widget ID|loaded|chat TEXT|event KIND replies once
the game gets there (--timeout, default 30 s, ends in an error of kind
timeout; --since SEQ counts chat and events from an earlier point), so
scripts and agents stop polling. events [--since SEQ] lists what
happened, oldest first, numbered: screens, chat lines, deaths, respawns,
items gained or lost, dimensions, worlds opened and closed. The state is
compared once per event loop turn and only under --control, so watching
never changes the game. respawn now says when the death screen's button
is not active yet instead of clicking it to no effect.
horizon-ctl and its MCP gain both; tools/agent_control_e2e.py gains an
events scenario.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
options-get [KEY...] gives every options.txt option by its vanilla key
with its value and what it accepts (type, range, step or choices; enums
by name, key bindings as key.keyboard.w). options-set KEY VALUE sets one,
applies it at once and saves options.txt as the options screen does
(language and resource packs reload too); a value the option refuses is
an error naming what it accepts, and the option keeps its value. After a
set, ui reads the redrawn screen.
horizon-ctl and its MCP gain both; tools/agent_control_e2e.py gains an
options scenario.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`ui` lists every widget the last frame drew (title menus, pause, options
in both menu styles, inventories and menus) with an id, its kind, label,
bounds and value; `click ID` clicks one through the same window events
as the mouse, `set ID VALUE` moves a slider along its track. Ids come
from labels (FOV: 70 is fov), world/server/language rows carry their
names, Horizon switches, sliders and segments their row's caption, and
inventory slots are slot-ID with their stacks. After a click, the next
widget command waits for a redrawn frame.
horizon-ctl and its MCP gain ui, click and set, and launch --offscreen.
tools/agent_control_e2e.py gains a ui scenario and runs offscreen on
macOS; both scenarios now handle Singleplayer opening Create New World
when there are no worlds.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
tools/fetch_sounds.py (and the lab.py functions it used) becomes
`horizon-prepare sounds` (horizon_prepare::sounds): the pinned asset index,
sounds.json and every .ogg from the player's own launcher install, each SHA-1
checked, into .local/horizon-client/<version>. The launcher runs it as part of
Prepare (in-process, the version's own binary when its sources differ and it
knows the subcommand, in-process for older versions), and a launcher install
without the assets only logs that the game will be silent. Player docs no
longer mention Python for sound.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The debug crash from #90 printed a stand-in report; it now ends the game
with a DebugCrash error that main reports as "Manually triggered debug
crash" with the stack where it was raised. The game keeps the error that
ends it as an error, not a string, so main can tell. Leading std backtrace
frames are dropped (macOS keeps them).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The game writes logs/latest.log in its home like vanilla's log4j config
(client-1.21.2.xml): [HH:mm:ss] [thread/LEVEL]: message, rolled at start and
at midnight to logs/<date>-<n>.log.gz, seven a day. Messages go through the
log facade and still print their old text on stderr.
A main-thread panic or a fatal error writes
crash-reports/crash-<date>_<time>-client.txt (error, stack, Horizon and
Minecraft versions, OS, CPU, memory, GPU adapter, render settings, resource
packs, world, last 50 log lines). Development builds crash on purpose with
HORIZON_DEBUG_CRASH.
The launcher shows "Horizon crashed" when the game it started ends badly,
with Open report, Copy report, Open logs folder and Report on Discord;
Settings gets a Logs row. horizon-launcher logs and horizon-ctl logs
(--lines N, --crash) print the log's end or the newest report, and both MCP
servers have a logs tool.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The launcher prepares a version in-process when it ships the preparer sources
the launcher was built with, with the version's own horizon-prepare when they
differ, and with the Python script only for versions made before it. Release
folders ship horizon-prepare and its sources; resources are reused between
versions with the same preparer. The game's messages, PLAY.txt, the docs and
release builds name horizon-prepare, and tools/prepare_native_client.py is a
wrapper of it for the development tools.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
README gets an Agent control section (out of the roadmap: it's done), the
native client guide describes every command group, the blocking semantics,
exit codes, launch discovery, MCP and the two transports, and PLAY.txt tells
release players about horizon-ctl.
Co-Authored-By: Claude Opus 5.5 <[email protected]>