Execution semantics explanation
This page explains how the simulator executes a model. It is not a command reference. For tasks such as batch mode, hot start and export, see Simulation tasks.
Cycle model
A runtime keeps an active stack from the root state to the active leaf. Calling
cycle() performs one model step against that stack and the persistent
variable map. The high-level phases are:
initialize the root-to-leaf stack if the runtime has not entered the model;
validate candidate transitions before committing them;
execute exits and transition effects for the selected transition;
enter the target path and perform any initial transitions needed by composites;
execute ordinary during work when no transition commits for the active leaf;
record the resulting state and variables in history.
The simulator uses speculative validation so a transition that cannot reach a stoppable state does not partially mutate the real runtime.
Execution-order matrix
Scenario |
Ordering guarantee |
Evidence family |
|---|---|---|
Cold entry |
Root/composite entry happens first, then initial transitions choose a
leaf, and only then can later active cycles run leaf |
|
Ordinary leaf cycle |
Ancestor aspect |
|
Leaf transition |
Source |
|
Composite initial transition |
Composite entry chooses the initial transition, runs its effect, runs
plain composite |
|
Pseudo or combo routing |
Intermediate pseudo states route automatically and are not stoppable leaf cycles; the whole candidate chain is validated before commit. |
|
Hot start |
The stack is constructed as already entered; enter actions on that path
and plain composite |
|
Terminal/end handling |
Exiting to the machine end empties the stack. Later |
|
Initial entry through composites
A composite state chooses an initial child with [*] -> Child. On initial
entry through a composite, the simulator runs the composite entry boundary,
selects the initial transition, executes that initial transition’s effect, runs
plain during before for that composite, then enters the selected child.
A child-to-child transition inside a composite is different: it exits the source
child, executes the transition effect, and enters the target child. Plain
composite during before and during after do not wrap ordinary
child-to-child transitions.
The checked demo below shows that plain during before runs during composite
entry, while ancestor aspect and leaf during work run on later active cycles:
#!/usr/bin/env python3
from pyfcstm.dsl import parse_with_grammar_entry
from pyfcstm.model import parse_dsl_node_to_state_machine
from pyfcstm.simulate import SimulationRuntime
dsl_code = """
def int counter = 0;
state System {
>> during before {
counter = counter + 1;
}
[*] -> Parent;
state Parent {
during before {
counter = counter + 100;
}
state Child {
during {
counter = counter + 10;
}
}
[*] -> Child;
}
}
"""
# Parse and create state machine
ast = parse_with_grammar_entry(dsl_code, 'state_machine_dsl')
sm = parse_dsl_node_to_state_machine(ast)
# Create runtime
runtime = SimulationRuntime(sm)
# Execute first cycle (initialization)
runtime.cycle()
print(f"Cycle 1: state={'.'.join(runtime.current_state.path)}, counter={runtime.vars['counter']}")
print(f" → Parent.during before (100) executed during entry")
# Execute second cycle (during phase)
runtime.cycle()
print(f"\nCycle 2: state={'.'.join(runtime.current_state.path)}, counter={runtime.vars['counter']}")
print(f" → >> during before (1) + Child.during (10)")
# Execute third cycle
runtime.cycle()
print(f"\nCycle 3: state={'.'.join(runtime.current_state.path)}, counter={runtime.vars['counter']}")
print(f" → >> during before (1) + Child.during (10)")
Output:
Cycle 1: state=System.Parent.Child, counter=111
→ Parent.during before (100) executed during entry
Cycle 2: state=System.Parent.Child, counter=122
→ >> during before (1) + Child.during (10)
Cycle 3: state=System.Parent.Child, counter=133
→ >> during before (1) + Child.during (10)
Lifecycle and transition effects
The simulator distinguishes lifecycle blocks from transition effects:
enterruns when the state is entered.duringruns when the active state remains active for a cycle.exitruns when the state is left.transition
effectblocks run between source exit and target entry.
For a source-to-target transition, source exit happens before the effect, and
target entry happens after the effect. If no transition commits, the active
leaf’s ordinary during path runs instead.
Aspect during actions
>> during before and >> during after are aspect actions contributed by
ancestor states to descendant leaf cycles. They are different from plain
composite during before / during after blocks.
Pseudo states are automatic routing states. They are not normal stoppable leaf states, and ancestor aspect during actions do not execute for the pseudo states inside a pseudo/combo routing chain. The semantic effect of the whole chain is validated as part of the surrounding transition path.
In a chain such as S1 -> P1 -> P2 -> S2, P1 and P2 are routing
boundaries. Their guard/effect decisions can affect which candidate path is
valid, but they do not create ordinary leaf during cycles and do not receive
ancestor aspect during actions as if they were real leaves.
Transition priority and validation
When several transitions are available, the model order and guard/event match determine which candidate is considered first. The runtime validates the candidate path before committing state or variable changes. If the chosen path cannot reach a stoppable leaf, the runtime reports a DFS validation failure instead of leaving the machine halfway through a pseudo or composite routing chain.
Hot start semantics
Hot start builds the active stack directly from an initial_state and
initial_vars. This represents an already-entered boundary:
all declared persistent variables must be provided;
enter actions on the constructed path are skipped;
plain composite
during beforefor the constructed boundary is not replayed;later cycles run normal transition and during semantics;
a composite hot-start target is validated so it can reach a stoppable leaf.
This makes hot start useful for debugging and tests, but it is not equivalent to replaying the full history from the root initial state.
Runtime history and export
After each committed cycle, the runtime records the cycle number, active state
and variables in history. The REPL history command formats that in the
terminal, while export <path> writes the retained history to a file. Export
is an observation feature; it does not change the runtime state.
Fixture evidence matrix
The semantic fixture suite is the executable evidence behind this page. The table intentionally names fixture families instead of individual assertions so the explanation remains stable while tests add more focused cases.
Behavior family |
Representative fixture pattern |
What the family protects |
|---|---|---|
Lifecycle ordering |
|
Cold entry, state entry, leaf |
Aspect actions |
|
Ancestor aspect before/after actions and the active-leaf metadata seen by handlers. |
Pseudo chains |
|
Automatic routing through pseudo states and recovery from invalid candidates. |
Combo routing |
|
Expanded combo relay paths, guard/effect order, rollback, terminal validation, and aspect boundaries. |
Rollback |
|
Variable and stack rollback when speculative validation rejects a path. |
Hot start |
|
Already-entered stack construction, variable requirements, and non-stoppable path rejection. |
Terminal/end handling |
|
End-state transitions, runtime termination, and terminal-safe queries. |
Abstract-handler context |
|
Read-only variable snapshots, callsite metadata, named references and active-leaf reporting. |
Boundary with generated runtimes
The simulator is the reference model-level executor used before code generation. Generated runtimes should be checked against the same scenarios, but target languages may add platform constraints such as integer width or C/C++ deployment risk that do not apply to the Python simulator itself. Use inspect and generated-runtime tests when target-specific behavior matters.