From ece9ab2b79b1f1b5fd9b42596439c65c3592255c Mon Sep 17 00:00:00 2001 From: Ludovic Marie <136255825+ludovic111@users.noreply.github.com> Date: Fri, 16 Oct 2026 17:53:47 +0200 Subject: [PATCH] Release smoke run: count frames drawn in the world The macOS release build has failed since 05:54 UTC on 2026-10-16: on the self-hosted Mac mini runner the game drew its 1500 frames in about 2.5 s, before the world had started generating (chunks 0, player null), so no release has been published since v2026.10.16.03. --world-frames N exits after N frames drawn with the player in the world. It is otherwise a frame-limited run (classic menus, --timeout-seconds), and --frames keeps its meaning for the harnesses. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 2 +- crates/horizon-client/src/game.rs | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3162b6f1b..38b3af3d0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -154,7 +154,7 @@ jobs: client="$PWD/target/release/horizon-client" resources="$PWD/.local/horizon-client/$version" (cd "$home" && HORIZON_HOME="$home" HORIZON_NO_AUDIO=1 "$client" --resources "$resources" --offscreen \ - --world smoke --seed 1 --render-distance 4 --frames 1500 --timeout-seconds 180 \ + --world smoke --seed 1 --render-distance 4 --world-frames 1500 --timeout-seconds 180 \ --smoke-report "$home/smoke.json" > "$home/client.log" 2>&1) || { tail -40 "$home/client.log"; exit 1; } python3 - "$home/smoke.json" <<'EOF' import json, sys diff --git a/crates/horizon-client/src/game.rs b/crates/horizon-client/src/game.rs index a54a3b38c..7072abeb5 100644 --- a/crates/horizon-client/src/game.rs +++ b/crates/horizon-client/src/game.rs @@ -211,6 +211,8 @@ struct Options { creative: bool, test_terrain: bool, frames: Option, + /// `--world-frames N`: exit after N frames drawn in the world (smoke runs; the world may load slowly). + world_frames: Option, screenshot_after: Option, timeout: Duration, report: Option, @@ -269,6 +271,7 @@ impl Options { creative: false, test_terrain: false, frames: None, + world_frames: None, screenshot_after: None, timeout: Duration::from_secs(120), report: None, @@ -297,7 +300,7 @@ impl Options { match arg.as_str() { "--help" | "-h" => { println!( - "Horizon — Minecraft Java Edition 26.3 in Rust\n\n --resources PATH Prepared resources (horizon-prepare)\n --render-distance N Chunks, 2..64 (default 12)\n --world NAME World folder under saves/ (default \"New World\")\n --seed N World seed for new worlds (default 0)\n --creative Creative mode (default survival)\n --test-terrain Simple test terrain instead of vanilla generation\n --frames N Exit after N frames (testing)\n --smoke-report PATH Write a JSON report on exit\n --benchmark SCENARIO Run a benchmark (standard, village, forest, ocean, mountain), write its report to benchmarks/ and quit\n --control Accept horizon-ctl commands on a local socket\n --control-socket PATH Control socket path (default $XDG_RUNTIME_DIR/horizon-control.sock)\n --server HOST[:PORT] Join a multiplayer server (offline mode) at start\n --name NAME Player name for multiplayer (default: generated once, kept in horizon-player.txt)\n --session-file PATH Signed-in account from the launcher (read once, then deleted)\n --dev-mod PATH A Horizon mod (.hmod or folder) to develop: always on, reloaded when rebuilt\n\nControls: mouse look, WASD move, Space jump, Left Shift sneak, Left Ctrl sprint,\nleft click break, right click place/use, 1-9/scroll hotbar, E inventory,\nQ drop, F3 debug, Esc pause." + "Horizon — Minecraft Java Edition 26.3 in Rust\n\n --resources PATH Prepared resources (horizon-prepare)\n --render-distance N Chunks, 2..64 (default 12)\n --world NAME World folder under saves/ (default \"New World\")\n --seed N World seed for new worlds (default 0)\n --creative Creative mode (default survival)\n --test-terrain Simple test terrain instead of vanilla generation\n --frames N Exit after N frames (testing)\n --world-frames N Exit after N frames drawn in the world (testing)\n --smoke-report PATH Write a JSON report on exit\n --benchmark SCENARIO Run a benchmark (standard, village, forest, ocean, mountain), write its report to benchmarks/ and quit\n --control Accept horizon-ctl commands on a local socket\n --control-socket PATH Control socket path (default $XDG_RUNTIME_DIR/horizon-control.sock)\n --server HOST[:PORT] Join a multiplayer server (offline mode) at start\n --name NAME Player name for multiplayer (default: generated once, kept in horizon-player.txt)\n --session-file PATH Signed-in account from the launcher (read once, then deleted)\n --dev-mod PATH A Horizon mod (.hmod or folder) to develop: always on, reloaded when rebuilt\n\nControls: mouse look, WASD move, Space jump, Left Shift sneak, Left Ctrl sprint,\nleft click break, right click place/use, 1-9/scroll hotbar, E inventory,\nQ drop, F3 debug, Esc pause." ); return Ok(None); } @@ -335,6 +338,11 @@ impl Options { }) } "--frames" => o.frames = Some(value.parse()?), + "--world-frames" => { + o.world_frames = Some(value.parse()?); + // A frame-limited run in every other respect; --timeout-seconds still applies. + o.frames.get_or_insert(u64::MAX); + } "--timeout-seconds" => o.timeout = Duration::from_secs(value.parse()?), "--smoke-report" => o.report = Some(value.into()), "--screenshot-after" => o.screenshot_after = Some(value.parse()?), @@ -782,6 +790,8 @@ struct Game<'a> { /// Controllers (Horizon's own). pad: pad::Pads, frames: u64, + /// Frames drawn with the player in the world, for `--world-frames`. + world_frames: u64, fps: f32, fps_frames: u64, fps_at: Instant, @@ -9633,6 +9643,7 @@ impl ApplicationHandler for Game<'_> { self.perf.title_shown(); } else if self.screen == Screen::Playing && self.player.is_some() { self.perf.frame(elapsed.as_secs_f32() * 1000.0); + self.world_frames += 1; } self.frames += 1; self.fps_frames += 1; @@ -9642,7 +9653,12 @@ impl ApplicationHandler for Game<'_> { self.fps_frames = 0; self.fps_at = Instant::now(); } - if self.options.frames.is_some_and(|f| self.frames >= f) { + if self.options.frames.is_some_and(|f| self.frames >= f) + || self + .options + .world_frames + .is_some_and(|f| self.world_frames >= f) + { event_loop.exit(); } } @@ -9963,6 +9979,7 @@ pub fn run() -> Result<()> { last_input: now, pad: Default::default(), frames: 0, + world_frames: 0, fps: 0.0, fps_frames: 0, fps_at: now,