Programming & Technical May 27, 2026

Bevy 0.17 ECS Plugin Schedule Order Debug for Fest Demo Menu Hitches - 2026

2026 Bevy 0.17 programming playbook for fest demo menu hitches—plugin registration order, schedule stages, SystemSet ordering, bevy_schedule_receipt_v1.json, and Tracy preflight pairing.

By GamineAI Team

Bevy 0.17 ECS Plugin Schedule Order Debug for Fest Demo Menu Hitches - 2026

Pixel-art hero for Bevy 0.17 ECS plugin schedule order debug fest demo menu hitch 2026

Your fest demo menu stutters for three frames when you open settings. Profiling shows no obvious GPU spike. Sprite count is modest. Someone on Discord says “just batch your sprites” — but the hitch is gone if you move one add_systems line above another.

2026 H2 is the window where Bevy 0.17 ships in public fest builds: required components and observers rewrote tutorials, but schedule order is still where micro-studios lose afternoons. Menu hitches are often ordering bugs: input vs state vs UI layout vs deferred World mutations applied in the wrong stage.

This Programming & Technical playbook is the schedule and plugin order layer. It pairs with the beginner weekend build in Your First Bevy 0.17 2D Platformer in One Weekend (API walk-through) and the guide chapter on Bevy 0.17 diagnostics, Tracy, and frame-time budget (profiling receipts). This URL owns why the menu hitches when order is wrong — not another sprite tutorial.

Non-repetition note: The weekend tutorial teaches what to type. This post teaches where in the schedule it runs and how to prove order with bevy_schedule_receipt_v1.json before you freeze a fest branch.

Why this matters now (2026 H2)

  1. October Next Fest and itch capsule traffic punish one-frame UI pops — players read them as “janky port,” not as “we had a deferred command ordering surprise.”
  2. Bevy 0.17’s plugin graph is largerDefaultPlugins plus UI, audio, and your gameplay plugins interact in PreUpdate / Update / PostUpdate lanes. Mis-ordered systems look like performance problems.
  3. Observers and required components moved code off hot loops — the remaining hitch classes cluster around state transitions and command flushes.
  4. Receipt culture — partners and future-you want a build_label and a schedule receipt, not a screen recording alone. Pair with BUILD_RECEIPT JSON evening pipeline when you wire optional bevy_schedule_order_ok booleans.
  5. Steamworks evidence rows — when you ship a PC demo, store QA lists expect reproducible build notes; cross-link 18 Free Steamworks and PC Distribution Integration Resources for upload log discipline beside engine order.

Direct answer: Treat menu hitch debug as schedule graph work: list plugins, list systems per stage, enforce SystemSet chains for menu open/close, verify deferred flushes, then file bevy_schedule_receipt_v1.json before fest promotion. If you cannot draw that graph on a whiteboard in five minutes, you are not done triaging yet.

Who this is for

Audience You get
Beginner who finished the weekend tutorial A checklist to read schedule order before blaming art
Working Rust dev SystemSet patterns, deferred-command traps, Tracy pairing
Solo maintainer A receipt JSON you can attach to release-evidence/

Time: ~45 minutes to read; 90–120 minutes for first receipt pass on a real repo.

Beginner path — the four questions

Before opening Tracy, answer these in order:

  1. Which schedule stage owns the hitch? (Startup is different from every-frame Update.)
  2. Does opening the menu spawn/despawn entities with Commands? If yes, you care about flush points.
  3. Does any system read UI state in the same stage another system writes it? That is a plain ordering bug.
  4. Did a plugin register systems implicitly? Third-party plugins often add systems you forgot to name in stand-up.

If all four are “unknown,” stop profiling draw calls — you do not have a measurement problem yet.

Developer path — the ordering model

Plugins register systems in registration order

App::add_plugins runs plugin build functions in the order you pass them. When two plugins both insert systems into Update, registration order tends to preserve relative order within that set — until you reorder plugins or use explicit sets.

Fest rule: Put DefaultPlugins first unless you have a documented exception. Then add domain plugins in a stable order: input smoothing, game state, UI shell, gameplay. Changing that order between release candidates without a receipt is how “random” menu hitches return.

Stages are buckets — misuse buckets and you get hitches

Common layout:

  • PreUpdate — input aggregation, low-latency reads.
  • Update — gameplay simulation, AI, physics stepping decisions.
  • PostUpdate — transforms, camera follow, often UI layout reactions.

Menu hitches often trace to running gameplay cleanup in Update while UI still reads old hierarchy in the same stage — split them with sets or move UI reactions to PostUpdate (or an explicit set after your state transition writers).

SystemSet is your contract language

Instead of “system A before system B” comments, encode:

  • configure_sets(Update, (MenuSystems::Input, MenuSystems::ApplyState, MenuSystems::RebuildUi).chain())

Name sets like production API: MenuSystems::ApplyState beats Set1.

Deferred commands and one-frame ghosts

When you open a menu you often commands.spawn panels and despawn old widgets. Observers and required components can amplify surprise if teardown runs in the same stage as code that still queries the old graph.

Pattern: State transition writers enqueue commands; UI readers run after the flush boundary you choose — either next stage or an ordered set after apply_state_transition analog in your codebase.

Fixed timestep vs variable UI

If you tie menu animation to FixedUpdate but read input in Update, you can see micro-stutter when fixed accumulators catch up. Fest demos often enable fixed timestep for platforming; keep menu feel on Update unless you have a deliberate reason not to.

Symptom → likely cause map

Symptom Likely cause First move
Hitch only when opening/closing menu Order vs Commands flush Chain sets; split stages
Hitch every N frames with stable N Fixed timestep + UI mismatch Move UI out of FixedUpdate
Hitch after adding a plugin Implicit systems in new plugin Dump plugin order; name systems
Hitch only in release Different feature flags / tracing off Compare schedules; enable Tracy
Hitch on WASM only Main thread + browser paint Pair wasm boot preflight + frame budget

Gates B1–B6 (promotion discipline)

Use this table before you call the fest branch “menu stable.”

Gate Check
B1 Every menu-related system is in a named SystemSet
B2 Update menu sets are chain()-ordered or explicitly ordered with before/after
B3 No system reads UI hierarchy in the same set that despawns those nodes without documented flush
B4 Plugin registration order is frozen in CHANGELOG for the fest candidate
B5 Tracy capture shows hitch within a named span — not “unknown bucket”
B6 bevy_schedule_receipt_v1.json committed under release-evidence/

bevy_schedule_receipt_v1.json (template)

{
  "schema": "bevy_schedule_receipt_v1",
  "build_label": "fest-demo-2026-10-rc3",
  "bevy_version": "0.17",
  "plugin_registration_order": [
    "DefaultPlugins",
    "GameInputPlugin",
    "GameStatePlugin",
    "MenuShellPlugin",
    "GameplayPlugin"
  ],
  "menu_pipeline": {
    "stage": "Update",
    "system_sets": [
      "MenuInputSet",
      "MenuStateTransitionSet",
      "MenuUiRebuildSet"
    ],
    "chain_order": [
      "MenuInputSet",
      "MenuStateTransitionSet",
      "MenuUiRebuildSet"
    ],
    "uses_commands_in_transition": true,
    "deferred_flush_strategy": "same_stage_ordered_after_transition"
  },
  "tracy": {
    "enabled": true,
    "capture_seconds": 90,
    "notes": "Hitch spans named MenuOpen / MenuRebuild"
  },
  "gates": {
    "B1_named_sets": true,
    "B2_chain_or_explicit_order": true,
    "B3_no_same_set_read_write_hazard": true,
    "B4_plugin_order_frozen": true,
    "B5_tracy_named_span": true,
    "B6_receipt_committed": true
  },
  "promotion_allowed": true
}

Honest limit: this JSON does not replace cargo tree or your CI — it is a human contract for what you believe the schedule is.

Tracy pairing (working dev)

Follow the guide chapter for trace_tracy, FrameTimeDiagnosticsPlugin, and the 2D frame budget table:

Bevy 0.17 Diagnostics, Tracy, and Frame-Time Budget Preflight for 2D Ship Candidates

For menu hitches specifically:

  1. Name spans around open, close, and rebuild paths — not only Update root.
  2. Capture 90 seconds including three open/close cycles.
  3. If the hitch is shorter than one frame in Tracy but visible on screen, suspect GPU sync / vsync — still log the engine-side span first so you do not optimize the wrong layer.

Course and help crosswalk

Wednesday smoke pairing

Slot a 60-second menu open/close loop into Wednesday demo build smoke ritual. If smoke passes but Tracy still shows a spike only on first open, treat that as cold-cache or shader first compile — document it in the receipt notes field instead of silently accepting regressions.

Evidence folder layout

Under release-evidence taxonomy, place schedule artifacts in:

release-evidence/marketing-and-demo/bevy-schedule-2026-10/
  bevy_schedule_receipt_v1.json
  tracy_menu_open_close_pr90.tcap
  plugin_order_changelog_snip.md

If your studio keeps engine evidence under build-and-binary/, mirror the same three files there — consistency beats perfect folder theology.

Anti-patterns (reject in code review)

  1. “Just add_systems(Update, …) in whatever order we typed them.”
  2. State transition systems that also rebuild UI in the same anonymous tuple.
  3. Plugin A registers a system that must run after plugin B’s systems, but A is added before B.
  4. Menu logic in FixedUpdate without documenting why.
  5. Removing Tracy spans because the capture “looks noisy.”
  6. Promoting a fest build when B3 is false — you are shipping a latent one-frame UI bug.

Optional automation sketch

If you use CI, a lightweight guard is to fail when bevy_schedule_receipt_v1.json is missing for tags matching fest-*:

test -f release-evidence/marketing-and-demo/bevy-schedule-2026-10/bevy_schedule_receipt_v1.json

Pair with jq checks on promotion_allowed if you want a hard gate — only after humans trust the JSON schema.

Annotated configure_sets sketch (copy shape, not prose)

The following is illustrative Rust shape — names and imports will vary with your crate layout. The point is to show three named sets in Update that always run in order when the menu is active:

// Illustrative only — adjust to your State type and system signatures.
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
enum MenuSchedule {
    ReadInput,
    TransitionGameState,
    RebuildUi,
}

fn plugin(app: &mut App) {
    app.configure_sets(
        Update,
        (
            MenuSchedule::ReadInput,
            MenuSchedule::TransitionGameState,
            MenuSchedule::RebuildUi,
        )
            .chain(),
    )
    .add_systems(
        Update,
        (
            read_menu_input.in_set(MenuSchedule::ReadInput),
            apply_menu_state_change.in_set(MenuSchedule::TransitionGameState),
            rebuild_menu_ui.in_set(MenuSchedule::RebuildUi),
        ),
    );
}

Why three sets instead of one tuple? Because your producer can read the receipt: “RebuildUi is last.” A single tuple of six anonymous systems does not compress into stand-up language.

run_if guards (keep sets from firing in gameplay)

Menu systems should not compete with exploration systems. Typical pattern:

  • run_if(in_state(MenuState::Open)) on rebuild systems
  • run_if(in_state(GameState::Playing)) on gameplay writers that must not touch menu entities

If you forget run_if, you get “phantom” systems that still run and still allocate queries — Tracy looks busy even when the screen is idle.

Exclusive systems (when you truly need a hard barrier)

Sometimes you need an exclusive system to inspect the World directly for one frame (debug tooling, one-off migration, emergency assert). Exclusive systems block parallelization for that system’s slot — fine for dev, dangerous if left in fest hot paths.

Fest rule: no exclusive systems in Update for menu unless documented in the receipt with why and measured cost.

One-day debug workflow (repeatable)

Hour block Task Output
0:00–0:20 List plugins in main.rs order plugin_registration_order in receipt
0:20–0:45 Export Tracy 90s with menu spam .tcap + screenshot of worst spike
0:45–1:10 Name sets + chain() for menu PR with only ordering change
1:10–1:30 Re-capture Tracy Compare p95 before/after
1:30–2:00 Fill bevy_schedule_receipt_v1.json + link in BUILD_RECEIPT optional row Evidence path in repo

If the second Tracy capture shows no named span where the hitch is felt, you are still measuring the wrong lane — go back to B5.

“Feels like Unity script order” — what is actually the same

Indies coming from Unity often think in Script Execution Order. Bevy’s answer is explicit scheduling instead of hidden inspector integers.

Unity habit Bevy analogue
Default Awake/Start ordering anxiety Startup systems + plugin build order
Update vs LateUpdate Update vs PostUpdate + your sets
OnEnable UI rebuild Same-frame Commands + ordered sets
Deep profiler timeline Tracy spans + diagnostics plugins

The emotional shift: order is not accidental — you encode it or you inherit whatever registration order your dependencies chose.

HDR store capture sibling (format ladder)

If your fest marketing thread is also fighting blown highlights in PNGs, that is the art lane — not this schedule lane:

HDR Screenshot False Bloom When Downscaling to Steam Store Captures

Keep engine receipts and marketing receipts in different JSON files so Thursday row review stays legible.

Common Bevy 0.17 menu stacks (names to grep)

When you inherit a jam game, grep for these plugin/system names — each often injects implicit order constraints:

  • bevy_ui — layout and focus interact with transform propagation timing.
  • bevy_stateOnEnter / OnExit schedules are easy to mis-pair with UI rebuild.
  • bevy_input / bevy_window — low-latency input vs winit events can reorder relative to your assumptions on Linux vs Windows.
  • bevy_audio / bevy_sprite — not “menu,” but they still steal Update time and change profiling baselines.

Document which crates are in the minimal fest feature set vs dev-only tooling (bevy_inspector_egui, editor overlays). A fest receipt should list features enabled on the candidate binary, not only bevy semver.

FAQ

Do I need a custom schedule label?

Not on day one. Named SystemSets in Update plus explicit ordering often fix fest menu hitches.

Is this the same as “use observers instead of systems”?

No. Observers reduce polling, but ordering still matters when observers enqueue Commands that interact with UI spawned in the same frame.

What if the hitch disappears in --release?

Compare feature flags and plugin lists. Some diagnostics plugins change timing. Your receipt should record debug vs release schedule assumptions separately if they diverge.

Should WASM demos use the same receipt?

Yes — add a target field (wasm32-unknown-unknown) and note main-thread constraints. Pair WASM memory ceiling playbook mindset even when the engine is Bevy: the store page is still a browser surface.

Should I split menus into a separate SubApp?

Usually not for a first fest slice. A second SubApp adds scheduler mental overhead. Prefer named SystemSets and strict run_if guards until Tracy proves the main-world schedule is the bottleneck. If you do split later, duplicate the receipt pattern: one JSON section per App so reviewers know which scheduler owns menu rebuild.

Key takeaways

  1. Fest menu hitches in Bevy 0.17 are often schedule order bugs, not sprite batching.
  2. Plugin registration order and stage choice are part of your public API — freeze them.
  3. SystemSet + chain() is the cheapest contract language for menu pipelines.
  4. Commands + same-stage readers cause one-frame ghosts — split with order or stages.
  5. bevy_schedule_receipt_v1.json turns tribal knowledge into promotion evidence.
  6. Gates B1–B6 give producers a binary pass/fail without reading Rust.
  7. Pair Tracy from the diagnostics guide chapter for named spans, not only FPS overlays.
  8. Wednesday smoke should include a deliberate menu loop, not only gameplay movement.
  9. Help hot reload and required-component fixes are adjacent — grep those when “menu hitch” is reported after asset saves.
  10. Steamworks resource lists belong in the same evidence story as engine receipts for PC fest demos.

Related reading

Outbound:

Next in cluster: blog backlog #8 October fest week founder time-box worksheet.