Lesson 259: Bevy Menu UI Flush Receipt on BUILD_RECEIPT (2026)
Direct answer: Before October fest Bevy 0.17 demo promotion, promote bevy_menu_flush_receipt_v1.json proving MenuUiWriterSet runs before MenuUiReaderSet in Update, deferred Commands from menu OnEnter flush before UI hierarchy reads, and Tracy shows no empty-frame hitch on three open/close cycles—distinct from schedule order blog (bevy_schedule_receipt_v1 strategy) and Lesson 245 (wasm panic build_label). Pair the Bevy menu UI flush Tracy preflight (Guide #22).

Why this matters now (October fest demo menus)
October 2026 teams ship Bevy 0.17 Next Fest slices where the title menu hitches one frame on open—sprites look fine, Tracy shows GPU idle while UI reads stale hierarchy because OnEnter(Menu) queued spawns that flush after layout systems run. Engineers blame batching; the fix is writer-before-reader SystemSet order and a Tracy empty-frame check—259 files bevy_menu_flush_receipt_v1.json so BUILD_RECEIPT bevy_menu_flush_ok blocks promotion when flush discipline drifts. Schedule order blog owns the whiteboard graph; Help #15 (menu flicker OnEnter) is reactive triage when shipped—this lesson is BUILD_RECEIPT flush proof.
The menu UI flush preflight is the ninety-second gate—259 is the promotion milestone.
Beginner path (sets → flush → Tracy → receipt)
| Step | Action | Success check |
|---|---|---|
| 1 | Name MenuUiWriterSet + MenuUiReaderSet |
U1 pass |
| 2 | chain() writer before reader in Update |
U2 pass |
| 3 | Move despawn/spawn to writer set only | U3 pass |
| 4 | Tracy capture: 3× open/close | U4 empty-frame check |
| 5 | File receipt + BUILD_RECEIPT | bevy_menu_flush_ok: true |
Time: ~52 minutes first fest candidate; ~15 minutes when sets are pinned.
Developer path (gates U1–U6)
| Gate | Check | Fail when |
|---|---|---|
| U1 | Menu systems in named SystemSets |
Anonymous systems in Update |
| U2 | MenuUiWriterSet.before(MenuUiReaderSet) |
Reader runs same frame as writer |
| U3 | Commands spawn/despawn only in writer set |
Reader queries entities mid-spawn |
| U4 | Tracy: no hitch span > 1 empty frame on open | Visible flicker with idle GPU |
| U5 | menu_flush_pipeline_v1.json committed |
Ad-hoc README only |
| U6 | Receipt + BUILD_RECEIPT | Promote before U4 GREEN |
U1 — cousin receipt crosswalk
| Field | Cousin (blog) | Cousin (245) | This lesson (259) |
|---|---|---|---|
| Schema | bevy_schedule_receipt_v1 |
playtest_crash_label_receipt_v1 |
bevy_menu_flush_receipt_v1 |
| Scope | Plugin/schedule graph | Wasm panic label | UI command flush + Tracy |
| Path | release-evidence/bevy/schedule/ |
release-evidence/bevy/playtest-crash/ |
release-evidence/bevy/menu-flush/ |
Do not merge schemas—reference paths in cousin_receipts only.
SystemSet chain (writer-before-reader)
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
pub enum MenuUiWriterSet {}
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
pub enum MenuUiReaderSet {}
pub fn configure_menu_flush_sets(app: &mut App) {
app.configure_sets(
Update,
(
MenuUiWriterSet,
MenuUiReaderSet,
)
.chain(),
)
.add_systems(Update, menu_on_enter_spawn.in_set(MenuUiWriterSet))
.add_systems(Update, menu_layout_read.in_set(MenuUiReaderSet));
}
Rule: anything that despawns menu nodes or inserts Node bundles stays in MenuUiWriterSet; layout/measurement stays in MenuUiReaderSet.
menu_flush_pipeline_v1.json
{
"schema": "menu_flush_pipeline_v1",
"build_label": "fest-demo-2026-10-rc4",
"bevy_version": "0.17",
"stage": "Update",
"system_sets": {
"writer": "MenuUiWriterSet",
"reader": "MenuUiReaderSet",
"chain": ["MenuUiWriterSet", "MenuUiReaderSet"]
},
"on_enter_menu_uses_commands": true,
"deferred_flush_strategy": "writer_before_reader_same_stage",
"tracy_capture_seconds": 90,
"open_close_cycles_required": 3
}
Pin under release-evidence/bevy/menu-flush/MENU_FLUSH_PIPELINE.json.
U4 — Tracy empty-frame check
- Enable
trace_tracy+ diagnostics preflight budget table. - Name spans:
MenuOpen,MenuUiWriter,MenuUiReader,MenuClose. - Pass: open hitch shows work inside
MenuUiWriterspan, not empty frame between writer and reader. - Archive
tracy_menu_flush_90s.csv+ screenshot.
bevy_menu_flush_receipt_v1.json
{
"schema": "bevy_menu_flush_receipt_v1",
"build_label": "fest-demo-2026-10-rc4",
"pipeline_path": "release-evidence/bevy/menu-flush/MENU_FLUSH_PIPELINE.json",
"pipeline_sha256": "ee55…",
"tracy_artifact": "release-evidence/bevy/menu-flush/tracy_menu_flush_90s.csv",
"cousin_receipts": {
"schedule_order_blog": "release-evidence/bevy/schedule/BEVY_SCHEDULE_RECEIPT.json",
"playtest_crash_label": "release-evidence/bevy/playtest-crash/PLAYTEST_CRASH_LABEL_RECEIPT.json"
},
"gates": {
"U1_named_sets": "pass",
"U2_writer_before_reader": "pass",
"U3_commands_in_writer_only": "pass",
"U4_tracy_empty_frame": "pass",
"U5_pipeline_committed": "pass",
"U6_build_receipt": "pass"
},
"bevy_menu_flush_ok": true,
"fest_demo_promotion_allowed": true
}
Pin under release-evidence/bevy/menu-flush/BEVY_MENU_FLUSH_RECEIPT.json.
BUILD_RECEIPT row (U6)
| Column | Pass when |
|---|---|
bevy_menu_flush |
bevy_menu_flush_ok: true |
bevy_schedule_order |
Cousin blog receipt independent |
playtest_crash_label |
Cousin 245 independent |
ALTER TABLE release_publish_gate ADD COLUMN IF NOT EXISTS
bevy_menu_flush_blocked BOOLEAN NOT NULL DEFAULT false;
Thursday row review — Bevy menu flush line: writer→reader Y/N.
Key takeaways
- Writer before reader in the same
Updatestage (U2)—not “eventually.” - OnEnter(Menu) + Commands is a flush problem, not a sprite problem (U3).
- Schedule order blog — graph; 259 — flush receipt on BUILD_RECEIPT.
- Tracy diagnostics preflight — capture discipline.
- Lesson 245 — crash label cousin.
- Hot reload help — masquerades as menu hitch.
- Forward: Lesson 260 UGS/OTLP diligence (queued).
- Lesson 258 — parallel playtest ingest column.
- October capstone 265 wires 254–264 including this row.
- Wednesday smoke — 60 s menu loop.
Common mistakes
- Putting layout read in the same set as
despawn(U3). - Filing blog schedule receipt while flush receipt RED—different gates.
- Tracy on
Updateroot only—noMenuUiWriterspan (U4). - Menu animation in
FixedUpdate, input inUpdate—see blog fixed-timestep section. - Merging
bevy_menu_flush_receiptintobevy_schedule_receipt.
Troubleshooting
| Symptom | Lane |
|---|---|
| One-frame flicker on open | U2/U3 chain + Help #15 when live |
| Hitch every N frames | Blog fixed timestep vs UI |
| Hitch after plugin add | Blog plugin order B4 |
| Wasm-only hitch | wasm boot preflight |
| Panic on menu open | Lesson 245 |
Mini exercise (50 minutes)
- Reproduce flicker with reader before writer—confirm U4 fail.
- Apply
chain()—confirm Tracy writer span precedes reader. - Commit
menu_flush_pipeline_v1.json. - File receipt; BUILD_RECEIPT GREEN.
Continuity — October–Q4 2026 fest ops truth (254–265)
| Lesson | Receipt focus |
|---|---|
| 258 | OBS replay buffer uniform fragments |
| 259 (this) | Bevy menu UI command flush |
| 260 | Unity UGS/OTLP diligence (queued) |
| 265 | October ops capstone (queued) |
Previous: Lesson 258 — OBS replay buffer uniform
Next: Lesson 260 — Unity UGS/OTLP diligence (publish on next Course-Create pass)
FAQ
Same as schedule order blog?
Blog = plugin graph + bevy_schedule_receipt; 259 = writer/reader flush + Tracy on BUILD_RECEIPT.
Same as Lesson 245?
245 = wasm panic build_label; 259 = menu UI flush.
Need Tracy for promotion?
U4 requires named spans + empty-frame check—screenshot alone is not enough.
Help #15?
Reactive OnEnter flicker fix when published—run 259 before fest branch promotion.
Fest demos fail the “polish” bar when menu open hitches one empty frame—chain writer before reader, flush Commands in the writer set, prove it in Tracy, then bevy_menu_flush_ok before promotion.