diff --git a/packages/engine-ts/src/ClimatePhysics.generated.ts b/packages/engine-ts/src/ClimatePhysics.generated.ts index c89011bd..fa1126d3 100644 --- a/packages/engine-ts/src/ClimatePhysics.generated.ts +++ b/packages/engine-ts/src/ClimatePhysics.generated.ts @@ -60,6 +60,7 @@ export function idealTerrain( const temp = tile.temperature const moist = tile.moisture const elev = tile.elevation + const canopy = tile.canopy_cover ?? 0 const transitions = (spec['terrain_transitions'] ?? {}) as Record>> const rules: Array> = transitions[tid] ?? [] @@ -67,7 +68,7 @@ export function idealTerrain( for (const rule of rules) { const cond = rule['condition'] as string ?? '' - if (evalCondition(cond, temp, moist, elev)) { + if (evalCondition(cond, temp, moist, elev, canopy)) { const becomes = rule['becomes'] as string ?? '' if (becomes === 'classify') return classifyTerrain(temp, moist, elev) return becomes @@ -88,17 +89,17 @@ export function leyChannelingMult( return leySpec['on_ley_generic'] ?? 2.0 } -export function evalCondition(cond: string, temp: number, moist: number, elev: number): boolean { +export function evalCondition(cond: string, temp: number, moist: number, elev: number, canopy = 0): boolean { if (cond.includes(' OR ')) { - return cond.split(' OR ').some(part => evalCondition(part.trim(), temp, moist, elev)) + return cond.split(' OR ').some(part => evalCondition(part.trim(), temp, moist, elev, canopy)) } if (cond.includes(' AND ')) { - return cond.split(' AND ').every(part => evalCondition(part.trim(), temp, moist, elev)) + return cond.split(' AND ').every(part => evalCondition(part.trim(), temp, moist, elev, canopy)) } let clean = cond.trim() if (clean.startsWith('(') && clean.endsWith(')')) clean = clean.slice(1, -1) const tokens = clean.trim().split(' ') - if (tokens.length < 3) { console.warn(`ClimateSpecEval: bad condition: '${cond}'`); return false } + if (tokens.length < 3) return false const [field, op, valStr] = tokens const value = parseFloat(valStr) let actual: number @@ -106,7 +107,8 @@ export function evalCondition(cond: string, temp: number, moist: number, elev: n case 'temperature': actual = temp; break case 'moisture': actual = moist; break case 'elevation': actual = elev; break - default: console.warn(`ClimateSpecEval: unknown field '${field}'`); return false + case 'canopy': actual = canopy; break + default: return false } switch (op) { case '<': return actual < value @@ -265,7 +267,7 @@ export class ClimatePhysics { let any_aerosol = false for (let i = 0; i < tiles.length; i++) { - if (((tiles[i] as any).sulfate_aerosol ?? 0) > 0.001) { any_aerosol = true; break } + if (((tiles[i] as any).sulfate_aerosol ?? 0) > 0.1) { any_aerosol = true; break } } if (!any_aerosol) return const cooling_rate = aerosol_cfg['cooling_rate'] ?? 0.06 diff --git a/packages/engine-ts/src/EcologyPhysics.generated.ts b/packages/engine-ts/src/EcologyPhysics.generated.ts index 0bc803f2..f70f9164 100644 --- a/packages/engine-ts/src/EcologyPhysics.generated.ts +++ b/packages/engine-ts/src/EcologyPhysics.generated.ts @@ -56,7 +56,7 @@ function getBiome(biomeId: string): BiomeDef | null { // --------------------------------------------------------------------------- function _getSubstrate(tile: TileState): string { - if (true) { + if (tile.substrate_id !== "") { return tile.substrate_id } return "" @@ -262,7 +262,7 @@ function classifyBiome(tile: TileState): string { // --------------------------------------------------------------------------- function _isWater(tile: TileState): boolean { - if (true) { + if (tile.substrate_id !== "") { let sub = tile.substrate_id return ["deep_water", "shallow_water", "lake_bed"].includes(sub) } diff --git a/packages/engine-ts/src/MapGenerator.generated.ts b/packages/engine-ts/src/MapGenerator.generated.ts index 17b2aba9..2acecfeb 100644 --- a/packages/engine-ts/src/MapGenerator.generated.ts +++ b/packages/engine-ts/src/MapGenerator.generated.ts @@ -249,7 +249,7 @@ class GenMap { wind_direction: gt?.wind_direction ?? 0, wind_speed: gt?.wind_speed ?? 0.5, quality: gt?.quality ?? 2, - quality_progress: gt?.quality_progress ?? 0, + quality_progress: gt?.quality_progress ?? (((col * 7 + row * 13) % 11) - 5), // stagger -5..+5 to prevent synchronized biome flips river_edges: gt?.river_edges ?? [], flow_accumulation: gt?.flow_accumulation ?? 0.0, original_biome_id: '', @@ -272,6 +272,7 @@ class GenMap { pressure: 1013.0, pressure_anomaly: 0.0, humidity: 0.0, + sulfate_aerosol: 0.0, // Ecology fields (populated by EcologyPhysics) canopy_cover: 0.0, undergrowth: 0.0, diff --git a/packages/engine-ts/src/runner.ts b/packages/engine-ts/src/runner.ts index c36c215a..eac4f2ac 100644 --- a/packages/engine-ts/src/runner.ts +++ b/packages/engine-ts/src/runner.ts @@ -81,50 +81,6 @@ function encodeTerrainId(terrainId: string): number { return i / (TERRAIN_ORDER.length - 1) } -// --------------------------------------------------------------------------- -// Terrain cache -// --------------------------------------------------------------------------- - -export function buildTerrainCache(): Map { - const terrainMods = import.meta.glob( - '@data/terrain/*.json', - { eager: true, import: 'default' }, - ) as Record - - const cache = new Map() - for (const mod of Object.values(terrainMods)) { - if (!mod?.terrains) continue - for (const terrain of mod.terrains) { - cache.set(terrain.id, terrain) - } - } - return cache -} - -// --------------------------------------------------------------------------- -// Params loading -// --------------------------------------------------------------------------- - -type ClimateParamsRaw = Record> - -function flattenParams(raw: ClimateParamsRaw): Record { - const out: Record = {} - for (const [k, v] of Object.entries(raw)) { - if (typeof v === 'number') out[k] = v - } - return out -} - -export function loadClimateParams(): Record { - const mods = import.meta.glob( - '@data/climate_params.json', - { eager: true, import: 'default' }, - ) as Record - const raw = Object.values(mods)[0] - if (!raw) throw new Error('climate_params.json not found via @data/ alias') - return flattenParams(raw) -} - // --------------------------------------------------------------------------- // Snapshot encoding // --------------------------------------------------------------------------- @@ -133,7 +89,6 @@ export function encodeSnapshot( grid: GridState, turn: number, events: EcologicalEvent[] = [], - terrainCache?: Map, prevStats?: TurnStats, ): GridSnapshot { const n = grid.tiles.length @@ -163,7 +118,7 @@ export function encodeSnapshot( texC[base + 3] = tile.habitat_suitability ?? 0.0 } - const stats = computeTurnStats(grid, terrainCache, prevStats) + const stats = computeTurnStats(grid, prevStats) return { texA, texB, texC, @@ -183,7 +138,6 @@ const EMPTY_SCHOOL_RECORD: Record = { death: 0, life: 0, natu export function computeTurnStats( grid: GridState, - terrainCache?: Map, prevStats?: TurnStats, ): TurnStats { const { tiles, width, height } = grid @@ -224,7 +178,7 @@ export function computeTurnStats( albedoSum += albedo solarSum += solar * (1.0 - albedo) - aerosolSum += (tile as Record).sulfate_aerosol ?? 0 + aerosolSum += tile.sulfate_aerosol ?? 0 windSpeedSum += tile.wind_speed ?? 0 if (isWater) { @@ -297,6 +251,7 @@ export function cloneGridState(grid: GridState): GridState { global_avg_temp: grid.global_avg_temp, ocean_dead_fraction: grid.ocean_dead_fraction, ecosystem_health: grid.ecosystem_health, + sea_level: grid.sea_level, tiles: grid.tiles.map((t) => ({ ...t, river_edges: [...t.river_edges], @@ -371,7 +326,7 @@ export function runScenarioSync( const events = physics.processStep(grid, turn, worldSeed) ecology.processStep(grid) const prev = snapshots.length > 0 ? snapshots[snapshots.length - 1].stats : undefined - snapshots.push(encodeSnapshot(grid, scenarioTurn, events, terrainCache, prev)) + snapshots.push(encodeSnapshot(grid, scenarioTurn, events, prev)) } return { @@ -409,7 +364,7 @@ export function extendSimulation( const events = physics.processStep(grid, turn, worldSeed) ecology.processStep(grid) const prev = snapshots.length > 0 ? snapshots[snapshots.length - 1].stats : undefined - snapshots.push(encodeSnapshot(grid, scenarioTurn, events, terrainCache, prev)) + snapshots.push(encodeSnapshot(grid, scenarioTurn, events, prev)) } return { diff --git a/packages/engine-ts/src/types.ts b/packages/engine-ts/src/types.ts index 0f766fc7..e0ba7592 100644 --- a/packages/engine-ts/src/types.ts +++ b/packages/engine-ts/src/types.ts @@ -13,6 +13,7 @@ export interface TileState { pressure: number // atmospheric pressure (hPa, ~995-1030) pressure_anomaly: number // dynamic anomaly offset from baseline humidity: number // atmospheric humidity [0, 1] + sulfate_aerosol: number // stratospheric aerosol opacity [0, 1] from volcanic/impact events quality: number // 1-5 (Q1 prolific .. Q5 epic) quality_progress: number // counter toward next quality change river_edges: number[] // edge indices [0-5] where rivers flow @@ -167,7 +168,7 @@ export interface SimulationResult { continuation: ContinuationState } -// Terrain data shape from games/age-of-four/data/terrain/*.json +// Terrain data shape from games/age-of-dwarves/data/terrain/*.json export interface TerrainData { id: string name: string diff --git a/packages/engine-ts/src/worker-protocol.ts b/packages/engine-ts/src/worker-protocol.ts index ede07adc..405645a4 100644 --- a/packages/engine-ts/src/worker-protocol.ts +++ b/packages/engine-ts/src/worker-protocol.ts @@ -15,6 +15,7 @@ export interface InitCommand { type: 'init' terrainData: Record params: Record + spec?: Record } export interface RunCommand {