The editor

The flow editor

Two questions live in the Flow panel, and they are the two biggest facts about a level: where can it be played, and what kind of game is it — a live world people walk around in, or a run with stages that ends.

Where it can be played

The Flow panel open on the shooter: where it can be played as checkboxes, and the run-shape picker below

The shooter's Flow panel: playable as a room and as a battle.

  1. 1As a room / as a battle — anybody can keep it standing and walk in, and the battle lobby can run a match on it: sides, a score, an end.
  2. 2As a ball game / as a race — each greyed with its reason until the world backs it up: a goal at each end, a start and a finish.
  3. 3What kind of game is this? A level is a live world, or it plays a run with stages — pick the shape closest to yours and move its pieces.
  4. 4The shape this level has now — the shooter is a live world: no start, no end.

These are the document's capabilities, said as checkboxes. A claim the world cannot back up is greyed out with the reason beside it — the same refusal the parser makes on load, said before the click instead of as a save that silently does nothing.

What this is, and what you do in it

Two questions, and they used to be one. preset answers what you do — shoot, score, run a course — and it had been quietly answering a second one as well: whether a round is happening at all. The tell was that its list read as four styles and an absence, because freestyle is not a game you play, it is a statement that no game is being played.

So mode is that second question on its own, above the styles in the panel:

spaceA place that is simply there. No round, nothing to win, and what happens in it stays. This is what a level says by not saying anything.
lobbyWhere people gather before or between rounds — and it can still keep score. The rest of the block applies unchanged, which is the whole reason it is a mode rather than a flag.
battleA run: it starts, it ends, and what it counted goes with it.

Keeping them as one list would mean a lobby-shooter beside shooter, and then a lobby-football — a product of two lists rather than a list. A deathmatch running all evening in the corner of a foyer and a deathmatch that starts and ends are the same style.

And a level can carry a round per mode. flow is the one it plays when nothing more specific is said; flows keys a round to a mode:

"flow":  { "start": "idle", "phases": { … } },
"flows": {
  "lobby":  { "start": "waiting", "phases": { … } },
  "battle": { "start": "warmup",  "phases": { … } }
}

A foyer with a kickabout in the corner has a round of its own — a whistle, a kick off, a score that resets — and it is not the round the foyer runs the rest of the evening. Said with one flow, that is a state machine with a second state machine written along its edges.

A mode with no round of its own plays flow, so a level with one round that happens to be scheduled as a match does not write its phases twice. The fallback only goes one way: a round under flows.battle never runs in a room, because a foyer that suddenly has a whistle in it is the quiet kind of wrong.

And a round can name the place it is played in. sceneon a flow, by its key in the level's scenes table — entering the round takes you there, the way walking through a door does:

"flows": {
  "lobby":  { "scene": "foyer", "start": "waiting", "phases": { … } },
  "battle": { "scene": "arena", "start": "warmup",  "phases": { … } }
}

The run names the scene, not the other way round: one arena hosting three different rulesets is the normal case, and a scene carrying its own ruleset would make it the exception. A scene the document does not have is refused when the level loads, naming the round it is on, because a round that jumps to a room that is not there is a level that starts and then goes nowhere. Naming nothing moves nobody.

Which one is running is the session'sanswer rather than the file's — a battle scheduled on a lobby is a battle for as long as it lasts. A script can ask: world.mode, world.style, and world.live for whether anybody else is in here.

A live world, or a run

Most levels are a place people are in: no start, no end, things work, nothing finishes. A run is the other kind — kick-off, play, full time; roll, move, next seat — and the flow block is a level saying what its own stages are, instead of being one of five presets. The board game is the level this was built for:

roll

Only the roll key is live. A rule on the dice emits "rolled".

move

The level says "your go"; only use is live. Moving emits "moved" — a six emits "six".

back to roll

"six" loops to roll for another throw; "moved" passes the turn.

wins

First colour with four pieces home — checked from the level’s own data.

Phases and transitions

Nothing in the block is new vocabulary. A phase's doesis the same verb list a rule's do is, and it runs once, on entering. A transition fires on a when (a condition), an on (an emitted event name — a rule that finishes a turn says so, and the flow hears it), or an after (seconds). The parser refuses a step with none of the three — a step that fires on nothing is a silent forever.

the flow block, in the document

"flow": {
  "start": "roll",
  "phases": {
    "roll": { "allow": ["roll"], "next": [{ "on": "rolled", "go": "move" }] },
    "move": {
      "does": [{ "op": "emit", "event": "your go" }],
      "allow": ["use"],
      "next": [
        { "on": "six",   "go": "roll" },
        { "on": "moved", "go": "roll" }
      ]
    }
  }
}

allownames which of the player's keys are live in a phase. Absent is all of them; empty is none — the useful one, because it is how a phase says watch, do not touch. A press the phase does not allow is dropped, not queued.

Two refusals are the point of the block: a destination that does not exist, and a phase nothing can reach. Neither is an error in any programming language, and both are always a mistake here — so the panel refuses them instead of letting a run freeze with nothing in any log to say why. The limits are 32 phases and 8 ways out of each.

What wins

winsis the field that makes a flow a game rather than a loop: a condition on the level's own data, checked from the first frame of the run. When it holds, the run ends, every entity hears finished, and a rematch is offered.

"wins": { "of": "world", "prop": "mine-home", "is": ">=", "value": 4 }

It says thatthe run is won, not who won — the scoreboard has been answering "who is ahead" all along. And what it counts has to be scoped to the run: a field that survives the game still holds last game's four, and the next run would be won on its opening frame. The parser refuses that, and names the field.

The graph

The panel draws the flow as a graph, laid out by distance from the start, because the one thing nested JSON cannot show is the shape of a state machine. Drag between two nodes to point an arrow; a phase's does is the same verb rows the rules panel draws. Start from the shape closest to yours — the presets are shapes, not modes — and move its pieces.