Expand description
Stigmergy: coordination through the environment.
Agents write into a shared field and read it back, so they coordinate through the world rather than by messaging each other (Holland & Melhuish, “Stigmergy, self-organization, and sorting in collective robotics”, 1999; Tang, Liu & Pan, ACO review, IEEE/CAA JAS 2021 — deposit + evaporation + positive feedback). Each channel is a scalar grid over cells, and the three standard influence-map operations are placement (deposit), diffusion (blur to neighbours), and query (sample/gradient) — Lewis, “Escaping the Grid”, Game AI Pro 2 Ch.29.
The field is computed once and shared by every agent (Mark, “Modular Tactical Influence Maps”, Ch.30), which is where emergent group behaviour comes from: nobody negotiates, and a crowd still converges on a trail, disperses from a threat, or recruits toward an alarm.
Two stores are provided:
StigGrid—Nscalar channels. Concentrations that decay, spread, and can be climbed.RallyGrid— one vector per cell, storing a bearing rather than an amount, for tracking a moving target (Tang et al. 2019).
§What this crate deliberately does not do
It owns no resources, registers no systems, and has no schedule. The caller decides when the field ticks and in what order relative to everything else — which is the only useful arrangement, because “deposit before or after the agents think” is a gameplay decision, not a library’s.
It owns no threads either. StigGrid::evaporate_diffuse runs on the thread that calls it, so a
Bevy system never parks waiting on another pool; a large grid hands its stencil to the caller’s own
pool with StigGrid::evaporate_diffuse_with, and gets the same bits.
It also does not name the channels. A channel table is game content; a library that shipped one
would be naming somebody else’s game. Channels are usize indices, and N is yours to pick.
§Cell space, not world space
Every entry point takes an IVec2 cell. The crate does not decide how big a cell is or where the
grid sits in the world.
CellFrame is an optional plain value type for the mapping nearly every caller writes anyway — an
axis-aligned grid of uniform cells: world_to_cell, cell_centre, the ascending cells() a floor
set wants, and covering for a world rectangle. It is a convenience, not a requirement: a caller with
a hex sheet, a rotated board or a camera-relative projection ignores it and keeps its own mapping, and
nothing in the crate behaves differently either way.
§Determinism
The passes are written to be bit-reproducible, because a simulation that hashes its state cannot afford otherwise:
- The neighbour sum keeps a fixed E/W/S/N order. Float addition is non-associative, so the order is load-bearing rather than stylistic.
- The diffusion is a pure stencil over disjoint output slots — each cell reads the previous grid
and writes only its own slot — so its result is identical however the pass is split and on
however many threads it runs, which is what makes
StigGrid::evaporate_diffuse_withsafe to hand to the caller’s pool. - Skipping rock cells is exact, not an approximation: deposits are floor-masked,
0 · retainis still 0, and the diffusion double-buffer’s rock cells stay 0 across the swap. StigGrid::channelsandRallyGrid::cellsexpose the full grids so a caller can fold the rock-cells-stay-zero invariant into its own fingerprint, not just the floor cells.
Deposits are applied with a non-associative +=, so a caller that produces them in an unordered way
(an ECS query, a hash map) must sort its batch before submitting. This crate cannot do that for you:
it never sees the batch, only the individual deposits.
Re-exports§
Modules§
- grid
- The grid convention every field in this crate shares, and the shared deposit kernel.
Structs§
- Cell
Frame - An axis-aligned world↔cell mapping: where the grid starts, how wide a cell is, and how many cells there are.
- Channel
Def - Per-channel behaviour. The caller supplies one of these per channel, typically from its own config.
- Diffuse
Chunk - One slice of a channel’s pass: a contiguous run of floor cells and the span of the next grid that only they write.
- Diffuse
Chunks - The chunks of one channel’s pass, in ascending cell order: an
ExactSizeIteratorofDiffuseChunks whose floor-cell counts differ by at most one. - Rally
Def - Tuning for the vectorial pheromone (mirrors
crate::ChannelDef, but for the vector store). - Rally
Grid - A vectorial pheromone map: each floor cell stores a 2-D direction, not a scalar concentration.
- Stig
Grid Nscalar influence channels over one fixed cell grid, row-majory*width + x.