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.