Replace bit-packed cell flags with material ID constants

This commit is contained in:
Jared Miller 2026-03-11 13:47:56 -04:00
parent f5b04f08c6
commit 0f9c1b47f2
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C
3 changed files with 46 additions and 80 deletions

View file

@ -4,10 +4,12 @@ import type { SceneCollection } from "./App";
import ColonyStats from "./ColonyStats"; import ColonyStats from "./ColonyStats";
import Config from "./Config"; import Config from "./Config";
import { import {
FOOD_QUALITY_MASK, MAT_AIR,
FOOD_QUALITY_SHIFT, MAT_DIRT,
TERRAIN_TYPE_MASK, MAT_FOOD,
TERRAIN_TYPE_SHIFT, MAT_HOME,
MAT_ROCK,
MAT_SAND,
} from "./constants"; } from "./constants";
import { import {
generateColorData, generateColorData,
@ -70,8 +72,8 @@ export default class Renderer {
format: THREE.RGBAFormat, format: THREE.RGBAFormat,
type: THREE.FloatType, type: THREE.FloatType,
depthBuffer: false, depthBuffer: false,
magFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter,
minFilter: THREE.LinearFilter, minFilter: THREE.NearestFilter,
}, },
), ),
worldRenderTargetCopy: new THREE.WebGLRenderTarget( worldRenderTargetCopy: new THREE.WebGLRenderTarget(
@ -264,10 +266,12 @@ export default class Renderer {
REPELLENT_THRESHOLD: Renderer.convertNumberToFloatString( REPELLENT_THRESHOLD: Renderer.convertNumberToFloatString(
Config.repellentThreshold, Config.repellentThreshold,
), ),
TERRAIN_TYPE_SHIFT: String(TERRAIN_TYPE_SHIFT), MAT_AIR: String(MAT_AIR),
TERRAIN_TYPE_MASK: String(TERRAIN_TYPE_MASK), MAT_SAND: String(MAT_SAND),
FOOD_QUALITY_SHIFT: String(FOOD_QUALITY_SHIFT), MAT_DIRT: String(MAT_DIRT),
FOOD_QUALITY_MASK: String(FOOD_QUALITY_MASK), MAT_ROCK: String(MAT_ROCK),
MAT_FOOD: String(MAT_FOOD),
MAT_HOME: String(MAT_HOME),
}; };
} }

View file

@ -1,66 +1,33 @@
import { describe, expect, test } from "bun:test"; import { describe, expect, test } from "bun:test";
import { import {
CELL_FOOD_BIT, MAT_AIR,
CELL_HOME_BIT, MAT_DIRT,
CELL_OBSTACLE_BIT, MAT_FOOD,
FOOD_QUALITY_MASK, MAT_HOME,
FOOD_QUALITY_SHIFT, MAT_ROCK,
TERRAIN_TYPE_MASK, MAT_SAND,
TERRAIN_TYPE_SHIFT,
} from "../constants"; } from "../constants";
describe("cell metadata bit layout", () => { describe("material ID constants", () => {
test("bit fields do not overlap", () => { test("IDs match registry order", () => {
// cell flags occupy bits 0-2 expect(MAT_AIR).toBe(0);
const cellFlagsBits = (1 << (CELL_OBSTACLE_BIT + 1)) - 1; expect(MAT_SAND).toBe(1);
// terrain occupies bits 3-5 expect(MAT_DIRT).toBe(2);
const terrainBits = TERRAIN_TYPE_MASK << TERRAIN_TYPE_SHIFT; expect(MAT_ROCK).toBe(3);
// food quality occupies bits 6-13 expect(MAT_FOOD).toBe(4);
const foodQualityBits = FOOD_QUALITY_MASK << FOOD_QUALITY_SHIFT; expect(MAT_HOME).toBe(5);
expect(cellFlagsBits & terrainBits).toBe(0);
expect(cellFlagsBits & foodQualityBits).toBe(0);
expect(terrainBits & foodQualityBits).toBe(0);
}); });
test("terrain type can encode 8 values", () => { test("IDs are unique", () => {
for (let t = 0; t <= 7; t++) { const ids = [MAT_AIR, MAT_SAND, MAT_DIRT, MAT_ROCK, MAT_FOOD, MAT_HOME];
const packed = t << TERRAIN_TYPE_SHIFT; expect(new Set(ids).size).toBe(ids.length);
const unpacked = (packed >> TERRAIN_TYPE_SHIFT) & TERRAIN_TYPE_MASK; });
expect(unpacked).toBe(t);
test("IDs fit in a byte", () => {
const ids = [MAT_AIR, MAT_SAND, MAT_DIRT, MAT_ROCK, MAT_FOOD, MAT_HOME];
for (const id of ids) {
expect(id).toBeGreaterThanOrEqual(0);
expect(id).toBeLessThanOrEqual(255);
} }
}); });
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 >> CELL_FOOD_BIT) & 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,
);
});
}); });

View file

@ -1,14 +1,9 @@
// cell metadata bit layout for world texture R channel // material IDs for world texture R channel
// must match registration order in materials/registry.ts
// bits 0-2: cell type flags export const MAT_AIR = 0;
export const CELL_FOOD_BIT = 0; export const MAT_SAND = 1;
export const CELL_HOME_BIT = 1; export const MAT_DIRT = 2;
export const CELL_OBSTACLE_BIT = 2; export const MAT_ROCK = 3;
export const MAT_FOOD = 4;
// bits 3-5: terrain type (0-7) export const MAT_HOME = 5;
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