All posts

SCUA, a scripting language for live-ops games

Every live game we've worked on has had the same three arguments in it somewhere.

Someone writes a gate. Can this player enter the raid? It hands back true or false. Then the UI has to tell the player what they're missing, so the reasons get written out a second time on the client, and by Thursday the two disagree with each other.

Someone writes a save system. It's fine until a player's inventory contains a reference to itself, or until the economy changes mid-season and every old save has to be dragged forward.

And someone, at some point, puts a currency in a float.

SCUA is the language we built after doing all of that once too often. It reads like Lua and it's small enough to embed. What it's really aimed at is live-ops, and games where the numbers do most of the talking.

The part we're proudest of is contracts. A contract is a named set of conditions. You call it and get back a report of what held and what didn't, and it checks every clause instead of stopping at the first failure.

contract LevelComplete(player, world)
  gold: player.gold >= 100              else "collect 100 gold"
  boss: world.boss_dead                 else "defeat the boss"
  time: player.time <= world.par_time   else "finish under par time"
end

That one declaration is the gate and the quest log at the same time. The report knows one of three is done, knows the player finished in 83 seconds against a par of 60, and knows the words to put on screen. Nobody has to remember to update the tooltip when the design changes. When you only want a yes or no on a hot path, player matches CanSprint gives you the boolean and skips building the report at all.

Then there's what happens when a player closes the app. State in SCUA lives in partitions, roughly one per player or per match. A partition is a self-contained block of bytes, and nothing inside it points at a fixed memory address, so you can write it straight to storage and read it back later on a different machine. There's no serialisation code to write. Cycles and shared references come back intact, which is exactly where hand-rolled save code usually falls over. A player who wandered off mid-session gets parked on disk and brought back to life hours later, on another box entirely, still mid-swing.

That one took a long time to get right. It's also the thing that made the rest of the language worth building.

The number types were picked for our kind of game, rather than inherited from someone else's. int for gold and counts. decimal for money, where 0.1 + 0.2 is exactly 0.3 and a ledger never drifts a fraction of a penny. big for idle counters that sail past what a 64-bit integer can hold, because someone always leaves the game running over a bank holiday. float stays where it belongs, in physics. Vectors, matrices and quaternions are in the language itself, not bolted on from a library.

SCUA is pre-1.0 and in early access. The docs and a playground are up at scua.dev, and the whole compiler runs inside the page, so you can poke at the real thing rather than a demo of it. Tell us what breaks.