Tornado disasters + settings toggle

- Monthly-chance tornadoes (after month 30 grace) carve a wide erratic
  path, flattening buildings into rubble; anchors stay consistent
- 'Tornado disasters' toggle in menu settings, persisted
- Help handbook documents the hazard and the rebuild loop
- Engine suite grows to 45 assertions (spawn damage, event, anchors,
  suppression when disabled); soak keeps economy focus disaster-free
This commit is contained in:
PolyCity
2026-08-22 19:37:36 +00:00
parent 2a614b5435
commit 77931e9747
14 changed files with 101 additions and 1 deletions
+38
View File
@@ -217,5 +217,43 @@ console.log('— save / load integrity —');
ok(c2.stats.powerCap >= 6000, 'power recomputed on load');
}
// ---------------- tornado disasters ----------------
{
const c = new City(777, 'Tornadoville');
for (let x = 8; x < 40; x++) c.placeStruct(STRUCT.ROAD, x, 30);
const tiles = [];
for (let x = 9; x < 30; x++) for (let z = 27; z <= 33; z++) tiles.push([x, z]);
c.placeZone(ZONE.RES, tiles);
const cp = findSpot(c, 2, 2, 12, 36);
c.placeStruct(STRUCT.COAL, cp[0], cp[1]);
for (let i = 0; i < 10; i++) c.tick();
const devBefore = [...c.grid.level].filter(v => v > 0).length;
ok(devBefore > 0, 'town exists before tornado');
let eventFired = false;
c.on('disaster', (e) => { if (e.type === 'tornado' && e.destroyed > 0) eventFired = true; });
c.spawnTornado();
const rubbleAfter = [...c.grid.rubble].filter(v => v > 0).length;
ok(eventFired, 'disaster event emitted with damage count');
ok(rubbleAfter > 0, `tornado left rubble behind (${rubbleAfter} tiles)`);
let anchorBad = 0;
for (let i = 0; i < c.grid.n; i++) {
const s = c.grid.struct[i];
if (!s || s === STRUCT.ROAD || c.grid.anchor[i] !== i) continue;
if (c.grid.struct[c.grid.anchor[i]] !== s) anchorBad++;
}
ok(anchorBad === 0, 'anchors consistent after tornado destruction');
// disabled flag suppresses natural spawning
c.disastersEnabled = false;
let spawnedWhileOff = false;
const origSpawn = c.spawnTornado.bind(c);
c.spawnTornado = () => { spawnedWhileOff = true; return origSpawn(); };
c.monthIndex = 60;
for (let m = 0; m < 80; m++) c.tick();
ok(!spawnedWhileOff, 'disabled flag prevents tornado spawns');
}
console.log(`\n${pass} passed, ${fail} failed`);
process.exit(fail ? 1 : 0);
+1
View File
@@ -10,6 +10,7 @@ let pass = 0, fail = 0;
const ok = (cond, name) => { if (cond) { pass++; console.log(' ✔', name); } else { fail++; console.log(' ✘ FAIL:', name); } };
const c = new City(90210, 'Soakville');
c.disastersEnabled = false; // economy focus here; tornado behaviour is covered by engine.mjs
const g = c.grid;
c.money = 60000;