On this page

First model: a spreading fire

Manual contents | Neighborhood reference | Tools and utilities | Project settings | Next: Wandering turtles

This tutorial assumes you can edit a small NetLogo model: you understand variables, if, and a rule repeated each tick. You do not need previous experience with JavaScript classes or the NL API.

We will build a deliberately simple model, not a realistic fire prediction:

There are no moving turtles yet. The patches themselves hold the model state.

1. Find the three pieces

Piece What you put there Rough NetLogo comparison
Project World size, palette, inputs, displays, execution options Interface and world settings
Main Create populations; count and publish results Observer procedures
Patch The rule each patch follows Code inside ask patches [...]

class FirePatch extends NLPatch means "make our patch type using the supplied patch behavior." this means the object currently executing the code. this.main is that patch's route to the model-wide Main object. super calls the supplied base behavior rather than replacing it.

In Studio:

  1. Download a backup of your current app if you want to keep it. New App replaces the current workspace.
  2. Choose New App, enter Fire, select Cellular Automaton, and choose OK. The editor opens; Turtle is not needed for this pattern.
  3. Replace the Project, Patch, and Main tab contents with the corresponding definitions below. Do not paste all three into one tab.
  4. Choose Save All, then Launch. Allow the app window to open if asked. The editor's Test command checks syntax only; use the observations below to check the model itself.

The code uses the module layout produced for an NL patch/agent app. Main class Fire and Patch class FirePatch match the app name. Keep the generated app shell/import wiring. Main and Patch below have default exports as Studio-generated classes do. Project is configuration, not a class. Existing standalone apps sometimes use named exports instead; preserve their entry-point imports if adapting one.

The shipped templates are still legacy. Use the complete definitions here rather than keeping template constructor calls to init() or assuming the template already has the aligned flag. If the editor retains a Turtle source, it is unused: remove the Project agent section and Main's initAgents().

2. Configure the world

Use this Project definition:

const Project = {
  version: 0.2,
  name: "Fire",
  title: "A spreading fire",
  lifecycle: "aligned",
  frameWidth: 1170,
  frameHeight: 1000,
  connect: true,
  visitMode: "C",
  dt: 1,
  cell: {
    cellSize: 12,
    rowDim: 21,
    colDim: 21,
    boundary: "Edge",
    palette: ["#228b22", "#ff4500", "#303030"],
    start: [10, 10, 1]
  },
  displays: [
    {
      code: "BURNING",
      site: "display_01",
      type: "linechart",
      dom: [0, 25],
      ran: [0, 100],
      width: 460,
      height: 275,
      legend: ["Burning patches"],
      palette: ["#ff4500"]
    }
  ]
};

3. Give each patch its rule

Use this Patch source:

import {NLPatch} from "@nwk/api/nlpatch.js";
import {Enum} from "@nwk/api/tools.js";

const FireState = Enum(
  {name: "Tree"},
  {name: "Burning"},
  {name: "Burnt"}
);

class FirePatch extends NLPatch {
  setup() {
    this.main.visitAlways(this);
  }

  patchStep() {
    super.patchStep();
    let nextState = this.color;

    if (this.color === FireState.Tree.ord) {
      let burningNearby = false;
      this.neighborVisit(neighbor => {
        if (neighbor.color === FireState.Burning.ord) {
          burningNearby = true;
        }
      });
      if (burningNearby) nextState = FireState.Burning.ord;
    } else if (this.color === FireState.Burning.ord) {
      nextState = FireState.Burnt.ord;
    }

    this.nextColor = nextState;
  }
}

export default FirePatch;

Read it a little at a time

Enum gives our states names and numbers: Tree has .ord 0, Burning 1, and Burnt 2, matching the palette. It is not a NetLogo breed or a population. See Enum for more examples.

setup() runs when the world is ready and again after Reset. visitAlways(this) puts this patch in the set that gets visited each tick. Selecting "C" alone does not activate every patch. Repeating this registration is safe because the runtime uses a Set: the same patch is not added twice. For this example, do not also enable visitAll.

neighbor => { ... } is a small function (a callback). neighborVisit calls it once for each neighboring position. In this square Edge world it sees up to eight neighboring patches, without the center patch. This is similar to examining NetLogo neighbors.

this.color reads the patch's state now. this.nextColor = ... proposes its state for the end of the tick. All patches therefore read the same old world, and the fire travels only one neighborhood outward per tick. The runtime commits the proposals automatically; do not call shift() yourself.

Do not replace the last line with this.color = nextState: that would change state immediately and replace the Reset baseline. Likewise, currentColor would change state immediately, making spread depend on visit order.

super.patchStep() preserves the base step's behavior, including any public color updater assigned later. We have not assigned an updater here.

4. Create the patches and count the fire

Use this Main source:

import {NLSimMain} from "@nwk/api/nlsimmain.js";

class Fire extends NLSimMain {
  constructor(Patch) {
    super();
    this.Patch = Patch;
  }

  initCells() {
    this.initializePatches(this.Patch);
  }

  setup() {
    this.publishBurning();
  }

  update() {
    this.publishBurning();
  }

  publishBurning() {
    const patches = this.patches.toList();
    const burning = patches.filter(patch => patch.color === 1).length;
    this.show("BURNING", burning);
  }
}

export default Fire;

The runtime supplies the Patch class to Main's constructor. We save it as this.Patch and create the world in initCells(), not in the constructor. initializePatches creates the patches and applies Project's initial colors.

toList() gives us an ordinary JavaScript array of patches. filter(patch => patch.color === 1) keeps only burning patches; .length counts them. This is like count patches with [pcolor = ...]. show("BURNING", burning) publishes a number to the configured chart.

Main's setup() publishes the initial count after patch setup is finished. update() publishes again after each tick has committed its state. We do not need to override init() or reset() for this model.

5. Step, run, and reset

Launch the saved app and wait for connection (or use Connect if automatic connection is disabled). Before clicking Run, check these milestones:

Time What to see Burning Burnt
0 One orange patch in a green world 1 0
1 Dark center, orange ring around it 8 1
2 A larger orange square ring 16 9
10 The orange ring reaches the world's edges 80 361
11 The whole world is dark 0 441

Use Step first. Then try Run and pause. Reset should restore time zero and the one burning center patch without reloading the page. Step twice, reset again, and check that the first two counts are still 8 and 16. This simple model has no randomness, so these checks should be exact.

If nothing changes, check visitMode: "C", Main's initCells(), and the patch setup() registration. If the whole forest burns in one step, check that the rule writes nextColor rather than color or currentColor.

6. Understand the lifecycle without learning the plumbing

Place Job in an aligned model This tutorial
Constructor Allocate/save stable objects; no manual init/setup calls Main saves Patch class
init() Initialize once after defaults/context exist No custom work needed
setup() Work requiring the completed world; repeat after reset Activate patches; publish count
reset() Restore the existing object's local state Inherited patch reset restores color

The browser Reset command coordinates the whole world. If writing code that requests the same operation, use main.resetWorld(), not main.reset(). Main and patches survive reset; turtles, when present, are recreated.

NetLogo's usual setup procedure often does several of these jobs together. Here the runtime owns creation and reset order. You supply hooks for your work, not calls to replay that order yourself.

7. Small experiments

Make one change at a time and predict the result before running:

  1. Two fires. Set start: [7, 10, 1, 13, 10, 1]. Where do the fronts meet? Reset restores both seeds; the table above no longer applies.
  2. Four neighbors. Replace the neighborVisit(...) call with this.main.nhood4(this).forEach(...), retaining the same callback body. The front becomes diamond-shaped. With "Edge" it stops at the boundaries; with "Wrap" it crosses to the opposite side. See the neighborhood reference.
  3. Uncertain spread. Import NLRoot from @nwk/api/nlroot.js in Patch and change the ignition line to if (burningNearby && NLRoot.random() < 0.4) nextState = FireState.Burning.ord;. This is one ignition chance per exposed tree per tick, not per burning neighbor. Some trees survive; repeated runs may differ.

Continue with Wandering turtles for movement, live sliders, and agent counts. Read Managing model state when a variable needs more than a color, and the aligned lifecycle contract when adding turtles, births, or relationships between objects.