diff --git a/src/sim/archetypes.ts b/src/sim/archetypes.ts index cb0b5b2..0f9cb97 100644 --- a/src/sim/archetypes.ts +++ b/src/sim/archetypes.ts @@ -7,7 +7,7 @@ import { BULLET_TRAIL, CLOUD, ACID, LAVA, SNOW, VOLCANO, MOLD, MERCURY, VOID, SEED, RUST, SPORE, ALGAE, POISON, DUST, FIREWORK, BUBBLE, GLITTER, STAR, COMET, BLUE_FIRE, BLACK_HOLE, FIREFLY, - WORM, FAIRY, FISH, MOTH, VENT, LIT_GUNPOWDER, COLORS_U32, + WORM, FAIRY, FISH, MOTH, VENT, LIT_GUNPOWDER, SMOKE, COLORS_U32, } from './constants' // --------------------------------------------------------------------------- @@ -109,10 +109,11 @@ ARCHETYPES[MERCURY] = { gravity: 1.0, liquid: 0.5, density: 8, killsCreatures: t // Rising / gaseous ARCHETYPES[FIRE] = { buoyancy: 0.5, volatile: [0.1, EMPTY], heatSource: true, color: COLORS_U32[FIRE], palette: 1 } ARCHETYPES[GAS] = { buoyancy: 1.0, volatile: [0.02, EMPTY], flammable: true, color: COLORS_U32[GAS] } +ARCHETYPES[SMOKE] = { buoyancy: 1.0, volatile: [0.04, EMPTY], color: COLORS_U32[SMOKE] } ARCHETYPES[PLASMA] = { buoyancy: 1.0, volatile: [0.08, EMPTY], heatSource: true, color: COLORS_U32[PLASMA], palette: 2 } ARCHETYPES[BLUE_FIRE] = { buoyancy: 0.5, volatile: [0.08, EMPTY], heatSource: true, color: COLORS_U32[BLUE_FIRE], palette: 4 } ARCHETYPES[SPORE] = { buoyancy: 0.4, spawnRate: 0.35, volatile: [0.01, EMPTY], infectiousHandler: true, color: COLORS_U32[SPORE] } -ARCHETYPES[CLOUD] = { buoyancy: 0.3, spawnerHandler: true, color: COLORS_U32[CLOUD] } +ARCHETYPES[CLOUD] = { buoyancy: 0.3, density: 1, spawnerHandler: true, color: COLORS_U32[CLOUD] } ARCHETYPES[FIREWORK] = { buoyancy: 0.95, fireworkHandler: true, color: COLORS_U32[FIREWORK] } ARCHETYPES[BUBBLE] = { buoyancy: 0.6, bubbleHandler: true, color: COLORS_U32[BUBBLE] } ARCHETYPES[COMET] = { buoyancy: 1.0, heatSource: true, cometHandler: true, color: COLORS_U32[COMET] } diff --git a/src/sim/constants.ts b/src/sim/constants.ts index cb67e40..6f28878 100644 --- a/src/sim/constants.ts +++ b/src/sim/constants.ts @@ -15,6 +15,7 @@ export const BLACK_HOLE = 60, FIREFLY = 61 export const WORM = 62, FAIRY = 63 export const FISH = 64, MOTH = 65, VENT = 66 export const LIT_GUNPOWDER = 67 +export const SMOKE = 68 export type Material = 'empty' | 'sand' | 'water' | 'dirt' | 'stone' | 'plant' | 'fire' | 'gas' | 'fluff' | 'bug' | 'plasma' | 'nitro' | 'glass' | 'lightning' | 'slime' | 'ant' | 'alien' | 'quark' | 'crystal' | 'ember' | 'static' | 'bird' | 'gunpowder' | 'tap' | 'anthill' | 'bee' | 'flower' | 'hive' | 'honey' | 'nest' | 'gun' | 'cloud' | 'acid' | 'lava' | 'snow' | 'volcano' | 'mold' | 'mercury' | 'void' | 'seed' | 'rust' | 'spore' | 'algae' | 'poison' | 'dust' | 'firework' | 'bubble' | 'glitter' | 'star' | 'comet' | 'blackhole' | 'firefly' | 'worm' | 'fairy' | 'fish' | 'moth' | 'vent' @@ -66,6 +67,7 @@ export const COLORS_U32 = new Uint32Array([ 0xFF00A5FF, 0xFF8CB4D2, 0xFF607860, 0xFF2060FF, // LIT_GUNPOWDER: bright orange-red + 0xFFA0A0A0, // SMOKE: whitish grey ]) export const FIRE_COLORS = new Uint32Array(32) diff --git a/src/sim/systems/falling.ts b/src/sim/systems/falling.ts index 40831e8..f16e34f 100644 --- a/src/sim/systems/falling.ts +++ b/src/sim/systems/falling.ts @@ -249,8 +249,8 @@ export function fallingPhysicsSystem(g: Uint8Array, cols: number, rows: number, continue } - // WATER: extinguish adjacent fire - if (c === WATER) { + // WATER: extinguish adjacent fire (probabilistic to reduce overhead) + if (c === WATER && rand() < 0.3) { for (let wdy = -1; wdy <= 1; wdy++) { for (let wdx = -1; wdx <= 1; wdx++) { if (wdy === 0 && wdx === 0) continue diff --git a/src/sim/systems/liquid.ts b/src/sim/systems/liquid.ts index 3ca36a6..5396381 100644 --- a/src/sim/systems/liquid.ts +++ b/src/sim/systems/liquid.ts @@ -20,13 +20,13 @@ export function applyLiquid( // Hydrostatic pressure: count non-empty cells directly above let pressure = 0 - for (let sy = y - 1; sy >= 0 && pressure < 30; sy--) { + for (let sy = y - 1; sy >= 0 && pressure < 8; sy--) { if (g[sy * cols + x] === EMPTY) break pressure++ } - // Spread range scales with pressure: base 5 + pressure - const spreadRange = 5 + pressure + // Spread range scales with pressure + const spreadRange = 3 + pressure const dx = rand() < 0.5 ? -1 : 1 diff --git a/src/sim/systems/rising.ts b/src/sim/systems/rising.ts index ca88f81..9f484ea 100644 --- a/src/sim/systems/rising.ts +++ b/src/sim/systems/rising.ts @@ -3,7 +3,7 @@ import { F_PROJECTILE, F_CREATURE, F_INFECTIOUS, F_FLAMMABLE, F_IMMOBILE, } from '../archetypes' import { - EMPTY, FIRE, BLUE_FIRE, GAS, SPORE, CLOUD, FIREWORK, BUBBLE, COMET, PLASMA, LIGHTNING, + EMPTY, FIRE, BLUE_FIRE, GAS, SMOKE, SPORE, CLOUD, FIREWORK, BUBBLE, COMET, PLASMA, LIGHTNING, BULLET_N, BULLET_NW, BULLET_S, BULLET_SE, BULLET_SW, BULLET_TRAIL, PLANT, FLUFF, BUG, FLOWER, EMBER, SAND, GLASS, WATER, ACID, HONEY, POISON, MOLD, ALGAE, DIRT, STONE, STATIC, NITRO, GLITTER, @@ -23,7 +23,7 @@ function updateFireRising( ): void { const idx = (bx: number, by: number) => by * cols + bx if (y === 0) { g[p] = EMPTY; return } - if (rand() < 0.03) { g[p] = rand() < 0.25 ? GAS : rand() < 0.15 ? EMBER : EMPTY; return } + if (rand() < 0.03) { const r = rand(); g[p] = r < 0.125 ? SMOKE : r < 0.2 ? EMBER : EMPTY; return } for (let fi = 0; fi < 3; fi++) { const dx = Math.floor(rand() * 7) - 3, dy = Math.floor(rand() * 7) - 3 if (dx === 0 && dy === 0) continue @@ -33,6 +33,28 @@ function updateFireRising( if (nc !== EMPTY && (ARCHETYPE_FLAGS[nc] & F_FLAMMABLE) && rand() < 0.4) g[ni] = FIRE } } + // Horizontal drift + if (rand() < 0.06) { + const hdx = rand() < 0.5 ? -1 : 1 + if (x + hdx >= 0 && x + hdx < cols && g[idx(x + hdx, y)] === EMPTY) { + const d = idx(x + hdx, y); g[d] = c; g[p] = EMPTY; stampGrid[d] = tickParity; return + } + } + // Chaotic movement when near other fire + let nearbyFire = 0 + if (x > 0 && g[idx(x - 1, y)] === FIRE) nearbyFire++ + if (x + 1 < cols && g[idx(x + 1, y)] === FIRE) nearbyFire++ + if (y > 0 && g[idx(x, y - 1)] === FIRE) nearbyFire++ + if (y + 1 < rows && g[idx(x, y + 1)] === FIRE) nearbyFire++ + if (nearbyFire >= 1 && rand() < 0.15) { + const rdx = Math.floor(rand() * 3) - 1, rdy = Math.floor(rand() * 3) - 1 + if (rdx !== 0 || rdy !== 0) { + const nx = x + rdx, ny = y + rdy + if (nx >= 0 && nx < cols && ny >= 0 && ny < rows && g[idx(nx, ny)] === EMPTY) { + const d = idx(nx, ny); g[d] = c; g[p] = EMPTY; stampGrid[d] = tickParity; return + } + } + } const up = idx(x, y - 1) if (y > 0 && g[up] === EMPTY) { g[up] = c; g[p] = EMPTY; stampGrid[up] = tickParity } else { @@ -51,7 +73,7 @@ function canGasDisplace(c: number): boolean { } function updateGasRising( - g: Uint8Array, x: number, y: number, p: number, + g: Uint8Array, x: number, y: number, p: number, c: number, cols: number, _rows: number, rand: () => number, stampGrid: Uint8Array, tickParity: number ): void { @@ -61,26 +83,26 @@ function updateGasRising( if (rand() < 0.08) { const hdx = rand() < 0.5 ? -1 : 1 if (x + hdx >= 0 && x + hdx < cols && g[idx(x + hdx, y)] === EMPTY) { - const d = idx(x + hdx, y); g[d] = GAS; g[p] = EMPTY; stampGrid[d] = tickParity; return + const d = idx(x + hdx, y); g[d] = c; g[p] = EMPTY; stampGrid[d] = tickParity; return } } // Slow the rise: skip upward movement 30% of the time if (rand() < 0.3) return const up = idx(x, y - 1) const upCell = y > 0 ? g[up] : -1 - if (upCell === EMPTY) { g[up] = GAS; g[p] = EMPTY; stampGrid[up] = tickParity } - else if (canGasDisplace(upCell)) { g[up] = GAS; g[p] = upCell; stampGrid[up] = tickParity } + if (upCell === EMPTY) { g[up] = c; g[p] = EMPTY; stampGrid[up] = tickParity } + else if (canGasDisplace(upCell)) { g[up] = c; g[p] = upCell; stampGrid[up] = tickParity } else { const dx = rand() < 0.5 ? -1 : 1 const diagX = x + dx if (y > 0 && diagX >= 0 && diagX < cols) { const dc = g[idx(diagX, y - 1)] if (canGasDisplace(dc)) { - const d = idx(diagX, y - 1); g[d] = GAS; g[p] = dc; stampGrid[d] = tickParity; return + const d = idx(diagX, y - 1); g[d] = c; g[p] = dc; stampGrid[d] = tickParity; return } } if (diagX >= 0 && diagX < cols && g[idx(diagX, y)] === EMPTY) { - const d = idx(diagX, y); g[d] = GAS; g[p] = EMPTY; stampGrid[d] = tickParity + const d = idx(diagX, y); g[d] = c; g[p] = EMPTY; stampGrid[d] = tickParity } } } @@ -148,7 +170,12 @@ function updateCloudRising( stampGrid: Uint8Array, tickParity: number ): void { const idx = (bx: number, by: number) => by * cols + bx - if (y < rows - 1 && g[idx(x, y + 1)] === EMPTY && rand() < 0.04) g[idx(x, y + 1)] = WATER + // Spawn water in one of the 3 cells below (diagonal or directly under) + if (y < rows - 1 && rand() < 0.013) { + const sdx = Math.floor(rand() * 3) - 1 + const snx = x + sdx + if (snx >= 0 && snx < cols && g[idx(snx, y + 1)] === EMPTY) g[idx(snx, y + 1)] = WATER + } if (rand() < 0.3) { const dx = rand() < 0.5 ? -1 : 1 const dy = rand() < 0.3 ? -1 : rand() < 0.5 ? 1 : 0 @@ -393,8 +420,8 @@ export function risingPhysicsSystem(g: Uint8Array, cols: number, rows: number, c // ── FIRE / BLUE_FIRE ── if (c === FIRE || c === BLUE_FIRE) { updateFireRising(g, x, y, p, c, cols, rows, rand, stampGrid, tickParity); continue } - // ── GAS ── - if (c === GAS) { updateGasRising(g, x, y, p, cols, rows, rand, stampGrid, tickParity); continue } + // ── GAS / SMOKE ── + if (c === GAS || c === SMOKE) { updateGasRising(g, x, y, p, c, cols, rows, rand, stampGrid, tickParity); continue } // ── PLASMA ── if (c === PLASMA) { updatePlasmaRising(g, x, y, p, cols, rows, rand, stampGrid, tickParity); continue }