diff --git a/packages/engine-ts/src/ClimatePhysics.generated.ts b/packages/engine-ts/src/ClimatePhysics.generated.ts index 245650a5..7ce487dc 100644 --- a/packages/engine-ts/src/ClimatePhysics.generated.ts +++ b/packages/engine-ts/src/ClimatePhysics.generated.ts @@ -56,7 +56,7 @@ export function idealTerrain( tile: TileState, spec: Record, ): string { - const tid = tile.terrain_id + const tid = tile.biome_id const temp = tile.temperature const moist = tile.moisture const elev = tile.elevation @@ -150,7 +150,7 @@ export class ClimatePhysics { this.oceanDistCache = new Int32Array(n).fill(20) const queue: number[] = [] for (let i = 0; i < n; i++) { - const tid = tiles[i].terrain_id + const tid = tiles[i].biome_id if (tid === 'ocean' || tid === 'coast' || tid === 'lake' || tid === 'inland_sea') { this.oceanDistCache[i] = 0 queue.push(i) @@ -586,47 +586,39 @@ export class ClimatePhysics { } private stepTerrainEvolution(grid: GridState): void { - const { tiles, width: w, height: h } = grid - let up_thresh = Math.floor((this.params as any)["quality_up_threshold"] ?? 10) - let down_thresh = Math.floor((this.params as any)["quality_down_threshold"] ?? 5) + const { tiles } = grid + const up_thresh = this.p('quality_up_threshold', 10) + const down_thresh = this.p('quality_down_threshold', 5) - for (let i = 0; i < tiles.length; i++) { - const tile = tiles[i] - const { col, row } = tile - let tid = tile.biome_id + for (let i = 0; i < tiles.length; i++) { + const tile = tiles[i] + const tid = tile.biome_id - if (tile.is_natural_wonder) { - continue - } - if (tid === "ocean" || tid === "coast" || tid === "lake" || tid === "volcano") { - continue + if (tile.is_natural_wonder) continue + if (tid === 'ocean' || tid === 'coast' || tid === 'lake' || tid === 'volcano') continue - } - let ideal = idealTerrain(tile, this.spec) + const ideal = idealTerrain(tile, this.spec) - if (ideal === tid) { - tile.quality_progress += 1 - if (tile.quality_progress >= up_thresh) { - tile.quality_progress = 0 - if (tile.quality < 5) { - tile.quality += 1 - } else { - tile.quality_progress -= 1 - if (tile.quality_progress <= -down_thresh) { - tile.quality_progress = 0 - if (tile.quality > 1) { - tile.quality -= 1 - } else { - tile.biome_id = ideal - tile.quality = 1 + if (ideal === tid) { + tile.quality_progress += 1 + if (tile.quality_progress >= up_thresh) { tile.quality_progress = 0 - + if (tile.quality < 5) tile.quality += 1 + } + } else { + tile.quality_progress -= 1 + if (tile.quality_progress <= -down_thresh) { + tile.quality_progress = 0 + if (tile.quality > 1) { + tile.quality -= 1 + } else { + tile.biome_id = ideal + tile.quality = 1 + tile.quality_progress = 0 + } } } } - } - } - } } private stepGlobalStats(grid: GridState): void { @@ -698,7 +690,7 @@ export class ClimatePhysics { const col = Math.floor(hashNoise(ch, 1, turnSeed) * w) const row = Math.floor(hashNoise(ch, 2, turnSeed) * (h - 4)) + 2 const tile = tiles[idx(col, row, w)] - if (!tile || tile.terrain_id === 'ocean' || tile.terrain_id === 'coast') return null + if (!tile || tile.biome_id === 'ocean' || tile.biome_id === 'coast') return null return tile } const hexDist = (c1: number, r1: number, c2: number, r2: number): number => { @@ -717,15 +709,15 @@ export class ClimatePhysics { const center = pickLand(11) const forestTypes = (wf['target_terrain'] as string[]) ?? ['forest', 'jungle', 'boreal_forest', 'enchanted_forest'] - if (center && forestTypes.includes(center.terrain_id)) { + if (center && forestTypes.includes(center.biome_id)) { const radius = (wf['radius'] as number) ?? 2 const moistLoss = (wf['moisture_loss'] as number) ?? 0.15 const becomes = (wf['becomes'] as string) ?? 'grassland' let burned = 0 for (const t of tilesInRadius(center.col, center.row, radius)) { if (t.is_natural_wonder) continue - if (forestTypes.includes(t.terrain_id)) { - t.terrain_id = becomes + if (forestTypes.includes(t.biome_id)) { + t.biome_id = becomes t.quality = 1 t.quality_progress = 0 t.moisture = Math.max(0.0, t.moisture - moistLoss) @@ -742,7 +734,7 @@ export class ClimatePhysics { if (roll(20) < ((sv['frequency'] as number) ?? 0.00667)) { const center = pickLand(21) if (center && !center.is_natural_wonder) { - center.terrain_id = 'volcano' + center.biome_id = 'volcano' center.quality = 1 center.quality_progress = 0 const svRadius = (sv['radius'] as number) ?? 3 @@ -753,8 +745,8 @@ export class ClimatePhysics { let scorched = 0 for (const t of tilesInRadius(center.col, center.row, svRadius)) { if (t === center || t.is_natural_wonder) continue - if (t.terrain_id !== 'ocean' && t.terrain_id !== 'coast') { - t.terrain_id = (sv['scorched_terrain'] as string) ?? 'desert' + if (t.biome_id !== 'ocean' && t.biome_id !== 'coast') { + t.biome_id = (sv['scorched_terrain'] as string) ?? 'desert' t.moisture = Math.max(0.0, t.moisture - svMoistLoss) t.quality = 1 scorched++ @@ -780,7 +772,7 @@ export class ClimatePhysics { if (center && !center.is_natural_wonder) { const mtRadius = (mt['heat_radius'] as number) ?? 2 const heatDelta = (mt['heat_delta'] as number) ?? 0.03 - center.terrain_id = center.elevation < 0.15 ? 'lake' : 'desert' + center.biome_id = center.elevation < 0.15 ? 'lake' : 'desert' center.elevation = Math.max(0.0, center.elevation - 0.15) center.quality = 1 for (const t of tilesInRadius(col, row, mtRadius)) t.magic_heat_delta += heatDelta @@ -799,7 +791,7 @@ export class ClimatePhysics { const drRadius = (dr['radius'] as number) ?? 4 const moistLoss = (dr['moisture_loss'] as number) ?? 0.10 for (const t of tilesInRadius(center.col, center.row, drRadius)) { - if (t.terrain_id !== 'ocean' && t.terrain_id !== 'coast') + if (t.biome_id !== 'ocean' && t.biome_id !== 'coast') t.moisture = Math.max(0.0, t.moisture - moistLoss) } events.push({ turn, type: 'drought', col: center.col, row: center.row, @@ -813,10 +805,10 @@ export class ClimatePhysics { const col = Math.floor(hashNoise(51, 1, turnSeed) * w) const row = Math.floor(hashNoise(51, 2, turnSeed) * h) const center = tiles[idx(col, row, w)] - if (center && (center.terrain_id === 'coast' || center.terrain_id === 'ocean')) { + if (center && (center.biome_id === 'coast' || center.biome_id === 'ocean')) { for (const t of tilesInRadius(col, row, 2)) { - if (t.terrain_id === 'coast') t.reef_health = Math.min(1.0, t.reef_health + 0.1) - if (t.terrain_id !== 'ocean' && t.terrain_id !== 'coast') + if (t.biome_id === 'coast') t.reef_health = Math.min(1.0, t.reef_health + 0.1) + if (t.biome_id !== 'ocean' && t.biome_id !== 'coast') t.moisture = Math.min(1.0, t.moisture + 0.05) } events.push({ turn, type: 'algal_bloom', col, row, description: 'Algal bloom' }) @@ -827,13 +819,13 @@ export class ClimatePhysics { const ip = cfg['insect_plague'] ?? {} if (roll(60) < ((ip['frequency'] as number) ?? 0.04)) { const center = pickLand(61) - if (center && (center.terrain_id === 'forest' || center.terrain_id === 'jungle' || - center.terrain_id === 'enchanted_forest')) { + if (center && (center.biome_id === 'forest' || center.biome_id === 'jungle' || + center.biome_id === 'enchanted_forest')) { const ipRadius = (ip['radius'] as number) ?? 3 for (const t of tilesInRadius(center.col, center.row, ipRadius)) { if (t.is_natural_wonder) continue - if (t.terrain_id === 'enchanted_forest') { t.terrain_id = 'forest'; t.quality = Math.max(1, t.quality - 1) } - else if (t.terrain_id === 'jungle' || t.terrain_id === 'forest') t.quality = Math.max(1, t.quality - 1) + if (t.biome_id === 'enchanted_forest') { t.biome_id = 'forest'; t.quality = Math.max(1, t.quality - 1) } + else if (t.biome_id === 'jungle' || t.biome_id === 'forest') t.quality = Math.max(1, t.quality - 1) } events.push({ turn, type: 'insect_plague', col: center.col, row: center.row, description: 'Insect plague degrades forest quality' }) @@ -844,7 +836,7 @@ export class ClimatePhysics { const mg = cfg['mountain_growth'] ?? {} if (roll(70) < ((mg['frequency'] as number) ?? 0.0167)) { const center = pickLand(71) - if (center && (center.terrain_id === 'hills' || center.terrain_id === 'mountains')) { + if (center && (center.biome_id === 'hills' || center.biome_id === 'mountains')) { center.elevation = Math.min(1.0, center.elevation + 0.05) for (const nb of neighbors(center.col, center.row, w, h)) tiles[idx(nb.col, nb.row, w)].elevation = Math.min(1.0, tiles[idx(nb.col, nb.row, w)].elevation + 0.02) @@ -857,11 +849,11 @@ export class ClimatePhysics { const er = cfg['erosion'] ?? {} if (roll(80) < ((er['frequency'] as number) ?? 0.033)) { const center = pickLand(81) - if (center && (center.terrain_id === 'mountains' || center.terrain_id === 'hills' || - center.terrain_id === 'volcano') && !center.is_natural_wonder) { + if (center && (center.biome_id === 'mountains' || center.biome_id === 'hills' || + center.biome_id === 'volcano') && !center.is_natural_wonder) { center.elevation = Math.max(0.0, center.elevation - 0.03) - const isVolcano = center.terrain_id === 'volcano' - if (isVolcano) center.terrain_id = 'hills' + const isVolcano = center.biome_id === 'volcano' + if (isVolcano) center.biome_id = 'hills' for (const nb of neighbors(center.col, center.row, w, h)) { const t = tiles[idx(nb.col, nb.row, w)] if (t.elevation < center.elevation) t.moisture = Math.min(1.0, t.moisture + 0.03) @@ -884,7 +876,7 @@ export class ClimatePhysics { const col = Math.floor(hashNoise(ch, 1, turnSeed) * w) const row = Math.floor(hashNoise(ch, 2, turnSeed) * (h - 4)) + 2 const tile = tiles[idx(col, row, w)] - if (!tile || tile.terrain_id === 'ocean' || tile.terrain_id === 'coast') return null + if (!tile || tile.biome_id === 'ocean' || tile.biome_id === 'coast') return null return tile } const hexDist = (c1: number, r1: number, c2: number, r2: number): number => { @@ -897,12 +889,12 @@ export class ClimatePhysics { tiles.filter(t => hexDist(col, row, t.col, t.row) <= radius) if (roll(10) < 0.125) { const center = pickLand(11) - if (center && ['forest','jungle','boreal_forest','enchanted_forest'].includes(center.terrain_id)) { + if (center && ['forest','jungle','boreal_forest','enchanted_forest'].includes(center.biome_id)) { let burned = 0 for (const t of tilesInRadius(center.col, center.row, 2)) { if (t.is_natural_wonder) continue - if (['forest','jungle','enchanted_forest','boreal_forest'].includes(t.terrain_id)) { - t.terrain_id = 'grassland'; t.quality = 1; t.quality_progress = 0 + if (['forest','jungle','enchanted_forest','boreal_forest'].includes(t.biome_id)) { + t.biome_id = 'grassland'; t.quality = 1; t.quality_progress = 0 t.moisture = Math.max(0.0, t.moisture - 0.15); burned++ } } @@ -913,12 +905,12 @@ export class ClimatePhysics { if (roll(20) < 0.00667) { const center = pickLand(21) if (center && !center.is_natural_wonder) { - center.terrain_id = 'volcano'; center.quality = 1; center.quality_progress = 0 + center.biome_id = 'volcano'; center.quality = 1; center.quality_progress = 0 let scorched = 0 for (const t of tilesInRadius(center.col, center.row, 3)) { if (t === center || t.is_natural_wonder) continue - if (t.terrain_id !== 'ocean' && t.terrain_id !== 'coast') { - t.terrain_id = 'desert'; t.moisture = Math.max(0.0, t.moisture - 0.2); t.quality = 1; scorched++ + if (t.biome_id !== 'ocean' && t.biome_id !== 'coast') { + t.biome_id = 'desert'; t.moisture = Math.max(0.0, t.moisture - 0.2); t.quality = 1; scorched++ } } for (const t of tiles) t.magic_heat_delta -= 0.002 @@ -932,7 +924,7 @@ export class ClimatePhysics { const row = Math.floor(hashNoise(31, 2, turnSeed) * (h - 4)) + 2 const center = tiles[idx(col, row, w)] if (center && !center.is_natural_wonder) { - center.terrain_id = center.elevation < 0.15 ? 'lake' : 'desert' + center.biome_id = center.elevation < 0.15 ? 'lake' : 'desert' center.elevation = Math.max(0.0, center.elevation - 0.15); center.quality = 1 for (const t of tilesInRadius(col, row, 2)) t.magic_heat_delta += 0.03 center.wonder_anchor_strength = 2; center.wonder_anchor_school = 'aether' @@ -943,7 +935,7 @@ export class ClimatePhysics { const center = pickLand(41) if (center) { for (const t of tilesInRadius(center.col, center.row, 4)) { - if (t.terrain_id !== 'ocean' && t.terrain_id !== 'coast') + if (t.biome_id !== 'ocean' && t.biome_id !== 'coast') t.moisture = Math.max(0.0, t.moisture - 0.10) } events.push({ turn, type: 'drought', col: center.col, row: center.row, description: 'Regional drought' }) @@ -953,10 +945,10 @@ export class ClimatePhysics { const col = Math.floor(hashNoise(51, 1, turnSeed) * w) const row = Math.floor(hashNoise(51, 2, turnSeed) * h) const center = tiles[idx(col, row, w)] - if (center && (center.terrain_id === 'coast' || center.terrain_id === 'ocean')) { + if (center && (center.biome_id === 'coast' || center.biome_id === 'ocean')) { for (const t of tilesInRadius(col, row, 2)) { - if (t.terrain_id === 'coast') t.reef_health = Math.min(1.0, t.reef_health + 0.1) - if (t.terrain_id !== 'ocean' && t.terrain_id !== 'coast') + if (t.biome_id === 'coast') t.reef_health = Math.min(1.0, t.reef_health + 0.1) + if (t.biome_id !== 'ocean' && t.biome_id !== 'coast') t.moisture = Math.min(1.0, t.moisture + 0.05) } events.push({ turn, type: 'algal_bloom', col, row, description: 'Algal bloom' }) @@ -964,18 +956,18 @@ export class ClimatePhysics { } if (roll(60) < 0.04) { const center = pickLand(61) - if (center && ['forest','jungle','enchanted_forest'].includes(center.terrain_id)) { + if (center && ['forest','jungle','enchanted_forest'].includes(center.biome_id)) { for (const t of tilesInRadius(center.col, center.row, 3)) { if (t.is_natural_wonder) continue - if (t.terrain_id === 'enchanted_forest') { t.terrain_id = 'forest'; t.quality = Math.max(1, t.quality - 1) } - else if (t.terrain_id === 'jungle' || t.terrain_id === 'forest') t.quality = Math.max(1, t.quality - 1) + if (t.biome_id === 'enchanted_forest') { t.biome_id = 'forest'; t.quality = Math.max(1, t.quality - 1) } + else if (t.biome_id === 'jungle' || t.biome_id === 'forest') t.quality = Math.max(1, t.quality - 1) } events.push({ turn, type: 'insect_plague', col: center.col, row: center.row, description: 'Insect plague' }) } } if (roll(70) < 0.0167) { const center = pickLand(71) - if (center && (center.terrain_id === 'hills' || center.terrain_id === 'mountains')) { + if (center && (center.biome_id === 'hills' || center.biome_id === 'mountains')) { center.elevation = Math.min(1.0, center.elevation + 0.05) for (const nb of neighbors(center.col, center.row, w, h)) tiles[idx(nb.col, nb.row, w)].elevation = Math.min(1.0, tiles[idx(nb.col, nb.row, w)].elevation + 0.02) @@ -984,10 +976,10 @@ export class ClimatePhysics { } if (roll(80) < 0.033) { const center = pickLand(81) - if (center && ['mountains','hills','volcano'].includes(center.terrain_id) && !center.is_natural_wonder) { + if (center && ['mountains','hills','volcano'].includes(center.biome_id) && !center.is_natural_wonder) { center.elevation = Math.max(0.0, center.elevation - 0.03) - const isVolcano = center.terrain_id === 'volcano' - if (isVolcano) center.terrain_id = 'hills' + const isVolcano = center.biome_id === 'volcano' + if (isVolcano) center.biome_id = 'hills' for (const nb of neighbors(center.col, center.row, w, h)) { const t = tiles[idx(nb.col, nb.row, w)] if (t.elevation < center.elevation) t.moisture = Math.min(1.0, t.moisture + 0.03) diff --git a/packages/engine-ts/src/MapGenerator.generated.ts b/packages/engine-ts/src/MapGenerator.generated.ts index a7a3232c..5a134bd1 100644 --- a/packages/engine-ts/src/MapGenerator.generated.ts +++ b/packages/engine-ts/src/MapGenerator.generated.ts @@ -163,7 +163,7 @@ interface GenTile { axial: Vec2i col: number row: number - terrain_id: string + biome_id: string elevation: number moisture: number temperature: number @@ -183,7 +183,7 @@ interface GenTile { function newGenTile(axial: Vec2i, col: number, row: number): GenTile { return { axial, col, row, - terrain_id: '', + biome_id: '', elevation: 0.0, moisture: 0.0, temperature: 0.0, @@ -226,7 +226,7 @@ class GenMap { getTilesByTerrain(terrainId: string): Vec2i[] { const result: Vec2i[] = [] for (const tile of this.tiles.values()) { - if (tile.terrain_id === terrainId) result.push(tile.axial) + if (tile.biome_id === terrainId) result.push(tile.axial) } return result } @@ -244,14 +244,14 @@ class GenMap { temperature: gt?.temperature ?? 0.0, moisture: gt?.moisture ?? 0.0, elevation: gt?.elevation ?? 0.0, - terrain_id: gt?.terrain_id ?? 'ocean', + biome_id: gt?.biome_id ?? 'ocean', wind_direction: gt?.wind_direction ?? 0, wind_speed: gt?.wind_speed ?? 0.5, quality: gt?.quality ?? 2, quality_progress: gt?.quality_progress ?? 0, river_edges: gt?.river_edges ?? [], flow_accumulation: gt?.flow_accumulation ?? 0.0, - original_terrain_id: '', + original_biome_id: '', ley_line_count: 0, ley_school: '', reef_health: 1.0, @@ -560,7 +560,7 @@ function assignSeaLevel( for (const tile of gm.tiles.values()) { const elev = elevation.get(axialKey(tile.axial)) ?? 0.0 - tile.terrain_id = elev < seaLevel ? 'ocean' : 'land' + tile.biome_id = elev < seaLevel ? 'ocean' : 'land' } smoothCoastlines(gm, genParams) @@ -578,35 +578,35 @@ function smoothCoastlines(gm: GenMap, params: Record): void { const nb = gm.getTile({ x: tile.axial.x + dq, y: tile.axial.y + dr }) if (!nb) continue neighborCount++ - if (!isWaterTerrain(nb.terrain_id)) landCount++ + if (!isWaterTerrain(nb.biome_id)) landCount++ } const waterCount = neighborCount - landCount - if (isWaterTerrain(tile.terrain_id) && landCount >= 5) { + if (isWaterTerrain(tile.biome_id) && landCount >= 5) { changes.push({ pos: tile.axial, terrain: 'grassland' }) - } else if (!isWaterTerrain(tile.terrain_id) && waterCount >= 5) { + } else if (!isWaterTerrain(tile.biome_id) && waterCount >= 5) { changes.push({ pos: tile.axial, terrain: 'ocean' }) } } for (const change of changes) { const tile = gm.getTile(change.pos) - if (tile) tile.terrain_id = change.terrain + if (tile) tile.biome_id = change.terrain } } } function assignCoastTiles(gm: GenMap): void { for (const tile of gm.tiles.values()) { - if (tile.terrain_id === 'ocean') { + if (tile.biome_id === 'ocean') { let hasLandNeighbor = false for (const [dq, dr] of AXIAL_DIRECTIONS) { const nb = gm.getTile({ x: tile.axial.x + dq, y: tile.axial.y + dr }) - if (nb && !isWaterTerrain(nb.terrain_id)) { hasLandNeighbor = true; break } + if (nb && !isWaterTerrain(nb.biome_id)) { hasLandNeighbor = true; break } } - if (hasLandNeighbor) tile.terrain_id = 'coast' - } else if (!isWaterTerrain(tile.terrain_id)) { + if (hasLandNeighbor) tile.biome_id = 'coast' + } else if (!isWaterTerrain(tile.biome_id)) { for (const [dq, dr] of AXIAL_DIRECTIONS) { const nb = gm.getTile({ x: tile.axial.x + dq, y: tile.axial.y + dr }) - if (nb && isWaterTerrain(nb.terrain_id)) { tile.is_coastal = true; break } + if (nb && isWaterTerrain(nb.biome_id)) { tile.is_coastal = true; break } } } } @@ -625,7 +625,7 @@ function placeTectonicRelief( // Compute local average elevation (radius 3) for each land tile const localAvg = new Map() for (const tile of gm.tiles.values()) { - if (tile.terrain_id === 'ocean' || tile.terrain_id === 'coast') continue + if (tile.biome_id === 'ocean' || tile.biome_id === 'coast') continue const nearby = hexSpiral(tile.axial, 3) let total = 0.0, count = 0 for (const nb of nearby) { @@ -637,7 +637,7 @@ function placeTectonicRelief( const landTiles: Vec2i[] = [] for (const tile of gm.tiles.values()) { - if (tile.terrain_id !== 'ocean' && tile.terrain_id !== 'coast') { + if (tile.biome_id !== 'ocean' && tile.biome_id !== 'coast') { landTiles.push(tile.axial) } } @@ -654,19 +654,19 @@ function placeTectonicRelief( let adjOcean = false for (const nb of axialNeighbors(axial)) { const nbTile = gm.getTile(nb) - if (nbTile && (nbTile.terrain_id === 'ocean' || nbTile.terrain_id === 'coast')) { + if (nbTile && (nbTile.biome_id === 'ocean' || nbTile.biome_id === 'coast')) { adjOcean = true; break } } if (!adjOcean && elev > avg * 1.20) { - tile.terrain_id = 'mountains' + tile.biome_id = 'mountains' mountainTiles.push(axial) } else if (!adjOcean && elev > avg * 1.10) { - tile.terrain_id = 'hills' + tile.biome_id = 'hills' hillTiles.push(axial) } else if (rng.randf() < 0.40) { - tile.terrain_id = 'hills' + tile.biome_id = 'hills' hillTiles.push(axial) } } @@ -681,7 +681,7 @@ function placeTectonicRelief( const excess = totalRelief - cap for (let i = 0; i < Math.min(excess, hillTiles.length); i++) { const tile = gm.getTile(hillTiles[i]) - if (tile) tile.terrain_id = 'land' + if (tile) tile.biome_id = 'land' } } } @@ -718,7 +718,7 @@ function computeMoisture( const dist = new Map() const queue: Vec2i[] = [] for (const tile of gm.tiles.values()) { - if (tile.terrain_id === 'ocean' || tile.terrain_id === 'coast') { + if (tile.biome_id === 'ocean' || tile.biome_id === 'coast') { dist.set(axialKey(tile.axial), 0) queue.push(tile.axial) } @@ -751,7 +751,7 @@ function computeMoisture( // Rain shadow from mountains for (const tile of gm.tiles.values()) { - if (tile.terrain_id !== 'mountains') continue + if (tile.biome_id !== 'mountains') continue const wind = 0 // base direction before quality pass const [dq, dr] = AXIAL_DIRECTIONS[wind] for (let r = 1; r < 3; r++) { @@ -784,7 +784,7 @@ function assignTerrainPatches( let landCount = 0 for (const tile of gm.tiles.values()) { - if (tile.terrain_id === 'land') landCount++ + if (tile.biome_id === 'land') landCount++ } const order: string[] = [ @@ -797,14 +797,14 @@ function assignTerrainPatches( if (terrainId === 'tundra' || terrainId === 'snow') { const isFrozen = terrainId === 'snow' for (const tile of gm.tiles.values()) { - if (tile.terrain_id !== 'land') continue + if (tile.biome_id !== 'land') continue const t = temperature.get(axialKey(tile.axial)) ?? 0.5 if (isFrozen && t < 0.10) targetCount++ else if (!isFrozen && t >= 0.10 && t < 0.25) targetCount++ } } else if (terrainId === 'jungle' || terrainId === 'forest' || terrainId === 'boreal_forest') { for (const tile of gm.tiles.values()) { - if (tile.terrain_id !== 'land') continue + if (tile.biome_id !== 'land') continue const t = temperature.get(axialKey(tile.axial)) ?? 0.5 const m = moisture.get(axialKey(tile.axial)) ?? 0.5 if (terrainId === 'jungle' && t > 0.65 && m >= 0.35) targetCount++ @@ -815,14 +815,14 @@ function assignTerrainPatches( } else if (terrainId === 'enchanted_forest') { let forestFamilyCount = 0 for (const tile of gm.tiles.values()) { - const tid = tile.terrain_id + const tid = tile.biome_id if (tid === 'forest' || tid === 'jungle' || tid === 'boreal_forest') forestFamilyCount++ } const baseCount = forestFamilyCount > 0 ? forestFamilyCount : landCount targetCount = Math.round(baseCount * (fractions['enchanted_forest'] ?? 0.01)) } else if (terrainId === 'grassland') { for (const tile of gm.tiles.values()) { - if (tile.terrain_id === 'land') targetCount++ + if (tile.biome_id === 'land') targetCount++ } } else if (terrainId === 'volcano') { const mtCount = gm.getTilesByTerrain('mountains').length @@ -836,7 +836,7 @@ function assignTerrainPatches( // Convert any remaining unclassified 'land' tiles to 'plains' for (const tile of gm.tiles.values()) { - if (tile.terrain_id === 'land') tile.terrain_id = 'plains' + if (tile.biome_id === 'land') tile.biome_id = 'plains' } } @@ -848,7 +848,7 @@ function expandPatch( const eligible: Vec2i[] = [] const src = terrainId === 'volcano' ? 'mountains' : 'land' for (const tile of gm.tiles.values()) { - if (tile.terrain_id === src && isEligible(tile.axial, terrainId, gm, elevation, moisture, temperature)) { + if (tile.biome_id === src && isEligible(tile.axial, terrainId, gm, elevation, moisture, temperature)) { eligible.push(tile.axial) } } @@ -861,19 +861,19 @@ function expandPatch( const ei = rng.randiRange(0, eligible.length - 1) const seedPos = eligible[ei] const tile = gm.getTile(seedPos) - if (!tile || tile.terrain_id !== src) { + if (!tile || tile.biome_id !== src) { eligible.splice(ei, 1); continue } - tile.terrain_id = terrainId + tile.biome_id = terrainId placed++ for (const nb of axialNeighbors(seedPos)) { if (placed >= targetCount) break const nbTile = gm.getTile(nb) - if (!nbTile || nbTile.terrain_id !== src) continue + if (!nbTile || nbTile.biome_id !== src) continue if (!isEligible(nb, terrainId, gm, elevation, moisture, temperature)) continue if (rng.randf() < 0.65) { - nbTile.terrain_id = terrainId + nbTile.biome_id = terrainId placed++ eligible.push(nb) } @@ -894,7 +894,7 @@ function isEligible( case 'volcano': for (const nb of axialNeighbors(axial)) { const nbTile = gm.getTile(nb) - if (nbTile && nbTile.terrain_id === 'mountains') return false + if (nbTile && nbTile.biome_id === 'mountains') return false } return true case 'jungle': return t > 0.65 && m >= 0.35 @@ -935,7 +935,7 @@ function computeWindMap(gm: GenMap, windParams: Record): void { // Pass 2: landmass friction const frictionLand = params['wind_friction_land'] ?? 0.7 for (const tile of gm.tiles.values()) { - if (tile.terrain_id !== 'ocean' && tile.terrain_id !== 'coast') { + if (tile.biome_id !== 'ocean' && tile.biome_id !== 'coast') { tile.wind_speed *= frictionLand } } @@ -943,7 +943,7 @@ function computeWindMap(gm: GenMap, windParams: Record): void { // Pass 3: mountain blocking const mtCap = params['wind_friction_mountain_downwind'] ?? 0.1 for (const tile of gm.tiles.values()) { - if (tile.terrain_id !== 'mountains') continue + if (tile.biome_id !== 'mountains') continue const mtDir = tile.wind_direction const [dwQ, dwR] = AXIAL_DIRECTIONS[mtDir] for (let step = 1; step <= 2; step++) { @@ -958,7 +958,7 @@ function computeWindMap(gm: GenMap, windParams: Record): void { for (const faceDir of [faceA, faceB]) { const [fq, fr] = AXIAL_DIRECTIONS[faceDir] const faceTile = gm.getTile({ x: tile.axial.x + fq, y: tile.axial.y + fr }) - if (faceTile && faceTile.terrain_id !== 'mountains') { + if (faceTile && faceTile.biome_id !== 'mountains') { faceTile.wind_direction = faceDir } } @@ -1013,11 +1013,11 @@ function bandWindForRow( function assignQuality(gm: GenMap): void { for (const tile of gm.tiles.values()) { - if (tile.terrain_id === 'ocean' || tile.terrain_id === 'coast' || tile.terrain_id === 'land') continue + if (tile.biome_id === 'ocean' || tile.biome_id === 'coast' || tile.biome_id === 'land') continue let same = 0 for (const nb of axialNeighbors(tile.axial)) { const nbTile = gm.getTile(nb) - if (nbTile && nbTile.terrain_id === tile.terrain_id) same++ + if (nbTile && nbTile.biome_id === tile.biome_id) same++ } if (same >= 3) tile.quality = 4 else if (same >= 1) tile.quality = 2 @@ -1053,7 +1053,7 @@ function computeRainfall( const out = new Map() for (const tile of gm.tiles.values()) { const key = axialKey(tile.axial) - if (isWaterTerrainHydro(tile.terrain_id)) { out.set(key, 0.0); continue } + if (isWaterTerrainHydro(tile.biome_id)) { out.set(key, 0.0); continue } const m = moisture.get(key) ?? tile.moisture const t = temperature.get(key) ?? tile.temperature let base = m * climateMult(t, params) @@ -1081,7 +1081,7 @@ function applyRainfallBonuses( gm: GenMap, params: Record, ): number { const sb = (params['source_bonuses'] ?? {}) as Record> - const terrain = tile.terrain_id + const terrain = tile.biome_id const elev = elevation.get(key) ?? tile.elevation const temp = temperature.get(key) ?? tile.temperature @@ -1124,7 +1124,7 @@ function applyRainfallBonuses( function adjTerrain(axial: Vec2i, terrains: string[], gm: GenMap): boolean { for (const [dq, dr] of AXIAL_DIRECTIONS) { const nb = gm.getTile({ x: axial.x + dq, y: axial.y + dr }) - if (nb && terrains.includes(nb.terrain_id)) return true + if (nb && terrains.includes(nb.biome_id)) return true } return false } @@ -1147,7 +1147,7 @@ function depressionFill( for (const tile of gm.tiles.values()) { const key = axialKey(tile.axial) - if (isWaterTerrainHydro(tile.terrain_id)) { + if (isWaterTerrainHydro(tile.biome_id)) { filled.set(key, 0.0) flowDir.set(key, -1) heapPush(heap, [0.0, tile.axial.x, tile.axial.y]) @@ -1255,7 +1255,7 @@ function detectLakes( const seaMin = cfg['inland_sea_min_tiles'] ?? 12 const lakeTiles = new Set() for (const tile of gm.tiles.values()) { - if (isWaterTerrainHydro(tile.terrain_id)) continue + if (isWaterTerrainHydro(tile.biome_id)) continue const key = axialKey(tile.axial) const re = elevation.get(key) ?? tile.elevation if ((filledElev.get(key) ?? re) - re > depth) lakeTiles.add(key) @@ -1282,7 +1282,7 @@ function detectLakes( const tid = group.length < seaMin ? 'lake' : 'inland_sea' for (const key of group) { const tile = gm.tiles.get(key) - if (tile) { tile.terrain_id = tid; tile.lake_id = nextId } + if (tile) { tile.biome_id = tid; tile.lake_id = nextId } } nextId++ } @@ -1300,10 +1300,10 @@ function markRivers( for (const axial of topoOrder) { const key = axialKey(axial) const tile = gm.getTile(axial) - if (!tile || isWaterTerrainHydro(tile.terrain_id)) continue + if (!tile || isWaterTerrainHydro(tile.biome_id)) continue const a = acc.get(key) ?? 0.0 const temp = temperature.get(key) ?? tile.temperature - if (a < riverThresh(temp, tile.terrain_id, rcfg) / density) continue + if (a < riverThresh(temp, tile.biome_id, rcfg) / density) continue const d = flowDir.get(key) ?? -1 if (d === -1) continue const dsPos: Vec2i = { @@ -1317,7 +1317,7 @@ function markRivers( tile.river_flow[String(d)] = fv tile.river_flow['_flow_dir'] = d const opp = OPPOSITE_DIR[d] - if (!isWaterTerrainHydro(ds.terrain_id)) { + if (!isWaterTerrainHydro(ds.biome_id)) { if (!ds.river_edges.includes(opp)) ds.river_edges.push(opp) ds.river_flow[String(opp)] = fv } @@ -1344,14 +1344,14 @@ function markDeltas( const frozenT = (params as Record)['frozen_river_temperature'] ?? 0.10 for (const tile of gm.tiles.values()) { const key = axialKey(tile.axial) - if (isWaterTerrainHydro(tile.terrain_id) || (acc.get(key) ?? 0) < thresh) continue + if (isWaterTerrainHydro(tile.biome_id) || (acc.get(key) ?? 0) < thresh) continue const waterDirs: number[] = [] for (let di = 0; di < 6; di++) { const nb = gm.getTile({ x: tile.axial.x + AXIAL_DIRECTIONS[di][0], y: tile.axial.y + AXIAL_DIRECTIONS[di][1], }) - if (nb && isWaterTerrainHydro(nb.terrain_id)) waterDirs.push(di) + if (nb && isWaterTerrainHydro(nb.biome_id)) waterDirs.push(di) } if (waterDirs.length === 0) continue const br = Math.min(maxBr - 1, waterDirs.length) @@ -1371,7 +1371,7 @@ function markDeltas( const nbKey = axialKey(nbPos) if ((flowDir.get(nbKey) ?? -1) === OPPOSITE_DIR[di]) { const up = gm.getTile(nbPos) - if (up && !isWaterTerrainHydro(up.terrain_id)) { + if (up && !isWaterTerrainHydro(up.biome_id)) { const d = waterDirs[br] let upFv = acc.get(nbKey) ?? 0.0 if (up.temperature <= frozenT) upFv = -upFv @@ -1410,7 +1410,7 @@ function classifySources( } if (hasUp) continue - const terrain = tile.terrain_id + const terrain = tile.biome_id const elev = elevation.get(key) ?? tile.elevation const temp = temperature.get(key) ?? tile.temperature const moist = moisture.get(key) ?? tile.moisture diff --git a/packages/engine-ts/src/runner.ts b/packages/engine-ts/src/runner.ts index 05a95faf..b6f668fe 100644 --- a/packages/engine-ts/src/runner.ts +++ b/packages/engine-ts/src/runner.ts @@ -104,7 +104,7 @@ export function encodeSnapshot( texB[base + 0] = tile.wind_direction / 5 texB[base + 1] = tile.wind_speed - texB[base + 2] = encodeTerrainId(tile.terrain_id) + texB[base + 2] = encodeTerrainId(tile.biome_id) let riverMask = 0 for (const e of tile.river_edges) riverMask |= (1 << e) texB[base + 3] = riverMask / 63 @@ -141,9 +141,9 @@ export function computeTurnStats(grid: GridState): TurnStats { const terrain_counts: Record = {} for (const tile of tiles) { - terrain_counts[tile.terrain_id] = (terrain_counts[tile.terrain_id] ?? 0) + 1 - const isWater = tile.terrain_id === 'ocean' || tile.terrain_id === 'coast' || - tile.terrain_id === 'lake' || tile.terrain_id === 'inland_sea' + terrain_counts[tile.biome_id] = (terrain_counts[tile.biome_id] ?? 0) + 1 + const isWater = tile.biome_id === 'ocean' || tile.biome_id === 'coast' || + tile.biome_id === 'lake' || tile.biome_id === 'inland_sea' if (!isWater) { landCount++ tempSum += tile.temperature @@ -198,7 +198,7 @@ export function applyVolcanicWinterForcing(grid: GridState): void { const { tiles } = grid const RADIUS = 3 - const volcanos = tiles.filter((t) => t.terrain_id === 'volcano') + const volcanos = tiles.filter((t) => t.biome_id === 'volcano') for (const volcano of volcanos) { const q1 = volcano.col const s1 = volcano.row - (volcano.col - (volcano.col & 1)) / 2 diff --git a/packages/engine-ts/src/scenarios.ts b/packages/engine-ts/src/scenarios.ts index b4c133a5..9ae051a3 100644 --- a/packages/engine-ts/src/scenarios.ts +++ b/packages/engine-ts/src/scenarios.ts @@ -115,12 +115,12 @@ const volcanicWinter: ScenarioConfig = { for (const { col, row } of volcanicSites) { if (col >= 0 && col < w && row >= 0 && row < grid.height) { const tile = grid.tiles[idx(col, row, w)] - if (tile) tile.terrain_id = 'volcano' + if (tile) tile.biome_id = 'volcano' } } // Seed ash cooling delta on volcano-adjacent tiles for (const tile of grid.tiles) { - if (tile.terrain_id === 'volcano') { + if (tile.biome_id === 'volcano') { tile.magic_heat_delta = -0.005 } } diff --git a/packages/engine-ts/src/types.ts b/packages/engine-ts/src/types.ts index f16c145f..92bf6dc2 100644 --- a/packages/engine-ts/src/types.ts +++ b/packages/engine-ts/src/types.ts @@ -7,7 +7,6 @@ export interface TileState { temperature: number // [0, 1] moisture: number // [0, 1] elevation: number // [0, 1] - terrain_id: string biome_id: string // computed biome from substrate + climate + flora wind_direction: number // [0, 5] axial direction index wind_speed: number // [0, 1] @@ -15,7 +14,7 @@ export interface TileState { quality_progress: number // counter toward next quality change river_edges: number[] // edge indices [0-5] where rivers flow flow_accumulation: number - original_terrain_id: string + original_biome_id: string ley_line_count: number ley_school: LeySchool | '' reef_health: number // [0, 1], relevant for coast tiles