diff --git a/src/Renderer.ts b/src/Renderer.ts index 60117b0..e2987fd 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -2,6 +2,12 @@ import type { WebGLRenderTarget } from "three"; import * as THREE from "three"; import type { SceneCollection } from "./App"; import Config from "./Config"; +import { + FOOD_QUALITY_MASK, + FOOD_QUALITY_SHIFT, + TERRAIN_TYPE_MASK, + TERRAIN_TYPE_SHIFT, +} from "./constants"; interface Resources { worldRenderTarget: THREE.WebGLRenderTarget; @@ -10,6 +16,7 @@ interface Resources { antsComputeTarget0: THREE.WebGLRenderTarget; antsComputeTarget1: THREE.WebGLRenderTarget; antsDiscreteRenderTarget: THREE.WebGLRenderTarget; + antsPresenceRenderTarget: THREE.WebGLRenderTarget; } export default class Renderer { @@ -207,6 +214,10 @@ export default class Renderer { REPELLENT_THRESHOLD: Renderer.convertNumberToFloatString( Config.repellentThreshold, ), + TERRAIN_TYPE_SHIFT: String(TERRAIN_TYPE_SHIFT), + TERRAIN_TYPE_MASK: String(TERRAIN_TYPE_MASK), + FOOD_QUALITY_SHIFT: String(FOOD_QUALITY_SHIFT), + FOOD_QUALITY_MASK: String(FOOD_QUALITY_MASK), }; } diff --git a/src/__tests__/constants.test.ts b/src/__tests__/constants.test.ts new file mode 100644 index 0000000..dae213d --- /dev/null +++ b/src/__tests__/constants.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, test } from "bun:test"; +import { + CELL_FOOD_BIT, + CELL_HOME_BIT, + CELL_OBSTACLE_BIT, + FOOD_QUALITY_MASK, + FOOD_QUALITY_SHIFT, + TERRAIN_TYPE_MASK, + TERRAIN_TYPE_SHIFT, +} from "../constants"; + +describe("cell metadata bit layout", () => { + test("bit fields do not overlap", () => { + // cell flags occupy bits 0-2 + const cellFlagsBits = (1 << (CELL_OBSTACLE_BIT + 1)) - 1; + // terrain occupies bits 3-5 + const terrainBits = TERRAIN_TYPE_MASK << TERRAIN_TYPE_SHIFT; + // food quality occupies bits 6-13 + const foodQualityBits = FOOD_QUALITY_MASK << FOOD_QUALITY_SHIFT; + + expect(cellFlagsBits & terrainBits).toBe(0); + expect(cellFlagsBits & foodQualityBits).toBe(0); + expect(terrainBits & foodQualityBits).toBe(0); + }); + + test("terrain type can encode 8 values", () => { + for (let t = 0; t <= 7; t++) { + const packed = t << TERRAIN_TYPE_SHIFT; + const unpacked = (packed >> TERRAIN_TYPE_SHIFT) & TERRAIN_TYPE_MASK; + expect(unpacked).toBe(t); + } + }); + + test("food quality can encode 0-255", () => { + for (const q of [0, 1, 127, 255]) { + const packed = q << FOOD_QUALITY_SHIFT; + const unpacked = (packed >> FOOD_QUALITY_SHIFT) & FOOD_QUALITY_MASK; + expect(unpacked).toBe(q); + } + }); + + test("all fields round-trip together", () => { + const food = 1; + const home = 1; + const obstacle = 0; + const terrain = 5; + const quality = 200; + + const packed = + food + + (home << CELL_HOME_BIT) + + (obstacle << CELL_OBSTACLE_BIT) + + (terrain << TERRAIN_TYPE_SHIFT) + + (quality << FOOD_QUALITY_SHIFT); + + expect(packed & 1).toBe(food); + expect((packed >> CELL_HOME_BIT) & 1).toBe(home); + expect((packed >> CELL_OBSTACLE_BIT) & 1).toBe(obstacle); + expect((packed >> TERRAIN_TYPE_SHIFT) & TERRAIN_TYPE_MASK).toBe( + terrain, + ); + expect((packed >> FOOD_QUALITY_SHIFT) & FOOD_QUALITY_MASK).toBe( + quality, + ); + }); +}); diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..f2b3cc9 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,14 @@ +// cell metadata bit layout for world texture R channel + +// bits 0-2: cell type flags +export const CELL_FOOD_BIT = 0; +export const CELL_HOME_BIT = 1; +export const CELL_OBSTACLE_BIT = 2; + +// bits 3-5: terrain type (0-7) +export const TERRAIN_TYPE_SHIFT = 3; +export const TERRAIN_TYPE_MASK = 0b111; // 3 bits + +// bits 6-13: food quality (0-255) +export const FOOD_QUALITY_SHIFT = 6; +export const FOOD_QUALITY_MASK = 0xff; // 8 bits diff --git a/src/shaders/world.frag b/src/shaders/world.frag index 94a10ba..6febbf8 100644 --- a/src/shaders/world.frag +++ b/src/shaders/world.frag @@ -24,5 +24,13 @@ void main() { isFood = 0; } - FragColor = vec4(float(isFood + (isHome << 1) + (isObstacle << 2)), scentToHome, scentToFood, repellent); + int terrainType = (cellData >> TERRAIN_TYPE_SHIFT) & TERRAIN_TYPE_MASK; + int foodQuality = (cellData >> FOOD_QUALITY_SHIFT) & FOOD_QUALITY_MASK; + + // reconstruct cell data preserving all bit fields + int newCellData = isFood + (isHome << 1) + (isObstacle << 2) + + (terrainType << TERRAIN_TYPE_SHIFT) + + (foodQuality << FOOD_QUALITY_SHIFT); + + FragColor = vec4(float(newCellData), scentToHome, scentToFood, repellent); }