Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
Horizon launcher
A small desktop app that installs Horizon builds, prepares them from your own copy of Minecraft, and starts the client or a dedicated server. Built with GPUI, Zed's UI framework; it has its own Cargo workspace and is not part of the game's builds.
The opening, then Play (clip):
What it does
- Versions: lists the published releases of horizon-rust/horizon, installs the build for this platform into its own folder, and removes it again.
- Play: prepares resources as
PLAY.txtdescribes (your launcher's 26.3 install, the 26.3 server jar from that version's own json, and Java 25) and copies its sounds from your launcher's asset store (horizon-prepare sounds; a launcher install without them only leaves the game silent), then starts the client. Preparing is horizon-prepare: linked into the launcher, used in-process when a version ships the same preparer sources, else that version's ownhorizon-prepare; versions released before it still prepare with their Python script and need Python 3.10+. Before importing, preparation reuses checksum-verified game jars and libraries from the current or another installed Horizon version, so the original Minecraft install is only required when no valid imported copy exists. - Download and prepare: for a signed-in account that owns the game (asked again first),
the launcher downloads the pinned version from Mojang itself (piston-meta, piston-data,
libraries.minecraft.net, resources.download.minecraft.net; never a mirror) into
minecraft/in its data folder, in the official launcher's layout: the version file, the client jar, this system's libraries, the asset index and the assets. Every file must have the size and sha1 Mojang lists. The Play page shows how far it is and has Cancel; files already whole are kept, so the next try goes on from there. From my install prepares from the player's own launcher instead, and an install that lacks the version falls back to the download. Without an owner, the Play page asks to sign in first.horizon-launcher prepare --download. - Mods: search Modrinth (CurseForge support is coming soon) for resource
packs and modpacks, each marked Works, Partly or Doesn't run in
Horizon; Java mod jars are never offered. Install puts a pack and what it needs in
resourcepacks/only after its SHA-512 matches, and a modpack goes to the Modpacks page; installed packs show their source and checksum, with Update, Update all and Remove (docs/mod-browser.md). - Modpacks: import a Java modpack (a Modrinth
.mrpack, a CurseForge zip, or a Prism, MultiMC or CurseForge instance folder) and see its coverage report: per mod, ported, partial, not yet or not needed, with the reason (docs/modpacks.md). Port gets the jars of what this computer can port today (content mods with data and assets only) the player's own way: copied from a folder they have, or downloaded from the pack's own links or the same file on Modrinth's CDN, hash checked; files only CurseForge has come from your installed instance (CurseForge downloads are coming soon). It carries them over for the selected version, and Play starts the game as the pack's own profile (ports/<pack>/home/in the game's home: its own saves and options) with those mods. Nothing is uploaded or shared: mod jars and ports stay on this computer. - Servers: host a world for friends with buttons. Create a server asks for a name, a world
(a new one, with an optional seed, or a copy of one of your single-player worlds), the game mode,
difficulty, most players, who can join (friends only, the default, or anyone with the address)
and your agreement to the Minecraft EULA; with the defaults (tick the EULA, press Create) you get
a friends-only survival world with you as its operator. Each server then has Start, Stop and Restart, its state (also
when it crashed and was restarted), players online with their heads, TPS, MSPT and RAM, the
address friends on your network type with Copy, a console that takes commands, Open the
web panel, and four tabs: Overview; Players (online players with Make operator, Kick
and Ban; who may join, with Friends only, Let in and Remove; Unban); Settings (the message
in the server list, game mode, difficulty, PvP, most players, view and simulation distance,
port, account checks, Run in the background and at login); Backups (Back up now, the
list with dates and sizes, Restore with a confirmation, Back up automatically, and Updates:
Check for updates, Update now with its automatic rollback, and how many older versions to
keep). Changes the running game can take apply at once, the others say "Restart to apply".
Everything goes through
horizon-serveritself (docs/hosting.md): the launcher copies a prepared version's programs and game files intoserver-runtime/laid out asinstall.shdoes, runshorizon-server init,config,start,backup,restore,updateandservicethere, and talks to a running server through its control API. Problems are said in plain words, with a button when there's a fix (another port, the game files, the EULA). Friends over the internet (on Overview) uses Tailscale: it says whether Tailscale is installed and signed in (with Install, Sign in and Check again; signing in is always yours, in Tailscale's own app or page), shows this computer's Tailscale name and address with Copy, and invites a friend to this one computer: in the admin console, or here with an access token you make there (kept in the system keychain), which also lists invitations with Withdraw / Stop sharing and offers Limit them to Minecraft: when your tailnet has Tailscale's default access controls, one click changes them so friends you share with reach only the server's port (Remove the limit undoes it); custom access controls are never rewritten, the page gives the rule to add instead. Horizon never turns on Funnel. - Instances: separate game folders, each with its own worlds, options and packs. The game's
folder stays the first one, "Horizon", exactly where it was; imported and registry modpacks are
instances too, in their own folders as before; new ones go to
instances/<name>/in the game's folder, empty but for the player's options and name, or a copy of another instance (never its sign-in sessions, logs or data collection state). Names, icons (a block or item texture from the player's prepared files, or a colour) and groups are kept ininstances.jsonin the launcher's data folder, so renaming moves nothing. Every instance follows the launcher's data collection answer and No AI. In the window, + beside Instances makes one, the sidebar lists them by group (click one to play it), and Home's Instance row has Edit (name, group, icon), Copy and Open folder. The Instance row shows its play time and when it was last played: every run started from the window orhorizon-launcher playadds to it (instance listshows it too). Each instance plays the launcher's version, which follows updates, unless the Play page's version picker keeps it on another installed one (to keep an old world on the version it was made with); picking the version marked "follows updates" lets it follow again. - Export: Home's Instance row has Export…: the selected instance (or a port) as a
.mrpack(Modrinth's modpack format: resource packs the browser installed from Modrinth are listed by their download, the rest is inoverrides/) or a.zipof the game folder's layout, with the worlds switched on. It is written toexports/in the launcher's data folder (Show in folder). Only settings (options.txt, horizon-*.txt, servers.dat), resource and shader packs,horizon-mods/andconfig/go in: never logs, screenshots, backups, caches or account files; a world goes withoutplayerdata/,stats/andadvancements/, and its level.dat without the player's UUID.horizon-launcher instance export [<instance>] [--format mrpack|zip] [--world <w>]… [--out <file>]. - Import by dropping a file: a file dropped anywhere on the window is imported for what it is
(
horizon_launcher::import). A shared instance (a.mrpackthat needs no mod loader, such as Export writes, or a game folder.zip) becomes a new instance, opened on Home. Its.mrpackdownloads come from Modrinth with their SHA-512 checked, and only what an instance keeps is taken, inside its folder. A Java modpack goes to the Modpacks import, and a resource or shader pack into the selected instance. A data pack is pointed at the world rows, and the Packs section and the world rows keep taking their own drops. A note in the corner says what happened.horizon-launcher instance import <file>. - Launch options: Home's Instance row has Launch…: per instance, a shell command run in its
folder before the game (the game starts only if it succeeds), a wrapper the game runs through
(
nice -n 5), and environment variables (NAME=value OTHER="two words"), kept ininstances.json(horizon_launcher::launch). The game stays one process the launcher follows: on Unix/bin/shruns the command and thenexecs the wrapper and the game, whose words pass through"$@"untouched; on Windowscmd /Cruns<command> && <game>. Both the window's Play andplayuse them.horizon-launcher instance launch <instance> [--pre-launch …] [--wrapper …] [--env …](an MCP agent needsconfirm: true: it sets commands Play runs). On Linux the panel also has switches for what the computer has: GameMode (gamemoderunon the PATH), MangoHud (mangohud), and, with two graphics cards under/sys/class/drm, the dedicated graphics card (DRI_PRIME=1, or NVIDIA's__NV_PRIME_RENDER_OFFLOAD,__VK_LAYER_NV_optimusand__GLX_VENDOR_LIBRARY_NAMEwhen its driver is loaded). A switch whose tool is gone does nothing.instance launch --gamemode on|off --mangohud on|off --dedicated-gpu on|off;HORIZON_LAUNCHER_LINUX_TOOLSmakes any system show them, for tests. - Desktop shortcut: Home's Instance row has Shortcut: a shortcut on the desktop that plays
the selected instance without opening the launcher (
horizon-launcher play --instance <id>). A.desktopfile on Linux (also put in the applications menu, and marked trusted withgiowhere it exists), a small app with Horizon's icon on macOS, a.lnkon Windows (horizon_launcher::shortcut). Remove shortcut moves it to the trash; it is found by the instance id inside it, so a rename doesn't lose it.horizon-launcher instance shortcut [instance] [--remove];HORIZON_LAUNCHER_DESKTOPpoints it at another folder. - Accounts and quick switch: the top bar's account chip opens a menu of everyone who can play:
each signed-in Microsoft account, the offline names the player keeps, and the name the game
made up (
horizon-player.txt). Picking one plays as it from the next Play. Offline names play only while an account that owns the game is signed in (Microsoft accounts); signing the last one out keeps them, unplayable until one is back. Settings > Account adds offline names (3 to 16 letters, digits or_; the game gets--name) and forgets them; the Console's Share log takes them out of the log too.horizon-launcher account offline [NAME],account select <name>(an account or an offline name),account forget-offline <name>. - Accent and Home background: on top of Dark, Light or System, Settings > Launcher picks an
accent (Horizon's violet, blue, teal, green, amber, red or pink) and a picture of the player's
own behind Home (PNG, JPEG or WebP, cropped to fill; Landscape brings Horizon's back). An
accent turns the violet tokens to its hue, then darkens or lightens them just enough to keep
white on the Play button at 4.5:1 and the accent at 3:1 on every background (a test holds every
accent in both looks to it). The picture is copied into the data folder.
horizon-launcher settings accent NAME,settings background PATH|default. - Language: English, French and Russian (the languages Horizon's own game menus have).
The window follows the system's language unless the player picks one in Settings > Launcher
(each named in itself); a string a table lacks shows in English. The tables are
launcher/lang/<code>.json, English text to its translation, Horizon's own; a test fails when the code askstr("…")for a string a table lacks, or a table keeps one the code dropped.horizon-launcher settings language system|en|fr|ru. - Skin and cape: each offline name's Skin… in Settings > Account shows the front of its
skin and cape, takes a skin PNG (64×64, or the older 64×32) and a cape PNG (64×32), and picks
classic or slim arms. The launcher keeps copies in its
skins/folder and gives the gameHORIZON_OFFLINE_SKIN,HORIZON_OFFLINE_SKIN_MODELandHORIZON_OFFLINE_CAPEwhen that name plays; the game draws the default skins until it reads them. A Microsoft account's skin and cape: coming soon, with sign-in.horizon-launcher account skin <name> [FILE] [--model classic|slim] [--cape FILE] [--no-skin] [--no-cape]. - Console: Home's Console shows the game's output of this run as it comes: a search (every
word, any case), Follow to keep the newest line in view, Copy, and Open logs folder. Warnings
and errors stand out. Share log… uploads the log to mclo.gs, a paste
service for Minecraft logs, only after the player confirms; the home folder, the user name and
the account names are taken out first (
horizon_launcher::game_log), and Delete it removes the paste.horizon-launcher share-log [--instance]andunshare-log <id:token>;HORIZON_LAUNCHER_PASTE_URLpoints it at a test server. - Worlds: Home lists the selected instance's worlds (picture, mode, version, last played,
size). Back up zips one into the instance's
backups/as the game's Make Backup does (<date>_<time>_<folder>.zip); Copy puts " (1)" beside it; Delete moves it to the system's trash after a confirmation; Open folder. The same from the command line:horizon-launcher worlds [list|backup|copy|delete] [--instance <name>]. Play on a world starts the game straight into it. Data packs opens the world'sdatapacks/under its row: what is there (and which the game has turned off), Add data pack…, and Remove (to the trash); a data pack.zipdropped on a world goes into it. The game turns a new one on when the world opens. From the command line:horizon-launcher worlds datapack-list|datapack-add|datapack-remove <world> …. The Mods page's Data packs tab finds them on Modrinth and installs one into the world picked there (mods search --kind datapack,mods install <pack> --world <world>), saying what of it runs in Horizon. - Packs: Home lists the selected instance's resource packs and shader packs, each with a switch.
The choice is the game's own:
resourcePacksin options.txt (a pack turned on goes on top, as the game's Resource Packs screen does) andpickedShaderPackinhorizon-graphics.txt(one shader pack at a time). Add pack… or a.zip/.hshaderdropped on the section copies it into the right folder; Remove turns it off and moves it to the trash after a confirmation. From the command line:horizon-launcher packs [list|add|on|off|remove] [--kind resource|shader] [--instance <name>]. - Screenshots: Home shows the selected instance's newest screenshots (what F2 saves in its
screenshots/folder) and all of them on request. Clicking one opens it over the window (← and → step through) with Copy (the picture, to the clipboard), Open, Show in folder and Delete (to the trash, after a confirmation). From the command line:horizon-launcher screenshots [list|delete] [--instance <name>]. - Multiplayer: Home lists the selected instance's servers, the game's own Multiplayer list
(
servers.dat, each with the picture the game last saw). Join starts the game straight into one; Add server and Remove (after a confirmation) change the file the game reads, keeping every field the launcher doesn't show and the servers joined with Direct Connection. The same from the command line:horizon-launcher multiplayer [list|add|remove] [--instance <name>]andhorizon-launcher play --server <address or name>. - Patch notes: each release's notes, newest first, with a link to GitHub Sponsors. Quiet "Sponsor", "Discord" and "Website" links sit in the sidebar footer.
Data lives in the platform's data directory (~/.local/share/horizon-launcher on Linux,
~/Library/Application Support/horizon-launcher on macOS, %APPDATA%\horizon-launcher on
Windows): versions/<tag>/, server-jars/, server/, settings.json, and for the Servers
page servers.json (the list), servers/<name>/ (the servers created there) and
server-runtime/ (the programs and game files they run).
- Settings: install new Horizon versions automatically (on unless you turn it off; the
selected version only moves to the new one if you were on the newest), the opening animation,
what the window does while you play (stays by default; Minimise, or Hide on macOS, puts it away
when the game starts and brings it back to the front when the game closes;
settings while-playing stay|minimize|hide), the game's extra command-line arguments, an HTTP proxy for downloads, and the launcher's own version and update state. - Updates: there is one channel. Every release is published as the release, GitHub's latest,
once its smoke test passed (docs/release-flow.md), and the launcher installs it. The game
pre-releases of the old Alpha channel (v0.2.1 on) stay on the Versions page to install
by hand, but are never installed as an update. The
update_channelandbeta_buildskeys older launchers wrote insettings.jsonare ignored and dropped on the next save. - Game arguments: extra arguments added to the game's command line (for example
--server-thread), one line where quotes group a word with spaces. They are appended after the launcher's own arguments, kept asgame_argsinsettings.json, and apply the next time the game starts.horizon-launcher game-args "<line>"sets them too, andplay --arg <ARG>passes one for a single run. - Proxy: when the internet here needs one (a school's, an office's, a country where a host is
unreachable), Settings' Proxy takes the
http://host:portaddress your proxy app listens on, and every download goes through it: Horizon's releases and the launcher's own update, Minecraft's files and Java from Mojang, Modrinth, CurseForge, Horizon's mod registry, pictures, Microsoft sign-in, and the jars a modpack import fetches. It is kept asproxyinsettings.json, andhorizon-launcher settings proxy http://127.0.0.1:7890sets it too ("default" removes it). Without one,HTTPS_PROXYandHTTP_PROXYare used when the launcher is started with them, andNO_PROXYnames the hosts to reach directly, a modpack import's jars included. A server on this computer never goes through it. - Links: the Discord invite, the Sponsors page and the website come from
launcher/links.jsonon main ({"discord": "...", "sponsor": "...", "website": "..."}), so they change without a launcher release. Offline, the launcher uses the same values built in; an empty address hides its button.
Long pages, dialogs, the server console and data previews have a visible scrollbar when their content overflows. Drag the thumb or click the track to navigate without a mouse wheel.
Pages and Home's landscape fill the window as it resizes. Windows uses a matching title bar with native dragging, resizing, Snap, minimize, maximize/restore and close; macOS and Linux keep their system title bars.
Updates
The launcher looks for a newer Horizon release when it starts and every 30 minutes while it is open, and shows a small "available, Install" line on Play.
It also updates itself from GitHub releases tagged launcher@X.Y.Z (Horizon's own are vX.Y.Z;
neither is ever taken for the other). Before 0.2 they were launcher-vX.Y.Z and vYYYY.MM.DD:
every 0.2 tag counts as newer than all of those (docs/versioning.md). It downloads the new build, replaces its
own binary, and offers "Restart" in the footer. Development builds and
HORIZON_LAUNCHER_NO_SELF_UPDATE=1 never replace themselves.
On Linux a release build that starts writes its applications-menu entry only when there is
none, or when the program the entry starts (its TryExec, else its Exec) no longer exists.
An entry pointing at another launcher that works is left alone, so a test or development build
never takes it over. Turning the Settings switch on, or horizon-launcher desktop-entry,
points the entry at this launcher whatever is there.
Linux packagers can set HORIZON_LAUNCHER_DESKTOP_EXEC to a wrapper's absolute path.
Menu entries the launcher writes then start that wrapper, which can
set HORIZON_LAUNCHER_NO_SELF_UPDATE=1 to keep launcher updates with the package manager.
Launcher releases are automatic (docs/release-flow.md in the repository): when main changes the
launcher, horizon-prepare or horizon-theme, the launcher workflow builds Linux, macOS and
Windows and publishes launcher-vX.Y.Z (the next patch, or the next minor after a pull request
labelled feature) with horizon-launcher-linux-x86_64.tar.gz, horizon-launcher-macos-arm64.zip
and horizon-launcher-windows-x64.zip, never marked latest. The version is built in from the tag
(HORIZON_LAUNCHER_VERSION); Cargo.toml's version is only what local builds report.
Command line
The same binary is a command-line tool; without a subcommand it opens the window. On Windows it
is a windowed program, so a desktop start opens no terminal; a subcommand writes to the terminal
it was typed in, or to the pipes and files it was given (MCP keeps its stdio). Interactive cmd
and PowerShell don't wait for a windowed program: they show the prompt again before its output
and don't set %ERRORLEVEL% / $LASTEXITCODE. Scripts that need the exit code use
start /wait horizon-launcher … or Start-Process -Wait, or read its output through a pipe.
horizon-launcher versions [--installed] releases on GitHub, or what is installed (* = selected)
horizon-launcher install <tag|latest>
horizon-launcher remove <tag>
horizon-launcher prepare [--minecraft <dir>] [--version <tag>]
horizon-launcher play [--version <tag>] [--instance <name>] [--arg <ARG>]... start the client; --arg passes an extra argument through (repeatable)
horizon-launcher server [--dir <dir>] [--version <tag>] [--accept-eula]
horizon-launcher notes [<tag>] patch notes as plain text
horizon-launcher links the Discord, sponsor and website links
horizon-launcher game-args [<line>] show or set the extra arguments the game starts with
horizon-launcher update [--check] Horizon and the launcher
horizon-launcher logs [--lines N] [--crash] the end of the game's log, or its newest crash report
horizon-launcher account [list] the Microsoft accounts signed in (* = plays)
horizon-launcher account sign-in sign in in your browser with a code
horizon-launcher account sign-out <name>
horizon-launcher account select <name> play as this account
horizon-launcher account offline play without an account
horizon-launcher instance [list] the instances and their game folders (* = Play starts it)
horizon-launcher instance create <name> [--copy <instance>] empty, or a copy of one's worlds, options and packs
horizon-launcher instance rename|icon|group <instance> <value> icon: a block or item name, #rrggbb or none
horizon-launcher instance select <instance> make Play start it
horizon-launcher instance version <instance> <tag|latest> keep it on one installed version, or follow updates
horizon-launcher worlds [list] [--instance <name>] an instance's worlds
horizon-launcher worlds backup|copy|delete <world> [--instance <name>] delete moves it to the trash
horizon-launcher packs [list] [--kind resource|shader] [--instance <name>] resource and shader packs, on or off
horizon-launcher packs add <file> | on|off|remove <pack> [--kind shader]
horizon-launcher screenshots [list] [--instance <name>] an instance's screenshots, the newest first
horizon-launcher screenshots delete <name> [--instance <name>] moves it to the trash
horizon-launcher multiplayer [list] [--instance <name>] the servers in an instance's Multiplayer list
horizon-launcher multiplayer add <address> [--name <name>] | remove <server>
horizon-launcher modpack [list] the imported packs and their coverage
horizon-launcher modpack import <pack> a .mrpack, CurseForge zip or instance folder
horizon-launcher modpack port <pack> [--from <dir>] [--version <tag>] get the jars and carry them over
horizon-launcher modpack play <pack> [--version <tag>] play it as its own profile
horizon-launcher modpack remove <pack>
horizon-launcher mods [list] what the mod browser installed (docs/mod-browser.md)
horizon-launcher mods search <text> [--kind resource|shader|modpack] [--everything] [--source modrinth]
horizon-launcher mods install <id or slug> checksum checked, with what it needs; a modpack is imported
horizon-launcher mods update [<pack>] [--check]
horizon-launcher mods remove <pack>
horizon-launcher servers [list] the servers hosted from the launcher, and whether they run
horizon-launcher servers create [--name N] [--seed S | --copy-world DIR] [--mode M] [--difficulty D]
[--max-players N] [--open] [--check-accounts] [--player NAME]... --accept-eula
horizon-launcher servers set-up [--version <tag>] copy a prepared version's programs and game files for servers
horizon-launcher servers run <server> -- <horizon-server command> for example: servers run "My server" -- start
horizon-launcher servers forget <server> take it off the list (its folder stays)
horizon-launcher benchmark [--repeats N] measure the launcher (see Performance log)
horizon-launcher data [status] what data collection is allowed, the install ID, what waits
horizon-launcher data allow|deny <kinds|all> performance, crashes, bugs, benchmarks
horizon-launcher data show exactly what would be sent
horizon-launcher data delete delete what was sent and what waits; new install ID
horizon-launcher data bug "<text>" [--log] a bug report, printed for you to confirm first
horizon-launcher data benchmark <report.json> upload a benchmark report, printed first
--json prints JSON where there is something to report (versions, install, notes,
next, links, game-args, update --check, logs, account, benchmark, data, modpack). Errors go to stderr with exit code 1. server runs in the terminal,
so lines you type reach its console. HORIZON_LAUNCHER_HOME replaces the data folder (for
scripts and tests). Test runs also set HORIZON_LAUNCHER_TRASH=<folder> and
HORIZON_LAUNCHER_CLIPBOARD=<file> inside their scratch folders: what Delete would move to the
system's trash goes into that folder, and what Copy would put on the clipboard is written to that
file, so a test never touches the real trash or clipboard.
AI agents (MCP)
horizon-launcher mcp is a Model Context Protocol server over stdio. horizon-launcher agent-config prints the config for Claude Code, Codex, Cursor and any MCP client, and the
command that adds it; for Claude Code:
claude mcp add --scope user horizon -- horizon-launcher mcp
(with the full path to the binary if it is not on your PATH). Every CLI command is a tool of the
same name and arguments, generated from the CLI's definition (src/cli_tools.rs): versions,
install, remove, prepare, settings, mods_search, mods_install, modpack_import,
servers_create, data_status... A call runs that command with --json and returns its
output. Tools that delete something (remove, mods_remove, modpack_remove,
servers_forget, account_sign_out, data_delete) need confirm: true. The session runs a few
itself, under the CLI's names: play, server with server_command, server_log and
server_stop, account_list and account_sign_in (returns the link and code for the player;
the sign-in finishes in the background). A server started through MCP stops (cleanly, with
stop) when the session ends. Starting one needs accept_eula, which an agent should only pass
with the player's consent.
The same server also plays the game: play starts it with a local control socket (world,
creative, join and offscreen are optional), and every horizon-ctl tool of the selected
version is offered as game_<name>: game_state, game_ui (every widget of the open screen),
game_click, game_goto, game_break, game_inventory, game_screenshot (an image) and the
rest of horizon-ctl's commands. The launcher runs that
version's horizon-ctl mcp and passes the calls through; while no game runs, they answer with an
error saying to call play.
horizon-launcher no-ai on (the Settings page's No AI switch) hides and turns off every AI
feature: mcp and agent-config refuse, the game opens no control socket and horizon-ctl
refuses. Every other command keeps working.
The window, the command line and the MCP server all call the same core, src/backend.rs.
Microsoft accounts
The launcher starts the game only while a Microsoft account that owns Minecraft: Java Edition is
signed in, as other launchers do: without one, Play (Home, instances, horizon-launcher play, the
MCP's play) says "Sign in with a Microsoft account that owns Minecraft: Java Edition to play"
and starts nothing. Settings → Accounts signs in: the launcher shows a code, you enter it at
microsoft.com/link, and the launcher checks that the account owns the game. It asks again at every
Play, with the token renewal; with no network the last answer stands, and a "doesn't own" blocks
at once. The account also plays online-mode servers under your own name and skin; offline names
play LAN and offline-mode servers beside it. The game binary started directly (horizon-client,
horizon-ctl launch), the harnesses and the servers don't go through this check. Tokens live in the system keychain (Keychain,
Credential Manager, or the Secret Service on Linux), never in a file, a log or the command line;
accounts.json in the data folder keeps only names and UUIDs. Without a keychain you stay signed
in until the launcher closes. When the game starts, it gets the session in a private file it
deletes as soon as it has read it. Sign out removes the tokens.
Sign-in needs Horizon's own Azure app registration: its client ID is built in from
HORIZON_MSA_CLIENT_ID (and HORIZON_MSA_CLIENT_ID at run time overrides it; empty = not
configured). How it works: docs/microsoft-account.md.
Logs and crash reports
The game writes its log to logs/latest.log in the game folder (HORIZON_HOME, else
~/.local/share/horizon, ~/Library/Application Support/horizon or %APPDATA%\horizon), and
archives the previous one at start as logs/<date>-<n>.log.gz, as Minecraft does. When it
crashes it writes crash-reports/crash-<date>_<time>-client.txt beside them. When a game the
launcher started crashes, the Play page says so, with buttons to open or copy the report, open the
logs folder and report it on Discord; Settings has the logs folder too. A crash report leaves the
machine only if you turn on crash reports (below).
Data collection
The first time it opens, the launcher asks whether you want to send performance statistics,
crash reports, bug reports and benchmark uploads, each on its own switch and all off until you
turn one on ("Send nothing" is one click). Settings → Data collection changes them any time, shows
exactly what would be sent (the JSON), and deletes everything sent from your install. It asks
again only when what is collected changes. The game obeys the same answer, kept in
telemetry/consent.json in the game folder. What waits is sent when the launcher starts and
closes. Every field, the caps and the format: docs/telemetry.md.
Performance log
Each session writes its own measurements to benchmarks/launcher-<time>-session.json in the
launcher's data folder, in the benchmark report format: start to the
first frame, the release list fetch, installs and updates (download and unpack), preparing, Play to
the game's first frame, memory, UI frame times and the binary's size, with the machine's CPU, RAM
and OS (read on a thread after the first frame: on Windows those queries take seconds). Attach it
to an issue about the launcher being slow. horizon-launcher benchmark measures on purpose: it starts the launcher --repeats times (5; the first start is the cold one, as far
as the OS's file cache allows), switches pages for a few seconds in each, rests 3 seconds to
measure idle CPU, fetches the release list 3 times, and writes one report there with means and
95% confidence intervals. Its window has to be on a screen that is awake. Session logs stay on
your machine; a benchmark report is uploaded, dated to the day, only if you allowed benchmark
uploads (data collection).
Long lists on Mods (results, installed entries, coverage reports and project versions), Versions
and Patch notes build only nearby rows, including on their first frame. Variable row heights
are measured as they enter view; hidden ranges share spacers in the existing page scrollbar.
On Windows, cargo test --bin horizon-launcher desktop_list_renders_the_viewport_after_scrollbar_jumps -- --ignored checks 10,000 rows of different heights in a real GPUI window (requires an awake desktop/GPU).
Building
cargo build --release
On NixOS, nix-shell in this folder provides the libraries GPUI links to (Wayland, X11,
xkbcommon, Vulkan, fontconfig). macOS needs Xcode's command line tools. The launcher
workflow builds all three platforms on every push to the launcher branch (or a
launcher-* one), then installs the latest Horizon into a temporary folder and checks that the
client starts (--help) and that the binaries import only system libraries (Windows).
cargo test --lib # tag separation, version order, update selection, notes as text,
# the auto-install default, the links fallback
Geist and Geist Mono (SIL Open Font License, crates/horizon-theme/fonts/OFL-*.txt, shared with the game's on-screen keyboard) are
bundled so the type is the same on every platform.







