Documentation

Quickstart

First, a compact syntax tour. Then Forest Fire and Ants show how the language reads when you build a model from patches, pets, lifecycle sections, and display styles.

compact model skeleton

world:  x: -25..25  y: -18..18  topology: box params:  density: number = slider(62, 0..100) monitors:  active: number = count patches where active patches:  state: inactive | active = inactive setup:  patches:    state := if (random-float(100) < density) then active else inactive step staged:  patches:    where active:      state := inactive

Syntax

Declarations, updates, and display

params: are editable, monitors: are readonly, memory: is model-owned state, and patches: / pets: define agent fields.

patches where (...): updates every matching patch. Block form lets one query run several actions with local let values and nested branches.

styles.petstyle maps states and fields to visuals. It is intentionally separate from the logic so a model can be restyled without changing its rules.

three syntax landmarks

params:  density: number = slider(62, 0..100) patches:  where (tree and py > 0):    heat := heat + 1    state := burning
patches:  states:    burning:      color: "#f97316"

Example 1

Forest Fire

Forest Fire is patch-only: the world is a grid, each patch owns a state, setup seeds trees, a staged step spreads fire, and styles map states to colors.

World, controls, monitors, and patch fields

This opening section gives the model its rectangular world, one editable density control, three derived counts, and the patch state enum. Because the field is named state, the enum values are also display states.

world:  x: -25..25  y: -18..18  topology: box params:  density: number = slider(62, 0..100) monitors:  burned-trees: number = count patches where burned  burning-trees: number = count patches where burning  living-trees: number = count patches where tree patches:  state: empty | tree | burning | burned = empty

Setup, staged spread, and stop condition

Setup randomly seeds trees and lights the left edge. The staged step lets every burning tree become burned while neighboring trees ignite from the previous state of the same phase.

setup:  patches:    state := if (random-float(100) < density) then tree else empty     where (tree and px = min-x):      state := burning step staged:  patches:    where burning:      state := burned     where (tree and any neighbors4 where burning):      state := burning stop when (count patches where burning = 0)

Display styles

The style sidecar maps patch states to colors. The transition on burned interpolates into that state over a few ticks.

The ordered tooltip list chooses exactly which live fields appear on hover and how they are formatted. Untouched background ground stays silent.

patches:  default:    color: "#efe7d9"  tooltip:    - field: state      label: State      format: title  states:    empty:      color: "#efe7d9"    tree:      color: "#2f7d32"    burning:      color: "#f97316"    burned:      color: "#29180f"      transition: 3

Example 2

Ants

Ants adds moving agents to the patch grid. Patches hold food, nest, and chemical trail fields; the ants breed senses those fields and chooses movement actions each tick.

Parameters, patch fields, and the ants breed

The declarations introduce population and trail controls, one model-owned release counter, patch fields for the environment, and a breed with ant-owned state.

params:  population: number = slider(125, 0..200)  diffusion-rate: number = slider(50, 0..99)  evaporation-rate: number = slider(10, 0..99) memory:  released-count: number = 0 patches:  state: empty | nest-patch | food = empty  chemical: number = 0  nest: boolean = false  nest-scent: number = 0  food-source-number: number = 0 pets:  ants:    state: searching | carrying = searching    released: boolean = false

Setup creates the environment and population

Patch setup computes nest and food regions. The create ants population statement creates pets in the named breed; breed updates initialize their position, heading, visibility, and state.

setup:  released-count := 0  patches:    state := empty    chemical := 0    food-source-number := 0    nest := false    nest-scent := 200 - sqrt(pow(px, 2) + pow(py, 2))     where (patch-in-radius(5, 0, 0)):      nest := true      state := nest-patch     where (patch-in-radius(5, 0.6 * max-x, 0)):      food-source-number := 1      state := food   create ants population  ants:    set-position(0, 0)    heading := random(360)    state := searching    released := false    hidden := true

Sensing and branching

Inside a breed block, each released ant reads nearby patch values and follows patch gradients. Nested where blocks choose the behavior for this tick.

step:  released-count := if (released-count + 1 < population) then released-count + 1 else population   ants:    released := id < released-count    hidden := not released     where released:      where searching:        where (patch.state = food):          state := carrying          patch.state := empty          turn(180)         otherwise:          follow-patch-gradient(chemical, 0.05, 2)          forward(1)       otherwise:        where (patch.nest):          state := searching          turn(180)         otherwise:          patch.chemical := patch.chemical + 60          follow-patch-gradient(nest-scent)          forward(1)

Commands, chemical trails, and diffusion

Commands mutate the current pet or world. The ant turns, moves, drops chemical on its patch, and the observer-level diffuse command spreads the patch chemical field.

ants:  where carrying:    patch.chemical := patch.chemical + 60    follow-patch-gradient(nest-scent)    forward(1)   where (not can-move(1)):    turn(180) diffuse(chemical, diffusion-rate / 100)patches:  chemical := chemical * (100 - evaporation-rate) / 100