Files
horizon/tools/generate_packet_ids.py
Ludovic MarieandClaude Opus 5.5 ce87bfb8c7 Tools read the pinned Minecraft version from research-lock.json
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 <[email protected]>
2026-10-21 12:22:20 +02:00

77 lines
3.1 KiB
Python

#!/usr/bin/env python3
"""Generate crates/horizon-protocol/src/ids.rs from the vanilla data generator's
packets report (`java -DbundlerMainClass=net.minecraft.data.Main -jar server.jar --reports`).
Packet ids are protocol facts (the order packets are registered in each
connection state); the report is the authoritative source for them.
"""
import argparse
import json
from pathlib import Path
try:
from . import lab
except ImportError:
import lab
ROOT = Path(__file__).resolve().parents[1]
STATES = ["handshake", "status", "login", "configuration", "play"]
DIRECTIONS = ["clientbound", "serverbound"]
def const_name(name):
return name.split(":", 1)[1].replace("/", "_").upper()
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--report", type=Path, default=ROOT / ".local/datagen/out/reports/packets.json")
parser.add_argument("--out", type=Path, default=ROOT / "crates/horizon-protocol/src/ids.rs")
args = parser.parse_args()
report = json.loads(args.report.read_text())
info = lab.version_info()
lines = [
f"//! Packet ids per connection state and direction for protocol {info['protocol_version']} ({info['name']}).",
"//! Generated by tools/generate_packet_ids.py from the data generator's",
"//! packets report; do not edit by hand.",
"#![allow(missing_docs)]",
"use crate::{Direction, State};",
"",
]
tables = []
for state in STATES:
lines.append(f"pub mod {state} {{")
for direction in DIRECTIONS:
packets = report.get(state, {}).get(direction, {})
if not packets:
continue
ordered = sorted(packets.items(), key=lambda kv: kv[1]["protocol_id"])
assert [e["protocol_id"] for _, e in ordered] == list(range(len(ordered))), (state, direction)
lines.append(f" pub mod {direction} {{")
for name, entry in ordered:
lines.append(f" pub const {const_name(name)}: i32 = {entry['protocol_id']};")
lines.append(" /// Packet names indexed by id.")
names = ", ".join(f'"{name.split(":", 1)[1]}"' for name, _ in ordered)
lines.append(f" pub const NAMES: &[&str] = &[{names}];")
lines.append(" }")
tables.append((state, direction))
lines.append("}")
lines.append("")
lines.append("/// The packet name for an id, if the state and direction define it.")
lines.append("pub fn name(state: State, direction: Direction, id: i32) -> Option<&'static str> {")
lines.append(" let names: &[&str] = match (state, direction) {")
for state, direction in tables:
lines.append(
f" (State::{state.capitalize()}, Direction::{direction.capitalize()}) => {state}::{direction}::NAMES,"
)
lines.append(" _ => &[],")
lines.append(" };")
lines.append(" usize::try_from(id).ok().and_then(|i| names.get(i)).copied()")
lines.append("}")
args.out.write_text("\n".join(lines) + "\n")
if __name__ == "__main__":
main()