Files
synthesis/tests/world.test.ts
Денис Шкабатур 6ba0746bb9 phase 9: biome expansion — 3 biomes, 40 elements, 119 reactions, 9 species
Expand beyond vertical slice with two new biomes and massive chemistry expansion:

Chemistry: +20 real elements (Li→U), +39 compounds (acids/salts/oxides/organics),
+85 reactions (Haber process, thermite variants, smelting, fermentation, etc.)

Biomes: Kinetic Mountains (physics/mechanics themed) and Verdant Forests
(biology/ecology themed), each with 8 tile types and unique generation rules.

Creatures: 6 new species — Pendulums/Mechanoids/Resonators (mountains),
Symbiotes/Mimics/Spore-bearers (forests). Species filtered by biome.

Infrastructure: CradleScene biome selector UI, generic world generator
(tile lookup by property instead of hardcoded names), actinide element category.

487 tests passing (32 new).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 17:27:15 +03:00

321 lines
11 KiB
TypeScript

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<number>();
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<number, number>();
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<number, number>();
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<number, number>();
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);
});
});