diff --git a/packages/engine-ts/src/ClimatePhysics.generated.ts b/packages/engine-ts/src/ClimatePhysics.generated.ts index 848be6f9..fd2b3690 100644 --- a/packages/engine-ts/src/ClimatePhysics.generated.ts +++ b/packages/engine-ts/src/ClimatePhysics.generated.ts @@ -27,6 +27,8 @@ const CLIMATE_DEFAULTS: Record = { lake_thermal_conductivity: 0.05, river_moisture_transport: 0.075, mountain_rain_shadow_block: 0.9, + solar_min: 0.15, + solar_max: 0.70, } const DEW_DEFAULTS: Record = { @@ -283,7 +285,7 @@ export class ClimatePhysics { for (let i = 0; i < tiles.length; i++) { const tile = tiles[i] const { col, row } = tile - let solar = solarByRow(row, h) + let solar = solarByRow(row, h, this.p('solar_min', 0.15), this.p('solar_max', 0.70)) let current_temp = oldTemp[i] let terrain_data = (this.terrainCache.get(tile.biome_id) ?? {}) diff --git a/packages/engine-ts/src/EcologyPhysics.generated.ts b/packages/engine-ts/src/EcologyPhysics.generated.ts new file mode 100644 index 00000000..e474480c --- /dev/null +++ b/packages/engine-ts/src/EcologyPhysics.generated.ts @@ -0,0 +1,588 @@ +// AUTO-GENERATED from GDScript ecology engine — do not edit manually. +// Source: engine/src/modules/ecology/flora.gd + fauna.gd + ecosystem.gd +// Regenerate: uv run tools/transpile-engine/transpile.py + +import type { GridState, TileState } from './types' +import { idx, neighbors } from './HexGrid' + +// --------------------------------------------------------------------------- +// Biome definitions (proof set — matches games/age-of-dwarves/data/world/) +// --------------------------------------------------------------------------- + +interface BiomeDef { + id: string + temp_range: [number, number] + moisture_range: [number, number] + flora_climax: { canopy: number; undergrowth: number; fungi: number } + fauna_capacity: number + quality_range: [number, number] +} + +const BIOME_DEFS: Record = { + temperate_forest: { + id: 'temperate_forest', + temp_range: [0.35, 0.65], + moisture_range: [0.4, 0.8], + flora_climax: { canopy: 0.9, undergrowth: 0.7, fungi: 0.5 }, + fauna_capacity: 12, + quality_range: [1, 5], + }, + tropical_rainforest: { + id: 'tropical_rainforest', + temp_range: [0.65, 1.0], + moisture_range: [0.6, 1.0], + flora_climax: { canopy: 1.0, undergrowth: 0.9, fungi: 0.8 }, + fauna_capacity: 16, + quality_range: [1, 5], + }, + grassland: { + id: 'grassland', + temp_range: [0.3, 0.7], + moisture_range: [0.2, 0.5], + flora_climax: { canopy: 0.1, undergrowth: 0.8, fungi: 0.2 }, + fauna_capacity: 8, + quality_range: [1, 4], + }, + desert: { + id: 'desert', + temp_range: [0.5, 1.0], + moisture_range: [0.0, 0.2], + flora_climax: { canopy: 0.0, undergrowth: 0.1, fungi: 0.0 }, + fauna_capacity: 3, + quality_range: [1, 3], + }, + boreal_forest: { + id: 'boreal_forest', + temp_range: [0.15, 0.4], + moisture_range: [0.3, 0.7], + flora_climax: { canopy: 0.7, undergrowth: 0.4, fungi: 0.6 }, + fauna_capacity: 8, + quality_range: [1, 5], + }, + tundra: { + id: 'tundra', + temp_range: [0.0, 0.2], + moisture_range: [0.1, 0.5], + flora_climax: { canopy: 0.0, undergrowth: 0.2, fungi: 0.1 }, + fauna_capacity: 4, + quality_range: [1, 3], + }, +} + +function getBiome(biomeId: string): BiomeDef | null { + return BIOME_DEFS[biomeId] ?? null +} + +// --------------------------------------------------------------------------- +// BiomeClassifier — substrate + climate + flora → biome_id +// Faithfully ports engine/src/models/world/biome_classifier.gd +// --------------------------------------------------------------------------- + +function classifyBiome(tile: TileState): string { + const sub = tile.substrate_id + // Aquatic tiles keep their terrain_id + if (sub === 'deep_water' || sub === 'shallow_water' || sub === 'lake_bed') { + return tile.terrain_id + } + + const temp = tile.temperature + const moist = tile.moisture + + // Wetland override + if (sub === 'wetland') return 'swamp' + + // Temperature-driven classification + if (temp < 0.15) return 'tundra' + if (temp < 0.4) { + if (moist > 0.3 && tile.canopy_cover > 0.3) return 'boreal_forest' + if (moist > 0.3) return 'grassland' + return 'tundra' + } + if (temp < 0.65) { + if (moist > 0.4 && tile.canopy_cover > 0.5) return 'temperate_forest' + if (moist > 0.2) return 'grassland' + return 'desert' + } + // Hot + if (moist > 0.6 && tile.canopy_cover > 0.6) return 'tropical_rainforest' + if (moist > 0.3) return 'grassland' + return 'desert' +} + +// --------------------------------------------------------------------------- +// Flora helpers +// --------------------------------------------------------------------------- + +function isWater(tile: TileState): boolean { + const sub = tile.substrate_id + if (sub) { + return sub === 'deep_water' || sub === 'shallow_water' || sub === 'lake_bed' + } + return tile.terrain_id === 'ocean' || tile.terrain_id === 'coast' +} + +function climateMatch(tile: TileState, biome: BiomeDef): number { + const temp = tile.temperature + const moist = tile.moisture + const [tMin, tMax] = biome.temp_range + const [mMin, mMax] = biome.moisture_range + + const tempOk = temp >= tMin && temp <= tMax + const moistOk = moist >= mMin && moist <= mMax + + if (tempOk && moistOk) return 1.0 + + const tempEdge = temp >= tMin - 0.1 && temp <= tMax + 0.1 + const moistEdge = moist >= mMin - 0.1 && moist <= mMax + 0.1 + + if (tempEdge && moistEdge) return 0.5 + return 0.0 +} + +function qualityMult(quality: number): number { + switch (quality) { + case 1: return 0.6 + case 2: return 0.8 + case 4: return 1.2 + case 5: return 1.4 + default: return 1.0 + } +} + +// --------------------------------------------------------------------------- +// Vegetation defaults (from DataLoader fallbacks in flora.gd) +// --------------------------------------------------------------------------- + +const VEG = { + growth_rate: 0.02, + decay_rate: 0.03, + shade_cap: 0.7, + drought_decay_multiplier: 1.5, + fungi_undergrowth_threshold: 0.3, + fungi_regrowth_bonus_cap: 2.0, +} as const + +const SUC = { + stability_turns: 50, + canopy_threshold: 0.8, + regrowth_stages: [ + { stage: 0, turns_to_advance: 10, canopy_target: 0.0, undergrowth_target: 0.1, fungi_target: 0.0 }, + { stage: 1, turns_to_advance: 15, canopy_target: 0.1, undergrowth_target: 0.3, fungi_target: 0.05 }, + { stage: 2, turns_to_advance: 20, canopy_target: 0.4, undergrowth_target: 0.5, fungi_target: 0.2 }, + { stage: 3, turns_to_advance: 25, canopy_target: 0.7, undergrowth_target: 0.6, fungi_target: 0.4 }, + ], +} as const + +const DES = { + moisture_threshold: 0.2, + turns_required: 30, + decay_multiplier: 2.0, + recovery_rate: 1, +} as const + +function getRegrowthStage(stageIdx: number): typeof SUC.regrowth_stages[number] | null { + for (const s of SUC.regrowth_stages) { + if (s.stage === stageIdx) return s + } + return null +} + +// --------------------------------------------------------------------------- +// Flora tick methods (from flora.gd process_turn) +// --------------------------------------------------------------------------- + +function tickCanopy(tiles: TileState[], w: number, h: number): void { + for (let i = 0; i < tiles.length; i++) { + const tile = tiles[i] + if (isWater(tile)) continue + const biome = getBiome(tile.biome_id) + if (!biome) continue + const climax = biome.flora_climax.canopy + const match = climateMatch(tile, biome) + const qm = qualityMult(tile.quality) + if (match > 0.0) { + const delta = VEG.growth_rate * match * qm + tile.canopy_cover = Math.min(tile.canopy_cover + delta, climax) + } else { + tile.canopy_cover = Math.max(tile.canopy_cover - VEG.decay_rate, 0.0) + } + } +} + +function tickUndergrowth(tiles: TileState[], w: number, h: number): void { + for (let i = 0; i < tiles.length; i++) { + const tile = tiles[i] + if (isWater(tile)) continue + const biome = getBiome(tile.biome_id) + if (!biome) continue + const climax = biome.flora_climax.undergrowth + const match = climateMatch(tile, biome) + const qm = qualityMult(tile.quality) + let effectiveCap = climax + if (tile.canopy_cover > VEG.shade_cap) { + effectiveCap = Math.min(climax, VEG.shade_cap) + } + if (match > 0.0) { + const delta = VEG.growth_rate * match * qm + tile.undergrowth = Math.min(tile.undergrowth + delta, effectiveCap) + } else { + let rate = VEG.decay_rate + if (tile.drought_counter > 0) rate *= VEG.drought_decay_multiplier + tile.undergrowth = Math.max(tile.undergrowth - rate, 0.0) + } + } +} + +function tickFungi(tiles: TileState[], w: number, h: number): void { + for (let i = 0; i < tiles.length; i++) { + const tile = tiles[i] + if (isWater(tile)) continue + const biome = getBiome(tile.biome_id) + if (!biome) continue + const climax = biome.flora_climax.fungi + + if (tile.undergrowth < VEG.fungi_undergrowth_threshold) { + tile.fungi_network = Math.max(tile.fungi_network - VEG.decay_rate * 0.5, 0.0) + continue + } + if (tile.moisture < 0.15 || tile.temperature < 0.1) { + tile.fungi_network = Math.max(tile.fungi_network - VEG.decay_rate * 0.5, 0.0) + continue + } + const ugFactor = tile.undergrowth + let oldGrowth = 1.0 + if (tile.canopy_cover > 0.7 && tile.undergrowth > 0.5 && tile.moisture > 0.4) { + oldGrowth = 1.5 + } + const qm = qualityMult(tile.quality) + const delta = VEG.growth_rate * ugFactor * oldGrowth * qm + tile.fungi_network = Math.min(tile.fungi_network + delta, climax) + } +} + +function tickSuccession(tiles: TileState[], w: number, h: number): void { + for (let i = 0; i < tiles.length; i++) { + const tile = tiles[i] + if (isWater(tile)) continue + if (tile.regrowth_stage >= 0) continue + + if (tile.canopy_cover >= SUC.canopy_threshold) { + tile.succession_progress += 1 + } else { + tile.succession_progress = 0 + continue + } + if (tile.succession_progress < SUC.stability_turns) continue + + // Succession triggered — reclassify + const oldBiome = tile.biome_id + const newBiome = classifyBiome(tile) + tile.succession_progress = 0 + if (newBiome !== oldBiome) { + tile.biome_id = newBiome + } + if (tile.quality >= 4 && tile.landmark_name === '') { + tile.landmark_name = `Ancient ${tile.biome_id.replace(/_/g, ' ')}` + } + } +} + +function tickDesertification(tiles: TileState[], w: number, h: number): void { + const baseDecay = VEG.decay_rate + for (let i = 0; i < tiles.length; i++) { + const tile = tiles[i] + if (isWater(tile)) continue + if (tile.moisture < DES.moisture_threshold) { + tile.drought_counter += 1 + const rate = baseDecay * DES.decay_multiplier + tile.canopy_cover = Math.max(tile.canopy_cover - rate, 0.0) + tile.undergrowth = Math.max(tile.undergrowth - rate * 1.5, 0.0) + tile.fungi_network = Math.max(tile.fungi_network - rate, 0.0) + if (tile.drought_counter >= DES.turns_required) { + const oldBiome = tile.biome_id + const newBiome = classifyBiome(tile) + if (newBiome !== oldBiome) tile.biome_id = newBiome + } + } else { + tile.drought_counter = Math.max(tile.drought_counter - DES.recovery_rate, 0) + } + } +} + +function tickRegrowth(tiles: TileState[], w: number, h: number): void { + for (let i = 0; i < tiles.length; i++) { + const tile = tiles[i] + if (tile.regrowth_stage < 0) continue + + tile.regrowth_turns += 1 + const stageData = getRegrowthStage(tile.regrowth_stage) + if (!stageData) continue + + const baseTurns = stageData.turns_to_advance + const fungiBonus = Math.min( + Math.max(1.0 + tile.fungi_network * VEG.fungi_regrowth_bonus_cap, 1.0), + VEG.fungi_regrowth_bonus_cap, + ) + const effectiveTurns = Math.max(1, Math.round(baseTurns / fungiBonus)) + if (tile.regrowth_turns < effectiveTurns) continue + + const nextStage = tile.regrowth_stage + 1 + const nextData = getRegrowthStage(nextStage) + if (!nextData || nextStage > 3) { + tile.regrowth_stage = -1 + tile.regrowth_turns = 0 + continue + } + tile.regrowth_stage = nextStage + tile.regrowth_turns = 0 + tile.canopy_cover = nextData.canopy_target + tile.undergrowth = nextData.undergrowth_target + tile.fungi_network = nextData.fungi_target + if (nextStage >= 3) { + tile.regrowth_stage = -1 + tile.regrowth_turns = 0 + } + } +} + +// --------------------------------------------------------------------------- +// Fauna — simplified for guide (no SQLite creature DB) +// Uses tile-level habitat suitability + fish stock approximation. +// --------------------------------------------------------------------------- + +const FAUNA_WEIGHTS = { + undergrowth_weight: 0.6, + canopy_weight: 0.2, + fungi_weight: 0.2, +} as const + +function updateHabitatSuitability( + tiles: TileState[], w: number, h: number, +): void { + for (let i = 0; i < tiles.length; i++) { + const tile = tiles[i] + if (isWater(tile)) continue + let tu = 0, tc = 0, tf = 0, n = 0 + // Radius-2 neighborhood average + const nbs = neighbors(tile.col, tile.row, w, h) + for (const nb of nbs) { + const nt = tiles[idx(nb.col, nb.row, w)] + if (isWater(nt)) continue + tu += nt.undergrowth + tc += nt.canopy_cover + tf += nt.fungi_network + n++ + } + // Include self + tu += tile.undergrowth; tc += tile.canopy_cover; tf += tile.fungi_network; n++ + if (n > 0) { + tile.habitat_suitability = ( + (tu / n) * FAUNA_WEIGHTS.undergrowth_weight + + (tc / n) * FAUNA_WEIGHTS.canopy_weight + + (tf / n) * FAUNA_WEIGHTS.fungi_weight + ) + } else { + tile.habitat_suitability = 0.0 + } + } +} + +function updateFishStock(tiles: TileState[], w: number, h: number): void { + for (let i = 0; i < tiles.length; i++) { + const tile = tiles[i] + if (!isWater(tile) || (tile.fish_stock ?? 0) <= 0) continue + let tempMult = 0.5 // polar + if (tile.temperature > 0.55) tempMult = 1.0 // tropical + else if (tile.temperature > 0.25) tempMult = 0.8 // temperate + let cap = 100.0 + if (tile.reef_health > 0.5) cap *= 1.5 + else if (tile.reef_health < 0.1) cap *= 0.5 + const stock = tile.fish_stock ?? 0 + const growth = 0.05 * tempMult * stock * (1.0 - stock / cap) + tile.fish_stock = Math.max(0, Math.min(Math.round(stock + growth), cap)) + } +} + +// --------------------------------------------------------------------------- +// Ecosystem quality computation (from ecosystem.gd) +// --------------------------------------------------------------------------- + +const QUALITY_THRESHOLDS = [0.2, 0.4, 0.6, 0.8] as const +const W_FLORA = 0.30 +const W_FAUNA = 0.25 +const W_STABILITY = 0.25 +const W_BALANCE = 0.20 + +const FOOD_YIELD_MULT: Record = { + 1: 0.5, 2: 1.0, 3: 1.5, 4: 2.0, 5: 2.5, +} + +function scoreToTier(score: number): number { + if (score >= 0.8) return 5 + if (score >= 0.6) return 4 + if (score >= 0.4) return 3 + if (score >= 0.2) return 2 + return 1 +} + +function floraHealth(tile: TileState, biome: BiomeDef | null): number { + if (!biome) return 0.5 + const { canopy, undergrowth, fungi } = biome.flora_climax + const cMax = Math.max(canopy, 0.001) + const uMax = Math.max(undergrowth, 0.001) + const fMax = Math.max(fungi, 0.001) + const c = Math.min(tile.canopy_cover / cMax, 1.0) + const u = Math.min(tile.undergrowth / uMax, 1.0) + const f = Math.min(tile.fungi_network / fMax, 1.0) + return (c + u + f) / 3.0 +} + +function biomeStability(tile: TileState): number { + const classified = classifyBiome(tile) + if (classified === tile.biome_id) return 1.0 + // Partial credit for same family + if (classified.startsWith('temperate') && tile.biome_id.startsWith('temperate')) return 0.6 + if (classified.startsWith('tropical') && tile.biome_id.startsWith('tropical')) return 0.6 + return 0.2 +} + +/** Approximate fauna diversity from habitat suitability (no creature DB in guide). */ +function faunaDiversity(tile: TileState, biome: BiomeDef | null): number { + if (!biome) return 0.5 + // Habitat suitability as proxy for species diversity + return Math.min(tile.habitat_suitability / 0.7, 1.0) +} + +/** Approximate population balance from flora ratios (no creature DB in guide). */ +function populationBalance(tile: TileState): number { + // Healthy undergrowth implies herbivore support, balanced canopy implies + // predator-prey equilibrium. In the full game this uses SQLite creature counts. + if (tile.undergrowth < 0.1) return 0.3 + const ratio = tile.canopy_cover / Math.max(tile.undergrowth, 0.01) + // Ideal ratio around 1.0-2.0 + if (ratio >= 0.5 && ratio <= 3.0) return 1.0 + return 0.5 +} + +function computeTileQuality(tiles: TileState[], w: number, h: number): void { + for (let i = 0; i < tiles.length; i++) { + const tile = tiles[i] + if (isWater(tile)) continue + const biome = getBiome(tile.biome_id) + const flora = floraHealth(tile, biome) + const fauna = faunaDiversity(tile, biome) + const stability = biomeStability(tile) + const balance = populationBalance(tile) + const score = flora * W_FLORA + fauna * W_FAUNA + + stability * W_STABILITY + balance * W_BALANCE + let newQ = scoreToTier(score) + if (biome) { + const [qMin, qMax] = biome.quality_range + newQ = Math.max(qMin, Math.min(qMax, newQ)) + } + if (newQ >= 4 && tile.quality < 4 && tile.landmark_name === '') { + tile.landmark_name = `Ancient ${tile.biome_id.replace(/_/g, ' ')}` + } + tile.quality = newQ + } +} + +function computeGlobalHealth(grid: GridState): number { + let total = 0, count = 0 + for (const tile of grid.tiles) { + if (isWater(tile)) continue + total += tile.quality / 5.0 + count++ + } + return count > 0 ? total / count : 0.5 +} + +/** Food yield modifier for a tile based on quality. */ +export function getEcologyFoodModifier(tile: TileState): number { + let base = FOOD_YIELD_MULT[tile.quality] ?? 1.0 + if (!isWater(tile)) { + base *= 0.8 + 0.4 * tile.undergrowth // lerp(0.8, 1.2, undergrowth) + } + return base +} + +// --------------------------------------------------------------------------- +// EcologyPhysics class — orchestrates flora + fauna + quality per turn +// --------------------------------------------------------------------------- + +// Biome recomputation deltas +const CANOPY_DELTA = 0.05 +const TEMP_DELTA = 0.02 +const MOISTURE_DELTA = 0.03 + +export class EcologyPhysics { + private lastCanopy: Float32Array | null = null + private lastTemp: Float32Array | null = null + private lastMoisture: Float32Array | null = null + + /** + * Process one turn of ecology dynamics. + * Call after ClimatePhysics.processStep(). + */ + processStep(grid: GridState): void { + const { tiles, width: w, height: h } = grid + + // Flora dynamics (6 ticks in order) + tickCanopy(tiles, w, h) + tickUndergrowth(tiles, w, h) + tickFungi(tiles, w, h) + tickSuccession(tiles, w, h) + tickDesertification(tiles, w, h) + tickRegrowth(tiles, w, h) + + // Fauna (simplified for guide) + updateHabitatSuitability(tiles, w, h) + updateFishStock(tiles, w, h) + + // Biome recomputation on significant changes + this.recomputeBiomes(tiles, w, h) + + // Quality scoring + computeTileQuality(tiles, w, h) + + // Global health + grid.ecosystem_health = computeGlobalHealth(grid) + } + + private recomputeBiomes(tiles: TileState[], w: number, h: number): void { + const n = tiles.length + if (!this.lastCanopy || this.lastCanopy.length !== n) { + this.lastCanopy = new Float32Array(n) + this.lastTemp = new Float32Array(n) + this.lastMoisture = new Float32Array(n) + for (let i = 0; i < n; i++) { + this.lastCanopy[i] = tiles[i].canopy_cover + this.lastTemp![i] = tiles[i].temperature + this.lastMoisture![i] = tiles[i].moisture + } + return + } + for (let i = 0; i < n; i++) { + const tile = tiles[i] + if (isWater(tile)) continue + const canopyD = Math.abs(tile.canopy_cover - this.lastCanopy[i]) + const tempD = Math.abs(tile.temperature - this.lastTemp![i]) + const moistD = Math.abs(tile.moisture - this.lastMoisture![i]) + + this.lastCanopy[i] = tile.canopy_cover + this.lastTemp![i] = tile.temperature + this.lastMoisture![i] = tile.moisture + + if (canopyD > CANOPY_DELTA || tempD > TEMP_DELTA || moistD > MOISTURE_DELTA) { + const newBiome = classifyBiome(tile) + if (newBiome !== tile.biome_id) { + tile.biome_id = newBiome + } + } + } + } +} + +// Re-export helpers for guide lenses +export { isWater, getBiome, classifyBiome, BIOME_DEFS } +export type { BiomeDef } diff --git a/packages/engine-ts/src/HexGrid.ts b/packages/engine-ts/src/HexGrid.ts index d8f6c7c9..3e1d1e88 100644 --- a/packages/engine-ts/src/HexGrid.ts +++ b/packages/engine-ts/src/HexGrid.ts @@ -83,10 +83,11 @@ export function neighborInDir( return { col: nc, row: nr } } -/** Solar insolation at a row: 1.0 at equator (center), 0.0 at poles. */ -export function solarByRow(row: number, height: number): number { +/** Solar insolation at a row, mapped to habitable range [solarMin, solarMax]. */ +export function solarByRow(row: number, height: number, solarMin = 0.15, solarMax = 0.70): number { const centerRow = height / 2.0 - return 1.0 - Math.abs((row - centerRow) / centerRow) + const raw = 1.0 - Math.abs((row - centerRow) / centerRow) + return solarMin + (solarMax - solarMin) * raw } // --------------------------------------------------------------------------- diff --git a/packages/engine-ts/src/MapGenerator.generated.ts b/packages/engine-ts/src/MapGenerator.generated.ts index 26f8961f..a7a3232c 100644 --- a/packages/engine-ts/src/MapGenerator.generated.ts +++ b/packages/engine-ts/src/MapGenerator.generated.ts @@ -77,7 +77,7 @@ const DEFAULT_TYPE_DATA: Record = { continent_count: { min: 2, max: 4 }, terrain_fractions: { enchanted_forest: 0.01, desert: 0.10, swamp: 0.07, - corrupted_land: 0.04, volcano: 0.02, + volcano: 0.02, }, generation_params: { num_landmass: 35, steepness: 0.20, prevailing_wind_direction: 0, @@ -251,7 +251,6 @@ class GenMap { quality_progress: gt?.quality_progress ?? 0, river_edges: gt?.river_edges ?? [], flow_accumulation: gt?.flow_accumulation ?? 0.0, - corruption_pressure: 0.0, original_terrain_id: '', ley_line_count: 0, ley_school: '', @@ -779,7 +778,7 @@ function assignTerrainPatches( const defaultFractions: Record = { forest: 0.12, jungle: 0.06, boreal_forest: 0.05, enchanted_forest: 0.01, desert: 0.10, swamp: 0.07, - corrupted_land: 0.04, volcano: 0.02, + volcano: 0.02, } const fractions = (typeData['terrain_fractions'] ?? defaultFractions) as Record @@ -790,7 +789,7 @@ function assignTerrainPatches( const order: string[] = [ 'volcano', 'jungle', 'forest', 'boreal_forest', 'enchanted_forest', - 'desert', 'swamp', 'corrupted_land', 'tundra', 'snow', 'grassland', + 'desert', 'swamp', 'tundra', 'snow', 'grassland', ] for (const terrainId of order) { @@ -904,7 +903,6 @@ function isEligible( case 'enchanted_forest': return m >= 0.40 case 'desert': return t >= 0.25 && m < 0.25 case 'swamp': return t > 0.25 && m > 0.75 && e < 0.48 - case 'corrupted_land': return true case 'tundra': return t >= 0.10 && t < 0.25 case 'snow': return t < 0.10 case 'grassland': return true diff --git a/packages/engine-ts/src/index.ts b/packages/engine-ts/src/index.ts index da67cfb3..55f3d4bc 100644 --- a/packages/engine-ts/src/index.ts +++ b/packages/engine-ts/src/index.ts @@ -2,6 +2,7 @@ export * from './types' export * from './HexGrid' export * from './ClimatePhysics.generated' export * from './MapGenerator.generated' +export * from './EcologyPhysics.generated' export * from './runner' export * from './scenarios' export * from './worker-protocol' diff --git a/packages/engine-ts/src/worker-protocol.ts b/packages/engine-ts/src/worker-protocol.ts index b478c179..ede07adc 100644 --- a/packages/engine-ts/src/worker-protocol.ts +++ b/packages/engine-ts/src/worker-protocol.ts @@ -90,6 +90,7 @@ export interface FramesResponse { export interface FramePayload { texA: Float32Array texB: Float32Array + texC: Float32Array width: number height: number turn: number