Skip to content

chore: latest dependencies, current nightly, and flecs on upstream main - #961

Merged
andrewgazelka merged 11 commits into
mainfrom
chore/latest-deps
Jul 27, 2026
Merged

chore: latest dependencies, current nightly, and flecs on upstream main#961
andrewgazelka merged 11 commits into
mainfrom
chore/latest-deps

Conversation

@andrewgazelka

@andrewgazelka andrewgazelka commented Jul 27, 2026

Copy link
Copy Markdown
Member

Brings every dependency to its newest release, moves the toolchain to a current nightly, and takes flecs off the crates.io release onto upstream main.

Part 2 of the original brief — booting the server — landed separately as #960 while this was in flight. This branch is reconciled in #960's favour throughout: its process-compose dev, its .hyperion-dev-certs generator, its deletion of crates/hyperion-proxy-module. My embedded-proxy work is dropped; deleting the crate is a better answer to ENG-10327 and ENG-10328 than making its certificate paths and port configurable.

Dependencies

Every crates.io dependency at its newest release, majors included: rand 0.9→0.10, reqwest 0.12→0.13, syn 2→3, toml 0.8→1.1, egui/eframe 0.31→0.35, criterion 0.5→0.8, roaring, heapless, heed, ndarray, snafu, sha2, base64. Toolchain nightly-2025-05-05 → nightly-2026-07-26 (rustc 1.99).

flecs: upstream main, not 0.2.2

0.2.2 is the only non-yanked crates.io release and is eight months behind. main vendors flecs C 4.1.6 rather than 4.1.2, makes World and Query !Send with a QueryHandle for the cross-thread case, and fixes a QueryIter double free. The !Send work is the reason to take it: SendableQuery's hand-rolled unsafe impl Send, whose safety argument was never written down, is deleted in favour of the upstream type built for exactly this, and player_join was already using the iter_stage pattern QueryHandle requires.

main does not work as it stands. Its new sparse-term safety locks hand a stage pointer to flecs_components_get, which asserts on ecs_world_t_magic, so every system with a sparse term aborts under set_threads > 1 — which is every system here:

fatal: flecs.c: 40950: assert(hdr->type == ecs_world_t_magic): ecs_stage_t (INVALID_PARAMETER)
  flecs_components_get
  flecs_ecs::core::safety_map::do_read_write_locks
  flecs_run_system

Fixed in Indra-db/Flecs-Rust#302; pinned to that branch until it lands, with the reason in Cargo.toml.

Three things 4.1.6 breaks that only a newer flecs reaches

  • hyperion-hot-reload's schema gate silently stops working. flecs stopped creating a child entity per struct member unless a type opts in with create_member_entities, so the gate's child walk found nothing and returned Layout::Unknown for every reflected component — a gate that sees no fields cannot see a field change. It reads the EcsStruct member vector now, which is what flecs serializes from and exists either way. Two of feat: hot reload flecs modules, refusing layout changes without a migration #958's own tests caught it.
  • Module import aborts on a name collision. 4.1.6 checks symbol consistency where 4.1.2 did not, and hyperion::egress::stats::StatsModule and bedwars::module::stats::StatsModule both registered as StatsModule. The bedwars one writes the tab list and nothing else, so it is TabListModule in module/tab_list.rs.
  • events/smash pinned crates.io 0.2.2, which would have put two component registries and two copies of flecs C in one lock. (Main reached the same conclusion independently in feat(smash): make Super Smash Mobs runnable and joinable #966.)

bvh vendored

crates/bvh instead of a git pin on TestingPlant/bvh-data: it is our own crate, the pin was a year stale, and it needed a dead #![feature(array_chunks)] removed (TestingPlant/bvh-data#1, sent upstream). Vendoring brought its tests into the workspace and showed that generic_const_exprs was declared, incomplete and unnecessary: dropping it fixes an E0391 cycle its own suite hit.

What did not move

glam stays at 0.29.3. valence_protocol exposes glam types across its whole public API and the vendored fork pins 0.29.3; anything newer puts two glam crates in the tree and every valence signature stops accepting the vectors hyperion holds. Moving it means bumping glam in TestingPlant/valence first, on both pinned branches.

Idiomatic, not minimal

  • 29 feature gates deleted, enumerated from rustc's own diagnostics rather than guessed. let_chains, assert_matches, maybe_uninit_slice and array_chunks went stable; the rest were never used.
  • hyperion-stats' array_chunks iterator dance is a step_by loop, needing no unstable feature at all.
  • hyperion-proxy's public surface is actually public: two pub items named private types. ProxyIdentity loads the proxy's mTLS material before anything binds, so a bad path names itself instead of failing inside a spawned task.
  • Collapsed if let chains, bit_width, is_multiple_of, mul_add, ?, Self, across this branch's code and the code that landed on main under the old toolchain.

Two runtime bugs #960 did not cover

BOT_BOT_COUNT never reached rust-mc-bot. envy::prefixed("BOT_") strips its prefix with trim_start_matches, which removes every leading repetition, so the value arrived as count, never matched bot_count, and silently fell back to the 500 default. #960's own description shows the symptom: BOT_BOT_COUNT=5 producing 295 players. rust-mc-bot takes clap arguments backed by the documented variables, positionally as well, so nix run .#bots -- 127.0.0.1:25565 100 connects 100 bots.

A second checkout could not run nix run .#dev at all. The ports and process-compose's own API port were constants, so the second one dies on listen tcp 127.0.0.1:8080: bind: address already in use before either process starts. HYPERION_PLAYER_PORT and HYPERION_SERVER_PORT override them and the API port follows the player port.

Also: the stats system logs the player count and tick averages once a second. They were previously visible only in a connected client's tab list.

Verified

Every CI gate, run locally through the same flake apps:

nix run .#lint          rc=0
nix run .#test          rc=0   273 tests, 273 passed
nix run .#doc           rc=0
nix run .#fmt -- --check rc=0
nix run .#deny          rc=0   advisories ok, bans ok, licenses ok, sources ok
nix run .#unused-deps   rc=0
nix run .#hot-reload-registry-guard  rc=0

Both audit jobs are blocking in CI now. cargo deny was failing on 10+ advisories including the use-after-free in openssl's Md::fetch and both webpki certificate-validation bugs; the bump removes every one. Five unmaintained notices remain — instant, paste, bincode, yaml-rust, ttf-parser — each on a crate this workspace does not choose, none with an upgrade available, each listed in deny.toml with its path and reason. Licences: hyperion-gui declares Apache-2.0, rust-mc-bot declares the GPL it actually is, and two third-party font/public-domain licences get narrow per-crate exceptions rather than a widened global allow list. cargo machete's 11 unused dependencies: 20 removed, 3 kept as documented false positives.

And it runs. Two stacks on one machine at once — the default ports held by another checkout, this one on 25567/35567:

$ HYPERION_PLAYER_PORT=25567 HYPERION_SERVER_PORT=35567 nix run .#dev
players: 0.0.0.0:25567 | game server: 127.0.0.1:35567
[proxy      ]  INFO 📡 Public proxy address: tcp://0.0.0.0:25567
[proxy      ]  INFO 👾 Internal server address: tcp://127.0.0.1:35567
[proxy      ]  INFO 🔗 Connected to server, accepting connections
[game-server]  INFO stats: 0 players online | tick µ/5s 0.14 ms, µ/15s 0.05 ms, µ/1m 0.68 ms

$ nix run .#bots -- 127.0.0.1:25567 100
 INFO rust_mc_bot: connecting 100 bots to 127.0.0.1:25567 across 18 threads

[game-server]  INFO stats: 100 players online | tick µ/5s 25.29 ms, µ/15s 8.97 ms, µ/1m 6.90 ms
[game-server]  INFO stats: 100 players online | tick µ/5s  5.18 ms, µ/15s 7.18 ms, µ/1m 7.94 ms

Settling to ~2 ms per tick against a 50 ms budget in a debug build once the join burst clears.

Known gaps

  • nix flake check's packages output still fails, unchanged from main: cargoUnit cannot vendor a lock holding one crate at one version from two git sources, which this lock still does via tools/packet-inspector's valence?branch=feat-open. That job stays continue-on-error.
  • cargo machete prints an error line for tools/antithesis-bot, which is not a workspace member, references an antithesis_sdk workspace dependency that does not exist, and cannot build — orphaned when docker went away in refactor: nix apps replace justfile, direnv and docker; generate the Minecraft protocol from Mojang #955. Filed as ENG-10363; does not affect the exit code.
  • The shutdown path panics with "Cannot drop a runtime in a context where blocking is not allowed". Pre-existing, exit-only, not investigated.

`main` panics on startup before accepting a single connection. Three
separate faults, none of which a test or a build catches.

`flecs::With` constructs its target through `Default`, so
`BowModule`'s `LastFireTime` and `BowCharging` aborted module import
with "flecs::with requires a default-constructible target". Both now
have a `Default` chosen for its meaning: never fired, and not drawing.

The in-process proxy read its certificates from three relative paths
compiled into `hyperion-proxy-module`, ignoring bedwars' flags
entirely, and died inside a spawned task with a bare "No such file or
directory" (ENG-10327). `run_proxy` now takes a loaded `ProxyIdentity`
instead of paths, so whoever owns the paths reports the failure, by
name, before a socket is bound. `EmbeddedProxy` carries the paths and
the listen address and has no `Default` to guess them with.

The same module hardcoded the player-facing port at 25565 (ENG-10328),
which collided with the standalone proxy `nix run .#dev` starts on the
same port, so the two could never run together. bedwars takes
`--proxy-addr` now and starts an in-process proxy only when asked.

`nix run .#certs` writes a throwaway CA and both certificates, and
every runner calls it first, so a clean checkout runs with one command
rather than the README's eight openssl invocations. The SANs cover
`127.0.0.1`, which is what the proxy dials.

`BOT_BOT_COUNT` was per thread, so `nix run .#bots -- addr 100` opened
100 bots on each of 18 cores. It is the total now.

The stats system logs the player count and tick averages once a
second; they were only ever visible in a connected client's tab list.
Two more things that only show up when you run the thing.

`envy::prefixed("BOT_")` strips its prefix with `trim_start_matches`,
which removes *every* leading repetition, so `BOT_BOT_COUNT` arrived as
`count`, never matched the `bot_count` field, and silently fell back to
the default of 500 — per thread. `nix run .#bots -- 127.0.0.1:25565 100`
opened 9000 connections on an 18-core machine and there was no way to
ask for fewer. rust-mc-bot takes clap arguments now, each backed by its
documented environment variable, and the count is a total. Positional,
so the flake passes the address and count straight through.

`.cargo/config.toml`'s `[env]` only reaches processes cargo starts, and
`nix run .#dev` execs the built binary directly, so the game server
produced no output at all under the one command the README recommends.
The runners export RUST_LOG themselves, defaulting to info rather than
the debug that made the proxy unreadable.

README leads with the one-machine path now; the eight openssl commands
are still there for the multi-machine setup they were written for.
Every crates.io dependency moves to its newest release, majors
included: rand 0.9 -> 0.10, reqwest 0.12 -> 0.13, syn 2 -> 3, toml 0.8
-> 1.1, egui/eframe 0.31 -> 0.35, criterion 0.5 -> 0.8, roaring,
heapless, heed, ndarray, snafu, sha2, base64, glam's neighbours, and
the rest. The toolchain goes from nightly-2025-05-05 to
nightly-2026-07-26 (rustc 1.99).

flecs moves off the 0.2.2 crates.io release to upstream `main`. 0.2.2
is the only non-yanked release and is eight months behind; `main`
vendors flecs C 4.1.6 rather than 4.1.2, makes `World` and `Query`
`!Send` with a `QueryHandle` for the cross-thread case, and fixes a
`QueryIter` double free. The `!Send` work is the reason to take it:
`SendableQuery`'s hand-rolled `unsafe impl Send` is gone, replaced by
the upstream type built for it, and `player_join` was already using
the `iter_stage` pattern `QueryHandle` requires.

`main` did not work as it stands. Its new sparse-term safety locks
hand a stage pointer to `flecs_components_get`, which asserts on
`ecs_world_t_magic`, so every system with a sparse term aborts under
`set_threads > 1` — which is every system here. Three integration
tests SIGABRT. Fixed in Indra-db/Flecs-Rust#302 and pinned to that
branch until it lands.

bvh is vendored as `crates/bvh` rather than fetched from
TestingPlant/bvh-data: it is our own crate, the pin was a year stale,
and it needed the same kind of fix (a dead `#![feature(array_chunks)]`
that rustc no longer accepts). Vendoring also brought its tests into
the workspace — 92 tests become 131 — and revealed that
`generic_const_exprs` was declared, incomplete, and unnecessary:
dropping it fixes an E0391 cycle its own test suite hit.

glam stays at 0.29.3, the one thing held back. `valence_protocol`
exposes glam types across its entire public API and the vendored fork
pins 0.29.3; anything newer puts two glam crates in the tree and every
valence signature stops accepting the vectors hyperion holds. Moving
it means bumping glam in TestingPlant/valence first, on both pinned
branches.

Idiomatic rather than minimal, where the bump made it possible:
- 26 feature gates deleted, enumerated from rustc's own diagnostics
  rather than guessed: let_chains, assert_matches, maybe_uninit_slice
  and array_chunks went stable, and a dozen more were never used.
- hyperion-stats' `array_chunks` iterator dance is a `step_by` loop,
  which needs no unstable feature at all.
- `run_proxy` and the whole of hyperion-proxy's public surface is
  actually public now; two `pub` items named private types.
- Collapsed `if let` chains, `bit_width`, `is_multiple_of`,
  `mul_add`, `?` in packet-channel, `Self` in two type positions.

Both audit jobs pass and are blocking in CI now. `cargo deny` was
failing on 10+ advisories including a use-after-free in openssl's
`Md::fetch` and two webpki certificate-validation bugs; the bump
removes all of them. Five `unmaintained` notices remain, each on a
crate this workspace does not choose and none with an upgrade
available — every one is listed in deny.toml with its path and reason.
`cargo machete`'s 11 unused dependencies are 20 removed and 3 kept:
rustls-webpki and valence_protocol are false positives, one because
its lib is named `webpki` and one because it is only reached through
macro expansion, and both now say so.
The bot count fix in e83e935 was wrong about the cause, and made
things worse. `bot_on` is shared across every `BotManager`, so
`count` was always the total already — dividing it by the thread
count made all eighteen managers stop at one thread's share, and
`nix run .#bots -- 127.0.0.1:25565 100` connected six bots. Every
manager gets the whole count again. The clap change in that commit
stands: `BOT_BOT_COUNT` genuinely never arrived, so the count was
always the 500 default.

flecs C 4.1.6 checks symbol consistency where 4.1.2 did not, and two
different types were both registering as `StatsModule` —
`hyperion::egress::stats` and `bedwars::module::stats` — which aborts
module import before the server finishes starting. The bedwars one
only ever writes the tab list header and footer, so it is
`TabListModule` in `module/tab_list.rs` now, which is what it does.

`nix run .#bots` killed `nix run .#dev`. The restart watcher runs with
`--no-vcs-ignores`, which is what lets it see a file cargo writes, but
it also stops that watcher honouring .gitignore — so a trigger file in
the repo root meant any write under `target/` restarted the game
server, including a build kicked off from another terminal. The
trigger lives in a temp directory now.

`HYPERION_PLAYER_PORT` and `HYPERION_SERVER_PORT` override the two
ports. Two checkouts of this repo on one machine both bind 25565, and
the loser gets a SIGTERM and no explanation.
@andrewgazelka andrewgazelka changed the title Latest dependencies, current nightly, and a server that actually boots chore: latest dependencies, current nightly, and a server that actually boots Jul 27, 2026
@github-actions github-actions Bot added the chore label Jul 27, 2026
Two conflicts, both mechanical: Cargo.lock, and one region of flake.nix
where both sides added a runner. Kept both apps.

Two things the merge itself broke, neither of them a conflict:

events/smash pinned `flecs_ecs = "0.2.2"` from crates.io, on the
reasoning — recorded in docs/flecs-rust-api-notes.md — that it should
match whatever the workspace pins. The workspace now pins upstream
main, so the same reasoning points the other way, and holding the
crates.io release would have put two flecs_ecs crates, two component
registries and two copies of flecs C in one lock. It takes the
workspace dependency, and the note says why.

flecs 4.1.6 stopped creating a child entity per struct member unless
the type opts in with `create_member_entities`. hyperion-hot-reload's
schema reader walked those children, so on the new flecs it found no
members and returned `Layout::Unknown` for every reflected component —
a gate that sees no fields cannot see a field change. It reads the
`EcsStruct` member vector now, which is what flecs serializes from and
exists either way. Two reflection tests caught it.

Also fixed the new nightly's lints in code that landed on main under
the old one: a fn-pointer field made `PartialEq` compare addresses,
plus const fn, mul_add, fill and sort_by_key.
main gained #960, #962 and #963 while this branch was open. #960 solves
the same problem this branch's second half did and is now canonical, so
everything about running the server is resolved in main's favour:

- crates/hyperion-proxy-module stays deleted. The ProxyIdentity and
  EmbeddedProxy refactor built on it is dropped with it: deleting the
  crate is a better answer than making its cert paths and ports
  configurable.
- main's .#certs (.hyperion-dev-certs, --force, ports shared with the
  standalone apps) replaces this branch's generator.
- main's process-compose .#dev replaces the parallel + trigger-file
  runner.
- main's EntitySize / Name meta-plus-opaque fix stays.
- README is main's.

Kept from this branch, because #960 is still on flecs 0.2.2 and these
only bite on 4.1.6:

- hyperion::egress::stats::StatsModule and
  bedwars::module::stats::StatsModule both register as StatsModule, and
  4.1.6 checks symbol consistency where 4.1.2 did not, so module import
  aborts. The bedwars one writes the tab list and nothing else, so it is
  TabListModule in module/tab_list.rs.
- flecs 4.1.6 stopped creating a child entity per struct member, so
  hyperion-hot-reload's schema reader found none and called every
  reflected component Layout::Unknown -- a gate that sees no fields
  cannot see a field change. It now reads the EcsStruct member vector.
- events/smash pinned crates.io flecs 0.2.2, which would put two
  component registries and two copies of flecs C in one lock. It uses
  the workspace pin.

Also kept: every dependency at latest, nightly-2026-07-26, flecs on
upstream main carrying Indra-db/Flecs-Rust#302, bvh vendored, the dead
feature gates, and deny and machete green and blocking.

BowCharging::default() keeps this branch's Self::now() rather than
main's SystemTime::UNIX_EPOCH. get_charge clamps elapsed time to 1.2s,
so the epoch reads as a fully drawn bow and hands every player a free
maximum-power shot on their first release. This is a deliberate
correction, not an accidental revert.

rust-mc-bot's clap rewrite is kept: envy's prefix handling strips
"BOT_" repeatedly, so BOT_BOT_COUNT arrived as `count`, never matched
bot_count, and every run silently used the default of 500 per thread.
clap reads both the positional and the environment form, so .#bots
passes the address and count positionally and main's comment that they
"cannot be passed positionally" no longer holds.
…necraft-proto

let_chains went stable, so collapsible_if now reaches nested `if let`.
Three sites in the proto crate that landed under the old toolchain.
process-compose hardcoded 25565, 35565 and its own API port at 8080, so
a second checkout of this repo cannot run `nix run .#dev` at all -- it
dies on "listen tcp 127.0.0.1:8080: bind: address already in use"
before either process starts, and would then fight over the game ports
too. HYPERION_PLAYER_PORT and HYPERION_SERVER_PORT override them and
the API port follows the player port, so a second checkout needs one
prefix rather than a patch.

Verified with both stacks up at once: 25565/35565 held by one checkout,
100 bots on 25567/35567 in the other.
Two tracing calls added to read a join transcript off a running client
were swept into "dev ports are defaults, not constants" by accident.
They logged every clientbound packet id and a line per join, which at
100 bots is 100 lines of noise the tool never asked for.
#965 and #966. Two conflicts, both places where main and this branch
parameterised the same thing:

- The process-compose game-server command: main added HYPERION_EVENT
  to pick bedwars or smash, this branch added HYPERION_SERVER_PORT.
  Kept both.
- events/smash's flecs dependency: main had already moved it to the
  workspace pin, which is where this branch had moved it. Converged.

The new `smash` runner takes HYPERION_SERVER_PORT too, so both events
move off the default port together.

#965's generated wire.rs needed the same collapsed-if pass the new
nightly's clippy asks of everything else.
@andrewgazelka andrewgazelka changed the title chore: latest dependencies, current nightly, and a server that actually boots chore: latest dependencies, current nightly, and flecs on upstream main Jul 27, 2026
@andrewgazelka
andrewgazelka merged commit 31cce5f into main Jul 27, 2026
10 of 12 checks passed
@andrewgazelka
andrewgazelka deleted the chore/latest-deps branch July 27, 2026 11:00
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.35359% with 214 lines in your changes missing coverage. Please review.
✅ Project coverage is 32.55%. Comparing base (2d1c099) to head (6c369f2).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
crates/bvh/src/lib.rs 89.89% 25 Missing and 3 partials ⚠️
crates/bvh/src/query.rs 84.66% 22 Missing and 3 partials ⚠️
crates/hyperion-proxy/src/lib.rs 0.00% 18 Missing ⚠️
tools/rust-mc-bot/src/main.rs 0.00% 18 Missing ⚠️
crates/bvh/src/node.rs 87.31% 15 Missing and 2 partials ⚠️
crates/bvh-region/src/query/ray.rs 66.66% 6 Missing and 3 partials ⚠️
tools/packet-inspector/src/app.rs 0.00% 9 Missing ⚠️
crates/bvh/src/aabb.rs 91.57% 8 Missing ⚠️
crates/hyperion-proxy/src/main.rs 0.00% 8 Missing ⚠️
tools/packet-inspector/src/app/text_viewer.rs 0.00% 8 Missing ⚠️
... and 24 more
@@            Coverage Diff             @@
##             main     #961      +/-   ##
==========================================
+ Coverage   31.38%   32.55%   +1.16%     
==========================================
  Files         215      216       +1     
  Lines       22950    21515    -1435     
  Branches      731      792      +61     
==========================================
- Hits         7204     7004     -200     
+ Misses      15569    14347    -1222     
+ Partials      177      164      -13     
Files with missing lines Coverage Δ
crates/bvh-region/src/lib.rs 62.50% <ø> (-1.14%) ⬇️
...tes/hyperion-minecraft-proto/src/generated/wire.rs 0.00% <ø> (ø)
crates/hyperion-minecraft-proto/src/text/mod.rs 83.33% <ø> (+1.93%) ⬆️
crates/hyperion-stats/src/lib.rs 88.52% <100.00%> (-0.05%) ⬇️
crates/hyperion-text/src/lib.rs 0.00% <ø> (ø)
crates/hyperion/src/lib.rs 72.44% <ø> (-3.75%) ⬇️
crates/hyperion/src/net/encoder/util.rs 100.00% <100.00%> (ø)
crates/hyperion/src/simulation/command.rs 84.00% <ø> (+2.43%) ⬆️
crates/hyperion/src/simulation/util.rs 69.69% <100.00%> (-1.18%) ⬇️
crates/packet-channel/src/lib.rs 89.50% <100.00%> (+0.04%) ⬆️
... and 35 more

... and 157 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

andrewgazelka added a commit that referenced this pull request Jul 27, 2026
#961 ran a `use_self` fix over the committed `generated/wire.rs` without
telling the generator, so `nix flake check`'s staleness check has been
failing since: the committed copy says `&'static Self` and the pipeline
still produced `&'static Wire`.

Emitting `Self` inside the enum is what clippy would leave, so the two agree
again. This is the check doing its job -- a hand-edit to a generated file is
exactly what it exists to catch.
andrewgazelka added a commit that referenced this pull request Jul 27, 2026
…#971)

The wire format now has one source of truth. `protocol.json` is
committed
beside the crate and `build.rs` turns the layouts the extractor
recovered in
full into Rust; nothing about a packet body is transcribed by hand any
more,
and there is no sync step for the structs to drift from.

## What the generated code looks like

```rust
/// `minecraft:intention`, sent serverbound as handshake id 0.
///
/// Layout from `net.minecraft.network.protocol.handshake.ClientIntentionPacket#STREAM_CODEC`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Encode, Decode)]
pub struct Intention<'a> {
    pub protocol_version: VarInt,
    #[proto(max_len = 32767)]
    pub host_name: &'a str,
    pub port: i16,
    pub intention: ClientIntent,
}

/// `minecraft:keep_alive`, sent clientbound as configuration id 4 and play id 38.
///
/// Layout from `net.minecraft.network.protocol.common.ClientboundKeepAlivePacket#STREAM_CODEC`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Encode, Decode)]
pub struct KeepAlive(pub i64);
```

**177 of the 180 packet classes the extractor recovered in full are
generated.**
The three it declines are named, with the reason, in a comment at the
top of
the file they would have gone into — a layout that is not fully
expressible is
absent, never approximated:

```
//   minecraft:custom_click_action -- a custom codec inside a combinator needs a hand-written packet
//   minecraft:damage_event -- two fields would both be named `output`
//   minecraft:disguised_chat -- `net.minecraft.network.chat.ChatType$Bound#STREAM_CODEC` contains itself
```

One Java class is one Rust type: `ClientboundKeepAlivePacket` serves
configuration and play, so it is defined once in `packets::common` and
re-exported into each state, and a value built for one is the value the
other
accepts.

## Committed versus generated

| artifact | where | why |
| --- | --- | --- |
| `protocol.json` | committed | the input everything derives from, and
the diff a version bump is reviewed as |
| packet structs | `OUT_DIR`, via `build.rs` | the wire contract, where
a stale copy silently desynchronises a stream |
| packet ids, registries, versions, `Wire` table | committed `.rs` |
data restated as Rust, greppable, and only wrong in a log line |

The line is **what a mistake costs, and whether the projection is
total**. The
tables are a total function of `protocol.json` and a committed copy can
be
checked by diffing it. The packet structs are a *partial* projection —
the
generator refuses what it cannot express — which is exactly the shape
that has
bitten this pipeline before, when an earlier extractor silently
truncated
eleven packets and reported them complete. Volume settles the rest:
moving
`registry.rs`'s 7,700 lines into `OUT_DIR` would make `rg
'minecraft:diamond_sword'`
miss, for no gain.

## The derive

`crates/hyperion-minecraft-proto-derive` derives the crate's existing
`Encode`/`Decode` traits. A packet body is a fixed sequence of fields
with
nothing between them, so the derive is a loop over the fields in
declaration
order; anything that is not that is a compile error rather than a guess.

| attribute | effect |
| --- | --- |
| `#[proto(max_len = N)]` | limit on the innermost string or byte slice
|
| `#[proto(max_count = N)]` | limit on the innermost collection's
element count |
| `#[proto(with = path)]` | `path::encode` and `path::decode` for this
field |

Named, tuple and unit structs; borrowed fields; type parameters;
fieldless
enums as a `VarInt` discriminant. Refused, loudly: a variant with
fields, more
than one lifetime, a `with` beside a limit.

The hand-written handshake, status and login codecs are gone — every one
of
their packets is generated.

## Enums are enums

`writeEnum` sends `ordinal()` and `ClientIntent` sends ids of its own,
so an
enum field used to come out as a `VarInt`. Both are readable off the
jar, so
eleven enums are now generated, and two packets that were partial became
complete because the enum was the only thing under them the extractor
could
not follow.

```rust
/// `net.minecraft.network.protocol.handshake.ClientIntent`, sent as a varint id.
pub enum ClientIntent { Status = 1, Login = 2, Transfer = 3 }
```

Ids 1, 2, 3 against ordinals 0, 1, 2 — reading one for the other picks
the
wrong intent silently, which is why the `byId` switch is parsed rather
than the
position assumed.

## Also fixed

The pipeline's rustfmt never saw the repo's `rustfmt.toml`, so `cargo
fmt`
rewrote every committed table and `fmt --check` failed on an untouched
tree.
It now runs the pinned nightly with `--config-path`.

## Gates

```
$ nix run .#fmt -- --check   # clean
$ nix run .#lint             # Finished in 16.06s, no warnings
$ nix run .#test             # 258 tests run: 258 passed, 1 skipped   (was 234)
$ nix build --no-link .#checks.aarch64-darwin.minecraft-proto-generated \
                      .#checks.aarch64-darwin.minecraft-proto-json \
                      .#checks.aarch64-darwin.minecraft-protocol
```

The new `protocol.json` guard was watched failing before it was trusted:
editing the committed protocol number to 999 fails the check with a diff
naming the line.

## Not done

Written up in `docs/minecraft-26.2-migration.md`, and worth reading
before
building on this:

- **Field limits come from the writer; the server enforces them on the
reader,
and the two disagree.** `ClientIntentionPacket` writes `hostName`
unbounded
and reads it with 255. The generated struct is permissive where vanilla
is
strict. It cannot desynchronise a stream, but the fix — parsing the
private
`(FriendlyByteBuf)` constructor as a second, independent statement of
the
layout and cross-checking it — would also settle `port`, which is
written
  `i16` and read `u16`.
- **`damage_event` is declined over a defect, not a limit.** The
extractor
  inlines a static helper without binding its parameter to the caller's
  argument, so two fields are labelled `output`.
- No play packet has been round-tripped against a real server; only
status.


---

## Rebased onto #968, and what that leaves

#968 landed while this was in flight. It hand-wrote configuration and
the
play-login sequence against the same decompiled sources, and **eleven of
its
packets are now also generated** — `select_known_packs`,
`registry_data`,
`update_enabled_features`, `keep_alive`, `ping`, `pong`, `disconnect`,
`finish_configuration`, `reset_chat`, `code_of_conduct`,
`accept_code_of_conduct`, plus `Login` and `Respawn` in `play_login`.

They are defined twice for now, once flat in the hand-written module and
once
under the state's `clientbound`/`serverbound` module. The generated one
is the
one to keep — it also closes #968's own stated gap, since the 64-element
cap on
`ServerboundSelectKnownPacks` is enforced here through the
`Error::ListTooLong`
this branch adds. Reconciling means rewriting 725 lines of tests that
name the
hand-written types, which is a review of its own, so it is the next
change
rather than this one; `src/packets/mod.rs` says so in the source.

What is *not* duplicated is the part the generator cannot do:
`ClientInformation`, `CustomPayload` and `UpdateTags` all branch on a
runtime
value, and stay hand-written.

## One thing found while rebasing

`nix flake check`'s staleness check has been failing on main since #961,
which
ran a `use_self` fix over the committed `generated/wire.rs` without
telling the
generator. The committed copy said `&'static Self` and the pipeline
still
produced `&'static Wire`. Fixed in the generator, which is the check
doing
exactly what it exists for.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant