soma.cell

Turn tracker — round/phase state machine with player rotation

Models a multi-player turn order: the active player index cycles 0..n-1, incrementing the round each time play wraps back to player 0. A `phase` state machine guards the upkeep → main → end → upkeep cycle, with a *->over catch-all so the game always terminates. Run: soma run turn_tracker.cell --signal demo Test: soma test turn_tracker.cell Verify: soma verify turn_tracker.cell (needs soma.toml)

A Soma program from the games part of the verified corpus: 71 lines, cells TurnTracker, TurnTrackerTests. It passes soma check with soma 3.2.4.

How it is built

The cell TurnTracker declares 3 signals in its face: next_turn(active: Int, round: Int, players: Int) -> Map, play_rounds(players: Int, turns: Int) -> Map and demo() -> Map. Its state machine phase starts in upkeep and allows 4 transitions: upkeep -> main, main -> end, end -> upkeep and * -> over. over is terminal. Handlers: next_turn (line 30), play_rounds (line 38) and demo (line 49).

The file also holds 1 test cell (TurnTrackerTests) with 8 assertions, run by soma test.

Generated from soma describe on this file.

The source

soma.toml

[package]
name = "games-corpus"
version = "0.1.0"
description = "Training corpus: games & simulations"

# The turn/phase machine is intentionally cyclic (a game loops through
# phases each round) with a `* -> over` escape, so we verify only that no
# reachable state deadlocks — liveness to a terminal is not the contract here.
[verify]
deadlock_free = true

turn_tracker.cell

// ============================================================================
// Turn tracker — round/phase state machine with player rotation
// ============================================================================
//
// Models a multi-player turn order: the active player index cycles 0..n-1,
// incrementing the round each time play wraps back to player 0. A `phase`
// state machine guards the upkeep → main → end → upkeep cycle, with a *->over
// catch-all so the game always terminates.
//
// Run:  soma run turn_tracker.cell --signal demo
// Test: soma test turn_tracker.cell
// Verify: soma verify turn_tracker.cell   (needs soma.toml)

cell TurnTracker {
    face {
        signal next_turn(active: Int, round: Int, players: Int) -> Map
        signal play_rounds(players: Int, turns: Int) -> Map
        signal demo() -> Map
    }

    state phase {
        initial: upkeep
        upkeep -> main
        main   -> end
        end    -> upkeep
        *      -> over
    }

    // Advance to the next player; wrapping past the last player bumps round.
    on next_turn(active: Int, round: Int, players: Int) {
        let nxt = (active + 1) % players
        let r = round
        if nxt == 0 { r = round + 1 }
        return map("active", nxt, "round", r)
    }

    // Simulate `turns` total turns over `players` seats.
    on play_rounds(players: Int, turns: Int) {
        let active = 0
        let round = 1
        for t in range(0, turns) {
            let s = next_turn(active, round, players)
            active = s.active
            round = s.round
        }
        return map("active", active, "round", round)
    }

    on demo() {
        // 3 players, 7 turns: turns wrap at 3 and 6 → round 3, active 1
        return play_rounds(3, 7)
    }
}

cell test TurnTrackerTests {
    rules {
        // player 1 → 2 of 3, same round
        assert next_turn(1, 5, 3).active == 2
        assert next_turn(1, 5, 3).round == 5
        // wrapping past player 2 → player 0, round increments
        assert next_turn(2, 5, 3).active == 0
        assert next_turn(2, 5, 3).round == 6
        // 3 players, 7 turns
        assert play_rounds(3, 7).round == 3
        assert play_rounds(3, 7).active == 1
        // 2 players, 4 turns → back to start of round 3
        assert play_rounds(2, 4).round == 3
        assert play_rounds(2, 4).active == 0
    }
}

What the toolchain says

Run on a fresh copy of the program at build time; the outputs are pasted unedited.

$ soma check turn_tracker.cell
✓ All checks passed.
$ soma test turn_tracker.cell
test TurnTrackerTests ...
  ✓ assert next_turn(1, 5, 3).active == 2
  ✓ assert next_turn(1, 5, 3).round == 5
  ✓ assert next_turn(2, 5, 3).active == 0
  ✓ assert next_turn(2, 5, 3).round == 6
  ✓ assert play_rounds(3, 7).round == 3
  ✓ assert play_rounds(3, 7).active == 1
  ✓ assert play_rounds(2, 4).round == 3
  ✓ assert play_rounds(2, 4).active == 0

8 tests: 8 passed, 0 failed
$ soma verify turn_tracker.cell
Verifying turn_tracker.cell...

State machine 'phase': 4 states, initial 'upkeep'
  States: [end, main, over, upkeep]

  ✓ 4 states, 6 transitions
  ✓ all states reachable from 'upkeep'
  ✓ terminal states: [over]
  ✓ no deadlocks
  ✓ liveness: every state can eventually reach a terminal state
  ✓ wildcard transitions: * -> [over]
  ✓ termination: all 3 handlers structurally terminate
  ⚠ refinement: declared transition `upkeep → main` is never reached by any handler — spec may be aspirational or stale
  ⚠ refinement: declared transition `main → end` is never reached by any handler — spec may be aspirational or stale
  ⚠ refinement: declared transition `end → upkeep` is never reached by any handler — spec may be aspirational or stale
  ⚠ refinement: declared transition `* → over` is never reached by any handler — spec may be aspirational or stale

7 passed, 4 warnings, 0 failures

  Temporal properties for 'phase':
  ✓ deadlock_free — no deadlocks in any reachable state
Temporal: 1 passed, 0 failed
VERIFY OK

Builtins used

map, range

Source file: /corpus/games/turn_tracker.cell · examples/corpus/games/turn_tracker.cell