AgentSkillsCN

scene-stack-demo-mode-coordination

修复场景栈架构在演示/展示模式下可能出现的无限循环问题。适用场景如下:(1) 演示模式陷入重复播放同一场景的困境;(2) 场景循环未能按顺序遍历所有场景;(3) 编排器场景的 on_resume 回调未被触发;(4) 被压入堆栈的场景绕过了演示控制器。本方案适用于游戏引擎、UI 框架,以及任何具备自动化测试或演示模式的基于栈的状态机。

SKILL.md
--- frontmatter
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:

  1. Maintains the current phase/step
  2. Pushes child scenes one at a time
  3. 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):

rust
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):

rust
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):

rust
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:

code
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):

code
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

  1. Log shows each scene's on_resume being called on the orchestrator
  2. Scene stack depth stays bounded (typically max 2: orchestrator + child)
  3. All phases/scenes are visited in order
  4. Demo completes and either exits or loops to beginning

Example

From the rpg-game project, the fix was changing:

rust
// 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