From 42f0c62b6b8e88e161304b4f7bc128735c67ff14 Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Sun, 23 Aug 2026 13:48:06 +0200 Subject: [PATCH] Gate readiness on stable viewport frames --- crates/metacrate-grid-agent/src/main.rs | 73 ++++++++++++++----------- docs/renderer-performance.md | 4 +- 2 files changed, 43 insertions(+), 34 deletions(-) diff --git a/crates/metacrate-grid-agent/src/main.rs b/crates/metacrate-grid-agent/src/main.rs index 6bb37d5..39a8a06 100644 --- a/crates/metacrate-grid-agent/src/main.rs +++ b/crates/metacrate-grid-agent/src/main.rs @@ -343,6 +343,7 @@ async fn run_live( { live.vision.set_generation(status.generation); record_session_observation(&live.observability, &event); + print_ready(&live, status.generation); return Ok::<(), CliError>(()); } Some( @@ -529,32 +530,7 @@ async fn run_live( status.agent_ready, ); if status.agent_ready { - let world = live - .world_loop - .as_ref() - .and_then(metacrate_game_loop::LoopHandle::latest_world); - let world_sequence = world.as_ref().map_or(0, |world| world.sequence); - let primitives = world - .as_ref() - .map_or(0, |world| world.value.primitives.len()); - let avatars = world.as_ref().map_or(0, |world| world.value.avatars.len()); - let dropped_world_events = live - .world_loop - .as_ref() - .map_or(0, metacrate_game_loop::LoopHandle::dropped_world_events); - let frames = live - .vision - .viewport_stats() - .map_or(0, |stats| stats.completed_frames); - println!( - "READY generation={} world_sequence={} world_primitives={} world_avatars={} dropped_world_events={} viewport_frames={} viewport_target_fps=10", - status.generation, - world_sequence, - primitives, - avatars, - dropped_world_events, - frames - ); + print_ready(&live, status.generation); } } } @@ -689,13 +665,20 @@ async fn prepare_live_framework( generation, libremetaverse_types::compat::CancellationToken::default(), ); + let mut stable_after_frame = 0; + let mut observed_missed_deadlines = 0; loop { - if live - .vision - .viewport_stats() - .is_some_and(|stats| stats.completed_frames >= 2 && stats.render_errors == 0) - { - return; + if let Some(stats) = live.vision.viewport_stats() { + if stats.missed_frame_deadlines != observed_missed_deadlines { + observed_missed_deadlines = stats.missed_frame_deadlines; + stable_after_frame = stats.completed_frames; + } + if stats.completed_frames.saturating_sub(stable_after_frame) >= 2 + && stats.render_errors == 0 + && stats.last_render <= metacrate_game_loop::DEFAULT_VIEWPORT_FRAME_INTERVAL + { + return; + } } tokio::time::sleep(Duration::from_millis(20)).await; } @@ -705,7 +688,7 @@ async fn prepare_live_framework( readiness.mark_ready(generation); let stats = live.vision.viewport_stats().unwrap_or_default(); println!( - "INITIALIZED generation={} viewport_frames={} last_render_ms={} missed_deadlines={} world_state=stable", + "INITIALIZED generation={} viewport_frames={} last_render_ms={} warmup_missed_deadlines={} world_state=stable viewport_state=stable", generation, stats.completed_frames, stats.last_render.as_millis(), @@ -714,6 +697,30 @@ async fn prepare_live_framework( Ok(()) } +#[cfg(feature = "live-grid")] +fn print_ready(live: &LiveInteractions, generation: u64) { + let world = live + .world_loop + .as_ref() + .and_then(metacrate_game_loop::LoopHandle::latest_world); + let world_sequence = world.as_ref().map_or(0, |world| world.sequence); + let primitives = world + .as_ref() + .map_or(0, |world| world.value.primitives.len()); + let avatars = world.as_ref().map_or(0, |world| world.value.avatars.len()); + let dropped_world_events = live + .world_loop + .as_ref() + .map_or(0, metacrate_game_loop::LoopHandle::dropped_world_events); + let frames = live + .vision + .viewport_stats() + .map_or(0, |stats| stats.completed_frames); + println!( + "READY generation={generation} world_sequence={world_sequence} world_primitives={primitives} world_avatars={avatars} dropped_world_events={dropped_world_events} viewport_frames={frames} viewport_target_fps=10" + ); +} + #[cfg(feature = "live-grid")] struct LiveInteractions { interaction: metacrate_grid_agent::InteractionHandle, diff --git a/docs/renderer-performance.md b/docs/renderer-performance.md index b12147c..67fbf68 100644 --- a/docs/renderer-performance.md +++ b/docs/renderer-performance.md @@ -40,7 +40,9 @@ INITIALIZING generation=N waiting_for=world_state,viewport target_fps=10 The framework gate opens only after the region-scoped world state exists, the first scene/assets have converged, and at least two complete viewport frames -have been published without render errors. The terminal then emits +have been published after the last missed deadline, within the frame budget, +and without render errors. One-time warm-up misses are reported separately and +do not contaminate the post-`READY` stability window. The terminal then emits `INITIALIZED ...` followed by one unambiguous `READY ...` line when the session and agent services are released. The default readiness allowance is two minutes; the measured scene convergence window was about 30.05 seconds.