Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
The roadmap
roadmap.toml is the one source of the roadmap. tools/roadmap.py build turns it into
ROADMAP.md; the website's hub (playhorizon.dev/hub) and Kira read the same items. The work
board stays the task-level view (every issue and pull request); the roadmap is the overview, and each pull
request carries the roadmap:<id> label of the item it moves.
An item
[[item]]
id = "server-dialogs" # lowercase words joined by dashes; never renamed (votes and links use it)
title = "Server dialogs and login screens"
status = "now" # now | next | later | help-wanted | shipped
area = "servers" # one of the [[area]] ids, listed in the order of priority
focus = true # optional: one of the 3 to 5 Now items in the overview
ongoing = true # optional: a workstream that goes on after its pull requests merge
plain = """Servers that show dialogs ... will work in Horizon.""" # players' words, at most 240 characters
dev = """26.3's dialog packets and model (#1720), ...""" # crates, approach, what is left
links = ["#1720", "#1734", "docs/hosting.md"] # #N, a repository path, or https://
shipped_in = "v0.2.58" # shipped items only: a game tag, launcher@x.y.z, or "website"
post = "..." # optional: the announcement when it ships, if `plain` doesn't read as one
announce = true # optional: when it ships, Kira also posts it on X (`post`, else `plain`)
- No dates, ever: Now, Next, Later. The check rejects month names, years, quarters and "next week". "Coming soon" is the only phrase for something waiting on someone else (Microsoft sign-in, CurseForge).
- Only what is decided. Ideas stay in issues and in the suggestion queue until they are.
- Only what is true in a release for shipped items: a merged pull request isn't shipped until a release has it.
plainis for players: what they will be able to do, not how.devis for contributors: the code, the vanilla classes, the pull requests, what is left. Both stay public: no machines, accounts or private notes.- Help wanted items are good first contributions: a clear vanilla behaviour, an issue with steps, a place to
start in
dev.
Keeping it current
Whoever moves the work (the orchestrator on each heartbeat, or anyone in their pull request):
python3 tools/roadmap.py sync: every item whose linked pull requests all went out in a release, whose linked issues are all closed and that isn'tongoingbecomesshipped, with that release inshipped_in, on top of the shipped items. It rewritesroadmap.tomland ROADMAP.md; open a pull request with the change (skip-release).python3 tools/roadmap.py check: the file's rules, then GitHub's state of every link. It fails when a link is not an issue or pull request, when a Now item has no open pull request and none merged in the last 14 days (link the new work, or move it to Next or Later), and when every link of a Help wanted item is closed. It also says which itemssyncwill ship once a release has them.- When work starts, opens a pull request or is decided: add or move the item, add its links, then
python3 tools/roadmap.py build. Keep the overview's focus to the 3 to 5 biggest Now items. python3 tools/roadmap.py labels(after the change merges): creates aroadmap:<id>label for each new item, updates their colour (by status) and description, and adds the label to the open pull requests an item links. Label a new pull request for an item by hand or by linking it and runninglabelsagain. Labels of removed items are listed, never deleted.- Once a week, write the week's summary in
roadmap/journal/<year>-<ISO week>.md(for example2026-43.md):# Title, a paragraph or two for players, then## Technicaland the same week for contributors, with its pull requests.buildputs it into roadmap/roadmap.json, which the website reads from main; the hub's Journal tab shows it among the releases, and/data/journal.jsoncarries it to Kira's #updates and to X. - Suggestions from the website and Discord reach the maintainers' private channel through Kira (below). A good one becomes an item in a normal pull request; none is published automatically.
CI runs tests/test_roadmap.py (in the Python tests), which fails when ROADMAP.md is not what build writes
or when the file breaks a rule; python3 tools/roadmap.py check --offline runs the same rules without GitHub.
The journal
The hub's Journal tab and https://playhorizon.dev/data/journal.json are built with each deploy of the website
(src/hub.js in horizon-rust/horizon-site). The tab shows every game and launcher release with its notes' changes, each placed under
the roadmap item it moves (the pull request's roadmap:<id> label, else the item that links it), the merged pull
requests no release has yet, and the weekly summaries above.
journal.json is what Kira reads for #updates and its X posts (kira's src/journal.js): one flat entry per
merged pull request, release, shipped item and weekly summary, newest first, the latest 300.
{ "format": "horizon-journal", "version": 2, "generated": "…", "url": "https://playhorizon.dev/hub#journal",
"entries": [
{ "id": "pr-1788", "kind": "merge", "date": "…", "title": "the pull request's title", "url": "…",
"number": 1788, "item": "discord-presence", "item_title": "Discord Rich Presence" },
{ "id": "release-v0.2.57", "kind": "release", "date": "…", "title": "…", "plain": "one line", "url": "…",
"tag": "v0.2.57" },
{ "id": "shipped-server-resource-packs", "kind": "shipped", "date": "the release's", "title": "…",
"plain": "…", "url": "https://playhorizon.dev/hub#server-resource-packs", "item": "…", "item_title": "…",
"shipped_in": "v0.2.48", "post": "at most 240 characters", "announce": false },
{ "id": "weekly-2026-43", "kind": "weekly", "date": "the week's last second", "title": "…", "plain": "…",
"dev": "…", "url": "https://playhorizon.dev/hub#week-2026-43" } ] }
- An
idnever changes: readers post each one once.mergeentries are the pull requests players see (noskip-releaselabel), plus any that moves a roadmap item;itemis absent when none does. announce: true(the item'sannouncekey) asks Kira to post the shipped item on X too, withpost.https://playhorizon.dev/data/roadmap.jsonisroadmap.tomlas JSON (areas,itemswith every key above,overview, the weekly summaries); an item's page ishttps://playhorizon.dev/hub#<id>.
The intake: votes, bug reports, suggestions
The hub's forms and vote buttons talk to the telemetry server (https://telemetry.playhorizon.dev,
crates/horizon-telemetry-server, src/hub.rs and src/intake.rs; its README lists the limits and settings).
Kira uses the same endpoints, so a bug or an idea goes the same way from the site and from Discord.
| Endpoint | Body | Answer |
|---|---|---|
POST /hub/vote |
{"item": "<roadmap id>", "voter": "<random id, 16 to 64 characters>", "up": true} |
{"item", "votes"}; "up": false takes the vote back |
GET /hub/votes |
{"votes": {"<item>": n}}, a minute old at most |
|
POST /hub/report |
{"title", "did", "happened", "expected", "version"?, "system", "name"?} |
201 {"id", "issue": "<GitHub URL>"}; 202 {"id", "issue": null} when GitHub was down (opened later) |
POST /hub/suggest |
{"text", "item"?, "name"?} |
202 {"id"} |
GET /hub/suggestions?after=<id> |
Authorization: Bearer <HORIZON_TELEMETRY_HUB_TOKEN> |
{"suggestions": [{"id", "text", "item"?, "name"?, "created_at"}]}, oldest first |
- A report becomes an issue at once, labels
bugandfrom-website, in the#horizon-bugstemplate (What I did, What happened, What I expected, Horizon version, OS, CPU, GPU) with "Reported on playhorizon.dev". Every field but the version and the name is required; mentions,#123references and HTML in people's text are made inert. - Suggestions are never published. Kira polls
/hub/suggestionswith the last id it relayed and posts each one to#roadmap-suggestions. - Errors are
{"error": "…"}: 400 names the field ("did: 3 to 2000 characters"), 429 means too many from that address, 503 a full day or reports turned off (no GitHub token on the server). - The page shows the forms and the buttons only once
GET /hub/votesanswers. Until then it points to GitHub and Discord. It keeps a random voter id and the items it voted for inlocalStorage(hub:voter,hub:voted).