No deep ore in rock hot enough to light or melt it; reloaded worlds settle again (t3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 18:25:13 -04:00
co-authored by Claude Opus 5.5
parent 0b37e497eb
commit 4e3d8e41f6
5 changed files with 34 additions and 3 deletions
+4
View File
@@ -240,6 +240,10 @@ above, including:
their price). The top quarter of the underground is unchanged; copper also forms in deep stone their price). The top quarter of the underground is unchanged; copper also forms in deep stone
and granite, coal in basalt, tin deeper. Lenses grow from lower noise thresholds only: and granite, coal in basalt, tin deeper. Lenses grow from lower noise thresholds only:
stretching the noise with depth sheared it into diagonal streaks. stretching the noise with depth sheared it into diagonal streaks.
No ore is placed where the natural rock temperature would light it (coal) or melt it (metal
ores, within 200°C): deep coal and gold next to the magma burned and ran forever once a
reload woke those areas, and a Huge world dropped to about 10 fps. So deep coal stays in a
cooler mid band, and coal is about 3.2x rather than 5x; total ore about 4.2x.
- Waste rock pays: grinding granite and basalt finds about twice the ore, and builders order - Waste rock pays: grinding granite and basalt finds about twice the ore, and builders order
granite and basalt rubble in bulk. granite and basalt rubble in bulk.
- A Huge size (6144 x 2048); saves are gzip-compressed (a Medium save went from 29 MB to 0.4 MB). - A Huge size (6144 x 2048); saves are gzip-compressed (a Medium save went from 29 MB to 0.4 MB).
+1 -1
View File
@@ -324,7 +324,7 @@ Weather: clouds from steam, rain and snow, and the world water count.
simTick: one simulation step, in order. simTick: one simulation step, in order.
- functions: simTick - functions: simTick
### src/sim/worldgen.js (322 lines) ### src/sim/worldgen.js (326 lines)
World generation: noise, layers, ores, caves, water, trees, and clearing tiny hard specks. World generation: noise, layers, ores, caves, water, trees, and clearing tiny hard specks.
- functions: makeNoise, fbm, clearHardSpecks, generateWorld - functions: makeNoise, fbm, clearHardSpecks, generateWorld
- variables: SPECK_MIN - variables: SPECK_MIN
+13
View File
@@ -4,6 +4,19 @@ What agents have done, newest first. Add an entry for every piece of work: the d
note or request it was for, what changed, the files touched, the tests run, and anything left note or request it was for, what changed, the files touched, the tests run, and anything left
open or not verified. Keep each entry short; the git history has the details. open or not verified. Keep each entry short; the git history has the details.
## 2026-10-24: t3 fix: deep ore burned and melted after a reload (Claude Code)
- Owner: a Huge world ran at about 10 fps after travelling between claims, even after a refresh.
Cause: reloading wakes every area, and t3's deep coal (ignites at 430°C, natural rock reaches
that at about 70% depth) and gold next to the magma burned and melted forever: about 2,100 of
12,288 areas stayed busy, 65 ms per tick. A fresh world hid it because nothing deep was awake.
- `worldgen.js`: an ore is not placed where `AMB[y]` is within 120°C of its ignition or 200°C of
its melting point; a cooler coal band in stone and granite at 45%+ depth replaces most of the
lost coal. Huge after a reload: 335 areas awake, about 5 ms per tick; Medium 104 (126 before
t3). Medium seed 424242 ore is now about 4.2x the original (coal 3.2x, gold 2.8x).
- `test/t_world.js`: no ore in too-hot rock; a reloaded world settles with no fires and under
10% of areas moving; ore balance ranges updated. Existing saves made with the bug keep it.
## 2026-10-24: t3: a world that lasts (Claude Code, Codex) ## 2026-10-24: t3: a world that lasts (Claude Code, Codex)
- `26d17c2` deeper is richer (`worldgen.js` ore thresholds drop with depth below fy 0.25). - `26d17c2` deeper is richer (`worldgen.js` ore thresholds drop with depth below fy 0.25).
+4
View File
@@ -177,10 +177,12 @@ function generateWorld(seedIn, w, h, onProgress) {
} }
// ores // ores
if (m === STONE || m === DIRT || m === GRANITE || m === BASALT || m === CLAY) { if (m === STONE || m === DIRT || m === GRANITE || m === BASALT || m === CLAY) {
const host = m;
// Leave the shallow quarter unchanged; deeper rock has broader, richer lenses. // Leave the shallow quarter unchanged; deeper rock has broader, richer lenses.
const rich = Math.min(1, Math.max(0, (fy - 0.25) / 0.6)); const rich = Math.min(1, Math.max(0, (fy - 0.25) / 0.6));
const c = nCoal(x / 13, y / 9); const c = nCoal(x / 13, y / 9);
if ((d > 6 && fy < 0.45 && c > 0.5 - 0.55 * rich && m !== CLAY) || if ((d > 6 && fy < 0.45 && c > 0.5 - 0.55 * rich && m !== CLAY) ||
(fy >= 0.45 && (m === STONE || m === GRANITE) && c > 0.62 - 0.3 * rich) ||
(m === BASALT && fy > 0.6 && c > 0.58 - 0.4 * rich)) m = COALV; (m === BASALT && fy > 0.6 && c > 0.58 - 0.4 * rich)) m = COALV;
else { else {
const cu = nCu(x / 9, y / 8); const cu = nCu(x / 9, y / 8);
@@ -201,6 +203,8 @@ function generateWorld(seedIn, w, h, onProgress) {
else if (fy > 0.68 && (m === GRANITE || m === BASALT) && nAu(x / 6, y / 5) > 0.7 - 0.18 * rich) m = AUV; else if (fy > 0.68 && (m === GRANITE || m === BASALT) && nAu(x / 6, y / 5) > 0.7 - 0.18 * rich) m = AUV;
} }
} }
// no ore where the rock is naturally hot enough to light or melt it: it would burn or run forever
if (m !== host && (IGN[m] ? AMB[y] > IGN[m] - 120 : MELT[m] && AMB[y] > MELT[m] - 200)) m = host;
} }
// caves // caves
if (d > 9) { if (d > 9) {
+12 -2
View File
@@ -170,11 +170,12 @@ const oldOre = [33493, 26508, 77679, 11797, 7723, 3638, 1681];
const oreNames = ['coal', 'copper', 'iron', 'zinc', 'tin', 'silver', 'gold']; const oreNames = ['coal', 'copper', 'iron', 'zinc', 'tin', 'silver', 'gold'];
oreNames.forEach((name, j) => { oreNames.forEach((name, j) => {
const ratio = oreBalance.counts[j] / oldOre[j]; const ratio = oreBalance.counts[j] / oldOre[j];
const lo = j < 2 ? 4.5 : j < 5 ? 4 : 3, hi = j < 2 ? 5.5 : j < 5 ? 5 : 4; // coal is lower than the rest: seams deep enough to light in the natural heat are left out
const lo = j === 0 ? 3 : j < 5 ? 4 : 2.5, hi = j === 0 ? 4 : j < 5 ? 5 : 3.5;
check(`${name} stays within its intended increase`, ratio >= lo && ratio <= hi, ratio.toFixed(2)); check(`${name} stays within its intended increase`, ratio >= lo && ratio <= hi, ratio.toFixed(2));
}); });
const totalOre = oreBalance.counts.reduce((a, b) => a + b, 0); const totalOre = oreBalance.counts.reduce((a, b) => a + b, 0);
check('total ore is about 4.5 to 5 times the original', totalOre >= 162519 * 4.5 && totalOre <= 162519 * 5, totalOre); check('total ore is about 4 to 4.5 times the original', totalOre >= 162519 * 4 && totalOre <= 162519 * 4.5, totalOre);
check('shallow ore matches the original generator', oreBalance.shallowHash === 706875613, oreBalance.shallowHash); check('shallow ore matches the original generator', oreBalance.shallowHash === 706875613, oreBalance.shallowHash);
run('generateWorld(99, 4096, 1536, null)'); run('generateWorld(99, 4096, 1536, null)');
const marks = run('(()=>{let lakes=0,peaks=0,inLake=false;for(let x=1;x<W-1;x++){const l=mat[(surfY[x]-1)*W+x]===WATER;if(l&&!inLake)lakes++;inLake=l;if(surfY[x]<surfBase-100&&surfY[x]<=surfY[x-1]&&surfY[x]<surfY[x+1])peaks++;}return [lakes,peaks]})()'); const marks = run('(()=>{let lakes=0,peaks=0,inLake=false;for(let x=1;x<W-1;x++){const l=mat[(surfY[x]-1)*W+x]===WATER;if(l&&!inLake)lakes++;inLake=l;if(surfY[x]<surfBase-100&&surfY[x]<=surfY[x-1]&&surfY[x]<surfY[x+1])peaks++;}return [lakes,peaks]})()');
@@ -258,4 +259,13 @@ const pour = (bx) => {
}; };
const [withBot, aboveBot] = pour(118), [without] = pour(200); const [withBot, aboveBot] = pour(118), [without] = pour(200);
check('sand poured onto the bot falls through it to the ground', withBot === without && aboveBot === 0, `${withBot} grains landed (${without} with the bot away), ${aboveBot} held up above it`); check('sand poured onto the bot falls through it to the ground', withBot === without && aboveBot === 0, `${withBot} grains landed (${without} with the bot away), ${aboveBot} held up above it`);
// deep ore never sits in rock hot enough to light or melt it: a reloaded world wakes every area,
// and burning coal or running gold down there kept thousands of areas busy forever
run('generateWorld(77, 2048, 1024, null)');
const hotOre = run('(() => { let n = 0; for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) { const m = mat[y * W + x]; if ([COALV, CUV, FEV, SNV, ZNV, AGV, AUV].includes(m) && (IGN[m] ? AMB[y] > IGN[m] - 120 : AMB[y] > MELT[m] - 200)) n++; } return n; })()');
check('no ore is generated where the rock is hot enough to light or melt it', hotOre <= 20, hotOre);
run('globalThis.__s = serializeWorld(); deserializeWorld(JSON.parse(JSON.stringify(__s.meta)), __s.buffers); for (let k = 0; k < 900; k++) simTick();');
const busy = run('(() => { let aw = 0, fl = 0; for (let c = 0; c < NCH; c++) if (awake[c]) aw++; for (let i = 0; i < N; i++) if (mat[i] === FLAME) fl++; return [aw, NCH, fl]; })()');
check('a reloaded world settles: no fires deep down, few areas still moving', busy[2] === 0 && busy[0] < busy[1] * 0.1, busy);
done(); done();