scene-stack-demo-mode-coordination
Fix infinite loops in demo/showcase modes for scene stack architectures. Use when: (1) Demo mode gets stuck repeating the same scene, (2) scene cycling doesn't progress through all scenes, (3) orchestrator scene's on_resume isn't being called, (4) pushed scenes bypass the demo controller. Applies to game engines, UI frameworks, and any stack-based state machine with automated testing or demo modes.
SKILL.md
| Name | scene-stack-demo-mode-coordination |
| Description | Fix infinite loops in demo/showcase modes for scene stack architectures. Use when: (1) Demo mode gets stuck repeating the same scene, (2) scene cycling doesn't progress through all scenes, (3) orchestrator scene's on_resume isn't being called, (4) pushed scenes bypass the demo controller. Applies to game engines, UI frameworks, and any stack-based state machine with automated testing or demo modes. |
name: scene-stack-demo-mode-coordination description: | Fix infinite loops in demo/showcase modes for scene stack architectures. Use when: (1) Demo mode gets stuck repeating the same scene, (2) scene cycling doesn't progress through all scenes, (3) orchestrator scene's on_resume isn't being called, (4) pushed scenes bypass the demo controller. Applies to game engines, UI frameworks, and any stack-based state machine with automated testing or demo modes. author: Claude Code version: 1.0.0 date: 2026-01-24
Scene Stack Demo Mode Coordination
Problem
When implementing a demo/showcase mode that cycles through multiple scenes in a stack-based scene manager, child scenes that directly push their successor (instead of popping back to the orchestrator) cause infinite loops or scene repetition.
Context / Trigger Conditions
- Demo mode gets stuck on one scene, repeatedly showing/screenshotting it
- Orchestrator scene's
on_resume()never gets called - Scene progression works manually but fails in automated demo mode
- Log shows scenes being pushed but orchestrator never advances
- Stack grows unboundedly:
Orchestrator → Scene1 → Scene2 → Scene3...
Solution
Architecture Pattern
Use a central orchestrator scene that:
- Maintains the current phase/step
- Pushes child scenes one at a time
- Advances to next phase in
on_resume()when child pops
Key Rule
Child scenes MUST pop in demo mode, never push the next scene directly.
Implementation
Orchestrator Scene (controls progression):
pub struct DemoScene {
phase: DemoPhase,
frames_in_phase: u32,
transition_requested: bool,
}
impl Scene for DemoScene {
fn update(&mut self, ctx: &mut SceneContext) -> SceneTransition {
self.frames_in_phase += 1;
// Wait for rendering to settle, then push current phase's scene
if self.frames_in_phase >= 30 && !self.transition_requested {
self.transition_requested = true;
if let Some(scene) = self.create_scene_for_phase(self.phase) {
return SceneTransition::Push(scene);
}
}
SceneTransition::None
}
fn on_resume(&mut self, _world: &mut World) {
// Called when pushed scene pops - advance to next phase
self.phase = self.phase.next();
self.frames_in_phase = 0;
self.transition_requested = false;
}
}
Child Scene (CORRECT - pops back):
impl Scene for ChildScene {
fn update(&mut self, ctx: &mut SceneContext) -> SceneTransition {
// Demo mode: auto-exit after delay
if self.demo_mode {
self.demo_timer += ctx.dt.as_secs();
if self.demo_timer > 2.0 {
log::info!("Demo mode: auto-exiting ChildScene");
return SceneTransition::Pop; // ← CORRECT: Returns to orchestrator
}
}
// ... normal logic ...
}
}
Child Scene (WRONG - causes infinite loop):
impl Scene for ChildScene {
fn update(&mut self, ctx: &mut SceneContext) -> SceneTransition {
if self.demo_mode {
self.demo_timer += ctx.dt.as_secs();
if self.demo_timer > 2.0 {
// WRONG: Bypasses orchestrator, creates unbounded stack
return SceneTransition::Push(Box::new(NextScene::new()));
}
}
}
}
Stack Visualization
Correct flow:
1. [DemoScene] pushes MainMenu
2. [DemoScene, MainMenu] - MainMenu shows, then pops
3. [DemoScene] on_resume called, advances to WorldMap phase
4. [DemoScene] pushes WorldMap
5. [DemoScene, WorldMap] - WorldMap shows, then pops
6. ... continues correctly ...
Incorrect flow (infinite loop):
1. [DemoScene] pushes MainMenu
2. [DemoScene, MainMenu] - MainMenu pushes WorldMap directly
3. [DemoScene, MainMenu, WorldMap] - WorldMap pushes Town
4. [DemoScene, MainMenu, WorldMap, Town] - Town pops
5. [DemoScene, MainMenu, WorldMap] - WorldMap pushes Town again!
6. ... infinite loop on Town ...
Verification
- Log shows each scene's
on_resumebeing called on the orchestrator - Scene stack depth stays bounded (typically max 2: orchestrator + child)
- All phases/scenes are visited in order
- Demo completes and either exits or loops to beginning
Example
From the rpg-game project, the fix was changing:
// BEFORE (wrong): MainMenuScene pushing directly
if self.demo_mode && self.demo_timer > 2.0 {
return SceneTransition::Push(Box::new(WorldMapScene::new()));
}
// AFTER (correct): MainMenuScene popping to DemoScene
if self.demo_mode && self.demo_timer > 2.0 {
return SceneTransition::Pop;
}
Notes
- This pattern applies to any pushdown automaton / stack-based state machine
- Also relevant for: game scene managers, wizard/multi-step forms, navigation stacks
- The same issue can occur in mobile app navigation (React Navigation, UIKit)
- For screenshot/testing modes, ensure sufficient delay before capturing (let animations settle)
- Consider adding phase/state logging to debug stack issues
Related Patterns
- State Machine with history
- Wizard/multi-step form controllers
- Mobile navigation coordinators
- Undo/redo stack managers