Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
33 KiB
Version ports: from a new Minecraft release to Horizon
Goal: every Minecraft Java release is turned into Rust by Horizon with as little human work as possible. A new
release is detected, downloaded and prepared on the Mac mini, compared with the version Horizon pins, everything
data-driven is regenerated, every parity harness runs against it, and what is left is a ranked list of agent-sized
work items, each a GitHub issue and a Kira card. This page is the design; tools/version_port.py implements it
(status at the end).
The downloads are for development only, on the Mac mini: nothing from them is ever committed, published or shipped
to players. Players still prepare their own copy from their official launcher (horizon-prepare), as today.
How Horizon pins and prepares 26.3 today
-
The pin.
research-lock.jsonminecraft: the version id, the sha1 of Mojang's version JSON (and of the republished ones it accepts), the client and server jar sha1s, the Java major version. Vineflower, REA and JADX are pinned beside it. -
The vanilla data.
tools/lab.py prepareimports the client jar, the server jar and the client libraries from the user's own launcher install, unpacks the server bundler intoserver-game.jar, and writes the class inventories;tools/lab.py decompileruns Vineflower on both jars. On the Mac mini all of this lives once in~/code/horizon-parity/shared-local/(minecraft/26.3/{jars,libraries,sources,reports}, 366 MB), and every worktree links to it from.local/. Release builds never do this:horizon-prepareimports the user's copy. -
The data generator.
horizon-preparerunsjava -DbundlerMainClass=net.minecraft.data.Main -jar server.jar --reportsinto.local/datagen/out/reports/:blocks.json,registries.json,packets.json,commands.json,datapack.json, item default components, biome parameters. It also runs Horizon's Java exporters (mods/horizon/src/test/java/dev/horizonlab/parity/Vanilla*Exporter.java) against the jar for block behaviours, worldgen bundles and the like. CI does the same throughtools/ci/vanilla-data.sh, keyed by the version inresearch-lock.json. -
Version facts in the code. The client jar's own
version.jsonstates them (26.3: world version 5023, protocol 777, resource pack 97.1, data pack 121.0, Java 25).tools/gen_version_consts.pywrites them into one generated file,crates/horizon-core/src/version.rs(VANILLA_VERSION,PROTOCOL_VERSION,DATA_VERSION, the pack formats, Java,/version's build time), and every Rust site reads them from there (horizon_protocol::PROTOCOL_VERSION,horizon_world::DATA_VERSIONand the save's version name are aliases of it; the status, the legacy ping, the crash report,/version, the title screens and the default resource folder use it). CI checks the file against the pinned jar (--check). Test fixtures that state a version (a level.dat to read, a relay message) keep their literals: they test parsing, not the pin. -
Generated tables (each has its generator in
tools/):Output Generator Input crates/horizon-core/src/version.rsgen_version_consts.pythe client jar's version.jsoncrates/horizon-protocol/src/ids.rsgenerate_packet_ids.pypackets.jsonreportcrates/horizon-client/src/lan/tracking.rsgenerate_entity_tracking.pydecompiled EntityTypescrates/horizon-client/src/lan/command_suggestions.rsgenerate_command_suggestions.pyVanillaCommandSuggestionsExportercrates/horizon-client/src/lan/command_order.rsgenerate_lan_command_order.pyvanilla's command tree crates/horizon-command/src/vanilla_order.rsgenerate_command_order.pycommand-parse harness export crates/horizon-world/src/datafix/block_state_data.txtgen_block_state_data.pydecompiled BlockStateDatacrates/horizon-world/src/datafix/flat_tables.rsgen_flat_tables.py1.13 flattening fixers (never change) Everything else data-driven (block behaviours, items, worldgen, models, sounds, languages) is not committed: the game reads what
horizon-prepareexports from the user's own jar, so it follows a new version by itself as long as the exporters still compile and the formats still parse. -
The proof. ~300 harnesses (
tools/test_*_parity.py) each compare a Rust probe with a Java oracle that calls vanilla's own classes;tools/parity.pyruns them and records each one's dependencies.
The pipeline
detect ─► prepare NEW ─► diff OLD..NEW ─► regenerate ─► harnesses on NEW ─► port report ─► issues + cards
(daily) (download, (data, protocol, (generators (candidate (ranked work (agents port,
verify, registries, per kind) worktree) items) pin bump PR,
datagen, assets, classes release)
class index) → Horizon map)
tools/version_port.py has one subcommand per stage and port NEW runs them all. Each stage writes its result
under the version store and can be rerun alone.
The version store
~/code/horizon-parity/versions/<id>/ (HORIZON_VERSIONS overrides it), next to shared-local/ and never inside
it:
versions/26.4/
version.json Mojang's version JSON (sha1 checked against the manifest)
jars/client.jar sha1 checked against version.json
jars/server.jar the bundler; server-game.jar beside it, unpacked with sha256 checks
datagen/reports/... the data generator's --reports output
classes.json the class index: every class's methods, each with a normalized bytecode hash
facts.json world/protocol/pack/Java versions from the jar's own version.json
prepared.json what was made, with which tool versions
versions/diff-26.3-26.4/ the diff, the source diffs of changed classes, the port report
About 150 MB a version (jars 120 MB, reports 20 MB, index 10 MB): client libraries are not needed (the data
generator runs from the bundler, which carries its own) and the full Vineflower tree is not kept (only changed
classes are decompiled, below). The store keeps at most three versions, the pinned one (whose files already exist in
shared-local/, so it is only indexed, not copied), the previous one and the candidate; version_port.py gc
deletes the rest and every scratch directory. Snapshots, if prepared, are deleted when the next one is.
Detect
version_port.py detect reads https://piston-meta.mojang.com/mc/game/version_manifest_v2.json, compares the
latest release (and, with --snapshots, the latest snapshot) with the pin and with
versions/detect-state.json, and prints what is new; exit status 0 means "nothing new", 10 "new release",
11 "new snapshot". It needs only the network, so it can run anywhere:
- Mac mini, launchd (chosen): the agent
dev.horizon.version-port(tools/ci/dev.horizon.version-port.plist) runstools/ci/version-port-daily.shdaily at 16:00 Zurich time (14:00 UTC in summer, 15:00 in winter; Mojang publishes on weekdays around 10:00-14:00 UTC), in a dedicated worktree oforigin/main(~/code/horizon-parity/port-main), at nice 10 with low-priority I/O (the plist'sNiceandLowPriorityIO;~/fleet/load prio lowwould lower the calling agent session instead). The whole port runs where the vanilla data, Java and the harnesses are. See "Turning on the daily agent" below. - GitHub Actions: a scheduled job could run
detectalone and open a "Minecraft X is out" issue; the Mac mini would still have to do the rest, so it adds a hop for nothing. Kept as the fallback if the Mac mini is down for long.
Snapshots are optional and only ever diffed (port --snapshot: prepare, diff, report, no issues), as an early
warning: agents can read what 26.4 changes weeks before it ships.
Prepare
version_port.py prepare NEW:
- Fetch the manifest, the version JSON (check its sha1 against the manifest), the client and server jars (check
their sha1s against the version JSON);
lab.downloadalready refuses partial or mismatched files. Releases since 26.1 ship unobfuscated, so no mappings are fetched; an obfuscated version (client_mappingspresent) stops here, aslab.py preparedoes. - Unpack the server bundler (
lab.unpack_server: one version, sha256 per entry). - Read the jar's
version.jsonintofacts.json. If its Java major is newer than the JDK on the Mac mini, stop with a single work item, "install JDK N" (a Ludovic task: fleet toolchains). - Run the data generator (
--reports) with that JDK intodatagen/reports/. - Index the classes of
client.jarandserver-game.jarintoclasses.json: for each class its superclass, interfaces, fields, and methods; each method's hash is over its bytecode with every constant-pool reference replaced by what it names (class, member, descriptor, literal), so recompiling unchanged code with a shifted constant pool hashes the same, and lambda bodies (lambda$name$N) are folded into the method that declares them so renumbered lambdas don't show as changes. This is exact where a decompiled-source diff is noisy, and takes seconds instead of the 10 minutes of a full Vineflower run.
The pinned version is indexed from shared-local/ the same way, without copying its jars.
Diff
version_port.py diff OLD NEW writes diff.json (and readable sections of the report), one section per kind:
| Kind | From | What it lists |
|---|---|---|
| Facts | facts.json |
protocol, world (data) version, pack formats, Java |
| Packets | packets.json |
per state and direction: added, removed, renumbered packets |
| Registries | registries.json |
per registry: added and removed entries, protocol-id shifts, new registries |
| Blocks | blocks.json |
added and removed blocks; per block: properties and values, state count, default state |
| Item components | components/item |
per item: added, removed and changed default components |
| Commands | commands.json |
added and removed nodes, changed arguments and executability |
| Data packs | jar data/minecraft/** |
added, removed, changed files by kind (recipe, loot table, worldgen, tags...), with a JSON path diff for changed files |
| Assets | jar assets/minecraft/** |
en_us.json keys added, removed, changed; models, blockstates, textures, sounds added, removed, changed (by hash) |
| Classes | classes.json |
added, removed and changed classes; per changed class the methods added, removed and changed (name and descriptor), field changes, hierarchy changes |
Changed classes are then decompiled, only those, on both sides: Vineflower runs on a small jar holding just those
classes (with the full jar as a library, so types resolve), and a unified diff per class goes into
diff-OLD-NEW/sources/. These diffs stay on the Mac mini: decompiled game code is never committed or posted
(tools/check_public_tree.py).
From vanilla classes to Horizon modules
The diff matters only through what it changes in Horizon. version_port.py map builds vanilla_map.json, from
the repository alone, in seconds:
- Oracles → classes. Each Java oracle and exporter under
mods/horizon/src/test/java/dev/horizonlab/parity/names the vanilla classes it imports and uses (net.minecraft.*imports and fully qualified names). - Harnesses → oracles and Rust. Each
tools/test_*_parity.pynames its oracle class; its probe (a crate's example or binary named in the script) and itsPARITY_RUST_SCOPEgive the Rust files it proves. - Rust → classes. Horizon's doc comments name the vanilla code they port (
/// SharedConstants.getProtocolVersion(),//! Fixes of 1.21.6 to 26.3,EntityType.Builder clientTrackingRange...): every CamelCase identifier in a comment that is the simple name of exactly one vanilla class (orOuter.Inner, orClass.method) maps that Rust file, and the method when named, to the class.
A changed vanilla class then resolves to the Horizon files that port it and the harnesses that prove them; a changed method resolves more precisely when a comment names it. A class with Horizon code but no harness, or with neither, is reported as such.
Regenerate
version_port.py regen NEW (tools/port_regen.py) makes the candidate and regenerates every table in it:
- The candidate: a worktree of the branch
port/NEW(from--base, defaultorigin/main) in~/code/horizon-parity/ports/NEW(HORIZON_PORTS), withresearch-lock.jsonpinned to NEW: version, the version JSON's sha1, the jars' sha1s, Java. Every tool reads the version from the lock (lab.VERSION,lab.GAME,lab.RESOURCES), so nothing else in the tree names the old one. - Its
.local/, a real directory:minecraft/NEWis prepared bytools/lab.py prepareitself, from a launcher-shaped copy of the download in the version store (versions/NEW/launcher: the version JSON, the client jar and its libraries, verified by sha1) intoversions/NEW/game, so the candidate's jars, libraries and manifest are exactly what a player's install gives.datagen/out/reportslinks to NEW's reports,toolsto the shared tools. Nothing is written intoshared-local/. - Decompiled sources: only the classes a generator parses (
EntityTypes,BlockStateData), each decompiled alone with the full jar and its libraries on Vineflower's path, intoversions/NEW/sources/(seconds instead of a 10-minute full run). The source is not the full run's text (inner classes of the jar's other classes are imported, chained calls are not wrapped), but what the generators read from it is: from 26.3's they write the committed tables byte for byte. - The generators, every row of the table above, each recorded in
diff-OLD-NEW/regen.jsonas unchanged, regenerated (with its--numstat) or failed (with the end of its output and, when an exporter or oracle no longer compiles, javac's errors one per line: they name the vanilla API that changed). Rust outputs are rustfmt'ed. The pin and what regenerated are committed onport/NEW, locally; nothing is pushed.
The report then lists what was regenerated in its summary and makes each failed generator a rank-1 item, instead of
guessing which tables the diff made stale. --only NAME... reruns some generators; port NEW runs regen after
diff (--no-regen for snapshots).
Three generators run vanilla through Horizon's Java oracles (VanillaCommandSuggestionsExporter,
VanillaCommandParseOracle) and one builds command_parse_fixture (Horizon's command tree in its own registration
order, before vanilla_order.rs reorders it), so regen needs Java and Cargo. horizon-prepare (the resources the
harnesses read) is the next stage's first step.
Run every harness against the new version
version_port.py harnesses NEW [NAME...] --baseline STATE (tools/port_harness.py), in the candidate regen
made:
-
horizon-prepare, built from the candidate and run there:
.local/horizon-client/NEWand its exporters' outputs. Itssoundsandpanoramasteps read the version store's launcher-shaped copy, whose asset index and the objects those steps copy (about 370 MB of sounds) are downloaded once per version from Mojang's resource server, each checked against its sha1. Regen's link to the version store's data generator reports is dropped first: horizon-prepare runs the generator itself, into the candidate. When the exporters no longer compile against NEW, the stage goes on and the report gets a rank-1 item with javac's errors (read from the log horizon-prepare names). -
tools/parity.py --allthere (or the harnesses named), from scratch. -
Each harness classified:
- pass: Horizon already matches NEW there;
- oracle: its log has javac errors, so vanilla's API changed under the oracle (javac names the symbol);
- fail: behaviour changed (or Horizon's data reading broke); the log has the first differing case;
and newly failing unless it fails in the baseline too:
--baselineis atools/parity.pystate.jsonfrom a run on the pinned version (the nightly audit's, or any worktree ofmain), so the known failures ofmaindon't count.--no-runclassifies the last run again against another baseline.
The result is diff-OLD-NEW/harnesses.json, with the logs of the harnesses that don't pass copied beside it
(harness-logs/, local only). report reads it: a newly failing harness marks the rank-2 item that names it as
failing (or "oracle no longer compiles"), a newly failing harness no item names gets its own rank-2 item, and the
summary counts pass / oracle / fail / newly failing.
The port report
diff-OLD-NEW/report.md and report.json:
-
Summary: facts changed, counts per kind, generators (clean / regenerated / failed), harnesses (pass / oracle broken / fail).
-
Work items, ranked. An item is one Horizon area, not one vanilla class: changed classes are grouped by the Horizon module (or, for unported code, by vanilla package) that owns them, so each item is a PR-sized task. Rank:
- blocks the switch: protocol or save format (packets, registries the protocol syncs, data version and the new DataFixer schemas), a failed generator, an exporter that no longer compiles;
- a harness that fails or whose oracle no longer compiles;
- a changed class Horizon ports but no harness covers (the risky kind: nothing will catch a difference);
- new content in data packs and assets the game reads at runtime (usually nothing to do: listed so it gets a look);
- changed vanilla code Horizon doesn't port yet (informational, no issue).
Within a rank, by the number of changed methods. Each item lists the vanilla classes and methods, the Horizon files and harnesses, the data-diff excerpts that concern it, and the local path of its source diffs.
-
Everything regenerated cleanly, so the reader knows what not to look at.
version_port.py publish OLD NEW (tools/port_publish.py) files the report as GitHub issues. It is a dry run
by default: it prints every issue it would create or update and the first bodies (--show N; --check-github
reads the open issues and labels, read-only, to tell creates from updates). --yes does it; the first --yes run
and the version-port label are Ludovic's call.
- One issue per item of rank 1, of rank 2 when a harness newly fails on NEW (or its oracle no longer compiles),
and of rank 3 when Horizon's comments name one of the changed methods: title
[NEW] <item>, labelsversion-portandport-NEW. An open issue with the same title is updated, and only its generated part (between<!-- version-port: generated -->markers) is replaced: what people wrote around it stays. - One tracking issue,
[NEW] Port to Minecraft NEW: the facts, the harness counts, a checklist of the item issues (made after them, so it has their numbers), and every other item: rank-2 leads no harness confirmed, rank-3 classes Horizon names but none of whose changed methods, new content, unported changes. - Kira: the board already shows every open issue as a card in To do, with its labels, so contributors and agents
claim them there (
task_claim);publishadds one Kira card for the port itself, linking the tracking issue (~/code/horizon-parity/tools/kira.mjs card_add, skipped if the board has it). - No decompiled code, ever: bodies hold names and signatures only (vanilla classes, methods with their JVM
descriptors, fields, packets, registry entries, harness names, javac's one-line errors about Horizon's own oracles,
Horizon paths, and the local path of the diff). Every vanilla-derived string is quoted, and
assert_no_coderefuses a body with a code block, or a line or quoted span that reads like Java (declarations, statements, braces).
For 26.2 → 26.3 the dry run would file 48 issues (5 of rank 1, 43 of rank 3) and the tracking issue.
The switch and the release
Work items are fixed on port/NEW branches stacked on the candidate (or as normal PRs into it). The pin bump PR is
the candidate branch itself: research-lock.json (version, sha1s, Java), the regenerated tables and
version_consts, the oracle fixes, and the ports. It merges when the report says no rank 1 or 2 item is left: every
harness passes against NEW, CI's vanilla data (tools/ci/vanilla-data.sh) and the Mac mini's shared-local/ have
been switched to NEW (the version store's files are copied in, then lab.py decompile runs once for the full
sources), and a playtest shows a world loads and a vanilla NEW client can join a Horizon LAN world.
A Horizon release then ships as usual (docs/release-flow.md), labelled feature with "Minecraft NEW" in its
title. Its research-lock.json makes horizon-prepare ask for NEW, so players must have played NEW once in the
official launcher; the launcher already reads each release's game version from its research-lock.json, so players
who haven't can keep the previous Horizon release. Supporting two Minecraft versions in one build is out of scope.
Turning on the daily agent
Not installed: it runs Mojang downloads, Cargo, Java and every harness on the Mac mini unattended, and its
publish dry run prepares issues under a new version-port label, so both need Ludovic's approval. What it does
on each run (tools/ci/version-port-daily.sh):
- update its worktree of
origin/main(~/code/horizon-parity/port-main, made on the first run,.local/linking the shared data as a parity worktree does); version_port.py detect --dry-run(--snapshotswithHORIZON_PORT_SNAPSHOTS=1): nothing new ends the run;- for a new release:
port NEW(prepare both, diff, regen intoports/NEWon the local branchport/NEW, report),tools/parity.pyon main as the baseline (only what never passed or changed reruns),harnesses NEWagainst that baseline,report OLD NEW, andpublish OLD NEW --check-github(the dry run: the log lists the issues it would file). A snapshot is only prepared, diffed and reported; detectagain, so the version is remembered only once its port went through (a failed run retries the next day), and a note on Kira card 28 (HORIZON_PORT_KIRA_CARD) saying the report is ready.
It never runs publish --yes, never pushes and never changes shared-local/. HORIZON_PORT_VERSION=V ports V at
once (by hand, or to replay a version); HORIZON_PORT_REF checks out another ref than origin/main.
To turn it on, on the Mac mini (as Ludovic, once approved):
# 1. The label publish files issues under (once):
gh label create version-port --repo horizon-rust/horizon --color 5319e7 \
--description "Work to port Horizon to a new Minecraft release (tools/version_port.py publish)"
# 2. The script, as a copy from main (it updates its own worktree, so it never runs from a checkout):
curl -fsSL https://raw.githubusercontent.com/horizon-rust/horizon/main/tools/ci/version-port-daily.sh \
-o ~/code/horizon-parity/version-port-daily.sh
# 3. The agent:
curl -fsSL https://raw.githubusercontent.com/horizon-rust/horizon/main/tools/ci/dev.horizon.version-port.plist \
| sed "s|@HOME@|$HOME|g" > ~/Library/LaunchAgents/dev.horizon.version-port.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/dev.horizon.version-port.plist
# A run now, to see it work (log: ~/Library/Logs/horizon-version-port.log):
launchctl kickstart gui/$(id -u)/dev.horizon.version-port
To turn it off: launchctl bootout gui/$(id -u)/dev.horizon.version-port and delete the plist. When the report is
in, publishing is still by hand: python3 tools/version_port.py publish OLD NEW (dry run), then --yes.
What stays manual, and how small it gets
Automatic: detecting, downloading, verifying, the data generator, every diff, the class-to-module map, regenerating data-driven tables, rerunning every harness, the report, issues and cards, and, once the report is clean, the pin bump branch.
Manual, by agents (and Ludovic's review):
- New mechanics (a new mob, block behaviour, worldgen feature, UI screen): someone has to port the new code. The pipeline hands them the exact classes and methods, the decompiled diff, the Horizon module and the harness to extend, so the agent starts at the code instead of searching for it.
- Changed behaviour under a failing harness: the harness names the case, the class diff names the method.
- Oracles whose vanilla API moved: javac names the symbol; usually a short fix.
- New harnesses for rank-3 items, where Horizon ports code that no harness checks.
- New JDK: a fleet toolchain install, once per Java bump.
- Judgment on the switch: merging the pin bump and releasing stay Ludovic's call.
Replay: 26.2 → 26.3
Horizon was written against 26.3 from its first commit, so its code is the ground truth of what porting 26.2 to 26.3
takes. On the Mac mini, prepare 26.2 took 9 s (136 MB), diff 26.2 26.3 6 s and report under a second.
| What 26.3 needed (in Horizon's code) | What the report found |
|---|---|
| Protocol 777, DataVersion 5023, pack formats | rank 1: protocol 776 → 777, world version 4903 → 5023, resource pack 88.0 → 97.1, data pack 107.1 → 121.0, with every site stating them (PROTOCOL_VERSION, both DATA_VERSIONs, the "26.3" names) |
ids.rs: ADD_TRANSIENT_BLOCK, POST_EFFECTS (configuration and play), SWING_ANIMATION, serverbound PUNCH instead of SWING; handlers in packets/play.rs, lan/hub.rs |
rank 1: exactly those packets, 131 renumbered, those files, and "regenerate ids.rs". Regenerating from 26.3's report reproduces the committed file byte for byte (after rustfmt); from 26.2's it differs by 274 lines |
DataFixers 4996-5016 in datafix/fixes_c.rs |
rank 1: schemas V4996, V4996_1, V4997, V5000 and 16 new fixes (MergeTerrainChunkStatusFix, ExplorerMapItemFix, SwingAnimationComponentSplitFix...), with datafix/ |
swing_animation split into attack_animation and interact_animation (first_person.rs, game/hand.rs, item_model.rs) |
rank 1: data component types +13 -2, those files among the ones naming them; per component, 1,537 items affected |
lan/tracking.rs and the command tables |
rank 1: regenerate tracking.rs, command_suggestions.rs, command_order.rs, vanilla_order.rs |
133 methods that exist only in 26.3 and that Horizon's comments name (Minecraft.startAttack, MultiPlayerGameMode.piercingAttack, SkyRenderer.buildEndSky, GameRenderer.render3dHud...) |
all 133: 132 in rank 2-3 items with the method named, on the file that ports it; 1 in the save-format item |
49 classes that exist only in 26.3 and that Horizon names (ComputeCommand, PostEffectCommand, LivingEntity.SwingState, BlockTransformer, StrawBedBlock...) |
all 49, in rank 4 "New in " items |
The report has 499 items: 10 of rank 1, 311 of rank 2, 96 of rank 3, 80 of rank 4, and 2 rank-5 summaries. Rank 1 is
exact. Ranks 2 and 3 are leads, not all work: 173 of their 407 items have a changed method that Horizon's comments
name. The rest are classes whose changed methods Horizon doesn't name. Many of those are mechanical (26.3 renamed
GuiGraphics to GuiGraphicsExtractor and reworked the render pipeline), and 105 are "widely named" buckets
(classes such as Entity that many files mention in passing). Running every harness against the new version is what turns a
rank-2 lead into a confirmed failure; since the oracles are written against 26.3, the replay runs that stage both ways
(below).
regen
regen 26.2 from a branch pinned to 26.3, then regen 26.3 --base port/26.2, both from versions downloaded from
Mojang (prepare --download):
| Generator | 26.3 → 26.2 | 26.2 → 26.3 |
|---|---|---|
version_consts |
regenerated (+8 -8: protocol 776, data version 4903, packs 88.0 / 107.1, build time) | regenerated, back to the committed file |
packet_ids |
regenerated (+134 -142) | regenerated (+142 -134), back to the committed file |
entity_tracking |
regenerated (+10 -13) | regenerated (+13 -10), back to the committed file |
block_state_data |
regenerated (+1 -1: the version in its header; BlockStateData is unchanged) |
regenerated, back to the committed file |
lan_command_order, vanilla_command_order |
failed: VanillaCommandParseOracle.java:81: no suitable constructor found for CommandSourceStack(...) and 2 more |
unchanged |
command_suggestions |
failed: ParityData.java:45: cannot find symbol: variable WORLD (class RegistryLayer), WORLD_REGISTRIES |
unchanged |
After the round trip, port/26.3 equals the branch it started from except research-lock.json's five lines of
republished 26.3 version JSONs it accepts (which a port cannot know). So every table Horizon commits is what the
generators make from vanilla's data, and going 26.3 → 26.2 fails exactly where Horizon's oracles use 26.3-only API,
with javac naming it. The report for 26.2 → 26.3 then has 5 rank-1 items instead of 10: the 5 "regenerate" guesses
are replaced by the generators' results in its summary. Both regens took under three minutes, most of it building
command_parse_fixture.
harnesses
The 17 harnesses closest to what 26.3 changed (block_properties, chunk_generation, command_exec, command_parse, component_patch, datafix, entity_components, first_person, food, hotbar, item_model, lan, light, options, resource_pack, save_roundtrip, sound_event; 22 runs with datafix's 6 shards), on the Mac mini at 2 to 3 jobs while CI and other sessions ran:
26.3 → 26.2 (ports/26.2) |
26.2 → 26.3 (ports/26.3) |
|
|---|---|---|
| horizon-prepare | failed: VanillaHeightmapOracle.java:33: cannot find symbol: variable FULL_CODEC (class BlockState) (rank 1) |
ok, sounds and panorama from the downloaded assets |
| pass | 1 (food) |
17 |
| oracle no longer compiles | 4: chunk_generation (ServerChunkCache constructor), command_exec (com.mojang.authlib.services), command_parse and hotbar (RegistryLayer.WORLD) |
0 |
| fail | 12 (Horizon's 26.3 packet tables and resources don't build or match 26.2) | 0 |
| newly failing, against the other direction's run as baseline | 16 | 0 |
| time | about an hour, most of it datafix's vanilla upgrades | about 85 min, 10 of them horizon-prepare |
So on a real port the stage separates the three cases the report needs: what already works on NEW, oracles whose vanilla API moved (javac names the symbol, as for regen), and behaviour that changed; and a candidate that is right passes everything it ran. The 26.2 → 26.3 report's summary now reads "harnesses on 26.3: 17 pass, 0 whose oracle no longer compiles, 0 fail; 0 newly failing".
Implementation status
python3 tools/version_port.py detect --snapshots # exit 10: new release, 11: new snapshot
python3 tools/version_port.py prepare 26.3 # the pinned version: indexes .local, downloads nothing
python3 tools/version_port.py prepare 26.4 # downloads, verifies, runs the data generator (about 10 s)
python3 tools/version_port.py diff 26.3 26.4 # summary; the full diff in versions/diff-26.3-26.4/diff.json
python3 tools/version_port.py regen 26.4 # ports/26.4 on port/26.4: pinned, every table regenerated
python3 tools/version_port.py report 26.3 26.4 [--results .local/parity-runner/state.json] # report.md, report.json
python3 tools/version_port.py harnesses 26.4 --baseline .local/parity-runner/state.json # in ports/26.4
python3 tools/version_port.py publish 26.3 26.4 # dry run: the issues it would file (--yes files them)
python3 tools/version_port.py port 26.4 # prepare both, diff, regen, report
python3 tools/version_port.py gc # keep the pinned version and the newest two others
python3 tools/vanilla_map.py versions/26.3/classes.json net.minecraft.world.food.FoodData # its Rust files and harnesses
| Slice | Status |
|---|---|
| This design | this page |
detect, prepare, diff (facts, packets, registries, blocks, items, commands, data, assets), gc |
done |
Class index (tools/class_index.py), class diff with moves, vanilla_map.json (tools/vanilla_map.py) |
done |
Port report (tools/port_report.py, report), port |
done |
version_consts: one generated file for the version facts (tools/gen_version_consts.py) |
done |
Tools read the version from the lock (lab.VERSION, lab.GAME, lab.RESOURCES, lab.version_info()) |
done |
regen (tools/port_regen.py): candidate worktree, its .local/, the generators' classes decompiled alone, every generator, regen.json in the report |
done |
Candidate harness run (tools/port_harness.py, harnesses): horizon-prepare with sounds, every harness, pass / oracle / fail, newly failing against a baseline, in the report |
done |
publish (tools/port_publish.py): dry run by default, issues, tracking issue, Kira card |
done (never run with --yes) |
Daily launchd agent (tools/ci/version-port-daily.sh, tools/ci/dev.horizon.version-port.plist) |
written, not installed: needs Ludovic's approval |