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.
Tour
The shape of a MathPets file
A model declares the world, editable parameters, derived monitors, patch fields, optional pet breeds, setup statements, and step statements. Assignments use :=. Predicates live in where clauses.
In a model's live view, use the transport to play or step the simulation. Leave Inspect on to preview a patch or pet, click to pin its current fields, and press Escape to dismiss the tooltip.
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 := inactiveSyntax
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 := burningpatches: states: burning: color: "#f97316"Example 1
Forest Fire
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 = emptySetup, 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: 3Example 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 = falseSetup 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 := trueSensing 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
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.