diff --git a/packages/engine-ts/src/biomeRegistry.ts b/packages/engine-ts/src/biomeRegistry.ts index 4c15759d..b2d1c93e 100644 --- a/packages/engine-ts/src/biomeRegistry.ts +++ b/packages/engine-ts/src/biomeRegistry.ts @@ -13,7 +13,7 @@ export interface BiomeEntry { tags: readonly BiomeTag[] } -export type BiomeCategory = 'water' | 'coastal' | 'ice' | 'cold' | 'temperate' | 'tropical' | 'elevation' | 'wetland' | 'special' | 'grassland' +export type BiomeCategory = 'water' | 'coastal' | 'ice' | 'cold' | 'temperate' | 'tropical' | 'elevation' | 'wetland' | 'special' | 'grassland' | 'volcanic' | 'arid' // ── Canonical biome definitions ────────────────────────────────────────── // Order matters: index position = encoded terrain value in snapshot textures. @@ -58,6 +58,17 @@ export const BIOME_REGISTRY: readonly BiomeEntry[] = [ // Special { id: 'volcanic', name: 'Volcanic', color: [0.65, 0.18, 0.08], category: 'special', tags: ['is_elevated', 'is_volcanic'] }, { id: 'subterranean', name: 'Subterranean', color: [0.32, 0.24, 0.18], category: 'special', tags: ['is_subterranean'] }, + // Volcanic worlds (Venus, flood basalt planets) + { id: 'lava_field', name: 'Lava Field', color: [0.70, 0.15, 0.05], category: 'volcanic', tags: ['is_volcanic'] }, + { id: 'caldera', name: 'Caldera', color: [0.45, 0.10, 0.08], category: 'volcanic', tags: ['is_elevated', 'is_volcanic'] }, + { id: 'basalt_highland', name: 'Basalt Highland', color: [0.30, 0.25, 0.22], category: 'volcanic', tags: ['is_elevated', 'is_volcanic'] }, + { id: 'volcanic_plains', name: 'Volcanic Plains', color: [0.25, 0.18, 0.14], category: 'volcanic', tags: ['is_volcanic'] }, + { id: 'lowland_basin', name: 'Lowland Basin', color: [0.20, 0.12, 0.10], category: 'volcanic', tags: ['is_volcanic'] }, + // Arid worlds (Mars, dry dead planets) + { id: 'dust_plain', name: 'Dust Plain', color: [0.72, 0.42, 0.28], category: 'arid', tags: ['is_dry'] }, + { id: 'canyon', name: 'Canyon', color: [0.60, 0.28, 0.16], category: 'arid', tags: ['is_dry', 'is_elevated'] }, + { id: 'ancient_lakebed', name: 'Ancient Lakebed', color: [0.55, 0.45, 0.32], category: 'arid', tags: ['is_dry'] }, + { id: 'dune_field', name: 'Dune Field', color: [0.80, 0.55, 0.35], category: 'arid', tags: ['is_dry'] }, ] as const // ── Runtime biome tags (IDs created during simulation, not in registry) ─── @@ -119,6 +130,8 @@ export const CARTO_CATEGORY_COLORS: Record b.category === 'temperate' || b.category === 'grassland') }, { label: 'Tropical', biomes: BIOME_REGISTRY.filter(b => b.category === 'tropical') }, { label: 'Elevation', biomes: BIOME_REGISTRY.filter(b => b.category === 'elevation') }, - { label: 'Wetland', biomes: BIOME_REGISTRY.filter(b => b.category === 'wetland') }, - { label: 'Special', biomes: BIOME_REGISTRY.filter(b => b.category === 'special') }, + { label: 'Wetland', biomes: BIOME_REGISTRY.filter(b => b.category === 'wetland') }, + { label: 'Special', biomes: BIOME_REGISTRY.filter(b => b.category === 'special') }, + { label: 'Volcanic Worlds', biomes: BIOME_REGISTRY.filter(b => b.category === 'volcanic') }, + { label: 'Arid Worlds', biomes: BIOME_REGISTRY.filter(b => b.category === 'arid') }, ] diff --git a/packages/engine-ts/src/scenarios.ts b/packages/engine-ts/src/scenarios.ts index e65faba1..02fc7ecf 100644 --- a/packages/engine-ts/src/scenarios.ts +++ b/packages/engine-ts/src/scenarios.ts @@ -349,6 +349,157 @@ const volcanicWinter: ScenarioConfig = { }, } +// --------------------------------------------------------------------------- +// Venus scenarios +// --------------------------------------------------------------------------- + +/** + * Apply Venus baseline tile state: extreme temperature, zero moisture, high aerosol. + */ +function applyVenusBaseline(grid: GridState, tempBase: number, tempJitter: number): void { + applyBiologyTemplate(grid) + for (const tile of grid.tiles) { + tile.temperature = tempBase + (Math.random() * tempJitter - tempJitter / 2) + tile.moisture = 0.0 + tile.surface_water = 0.0 + tile.sulfate_aerosol = 0.45 + tile.humidity = 0.0 + tile.relative_humidity = 0.0 + tile.dew_point = 0.0 + tile.cape = 0.0 + } + grid.total_ocean_water = 0.0 + grid.o2_fraction = 0.0 + grid.co2_ppm = 965000 +} + +const venusModern: ScenarioConfig = { + id: 'venus_modern', + planet: 'venus', + worldAge: DEFAULT_WORLD_AGE, + name: 'Venus Today', + description: + 'Modern Venus: 460°C surface, 92 atm CO2 atmosphere, no water cycle, active volcanism. ' + + 'Sulfuric acid clouds persist at 45–70 km altitude. The planet is a cautionary tale of runaway greenhouse.', + startingAtmosphere: 'primordial', + abioticWorld: true, + initMap: (grid) => { + applyVenusBaseline(grid, 0.95, 0.04) + // Volcanic highlands are slightly cooler (higher elevation = less atmospheric pressure) + for (const tile of grid.tiles) { + if (tile.elevation > 0.65) { + tile.temperature = Math.max(0.88, tile.temperature - tile.elevation * 0.06) + } + } + }, +} + +const venusPrimordial: ScenarioConfig = { + id: 'venus_primordial', + planet: 'venus', + worldAge: 0, + name: 'Ancient Venus', + description: + 'Venus 2–3 billion years ago, when solar output was lower and liquid water may have existed on the surface. ' + + 'Watch the runaway greenhouse take hold as the simulation advances.', + startingAtmosphere: 'primordial', + abioticWorld: true, + initMap: (grid) => { + applyBiologyTemplate(grid) + for (const tile of grid.tiles) { + // Much cooler ancient Venus — near habitable but already warming + tile.temperature = 0.55 + (Math.random() * 0.10 - 0.05) + tile.moisture = 0.02 // trace water before boiloff + tile.surface_water = 0.0 + tile.sulfate_aerosol = 0.05 + tile.humidity = 0.0 + tile.relative_humidity = 0.0 + tile.dew_point = 0.0 + tile.cape = 0.0 + } + grid.total_ocean_water = 0.0 + grid.co2_ppm = 50000 + }, +} + +// --------------------------------------------------------------------------- +// Mars scenarios +// --------------------------------------------------------------------------- + +/** + * Apply Mars baseline tile state: cold, dry, minimal aerosol. + */ +function applyMarsBaseline(grid: GridState, tempBase: number, tempJitter: number): void { + applyBiologyTemplate(grid) + for (const tile of grid.tiles) { + tile.temperature = tempBase + (Math.random() * tempJitter - tempJitter / 2) + tile.moisture = 0.0 + tile.surface_water = 0.0 + tile.sulfate_aerosol = 0.008 + tile.humidity = 0.0 + tile.relative_humidity = 0.0 + tile.dew_point = 0.0 + tile.cape = 0.0 + } + grid.total_ocean_water = 0.0 + grid.o2_fraction = 0.0 + grid.co2_ppm = 953000 +} + +const marsModern: ScenarioConfig = { + id: 'mars_modern', + planet: 'mars', + worldAge: DEFAULT_WORLD_AGE, + name: 'Mars Today', + description: + 'Modern Mars: -60°C average, 6 mbar CO2 atmosphere, no water cycle, global dust storms. ' + + 'Seasonal CO2 sublimation drives polar cap dynamics. Dust aerosols reshape the regolith plains.', + startingAtmosphere: 'primordial', + abioticWorld: true, + initMap: (grid) => { + applyMarsBaseline(grid, 0.10, 0.06) + // Polar caps: freeze tiles at high/low latitudes + for (const tile of grid.tiles) { + if (tile.row < 3 || tile.row >= grid.height - 3) { + tile.temperature = Math.max(0.01, tile.temperature * 0.5) + } + // Tharsis / Olympus Mons: elevated region runs warmer by day + if (tile.elevation > 0.70) { + tile.temperature = Math.max(0.04, tile.temperature - 0.03) + } + } + }, +} + +const marsAncient: ScenarioConfig = { + id: 'mars_ancient', + planet: 'mars', + worldAge: 0, + name: 'Ancient Mars', + description: + 'Noachian-era Mars 3.8 billion years ago: warmer, wetter, with a thicker atmosphere and liquid water flowing through river valleys into shallow seas. ' + + 'Watch the water cycle collapse as the magnetic field fails and the atmosphere is stripped.', + startingAtmosphere: 'primordial', + abioticWorld: true, + initMap: (grid) => { + applyBiologyTemplate(grid) + for (const tile of grid.tiles) { + // Warmer ancient Mars — liquid water was possible + tile.temperature = 0.28 + (Math.random() * 0.10 - 0.05) + // Low-lying areas have trace moisture (ancient river/sea basins) + tile.moisture = tile.elevation < 0.30 ? 0.15 : 0.02 + tile.surface_water = 0.0 + tile.sulfate_aerosol = 0.004 + tile.humidity = 0.0 + tile.relative_humidity = 0.0 + tile.dew_point = 0.0 + tile.cape = 0.0 + } + grid.total_ocean_water = 0.0 + grid.co2_ppm = 200000 + }, +} + // --------------------------------------------------------------------------- // Export — classic first, then advanced scenarios // --------------------------------------------------------------------------- @@ -368,4 +519,8 @@ export const SCENARIOS: ScenarioConfig[] = [ trophicCascade, methaneRunaway, supervolcano, + venusModern, + venusPrimordial, + marsModern, + marsAncient, ] diff --git a/packages/engine-ts/src/types.ts b/packages/engine-ts/src/types.ts index 34016252..e55300d4 100644 --- a/packages/engine-ts/src/types.ts +++ b/packages/engine-ts/src/types.ts @@ -266,6 +266,8 @@ export interface ScenarioConfig { startingAtmosphere?: 'modern' | 'depleted' | 'lush' | 'primordial' /** Skip ecology engine entirely — used for pre-biotic worlds where life cannot spontaneously arise. */ abioticWorld?: boolean + /** Which planet this scenario belongs to. Defaults to 'earth' if omitted. */ + planet?: string } /** Opaque handle for continuing a simulation beyond its initial run. */ diff --git a/packages/engine-ts/src/worker-protocol.ts b/packages/engine-ts/src/worker-protocol.ts index b67e0b52..3594c3f5 100644 --- a/packages/engine-ts/src/worker-protocol.ts +++ b/packages/engine-ts/src/worker-protocol.ts @@ -20,6 +20,10 @@ export interface InitCommand { spec?: Record /** Easter egg seed table from game data (seed_easter_eggs.json). Keys are seed integers as strings. */ easterEggs?: Record + /** Per-planet climate params keyed by planet id. If present, overrides `params` for non-earth scenarios. */ + allPlanetParams?: Record> + /** Per-planet climate spec keyed by planet id. */ + allPlanetSpecs?: Record> } export interface RunCommand {