Skip to content

Examples

iKryptonic edited this page May 3, 2026 · 6 revisions

Examples

A source-driven cookbook of real patterns from the repository and current runtime APIs.

Important

This page intentionally favors real repository examples and current runtime behavior over older pseudo-code. Where older example files need modernization, this page calls that out explicitly.


1. TrafficLightFSM walkthrough

Source: example/TrafficLightFSMExample.luau

local TrafficLightFSM = {
    Name = "TrafficLightFSM",
    ValidStates = { "Red", "Green", "Yellow", "Blink", "Maintenance" },
    TerminalStates = {},
    InitialState = "Red",
}

function TrafficLightFSM:RegisterStates()
    self:AddState("Red", function(fsm)
        fsm.Context.Cycles = (fsm.Context.Cycles or 0) + 1
        fsm:ScheduleTransition(4, "Green")
    end, { "Green", "Maintenance" })

    self:AddState("Green", function(fsm)
        fsm:ScheduleTransition(5, "Yellow")
    end, { "Yellow", "Maintenance" })

    self:AddState("Yellow", function(fsm)
        local cycles = fsm.Context.Cycles or 0
        if cycles % 3 == 0 and not fsm.Context._blinkedThisCycle then
            fsm.Context._blinkedThisCycle = true
            fsm:ScheduleTransition(2, "Blink")
        else
            fsm.Context._blinkedThisCycle = false
            fsm:ScheduleTransition(3, "Red")
        end
    end, { "Red", "Blink", "Maintenance" })

    self:AddState("Blink", function(fsm)
        fsm:ScheduleTransition(2, "Yellow")
    end, { "Yellow", "Blink", "Maintenance" })

    self:AddState("Maintenance", function(fsm)
        -- manual stop state
    end, { "Red" })
end

return TrafficLightFSM

Why this example matters

It demonstrates the cleanest possible use of the current timing API:

  • no manual task.delay
  • no stale closure guards
  • a simple state graph that ServiceManager visualizes nicely
  • context state (Cycles, _blinkedThisCycle) used exactly where it belongs

State diagram

  [*] ──→ Red
  Red ──after 4s──→ Green
  Green ──after 5s──→ Yellow
  Yellow ──every 3rd cycle──→ Blink
  Yellow ──otherwise after 3s──→ Red
  Blink ──after 2s──→ Yellow

  Red ─────────→ Maintenance
  Green ───────→ Maintenance
  Yellow ──────→ Maintenance
  Blink ───────→ Maintenance
  Maintenance ─→ Red

How to run it quickly

The repository's test/main.server.luau already creates a demo instance of this machine:

local trafficFsm = Orchestrator.CreateStateMachine({
    StateMachineClass = "TrafficLightFSM",
    StateMachineId = "TrafficLight_Demo",
    Context = { Cycles = 0 },
})

Then the client bootstrap launches ServiceManager, making the graph visible immediately in Studio.


2. DoorEntity + DoorStateMachine walkthrough

Sources:

  • example/DoorEntityExample.luau
  • example/DoorStateMachineExample.luau
  • example/GameControllerExample.luau

This is the repository's most complete “real workflow” example because it combines:

  • an entity with domain methods (Open, Close, Reset)
  • replication-aware visual updates
  • an FSM with validation, failure, and cleanup
  • a controller script that creates jobs and reacts to completion

2.1 Door entity pattern

Core idea from DoorEntityExample.luau:

local DoorEntity = {
    Name = "DoorEntity",
    Replication = {
        Enabled = true,
        RateLimit = 60,
    },
    Schema = {
        IsOpen = { Type = "boolean", Replicate = true },
        Locked = { Type = "boolean", Replicate = true },
        Color  = { Type = "Color3", Replicate = true },
        _hinge = { Type = "BasePart" },
        _door = { Type = "BasePart" },
        _statusLight = { Type = "BasePart" },
    },
}

function DoorEntity:GetContext(params)
    local hinge = self.Instance:WaitForChild("Hinge", 5)
    local statusLight = self.Instance:WaitForChild("StatusLight", 5)

    return self({
        _hinge = hinge,
        _statusLight = statusLight,
        IsOpen = self.IsOpen or false,
        Locked = self.Locked or true,
    })
end

function DoorEntity:Open()
    self.IsOpen = true
    self.Color = Color3.fromRGB(0, 255, 0)
    self.Locked = false
    return self:UpdateEntity()
end

2.2 Recommended modernization

The example file is conceptually useful, but for current runtime correctness you should add:

Mutable = true,

because UpdateEntity() requires a mutable entity in the current BaseEntity implementation.

2.3 Door FSM pattern

The DoorStateMachineExample.luau example models a job-like workflow:

local DoorStateMachine = {
    Name = "DoorStateMachine",
    ValidStates = { "Validate", "CreatingMessage", "Opening", "Closing", "Completed", "Failed" },
    TerminalStates = { "Completed", "Failed" },
    InitialState = "Validate",
}

It then does the following:

  1. validates the command request
  2. creates UI feedback for the user
  3. opens or closes the door through entity methods
  4. polls the result with WaitSpan = 0.5
  5. finishes or fails cleanly

2.4 State diagram

  [*] ──→ Validate
  Validate ──valid request──→ CreatingMessage
  Validate ──invalid request──→ Failed

  CreatingMessage ──DesiredState = Open──→ Opening
  CreatingMessage ──DesiredState = Close──→ Closing
  CreatingMessage ──UI setup failure──→ Failed

  Opening ──WaitSpan polling──→ Opening
  Closing ──WaitSpan polling──→ Closing
  Opening ──→ Completed
  Closing ──→ Completed
  Opening ──→ Failed
  Closing ──→ Failed

  Completed ──→ [*]
  Failed ─────→ [*]

2.5 Controller pattern

example/GameControllerExample.luau shows how a gameplay script stitches the pieces together:

local Door1 = Orchestrator.CreateEntity({
    EntityClass = "DoorEntity",
    EntityId = "Door1",
    Context = {
        Name = doorModel.Name,
        Instance = doorModel,
        OwnerId = script.Name,
    },
})

local openJob = Orchestrator.CreateStateMachine({
    StateMachineClass = "DoorStateMachine",
    StateMachineId = script.Name .. " DoorStateMachine " .. tick(),
    Context = {
        DoorEntity = Door1,
        DesiredState = "Open",
    },
})

Then it connects Completed and Failed signals to chain the close job.

2.6 One important current-runtime note

The example file manually calls :Start({ State = "Validate" }) after CreateStateMachine(). With the current factory, that is now optional unless you explicitly pass AutoStart = false.


3. BehaviorTree composition example

Source features:

  • Core/Factory/BehaviorTree.luau
  • ServiceManager/ServiceBrain.luau

The built-in BehaviorTree module currently exposes:

  • Selector
  • Sequence
  • Inverter
  • Succeeder
  • Condition
  • SetState

3.1 A practical guard AI tree

local BehaviorTree = require(game:GetService("ReplicatedStorage").Orchestrator.Core.Factory.BehaviorTree)

local DecideState = BehaviorTree.Selector({
    BehaviorTree.Sequence({
        BehaviorTree.Condition(function(ctx)
            return ctx.Entity.Health <= 0
        end),
        BehaviorTree.SetState("Dead"),
    }),

    BehaviorTree.Sequence({
        BehaviorTree.Condition(function(ctx)
            return ctx.TargetVisible == true
        end),
        BehaviorTree.SetState("Chase"),
    }),

    BehaviorTree.Sequence({
        BehaviorTree.Condition(function(ctx)
            return ctx.HasPatrolRoute == true
        end),
        BehaviorTree.SetState("Patrol"),
    }),

    BehaviorTree.SetState("Idle"),
})

You can then call that tree from a lightweight decision state:

self:AddState("Think", function(fsm)
    DecideState(fsm)
end, { "Dead", "Chase", "Patrol", "Idle" })

3.2 Why this is useful

A BehaviorTree is a good front-end for choosing which FSM state to enter when the decision logic is more complex than a couple of if statements.

Use it when:

  • many conditions compete for control
  • you want reusable decision leaves
  • you want declarative “decision first, state transition second” logic

3.3 Real repo usage: ServiceBrain

ServiceManager/ServiceBrain.luau uses a BT sequence/selector composition to decide whether to fetch data, run insight analysis, re-render UI, and tick animations.

That is an important architectural clue: the BehaviorTree system is not theoretical; the framework uses it in production code.


4. Hierarchical FSM example

Source feature: BaseStateMachine:AddSubMachine()

A good HFSM pattern is to let the parent own phase-level behavior while the child owns the details.

Note

The following is a conceptual pattern example — it is not shipped in the example/ folder. For shipped examples, see DoorEntityExample.luau, DoorStateMachineExample.luau, and TrafficLightFSMExample.luau.

Parent machine

local BossFSM = {
    Name = "BossFSM",
    ValidStates = { "Intro", "Combat", "Recover", "Dead" },
    InitialState = "Intro",
    TerminalStates = { "Dead" },
}

function BossFSM:RegisterStates()
    self:AddState("Intro", function(fsm)
        fsm:ScheduleTransition(2, "Combat")
    end, { "Combat" })

    self:AddSubMachine("Combat", CombatPhaseFSM, {
        InitialState = "Approach",
        Transitions = {
            OnCompleted = "Recover",
            OnFailed = "Dead",
            OnCancelled = "Recover",
        },
        StoreReference = "CombatMachine",
    })

    self:AddState("Recover", function(fsm)
        fsm:ScheduleTransition(3, "Combat")
    end, { "Combat", "Dead" })

    self:AddState("Dead", function(fsm)
        fsm:Finish()
    end)
end

Child machine

local CombatPhaseFSM = {
    Name = "CombatPhaseFSM",
    ValidStates = { "Approach", "Attack", "Done", "Fail" },
    InitialState = "Approach",
    TerminalStates = { "Done", "Fail" },
}

function CombatPhaseFSM:RegisterStates()
    self:AddState("Approach", {
        OnHeartbeat = function(state, fsm, dt)
            if fsm.Context.DistanceToTarget < 8 then
                fsm.State = "Attack"
            end
        end,
    }, { "Attack", "Fail" })

    self:AddState("Attack", {
        OnHeartbeat = function(state, fsm, dt)
            if fsm.Context.TargetDefeated then
                fsm:Finish()
            elseif fsm.Context.Entity.Health <= 0 then
                fsm:Fail("boss died during combat")
            end
        end,
    })
end

What this buys you

  • the parent stays readable
  • the child remains reusable
  • context is shared automatically
  • completion/failure/cancel paths are explicit

Lifecycle view

  BossFSM                   CombatPhaseFSM
    │                             │
    │── create sub-machine on enter Combat ─→│
    │                             │── Start({State="Approach"})
    │←── Completed ───────────────│
    │── ChangeState({Name="Recover"})
    │── OnLeave -> Cancel if still active ─→│

5. ServiceManager usage example

Source features:

  • Orchestrator:StartServiceManager()
  • ServiceManager.SwitchSubsystem(name)
  • ServiceManager.SetContextMode(mode)
  • ConsoleModule built-in commands

Minimal boot pattern

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Orchestrator = require(ReplicatedStorage:WaitForChild("Orchestrator"))

Orchestrator:RegisterComponents()

if RunService:IsStudio() then
    local sm = Orchestrator:StartServiceManager()
    sm.SwitchSubsystem("FSM")
    sm.SetContextMode("server")
end

A simple debug workflow

  1. boot the place
  2. open FSM in server mode to inspect authoritative state
  3. open ENTITY to inspect schema values and versions
  4. open NETWORK to inspect EntityUpdateEvent or request callbacks
  5. open PROFILER to see if the heartbeat or a task is hot
  6. use CONSOLE commands such as:
    • tasks list
    • fsm info TrafficLight_Demo
    • entities info Door1
    • perf
    • switch network

6. A full “first playable” sample

If you want a practical demo setup with almost no extra code, use the repository's shipped bootstraps:

  • test/main.server.luau
  • test/Client.client.luau

The server script does:

local Orchestrator = require(ReplicatedStorage:WaitForChild("Orchestrator"))
Orchestrator:RegisterComponents()

local trafficFsm = Orchestrator.CreateStateMachine({
    StateMachineClass = "TrafficLightFSM",
    StateMachineId = "TrafficLight_Demo",
    Context = { Cycles = 0 },
})

The client script does:

local Orchestrator = require(ReplicatedStorage:WaitForChild("Orchestrator", 30))
Orchestrator:RegisterComponents()
Orchestrator:StartServiceManager()

That combination is the easiest way to verify the framework end to end in Studio.


7. When to choose which example style

Need Best example
simple timed transitions TrafficLightFSMExample
entity methods + visuals + workflow FSM DoorEntityExample + DoorStateMachineExample
orchestration script that chains jobs GameControllerExample
decision trees over many conditions BehaviorTree composition
parent/child phase decomposition HFSM AddSubMachine pattern
live runtime introspection ServiceManager bootstrap + console

8. Recommended “productionized” version of the repo examples

When adapting the shipped examples into real gameplay code, make these updates immediately:

  • add Mutable = true to any entity that commits with UpdateEntity()
  • remove redundant manual :Start() calls unless AutoStart = false
  • replace ad-hoc polling with ScheduleTransition() when the logic is a fixed timed exit
  • gate StartServiceManager() behind Studio/dev checks
  • assign stable IDs intentionally rather than using tick() everywhere

Related guides

Clone this wiki locally