Skip to content

Traffic Intersection

A canvas-rendered traffic intersection demonstrating hierarchical FSMs in machina v6. The parent FSM manages the intersection’s phase sequencing; two child FSMs independently control the signal progression for each active phase.

Five machina v6 features working together in a realistic scenario:

  1. _child delegation — parent states forward inputs to an active child FSM automatically; the parent never manually routes them
  2. Input bubblingphaseComplete has no handler in the child’s red state, so it bubbles up to the parent
  3. compositeState() — returns "northSouthPhase.green" as a single readable string, combining parent and child state
  4. Child auto-reset — when the parent re-enters a phase state, machina calls reset() on the child automatically, returning it to green
  5. defer() — a pedestrian button press during non-interruptible green is queued and replayed automatically when interruptibleGreen is entered
Intersection (parent FSM)
  ready                    — all signals red, waiting for Start
  northSouthPhase          — N/S has right-of-way
    _child: nsPhaseCtrl    — delegates to child FSM
  clearanceNS              — all-red interval, parent owns the timer
  eastWestPhase            — E/W has right-of-way
    _child: ewPhaseCtrl    — delegates to child FSM
  clearanceEW              — all-red interval, parent owns the timer

PhaseController (child FSM — one factory, two instances)
  green                    — 3s, pedestrianRequest deferred here
  interruptibleGreen       — 6s, pedestrianRequest shortens the phase
  yellow                   — 2.5s caution interval
  red                      — emits phaseComplete, bubbles to parent

The two child instances (nsPhaseCtrl and ewPhaseCtrl) are created by the same factory but have independent context objects and timers. They never share state.

compositeState() drives everything the renderer shows. The lookup table in config.ts maps every composite state string to visual signal states for all four directions:

Composite StateN/S VehicleN/S PedE/W VehicleE/W Ped
readyreddon’t walkreddon’t walk
northSouthPhase.greengreenwalkreddon’t walk
northSouthPhase.interruptibleGreengreenflashingreddon’t walk
northSouthPhase.yellowyellowdon’t walkreddon’t walk
northSouthPhase.redreddon’t walkreddon’t walk
clearanceNSreddon’t walkreddon’t walk
eastWestPhase.greenreddon’t walkgreenwalk
eastWestPhase.interruptibleGreenreddon’t walkgreenflashing
eastWestPhase.yellowreddon’t walkyellowdon’t walk
eastWestPhase.redreddon’t walkreddon’t walk
clearanceEWreddon’t walkreddon’t walk

Pressing the button during the non-interruptible green state calls defer({ until: "interruptibleGreen" }). Machina queues the input and replays it automatically when interruptibleGreen is entered — the press “works”, it just takes effect at the earliest safe moment.

Whether the pedestrianRequest input arrives as a live press or a deferred replay, it lands in interruptibleGreen and immediately transitions to yellow, shortening the phase. Pressing the button during yellow, clearance, or ready is unhandled and fires a nohandler event.

examples/traffic-intersection/