Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/sim/archetypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -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] }
Comment thread
VoX marked this conversation as resolved.
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] }
Expand Down
2 changes: 2 additions & 0 deletions src/sim/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions src/sim/systems/falling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions src/sim/systems/liquid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
49 changes: 38 additions & 11 deletions src/sim/systems/rising.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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
}
}
}
Expand Down Expand Up @@ -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

Copilot AI Feb 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cloud water spawning writes directly into g[idx(snx, y + 1)] but doesn't wake/mark the destination chunk. Since ChunkMap.updateActivity() only recomputes checksums for currently-active chunks, spawning into a sleeping chunk can leave the new WATER unrendered and unprocessed until something else wakes that chunk. Consider passing chunkMap into this handler (or using a helper like simSetCell) to wakeChunk/mark dirty for the destination cell’s chunk when spawning water (especially now that spawning can be diagonal into neighboring chunk columns).

Suggested change
if (snx >= 0 && snx < cols && g[idx(snx, y + 1)] === EMPTY) g[idx(snx, y + 1)] = WATER
if (snx >= 0 && snx < cols) {
const d = idx(snx, y + 1)
if (g[d] === EMPTY) {
g[d] = WATER
stampGrid[d] = tickParity
}
}

Copilot uses AI. Check for mistakes.
}
if (rand() < 0.3) {
const dx = rand() < 0.5 ? -1 : 1
const dy = rand() < 0.3 ? -1 : rand() < 0.5 ? 1 : 0
Expand Down Expand Up @@ -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 }
Expand Down
Loading