# How the engine runs a model

The engine runs a model as a sequence of **requests**. At each request whoever plays (a person in
the [player](https://tabletwin.com/play/), or an agent) answers with one of the options, or chance
picks an outcome. The same engine runs the [validator's](https://tabletwin.com/validate/) test
games and the player, in your browser. This page describes exactly what the engine does with each
part of a model, who sees what, how executability is measured and what the validator reports. The
keys themselves are listed in the [reference](https://tabletwin.com/docs/reference/); the expression language is in
[expressions](https://tabletwin.com/docs/expressions/).

## Requests

| Request | What it carries | The answer |
|---|---|---|
| **Decision** | The deciding player, the options, the place (`where`), and who sees the answer | One of the options |
| **Chance** | The possible outcomes with their probabilities, the place, and who sees the outcome | One outcome, drawn with those probabilities |
| **End** | The payoff per player (one number each) and the reason | Nothing: the game is over |

- Who sees the answer is either everyone, or a list of players (an empty list: nobody).
- A decision inside a subset choice (`choose` with `min`/`max`) also knows the item behind each
  option (nothing for `done`), all options of the choice, and what has been picked so far. Tactics
  read these as `opts`, `pool` and `chosen`.
- The reason is the `id` of the end condition that ended the game, or
  `flow finished without an end condition`.
- An option is shown by its label: a dict shows its `label`, else its `id`, else its `uid`, else
  `?`. Two instances of the same card therefore look the same to a player.

### Places (`where`)

Every request names the place it comes from. Tactics match rules on these names (`at:`), and the
player shows the place of every question.

| Place | Where it comes from |
|---|---|
| `step <id>` | Choosing between the legal actions of a step |
| `action <name>` | An op in the `effect` of action `<name>` (also an ability on a content item) |
| `action <name> (cost)` | An op in the `cost` of that action |
| `trigger <id>` | An optional trigger (`use` / `skip`), or an op in a trigger's `do`. A trigger on a content item without its own `id` uses the item's `id` |
| `window <name>` | Priority in window `<name>`: the playable fast actions plus `pass`. A window opened with `window: true` (no name) is `window None` |
| `content <id>` | An op in the `effect` of a content item run by `exec` |

Suffixes: a subset choice adds ` · n` (the n-th element being chosen, from 1); an op in the
`impact` of an `emit` gets ` · <event>` after the place of the `emit`; a `sample` chance adds
` · <outcome model>`. The ops `if`, `for_each`, `repeat` and the effect of an outcome keep the
place of their parent. Two more names only appear in error messages: `end <node id>` (emitting an
`end_event`) and `<place> · window` (a `window` op). Decisions inside a window opened by a `window`
op are still `window <name>`, and the actions played there are `action <name>`.

## Start of a game

1. **Constants**, as written in the model.
2. **Variables**, in declaration order. `init_x` may read constants, `content`, `players`,
   `n_players`, board and own functions and the variables declared *before* it (not zones, not
   derived values). `init` is copied as is (`null` is `None`). With `scope: player` a value that is
   not a list is repeated for every player; a list is taken as the per-player list.
3. **Zones**, in declaration order. `init_x` gives the contents (for `owner: player`: a list of
   lists) and may read every variable. Without `init_x` a zone is empty (per player: one empty list
   each). Every dict in the initial contents becomes an **instance** with its own `uid`.

The site always runs a model with the values written in it. To try another value, change the
constant in the model and check it again.

**Instances.** `create` (and a zone's `init_x`) turn a dict into an instance: a fresh dict with a
unique `uid`. A dict that is model data (from `content` or `constants`) is not copied whole: its
atomic fields (numbers, text, booleans, `null`) are copied and the source dict itself is kept under
the key `origin` (so nested fields stay readable as `x['origin']['effect']`). Writing an attribute of
model data is an error (`... is model data; make an instance with create first`).

## Running the flow

The engine walks the flow tree depth first.

| Node | What happens |
|---|---|
| `sequence` | Children in order |
| `loop` | Before every round: if `until_x` is true, the loop ends. With `over_x`: the list is evaluated at the start of the round; for each value the variable named in `as` is set and all children run. Without `over_x`: the children run once per round. With `once: true` the loop stops after one round. After 10,000 rounds: `ExprError: flow <id>: loop does not end` |
| `branch` | The children of the first case whose `when_x` is true; none true: nothing. A case without `when_x` is `NotExecutable` |
| `subflow` | Clears `results[<ref>]`, then runs flow `flows.<ref>` as a scope (see end conditions with `scope`) |
| `step` | See below |

After a node has finished: if it has `window`, that priority window opens; then, if it has
`end_event`, each listed event is emitted in order, with an end check after each. On a loop both
happen once, after the whole loop, not per round.

`until_x` is only tested at the start of a round, never halfway. `over_x` is not re-evaluated
during a round: a player who is eliminated halfway still gets a turn in that round unless an action
condition prevents it.

### A step

1. Evaluate `actor_x` if present (otherwise there is no actor).
2. The legal actions are those in `do` whose `pre_x` is true, with `actor` in scope (and `self` for
   an ability). An action with `pre` but no `pre_x` is `NotExecutable`.
3. No legal action: the step is skipped. **No end check.**
4. One legal action: it is taken without asking. The player lists it under "automatic", with
   what happened after the previous question.
5. Several: a decision for `actor` at `step <id>` (no actor: `NotExecutable: step <id>: choice
   between actions without actor_x`). With `secret: true` only the actor sees the choice; with
   `visible_to_x` the listed players.
6. Run the action's `cost`, then its `effect`, with local names `{actor}`.
7. **End check.**

## Effects and ops

An effect is a list of ops run top to bottom. Local names (`actor`, names bound by `choose`, `roll`,
`let`, `look`, `search`, `create`) live in one dict per action run: `cost` and `effect` share it,
and so do `if` branches and `repeat` bodies. Four places get a **copy**, so names bound inside are
gone afterwards: a `for_each` body (per item), the impact and the triggers of an `emit`, the effect
of an outcome model, and the effect of an `exec`. Use a variable to remember something across
those boundaries.

Which ops ask something:

| Op | Request |
|---|---|
| `choose` | A decision when there are 2+ options. One option: taken without asking. No options: `ExprError` (except a subset choice, which then binds `[]` when `min` is 0) |
| `search` | A decision among the matches (via `choose`; secret unless `visible_to_x`) |
| `roll` | A chance, uniform over the faces |
| `sample` | A chance with the probabilities `p_x` of the outcome model (must add up to 1 within 1e-6) |
| `draw` from a `shuffled` zone | A chance, uniform over the cards whose position is not known, unless the top card is known or only one unknown card is left |
| `look` at a `shuffled` zone | A chance per looked-at card whose position is not yet known |
| `window` | The decisions of the priority window |
| `emit` | `use` / `skip` decisions for optional triggers, plus whatever the triggers ask |

### Fields: text or expression

For most fields the engine reads `<field>_x` (an expression) if present, else `<field>` if it is a
literal that is not text (number, list, bool). Text without `_x` is a description for people:
the engine stops with `NotExecutable: <place>: field '<field>' is text without <field>_x`. The
exception is `value` on `set` and `let`, where text is a literal string (`value: open` sets
`'open'`). A required field that is missing gives `NotExecutable: <place>: field '<field>' is
missing`.

The engine reads only `pre_x`, `cond_x`, `if_x` (triggers, modifiers, end conditions), `when_x`,
`until_x`, `over_x`, `actor_x`, `who_x`, `returns_x`, `first_x`, `controller_x`, `change_x`,
`active_x`, `filter_x`, `visible_to_x`, `with_x`, `of_x` (exec) and `p_x` as expressions; their
text twins are documentation. A trigger `if`, a branch case `when`, an action `pre`, an `if` op
`cond`, `exec` `of` and `protocol.first` without their `_x` are `NotExecutable`. A loop's `until`
or `over` without `_x`, and an end condition without `if_x`, are **silently ignored**: the loop
runs forever (until an end condition) or not per player, and the end condition never fires.
The validator's gap list ("What the engine still misses") shows all of these.

### Deck order

Index 0 of a zone is the top. `draw` and `look` take from the top. A zone with `shuffled: true` has
an unknown order except for a known top and bottom:

- known on top: after `look`, or after `move`/`create`/`draw` with `position: top`;
- known at the bottom: after `move`/`create`/`draw` with `position: bottom`;
- everything else is unordered; a chance decides which card it is only when it is drawn or looked
  at;
- an item added without `position` goes between the unknown cards (or at the end if no bottom is
  known);
- `shuffle` (with `zone`) forgets the known top and bottom. It does not reorder anything: the
  randomness is in drawing.

Drawing takes the known top first, then a chance over the unknown middle, and only when that is
empty the known bottom. From a zone that is not shuffled, `draw` always takes element 0. `draw`
stops early when the source is empty.

### Events and triggers

`emit` appends the event name to the game's event list and runs:

1. triggers with `timing: when` or `before`;
2. triggers with `timing: instead`: the **first** one that fires replaces the impact;
3. the `impact` ops, if no instead-trigger fired;
4. triggers with `timing: after` (the default).

Within each phase: mandatory triggers first, then optional ones; within those, model triggers in
model order, then triggers on content items. `if_x` is evaluated just before the trigger would
fire, so an earlier trigger can switch off a later one. A content trigger only counts while the
item's `active_x` is true (default: always). An optional trigger asks its controller (`controller_x`,
else the `actor` of the emitting place; neither: `NotExecutable`) `use` or `skip` at
`trigger <id>`. An instead-trigger that fires adds the event `instead:<id>`.

Triggers see the local names of the place that emitted, plus `event` (the event name) and, on a
content item, `self`. A trigger that reads a name another emitting place does not bind fails with
a `NameError` there. Triggers may emit events; more than 50 nested emits is
`ExprError: ... trigger chain too deep`. When the emit is done, temporary modifiers whose `until`
equals this event expire.

A trigger belongs to a content *item* (the card type), not to one instance: with two copies in
play it still fires once per event. Select the instance in `if_x` through the emitter's local
names, for example `"card['id'] == self['id']"`.

### Modifiers

`modified('stat', base, name=value, ...)` starts from `base` and applies every active modifier
for `stat`: the model's `modifiers`, those on content items, and temporary ones from `modify`.
Order: by `layer` (low first, default 0); within a layer, static modifiers in model order and then
temporary ones in the order they were added. For each: skip it if the item's `active_x` or the
modifier's `if_x` is false; else `value` becomes `change_x`. Scope: `value`, the keyword arguments
of `modified()`, `self` on a content item, and for `modify` the local names at the time it ran
(minus `stack` and `self`). A modifier without `change_x` is `NotExecutable`; modifiers that call
`modified()` on each other more than 20 deep raise `ExprError: modifiers for 'x' refer to each
other in a cycle`. A `modify` without `until` lasts until the next campaign `reset`.

### Priority windows

A window opens after a flow node with `window: <name>` (or `window: true`), or through the op
`{op: window, name: <name>}`. Candidates are all actions and content abilities with a `speed` that
the window's `allows` lists (`window: true` or a `window` op without `name`: every speed). The name
must exist in `priority.windows`.

1. No candidates at all: the window closes at once, without a request.
2. `first_x` decides who gets priority first (evaluated once). Without it: the local `actor` (for
   a `window` op), else player 0. `protocol.first` as text without `first_x` is `NotExecutable`.
3. The player with priority gets a decision at `window <name>` between the legal candidates
   (`pre_x` true with `actor` = that player, `window` = the name, `stack` = the open stack) and
   `pass`. A player without a legal candidate passes automatically, without a request.
   Eliminated players are skipped.
4. Playing an action: its `cost` runs at once (place `action <name> (cost)`). Then:
   - `resolution: immediate` (default) or `nested`: the `effect` runs at once, followed by an end
     check. (`nested` is accepted and currently behaves exactly like `immediate`.)
   - `resolution: lifo_stack`: the action goes on the stack (`{action, actor, cancelled}`),
     event `stack:<name>`. The local names of the cost are kept for the effect.
   Then priority goes to the next player (`after_action: next`, default) or stays
   (`after_action: same`).
5. Closing (`closes`): `all_pass_in_succession` (default) closes when every non-eliminated player
   has passed in a row; any action resets the count. `single_pass_each`: a player who passes is out
   for the rest of the window; it closes when all are out.
6. With `lifo_stack`, when the window would close and the stack is not empty, the top item
   resolves instead: a cancelled item is removed with event `cancelled:<name>` and no effect and
   no end check; otherwise event `resolves:<name>`, its effect runs with the player who played it
   as `actor`, the names bound in its cost, and `stack` without the item, followed by an end
   check. Then the pass count is reset and the same first player gets priority again: the value
   `first_x` gave when the window opened, not a new evaluation. The window closes only when
   everyone passes with an empty stack.
7. `cancel` with `target_x` marks stack item `target` (an index into `stack`, bottom first, so
   `len(stack) - 1` is the top) as cancelled at the moment the op runs: in an `effect`, when that
   effect resolves, not when the action is played. Cancelling an item twice is allowed; `None`
   does nothing. Without an open window: `ExprError`. To fix the target when the action is
   played, bind its index in `cost` (cost ops see `stack`); see
   [reference § cancel](https://tabletwin.com/docs/reference/#cancel).

More than 10,000 turns of priority in one window is `ExprError: ... window does not close`.
`pass` is reserved: an action with a `speed` may not be called `pass`.

## End conditions

The engine checks `end` at these moments, and only these:

- after every step that ran an action;
- in a window, after every effect that resolves (with `immediate`, after every played action);
- after each event of an `end_event`.

Not after a cost, not after a cancelled stack item, not when a window closes, not at the start or
end of a loop round, not when a step is skipped.

The conditions are tested in list order; the first one that fires ends the game (or its subflow)
and the rest are not tested. `eliminate` and `continue` never stop the scan:

| Condition | Effect |
|---|---|
| no `if_x` | ignored |
| `scope: <flow>` while that subflow is not running | ignored |
| `scope: <flow>`, result `win`/`loss`/`draw`, `if_x` true | `results['<flow>']` is set to the result, event `<flow>:<result>`, and that subflow ends; the calling flow continues. Checking stops |
| `result: eliminate` | Every player in `who_x` (an index or a list) not yet eliminated is added to `eliminated` (event `eliminated:<p>`). Checking **continues** with the next condition |
| `result: continue` | ignored |
| other result, `if_x` false | next condition |
| `draw` | the game ends: 0 for everyone |
| `score` | the game ends with `returns_x` (one number per player, else `ExprError`) |
| `win` / `loss`, cooperative | the game ends: everyone +1 (win) or −1 (loss) |
| `win` / `loss`, competitive | needs `who_x` (one index): that player gets +1 (win) or −1 (loss), all others the opposite. A list, tuple or set is an `ExprError` (it would match no player) |

A `scope` equal to the `id` of the main flow means the whole game. `scope` only changes the meaning
of `win`, `loss` and `draw`; any other result with a scope is only tested while that subflow runs
and then acts as without scope. If the main flow runs out without an end condition, the game ends
with 0 for everyone and reason `flow finished without an end condition`.

## Engine events

Besides the names you emit, the engine adds these to the event list. The player shows them after
each question: emitted events with the triggers they set off, the others under "messages".

| Event | When |
|---|---|
| `<event>` | every `emit` and every `end_event` |
| `instead:<trigger id>` | an instead-trigger replaced an impact |
| `stack:<action>`, `resolves:<action>`, `cancelled:<action>` | priority window with `lifo_stack` |
| `<flow>:win`, `<flow>:loss`, `<flow>:draw` | an end condition with `scope` ended a subflow |
| `eliminated:<player>` | a player was eliminated |
| `<model>:<outcome id>` | a `sample` from an outcome model |
| `exec:<content id>` | an `exec` ran a content item |
| `destroy:<label>` | a `destroy` |
| `reset:<scope>` | a campaign `reset` (default scope label `scenario`) |

## Information: who sees what

The engine keeps a list of **observations**. Each one has a place, a kind, a visibility and a label.

| Event | Who sees the outcome |
|---|---|
| Decision | Everyone; with `secret` only the chooser; with `visible_to_x` the listed players |
| `roll` | Everyone, or `visible_to_x` |
| `sample` | Everyone |
| `draw` | By destination zone: `public` everyone, `owner` the owner, `hidden` nobody; or `visible_to_x` |
| `look` | The actor (`actor_x` or the local `actor`), or `visible_to_x`; with no actor, everyone |
| `search` | The searcher sees the whole zone; the choice is secret unless `visible_to_x` |
| `reveal` | Everyone, or `visible_to_x`. Zone visibility does not change |

What player `p` knows is everything they observed, in order, with a blank for what they could not
see. In the player, "view as" a player shows the table and the questions as that player saw them:
hidden zones and other players' hands show card backs, and a hidden outcome says who can see it.

A player that looks ahead (see [tactics](https://tabletwin.com/docs/tactics/#look-ahead-search)) must not peek. Before it
tries options, everything that player cannot know is redistributed:

- Drawing from a shuffled zone is already fair: the order is only fixed when a card is drawn.
- Hidden cards that are already on the table (shadow cards dealt face down) must be marked with
  `drawn_from: <source zone>` on their zone. The contents of every such zone that `p` cannot see
  are shuffled together with the unknown middle part of the source zone, and dealt back. Counts and
  identities (`uid`) stay; only which card each one is changes.

```yaml
shadow: {kind: set, visibility: hidden, drawn_from: encounter_deck}
```

A tactic (and a policy) reads the full state, hidden zones included. Not reading what the player
cannot know is up to the author.

## Executability

A model can be partly executable. The validator (and the diagram) measure it in two layers:

- **rules**: every slot the engine needs in flow, actions, triggers, modifiers, the priority
  protocol, outcome models and end conditions has an `_x` or a literal.
- **content**: every content item with behaviour or card text (`effect`, `triggers`, `modifiers`,
  `abilities`, `keywords`, `text`, or `status: missing`) counts as one slot, executable when it has
  behaviour, its own ops and triggers have no gaps, and its status is not `missing`. An item with
  only `text` is a gap. Keywords count as behaviour when every keyword occurs in quotes in some rule
  expression (`'Sentinel' in c.get('keywords', '')`). A `call` to code of kind `content` counts as
  a content gap.

`executable` is the share over both layers together.

The slots in the rules layer:

| Where | Slot is executable when |
|---|---|
| action with `pre` | `pre_x` exists |
| `choose` | `options`, `min`, `max` (those present) have `_x` or a non-text literal |
| `if` | `cond_x` exists |
| `for_each`, `repeat` | `items`, `until` likewise |
| `set`, `let` | `value` has `_x`, a non-text literal, or text that is a plain name (`open`, `face_up`) |
| `add` | `amount` likewise (no text) |
| `set`/`add` with `attr` | `of` likewise |
| `place`, `create`, `move`, `destroy`, `reveal` | `what`, and `at` (place) and `n` (create) when present |
| `draw`, `look` | `n` when present |
| `roll`, `cancel` | `die`, `target` |
| `modify`, modifier | `stat` and `change_x` |
| `call` | never (counted as `code:<kind>`) |
| `say` | never (counted as `language`) |
| trigger with `if` | `if_x` exists |
| `priority.protocol` (if any action has a `speed`) | `first_x` exists, or no `first` |
| outcome | `p_x` exists |
| branch case | `when_x` exists |
| loop with `until` / `over` | `until_x` / `over_x` exists |
| step with several actions, or a `choose` without `actor_x` | `actor_x` on the step (or one action whose `choose` ops all have `actor_x`) |
| end condition `score` | `if_x` and `returns_x` |
| other end condition | `if_x`, plus `who_x` for `eliminate` and for competitive `win`/`loss` without a subflow scope |

Below 100%, the validator lists the missing slots under "What the engine still misses", per kind
(`text`, `code:<kind>`, `language`, and `content:...` for gaps inside content items) with their
place.

### The validator

The [validator page](https://tabletwin.com/validate/) shows **errors** (must be fixed) and
**warnings** (check them: each is either a deliberate choice or a bug). Only a model without errors
gets test games.

| Errors | Warnings |
|---|---|
| `mode` not `competitive`/`cooperative`; `persist` not `campaign` | `drawn_from` source not shuffled; `drawn_from` on a public zone |
| duplicate flow `id`; unknown `kind` or loop `order`; branch without `cases`; subflow or `run` flow missing; window not in `priority.windows`; step action missing; loop `as` not declared in `state.vars` | action never used by a step (unless it has `speed` or `note`); named flow never called |
| unknown op; zone in `from`/`to`/`zone` not declared; `code`, outcome model or agent not declared; `exec` content catalogue missing | `say` without `agent`; `exec` without `content` or `of_x` |
| expression syntax error in any `_x`, derived value or function | board never named in an expression; function never called |
| invalid board; function name taken; function without `x`; bad `params` or `let` | trigger event never emitted (model and content); optional trigger without `controller_x` |
| trigger `timing` unknown; `resolution`/`closes` unknown; action with `speed` named `pass` | `speed` not allowed by any window; fast action always playable in an opened window (no `pre_x`, no `cost`, nothing in its effect an end condition reads) |
| `end` `scope` not a named flow; `end_event` not a name; competitive `win`/`loss` whose `who_x` is obviously a list (list/tuple/set literal, comprehension, `list()`, `sorted()`, `set()`, `tuple()`) | modifier or `modify` stat never read with `modified('stat', ...)` |
| content ability without `id` or with a name that is already an action; dice face type not in `face_types`; outcome model without outcomes or an outcome without `p` | content item `status` not `verified`/`uncertain`/`missing`; keyword not used by any rule; ability without `speed` |
| open question without `question` | open question `status` not `open`/`assumed`/`answered` |
| | key not allowed by the [JSON schema](https://tabletwin.com/models/docs/schema.json) (`unknown key 'x' at actions.y`; `content` is not checked) |

Before that, the page checks that the text is readable YAML ("The YAML cannot be read") and that it
is a model at all, a mapping with a `flow` ("This is not a model").

The validator does **not** check that the names in an expression exist; a wrong name shows up as a
`NameError` when the engine evaluates it. The test games find most of these (under "Where games
stopped"); follow games in the player to find the rest.

### What the validator shows

| Part | Meaning |
|---|---|
| Errors and warnings | As above |
| `executable` | Executability over rules and content together, with a bar |
| rules | Executability of the rules layer |
| content | Executability of the content layer: `pct (ok/total)`, or `-` without content |
| steps | Number of step nodes in the flow |
| ops | Number of ops in actions, triggers, outcome models and content |
| code | Share of `call` ops among all ops |
| open questions | Number of `open_questions` |
| What the engine still misses | The gap list, below 100% |
| Test games | 20 games with random choices and random chance: how many finished, the average number of requests, how they ended (per reason), where games stopped (engine errors), and how many did not end within 5000 requests |
| The flow as a tree | The flow as a text tree: nodes, who acts, actions and their `pre`, windows, end events |

The markers of each step (choice, chance, hidden, window, ...) are shown in the
[diagram](https://tabletwin.com/docs/diagram/#markers).

## Checking and running a model on the site

Everything runs in your browser; nothing is uploaded.

| Tool | What it does |
|---|---|
| [Validator](https://tabletwin.com/validate/) | Paste a model (and optionally a [view](https://tabletwin.com/docs/views/)). Errors, warnings including unknown keys, executability, the gaps, the flow as a tree, and 20 random test games. "Start from" loads one of the site's examples |
| [Player](https://tabletwin.com/play/) | After at least one test game finished, "Open in the player" adds your model to the player as an extra game ("Your model: ..."). The player plays one game question by question: the track of the rules, what was asked, what was chosen, what changed, and the table |
| [Diagram](https://tabletwin.com/diagram/) | "Draw your own model": the rules as a text tree and as a track diagram, with markers per step. See [diagram](https://tabletwin.com/docs/diagram/) |

In the player:

- **Players** chooses who plays: one seat by hand ("You play player 0") with the others played by
  the computer, everyone by hand on one screen, random players, a heuristic from the model's
  `agents` (`policy_x`), or always the first option. The built-in games also offer their tactics
  ([tactics](https://tabletwin.com/docs/tactics/)); tactics for your own model cannot be loaded on the site yet.
- **Game (seed)** fixes the random numbers: the same seed and the same players give the same game.
  "Random game" picks another seed.
- **View as** shows the game as one player saw it.
- The table has two tabs: **Board** (laid out by the view, or automatically without one) and
  **Zones and values**.
- At the end: "End: <reason>" and the payoff per player. An engine error stops the game and shows
  the message.

Playing thousands of games to measure win rates, comparing rule variants or tactics, and changing
constants per run are not yet available on the site.

## Error messages

The engine stops with one of two kinds of error. **NotExecutable**: the model lacks something the
engine needs (the site shows it as "Not executable: ..."). **ExprError**: something went wrong
while running, such as an expression that fails or a loop that never ends (the site shows it as
"Error: ..."). The validator lists them under "Where games stopped" with the number of test games
that stopped there; the player shows the message where the game stopped.

| Message | Meaning | Fix |
|---|---|---|
| `NotExecutable: action X: pre is text without pre_x` | The engine needs an expression | Add `pre_x` |
| `NotExecutable: <place>: field 'amount' is text without amount_x` | A text field where the engine needs a value | Add the `_x` field, or a literal |
| `NotExecutable: step X: choice between actions without actor_x` | Several legal actions, nobody to choose | `actor_x` on the step |
| `NotExecutable: <place>: choose without actor_x and without an actor on the step` | A `choose` with 2+ options and no chooser | `actor_x` on the op or the step |
| `NotExecutable: end condition X: a competitive game needs who_x` | A win without a winner | `who_x` on the condition |
| `ExprError: end condition X: who_x gives a list ([0, 1]); a competitive win needs one player index ...` | `who_x` gave several players; with a list no player would match | One index; several winners: `result: score` with `returns_x` |
| `ExprError: <place>: window does not close` | Players kept playing fast actions (10,000 priority turns in one window) | A `pre_x` or `cost` that limits the action (the validator warns: `always playable in window`) |
| `NotExecutable: trigger X: optional without controller_x or actor` | Nobody to ask `use`/`skip` | `controller_x` on the trigger |
| `NotExecutable: code 'x' → m:f is not implemented` | A `call` to code outside the model | Model it with ops; a model cannot bring its own code |
| `ExprError: error evaluating '...': NameError: name 'x' is not defined` | Name unknown at that place | See the scope table in [expressions](https://tabletwin.com/docs/expressions/); local names do not exist in flow and `end` |
| `ExprError: zone 'x' not declared` / `zone 'x' belongs to a player; write x[player]` | Bad zone path | Declare the zone, or index it |
| `ExprError: variable 'x' not declared` | `set`/`add` on an undeclared variable | Declare it under `state.vars` |
| `ExprError: <place>: move: 'x' is not in <zone>` | The item is not in `from` | Follow the game in the player and look at the zone and the value |
| `ExprError: flow X: loop does not end` | `until_x` never becomes true (10,000 rounds) | Set a flag in an effect |
| `ExprError: end condition X: returns_x gives 1 values, expected 2` | `returns_x` length differs from `num_players` | One number per player |
| `ExprError: outcome model X: probabilities add up to 0.900000, not 1` | `p_x` values do not sum to 1 | Fix the probabilities |
| End reason `flow finished without an end condition` (in the test games and the player) | The main flow ran out | Check `end` and when it is checked |
| `N games did not end within 5000 requests: a loop without an end?` (validator), "cut off" (player) | A loop that never ends | Check the loop conditions and end conditions |

## Limits

| Limit | Value |
|---|---|
| Rounds of one loop, iterations of `repeat`, turns of priority in one window | 10,000 |
| Nested emits (trigger chains) | 50 |
| Nested calls of own functions | 100 |
| Nested `modified()` calls | 20 |
| Requests in one test game of the validator | 5,000 |
