# The Banto language

This is the friendly tour of the Banto DSL — the language you write
in `.banto` files. It assumes you've followed
[Getting started](./getting-started.md) and have a project to play
with.

The canonical, every-rule version is
[`SYNTAX-AND-USAGE.md`](../SYNTAX-AND-USAGE.md) at the repo root.
This guide is the readable version: same content, less reference-doc
density, more examples.

## Contents

1. [Mental model](#1-mental-model)
2. [Project layout](#2-project-layout)
3. [Types](#3-types)
4. [Blocks: the building units](#4-blocks-the-building-units)
5. [State files: `state`, `var`, `cstate`](#5-state-files-state-var-cstate)
6. [Views: `host` and `plyr`](#6-views-host-and-plyr)
7. [Listeners: `lstn`](#7-listeners-lstn)
8. [Components: `cpnt`](#8-components-cpnt)
9. [Datasets: `data`](#9-datasets-data)
10. [Styling: `css` and the `style` type](#10-styling-css-and-the-style-type)
11. [Actions](#11-actions)
12. [The function registries: `func` and `client`](#12-the-function-registries-func-and-client)
13. [Comments](#13-comments)

---

## 1. Mental model

Banto is a small language for building **multiplayer party games**.
One person is the **host** (the screen everyone watches) and several
are **players** (their phones). Your job is to describe what each
viewer should see, and what should happen when they tap things.

Three ideas to keep in mind:

1. **The server is in charge.** Game state lives on the server, not in
    anyone's browser. When a player taps a button, their browser sends
    a token back to the server; the server runs the code; everyone
    affected gets a fresh view.
2. **You write what each viewer sees, not how to update it.** When
    the data changes, the runtime works out which views need to
    refresh and ships them only the data they actually use.
3. **No `if` you, no `for` you in views.** Views are declarative.
    Use multiple ordered blocks for "if/else" (first true wins) and
    `.map` over an array to build a list.

The rest of this guide is the vocabulary for doing that.

---

## 2. Project layout

A Banto project is a flat directory of `.banto` files plus a single
CSS file:

```text
my-game/
├── globals.banto         # required: shared declarations
├── start.banto           # required: the entry state
├── question.banto        # optional: more states, named however you want
├── results.banto
├── styles.banto.css      # optional: CSS classes for your views
└── banto.config.json     # publish settings
```

Two rules to remember:

- Every project has exactly one `globals.banto` and exactly one
   `start.banto`. The latter is where every game starts.
- Every other `.banto` file is a **state**. The file name (without
   `.banto`) is the state's name and must match the `state` block
   declared inside it: `question.banto` contains `state question { … }`.

State files are how you carve a game into "phases" — lobby, ask a
question, show results, final scores. Each state file describes what
the host and players see during that phase, and how the game moves
on.

---

## 3. Types

Banto uses TypeScript-style type expressions. If you've written a
TypeScript interface, this will be familiar:

```text
string                        // primitive
number
boolean
object
any
string[]                      // array of strings
{ name: string, score: number }   // object — note the commas, not semicolons
{ [key: string]: number }     // dictionary / record
{ name: string, nickname?: string }   // optional field
```

The only syntactic surprise is **commas** between fields, where
TypeScript would let you use semicolons.

### Banto-specific primitives

Three extra types appear throughout the language; you'll get to know
them as you read about the blocks that use them:

| Type      | What it represents |
|-----------|--------------------|
| `element` | Something renderable on screen — a primitive, a component, or a list of those. |
| `style`   | A list of CSS class references. Used by component params that take styling. |
| `action`  | A block of side-effects (assignments, registry calls). The thing that runs when a button is clicked. |
| `image`   | A reference to a custom image asset (`asst.<name>`). See [Custom image & audio assets](#custom-image--audio-assets). |
| `audio`   | A reference to a custom audio asset (`asst.<name>`). |

### String-enum types (registry-only)

Some component or function params restrict a string to a fixed set of
values, written as a TypeScript-style union:

```text
"tick" | "ding" | "whoosh"
```

You'll only see these in registry signatures (you can't declare your
own). They behave like normal strings but the compiler will reject any
literal that isn't in the list.

---

## 4. Blocks: the building units

Everything you write at the top level of a `.banto` file is a
**block**. A block has the shape:

```banto
blockKind blockName {
    attribute1: value,
    attribute2: value
}
```

Single-line is fine too:

```banto
var hasStarted { type: boolean, default: false }
```

There are nine block kinds. They split into three groups:

| Where they go        | Block kinds                                  |
|----------------------|----------------------------------------------|
| State files only     | `state`, `host`, `plyr`, `lstn`, `cstate`    |
| `globals.banto` only | `data`, `cpnt`, `func`                        |
| Both                 | `var`                                         |

`func` is dual-purpose: `func.X(...)` calls a built-in registry function,
and you can also **declare** your own `func` blocks in `globals.banto`
(see §12). `client` (the client-only registry) is the one namespace you
write like a block but never declare yourself.

The next sections walk through every block kind.

---

## 5. State files: `state`, `var`, `cstate`

### `state`

Every state file has exactly one `state` block. Its name must match
the file:

```banto
// start.banto
state start {}
```

If a state needs incoming data — usually because another state
transitioned into it with `lstn.next` — declare a `params` type:

```banto
// question.banto
state question {
    params: { questionId: number }
}
```

Inside this state file, anywhere you can use a value, you can read
those params: `state.params.questionId`.

### `var`

A `var` is **server-side mutable state**. It has a type and a
default value:

```banto
var hasStarted {
    type: boolean,
    default: false
}

var players {
    type: { [key: string]: { name: string, score: number } },
    default: {}
}
```

`var`s declared in `globals.banto` live for the whole game. `var`s
declared in a state file live as long as that state is active — when
the game transitions away and back, they reset to `default`.

Read a var with `var.<name>`. You can read vars from `host`, `plyr`,
and `lstn` blocks. Mutate them inside an `action` (covered in
[§11](#11-actions)).

### `cstate` — per-client state

A `cstate` is **per-client, browser-only state**: each viewer has
their own copy, the server never holds it, and it resets to `default`
on every state transition.

```banto
cstate draft {
    type: string,
    default: ""
}
```

Read it with `cstate.<name>` from `host` or `plyr` blocks.

The textbook use case is a text input in progress: the typing-in-progress
text lives in `cstate.draft` so it stays smooth and local; only when the
player presses Enter does it travel to the server. See
[Recipes → text input](./recipes.md#text-input-with-a-cstate-draft).

| | `var` | `cstate` |
|---|---|---|
| Lives where? | Server | Each viewer's browser |
| Lives how long? | Across state transitions | Wiped on every transition |
| Who can write it? | `host`, `plyr`, `lstn` (via actions) | `host`, `plyr` only |
| Who can read it? | Anyone server-side | Only the client that owns it |

---

## 6. Views: `host` and `plyr`

`host` and `plyr` blocks describe what's on screen during a state.
They look identical:

```banto
host default {
    condition: true,
    child: cpnt.HostScreen({ … })
}

plyr default {
    condition: true,
    child: cpnt.PlayerScreen({ … })
}
```

Two attributes:

- **`condition`** — a boolean expression. If true, this block "wins".
- **`child`** — what to render. An `element` value: a string, a
   component call (`cpnt.X(…)`), or a list of those.

You can declare multiple `host` blocks and multiple `plyr` blocks per
state. They're evaluated **top to bottom**; the first one whose
`condition` is true is the one rendered. Like a chain of `else if`s.

The last block of each kind **must** be named `default` with
`condition: true`, so something always renders:

```banto
plyr hasAnswered {
    condition: var.playerData.includes(player.sessionId),
    child: cpnt.WaitingScreen({ … })
}

plyr default {
    condition: true,
    child: cpnt.QuestionScreen({ … })
}
```

### The `player` value

Inside a `plyr` block you have access to a special value, `player`,
representing **the specific viewer this render is for**:

```text
player.sessionId   // string, unique per viewer
player.name        // string, the player's display name
player.score       // number, their current score
```

Each player gets their own evaluation of the `plyr` blocks, with
`player` bound to their record. The host gets one evaluation of the
`host` blocks, with no `player` value (don't reference it there).

### Building the `child`

`child` is whatever you want on screen. The easy cases:

```banto
child: "Hello!"                                    // a string
child: var.message                                 // a var that holds a string
child: cpnt.button({ child: "Tap me", onClick: () => {}, class: [] })
child: [cpnt.container({…}), "footer", cpnt.button({…})]   // a list of elements
```

For larger UIs, lean on the component registry — see
[§8 Components](#8-components-cpnt) and the [Registry reference](./registry-reference.md).

---

## 7. Listeners: `lstn`

A `lstn` block watches the game state and **fires when its condition
becomes true**. It does two things:

- Run an `actions` block (mutate vars, call `func.X` side effects).
- Optionally, **transition to another state** via `next`.

```banto
lstn hasStarted {
    condition: var.started,
    actions: {},
    next: {
        state: "question",
        inputs: { questionId: 0 }
    }
}
```

| Attribute   | Required? | What it is |
|-------------|-----------|------------|
| `condition` | yes       | A boolean. The listener fires when this becomes true. |
| `actions`   | no        | An `action` block — server-side side effects (assignments, registry calls). |
| `next`      | no        | `{ state: "<otherState>", inputs?: <params> }`. The state name must exist; `inputs` must match that state's `params` type. |

Like `host` and `plyr`, multiple `lstn` blocks are evaluated in
declared order — the first one whose condition is true wins. They're
the workhorse for "when X happens, do Y and move on".

A real example from the trivia starter:

```banto
lstn onDone {
    condition: var.skipped
        || (Object.keys(var.players).length > 0
            && Object.keys(var.players).length == Object.keys(var.playerData).length)
        || (func.currentTime() - state.params.initTime) >= var.questionTime,
    actions: {
        // ... grade the answers, update scores ...
    },
    next: { state: "results" }
}
```

---

## 8. Components: `cpnt`

A **component** is a renderable thing — a button, a container, a
custom screen layout. Components come from two places:

1. **The component registry** — components shipped with Banto.
    Reference them with `cpnt.<Name>(args)`.
2. **Your own globals** — declare a custom component with a `cpnt`
    block in `globals.banto`.

### Calling a registry component

Pass an object whose fields match the component's params:

```banto
cpnt.button({
    child: "Start the game",
    class: [css.big-button],
    onClick: () => { var.started = true; }
})
```

The full list of registry components is in the
[Registry reference](./registry-reference.md). The most common ones
to get started:

- `cpnt.HostLobby` — default lobby screen with a kick list.
- `cpnt.PlayerScreen` — wrapper for player views, shows score + room code.
- `cpnt.button` — a button.
- `cpnt.container` — a styleable wrapper.
- `cpnt.textInput` — a text input bound to a `cstate`.
- `cpnt.container` — a styleable wrapper; lay out its children with CSS
   flex classes.
- `cpnt.image` / `cpnt.audio` — render your own uploaded media (see below).

### Declaring your own component

Declare it in `globals.banto`:

```banto
cpnt MyButton {
    params: string,
    child: cpnt.button({
        child: params,
        class: [css.my-button],
        onClick: () => {}
    })
}
```

Two attributes:

- `params` (optional) — the input type. Inside the body, read it as
   `params` (or `params.foo` for an object).
- `child` (required) — an `element` value. Same rules as in
   `host`/`plyr` blocks.

Use it like any registry component:

```banto
cpnt.MyButton("Hello")
```

A component with object params:

```banto
cpnt myCard {
    params: { title: string, body: string },
    child: cpnt.container({
        class: [css.card],
        child: [params.title, params.body]
    })
}
```

> **Limitation, on purpose.** A custom component's body can read its
> own `params` and use other components, but it **cannot** read
> `var`, `state`, `data`, or `player`. Components are pure with
> respect to game state — pass values in via `params` instead.

### Lists of elements

The `element` type accepts a single element or a list. Both are
valid:

```banto
child: cpnt.button(...)                        // single
child: [cpnt.a(...), cpnt.b(...), "footer"]    // list, rendered in order
child: []                                      // empty list
```

That makes `[…].map(item => cpnt.X(item))` the natural way to render
a dynamic list of components.

### Custom image & audio assets

Games can ship their own images and audio. The flow has two halves —
an upload step in the CLI and an `asst.<name>` reference in your
`.banto` files.

**1. Upload.** Put media files in an `assets/` directory at your
project root, give them titles in `banto.config.json`, then push:

```text
my-game/
├── assets/
│   ├── logo.png
│   └── victory-fanfare.mp3
└── banto.config.json
```

```jsonc
// banto.config.json
{
    "assets": {
        "logo": { "title": "Game logo" },
        "victory-fanfare": { "title": "Victory fanfare" }
    }
}
```

```bash
banto assets push
```

**2. Reference.** `asst.<name>` is a namespace (like `css.<class>`);
`<name>` is the file's base name (no extension). It resolves at build
time to the asset's id — there's no runtime `asst` lookup.

```banto
// an image
cpnt.image({ src: asst.logo, alt: "Our logo", fit: "contain", class: [css.badge] })

// background audio
cpnt.audio({ src: asst.victory-fanfare, autoplay: true })

// a one-shot cue fired from an action
onClick: () => { client.playSound(asst.victory-fanfare); }
```

`asst.<name>` carries a **kind** — `image` or `audio`. The compiler
checks it: an `image` asset in an `audio` slot (or vice versa) is a
type error. A plain string is also accepted anywhere an `image`/`audio`
is expected (a raw asset id or URL escape hatch), but it's resolved at
runtime and renders nothing if it can't load.

> **Moderation.** Freshly-pushed assets are **pending** — usable in
> your own games immediately, but only listable to others once an admin
> approves them. The editor lists your asset names for completion even
> before you push; the build is what enforces that a referenced asset
> actually exists. See the [CLI reference](./cli.md#banto-assets) for
> `banto assets ls` / `rm` / `catalog`.

---

## 9. Datasets: `data`

A `data` block declares a **dataset that the host picks at game
start**. The most common case: a question list for a trivia game.

`data` blocks are declared in `globals.banto` only. Exactly **three
names** are allowed: `questionSet`, `promptSet`, or `dynamic`.

```banto
data questionSet
data promptSet
data dynamic
```

You can have at most one `data` block in a project. The shape is
fixed by Banto:

| Block                  | Type                                                                       |
|------------------------|----------------------------------------------------------------------------|
| `data questionSet`     | `{ prompt: string, options: string[], correctOptions: number[] }[]`        |
| `data promptSet`       | `string[]`                                                                 |
| `data dynamic`         | `(string \| number \| (string \| number \| (string \| number)[])[])[]`     |

Read it like a var: `data.questionSet`, `data.promptSet`, `data.dynamic`.

The `dynamic` block is the general-purpose escape hatch: at any position,
of any list, at any depth, a value is a string, a number, or another list.
Lists may nest to any depth (type `Dynamic[]` where
`Dynamic = string | number | Dynamic[]`).

When you publish, `banto.config.json` determines what real data
source the host sees pre-selected — see the
[CLI reference's `banto.config.json` section](./cli.md#bantoconfigjson-per-project).

---

## 10. Styling: `css` and the `style` type

CSS classes live in `styles.banto.css`. Restrictions:

- **Class selectors only** (`.my-class`) and pseudo-classes
   (`.my-class:hover`). No element selectors, no `@import`,
   no `@font-face`, no `@keyframes`.
- The compiler will reject anything outside that subset.

```css
.host-column {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.option-button:hover {
    transform: translateY(-2px);
}
```

Reference a class from a `.banto` file as `css.<class-name>`. A
single `css.X` is **a single class reference**, not a `style`.

The `style` type is a **list** of class references:

```banto
class: [css.host-column]            // one class
class: [css.host-column, css.with-shadow]   // multiple
class: []                                   // none
```

Always wrap in `[ ]` — even for one class. A bare `css.X` in a
`style` slot is a type error.

### Conditional classes with `&&`

`&&` and `||` follow JavaScript semantics — they return one of their
operands. The runtime drops non-string entries from `style` arrays
before joining, so this just works:

```banto
class: [
    css.option-button,
    var.isCorrect && css.correct,
    var.isWrong && css.wrong,
    player.sessionId == state.params.activePlayer && css.is-you
]
```

When a guard is false, that array entry becomes `false`, gets
dropped, and the final class string only contains the survivors.

---

## 11. Actions

An **action** is a block of server-side side effects: changing var
values, calling registry functions that have side effects. Anywhere
the type `action` is expected, you can supply one.

### Action statements

Inside an action, statements end with a **semicolon**. The available
statements:

- **Assignment**: `var.score = 0;` and the compound forms `+= -= *= /= %=`.
- **Mutating method calls**: `var.players.push(p);`.
- **Function calls** that return `action`: `func.kickPlayer(sId);`.
- **Iterators** for side effects: `var.players.forEach((p) => { … });`.
- **`if` / `else if` / `else`** — same syntax as JavaScript.
- **`return;`** inside a `.forEach` body — works like `continue`,
   skipping to the next iteration.

```banto
actions: {
    var.players[player.sessionId].score += 100;
    if (var.players[player.sessionId].score >= 1000) {
        var.winners.push(player.sessionId);
    }
    Object.entries(var.players).forEach(([sessionId, p]) => {
        if (p.score < 0) { return; }
        var.scoreBoard[sessionId] = p.score;
    });
}
```

> **No `for` or `while` loops.** All looping goes through array methods —
> `.map` (when you want a new value back), `.forEach` (when you just
> want side effects).

### Action values: `() => { … }`

Anywhere a slot is typed `action`, you can write an arrow function
whose body is an action block. This is how event handlers are wired
up:

```banto
cpnt.button({
    child: "Start",
    onClick: () => {
        var.hasStarted = true;
    }
})
```

The arrow runs **on the server, when the user emits the event**. From
inside it, you have full access to `var`, `state`, `data`, registry
functions — the same things `lstn` actions can touch.

### Events that carry data: `action(T)`

Some events deliver a value (a text input sends what was typed; a
slider sends its new position). Those slots are typed `action(T)` —
declare an arrow with one parameter to receive the value:

```banto
cpnt.textInput({
    value: cstate.draft,
    onChange: (next) => { cstate.draft = next; },
    onSubmit: (text) => {
        var.messages.push(text);
        cstate.draft = "";
    }
})
```

Rules:

- A plain `action` slot accepts only zero-arg arrows.
- An `action(T)` slot accepts zero- or one-arg arrows.
- Two or more parameters is always an error.

### Closures: what's captured vs. what's live

When you write an arrow inside a `.map` (for example, building a
button per player), the arrow's body can refer to two kinds of names:

| What | When is it read? |
|---|---|
| Iteration locals (`item`, `i`, destructured names) and `player` | **Captured by value at render time.** The action remembers which iteration produced it. |
| `var.*`, `state.params`, `data.*`, registry calls | **Read live at click time.** The action sees the latest server state. |

This means the player-mapping pattern below works correctly:

```banto
items: data.questionSet[state.params.questionIndex].options.map((option, i) =>
    cpnt.button({
        child: option,
        class: [],
        onClick: () => {
            // `i` is captured: this button's onClick always uses ITS i.
            // `player.sessionId` is captured: it's this player's id.
            // `var.playerData` is live: we read the latest snapshot.
            var.playerData[player.sessionId] = {
                selection: i,
                selectionTime: func.currentTime()
            };
        }
    })
)
```

There's no "last-i" footgun here. Each button captures its own `i`.

---

## 12. The function registries: `func` and `client`

Banto ships two read-only registries of utility functions.

### `func.X` — server-side

Anything under `func.` runs on the server, in the same place as your
action bodies. Grouped:

- **Time**: `func.currentTime()`.
- **RNG**: `func.randomInt({min, max})`, `func.randomFloat({min, max})`, `func.shuffle(arr)`, `func.pickRandom(arr)`, `func.pickUnused({total, used})`.
- **Math**: `func.floor`, `func.ceil`, `func.round`, `func.abs`, `func.clamp`, `func.sum`, `func.avg`, `func.minOf`, `func.maxOf`.
- **Collections**: `func.unique`, `func.first`, `func.last`, `func.size`, `func.isEmpty`, `func.hasKey`, `func.merge`, `func.removeKey`, `func.mapValues`, `func.groupBy`, `func.range`, `func.equals`.
- **Game control** (`returns: action`): `func.kickPlayer(sessionId)`.
- **UX side effects**: `func.notify({to, message, type?})`, `func.log({message, level?})`.

A function whose return type is `action` can appear as a **statement** in
an action block; the others are just expressions.

The full catalog is in the [Registry reference](./registry-reference.md).

### Declaring your own functions

Beyond the built-in registry you can define **reusable functions** in
`globals.banto` and call them the same way — `func.<name>(...)`. They're
pure helpers: they take a single argument, compute, and `return` a value
for an action body (or a view) to use.

```banto
func scoreFor {
    params: { correct: boolean, streak: number },
    returnType: number,
    action: {
        let base = params.correct ? 100 : 0;
        return base + params.streak * 10;
    }
}
```

```banto
// In a listener or action body:
var.players[player.sessionId].score += func.scoreFor({ correct: true, streak: var.streak });
```

Declaration shape:

- `params` — the type of the single argument, referenced inside the body
  as `params.<name>` (or bare `params` when the type isn't an object).
  Omit for a no-argument function.
- `returnType` — the type returned. **Optional**: omit it for a `void`
  helper that just computes side-effect-free intermediate values. When
  set, every path through the body must `return` a value of this type.
- `action` — the body. It behaves like an action block: `let` bindings
  are allowed, and it may call other registry functions (`func.X`).

The rules that keep them predictable:

- **Params are read-only.** You can't assign to `params.x` or call a
  mutating method on it — build a new value and `return` it.
- **Pure over `params`.** A `func` body can't read game state
  (`var`, `cstate`, `state`, `data`, `player`) or call `client.X`. Pass
  whatever it needs in as an argument. This is what lets the same
  function run on the server *and* the browser.
- **No recursion.** A function may not call itself, directly or through
  another function.

Functions run wherever they're needed: server-side in action bodies and
listeners, and client-side when referenced from a view or a client-side
cstate write. A function reached only from the client is shipped to the
browser automatically.

### `client.X` — browser-side

`client.X(...)` runs on the browser of the player who triggered the
action — useful for UI sound effects and other purely-presentational
things the server has no analogue for.

```banto
cpnt.button({
    child: "Buzz in!",
    class: [],
    onClick: () => {
        client.playSound("ding");
        var.buzzedPlayers.push(player.sessionId);
    }
})
```

Two restrictions:

- `client.X(...)` may **only** appear inside an action arrow body.
- `client.X(...)` may **not** appear inside an iterator body
   (`.map`, `.forEach`).

The current registry has one function — `client.playSound(sound)` —
where `sound` is one of the built-in cue names (`"tick"`, `"ding"`,
`"whoosh"`, `"chching"`, `"applause"`, `"countdown"`, `"anticipation"`,
`"waiting"`) **or** an `audio` asset (`asst.<name>`) to play one of your
own uploaded clips.

---

## 13. Comments

Single-line `//` comments only — same as JavaScript. Multi-line
`/* … */` comments are not allowed.

```banto
// This var holds whether the host has hit Start yet.
var hasStarted { type: boolean, default: false }
```

---

## Where to go next

- **[Recipes](./recipes.md)** — the patterns you'll keep reaching
   for: text inputs, kicks, scoring, conditional UI, state
   transitions.
- **[Registry reference](./registry-reference.md)** — every component
   and function Banto ships with.
- **[`SYNTAX-AND-USAGE.md`](../SYNTAX-AND-USAGE.md)** — the
   exhaustive language spec. Every rule, every edge case.
- **`examples/trivia/`** in this repo — a complete working game.
   Read it once you've absorbed this guide; everything in it should
   be familiar.