tarinai/js/fire_runtime_system.js
2026-07-01 22:59:49 +09:00

469 lines
22 KiB
JavaScript

"use strict";
// Runtime behavior for fire, firecracker, flame spread, and fire contact effects.
(function (global) {
function updateFirecracker(item, dt, worldRef) {
item.fuseTimer = Math.max(0, (item.fuseTimer ?? 5) - dt);
if (item.fuseTimer <= 0) {
if (worldRef?.explodeFirecracker) worldRef.explodeFirecracker(item);
else item.amount = 0;
}
}
const FIRE_BURNING_TEXT = "\u71c3\u3048\u3066\u9003\u3052\u307e\u3069\u3063\u3066\u3044\u308b\u3002";
const FIRE_SEEN_TEXT = "\u708e\u3092\u898b\u3066\u9a5a\u3044\u3066\u3044\u308b\u3002";
const FIRE_CONTACT_INTERVAL = 0.35;
const FIRE_PANIC_INTERVAL = 1.20;
const FIRE_WATER_INTERVAL = 0.16;
const FIRE_CLUSTER_SCAN_INTERVAL = 0.45;
const FIRE_INTERACTION_BUDGET_DESKTOP = 18;
const FIRE_INTERACTION_BUDGET_MOBILE = 10;
const FIRE_MAX_ACTIVE_DESKTOP = 72;
const FIRE_MAX_ACTIVE_MOBILE = 44;
const GRASS_FIRE_EXTINGUISH_RATE = 0.56;
function fireTempMultiplier(worldRef, x, y, entity = null) {
const weather = String(worldRef?.weather || "");
const temp = Number(worldRef?.temperatureAt?.(x, y, entity) ?? worldRef?.feltTemperatureFor?.(entity) ?? worldRef?.temperature ?? global.CONFIG?.standardTemperature ?? 15);
let mul = 1;
if (weather === "light_rain") mul *= 2;
if (Number.isFinite(temp) && temp <= 10) mul *= 2;
return mul;
}
function pushFireEffect(worldRef, x, y, size = 12, seed = null) {
if (!worldRef?.effects || typeof Effect !== "function") return;
if ((worldRef.effects.length || 0) > 180) return;
const a = global.deterministicAngle(worldRef, "fire-effect-angle", seed || x, y, worldRef.time || 0);
const speed = global.deterministicRange(worldRef, "fire-effect-speed", 3, 15, seed || x, y);
worldRef.effects.push(new Effect("flame", x, y, {
vx: Math.cos(a) * speed * 0.35,
vy: -Math.max(4, speed * 0.65),
size: Math.max(4, size * 0.58),
life: global.deterministicRange(worldRef, "fire-effect-life", 0.18, 0.34, seed || x, y),
color: "rgba(236,46,24,0.78)",
}));
}
function fireInteractionBudget(worldRef) {
const frame = Math.floor((Number(worldRef?.time || 0) || 0) * 60);
if (!worldRef._fireInteractionBudget || worldRef._fireInteractionBudget.frame !== frame) {
const mobile = Boolean(global.TarinaiInputMode?.snapshot?.().touchFirst || (global.innerWidth || 9999) <= 760);
worldRef._fireInteractionBudget = {
frame,
remaining: mobile ? FIRE_INTERACTION_BUDGET_MOBILE : FIRE_INTERACTION_BUDGET_DESKTOP,
};
}
return worldRef._fireInteractionBudget;
}
function useFireInteractionBudget(worldRef, cost = 1) {
const budget = fireInteractionBudget(worldRef);
if (!budget || budget.remaining < cost) return false;
budget.remaining -= cost;
return true;
}
function contactWaterDrop(worldRef, x, y, radius = 16) {
if (!worldRef) return null;
const list = typeof worldRef.nearbyItems === "function" ? [...(worldRef.nearbyItems(x, y, radius + 28, true) || [])] : (worldRef.items || []);
for (const it of list) {
if (!it || it.dead || it.type !== "water") continue;
const rr = Math.max(radius, 10) + Math.max(it.r || 10, 10);
if (distXY(x, y, it.x, it.y) <= rr) return it;
}
return null;
}
function consumeWaterForFire(worldRef, water, amount = 8) {
if (!water) return false;
water.amount = Math.max(0, Number(water.amount || 0) - amount);
worldRef?.effects?.push(new Effect("ring", water.x, water.y, { size: Math.max(10, (water.r || 10) * 1.4), life: 0.18, color: "rgba(108,192,236,0.58)" }));
worldRef?.markItemBucketsDirty?.("fire-water");
worldRef?.markSpatialDirty?.("fire-water");
return true;
}
function igniteTarinaiByFire(t, source = null, worldRef = null, force = false) {
if (!t || t.dead) return false;
if (typeof t.igniteFire === "function") return t.igniteFire(source, force);
t.burning = true;
t.burnTimer = Math.max(t.burnTimer || 0, 5.0);
t.thought = FIRE_BURNING_TEXT;
t.lastPanicCause = "burning";
t.lastPanicDetail = FIRE_BURNING_TEXT;
t.fearTimer = Math.max(t.fearTimer || 0, 1.5);
t.sleeping = false;
t.setActionState?.("panic", { target: t.panicDestination ? t.panicDestination(source || null, true) : null, reason: FIRE_BURNING_TEXT, wake: true });
worldRef?.emit?.("fire:ignite", { tarinai: t, source });
return true;
}
function panicTarinaiFromFire(t, source = null, worldRef = null) {
if (!t || t.dead || t.burning || (t.burnTimer || 0) > 0.02) return false;
const now = Number(worldRef?.time || t.world?.time || 0) || 0;
if (now < Number(t.firePanicCooldownUntil || 0)) return false;
t.firePanicCooldownUntil = now + 0.85;
t.firePanicTimer = Math.max(t.firePanicTimer || 0, 1.4);
t.lastPanicCause = "fire_seen";
t.lastPanicDetail = FIRE_SEEN_TEXT;
t.thought = FIRE_SEEN_TEXT;
if (typeof t.enterPanic === "function") t.enterPanic({ threat: source, target: source, reason: FIRE_SEEN_TEXT, fear: 1.25, wake: true, cause: "fire_seen", surpriseTimer: 0.55, awakeLockTimer: 0.35 });
else { t.fearTimer = Math.max(t.fearTimer || 0, 1.25); t.setActionState?.("panic", { target: t.panicDestination ? t.panicDestination(source || null, true) : null, reason: FIRE_SEEN_TEXT, wake: true }); }
return true;
}
function igniteAntByFire(ant, source = null, worldRef = null) {
if (!ant || ant.dead) return false;
if (typeof ant.igniteFire === "function") return ant.igniteFire(source);
ant.burning = true;
ant.burnTimer = Math.max(ant.burnTimer || 0, 5.0);
ant.state = "panic";
ant.targetId = "";
ant.targetRef = null;
worldRef?.emit?.("fire:ignite-ant", { ant, source });
return true;
}
function grassFireExtinguishChance(worldRef, grass, dt = 0.016) {
const baseDt = Math.max(Number(dt || 0) || 0, FIRE_CONTACT_INTERVAL);
const weather = String(worldRef?.weather || "");
const fresh = clamp(Number(grass?.growth ?? grass?.health ?? 0.65) || 0, 0, 1);
const rainMul = weather === "light_rain" ? 1.75 : 1;
return clamp(baseDt * GRASS_FIRE_EXTINGUISH_RATE * rainMul * (1 + fresh * 0.45), 0, 0.58);
}
function tryGrassExtinguishFire(grass, fire, worldRef = null, dt = 0.016) {
if (!grass || grass.dead || grass.type !== "grass" || !fire || fire.dead || fire.type !== "fire") return false;
const chance = grassFireExtinguishChance(worldRef, grass, dt);
if (!global.deterministicChance(worldRef, "grass-fire-extinguish", chance, grass, fire)) return false;
const reduction = Math.max(18, Math.max(grass.r || 16, 12) * 0.72 + Math.max(fire.r || 14, 12) * 0.52);
fire.amount = Math.max(0, Number(fire.amount || 0) - reduction);
fire.flameLife = Math.max(0, Number(fire.flameLife || 0) - 0.85);
grass.health = clamp((grass.health ?? 1) - 0.035, 0, 1);
if ((fire.amount || 0) <= 24 || (fire.flameLife || 0) <= 0.35) fire.amount = 0;
worldRef?.effects?.push(new Effect("ring", fire.x, fire.y, { size: Math.max(18, (fire.r || 14) * 1.8), life: 0.18, color: "rgba(118,188,94,0.46)" }));
worldRef?.markTerrainDirtyAt?.(grass.x, grass.y, Math.max(grass.r || 17, 28), "grass-extinguish-fire");
worldRef?.markItemBucketsDirty?.("grass-extinguish-fire");
worldRef?.markSpatialDirty?.("grass-extinguish-fire");
worldRef.drawListDirty = true;
return true;
}
function igniteGrassByFire(grass, source = null, worldRef = null, dt = 0.016) {
if (!grass || grass.dead || grass.type !== "grass") return false;
grass.burning = true;
grass.burnTimer = Math.max(grass.burnTimer || 0, 2.8);
grass.health = clamp((grass.health ?? 1) - dt * 0.30, 0, 1);
grass.amount = Math.max(0, Number(grass.amount || 0) - dt * 18);
if (grass.health <= 0.02 || grass.amount <= 0.5) grass.amount = 0;
if (worldRef && (worldRef.time || 0) > Number(grass.nextFireSpreadAt || -Infinity)) {
grass.nextFireSpreadAt = (worldRef.time || 0) + 1.6;
if (typeof worldRef.spawnFireAt === "function") {
const a = global.deterministicAngle(worldRef, "grass-fire-spread", grass, source);
worldRef.spawnFireAt(grass.x + Math.cos(a) * (grass.r || 14) * 0.55, grass.y + Math.sin(a) * (grass.r || 14) * 0.35, { amount: 60, radius: 12, life: 4.2, source: grass });
}
}
worldRef?.markTerrainDirtyAt?.(grass.x, grass.y, Math.max(grass.r || 17, 26), "grass-fire");
return true;
}
function igniteZunchiByFire(zunchi, source = null, worldRef = null, dt = 0.016) {
if (!zunchi || zunchi.dead || zunchi.type !== "zunchi") return false;
const wasBurning = Boolean(zunchi.burning || (zunchi.burnTimer || 0) > 0.02);
zunchi.burning = true;
zunchi.burnTimer = Math.max(Number(zunchi.burnTimer || 0) || 0, 4.6);
zunchi.spawnGrace = 0;
zunchi.freshness = clamp((zunchi.freshness ?? 1) - dt * 0.55, 0, 1);
zunchi.amount = Math.max(0, Number(zunchi.amount || 0) - dt * 34);
zunchi.stageTimer = Math.max(0, Number(zunchi.stageTimer || 0) + dt * 2.4);
if (!wasBurning) {
pushFireEffect(worldRef, zunchi.x, zunchi.y - Math.max(5, (zunchi.r || 14) * 0.2), Math.max(10, zunchi.r || 14), zunchi);
worldRef?.emit?.("fire:ignite-zunchi", { item: zunchi, source });
}
if ((zunchi.amount || 0) <= 0.5) zunchi.amount = 0;
worldRef?.markTerrainDirtyAt?.(zunchi.x, zunchi.y, Math.max(zunchi.r || 15, 28), "zunchi-fire");
worldRef?.markItemBucketsDirty?.("zunchi-fire");
worldRef?.markSpatialDirty?.("zunchi-fire");
worldRef.drawListDirty = true;
return true;
}
function updateFireContacts(item, dt, worldRef) {
if (!item || !worldRef) return false;
const r = Math.max(14, Number(item.r || 0) || 14);
const spreadRadius = r + 16;
let changed = false;
for (const t of worldRef.nearbyTarinai?.(item.x, item.y, spreadRadius + 32, true) || []) {
if (!t || t.dead) continue;
const d = distXY(item.x, item.y, t.x, t.y);
if (d <= spreadRadius + Math.max(t.radius || 18, 12) * 0.72) changed = igniteTarinaiByFire(t, item, worldRef, false) || changed;
}
for (const ant of worldRef.nearbyAnts?.(item.x, item.y, spreadRadius + 20, true) || []) {
if (!ant || ant.dead) continue;
const d = distXY(item.x, item.y, ant.x, ant.y);
if (d <= spreadRadius + Math.max(ant.r || 5, 4)) changed = igniteAntByFire(ant, item, worldRef) || changed;
}
const nearby = typeof worldRef.nearbyItems === "function" ? (worldRef.nearbyItems(item.x, item.y, spreadRadius + 38, true) || []) : (worldRef.items || []);
for (const it of nearby) {
if (!it || it.dead || it === item) continue;
if (it.type === "grass" && distXY(item.x, item.y, it.x, it.y) <= spreadRadius + Math.max(it.r || 16, 12)) {
const extinguished = tryGrassExtinguishFire(it, item, worldRef, dt);
changed = extinguished || changed;
if (!extinguished && (item.amount || 0) > 0) changed = igniteGrassByFire(it, item, worldRef, dt) || changed;
}
if (it.type === "zunchi" && distXY(item.x, item.y, it.x, it.y) <= spreadRadius + Math.max(it.r || 15, 12)) changed = igniteZunchiByFire(it, item, worldRef, dt) || changed;
if (it.type === "water" && distXY(item.x, item.y, it.x, it.y) <= spreadRadius + Math.max(it.r || 10, 8)) {
consumeWaterForFire(worldRef, it, Math.max(6, r * 0.45));
item.amount = 0;
changed = true;
}
}
return changed;
}
function panicFromNearbyFire(item, worldRef) {
if (!item || !worldRef) return;
const r = Math.max(170, (item.r || 14) + 170);
for (const t of worldRef.nearbyTarinai?.(item.x, item.y, r, true) || []) {
if (!t || t.dead) continue;
panicTarinaiFromFire(t, item, worldRef);
}
}
function updateFire(item, dt, worldRef) {
if (!item || item.dead || !worldRef) return false;
dt = Math.max(0, Math.min(0.08, Number(dt || 0) || 0));
item.amount = Math.max(0, Number(item.amount || 0) - dt * 4.2);
item.flameLife = Math.max(0, Number(item.flameLife ?? 8.0) - dt);
item.flameSpreadCooldown = Math.max(0, Number(item.flameSpreadCooldown || 0) - dt);
item.flamePanicCooldown = Math.max(0, Number(item.flamePanicCooldown || 0) - dt);
item.flameWaterCooldown = Math.max(0, Number(item.flameWaterCooldown || 0) - dt);
item.flameClusterCooldown = Math.max(0, Number(item.flameClusterCooldown || 0) - dt);
item.vx = 0;
item.vy = 0;
const lowFire = (item.amount || 0) < 24 || item.flameLife < 1.2;
if (item.flameWaterCooldown <= 0) {
item.flameWaterCooldown = FIRE_WATER_INTERVAL;
const water = contactWaterDrop(worldRef, item.x, item.y, Math.max(item.r || 14, 12));
if (water) {
consumeWaterForFire(worldRef, water, Math.max(8, (item.r || 14) * 0.6));
item.amount = 0;
return true;
}
}
const chance = dt * 0.012 * fireTempMultiplier(worldRef, item.x, item.y, item);
if (global.deterministicChance(worldRef, "fire-random-extinguish", chance, item)) {
item.amount = 0;
return true;
}
if (item.flameLife <= 0) item.amount = Math.min(item.amount, 1);
if (item.flameClusterCooldown <= 0) {
mergeNearbyFires(worldRef, item, Math.max(24, (item.r || 14) * 2.4), 4);
item.flameClusterCooldown = FIRE_CLUSTER_SCAN_INTERVAL;
}
if (!lowFire && item.flameSpreadCooldown <= 0 && useFireInteractionBudget(worldRef, 1)) {
updateFireContacts(item, dt, worldRef);
item.flameSpreadCooldown = FIRE_CONTACT_INTERVAL;
}
if (!lowFire && item.flamePanicCooldown <= 0 && useFireInteractionBudget(worldRef, 1)) {
panicFromNearbyFire(item, worldRef);
item.flamePanicCooldown = FIRE_PANIC_INTERVAL;
}
if (!lowFire && global.deterministicChance(worldRef, "fire-effect", dt * 0.72, item)) pushFireEffect(worldRef, item.x, item.y - (item.r || 14) * 0.2, Math.max(8, (item.r || 14) * 0.9), item);
return true;
}
function mergeNearbyFires(worldRef, fire, radius = 36, maxMerges = 4) {
if (!worldRef || !fire || fire.dead) return 0;
const nearby = typeof worldRef.nearbyItems === "function" ? (worldRef.nearbyItems(fire.x, fire.y, radius, true) || []) : (worldRef.items || []);
let merged = 0;
for (const other of nearby) {
if (!other || other === fire || other.dead || other.type !== "fire") continue;
if (distXY(fire.x, fire.y, other.x, other.y) > radius + Math.max(other.r || 14, 12)) continue;
fire.amount = Math.min(260, Math.max(fire.amount || 0, other.amount || 0) + Math.min(34, Math.max(0, Number(other.amount || 0) * 0.35)));
fire.flameLife = Math.max(fire.flameLife || 0, other.flameLife || 0);
fire.r = Math.min(42, Math.max(fire.r || 14, other.r || 14));
other.amount = 0;
merged += 1;
if (merged >= maxMerges) break;
}
if (merged) {
worldRef.markItemBucketsDirty?.("fire-merge");
worldRef.markSpatialDirty?.("fire-merge");
worldRef.drawListDirty = true;
}
return merged;
}
function activeFireLimit() {
const mobile = Boolean(global.TarinaiInputMode?.snapshot?.().touchFirst || (global.innerWidth || 9999) <= 760);
return mobile ? FIRE_MAX_ACTIVE_MOBILE : FIRE_MAX_ACTIVE_DESKTOP;
}
function activeFires(worldRef) {
return worldRef?.itemsOfType?.("fire") || (worldRef?.items || []).filter(it => it && !it.dead && it.type === "fire");
}
function foldIntoExistingFire(worldRef, x, y, opts = {}) {
const fires = activeFires(worldRef);
if (!fires.length) return null;
let best = null;
let bestD = Infinity;
const exclude = opts.exclude || null;
for (const fire of fires) {
if (!fire || fire.dead) continue;
if (exclude && fire === exclude) continue;
const d = distXY(x, y, fire.x, fire.y);
if (d < bestD) { best = fire; bestD = d; }
}
if (!best) return null;
best.amount = Math.min(280, Math.max(Number(best.amount || 0), Number(opts.amount || 0) * 0.45) + 14);
best.flameLife = Math.max(Number(best.flameLife || 0), Math.max(1.0, Number(opts.life || 3.5) * 0.75));
best.r = Math.min(44, Math.max(best.r || 14, Number(opts.radius || 14)));
best.vx = 0;
best.vy = 0;
worldRef.drawListDirty = true;
return best;
}
function compactWorldFires(worldRef, opts = {}) {
const fires = activeFires(worldRef).filter(fire => fire && !fire.dead);
const limit = Math.max(8, Number(opts.limit || activeFireLimit()) || activeFireLimit());
if (fires.length <= limit) return 0;
const cellSize = Math.max(46, Number(opts.cellSize || 72) || 72);
const cells = new Map();
for (const fire of fires) {
const key = `${Math.floor((fire.x || 0) / cellSize)}:${Math.floor((fire.y || 0) / cellSize)}`;
let group = cells.get(key);
if (!group) {
group = [];
cells.set(key, group);
}
group.push(fire);
}
let removed = 0;
for (const group of cells.values()) {
if (group.length <= 1) continue;
group.sort((a, b) => ((b.amount || 0) + (b.flameLife || 0) * 8 + (b.r || 0)) - ((a.amount || 0) + (a.flameLife || 0) * 8 + (a.r || 0)));
const keep = group[0];
for (let i = 1; i < group.length; i += 1) {
const fire = group[i];
keep.amount = Math.min(300, Math.max(keep.amount || 0, fire.amount || 0) + Math.min(18, Math.max(0, Number(fire.amount || 0) * 0.20)));
keep.flameLife = Math.max(keep.flameLife || 0, fire.flameLife || 0);
keep.r = Math.min(46, Math.max(keep.r || 14, fire.r || 14));
fire.amount = 0;
removed += 1;
}
}
if (fires.length - removed > limit) {
const survivors = fires.filter(fire => !fire.dead).sort((a, b) => ((a.amount || 0) + (a.flameLife || 0) * 8) - ((b.amount || 0) + (b.flameLife || 0) * 8));
let remaining = survivors.length;
for (const fire of survivors) {
if (remaining <= limit) break;
if (fire.amount <= 0) continue;
foldIntoExistingFire(worldRef, fire.x, fire.y, { ...fire, exclude: fire });
fire.amount = 0;
removed += 1;
remaining -= 1;
}
}
if (removed) {
worldRef.compactItems?.();
worldRef.markItemBucketsDirty?.("fire-compact");
worldRef.markSpatialDirty?.("fire-compact");
worldRef.drawListDirty = true;
}
return removed;
}
function spawnFireAt(worldRef, x, y, opts = {}) {
if (!worldRef || typeof Item !== "function") return null;
const currentFires = activeFires(worldRef);
if (currentFires.length >= activeFireLimit()) return foldIntoExistingFire(worldRef, x, y, opts);
const mergeRadius = Math.max(70, Number(opts.mergeRadius || opts.radius || 16) * 4.5);
const nearby = typeof worldRef.nearbyItems === "function" ? (worldRef.nearbyItems(x, y, mergeRadius, true) || []) : (worldRef.items || []);
for (const existing of nearby) {
if (!existing || existing.dead || existing.type !== "fire") continue;
if (distXY(x, y, existing.x, existing.y) > mergeRadius + Math.max(existing.r || 14, 12)) continue;
existing.amount = Math.min(260, Math.max(Number(existing.amount || 0), Number(opts.amount || 0) * 0.55) + 18);
existing.flameLife = Math.max(Number(existing.flameLife || 0), Math.max(1.2, Number(opts.life || 4.0) * 0.85));
existing.r = Math.min(42, Math.max(existing.r || 14, Number(opts.radius || 14)));
existing.vx = 0;
existing.vy = 0;
worldRef.drawListDirty = true;
return existing;
}
const fire = new Item("fire", x, y);
const scale = Math.max(0.45, Math.min(2.0, Number(opts.scale || 1) || 1));
fire.r = Math.max(9, Number(opts.radius || fire.r || 14) * scale);
fire.amount = Math.max(12, Number(opts.amount || fire.amount || 100) * scale);
fire.flameLife = Math.max(1.2, Number(opts.life || fire.flameLife || 7.5));
fire.vx = 0;
fire.vy = 0;
worldRef.addItem?.(fire, "spawn-fire", { terrain: false });
worldRef.drawListDirty = true;
return fire;
}
function scatterFireFrom(item, worldRef) {
if (!item || !worldRef) return false;
const existing = activeFires(worldRef).length;
const limit = activeFireLimit();
const room = Math.max(0, limit - existing);
const desired = Math.max(5, Math.round(8 * Math.max(0.75, Math.min(1.4, (item.r || 15) / 15))));
const count = Math.max(3, Math.min(desired, room > 0 ? room : 3));
for (let i = 0; i < count; i++) {
const a = (Math.PI * 2 * i / count) + global.deterministicRange(worldRef, "flame-cracker-angle", -0.20, 0.20, item, i);
const d = global.deterministicRange(worldRef, "flame-cracker-radius", 28, 118, item, i);
const fire = spawnFireAt(worldRef, item.x + Math.cos(a) * d, item.y + Math.sin(a) * d, {
radius: global.deterministicRange(worldRef, "flame-cracker-fire-radius", 10, 18, item, i),
amount: global.deterministicRange(worldRef, "flame-cracker-fire-amount", 55, 120, item, i),
life: global.deterministicRange(worldRef, "flame-cracker-fire-life", 4.0, 8.5, item, i),
source: item,
});
if (fire) {
fire.vx = 0;
fire.vy = 0;
updateFireContacts(fire, FIRE_CONTACT_INTERVAL, worldRef);
}
pushFireEffect(worldRef, item.x + Math.cos(a) * Math.min(d, 28), item.y + Math.sin(a) * Math.min(d, 28), 14, item);
}
worldRef.effects?.push(new Effect("ring", item.x, item.y, { size: Math.max(42, (item.r || 15) * 3.0), life: 0.36, color: "rgba(255,126,54,0.58)" }));
item.amount = 0;
worldRef.markItemBucketsDirty?.("flame-firecracker");
worldRef.markSpatialDirty?.("flame-firecracker");
return true;
}
function updateFlameFirecracker(item, dt, worldRef) {
item.fuseTimer = Math.max(0, (item.fuseTimer ?? 5) - dt);
if (item.fuseTimer <= 0) {
if (worldRef) scatterFireFrom(item, worldRef);
else item.amount = 0;
}
return true;
}
global.TarinaiFireRuntimeSystem = Object.freeze({
updateFirecracker,
updateFlameFirecracker,
updateFire,
fireBurningText: FIRE_BURNING_TEXT,
fireSeenText: FIRE_SEEN_TEXT,
fireTempMultiplier,
contactWaterDrop,
consumeWaterForFire,
igniteTarinaiByFire,
panicTarinaiFromFire,
igniteAntByFire,
igniteGrassByFire,
spawnFireAt: (worldRef, x, y, opts = {}) => spawnFireAt(worldRef, x, y, opts),
scatterFireFrom,
compactWorldFires,
});
})(typeof window !== "undefined" ? window : globalThis);