import { describe, it, expect } from 'vitest'; import biomeDataArray from '../src/data/biomes.json'; import { createSeededNoise, sampleNoise } from '../src/world/noise'; import { generateWorld } from '../src/world/generator'; import type { BiomeData } from '../src/world/types'; const allBiomes = biomeDataArray as BiomeData[]; // Load the first biome — structural compatibility with BiomeData const biome = allBiomes[0]; // ─── Noise ────────────────────────────────────────────────────── describe('Seeded Noise', () => { it('is deterministic with same seed', () => { const n1 = createSeededNoise(42); const n2 = createSeededNoise(42); expect(n1(0.5, 0.5)).toBe(n2(0.5, 0.5)); }); it('produces different results with different seeds', () => { const n1 = createSeededNoise(42); const n2 = createSeededNoise(99); expect(n1(0.5, 0.5)).not.toBe(n2(0.5, 0.5)); }); it('normalizes to [0, 1] via sampleNoise', () => { const noise = createSeededNoise(42); for (let i = 0; i < 200; i++) { const val = sampleNoise(noise, i * 0.3, i * 0.7, 0.1); expect(val).toBeGreaterThanOrEqual(0); expect(val).toBeLessThanOrEqual(1); } }); it('varies across coordinates', () => { const noise = createSeededNoise(42); const values = new Set(); for (let i = 0; i < 50; i++) { values.add(sampleNoise(noise, i, 0, 0.1)); } // Should have significant variety (not all same value) expect(values.size).toBeGreaterThan(20); }); }); // ─── Biome Data ───────────────────────────────────────────────── describe('Biome Data', () => { it('has valid structure', () => { expect(biome.id).toBe('catalytic-wastes'); expect(biome.tileSize).toBe(32); expect(biome.mapWidth).toBe(80); expect(biome.mapHeight).toBe(80); }); it('has 8 tile types', () => { expect(biome.tiles).toHaveLength(8); }); it('tile IDs are sequential starting from 0', () => { biome.tiles.forEach((tile, index) => { expect(tile.id).toBe(index); }); }); it('has both walkable and non-walkable tiles', () => { const walkable = biome.tiles.filter(t => t.walkable); const blocked = biome.tiles.filter(t => !t.walkable); expect(walkable.length).toBeGreaterThan(0); expect(blocked.length).toBeGreaterThan(0); }); it('elevation rules cover full [0, 1] range', () => { const rules = biome.generation.elevationRules; expect(rules.length).toBeGreaterThan(0); // Last rule should cover up to 1.0 expect(rules[rules.length - 1].below).toBe(1); // Rules should be sorted ascending for (let i = 1; i < rules.length; i++) { expect(rules[i].below).toBeGreaterThan(rules[i - 1].below); } }); it('all elevation rule tileIds reference valid tiles', () => { const validIds = new Set(biome.tiles.map(t => t.id)); for (const rule of biome.generation.elevationRules) { expect(validIds.has(rule.tileId)).toBe(true); } }); }); // ─── World Generation ─────────────────────────────────────────── describe('World Generation', () => { it('generates grid with correct dimensions', () => { const world = generateWorld(biome, 42); expect(world.grid).toHaveLength(biome.mapHeight); for (const row of world.grid) { expect(row).toHaveLength(biome.mapWidth); } }); it('all tile IDs in grid are valid', () => { const world = generateWorld(biome, 42); const validIds = new Set(biome.tiles.map(t => t.id)); for (const row of world.grid) { for (const tileId of row) { expect(validIds.has(tileId)).toBe(true); } } }); it('is deterministic — same seed same map', () => { const w1 = generateWorld(biome, 42); const w2 = generateWorld(biome, 42); expect(w1.grid).toEqual(w2.grid); }); it('different seeds produce different maps', () => { const w1 = generateWorld(biome, 42); const w2 = generateWorld(biome, 99); expect(w1.grid).not.toEqual(w2.grid); }); it('stores seed and biome reference', () => { const world = generateWorld(biome, 12345); expect(world.seed).toBe(12345); expect(world.biome).toBe(biome); }); it('has diverse tile distribution (no single tile > 60%)', () => { const world = generateWorld(biome, 42); const counts = new Map(); for (const row of world.grid) { for (const tileId of row) { counts.set(tileId, (counts.get(tileId) ?? 0) + 1); } } const total = biome.mapWidth * biome.mapHeight; // At least 4 different tile types present expect(counts.size).toBeGreaterThanOrEqual(4); // No single type dominates for (const count of counts.values()) { expect(count / total).toBeLessThan(0.6); } }); it('generates acid pools (low elevation)', () => { const world = generateWorld(biome, 42); const acidId = biome.tiles.find(t => t.name === 'acid-pool')?.id; const hasAcid = world.grid.some(row => row.includes(acidId!)); expect(hasAcid).toBe(true); }); it('generates crystal formations (high elevation)', () => { const world = generateWorld(biome, 42); const crystalId = biome.tiles.find(t => t.name === 'crystal')?.id; const hasCrystals = world.grid.some(row => row.includes(crystalId!)); expect(hasCrystals).toBe(true); }); it('generates mineral veins (overlay on ground)', () => { const world = generateWorld(biome, 42); const mineralId = biome.tiles.find(t => t.name === 'mineral-vein')?.id; const hasMinerals = world.grid.some(row => row.includes(mineralId!)); expect(hasMinerals).toBe(true); }); it('generates geysers (overlay near acid)', () => { const world = generateWorld(biome, 42); const geyserId = biome.tiles.find(t => t.name === 'geyser')?.id; const hasGeysers = world.grid.some(row => row.includes(geyserId!)); expect(hasGeysers).toBe(true); }); it('produces unique map every seed (sample 5 seeds)', () => { const grids = [1, 2, 3, 4, 5].map(s => generateWorld(biome, s).grid); for (let i = 0; i < grids.length; i++) { for (let j = i + 1; j < grids.length; j++) { expect(grids[i]).not.toEqual(grids[j]); } } }); }); // ─── Multi-Biome Support (Phase 9) ────────────────────────────── describe('Multi-Biome Data', () => { it('has 3 biomes loaded', () => { expect(allBiomes).toHaveLength(3); }); it('each biome has a unique id', () => { const ids = allBiomes.map(b => b.id); expect(new Set(ids).size).toBe(3); expect(ids).toContain('catalytic-wastes'); expect(ids).toContain('kinetic-mountains'); expect(ids).toContain('verdant-forests'); }); it('each biome has 8 tile types with sequential IDs', () => { for (const b of allBiomes) { expect(b.tiles).toHaveLength(8); b.tiles.forEach((tile, index) => { expect(tile.id, `${b.id}: tile ${index}`).toBe(index); }); } }); it('each biome has an interactive tile and a resource tile', () => { for (const b of allBiomes) { const interactive = b.tiles.find(t => t.interactive); const resource = b.tiles.find(t => t.resource); expect(interactive, `${b.id}: no interactive tile`).toBeDefined(); expect(resource, `${b.id}: no resource tile`).toBeDefined(); } }); it('each biome has valid elevation rules covering [0, 1]', () => { for (const b of allBiomes) { const rules = b.generation.elevationRules; expect(rules.length).toBeGreaterThan(0); expect(rules[rules.length - 1].below).toBe(1); const validIds = new Set(b.tiles.map(t => t.id)); for (const rule of rules) { expect(validIds.has(rule.tileId), `${b.id}: invalid tileId ${rule.tileId}`).toBe(true); } } }); }); describe('Kinetic Mountains Generation', () => { const mtns = allBiomes.find(b => b.id === 'kinetic-mountains')!; it('generates correct-size grid', () => { const world = generateWorld(mtns, 42); expect(world.grid).toHaveLength(mtns.mapHeight); expect(world.grid[0]).toHaveLength(mtns.mapWidth); }); it('all tile IDs are valid', () => { const world = generateWorld(mtns, 42); const validIds = new Set(mtns.tiles.map(t => t.id)); for (const row of world.grid) { for (const tileId of row) { expect(validIds.has(tileId)).toBe(true); } } }); it('has diverse tiles with no single type > 60%', () => { const world = generateWorld(mtns, 42); const counts = new Map(); for (const row of world.grid) { for (const t of row) counts.set(t, (counts.get(t) ?? 0) + 1); } const total = mtns.mapWidth * mtns.mapHeight; expect(counts.size).toBeGreaterThanOrEqual(4); for (const count of counts.values()) { expect(count / total).toBeLessThan(0.6); } }); it('generates chasms (low elevation)', () => { const world = generateWorld(mtns, 42); const chasmId = mtns.tiles.find(t => t.name === 'chasm')?.id; expect(world.grid.some(row => row.includes(chasmId!))).toBe(true); }); it('generates ore deposits (resources)', () => { const world = generateWorld(mtns, 42); const oreId = mtns.tiles.find(t => t.resource)?.id; expect(world.grid.some(row => row.includes(oreId!))).toBe(true); }); }); describe('Verdant Forests Generation', () => { const forest = allBiomes.find(b => b.id === 'verdant-forests')!; it('generates correct-size grid', () => { const world = generateWorld(forest, 42); expect(world.grid).toHaveLength(forest.mapHeight); expect(world.grid[0]).toHaveLength(forest.mapWidth); }); it('all tile IDs are valid', () => { const world = generateWorld(forest, 42); const validIds = new Set(forest.tiles.map(t => t.id)); for (const row of world.grid) { for (const tileId of row) { expect(validIds.has(tileId)).toBe(true); } } }); it('has diverse tiles with no single type > 60%', () => { const world = generateWorld(forest, 42); const counts = new Map(); for (const row of world.grid) { for (const t of row) counts.set(t, (counts.get(t) ?? 0) + 1); } const total = forest.mapWidth * forest.mapHeight; expect(counts.size).toBeGreaterThanOrEqual(4); for (const count of counts.values()) { expect(count / total).toBeLessThan(0.6); } }); it('generates bogs (low elevation)', () => { const world = generateWorld(forest, 42); const bogId = forest.tiles.find(t => t.name === 'bog')?.id; expect(world.grid.some(row => row.includes(bogId!))).toBe(true); }); it('generates herb patches (resources)', () => { const world = generateWorld(forest, 42); const herbId = forest.tiles.find(t => t.resource)?.id; expect(world.grid.some(row => row.includes(herbId!))).toBe(true); }); });