Move-only step B6 of the horizon-client crate split. These four modules
only reached gameplay modules for small helpers, so after this they depend
only on the render/world layer and can move into its crate whole, impls
included.
- save::float moves to horizon_client_core::nbt_value (save re-exports it).
- mobs::spawner::normalize_id moves to horizon_client_core::identifier
(spawner re-exports it).
- containers::Providers moves to its own module, providers.rs
(containers re-exports it).
- vault, command_block and sculk import number/text/float, item_nbt,
Who and scoreboard from their real homes instead of through save and
mobs; brewing imports Providers from providers.
- The vault, spawner and trial_spawner harnesses record the client-core
files the moved helpers now live in.
- villager_brain's `impl Poi` (max_tickets, valid_range) moves next to
the type in poi_sections, and its two methods become pub.
No behaviour change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Step A of the horizon-client crate split. `Chunk.block_entities` held
`containers::BlockEntity`, whose payloads were defined across containers,
brewing, signs, sculk, mobs::spawner, mobs::trial_spawner, vault, beehive
and command_block, so `chunk` reached most of the gameplay code.
The enum and every payload type now live in `chunk/block_entity.rs` as
plain data (definitions and derives). Every impl block stays in its
feature module, and each module re-exports its types under the old path,
so callers don't change. Also:
- `mobs::Who` moves to `horizon_client_core::who` (the vibration `Info`
names it); mobs re-exports it.
- `skins::NameOrId` moves beside `SkullSkin`, which holds it; skins
re-exports it.
- `Spawner.next`, the `Next` enum and `Selector.current` become
`pub(crate)`, since their struct now lives in another module.
- The vault, spawner and trial_spawner harnesses record
`chunk/block_entity.rs`.
No behaviour change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under parity.py the release and fast profiles have identical settings
(PROFILE: no LTO, 16 codegen units, incremental), yet 38 harnesses built
horizon-client in release and 16 others in fast: every run where the client
changed compiled it twice. They all use fast now. Release stays for
horizon-world's probes, which no fast harness builds.
Measured on playhorizon (4 CPUs), all 13 probe builds after 78 merges:
1578 s, of which the release client (bin + client_probe) took 485 s.
Co-Authored-By: Claude Opus 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>
81 client examples that harnesses run become modules of examples/probe:
harnesses run `probe <name> ...` (or `cargo run --example probe -- <name>`),
and the shared helpers (audio_common, hud_fixture, chat_screen_fixture)
move with them. The benchmarks and manual tools stay standalone examples.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lab.VERSION, lab.GAME (.local/minecraft/<version>) and lab.RESOURCES
(.local/horizon-client/<version>) come from the lock, and lab.version_info()
reads the pinned client jar's version.json. Every harness, generator and
tool that named 26.3 (resource folders, jar and source paths, result
records, the vanilla client's --version and asset index, the bare login
client's protocol, the benchmark's options.txt data version, the packet id
table's header) uses them, so a candidate worktree pinned to another version
runs them unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spawners were never ticked: Horizon kept their block entity as loaded NBT,
and a freshly generated dungeon or mineshaft spawner lost even that, so no
mob ever came out, nothing turned inside and a spawn egg used on one spawned
a mob next to it instead of setting the spawner's.
The spawner is now a modelled block entity (mobs::spawner): BaseSpawner's
load and save with SpawnData's codec (normalised ids, custom spawn rules,
weighted potentials, the next data shared with the one potential when the
potentials are missing), serverTick (the player range, spawner_blocks_work,
the delay, getOrCreateNextSpawnData, Pos, the spawn box against blocks and
solid shulkers, custom light rules and peaceful, SpawnPlacements, the nearby
cap, the yaw, checkSpawnRules and checkSpawnObstruction, finalizeSpawn for
an id alone, the 2004 burst, entity_place and the spawn poof) and delay with
its block event, in the level random's order. The client copy keeps its own
delay for the spin and puffs smoke and flames; SpawnerRenderer draws the
next mob small and turning. SpawnEggItem.useOn on a spawner sets its mob
(setEntityId), tells the player when spawner_blocks_work is off and fails in
peaceful for monsters. Breaking one pops 15 + nextInt(15) + nextInt(15)
experience whatever the tool. LAN guests get the update tag, the block event
and the entity event.
tools/test_spawner_parity.py ticks vanilla's SpawnerBlockEntity on the
natural spawn oracle's level against mobs::spawner on the same worlds (6,000
cases); tools/test_use_item_on_parity.py now covers spawn eggs on spawners.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>