Skip to main content
Glama
quality-gates.test.ts12 kB
/** * Quality Gates for World Generation * * These tests define what "good" world generation looks like, inspired by * Azgaar's Fantasy Map Generator quality benchmarks. * * Tests ensure: * - Terrain continuity (no abrupt elevation jumps) * - Biome plausibility (realistic climate-biome relationships) * - River validity (downhill flow, no loops, proper branching) */ import { describe, it, expect } from 'vitest'; import { generateWorld } from '../../src/engine/worldgen/index'; import { BiomeType } from '../../src/schema/biome'; describe('Quality Gate: Terrain Continuity', () => { it('should have no abrupt elevation jumps between adjacent tiles', () => { const seed = 'terrain-continuity-test'; const width = 100; const height = 100; const world = generateWorld({ seed, width, height }); const heightmap = world.elevation; // Check all tiles for smooth transitions const maxAllowedJump = 30; // 30 units on 0-100 scale let abruptJumps = 0; for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const current = heightmap[y * width + x]; // Check 4-connected neighbors (N, E, S, W) const neighbors = [ y > 0 ? heightmap[(y - 1) * width + x] : null, // North x < width - 1 ? heightmap[y * width + x + 1] : null, // East y < height - 1 ? heightmap[(y + 1) * width + x] : null, // South x > 0 ? heightmap[y * width + x - 1] : null // West ].filter((n): n is number => n !== null); for (const neighbor of neighbors) { const diff = Math.abs(current - neighbor); if (diff > maxAllowedJump) { abruptJumps++; } } } } // Allow up to 0.5% of connections to be abrupt (natural cliffs) const totalConnections = (width - 1) * height + width * (height - 1); const allowedAbruptJumps = Math.floor(totalConnections * 0.005); expect(abruptJumps).toBeLessThanOrEqual(allowedAbruptJumps); }); it('should have realistic land/sea ratio (20-40% land)', () => { const seed = 'land-sea-ratio-test'; const width = 100; const height = 100; const world = generateWorld({ seed, width, height }); const heightmap = world.elevation; // Sea level at 20 (based on our heightmap implementation 0-100 scale) const seaLevel = 20; let landTiles = 0; for (let i = 0; i < width * height; i++) { if (heightmap[i] >= seaLevel) { landTiles++; } } const totalTiles = width * height; const landRatio = landTiles / totalTiles; // Allow wider range for procedural generation variability expect(landRatio).toBeGreaterThanOrEqual(0.05); expect(landRatio).toBeLessThanOrEqual(0.6); }); it('should have smooth elevation distribution (no extreme spikes)', () => { const seed = 'elevation-distribution-test'; const width = 100; const height = 100; const world = generateWorld({ seed, width, height }); const heightmap = world.elevation; // Use TypedArray directly (it is already flat) const elevations = heightmap; // Calculate mean and standard deviation const mean = elevations.reduce((sum, e) => sum + e, 0) / elevations.length; const variance = elevations.reduce((sum, e) => sum + Math.pow(e - mean, 2), 0) / elevations.length; const stdDev = Math.sqrt(variance); // Count values beyond 3 standard deviations (outliers) const outliers = elevations.filter(e => Math.abs(e - mean) > 3 * stdDev).length; const outlierRatio = outliers / elevations.length; // Allow up to 3% outliers (mountain peaks, deep ocean trenches) expect(outlierRatio).toBeLessThanOrEqual(0.03); }); it('should have connected landmasses (no floating single-tile islands)', () => { const seed = 'landmass-connectivity-test'; const width = 100; const height = 100; const world = generateWorld({ seed, width, height }); const heightmap = world.elevation; const seaLevel = 20; // Find all single-tile islands let singleTileIslands = 0; for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const current = heightmap[y * width + x]; // Skip if not land if (current < seaLevel) continue; // Check if all neighbors are water const neighbors = [ y > 0 ? heightmap[(y - 1) * width + x] : null, x < width - 1 ? heightmap[y * width + x + 1] : null, y < height - 1 ? heightmap[(y + 1) * width + x] : null, x > 0 ? heightmap[y * width + x - 1] : null ].filter((n): n is number => n !== null); const allNeighborsWater = neighbors.every(n => n < seaLevel); if (allNeighborsWater) { singleTileIslands++; } } } // Allow up to 0.5% single-tile islands (realistic for archipelagos) const totalLandTiles = heightmap.filter(e => e >= seaLevel).length; const allowedSingleTiles = Math.floor(totalLandTiles * 0.005); expect(singleTileIslands).toBeLessThanOrEqual(allowedSingleTiles); }); }); describe('Quality Gate: Biome Plausibility', () => { it('should not place tropical biomes next to polar biomes', () => { const seed = 'biome-adjacency-test'; const width = 100; const height = 100; const world = generateWorld({ seed, width, height }); const biomeMap = world.biomes; // Define incompatible biome pairs (tropical vs polar) const tropicalBiomes = [BiomeType.RAINFOREST, BiomeType.SAVANNA]; const polarBiomes = [BiomeType.TUNDRA, BiomeType.GLACIER]; let incompatibleAdjacencies = 0; for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const current = biomeMap[y][x]; const neighbors = [ y > 0 ? biomeMap[y - 1][x] : null, x < width - 1 ? biomeMap[y][x + 1] : null, y < height - 1 ? biomeMap[y + 1][x] : null, x > 0 ? biomeMap[y][x - 1] : null ].filter((n): n is BiomeType => n !== null); const isTropical = tropicalBiomes.includes(current); const hasPolarNeighbor = neighbors.some(n => polarBiomes.includes(n)); const isPolar = polarBiomes.includes(current); const hasTropicalNeighbor = neighbors.some(n => tropicalBiomes.includes(n)); if ((isTropical && hasPolarNeighbor) || (isPolar && hasTropicalNeighbor)) { incompatibleAdjacencies++; } } } // Strict: no direct tropical-polar adjacencies allowed expect(incompatibleAdjacencies).toBe(0); }); it('should correlate cold biomes with high latitudes', () => { const seed = 'latitude-biome-correlation-test'; const width = 100; const height = 100; const world = generateWorld({ seed, width, height }); const biomeMap = world.biomes; const polarBiomes = [BiomeType.TUNDRA, BiomeType.GLACIER, BiomeType.TAIGA]; // Northern 20% should have more polar biomes const northernThird = Math.floor(height * 0.2); let northernPolarCount = 0; let northernTotalLand = 0; for (let y = 0; y < northernThird; y++) { for (let x = 0; x < width; x++) { const biome = biomeMap[y][x]; if (biome !== BiomeType.OCEAN && biome !== BiomeType.DEEP_OCEAN) { northernTotalLand++; if (polarBiomes.includes(biome)) { northernPolarCount++; } } } } // Southern 20% should have more polar biomes const southernThird = Math.floor(height * 0.8); let southernPolarCount = 0; let southernTotalLand = 0; for (let y = southernThird; y < height; y++) { for (let x = 0; x < width; x++) { const biome = biomeMap[y][x]; if (biome !== BiomeType.OCEAN && biome !== BiomeType.DEEP_OCEAN) { southernTotalLand++; if (polarBiomes.includes(biome)) { southernPolarCount++; } } } } // At least 30% of polar regions should be polar biomes const northernPolarRatio = northernTotalLand > 0 ? northernPolarCount / northernTotalLand : 0; const southernPolarRatio = southernTotalLand > 0 ? southernPolarCount / southernTotalLand : 0; // Skip test if no land in polar regions (rare seed) if (northernTotalLand > 10) { expect(northernPolarRatio).toBeGreaterThanOrEqual(0.3); } if (southernTotalLand > 10) { expect(southernPolarRatio).toBeGreaterThanOrEqual(0.3); } }); it('should have diverse biome distribution (not dominated by single type)', () => { const seed = 'biome-diversity-test'; const width = 100; const height = 100; const world = generateWorld({ seed, width, height }); const biomeMap = world.biomes; // Count each biome type const biomeCounts = new Map<BiomeType, number>(); const landBiomes = biomeMap.flat().filter(b => b !== BiomeType.OCEAN && b !== BiomeType.DEEP_OCEAN); for (const biome of landBiomes) { biomeCounts.set(biome, (biomeCounts.get(biome) || 0) + 1); } // No single land biome should dominate more than 50% of land const totalLand = landBiomes.length; for (const [_biome, count] of biomeCounts.entries()) { const ratio = count / totalLand; expect(ratio).toBeLessThanOrEqual(0.5); } // Should have at least 3 different land biome types expect(biomeCounts.size).toBeGreaterThanOrEqual(3); }); it('should match biomes to climate zones (temperature/moisture logic)', () => { const seed = 'climate-biome-match-test'; const width = 100; const height = 100; const world = generateWorld({ seed, width, height }); const biomeMap = world.biomes; // Verify biome assignments match climate rules let invalidAssignments = 0; for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const biome = biomeMap[y][x]; const temp = world.temperature[y * width + x]; const moisture = world.moisture[y * width + x]; // Skip ocean biomes if (biome === BiomeType.OCEAN || biome === BiomeType.DEEP_OCEAN) continue; // Validation rules based on temperature/moisture // Rainforest: Hot (>19°C) + Very wet (>60% moisture) if (biome === BiomeType.RAINFOREST && (temp < 19 || moisture < 60)) { invalidAssignments++; } // Glacier: Very cold (<-10°C) + High moisture if (biome === BiomeType.GLACIER && temp > -10) { invalidAssignments++; } // Desert: Hot (>19°C) + Very dry (<20% moisture) if (biome === BiomeType.DESERT && (temp < 19 || moisture > 20)) { invalidAssignments++; } // Tundra: Cold (<0°C) if (biome === BiomeType.TUNDRA && temp > 0) { invalidAssignments++; } } } // Allow up to 2% mismatches (edge cases, transitions) const totalTiles = width * height; const allowedInvalid = Math.floor(totalTiles * 0.02); expect(invalidAssignments).toBeLessThanOrEqual(allowedInvalid); }); }); describe('Quality Gate: World Completeness', () => { it('should generate all world components', () => { const seed = 'completeness-test'; const width = 50; const height = 50; const world = generateWorld({ seed, width, height, numRegions: 5, numCities: 2, numTowns: 5, numDungeons: 2 }); // Check Rivers // Count river tiles let riverTiles = 0; for (let i = 0; i < width * height; i++) { if (world.rivers[i] > 0) riverTiles++; } // Should have some rivers (unless map is tiny/dry, but default settings usually produce some) expect(riverTiles).toBeGreaterThanOrEqual(0); // Can be 0 if random seed is dry, but usually >0 // Check Regions expect(world.regions.length).toBeGreaterThan(0); expect(world.regionMap.length).toBe(width * height); // Check Structures expect(world.structures.length).toBeGreaterThan(0); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Mnehmos/rpg-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server