Bevy version
0.18.1 (workspace-pinned in our project's Cargo.toml)
Platform
- macOS 26.3.1 (Apple Silicon, M1 Max)
- Metal backend (wgpu)
cargo 1.97.0-nightly
What I'm doing
Writing a small "scenario harness" that boots the full Bevy app to capture screenshots of game state for visual QA. The harness queues several Screenshot::primary_window() entities at known frame counts (4 captures across a ~360-frame run), then sends AppExit::Success via MessageWriter<AppExit> once everything has been captured.
What I expect
The app exits cleanly within a frame or two of AppExit::Success being sent, the same way a single-screenshot scenario does.
What actually happens
The AppExit::Success write succeeds (I can see the log line from the writer's surrounding system), but the app never terminates — it idles at 0% CPU indefinitely. Has to be SIGKILLed.
I've isolated this to two specific user actions that each independently trigger the hang. Either alone causes it; both together also trigger it.
Trigger 1 — Adding ANY new per-frame system to a scenario after multiple Screenshot entities have queued
A scenario does:
super::schedule_screenshot(app, 60, output_dir.join("a.png"));
super::schedule_screenshot(app, 120, output_dir.join("b.png"));
super::schedule_screenshot(app, 180, output_dir.join("c.png"));
super::schedule_screenshot(app, 240, output_dir.join("d.png"));
super::schedule_exit(app, 360);
app.add_systems(Update, force_phase_per_frame); // <-- this line triggers the hang
Where force_phase_per_frame does NOTHING relevant to the screenshots — it just writes a Resource value:
fn force_phase_per_frame(mut solar: ResMut<SolarTime>, frame: Res<FrameCount>) {
solar.phase = match frame.0 {
n if n < 120 => 0.0,
n if n < 180 => FRAC_PI_2,
n if n < 240 => PI,
_ => 3.0 * FRAC_PI_2,
};
}
I also tried registering it in PreUpdate instead of Update — same hang. I also tried writing to a different (otherwise unused) Resource from the system body — same hang. The hang is triggered by the act of registering an additional per-frame system in a scenario that has multiple queued screenshot entities, regardless of what the system body does.
Removing this add_systems call (and dropping the per-frame override mechanism) makes the test pass cleanly in ~6 seconds.
Trigger 2 — app.insert_resource(X) overwriting a Resource already init_resource'd by a Plugin::build
A different scenario configures the camera target like this:
app.insert_resource(CameraTarget {
position: Vec3::new(0.0, 0.0, 25.0),
view_height: MAX_VIEW_HEIGHT,
});
Where CameraTarget was already init_resource::<CameraTarget>()'d in our RenderPlugin::build. Same hang signature: screenshots all save, AppExit::Success log line appears, app then idles forever.
Replacing insert_resource with a Startup system that mutates CameraTarget via ResMut (so the same instance is updated rather than replaced) avoids the hang.
Workarounds we're using
- For Trigger 1: instead of registering a new per-frame system, we extended the existing scheduled-actions processor (which is the system that queues the screenshots and sends
AppExit) to also write the override Resource itself. Keeping the system count constant sidesteps the hang.
- For Trigger 2: scenarios mutate init-resourced types via a
Startup system + ResMut instead of insert_resource.
Both workarounds are stable. The visual-QA scenarios run end-to-end in 4–6 seconds. We have ~3 scenarios passing in our integration test suite.
What I haven't done yet
I haven't built a minimal standalone repro outside our codebase. The hang reproduces 100% of the time in our test suite when either trigger is present, and removing the trigger fixes it 100% of the time. Happy to build a minimal repro if it would help — wanted to file the report first since the symptom + isolation is very clean.
Reference
- Repository: https://github.com/bryancostanich/lunie (private at time of filing — can share access on request)
- The two affected scenarios are at
source/lunie-game/src/scenarios/sun_phases.rs and source/lunie-game/src/scenarios/terrain_overview.rs.
- The scenario harness lives at
source/lunie-game/src/scenarios/mod.rs.
- The
process_scheduled_actions system showing the workaround pattern is in the same file.
Happy to provide more context, build a minimal repro, or test patches on this hardware.
Bevy version
0.18.1(workspace-pinned in our project'sCargo.toml)Platform
cargo 1.97.0-nightlyWhat I'm doing
Writing a small "scenario harness" that boots the full Bevy app to capture screenshots of game state for visual QA. The harness queues several
Screenshot::primary_window()entities at known frame counts (4 captures across a ~360-frame run), then sendsAppExit::SuccessviaMessageWriter<AppExit>once everything has been captured.What I expect
The app exits cleanly within a frame or two of
AppExit::Successbeing sent, the same way a single-screenshot scenario does.What actually happens
The
AppExit::Successwrite succeeds (I can see the log line from the writer's surrounding system), but the app never terminates — it idles at 0% CPU indefinitely. Has to be SIGKILLed.I've isolated this to two specific user actions that each independently trigger the hang. Either alone causes it; both together also trigger it.
Trigger 1 — Adding ANY new per-frame system to a scenario after multiple
Screenshotentities have queuedA scenario does:
Where
force_phase_per_framedoes NOTHING relevant to the screenshots — it just writes aResourcevalue:I also tried registering it in
PreUpdateinstead ofUpdate— same hang. I also tried writing to a different (otherwise unused)Resourcefrom the system body — same hang. The hang is triggered by the act of registering an additional per-frame system in a scenario that has multiple queued screenshot entities, regardless of what the system body does.Removing this
add_systemscall (and dropping the per-frame override mechanism) makes the test pass cleanly in ~6 seconds.Trigger 2 —
app.insert_resource(X)overwriting aResourcealreadyinit_resource'd by aPlugin::buildA different scenario configures the camera target like this:
Where
CameraTargetwas alreadyinit_resource::<CameraTarget>()'d in ourRenderPlugin::build. Same hang signature: screenshots all save,AppExit::Successlog line appears, app then idles forever.Replacing
insert_resourcewith aStartupsystem that mutatesCameraTargetviaResMut(so the same instance is updated rather than replaced) avoids the hang.Workarounds we're using
AppExit) to also write the overrideResourceitself. Keeping the system count constant sidesteps the hang.Startupsystem +ResMutinstead ofinsert_resource.Both workarounds are stable. The visual-QA scenarios run end-to-end in 4–6 seconds. We have ~3 scenarios passing in our integration test suite.
What I haven't done yet
I haven't built a minimal standalone repro outside our codebase. The hang reproduces 100% of the time in our test suite when either trigger is present, and removing the trigger fixes it 100% of the time. Happy to build a minimal repro if it would help — wanted to file the report first since the symptom + isolation is very clean.
Reference
source/lunie-game/src/scenarios/sun_phases.rsandsource/lunie-game/src/scenarios/terrain_overview.rs.source/lunie-game/src/scenarios/mod.rs.process_scheduled_actionssystem showing the workaround pattern is in the same file.Happy to provide more context, build a minimal repro, or test patches on this hardware.