soma.cell

Seasonal ladder — decay you cannot dodge, ratings that cannot underflow

Two real ladder failures. One: integer underflow on rating loss minted players with astronomical "unsigned" MMR on early third-party CS ladders. Two: operators quietly editing ARCHIVED seasons, retroactively changing who earned rewards. Here the rating lives behind a 0..3000 memory invariant — no underflow, no injected over-cap — and the season is a machine soma verify proves: gains only while `active`, decay only inside the `decaying` window, and `archived` is terminal. History cannot be edited.

A Soma program from the games_economy part of the verified corpus: 98 lines, cells Ladder, LadderTests. It passes soma check with soma 3.2.4.

How it is built

The cell Ladder declares 7 signals in its face: open_season() -> Map, start_decay() -> Map, archive_season() -> Map, award_points(player: String, pts: Int) -> Map, decay_tick(player: String) -> Map, rating_of(player: String) -> Int and season_phase() -> String; keeps 1 memory slot: rating (Map<String, Int>, persistent and consistent) under the invariant rating >= 0 && rating <= 3000. Its state machine season starts in preseason and allows 3 transitions: preseason -> active, active -> decaying and decaying -> archived. archived is terminal. Handlers: season_phase (line 37), open_season (line 39), award_points (line 45), start_decay (line 52), decay_tick (line 58), archive_season (line 65) and rating_of (line 71).

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

Generated from soma describe on this file.

The source

season_ladder.cell

// ============================================================================
// SEASONAL LADDER — decay you cannot dodge, ratings that cannot underflow
// ============================================================================
// Two real ladder failures. One: integer underflow on rating loss minted
// players with astronomical "unsigned" MMR on early third-party CS ladders.
// Two: operators quietly editing ARCHIVED seasons, retroactively changing
// who earned rewards. Here the rating lives behind a 0..3000 memory
// invariant — no underflow, no injected over-cap — and the season is a
// machine soma verify proves: gains only while `active`, decay only inside
// the `decaying` window, and `archived` is terminal. History cannot be
// edited.
// ============================================================================

cell Ladder {
    face {
        signal open_season() -> Map
        signal start_decay() -> Map
        signal archive_season() -> Map
        signal award_points(player: String, pts: Int) -> Map
        signal decay_tick(player: String) -> Map
        signal rating_of(player: String) -> Int
        signal season_phase() -> String
    }

    memory {
        rating: Map<String, Int> [persistent, consistent]
        invariant rating >= 0 && rating <= 3000
    }

    state season {
        initial: preseason
        preseason -> active
        active -> decaying
        decaying -> archived
    }

    on season_phase() { return get_status("season") }

    on open_season() {
        let r = try { transition("season", "active") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        return map("ok", true)
    }

    on award_points(player: String, pts: Int) {
        if season_phase() != "active" { return map("ok", false, "reason", "season not active") }
        let w = try { rating.set(player, (rating.get(player) ?? 0) + pts) }
        if w.error != () { return map("ok", false, "reason", "rating must stay in 0..3000") }
        return map("ok", true, "rating", rating.get(player))
    }

    on start_decay() {
        let r = try { transition("season", "decaying") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        return map("ok", true)
    }

    on decay_tick(player: String) {
        if season_phase() != "decaying" { return map("ok", false, "reason", "no decay outside the window") }
        let cur = rating.get(player) ?? 0
        rating.set(player, max(0, cur - 100))   // floors at 0 — the invariant agrees
        return map("ok", true, "rating", rating.get(player))
    }

    on archive_season() {
        let r = try { transition("season", "archived") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        return map("ok", true)
    }

    on rating_of(player: String) { return rating.get(player) ?? 0 }
}

cell test LadderTests {
    rules {
        // no points before the season opens
        assert award_points("kay", 100).ok == false
        assert open_season().ok == true
        assert award_points("kay", 2950).ok == true
        // over-cap and underflow are unrepresentable
        assert award_points("kay", 100).ok == false
        assert rating_of("kay") == 2950
        assert_fails rating.set("kay", 3001)
        assert_fails rating.set("kay", -1)
        // decay runs only inside the decay window, and floors at zero
        assert decay_tick("kay").ok == false
        assert start_decay().ok == true
        assert award_points("kay", 10).ok == false
        assert decay_tick("kay").ok == true
        assert rating_of("kay") == 2850
        // ARCHIVED IS FOREVER — no retroactive season edits
        assert archive_season().ok == true
        assert_fails transition("season", "active")
        assert_fails transition("season", "decaying")
        assert decay_tick("kay").ok == false
    }
}

What the toolchain says

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

$ soma check season_ladder.cell
✓ All checks passed.
$ soma test season_ladder.cell
test LadderTests ...
  ✓ assert award_points("kay", 100).ok == false
  ✓ assert open_season().ok == true
  ✓ assert award_points("kay", 2950).ok == true
  ✓ assert award_points("kay", 100).ok == false
  ✓ assert rating_of("kay") == 2950
  ✓ assert_fails rating.set("kay", 3001) — raised memory invariant violated on 'rating': rating >= 0 && rating <= 3000 — rejected write of 3001 (key "kay"); the slot is unchanged
  ✓ assert_fails rating.set("kay", -1) — raised memory invariant violated on 'rating': rating >= 0 && rating <= 3000 — rejected write of -1 (key "kay"); the slot is unchanged
  ✓ assert decay_tick("kay").ok == false
  ✓ assert start_decay().ok == true
  ✓ assert award_points("kay", 10).ok == false
  ✓ assert decay_tick("kay").ok == true
  ✓ assert rating_of("kay") == 2850
  ✓ assert archive_season().ok == true
  ✓ assert_fails transition("season", "active") — raised invalid transition: archived → active. Current state: 'archived'. Valid targets: []
  ✓ assert_fails transition("season", "decaying") — raised invalid transition: archived → decaying. Current state: 'archived'. Valid targets: []
  ✓ assert decay_tick("kay").ok == false

16 tests: 16 passed, 0 failed
$ soma verify season_ladder.cell
Verifying season_ladder.cell...

State machine 'season': 4 states, initial 'preseason'
  States: [active, archived, decaying, preseason]

  ✓ 4 states, 3 transitions
  ✓ all states reachable from 'preseason'
  ✓ terminal states: [archived]
  ✓ no deadlocks
  ✓ liveness: every state can eventually reach a terminal state
  ✓ think-isolated: CTL safety properties hold regardless of LLM output (3 handlers, 3 literal transitions, 0 dynamic)
  ✓ termination: all 7 handlers structurally terminate
  ✓ refinement: handler `open_season` ⟶ {active}
  ✓ refinement: handler `start_decay` ⟶ {decaying}
  ✓ refinement: handler `archive_season` ⟶ {archived}
  · transition sources: `open_season`, `start_decay`, `archive_season` call transition(id, target) without declaring the source — the runtime refuses a move the graph forbids, but the proof does not see where the handler starts; `transition(id, from, to)` names the edge (checked by `soma check`, refused at run time from any other state)

Ladder/invariants:

  ✓ invariant rating >= 0 && rating <= 3000 — writer 'decay_tick' proven by induction (writes max(0, cur - 100))
  ⚠ invariant rating >= 0 && rating <= 3000 — runtime-checked (computed values): award_points → rating because `pts` is a parameter (narrow it: `require ((rating.get(player) ?? 0) + pts) >= 0 && ((rating.get(player) ?? 0) + pts) <= 3000 else …`); `rating` has no known range

11 passed, 1 warnings, 0 failures

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

Builtins used

get_status, map, max, transition

Source file: /corpus/games_economy/season_ladder.cell · examples/corpus/games_economy/season_ladder.cell