ants/docs/INFRASTRUCTURE.md
2026-03-09 10:22:03 -04:00

12 KiB

Infrastructure Requirements

Foundational data structures and capabilities needed to support all features described in REALISM-IDEAS.md. These are the structural primitives — every realism feature reduces to needing one or more of these five layers.

A. Expanded ant state

Currently all 4 RGBA Float32 channels are used: R = pos.x, G = pos.y, B = angle, A = packed(storage | isCarrying)

Most realism features need more per-ant data. This means a second ant texture (RGBA Float32, same dimensions, same ping-pong pattern).

What needs to live in ant state:

personality/threshold float #3 individuality, #4 adaptive noise steps since pickup int #1 distance-modulated deposition cargo quality float #5 food quality encoding path integration dx float #9 path integration path integration dy float #9 path integration role/caste float #11 caste switching colony ID int #12 CHC recognition

That's 7 values. Two RGBA textures give 8 floats total, so with some packing of ints into float bits (steps, colony ID, carrying flag are all small integers) it fits in 2 textures. If the packing gets uncomfortable a third texture is an option — WebGL supports up to 16 texture units per shader, current shaders use 2-3, so headroom is fine.

Layout sketch for texture 2:

R = personality (0.0 = pure follower, 1.0 = pure explorer) G = cargo quality (0.0 when not carrying, otherwise food quality value) B = path integration dx (accumulated displacement since last reset) A = packed(path_integration_dy | role | colony_id | steps_since_pickup)

The A channel packing is tight. Alternative: move steps_since_pickup into texture 1's storage bits (current storage uses ~20 of 31 available bits) and give path_integration_dy its own float in texture 2.A. Then role and colony_id pack together (role quantized to 8 bits, colony_id in 8 bits = 16 bits, fits in the fractional part of another channel).

Pipeline impact:

  • AntsComputeScene needs to read/write 2 textures per ant instead of 1
  • Ping-pong doubles from 2 textures to 4
  • antsCompute.frag gets a second sampler input and second render target (requires MRT — multiple render targets — or a second pass)
  • MRT is available in WebGL2 via gl.drawBuffers(), clean solution

Features unlocked: #1, #3, #4, #5, #9, #11, #12

B. Generalized pheromone system

Currently 2 pheromone channels in the world texture: G = scentToHome, B = scentToFood A = unused (free slot for one more)

Minimum viable expansion: put repellent pheromone in world.A. That gives 3 channels with zero new textures.

But alarm pheromone (#6) and multicomponent blends (#8) need more. Options:

Option 1: Second world texture (RGBA Float32, same worldSize) R = alarm pheromone G = blend component A B = blend component B A = reserved

Option 2: Multiplex channels temporally or semantically Less clean, harder to reason about, not recommended

Going with option 1, total pheromone channels = 6: world1.G scentToHome world1.B scentToFood world1.A repellent world2.R alarm world2.G blend component A world2.B blend component B

The blur/diffusion shader must generalize:

  • Currently applies one blur radius and one fade-out factor uniformly

  • Needs per-channel parameters: channel decay rate diffusion radius notes toHome medium medium standard trail toFood medium medium standard trail repellent slow (2x) narrow persists longer per biology alarm fast wide spreads fast, fades fast blend A configurable configurable depends on compound blend B configurable configurable depends on compound

  • Pass these as a uniform array or pack into a small config texture

  • The blur pass runs once but processes all channels with their own params

Pipeline impact:

  • worldBlur.frag becomes per-channel parameterized
  • world.frag merges deposits into more channels
  • antsCompute.frag reads more pheromone channels for decision-making
  • antsDiscretize.frag may need more output channels (MRT again, or expand discrete texture to RGBA Float32 to carry more deposit types)

Features unlocked: #2, #6, #8

C. World cell metadata

Currently world.R uses 3 bits for cell flags: bit 0 = hasFood, bit 1 = isHome, bit 2 = isObstacle bits 3-31 = unused (29 bits free in float representation)

Several features need more per-cell information:

terrain type 3-4 bits #7 substrate-dependent decay colony ownership 4-8 bits #12 multi-colony territories food quality ~8 bits #5 quality encoding (quantized)

Terrain type (3 bits = 8 terrain types) and colony ownership (4 bits = 16 colonies) fit cleanly into world.R's unused bits:

bits 0-2: cell type flags (food, home, obstacle) — existing bits 3-5: terrain type (0=default, 1-7 = surface variants) bits 6-9: colony ID that owns this cell (0=neutral, 1-15 = colonies) bits 10-17: food quality (0-255 quantized, only meaningful when hasFood) bits 18-31: reserved

This keeps everything in a single channel with bit operations the shader already uses (the existing code does bitwise AND/OR on world.R).

Terrain type feeds into the blur shader — the decay rate per cell becomes: effective_decay = base_decay * terrain_decay_multiplier[terrain_type] This means the blur shader needs to read the world texture (not just the blurred copy) to know each cell's terrain type. Small perf cost but the texture is already bound.

Food quality feeds into ant pickup behavior — when an ant grabs food, it reads the quality bits and stores them in its cargo quality channel (ant texture 2.G from section A).

Features unlocked: #5, #7, #12

D. Ant-ant spatial interaction

Currently zero. Ants only interact through stigmergy (pheromone trails in the world texture). No ant knows about any other ant's position or state.

The discrete ants texture maps ant deposits to grid cells but doesn't preserve ant identity — it just accumulates pheromone values.

To enable ant-ant awareness, options:

Option 1: Identity-preserving discrete texture Instead of (or in addition to) accumulating pheromone deposits, store the ant index of whoever occupies each cell. Multiple ants per cell requires either: a) Last-write-wins (lossy but simple, GPU-friendly) b) Linked list in a buffer (complex, needs WebGL2 atomic ops or compute shaders) c) Spatial hash with fixed bucket size (e.g. 4 ants per cell, pack 4 ant indices into RGBA)

Option (a) is probably fine — tandem running only needs to know
"is there an ant near me" and check one neighbor, not enumerate
all neighbors.

Option 2: Proximity via world texture overlay A separate texture where each cell stores the ID (or packed state) of an ant occupying it. Ants sample neighboring cells to find nearby ants. Radius-1 gives 8 neighbors, radius-2 gives 24.

For tandem running: leader deposits its ID in the cell. Follower
checks adjacent cells for the leader's ID, moves toward it.

For CHC recognition: ant reads neighbor cell, extracts colony ID
from neighbor's state, compares to own colony ID.

Implementation: - New texture: antsPresenceTexture (RGBA Float32, worldSize x worldSize) - R = ant index (or packed ant state subset) - G = colony ID of occupant - B = role/caste of occupant - A = reserved - Written during discretize pass, read during ant compute pass - Cleared each frame before discretize

Tandem running specifics: - Leader state: "has follower" flag, movement gated on follower proximity - Follower state: "leader index" reference, moves toward leader cell - Pairing logic: when a returning forager passes a naive ant, the naive ant checks if the forager is available as a leader (not already paired) - Race condition on pairing: last-write-wins means two ants might both claim the same leader. Acceptable — worst case is a broken tandem that reforms next frame.

Pipeline impact:

  • New texture in discretize pass
  • antsCompute.frag gets a new sampler for neighbor awareness
  • Possibly a CPU readback for complex pairing logic that's too hard on GPU

Features unlocked: #10, #12

E. Colony-level feedback

Currently no aggregate state. Individual ants have no information about the colony as a whole — they only react to local pheromone concentrations.

Caste switching (#11) and alarm response scaling (#6) need colony-wide stats:

forager count how many ants are currently foraging scout count how many ants are currently exploring total food collected cumulative food delivered to nest threat level number of alarm pheromone cells above threshold colony size total ants (static, but relevant for ratios)

Two approaches:

Option 1: GPU reduction Run a reduction shader that sums values across the ant texture: - Count ants with isCarrying = 1 (foragers returning) - Count ants with role > threshold (scouts) - Sum food deposits at home cells Requires multiple passes halving texture dimensions each time (standard parallel reduction). Result lands in a 1x1 texture. Read the 1x1 texture as a uniform for the next frame.

Pros: stays on GPU, no sync stall
Cons: multiple passes, more textures, latency (stats are 1 frame old)

Option 2: CPU readback Use gl.readPixels on the ant texture (or a downsampled version). Compute stats on CPU. Upload as uniforms next frame.

Pros: simpler to implement, flexible computation
Cons: GPU-CPU sync stall (readPixels is blocking in WebGL1, async in
WebGL2 via pixel buffer objects / fences). Could downsample first to
reduce transfer size.

Option 3: Hybrid Reduce on GPU to a small texture (e.g. 4x4), read back 16 pixels instead of thousands, compute final stats on CPU.

This is probably the pragmatic choice.

Colony stats feed back as uniforms into antsCompute.frag:

uniform float u_foragerRatio; // foragers / total ants uniform float u_scoutRatio; // scouts / total ants uniform float u_foodCollected; // cumulative food at nest uniform float u_threatLevel; // alarm pheromone intensity

Individual ants use these to adjust their role variable:

  • If foragerRatio is high and scoutRatio is low, some foragers transition toward scouting (role variable drifts)
  • If threatLevel is high, nearby ants shift toward defensive behavior
  • Transitions are gradual (continuous role variable, not discrete switch)

Features unlocked: #6, #11

Dependency graph

Which infrastructure layers does each feature need?

feature A B C D E ant pher cell a2a agg #1 distance-modulated deposition x #2 negative pheromone x #3 individual thresholds x #4 adaptive noise x #5 food quality encoding x x #6 alarm pheromone x x #7 substrate-dependent decay x x #8 multicomponent blends x #9 path integration x #10 tandem running x x #11 caste switching x x #12 CHC recognition x x x

Layer A (expanded ant state) unlocks: 9 of 12 features Layer B (generalized pheromones) : 4 of 12 features Layer C (cell metadata) : 3 of 12 features Layer D (ant-ant interaction) : 2 of 12 features Layer E (colony feedback) : 2 of 12 features

Suggested build order based on feature unlock count and dependency:

  1. Layer A — second ant texture + MRT (unlocks most features)
  2. Layer B — pheromone channel expansion + per-channel blur params
  3. Layer C — bit-packed cell metadata (small change, high leverage)
  4. Layer E — colony aggregate readback (needed before caste switching)
  5. Layer D — spatial neighbor queries (hardest, fewest features, do last)

WebGL2 requirements

Several infrastructure pieces depend on WebGL2 features:

MRT (multiple render targets) layers A, D gl.drawBuffers() writing 2+ textures per pass pixel buffer objects layer E async readback integer textures (optional) cleaner bit packing in layers A, C

The simulation already uses three.js WebGLRenderer. Confirm it's running in WebGL2 mode (three.js defaults to WebGL2 when available). If targeting WebGL1 fallback, MRT requires WEBGL_draw_buffers extension and some features may not be feasible.