aether-shards/test/generation/CaveGenerator.test.js

239 lines
7 KiB
JavaScript
Raw Normal View History

import { expect } from "@esm-bundle/chai";
import { CaveGenerator } from "../../src/generation/CaveGenerator.js";
import { VoxelGrid } from "../../src/grid/VoxelGrid.js";
// Mock OffscreenCanvas for texture generation
if (typeof OffscreenCanvas === "undefined") {
class MockCanvas {
constructor(width, height) {
this.width = width;
this.height = height;
}
getContext() {
return {
createImageData: (w, h) => ({
data: new Uint8ClampedArray(w * h * 4),
}),
putImageData: () => {},
drawImage: () => {},
fillStyle: "",
fillRect: () => {},
};
}
}
globalThis.OffscreenCanvas = MockCanvas;
}
describe("Generation: CaveGenerator", () => {
let grid;
let generator;
beforeEach(() => {
grid = new VoxelGrid(20, 10, 20);
generator = new CaveGenerator(grid, 12345);
});
it("CoA 1: Should initialize with texture generators", () => {
expect(generator.floorGen).to.exist;
expect(generator.wallGen).to.exist;
expect(generator.generatedAssets).to.have.property("palette");
});
it("CoA 2: preloadTextures should generate texture palette", () => {
generator.preloadTextures();
// Should have wall variations (100-109)
expect(generator.generatedAssets.palette[100]).to.exist;
expect(generator.generatedAssets.palette[109]).to.exist;
// Should have floor variations (200-209)
expect(generator.generatedAssets.palette[200]).to.exist;
expect(generator.generatedAssets.palette[209]).to.exist;
});
it("CoA 3: generate should create foundation layer", () => {
generator.generate(0.5, 2);
// Foundation (y=0) should be solid
for (let x = 0; x < grid.size.x; x++) {
for (let z = 0; z < grid.size.z; z++) {
expect(grid.getCell(x, 0, z)).to.not.equal(0);
}
}
});
it("CoA 4: generate should create extruded walls", () => {
generator.generate(0.5, 2);
// Check a spot that is a wall (has block at y=5 for example)
// It should be solid all the way down to y=0
let foundWall = false;
for (let x = 0; x < grid.size.x; x++) {
for (let z = 0; z < grid.size.z; z++) {
const midY = Math.floor(grid.size.y / 2);
if (grid.getCell(x, midY, z) !== 0) {
foundWall = true;
// Valid wall stack
expect(grid.getCell(x, 0, z)).to.not.equal(0);
expect(grid.getCell(x, 1, z)).to.not.equal(0);
}
}
}
expect(foundWall).to.be.true;
});
it("CoA 5: smoothMap should apply 2D cellular automata", () => {
// 5x5 Map
// 1 1 1 0 0
// 1 0 1 0 0
// 1 1 1 0 0
// 0 0 0 0 0
// 0 0 0 0 0
// Center (2, 2) has neighbors: (1,1)=0, (1,2)=1, (1,3)=0, (2,1)=1, (2,3)=1, (3,1)=0, (3,2)=0, (3,3)=0
// Total 1s = 3. Less than 4 -> should become 0.
// Actually let's just test simple behavior: dense area stays dense, sparse area clears.
let map = [
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
];
// Mock dimensions temporarily for this test if smoothMap uses this.width/depth
// implementation uses map.length so it might be fine or we need to override width/depth
// My implementation used `this.width`. I should mock it or use the real grid size if I constructed it right.
// The real grid is 20x10x20.
// Let's use a full size map initialized to match grid size to avoid bounds errors.
map = [];
for (let x = 0; x < 20; x++) {
map[x] = [];
for (let z = 0; z < 20; z++) {
map[x][z] = x < 10 && z < 10 ? 1 : 0; // Block of walls
}
}
// Creating a hole in the wall block
map[5][5] = 0;
// Surrounded by 1s (8 neighbors). Should become 1.
const newMap = generator.smoothMap(map);
// (5,5) should be filled
expect(newMap[5][5]).to.equal(1);
});
it("CoA 6: applyTextures should assign floor and wall IDs", () => {
// Setup manual grid state replicating generate() output
// Wall at 5,5 (Column)
for (let y = 0; y < grid.size.y; y++) {
grid.setCell(5, y, 5, 100);
}
// Floor at 6,6 (Only y=0)
grid.setCell(6, 0, 6, 100);
// y=1 is air
grid.setCell(6, 1, 6, 0);
generator.applyTextures();
// Wall Check
const wallId = grid.getCell(5, 5, 5);
expect(wallId).to.be.greaterThanOrEqual(100);
expect(wallId).to.be.lessThanOrEqual(109);
// Floor Check
const floorId = grid.getCell(6, 0, 6);
expect(floorId).to.be.greaterThanOrEqual(200);
expect(floorId).to.be.lessThanOrEqual(209);
});
it("CoA 7: generate should scatter cover objects", () => {
generator.generate(0.5, 2);
// Check for cover objects (ID 10)
let coverCount = 0;
for (let x = 0; x < grid.size.x; x++) {
for (let z = 0; z < grid.size.z; z++) {
for (let y = 0; y < grid.size.y; y++) {
if (grid.getCell(x, y, z) === 10) {
coverCount++;
}
}
}
}
// Should have some cover objects
expect(coverCount).to.be.greaterThan(0);
});
it("CoA 8: generate with same seed should produce consistent results", () => {
const grid1 = new VoxelGrid(20, 10, 20);
const gen1 = new CaveGenerator(grid1, 12345);
gen1.generate(0.5, 2);
const grid2 = new VoxelGrid(20, 10, 20);
const gen2 = new CaveGenerator(grid2, 12345);
gen2.generate(0.5, 2);
// Same seed should produce same results
expect(grid1.cells).to.deep.equal(grid2.cells);
});
it("CoA 9: ensureConnectivity should remove disconnected small regions", () => {
// Create a map with two disconnected regions
// Region A: 3x3 (Total 9)
// Region B: 1x1 (Total 1)
let map = [];
for (let x = 0; x < 20; x++) {
map[x] = [];
for (let z = 0; z < 20; z++) {
map[x][z] = 1; // Walls everywhere
}
}
// Region A (Large)
for (let x = 1; x <= 3; x++) {
for (let z = 1; z <= 3; z++) {
map[x][z] = 0;
}
}
// Region B (Small, disconnected)
map[10][10] = 0;
const connectedMap = generator.ensureConnectivity(map);
// Region A should remain
expect(connectedMap[2][2]).to.equal(0);
// Region B should be filled
expect(connectedMap[10][10]).to.equal(1);
});
it("CoA 10: scatterWallDetails should place objects on wall faces", () => {
// Setup a single wall column at 5,5 from y=0 to y=10
// And air at 6,5 (adjacent)
for (let y = 0; y < grid.size.y; y++) {
grid.setCell(5, y, 5, 100);
grid.setCell(6, y, 5, 0);
}
// Force seed/density to ensure placement
// Or just check that it CAN place
// Let's rely on randomness with a loop or high density
generator.scatterWallDetails(11, 1.0, 2); // 100% density for test
// Check y=2 to y=height-2 (safe range)
let foundDetail = false;
for (let y = 2; y < grid.size.y - 1; y++) {
const id = grid.getCell(6, y, 5);
if (id === 11) foundDetail = true;
}
expect(foundDetail).to.be.true;
// Ensure it didn't place below minHeight (y=1)
expect(grid.getCell(6, 1, 5)).to.equal(0);
});
});