On this page

Patch neighborhoods and local agent queries

Manual contents | First-model tutorial | Programming patches | NLPatch reference | Tools and utilities

A patch neighborhood is a collection of patches near a patch. An agent query finds turtles occupying those patches. These are different operations: neighbors returns patches, while nhoodPatches returns turtles. None of these queries steps objects or commits pending state. The annulus helper can optionally schedule returned patches for later visitation. The examples assume the guide's aligned lifecycle.

Choosing an operation

Question Start with Result
Which patches immediately surround this patch? patch.neighbors() or patch.ring(1) Set of patches
Do something for each surrounding position patch.neighborVisit(visitor) Callback visits; no result collection
Which patches are on the outside of a larger square? patch.ring(distance) Set of patches
Which patches are within a square/hex neighborhood, including the center? patch.block(distance) Set of patches
The same, excluding the center? patch.blockX(distance) Set of patches
Which of the four square-grid side neighbors exist? main.nhood4(patch) Array of patches; respects Edge/Wrap
Give the nearest ring as an array main.neighbors(patch) Array; up to eight square or six hex patches
Which other turtles occupy these patches? turtle.nhoodPatches(patches) AgentSet of turtles
How many other turtles occupy these patches? turtle.nhoodCount(predicate, patches) Number; matches the corresponding nhoodPatches size

Grid ring/block distances must be nonnegative safe integers; negative, fractional, nonnumeric, nonfinite, and unsafe-integer distances throw RangeError. The collection methods default to distance 1 when omitted. ring(0) and blockX(0) are empty; block(0) contains just the center. Euclidean radius helpers, described below, also accept fractional radii.

Patch methods

Source: NLPatch; hex overrides: HexPatch. These are instance methods: call this.ring(1) inside a patch rule.

neighbors() and ring(dist = 1)

Returns a new JavaScript Set of patch objects on the perimeter at distance dist. On a square grid the distance is in square rings, not Euclidean circles: an interior distance-1 ring has eight patches, distance 2 has sixteen. The caller is excluded, even when wrapping maps an offset back to it. neighbors() is the topology-neutral shorthand for ring(1).

const neighbors = this.ring(1);
let burning = 0;
for (const patch of neighbors) {
  if (patch.color === 1) burning++;
}

In an ordinary square world this resembles NetLogo neighbors, not neighbors4. On a hex grid the inherited wrapper calls HexPatch.ringVisit, so a full first ring has six patches and a second ring twelve. Use HexPatch-derived patches for hex neighborhoods; configuration alone does not replace an NLPatch subclass's neighborhood methods.

neighborVisit(visitor, dist = 1) and ringVisit(dist, visitor)

These call visitor(patch) for each perimeter position and return undefined. neighborVisit(fn, d) delegates to ringVisit(d, fn); note the different argument order. ringVisit requires its distance argument.

let total = 0;
this.neighborVisit(patch => { total += patch.color; });

The callback is not bound to the neighboring patch. Use the callback argument to read it; arrow functions retain the enclosing this. Visitors call your function once per distinct patch, just like the Set forms; they maintain a visited Set internally. Returning false does not stop visitation.

block(dist = 1), blockX(dist = 1), blockVisit(dist, visitor, notMe)

These cover the interior as well as the rings, rather than only the perimeter. The collection wrappers return new Sets; the visitor returns undefined.

Operation Square NLPatch HexPatch
block(d) Center plus all square offsets through distance d Center plus hex rings 1 through d
blockX(d) Same square, excluding the caller Same hex region, excluding the caller
blockVisit(d, fn, false) Same unique patches as block Same unique patches as block
blockVisit(d, fn, true) Same unique patches as blockX Same unique patches as blockX

notMe defaults to false. In a sufficiently large square interior block(1) has nine patches, blockX(1) eight, and blockX(2) twenty-four. For example:

const region = this.block(2);

Center inclusion is the same on both grid types, including tiny wrapping worlds.

Boundary behavior and duplicates

With cell boundary "Edge", patch ring/block visitors skip positions outside the world. A square corner has three distance-1 ring neighbors. With "Wrap", out-of-range positions map to the opposite edge.

Wrapping uses modulo arithmetic at every distance, not just at the nearest edge. Different offsets may reach the same patch, but all neighborhood collection and visitor forms deduplicate by patch identity. ring/neighbors/blockX exclude the caller after wrapping.

for (const patch of this.neighbors()) {
  // Apply a rule once per distinct other patch.
}

Hex worlds use doubled columns: valid patch coordinates have even col + row. Horizontal wrapping spans 2 * cols. Vertical wrapping in an odd-height hex world includes a one-doubled-column skew, preserving parity and reciprocal adjacency. In even-height worlds the vertical seam has no skew. Rings describe wrapped grid offsets, not Euclidean circles: for circular selection use the radius helpers.

Main patch helpers

Source: NLSimMain. Inside Patch or Turtle, reach these through this.main.

main.neighbors(patch) and main.nhood8(patch)

Returns an array of the patch's unique nearest neighbors, excluding itself. It inherits that patch type's topology and boundaries: normally eight interior square patches or six hex patches, fewer at Edge boundaries or in tiny worlds. nhood8 is retained as a compatibility alias; prefer neighbors in new code because it does not imply a square-grid neighbor count.

main.nhood4(patch)

Returns an array of unique square-grid side neighbors, in order: row minus one, row plus one, column minus one, column plus one. It excludes the center and diagonals.

It respects the cell boundary: an ordinary Edge corner has two neighbors; a Wrap corner has four when the world is large enough. Repeated neighbors and the caller are removed in tiny worlds. Hex worlds throw RangeError; use neighbors for hex adjacency.

const burning = this.main.nhood4(this)
  .filter(patch => patch.color === 1).length;

main.patch(col, row)

Looks up one patch, flooring both coordinates. Within bounds on a square grid, main.patch(2.7, 3.2) returns column 2, row 3. Out-of-bounds matrix lookup wraps in Wrap mode and clamps in Edge mode; it does not return null at an Edge boundary. Check bounds yourself if your rule should treat an outside position as absent. Wrap lookup handles offsets spanning multiple world widths/heights. Hex coordinates use a parity-dependent doubled-column layout; use valid patch coordinates or a neighborhood operator rather than ordinary square offsets.

Turtles on patches

Source: NLTurtle and AgentGrid/AgentSet.

These queries read the spatial index. Use them after population insertion (for example in setup or a step), and configure agent indexing with agent.agentGrid: true. They read committed occupancy, not pending nextLocation. A query is not a prediction of where turtles will move. AgentSet.toList() hides turtles flagged as dying (zombie) or newly born (newbie); newborn insertion alone does not guarantee visibility yet.

patch.myTurtles(me) and patch.occupied

myTurtles() returns an AgentList (an array subclass) of visible turtles on the patch. occupied is true if that list is nonempty. Optional me filters to turtles on the same layer as me; it is not an instruction to exclude me itself.

main.turtlesAt(col, row) similarly returns the visible occupancy list for a location. Filter explicitly when self or class membership matters.

turtle.nhoodPatches(patches) / turtle.nhoodPatches(predicate, patches)

Returns an AgentSet (a Set subclass) containing visible turtles on the supplied patches, excluding the calling turtle. The optional predicate(other) must be true for inclusion. Repeated patches do not produce duplicate turtles in this result.

const nearby = this.nhoodPatches(this.myPatch().ring(1));
const redNeighbors = this.nhoodPatches(
  other => other.color === 1,
  this.myPatch().ring(1)
);
for (const other of redNeighbors) {
  // Read other.color, other.location, etc.
}

This searches patches, not a continuous distance circle. Turtles sharing the caller's own patch are included when that patch is in the supplied collection (for example block(1)), but the caller is still excluded.

turtle.nhoodCount(predicate = other => true, patches = this.myPatch().ring(1))

Counts unique visible other turtles satisfying the predicate across the supplied patches. It uses the same query as nhoodPatches, so these agree:

const patches = this.myPatch().block(1);
const count = this.nhoodCount(patches);
const sameCount = this.nhoodPatches(patches).size;

The patches-only overload is this.nhoodCount(patches). With no arguments it counts the nearest ring. Predicates receive each distinct other turtle at most once, even if the supplied patches repeat.

main.nhood(predicate, coords, Class)

Returns an array of indexed turtles at the supplied coordinate objects (coord.x and coord.y numeric properties), with zombie/newbie entries skipped, exact agent.constructor === Class, and predicate(agent) true. It has no default class/predicate and has no calling turtle to exclude. Repeated coordinates do not repeat turtles. Subclasses of Class are not matched by inheritance. Prefer the patch-based turtle query unless you need this form.

turtle.nhoodCoords(coords) / turtle.nhoodCoords(predicate, coords)

Returns the same unique-other-turtle AgentSet as nhoodPatches, but selects occupancy using coordinates. Standard newCoords(x, y) objects with numeric x/y properties are supported, as are older callable x()/y() coordinates. Coordinates must be finite; malformed coordinates throw TypeError.

const nearby = this.nhoodCoords([this.newCoords(2, 3), this.newCoords(3, 3)]);

Euclidean radius and annulus helpers

Radii may be any finite nonnegative numbers, including fractions. Negative, nonfinite, nonnumeric, or reversed radius bounds throw RangeError. Center coordinates must be finite (TypeError otherwise). Results are arrays without duplicates. Do not depend on their traversal order.

Distances use patch coordinate points (col, row), not display pixels. Square spacing is one unit in x and y. Hex doubled-column coordinates are scaled to x = col / 2, y = row * sqrt(3) / 2, giving adjacent hex patches unit distance. Edge worlds use direct distance; Wrap worlds use the shortest displacement across their seams, including the hex odd-row skew.

Signature Selection and side effects
main.patchesInRadius(patch, radius) Patches with distance <= radius from the patch's coordinate point; includes the center; no activation
main.turtlesInRadius(patch, radius) Visible turtles whose actual committed locations are within radius of that coordinate point; not merely turtles on nearby patches; no activation
main.patchesBetweenRadii(row, col, inner, outer, closed = true, touch = true) Patches with inner <= distance <= outer; closed: false excludes the inner boundary only; touch: true schedules the returned patches through touchPatch
main.patchesBetweenRadiiAndFringe(row, col, inner, outer) [patches, fringe]; both annulus bounds inclusive; fringe is the selected subset with distance >= outer - 1; no activation

The between-radii helpers take row first, column second, unlike main.patch. Radius zero selects coincident points only. Turtle radius queries exclude dying/newborn-hidden turtles, but do not have a calling turtle to exclude and do not require the spatial index. In a patch-only world they return [].

const disk = this.main.patchesInRadius(this, 2);
const annulus = this.main.patchesBetweenRadii(this.row, this.col, 1, 3, true, false);

Compatibility note

These are shared API repairs in both lifecycle modes, not additional app lifecycle migrations. Existing code relying on repeated wrapped visits, center-excluding square block, clipped Wrap nhood4, or self-counting nhoodCount must use the explicit new contracts instead. The regression tests are in nlapi-neighborhoods.test.mjs.