Files
horizon-airdef/website
Ludovic Marie 4e7c25bb55 Remove shader packs
A developer of Iris showed that Horizon's shader pack code was written from
Iris's source. This removes it: the horizon-shaderpack crate, Java pack
loading and its render stages, Horizon's own WGSL packs and the example
packs, the Shader Packs screen and its option, the launcher's shader pack
browsing and installing, and the docs about them. Shader packs may come
back later as a clean-room rewrite.

The vanilla renderer and its post-processing (resolution scale, FXAA) are
unchanged. Options files that name a pack load and drop the key; the
launcher reads manifests that list a shader pack, and files in
`shaderpacks/` are never deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 16:15:39 +02:00
..
2026-10-24 16:15:39 +02:00
2026-10-24 16:15:39 +02:00
2026-10-24 16:15:39 +02:00
2026-10-24 16:15:39 +02:00

Horizon's website

Live at https://playhorizon.dev.

A landing page, a Releases page, a Benchmarks page and a documentation section in Horizon's design language (docs/design-system.md), with the tokens of crates/horizon-theme (generated into src/tokens.css by cargo run -p xtask -- tokens): the Blocks look: Pixelify Sans headings over Geist text, Horizon's purple in a light and a dark appearance (the system's, or the toggle's), square panels with an outline, a bevel and a lip, a stone texture and a pixel landscape drawn in code (scripts/art.mjs), and short, eased motion. A headline and the download, then a scroll through what it is, the screenshots, the parity grid, the launcher, the newest releases, the community links and the contributors. Plain HTML, CSS and JavaScript; no framework and no runtime dependencies.

Layout

build.mjs          renders dist/ (no dependencies)
docs/*.md          the documentation, one Markdown file per page: "# Title", a lede paragraph, then sections
docs/nav.json      the order of the pages and the groups of the left navigation
serve.mjs          a tiny static server for local checks
src/index.html     the page, with {{placeholders}} build.mjs fills
src/404.html       the not-found page
src/privacy.html   what the launcher can send when players allow it, retention, deletion (/privacy)
src/releases.html  every release, newest first, with what it adds and fixes, its notes and downloads (/releases)
src/benchmarks.html Horizon against vanilla 26.3 on our machines, the hardware picker, players' results (/benchmarks)
src/docs.html      the template of every documentation page (/docs/...), with its left navigation
src/tokens.css     the design tokens, generated from crates/horizon-theme (do not edit)
src/style.css      every style, on top of the tokens
src/main.js        the home page: download buttons, recent releases, links, contributors, nav, tabs, lightbox, motion
src/chrome.js      the light/dark switch, the phone menu and GitHub fetching, shared by the pages
src/releases-page.js  the Releases page: the channel filter and the refresh from GitHub
src/benchmarks-page.js  the Benchmarks page: players' results from the telemetry server, the picker
src/bench.js       the Benchmarks tables, shared by build and page
src/hardware.js    the spec table estimates use (GPU TFLOPS and bandwidth, CPU single-thread score)
src/estimate.js    estimates for unmeasured hardware from the nearest measured machine
src/markdown.js    a small Markdown renderer (escapes everything), shared by build and page
src/releases.js    the home page's "Recently added" and the Releases page's markup, shared by build and page
src/contributors.js the contributors' filtering and markup, shared by build and page
public/            fonts, screenshots, icons, og.png, robots.txt, benchmarks.json: copied as is (sitemap.xml is written by the build)
data/benchmarks/   our raw benchmark reports (tools/perf_bench.py) and fleet.json, the list scripts/benchmarks.mjs reads
scripts/benchmarks.mjs  benchmark reports -> public/benchmarks.json (ours, and players' groups of 3+ installs)
scripts/assets.mjs rebuilds public/ from the repo (fonts to WOFF2, images to WebP, the pixel art)
scripts/art.mjs    draws the stone tile and the hero's landscape (PNG, one pixel per texel)
scripts/shots.mjs  Playwright screenshots at every width, and og.png
Dockerfile, Caddyfile, railway.json   the Railway service
LEGAL.md           every asset, its source and licence
shots/             (gitignored) full-page screenshots at 360–1920 px, a 1250×700 window, the open phone menu

What comes from where

  • Built in (build.mjs): the parity table, its score, harness count and audit date, all from README.md; the documentation pages (/docs) from website/docs/; the Discord and Sponsor links from launcher/links.json; the latest releases and the contributors (/repos/ludovic111/horizon/contributors, bots left out, the owner first), when GitHub answers; without an answer the build still succeeds and the Contributors block and its menu entries stay hidden until the page loads them. Locally these come from the checkout; on Railway, which builds only this folder, from main on GitHub. The build fails if the README has no parity table, so a broken README never deploys.
  • Fetched by the page: the releases (the download button picks the newest launcher-v* release's asset for the visitor's OS, the home page lists the newest game releases, and the Releases page reloads its list when GitHub has a newer release), players' benchmark results (GET https://telemetry.playhorizon.dev/v1/benchmarks/summary, 3-second timeout), the contributors (avatars come from avatars.githubusercontent.com), and links.json from main. Each falls back to what was built in, and the download button to the releases page.

The documentation

docs/<slug>.md becomes /docs/<slug> (index.md is /docs/). Write for players first, then hosters, then contributors; plain words, short paragraphs; claim only what players can download today, and mark merged-but-unreleased things "next release". {{HARNESSES}} and {{PERCENT}} are filled from the README. A line holding only ![caption](/media/...) becomes a figure (the file must be in public/media/); links to other pages are /docs/<slug>#heading. The build checks every link between pages and every image, and fails with the list when one is broken. /server redirects to /docs/hosting. The developer docs in the repository's docs/ stay as they are and are linked from the Contributing page.

Run it locally

cd website
node build.mjs && node serve.mjs     # http://localhost:4173

Asset and screenshot scripts need the dev dependencies (sharp, wawoff2, Playwright):

npm install
GITHUB_TOKEN=$(gh auth token) node build.mjs   # optional token: bakes the releases past the API rate limit
npm run assets     # after a font, screenshot or logo changes in the repo
npm run shots      # shots/*.png (gitignored; SHOTS=dir to write elsewhere) and public/og.png

The screenshots are not committed: full-page PNGs at a dozen widths weigh about 17 MB. Running text stays under 80 characters a line; on 360–430 px phones the narrowest paragraphs fall to about 26–35, which is what that width allows. npm run shots also reports horizontal overflow, tap targets under 44 px on touch widths and characters per line, for 360 to 1920 px wide and a 1250×700 window.

Railway

The service deploys main (Railway's GitHub integration; there is no website branch any more) when /website/**, README.md or launcher/links.json change, and answers on https://playhorizon.dev. Its *.up.railway.app address redirects there with a 301 (the Caddyfile), so search engines index one URL; the page also names it as canonical, in its Open Graph tags and in sitemap.xml. Railway's healthcheck reaches $PORT under its own host name and is served normally.

Create a service from the GitHub repo with root directory website. railway.json selects the Dockerfile: a Node stage runs build.mjs (reading the README from main), and a Caddy stage serves dist/ on $PORT with compression, cache headers, a strict Content-Security-Policy and the 404 page. Its watch patterns also redeploy when the README or launcher/links.json change on main. No variables are needed; an optional GITHUB_TOKEN (read-only, public repos) lets the build bake the releases even when GitHub's anonymous rate limit is hit.

Rules the site keeps

  • The disclaimer, "Not an official Minecraft product. Not approved by or associated with Mojang or Microsoft.", is under the headline, in the phone menu, the footer and the 404 page.
  • No Minecraft logo or wordmark; "Minecraft" appears only descriptively ("for Minecraft: Java Edition 26.3"). No game files are offered or linked.
  • Parity is worded as the README words it: 100% of what the harnesses compare.
  • No cookies, analytics, trackers or third-party fonts and scripts.
  • Motion stops under prefers-reduced-motion; nothing hijacks the scroll.

Benchmarks

public/benchmarks.json is what the Benchmarks page shows: { format: "horizon-benchmark-summary", version: 1, generated, min_installs, groups }, one group per GPU, CPU, render distance and game (horizon, vanilla) with medians (avg_fps, low_1pct_fps, p95_ms, world_ms, chunk_load_ms, rss_peak_mb, rss_steady_mb). Groups "source": "us" are our runs (with the machine, date, version and the four scenes); groups "source": "players" come from players' benchmark uploads (docs/telemetry.md) and need at least min_installs (3) different installs. The telemetry server's GET /v1/benchmarks/summary answers in the same shape; the page asks it for players' groups and keeps the baked ones when it doesn't answer. The rules are in scripts/benchmarks.mjs, with a fixture set (scripts/fixtures/benchmarks/) the server's test shares.

To publish new measurements: run tools/perf_bench.py for Horizon and vanilla on a machine (docs/benchmark.md), copy the reports into data/benchmarks/, list them in data/benchmarks/fleet.json, then node scripts/benchmarks.mjs --ours data/benchmarks/fleet.json.