Auto-Build advisor + resilient simulation loop
- New advisor button (toolbar, purple wand): one click per planning pass * Genesis: founds a town from a blank map (main street + zone bands) * Keeps power ahead of demand (wind/solar/coal by deficit size) * BFS road spurs to reach orphaned zoned land * Stamps new districts when demand is high and empty plots run out * Places the weakest service coverage; parks/plazas for land value * Never spends below an emergency reserve; recomputes power per action - Simulation no longer dies when rAF throttles: interval watchdog compensates only after 3s of total frame silence - preserveDrawingBuffer now opt-in via ?cap=1 (software-GL perf) - Smoke test drives via game API, prefers headless_shell binary, tolerates stalled screenshots; engine suite at 51 assertions
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
* brownouts, save/load integrity and milestones.
|
||||
*/
|
||||
import { City } from '../src/game/city.js';
|
||||
import { AutoBuilder } from '../src/game/autobuilder.js';
|
||||
import { STRUCT, ZONE, SIM } from '../src/config.js';
|
||||
|
||||
function findSpot(city, w = 1, h = 1, startX = 8, startZ = 34) {
|
||||
@@ -255,5 +256,41 @@ console.log('— save / load integrity —');
|
||||
ok(!spawnedWhileOff, 'disabled flag prevents tornado spawns');
|
||||
}
|
||||
|
||||
// ---------------- auto-builder advisor ----------------
|
||||
{
|
||||
// blank-map genesis
|
||||
const c0 = new City(777777, 'Blankville');
|
||||
const a0 = new AutoBuilder(c0);
|
||||
const acts0 = a0.run();
|
||||
const roadCount0 = [...c0.grid.struct].filter(v => v === STRUCT.ROAD).length;
|
||||
ok(roadCount0 >= 10 && acts0.length > 0, `genesis builds a town from nothing (${acts0[0] || 'nothing'})`);
|
||||
a0.run(); c0.tick();
|
||||
const plantOnBlank = [...c0.grid.struct].some(v => v >= STRUCT.COAL && v <= STRUCT.WIND);
|
||||
ok(plantOnBlank, 'second pass powers the new town');
|
||||
}
|
||||
{
|
||||
const c = new City(424242, 'AutoTown');
|
||||
for (let x = 10; x < 42; x++) c.placeStruct(STRUCT.ROAD, x, 30);
|
||||
const tiles = [];
|
||||
for (let x = 11; x < 32; x++) for (let z = 27; z <= 33; z++) tiles.push([x, z]);
|
||||
c.placeZone(ZONE.RES, tiles);
|
||||
c.placeZone(ZONE.COM, [[33, 27], [34, 28], [35, 29], [36, 30], [37, 31], [38, 32]]);
|
||||
|
||||
const ab = new AutoBuilder(c);
|
||||
const moneyBefore = c.money;
|
||||
const actions1 = ab.run();
|
||||
ok(actions1.length > 0, `advisor acts on a needy town (${actions1.join('; ')})`);
|
||||
const hasPlant = [...c.grid.struct].some(v =>
|
||||
v === STRUCT.COAL || v === STRUCT.SOLAR || v === STRUCT.WIND);
|
||||
if (hasPlant) c.tick();
|
||||
ok(c.stats.powerCap > 0, 'advisor secured a working power plant');
|
||||
const spent = moneyBefore - c.money;
|
||||
ok(spent > 0 && c.money >= ab.reserve - 1, `advisor respects the emergency reserve (spent $${spent}, left $${Math.round(c.money)})`);
|
||||
|
||||
// idempotent-ish: immediately running again with everything satisfied does little/nothing
|
||||
const actions2 = ab.run();
|
||||
ok(actions2.length <= 2, `second pass is conservative (${actions2.length} actions)`);
|
||||
}
|
||||
|
||||
console.log(`\n${pass} passed, ${fail} failed`);
|
||||
process.exit(fail ? 1 : 0);
|
||||
|
||||
Reference in New Issue
Block a user