Rewrite ant behavior as priority stack with suppressors

This commit is contained in:
Jared Miller 2026-03-11 20:53:22 -04:00
parent 8bf718bbbe
commit a7aeb064b3
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C

View file

@ -89,8 +89,6 @@ void main() {
float pathIntDx = lastExtState.b; float pathIntDx = lastExtState.b;
float pathIntDy = lastExtState.a; float pathIntDy = lastExtState.a;
bool movementProcessed = false;
// calculate this ant's index from its UV position in the texture // calculate this ant's index from its UV position in the texture
ivec2 texSize = textureSize(tLastState, 0); ivec2 texSize = textureSize(tLastState, 0);
int antIndex = int(vUv.y * float(texSize.y)) * texSize.x + int(vUv.x * float(texSize.x)); int antIndex = int(vUv.y * float(texSize.y)) * texSize.x + int(vUv.x * float(texSize.x));
@ -163,177 +161,162 @@ void main() {
} }
if (!isFalling) { if (!isFalling) {
bool acted = false;
if (isCarrying == 0.) { // ---- PRIORITY 1: DEPOSIT ----
if (noise < 0.33) { if (!acted && isCarrying == 1.) {
vec2 offset = vec2(cos(angle), sin(angle)) * sampleDistance;
vec2 point = applyOffsetToPos(pos, offset);
if (tryGetFood(point)) {
movementProcessed = true;
}
} else if (noise < 0.66) {
float newAngle = angle - ANT_ROTATION_ANGLE;
vec2 offset = vec2(cos(newAngle), sin(newAngle)) * sampleDistance;
vec2 point = applyOffsetToPos(pos, offset);
if (tryGetFood(point)) {
movementProcessed = true;
angle = newAngle;
}
} else {
float newAngle = angle + ANT_ROTATION_ANGLE;
vec2 offset = vec2(cos(newAngle), sin(newAngle)) * sampleDistance;
vec2 point = applyOffsetToPos(pos, offset);
if (tryGetFood(point)) {
movementProcessed = true;
angle = newAngle;
}
}
} else if (isCarrying == 1.) {
if (noise < 0.33) {
vec2 offset = vec2(cos(angle), sin(angle)) * sampleDistance;
vec2 point = applyOffsetToPos(pos, offset);
if (tryDropFood(point)) {
movementProcessed = true;
}
} else if (noise < 0.66) {
float newAngle = angle - ANT_ROTATION_ANGLE;
vec2 offset = vec2(cos(newAngle), sin(newAngle)) * sampleDistance;
vec2 point = applyOffsetToPos(pos, offset);
if (tryDropFood(point)) {
movementProcessed = true;
angle = newAngle;
}
} else {
float newAngle = angle + ANT_ROTATION_ANGLE;
vec2 offset = vec2(cos(newAngle), sin(newAngle)) * sampleDistance;
vec2 point = applyOffsetToPos(pos, offset);
if (tryDropFood(point)) {
movementProcessed = true;
angle = newAngle;
}
}
}
if (!movementProcessed) {
#if VIEW_MODE_SIDE
// vertical bias for digging behavior
if (isCarrying == 1. && int(cargoMaterialId) != MAT_FOOD) {
// carrying powder: bias upward toward surface
float upwardBias = PI * 0.5; // straight up
float angleDiff = upwardBias - angle;
// normalize to [-PI, PI]
angleDiff = mod(angleDiff + PI, 2.0 * PI) - PI;
// gentle steering toward up (blend 30% toward upward)
angle += angleDiff * 0.3;
} else if (isCarrying == 0.) {
// not carrying: check if surrounded by diggable material
// if so, prefer downward movement
float cellMatId = texture(tWorld, roundUvToCellCenter(pos)).x; float cellMatId = texture(tWorld, roundUvToCellCenter(pos)).x;
vec4 props = texelFetch(uMaterialProps, ivec2(int(cellMatId), 0), 0);
// check cell below for diggable material // carrying food and at home -> drop food
vec2 belowUv = roundUvToCellCenter(pos - vec2(0., cellSize)); if (int(cargoMaterialId) == MAT_FOOD && tryDropFood(pos)) {
float belowDigMat = texture(tWorld, belowUv).x; isCarrying = 0.;
vec4 belowProps = texelFetch(uMaterialProps, ivec2(int(belowDigMat), 0), 0); cargoMaterialId = 0.;
angle += PI;
storage = getMaxScentStorage(vUv);
acted = true;
}
if (belowProps.r == BEHAVIOR_POWDER && belowProps.b <= ANT_CARRY_STRENGTH) { // carrying powder and on surface (air cell, solid below) -> deposit
// diggable material below — bias downward if (!acted && int(cargoMaterialId) != MAT_FOOD) {
float downwardBias = -PI * 0.5; // straight down if (int(cellMatId) == MAT_AIR) {
float angleDiff = downwardBias - angle; vec2 belowPos = pos - vec2(0., cellSize);
float belowMatId = texture(tWorld, roundUvToCellCenter(belowPos)).x;
vec2 abovePos = pos + vec2(0., cellSize);
float aboveMatId = texture(tWorld, roundUvToCellCenter(abovePos)).x;
if ((int(belowMatId) != MAT_AIR || belowPos.y <= 0.)
&& int(aboveMatId) == MAT_AIR) {
isCarrying = 0.;
// keep cargoMaterialId set so discretize reads it this frame
angle += PI;
storage = getMaxScentStorage(vUv);
acted = true;
}
}
}
}
// ---- PRIORITY 2: NAVIGATE (carrying) ----
if (!acted && isCarrying == 1.) {
if (int(cargoMaterialId) == MAT_FOOD) {
// follow toHome pheromone
float sAhead = smell(applyOffsetToPos(pos, vec2(cos(angle), sin(angle)) * sampleDistance), 1.);
float sLeft = smell(applyOffsetToPos(pos, vec2(cos(angle - ANT_ROTATION_ANGLE), sin(angle - ANT_ROTATION_ANGLE)) * sampleDistance), 1.);
float sRight = smell(applyOffsetToPos(pos, vec2(cos(angle + ANT_ROTATION_ANGLE), sin(angle + ANT_ROTATION_ANGLE)) * sampleDistance), 1.);
if (sLeft > sAhead && sLeft > sRight) {
angle -= ANT_ROTATION_ANGLE;
} else if (sRight > sAhead && sRight > sLeft) {
angle += ANT_ROTATION_ANGLE;
}
} else {
// carrying powder: bias upward toward surface
#if VIEW_MODE_SIDE
float upwardBias = PI * 0.5;
float angleDiff = upwardBias - angle;
angleDiff = mod(angleDiff + PI, 2.0 * PI) - PI; angleDiff = mod(angleDiff + PI, 2.0 * PI) - PI;
// gentle steering toward down (20% blend) angle += angleDiff * 0.3;
angle += angleDiff * 0.2; #endif
}
// add wander noise to carrying ants too
float noise2 = rand(vUv * 1000. + fract(uTime / 1000.) + 0.2);
if (noise2 > 0.5) {
angle += ANT_ROTATION_ANGLE;
} else {
angle -= ANT_ROTATION_ANGLE;
}
acted = true;
}
// ---- PRIORITY 3: FORAGE (not carrying, food scent detected) ----
if (!acted && isCarrying == 0.) {
float sAhead = smell(applyOffsetToPos(pos, vec2(cos(angle), sin(angle)) * sampleDistance), 0.);
float sLeft = smell(applyOffsetToPos(pos, vec2(cos(angle - ANT_ROTATION_ANGLE), sin(angle - ANT_ROTATION_ANGLE)) * sampleDistance), 0.);
float sRight = smell(applyOffsetToPos(pos, vec2(cos(angle + ANT_ROTATION_ANGLE), sin(angle + ANT_ROTATION_ANGLE)) * sampleDistance), 0.);
float maxSmell = max(sAhead, max(sLeft, sRight));
if (maxSmell > SCENT_THRESHOLD) {
if (sLeft > sAhead && sLeft > sRight) {
angle -= ANT_ROTATION_ANGLE;
} else if (sRight > sAhead && sRight > sLeft) {
angle += ANT_ROTATION_ANGLE;
}
acted = true;
}
}
// ---- PRIORITY 4: DIG (not carrying, diggable material nearby below surface) ----
#if VIEW_MODE_SIDE
if (!acted && isCarrying == 0.) {
vec2 belowUv = roundUvToCellCenter(pos - vec2(0., cellSize));
float belowMat2 = texture(tWorld, belowUv).x;
vec4 belowProps2 = texelFetch(uMaterialProps, ivec2(int(belowMat2), 0), 0);
// suppress digging if on the surface (air above) — don't dig topsoil into sky
vec2 aboveUv = roundUvToCellCenter(pos + vec2(0., cellSize));
float aboveMat2 = texture(tWorld, aboveUv).x;
bool onSurfaceTop = (int(aboveMat2) == MAT_AIR);
if (!onSurfaceTop
&& belowProps2.r == BEHAVIOR_POWDER
&& belowProps2.b <= ANT_CARRY_STRENGTH) {
// bias angle toward ~40 degrees below horizontal (angle of repose)
float targetAngle = (cos(angle) >= 0.)
? -0.7 // ~-40 degrees (right and down)
: -(PI - 0.7); // ~-(180-40) degrees (left and down)
float angleDiff2 = targetAngle - angle;
angleDiff2 = mod(angleDiff2 + PI, 2.0 * PI) - PI;
angle += angleDiff2 * 0.2;
acted = true;
} }
} }
#endif #endif
float noise2 = rand(vUv * 1000. + fract(uTime / 1000.) + 0.2); // ---- PRIORITY 5: WANDER (fallback) ----
if (!acted) {
float sampleAhead = smell(applyOffsetToPos(pos, vec2(cos(angle), sin(angle)) * sampleDistance), isCarrying); if (noise < 0.33) {
float sampleLeft = smell(applyOffsetToPos(pos, vec2(cos(angle - ANT_ROTATION_ANGLE), sin(angle - ANT_ROTATION_ANGLE)) * sampleDistance), isCarrying); angle += ANT_ROTATION_ANGLE;
float sampleRight = smell(applyOffsetToPos(pos, vec2(cos(angle + ANT_ROTATION_ANGLE), sin(angle + ANT_ROTATION_ANGLE)) * sampleDistance), isCarrying); } else if (noise < 0.66) {
angle -= ANT_ROTATION_ANGLE;
if (sampleAhead > sampleLeft && sampleAhead > sampleRight) { }
// don't change direction float noise2 = rand(vUv * 1000. + fract(uTime / 1000.) + 0.2);
} else if (sampleLeft > sampleAhead && sampleLeft > sampleRight) { if (noise2 > 0.5) {
angle -= ANT_ROTATION_ANGLE; // steer left angle += ANT_ROTATION_ANGLE * 2.;
} else if (sampleRight > sampleAhead && sampleRight > sampleLeft) { } else {
angle += ANT_ROTATION_ANGLE; // steer right angle -= ANT_ROTATION_ANGLE * 2.;
} else if (noise < 0.33) { }
angle += ANT_ROTATION_ANGLE; // no smell detected, do random movement
} else if (noise < 0.66) {
angle -= ANT_ROTATION_ANGLE;
} }
if (noise2 > 0.5) { // ---- MOVEMENT APPLICATION ----
angle += ANT_ROTATION_ANGLE * 2.; vec2 offset = vec2(cos(angle), sin(angle));
} else { pos = applyOffsetToPos(pos, offset);
angle -= ANT_ROTATION_ANGLE * 2.;
if (fract(pos.x) == 0. || fract(pos.y) == 0. || (!wasObstacle && isObstacle(pos + offset * cellSize))) {
angle += PI * (noise - 0.5);
} }
}
vec2 offset = vec2(cos(angle), sin(angle)); // ---- PICKUP ----
pos = applyOffsetToPos(pos, offset); if (isCarrying == 0.) {
float cellMatId = texture(tWorld, roundUvToCellCenter(pos)).x;
int cellMatInt = int(cellMatId);
if (fract(pos.x) == 0. || fract(pos.y) == 0. || (!wasObstacle && isObstacle(pos + offset * cellSize))) { if (cellMatInt == MAT_FOOD) {
angle += PI * (noise - 0.5);
}
if (isCarrying == 0.) {
float cellMatId = texture(tWorld, roundUvToCellCenter(pos)).x;
int cellMatInt = int(cellMatId);
if (cellMatInt == MAT_FOOD) {
// food pickup (existing foraging behavior)
isCarrying = 1.;
cargoMaterialId = cellMatId;
angle += PI;
storage = getMaxScentStorage(vUv);
} else if (cellMatInt != MAT_AIR && cellMatInt != MAT_HOME) {
// check if diggable powder material
vec4 props = texelFetch(uMaterialProps, ivec2(cellMatInt, 0), 0);
float behavior = props.r;
float hardness = props.b;
if (behavior == BEHAVIOR_POWDER && hardness <= ANT_CARRY_STRENGTH) {
isCarrying = 1.; isCarrying = 1.;
cargoMaterialId = cellMatId; cargoMaterialId = cellMatId;
angle += PI; angle += PI;
storage = getMaxScentStorage(vUv); storage = getMaxScentStorage(vUv);
} else if (cellMatInt != MAT_AIR && cellMatInt != MAT_HOME) {
vec4 props = texelFetch(uMaterialProps, ivec2(cellMatInt, 0), 0);
float behavior = props.r;
float hardness = props.b;
if (behavior == BEHAVIOR_POWDER && hardness <= ANT_CARRY_STRENGTH) {
isCarrying = 1.;
cargoMaterialId = cellMatId;
angle += PI;
storage = getMaxScentStorage(vUv);
}
} }
} }
}
if (tryDropFood(pos)) {
storage = getMaxScentStorage(vUv);
if (isCarrying == 1.) {
isCarrying = 0.;
cargoMaterialId = 0.;
angle += PI;
}
}
// deposit carried powder material when standing on air with solid ground below
if (isCarrying == 1. && int(cargoMaterialId) != MAT_FOOD) {
float cellMatId = texture(tWorld, roundUvToCellCenter(pos)).x;
if (int(cellMatId) == MAT_AIR) {
vec2 belowPos = pos - vec2(0., cellSize);
float belowMatId = texture(tWorld, roundUvToCellCenter(belowPos)).x;
if (int(belowMatId) != MAT_AIR || belowPos.y <= 0.) {
isCarrying = 0.;
// keep cargoMaterialId set so discretize can read it this frame
angle += PI;
storage = getMaxScentStorage(vUv);
}
}
}
} // end !isFalling } // end !isFalling
FragColor = vec4( FragColor = vec4(