🤖 Agent: gate verified on e3e08911b (main merged). fmt, clippy -D warnings and horizon-client tests pass. lan_e2e with the server-thread host: 131/133, and cursor/falling pass on rerun in both modes. 14 in-frame checks pass. agent_control_e2e: only manage fails, and it fails the same way on main. 82 of the 83 affected harnesses pass. lan_integration flakes under load and is now named-only (nightly). Queued.
The harness runs the whole lan_e2e suite: ~40 minutes on a loaded runner,
and single checks flake under load (barter, riding, shulker_bullets in one
run; cursor, falling in another, all passing on rerun). Selected by
--changed it would run on most gameplay pull requests and on parity-main.
tools/parity_hosts.json gains a named_only table that every automatic
selection (--changed, --failed, the default, --flake-check) leaves out;
naming the harness or --all (the nightly audit) still runs it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The reach parse cache lives in the shared parity folder, keyed by content,
so a new worktree starts warm (written atomically, other worktrees'
entries kept within bounds).
- A harness's reach search stops as soon as it reaches a changed file that
differs in more than its `mod` declarations; partial searches are not
memoized. Declaration diffs are memoized (one git show per file).
- A harness whose changed dependency is outside crates/ is picked without a
search (narrowing always keeps those).
- After PARITY_REACH_BUDGET seconds (default 180) the remaining harnesses
count every source their probes link.
- crate_of is cached; no pathlib in the hot paths.
Same 79 harnesses on the 19-file diff from contrib-gate-16; CPU 19.9 s cold /
16.6 s warm before, 13.1 s cold / 9.9 s warm after (Mac mini under load).
parity.yml: skip the prepare_output check for heads made before #1947 (no
OUTPUT_VERSION, no tool): the job runs main's workflow on the head's files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Disk: clear_large_outputs only looked at .local directories named after the
harness, so the 174 harnesses writing to rust-<name>-parity kept every dump
(86 GB in one runner checkout, 13 GB for state_provider alone). It now
clears those too, keeps vanilla outputs only up to 256 MB (larger ones cost
more disk than rerunning their oracle), and handles shard names.
collision, world and geometry dropped one mkdtemp run directory per run
(11 GB): each run now removes the earlier ones.
Time: harnesses inherited the runner's stdin, a pipe on GitHub's runner, and
parity_shim never reuses an oracle run whose stdin is a pipe, so oracle reuse
never worked on CI. Harnesses now get /dev/null.
CI: parity.yml falls back to the head's own selection when the names main's
`--list` printed don't match its summary count.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`--changed` / `--changed-files` now narrow each harness's recorded Rust
sources to those its probes can reach (tools/parity_reach.py): an item-level
reachability walk from the probe modules a harness ran (or the binary it
built) over free items, inherent methods, trait impls and macros, matched by
name so it only over-approximates. Parent modules count through their `mod`
declarations, compared with the merge base. A harness whose probe can't be
placed keeps every source; `--by-crate` turns the narrowing off.
On the last 20 PRs merged on 2026-10-25: 2255 -> 1061 harnesses picked,
2384 -> 1592 harness-minutes. Replaying the week's 61 failed CI parity runs,
every failed harness the old selection picked is still picked; LLVM coverage of
six harnesses' probes executes only kept files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
decorated_region compiles its oracle into chunk-generation-parity; when it
passed while chunk_generation was still running, the trim deleted
chunk_generation's dumps mid-run (FileNotFoundError on
*-987654321987-full.txt in CI on #1569, #1571 and #1584).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The game logs into HORIZON_HOME and falls back to the player's own game folder when it is unset. Harnesses
that set only HORIZON_NO_AUDIO (and test_native_client, which set nothing) wrote logs, and on a world run
advancements, into ~/Library/Application Support/horizon.
lab.game_env gives every game a fresh HORIZON_HOME, HORIZON_LAUNCHER_HOME, XDG_DATA_HOME and XDG_CONFIG_HOME,
no audio and no launcher self-update; a folder the caller passes is kept. Every launcher of the game uses it,
parity.py gives each harness its own HORIZON_HOME, and tests/test_game_home.py fails for a script in tools/
that starts the game without it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
horizon-client, horizon-world and horizon-protocol each had an example
named `probe`. Cargo copies every crate's examples into one
<profile>/examples/ directory, so parallel parity harnesses sharing a
target dir could run another crate's probe (CI parity run 40055656303:
command_parse ran the horizon-world dispatcher). The examples are now
client_probe, world_probe and protocol_probe ([[example]] in each
Cargo.toml; the files stay where they are), and every harness builds
and runs its own crate's name.
tools/parity.py recognises the new names when it counts the probes a
harness ran, and renames the old `probe` in recorded artifacts to the
crate's target (from the record's -p, else its probe sources), so the
prebuild still builds them instead of dropping them as unknown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The harness failed about 1 run in 3 on CI with "play-trade villager false" on
Horizon's side: the protocol client never saw the villager it trades with.
A server pairs a player with an entity only while the entity's chunk has been
sent to it, and checks again when either moves (ChunkMap.move / updatePlayers).
VanillaProtocolClient sent its position once, at the spawn teleport, and never
again. Horizon's first chunk batch goes out two ticks after the player is placed
(after the first tick's packets, as vanilla orders them); when a busy machine
held the tick thread back long enough for the client's move to be handled one
tick after placement, the villager's chunk wasn't sent yet, and with no later
move the villager was never paired.
The 26.3 client re-sends its position every 20 ticks standing still
(LocalPlayer.sendPosition, after a ServerboundClientTickEnd each tick). The
parity client now does the same from the time it is loaded until its death, so
an entity whose chunk comes late is paired at the next reminder, on either
server.
On failure the harness now adds the servers' and clients' warnings and errors to
its message and the client log's tail when the client fails, and parity.py's
summary prints the end of the first failing harnesses' logs, so CI shows why.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main.rs now only lists the probes; dispatch.rs runs them. The runner leaves
main.rs out of a probe-binary harness's dependencies and keeps dispatch.rs,
so adding a probe no longer reruns (or flake-checks) every harness of its
crate. It also follows crate::<probe> references between probes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Now that the probes are modules of one binary, the helpers copied between
them live once in examples/probe/support: the collision probes' shape
printer (emit, values, box_values, bounds, 8 copies each), the canonical
NaN bits of f64 and f32 (50 copies), the registry JSON-to-NBT reader (15)
and the JSON block position (4). -1,097 lines of copies.
f64::NAN.to_bits() is 0x7ff8000000000000, the literal some copies used,
so every probe prints the same bytes.
tools/parity.py follows include!: three probes include another probe's
source, and stay dependent on it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 168 horizon-world examples the parity harnesses run become modules of
one example, examples/probe, that dispatches on its first argument;
crate::args() gives each the command line its example saw. Linking ~170
binaries, each instantiating horizon-world's generics again, was most of
the parity job's probe build (review plan, section 1).
The harnesses build `--example probe` and run `probe <name> ...`.
tools/parity.py counts only the probes a harness ran, and the support
modules they name, as its dependencies, so editing one probe still reruns
only its own harnesses (and PARITY_RUST_SCOPE keeps working).
The six manual tools (benches, loot_fill, structure_blocks, vanilla_diff,
attribute_memory, behaviour_load_profile) stay separate examples.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A record naming an example since renamed or deleted made the whole Cargo
run fail ("no example target named ..."), so no probe was prebuilt and
every harness built its own: the probe binaries rename ~250 of them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The probe prebuild ran `cargo build --workspace --examples --bins` (or
named targets with --workspace). The harnesses build with `-p <crate>`, and
Cargo unifies features over the selected packages: under --workspace,
flate2, libc, memchr, syn, thiserror, postcard and digest get other
features for horizon-world, and horizon-mods (pack) and horizon-telemetry
(upload) for horizon-client. So every probe the prebuild made was a
variant no harness used, and each harness rebuilt its crate and its probe
again while the others waited on Cargo's lock.
Record each probe with its command's package and feature flags and
whether that command builds examples (which turns dev-dependencies on),
and prebuild one Cargo run per such selection. --bins and --examples are
recorded too, so the fast-profile horizon-client build that
save_roundtrip and world_reopen run is prebuilt as well. A harness that
never ran here takes its probes from the shared dependency lists instead
of a build of all ~275 examples.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pr_ready is the one self-check to run before asking for review: from the
files a branch changes it runs rustfmt, clippy with warnings denied, the
tests of the touched crates (mapped through cargo metadata), the launcher's
own workspace when it is affected, the tokens.css check, the Cargo.lock
rule, the Python tests and the public-tree check, and the vanilla harnesses
the change can affect, picked by tools/parity.py exactly as CI's parity job
picks them. It runs locally or on a fleet machine through ~/fleet/rbuild;
on the Mac mini the harnesses use the parity runner's own workspace and
scripts. It prints one Markdown block for the pull request body and exits
non-zero on any failure.
tools/parity.py gains --changed-files for copies without Git history, and
tools/ci/vanilla-data.sh accepts the preparation key from outside.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
verify keeps its dependencies and workspace build in a cache saved by main's runs (Swatinem/rust-cache).
The new parity job runs on the Mac mini's self-hosted runner (label horizon-game), which holds the
maintainer's own Minecraft files: tools/ci/vanilla-data.sh links .local/ to the runner's copy and prepares
the resources with the pull request's own horizon-prepare, once per preparer version; then
tools/parity.py --changed <base> runs only the harnesses whose traced dependencies changed since the merge
base and that have no pass for their current inputs. A fork's pull request runs it only when a maintainer
adds run-parity, once per label, on the commit it had then.
parity.py: --changed REF, and linux_only in tools/parity_hosts.json for harnesses that differ on other
platforms for a reason that is not a Horizon bug (skipped there with the reason, unless named).
docs/release-flow.md starts with the pull request checks and merging.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Number the server files cases, whose folders eula-true and eula-TRUE collide
on case-insensitive macOS file systems; keep world_border_render and mob_ai
off the Mac, where platform float math changes their results; give the local
executor the dead flag the flake check reads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
parity.py --repeat N runs each harness N times; its runs must agree, else it
is reported FLAKY and recorded as failing. Later rounds run on the machine
that ran the first, reusing its oracle results, and never two at once there.
With --flake-check REF (passed by gate.py, merge base with parity-base; by
supervise.py integrate, the integration branch before the merge), harnesses
whose script, oracle or probe is new or changed must pass three runs in a
row; a pass records its rounds so it is not repeated again. gate.py matches
shards of a known failure by the harness's name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
parity.py:
- A harness declaring PARITY_SHARDS = N runs as N entries, NAME#1..NAME#N,
each told its share through PARITY_SHARD; NAME selects them all.
- PARITY_RUST_SCOPE lists the Rust sources a harness's probes can reach.
It applies only if closed (nothing in it names a module, crate-root
re-export or macro outside it, and nothing outside implements anything for
its types); crate roots then count without their other module declarations.
- Bytecode caches no longer count as dependencies.
- With other hosts, this machine runs 3 harnesses at once and only those
under 800 MB, admitted against the memory free at each start; hosts can
exclude harnesses and run at a nice level.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A worker started over ssh (parity.py --worker) runs harnesses on a mirror of
the worktree on another machine, here the Mac mini. One longest-first queue
feeds every machine, remote ones first; passes come back with the content
hash of each dependency, so they are keyed on exactly what ran. Harnesses
that need this machine are listed in tools/parity_hosts.json.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tools/gate.py took a crashed tools/parity.py for a pass with no new
failures. The runner now records each completed run, and the gate fails
unless its own run finished. A missing or empty trace (the disk was full)
no longer crashes the runner.
After a harness passes, the runner deletes its large regenerated outputs
(64 MB or more), keeping the vanilla oracle outputs that reusing an oracle
run needs. Full suites left 2-4 GB files in every worktree and filled the
disk.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A pass is keyed by the content of the harness's dependencies, so it is
also valid in any worktree with the same files. Passes are published to
parity-shared/ beside the shared vanilla data, and a worktree adopts one
whose key matches its own files instead of running the harness: an
agent's gate only runs what its own changes affect, instead of the whole
suite from scratch. Every runner on the machine also shares
PARITY_MACHINE_JOBS slots, so several agents' gates can't overload it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tools/parity_shim.py stands in for java and javac in harness runs: it
records each oracle run's outputs and the content of every file under the
paths its arguments name, and replays the run while those are unchanged.
After a Rust-only change a harness only pays for its Rust side
(block_update 53 s -> 5 s). A harness that fails while reusing oracle
results is rerun fresh, and stops reusing them if that passes; --all and
--fresh never reuse them.
Harness probes built in the dev profile are optimised (overflow checks
and debug assertions kept), and the chunk generation harness runs its
vanilla and Horizon sides concurrently.
HORIZON_NO_AUDIO keeps the game off the speakers; tools/parity.py sets it,
since the save round-trip harness launches the game on a virtual screen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tools/parity.py traces each harness (its Python, Java oracle sources and
the Rust sources of the probes it runs, from Cargo's dep-info) and reruns
it only when one of those, the vanilla artifacts or the toolchains change.
Harnesses run in parallel within the available memory, and their probes
build without LTO and with 16 codegen units into .local/parity-target: a
one-line change rebuilds every horizon-world probe in 3 seconds instead of
122. The full suite takes about 20 minutes instead of an hour.
tools/gate.py is the check a branch passes before integration, and
tools/supervise.py manages the per-area worktrees: status, new worktrees,
saving unfinished work, and integrating a branch only when the gate passes.
Harnesses find probes through lab.cargo_target() instead of a hard-coded
target/, the save round-trip harness builds the binaries it launches, and
the mob AI harness now fails on divergences like every other harness
(--report-only keeps the old behaviour). Debug builds keep line tables
only, so worktrees stay small.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>