Coord-Aware Neighbor Lookup in Tile-Based Cellular Automaton Simulators: A Note on the width=100 Heuristic
A short note on a class of bug common in tile-based cellular-automaton engines: the use of a fixed stride when mapping a (dx, dy) neighbour offset to a linear tile id. We describe the failure mode, the conditions under which it is silent, and the minimal fix in terms of explicit (x, y) coordinates on the tile.
1. Introduction
Tile-based CA simulators typically identify tiles by a linear id derived from row-major coordinates: id = (y · W + x). A neighbour lookup is then neighbor_id = id + dx + dy · W. The stride W is the row width. When W is encoded as a literal, the literal is a parameter that does not appear in the type system. When the actual width does not equal the literal, the lookup is silently wrong.
2. The Bug
The OOZE engine's DiffusionRule uses neighbor_id_val = tile_id.0 as i64 + (dx as i64) + (dy as i64) * 100. The 100 is a stand-in for the row width.
3. When It Is Silent
The bug only manifests when (dx, dy) ≠ (0, 0) and the target row is not the same as the source row. On a 100×100 grid the bug never fires. On any other width it fires on every dy ≠ 0 lookup, silently reading from the wrong tile.
4. The Minimal Fix
Make (x, y) part of the tile's identity:
Tile {
id: TileId,
coords: (u32, u32),
material: MaterialId,
...
}
fn neighbor_of(tile: &Tile, dx: i32, dy: i32, width: u32, height: u32) -> Option<TileId> {
let nx = tile.coords.0 as i32 + dx;
let ny = tile.coords.1 as i32 + dy;
if nx < 0 || ny < 0 || nx >= width as i32 || ny >= height as i32 {
None
} else {
Some(TileId::new((ny as u32 * width + nx as u32) as u64))
}
}5. The General Lesson
When a physical quantity is encoded as a derived value and a neighbour lookup is performed in derived coordinates, something has to carry the row width. If that something is a literal, the literal is a parameter that does not appear in the type system. A small change to make coordinates explicit eliminates the entire class of bug.
6. Why We Wrote This Up
The bug is silent, common, and easy to miss. The first symptom is diffusion that does not quite work on a non-100×100 grid.
References
Chopard & Droz (1998). Cambridge UP. / Vichniac (1984). In Cellular Automata. North-Holland. / Wolfram (1986). World Scientific.