refactor(physics): ♻️ Refactor climate physics computation engine in generated files and update supporting runner and type definitions
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
d8ed38930e
commit
4ec3be52d9
3 changed files with 55 additions and 205 deletions
|
|
@ -12,23 +12,18 @@ import { idx, neighbors, upwindPos, solarByRow, classifyTerrain, hashNoise, neig
|
|||
|
||||
const CLIMATE_DEFAULTS: Record<string, number> = {
|
||||
wind_conductivity: 0.1,
|
||||
energy_scale: 0.01,
|
||||
equilibrium_relaxation: 0.05,
|
||||
energy_scale: 0.005,
|
||||
equilibrium_relaxation: 0.08,
|
||||
evaporation_rate: 0.05,
|
||||
moisture_transport: 0.15,
|
||||
precipitation_threshold: 0.7,
|
||||
moisture_decay: 0.98,
|
||||
moisture_relaxation: 0.02,
|
||||
ocean_evaporation_hops: 3,
|
||||
moisture_decay: 0.995,
|
||||
moisture_relaxation: 0.04,
|
||||
ocean_evaporation_hops: 4,
|
||||
ocean_evaporation_hop_decay: 0.5,
|
||||
atmospheric_loss_rate: 0.001,
|
||||
atmospheric_loss_rate: 0.0003,
|
||||
quality_up_threshold: 10,
|
||||
quality_down_threshold: 5,
|
||||
corruption_spread_rate: 0.02,
|
||||
corruption_flip_threshold: 0.5,
|
||||
corruption_decay_rate: 0.004,
|
||||
corruption_heal_rate: 0.008,
|
||||
corruption_heal_threshold: 0.15,
|
||||
lake_thermal_conductivity: 0.05,
|
||||
river_moisture_transport: 0.075,
|
||||
mountain_rain_shadow_block: 0.9,
|
||||
|
|
@ -206,8 +201,6 @@ export class ClimatePhysics {
|
|||
this.stepLakeEvaporation(grid)
|
||||
this.stepDeepEarthWater(grid)
|
||||
this.stepPrecipitation(grid)
|
||||
this.stepTerrainEvolution(grid)
|
||||
this.stepCorruption(grid)
|
||||
const events = this.stepEcologicalEvents(grid, turn, seed)
|
||||
this.stepAnchorDecay(grid)
|
||||
this.stepGlobalStats(grid)
|
||||
|
|
@ -271,8 +264,8 @@ export class ClimatePhysics {
|
|||
private stepTemperature(grid: GridState): void {
|
||||
const { tiles, width: w, height: h } = grid
|
||||
let conductivity = (this.params as any)["wind_conductivity"] ?? 0.1
|
||||
let energy_scale = (this.params as any)["energy_scale"] ?? 0.01
|
||||
let relaxation = (this.params as any)["equilibrium_relaxation"] ?? 0.05
|
||||
let energy_scale = (this.params as any)["energy_scale"] ?? 0.005
|
||||
let relaxation = (this.params as any)["equilibrium_relaxation"] ?? 0.08
|
||||
|
||||
// Pre-compute solar baseline per row — same value for every tile in a row
|
||||
for (let row = 0; row < h; row++) {
|
||||
|
|
@ -293,7 +286,7 @@ export class ClimatePhysics {
|
|||
let solar = solarByRow(row, h)
|
||||
let current_temp = oldTemp[i]
|
||||
|
||||
let terrain_data = (this.terrainCache.get(tile.terrain_id) ?? {})
|
||||
let terrain_data = (this.terrainCache.get(tile.biome_id) ?? {})
|
||||
let albedo = (terrain_data as any)["albedo"] ?? 0.4
|
||||
let net_solar = solar * (1.0 - albedo) * energy_scale
|
||||
|
||||
|
|
@ -325,7 +318,7 @@ export class ClimatePhysics {
|
|||
for (let i = 0; i < tiles.length; i++) {
|
||||
const tile = tiles[i]
|
||||
const { col, row } = tile
|
||||
if (tile.terrain_id !== "lake") {
|
||||
if (tile.biome_id !== "lake") {
|
||||
continue
|
||||
}
|
||||
let lake_temp = tile.temperature
|
||||
|
|
@ -344,10 +337,10 @@ export class ClimatePhysics {
|
|||
private stepMoistureWind(grid: GridState): void {
|
||||
const { tiles, width: w, height: h } = grid
|
||||
let transport_rate = (this.params as any)["moisture_transport"] ?? 0.15
|
||||
let decay = (this.params as any)["moisture_decay"] ?? 0.98
|
||||
let decay = (this.params as any)["moisture_decay"] ?? 0.995
|
||||
let rain_shadow_block = (this.params as any)["mountain_rain_shadow_block"] ?? 0.9
|
||||
let relaxation = (this.params as any)["moisture_relaxation"] ?? 0.02
|
||||
let atmo_loss = (this.params as any)["atmospheric_loss_rate"] ?? 0.001
|
||||
let relaxation = (this.params as any)["moisture_relaxation"] ?? 0.04
|
||||
let atmo_loss = (this.params as any)["atmospheric_loss_rate"] ?? 0.0003
|
||||
|
||||
// Snapshot moisture before decay so all tiles decay from the same baseline
|
||||
const oldMoisture = new Float32Array(tiles.length)
|
||||
|
|
@ -368,13 +361,13 @@ export class ClimatePhysics {
|
|||
let transported = 0.0
|
||||
if (upwind_pos !== null) {
|
||||
const upwind_tile = tiles[idx(upwind_pos.col, upwind_pos.row, w)]
|
||||
let upwind_is_mountain = ( upwind_tile !== null && upwind_tile.terrain_id === "mountains" )
|
||||
let upwind_is_mountain = ( upwind_tile !== null && upwind_tile.biome_id === "mountains" )
|
||||
let block = (upwind_is_mountain ? rain_shadow_block : 0.0)
|
||||
transported = ( oldMoisture[idx(upwind_pos.col, upwind_pos.row, w)] * tile.wind_speed * transport_rate * (1.0 - block) )
|
||||
|
||||
}
|
||||
// Evapotranspiration and magic forcing are per-tile local effects — safe to add here
|
||||
let terrain_data = (this.terrainCache.get(tile.terrain_id) ?? {})
|
||||
let terrain_data = (this.terrainCache.get(tile.biome_id) ?? {})
|
||||
let evapotrans = (terrain_data as any)["evapotranspiration"] ?? 0.0
|
||||
|
||||
// Moisture equilibrium relaxation — pull toward climate baseline (like temperature)
|
||||
|
|
@ -423,7 +416,7 @@ export class ClimatePhysics {
|
|||
private stepLakeEvaporation(grid: GridState): void {
|
||||
const { tiles, width: w, height: h } = grid
|
||||
let base_evap = (this.params as any)["evaporation_rate"] ?? 0.05
|
||||
let max_hops = Math.floor( (this.params as any)["ocean_evaporation_hops"] ?? 3 )
|
||||
let max_hops = Math.floor( (this.params as any)["ocean_evaporation_hops"] ?? 4 )
|
||||
let hop_decay = (this.params as any)["ocean_evaporation_hop_decay"] ?? 0.5
|
||||
let rain_shadow_block = (this.params as any)["mountain_rain_shadow_block"] ?? 0.9
|
||||
// Biological pump failure: dead ocean reduces evaporation → inland forests dry out.
|
||||
|
|
@ -436,7 +429,7 @@ export class ClimatePhysics {
|
|||
for (let i = 0; i < tiles.length; i++) {
|
||||
const tile = tiles[i]
|
||||
const { col, row } = tile
|
||||
let tid = tile.terrain_id
|
||||
let tid = tile.biome_id
|
||||
if (tid !== "lake" && tid !== "ocean" && tid !== "coast") {
|
||||
continue
|
||||
|
||||
|
|
@ -468,7 +461,7 @@ export class ClimatePhysics {
|
|||
if (hop_tile === null) {
|
||||
break
|
||||
}
|
||||
let hop_tid = hop_tile.terrain_id
|
||||
let hop_tid = hop_tile.biome_id
|
||||
// Stop if we hit another water tile (no double-injection)
|
||||
if (hop_tid === "ocean" || hop_tid === "coast" || hop_tid === "lake") {
|
||||
break
|
||||
|
|
@ -506,7 +499,7 @@ export class ClimatePhysics {
|
|||
const tile = tiles[i]
|
||||
const { col, row } = tile
|
||||
|
||||
if (tile.terrain_id === "volcano") {
|
||||
if (tile.biome_id === "volcano") {
|
||||
tile.moisture = Math.min(1.0, Math.max(0.0, tile.moisture + vol_self))
|
||||
for (const nb_pos of neighbors(col, row, w, h)) {
|
||||
const nb = tiles[idx(nb_pos.col, nb_pos.row, w)]
|
||||
|
|
@ -566,170 +559,6 @@ export class ClimatePhysics {
|
|||
}
|
||||
}
|
||||
|
||||
private stepTerrainEvolution(grid: GridState): void {
|
||||
const { tiles, width: w, height: h } = grid
|
||||
let up_thresh = (this.params as any)["quality_up_threshold"] ?? 10
|
||||
let down_thresh = (this.params as any)["quality_down_threshold"] ?? 5
|
||||
|
||||
for (let i = 0; i < tiles.length; i++) {
|
||||
const tile = tiles[i]
|
||||
const { col, row } = tile
|
||||
let tid = tile.terrain_id
|
||||
|
||||
// Natural wonders are geological formations — quality evolves but terrain doesn't flip
|
||||
if (tile.is_natural_wonder) {
|
||||
continue
|
||||
|
||||
}
|
||||
// Water, corrupted, and fixed-form terrains don't evolve via climate
|
||||
if (( tid === "ocean" || tid === "coast" || tid === "lake" || tid === "corrupted_land" || tid === "volcano" )) {
|
||||
continue
|
||||
|
||||
}
|
||||
let 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) {
|
||||
let old_q = tile.quality
|
||||
tile.quality += 1
|
||||
} else {
|
||||
tile.quality_progress -= 1
|
||||
if (tile.quality_progress <= -down_thresh) {
|
||||
tile.quality_progress = 0
|
||||
if (tile.quality > 1) {
|
||||
let old_q = tile.quality
|
||||
tile.quality -= 1
|
||||
} else {
|
||||
// Quality at floor — terrain flips one step along its chain
|
||||
let old_type = tid
|
||||
tile.terrain_id = ideal
|
||||
tile.quality = 1
|
||||
tile.quality_progress = 0
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private stepCorruption(grid: GridState): void {
|
||||
const { tiles, width: w, height: h } = grid
|
||||
let spread_rate = (this.params as any)["corruption_spread_rate"] ?? 0.02
|
||||
let flip_threshold = (this.params as any)["corruption_flip_threshold"] ?? 0.5
|
||||
let decay_rate = (this.params as any)["corruption_decay_rate"] ?? 0.004
|
||||
let heal_rate = (this.params as any)["corruption_heal_rate"] ?? 0.008
|
||||
let heal_threshold = (this.params as any)["corruption_heal_threshold"] ?? 0.15
|
||||
|
||||
// Accumulate pressure increments into a separate dict — one O(n) read pass,
|
||||
// then one O(n) write pass. Avoids double-counting and in-place mutation.
|
||||
const pressureDeltas = new Float32Array(tiles.length)
|
||||
|
||||
for (let i = 0; i < tiles.length; i++) {
|
||||
const tile = tiles[i]
|
||||
const { col, row } = tile
|
||||
if (tile.corruption_pressure <= 0.0) {
|
||||
continue
|
||||
}
|
||||
// terrain_power corruption_spread_modifier on the SOURCE tile scales spread rate.
|
||||
// e.g. jungle = 0.5x (resists spread), swamp = 1.5x (accelerates spread).
|
||||
let source_modifier = 1.0
|
||||
let base_spread = spread_rate * source_modifier
|
||||
for (const nb_pos of neighbors(col, row, w, h)) {
|
||||
if (!true) {
|
||||
continue
|
||||
}
|
||||
let nb = tiles[idx(nb_pos.col, nb_pos.row, w)]
|
||||
if (nb.terrain_id === "corrupted_land") {
|
||||
continue
|
||||
}
|
||||
// Ley line channeling: spread rate is modified by the ley properties of the
|
||||
// receiving tile. Death ley = 3x, generic ley edge = 2x,
|
||||
// Nature/Life ley = 0.5x (resists). Off-ley = no channeling modifier.
|
||||
let ley_mult = leyChannelingMult(nb, this.spec)
|
||||
// Moisture resistance: high-moisture terrain (jungle, swamp, water) resists
|
||||
// corruption biologically. Marine life (fish, reefs, algae) can still carry
|
||||
// corruption through water — so water isn't immune, just dampened.
|
||||
let moisture_resist = 1.0 - nb.moisture * 0.4
|
||||
// City protection buildings write corruption_resistance_pct to scale down spread
|
||||
let corruption_resist = 1.0 - Math.min(1.0, Math.max(0.0, (nb.corruption_resistance_pct ?? 0)))
|
||||
pressureDeltas[idx(nb_pos.col, nb_pos.row, w)] += base_spread * ley_mult * moisture_resist * corruption_resist
|
||||
|
||||
}
|
||||
}
|
||||
// Apply pressure deltas + natural decay + Life/Nature ley active healing
|
||||
for (let i = 0; i < tiles.length; i++) {
|
||||
const tile = tiles[i]
|
||||
const { col, row } = tile
|
||||
let incoming = pressureDeltas[i]
|
||||
|
||||
// Natural pressure decay — corruption dissipates without active feeding sources
|
||||
let drain = decay_rate
|
||||
|
||||
// Life/Nature ley lines actively heal (bonus drain, capped at 3 stacks)
|
||||
if (tile.ley_line_count > 0 && (tile.ley_school === "life" || tile.ley_school === "nature")) {
|
||||
drain += heal_rate * Math.min(tile.ley_line_count, 3.0)
|
||||
|
||||
}
|
||||
if (incoming === 0.0 && tile.corruption_pressure === 0.0) {
|
||||
continue
|
||||
}
|
||||
tile.corruption_pressure = Math.min(1.0, Math.max(0.0, tile.corruption_pressure + incoming - drain))
|
||||
|
||||
}
|
||||
// Terrain flip: pressure crosses threshold → corrupted_land
|
||||
// Water tiles (ocean, lake, inland_sea, coast) never flip terrain — corruption
|
||||
// in water expresses through the marine ecosystem (reef_health, fish_stock) instead.
|
||||
for (let i = 0; i < tiles.length; i++) {
|
||||
const tile = tiles[i]
|
||||
const { col, row } = tile
|
||||
let tid = tile.terrain_id
|
||||
if (( tid === "corrupted_land" || tid === "ocean" || tid === "lake" || tid === "inland_sea" || tid === "coast" )) {
|
||||
continue
|
||||
}
|
||||
if (tile.corruption_pressure > flip_threshold) {
|
||||
if (tile.original_terrain_id === "") {
|
||||
tile.original_terrain_id = tile.terrain_id
|
||||
}
|
||||
tile.terrain_id = "corrupted_land"
|
||||
|
||||
}
|
||||
}
|
||||
// Marine corruption: coast tiles with elevated pressure degrade reef and fish stock
|
||||
// (corrupted fish and algae carry corruption through the marine ecosystem)
|
||||
for (let i = 0; i < tiles.length; i++) {
|
||||
const tile = tiles[i]
|
||||
const { col, row } = tile
|
||||
if (tile.terrain_id !== "coast") {
|
||||
continue
|
||||
}
|
||||
if (tile.corruption_pressure > 0.2) {
|
||||
tile.reef_health = Math.max(0.0, tile.reef_health - tile.corruption_pressure * 0.015)
|
||||
if ((tile.fish_stock ?? 0) > 0) {
|
||||
tile.fish_stock = Math.max(0.0, (tile.fish_stock ?? 0) - tile.corruption_pressure * 0.01)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// Terrain healing: corrupted tile's pressure falls below heal threshold → restore
|
||||
for (let i = 0; i < tiles.length; i++) {
|
||||
const tile = tiles[i]
|
||||
const { col, row } = tile
|
||||
if (tile.terrain_id !== "corrupted_land" || tile.corruption_pressure > heal_threshold) {
|
||||
continue
|
||||
}
|
||||
let original = tile.original_terrain_id
|
||||
let restore_to = (original !== "" ? original : "grassland")
|
||||
tile.terrain_id = restore_to
|
||||
tile.original_terrain_id = ""
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private stepGlobalStats(grid: GridState): void {
|
||||
const { tiles, width: w, height: h } = grid
|
||||
let total = 0.0
|
||||
|
|
@ -737,7 +566,7 @@ export class ClimatePhysics {
|
|||
for (let i = 0; i < tiles.length; i++) {
|
||||
const tile = tiles[i]
|
||||
const { col, row } = tile
|
||||
if (tile.terrain_id !== "ocean") {
|
||||
if (tile.biome_id !== "ocean") {
|
||||
total += tile.temperature
|
||||
count += 1
|
||||
}
|
||||
|
|
@ -755,7 +584,7 @@ export class ClimatePhysics {
|
|||
for (let i = 0; i < tiles.length; i++) {
|
||||
const tile = tiles[i]
|
||||
const { col, row } = tile
|
||||
if (tile.terrain_id !== "coast") {
|
||||
if (tile.biome_id !== "coast") {
|
||||
continue
|
||||
}
|
||||
coast_count += 1
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { WORLD_SEED, DEFAULT_SCENARIO_TURNS } from './configs'
|
|||
export const TERRAIN_ORDER: readonly string[] = [
|
||||
'ocean', 'coast', 'lake', 'inland_sea', 'ice', 'snow', 'tundra', 'desert',
|
||||
'plains', 'grassland', 'forest', 'boreal_forest', 'jungle', 'enchanted_forest',
|
||||
'hills', 'mountains', 'swamp', 'corrupted_land', 'volcano',
|
||||
'hills', 'mountains', 'swamp', 'volcano',
|
||||
// Natural wonders (geological/biological formations)
|
||||
'mana_node', 'ley_nexus', 'lodestone_spire', 'crystal_cavern',
|
||||
'worldroot', 'primordial_spring', 'abyssal_vortex',
|
||||
|
|
@ -91,6 +91,7 @@ export function encodeSnapshot(
|
|||
const n = grid.tiles.length
|
||||
const texA = new Float32Array(n * 4)
|
||||
const texB = new Float32Array(n * 4)
|
||||
const texC = new Float32Array(n * 4)
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
const tile = grid.tiles[i]
|
||||
|
|
@ -98,7 +99,7 @@ export function encodeSnapshot(
|
|||
|
||||
texA[base + 0] = tile.temperature
|
||||
texA[base + 1] = tile.moisture
|
||||
texA[base + 2] = tile.corruption_pressure
|
||||
texA[base + 2] = tile.canopy_cover ?? 0.0
|
||||
texA[base + 3] = tile.reef_health
|
||||
|
||||
texB[base + 0] = tile.wind_direction / 5
|
||||
|
|
@ -107,12 +108,17 @@ export function encodeSnapshot(
|
|||
let riverMask = 0
|
||||
for (const e of tile.river_edges) riverMask |= (1 << e)
|
||||
texB[base + 3] = riverMask / 63
|
||||
|
||||
texC[base + 0] = tile.undergrowth ?? 0.0
|
||||
texC[base + 1] = tile.fungi_network ?? 0.0
|
||||
texC[base + 2] = (tile.quality ?? 1) / 5.0
|
||||
texC[base + 3] = tile.habitat_suitability ?? 0.0
|
||||
}
|
||||
|
||||
const stats = computeTurnStats(grid)
|
||||
|
||||
return {
|
||||
texA, texB,
|
||||
texA, texB, texC,
|
||||
width: grid.width,
|
||||
height: grid.height,
|
||||
turn,
|
||||
|
|
@ -132,7 +138,6 @@ export function computeTurnStats(grid: GridState): TurnStats {
|
|||
let tempSum = 0
|
||||
let moistSum = 0
|
||||
let landCount = 0
|
||||
let corruptedCount = 0
|
||||
const terrain_counts: Record<string, number> = {}
|
||||
|
||||
for (const tile of tiles) {
|
||||
|
|
@ -143,14 +148,12 @@ export function computeTurnStats(grid: GridState): TurnStats {
|
|||
landCount++
|
||||
tempSum += tile.temperature
|
||||
moistSum += tile.moisture
|
||||
if (tile.terrain_id === 'corrupted_land') corruptedCount++
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
avg_temp: landCount > 0 ? tempSum / landCount : 0.5,
|
||||
avg_moisture: landCount > 0 ? moistSum / landCount : 0.5,
|
||||
corrupted_pct: landCount > 0 ? corruptedCount / landCount : 0,
|
||||
total_ley_strength: 0,
|
||||
dominant_ley_school: '',
|
||||
ley_school_strengths: { ...EMPTY_SCHOOL_RECORD },
|
||||
|
|
@ -178,9 +181,11 @@ export function cloneGridState(grid: GridState): GridState {
|
|||
height: grid.height,
|
||||
global_avg_temp: grid.global_avg_temp,
|
||||
ocean_dead_fraction: grid.ocean_dead_fraction,
|
||||
ecosystem_health: grid.ecosystem_health,
|
||||
tiles: grid.tiles.map((t) => ({
|
||||
...t,
|
||||
river_edges: [...t.river_edges],
|
||||
wonder_anchor_schools: [...t.wonder_anchor_schools],
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ export interface TileState {
|
|||
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]
|
||||
quality: number // 1 | 2 | 3
|
||||
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
|
||||
flow_accumulation: number
|
||||
corruption_pressure: number // [0, 1]
|
||||
original_terrain_id: string
|
||||
ley_line_count: number
|
||||
ley_school: LeySchool | ''
|
||||
|
|
@ -26,10 +26,24 @@ export interface TileState {
|
|||
wonder_anchor_school: School // LEGACY — single school (kept for anchor decay compat)
|
||||
wonder_anchor_schools: LeySchool[] // multi-school affinities for ley network
|
||||
wonder_tier: number // 1-5, aligned with eras (separate from terrain quality)
|
||||
// Substrate fields (set at map gen, rarely change)
|
||||
substrate_id: string // geological substrate from elevation
|
||||
water_body_id: number // water body index (-1 if land)
|
||||
depth_from_coast: number // BFS distance from land (-1 if land)
|
||||
// Flora fields (updated per turn by ecology system)
|
||||
canopy_cover: number // [0, 1] forest canopy density
|
||||
undergrowth: number // [0, 1] ground vegetation density
|
||||
fungi_network: number // [0, 1] mycorrhizal network density
|
||||
drought_counter: number // turns of consecutive low moisture
|
||||
succession_progress: number // turns of stable high canopy
|
||||
regrowth_stage: number // -1 = none, 0-3 = barren→forest
|
||||
regrowth_turns: number // turns in current regrowth stage
|
||||
// Fauna fields (updated per turn by ecology system)
|
||||
habitat_suitability: number // [0, 1] weighted flora average
|
||||
landmark_name: string // Q4+ tile name from FlavorGenerator
|
||||
// Optional fields written by subsystems (not present on all tiles)
|
||||
river_source_type?: string // 'snowmelt' | 'spring' | 'hot_spring' | 'glacial' | undefined
|
||||
fish_stock?: number // marine ecosystem fish population [0, 1]
|
||||
corruption_resistance_pct?: number // city building protection [0, 1]
|
||||
}
|
||||
|
||||
export interface GridState {
|
||||
|
|
@ -38,12 +52,12 @@ export interface GridState {
|
|||
height: number
|
||||
global_avg_temp: number
|
||||
ocean_dead_fraction: number
|
||||
ecosystem_health: number // [0, 1] global ecology health
|
||||
}
|
||||
|
||||
export interface TurnStats {
|
||||
avg_temp: number
|
||||
avg_moisture: number
|
||||
corrupted_pct: number
|
||||
total_ley_strength: number
|
||||
dominant_ley_school: LeySchool | ''
|
||||
ley_school_strengths: Record<LeySchool, number>
|
||||
|
|
@ -52,12 +66,14 @@ export interface TurnStats {
|
|||
terrain_counts: Record<string, number>
|
||||
}
|
||||
|
||||
// Packed per-tile data for rendering: 2 RGBA float textures
|
||||
// texA: [temperature, moisture, corruption_pressure, reef_health]
|
||||
// Packed per-tile data for rendering: 3 RGBA float textures
|
||||
// texA: [temperature, moisture, canopy_cover, reef_health]
|
||||
// texB: [wind_direction/5, wind_speed, terrain_encoded, river_bitmask/63]
|
||||
// texC: [undergrowth, fungi_network, quality/5, habitat_suitability]
|
||||
export interface GridSnapshot {
|
||||
texA: Float32Array // width * height * 4
|
||||
texB: Float32Array // width * height * 4
|
||||
texC: Float32Array // width * height * 4 (ecology)
|
||||
width: number
|
||||
height: number
|
||||
turn: number
|
||||
|
|
@ -134,7 +150,7 @@ export interface TerrainData {
|
|||
color: [number, number, number]
|
||||
flags: string[]
|
||||
climate_zone: string
|
||||
terrain_power?: { spell_school?: string; corruption_spread_modifier?: number }
|
||||
terrain_power?: { spell_school?: string }
|
||||
mana_major?: { school: string; amount: number }
|
||||
ley_anchor_strength?: number
|
||||
ley_anchor_school?: string
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue