Skip to content

The State Machine: Flow of Time ​

A robust deterministic engine relies completely on a tight State Machine. If you don't know exactly what phase a turn is in, you cannot dictate valid logic updates.

ATLAS governs time using a deeply nested structure: Stages -> Turns -> Phases.

1. Stages (Macro-State) ​

Stages define sweeping chapters of a game, like Drafting, Setup, or MainGameplay. They contain transitions which conditionally route the engine based on global state.

2. Turns (Priority Loops) ​

Within a Stage, the game typically enters a revolving loop of priority known as Turns.

  • Alternating: Player 1 executes their complete turn, then passes priority entirely to Player 2.
  • Sequential: Player 1 completes a single Phase, then Player 2 completes the same phase before progressing.
  • Simultaneous: Both players execute actions concurrently, resolving once both pass.

3. Phases & Valid Actions (Micro-State) ​

Phases act as the atomic time block where players are actually authorized to act.

The Whitelist Strategy ​

A Phase is completely locked down by default. By explicitly defining the valid_actions array, it behaves as a strict security whitelist. If a requested action isn't listed, the engine strictly rejects the request.

yaml
timeline:
  stages:
    - id: MainGameplay
      turns:
        type: Alternating
        order:
          - Player1
          - Player2
        phases:
          - id: UpkeepPhase
            valid_actions: []
            auto_advance: true
            on_enter:
              - $Action.DrawCard(@player.current)
          - id: ActionPhase
            valid_actions:
              - PlayCard
              - Attack
              - UseEntityAction
              - Pass
            end_condition: $Action.Pass
json
{
  "timeline": {
    "stages": [
      {
        "id": "MainGameplay",
        "turns": {
          "type": "Alternating",
          "order": ["Player1", "Player2"],
          "phases": [
            {
              "id": "UpkeepPhase",
              "valid_actions": [],
              "auto_advance": true,
              "on_enter": [
                "$Action.DrawCard(@player.current)"
              ]
            },
            {
              "id": "ActionPhase",
              "valid_actions": ["PlayCard", "Attack", "UseEntityAction", "Pass"],
              "end_condition": "$Action.Pass"
            }
          ]
        }
      }
    ]
  }
}

Engine Rejection

If a user calls a PlayCard action but they are inside an UpkeepPhase (which has valid_actions: []), the backend engine immediately terminates the request. This provides ironclad protection against client-side exploitation.

Shorthand for simple turn orders

Not every game needs a nested Stage/Phase tree. If your game is just a single, uninterrupted Alternating/Sequential loop with no distinct phases (e.g. Chess — one action ends your turn, no upkeep/draw/combat split), you can skip timeline entirely and declare turn_order directly under global_rules instead:

yaml
global_rules:
  turn_order: Alternating

Appendix: Flow Reference ​

Valid Turn Types ​

When building a Turn block, the type determines when a player hands off root interaction rights back to completing the iteration pool.

  • Alternating: Player 1 executes their complete turn priority entirely, and then completely passes priority exactly once they hit their macro end condition.
  • Sequential: Player 1 completes a single Phase block, Player 2 completes the identical Phase block simultaneously, and only then do they both advance down the state machine together.
  • Simultaneous: Both players execute actions randomly/concurrently inside the same atomic Phase block. Priority naturally collapses manually.