Professional City Advisor rewrite + wall-clock simulation
Advisor v2: - Phased policy (founding/village/town/city) with scaling reserves - Maximal-coverage siting: services score candidate spots by count of previously-uncovered developed buildings in radius - RCI gradient genesis: homes north, commerce buffering, industry set back 2 extra tiles; per-tile separation filters on new districts - District placement scored by runway length and distance from housing - Power sized to PROJECTED demand (history-smoothed growth x14 months) with tech diversity cap (<=3 per type) and home-distance scoring - Collector roads: cluster orphaned plots, one straight L-path - Budget discipline: one big-ticket purchase max per pass Simulation core: - City clock now advances in wall time (tick-guard bounded), decoupled from render fps — slow devices no longer freeze the city - Smoke test polls in game-time (monthIndex targets), not wall time
This commit is contained in:
@@ -292,5 +292,42 @@ console.log('— save / load integrity —');
|
||||
ok(actions2.length <= 2, `second pass is conservative (${actions2.length} actions)`);
|
||||
}
|
||||
|
||||
// ---------------- advisor professionalism ----------------
|
||||
{
|
||||
// RCI separation: industry must keep its distance from homes
|
||||
const c9 = new City(31337, 'Zonedville');
|
||||
const a9 = new AutoBuilder(c9);
|
||||
a9.run();
|
||||
const g9 = c9.grid, S = g9.size;
|
||||
const resTiles = [], indTiles = [];
|
||||
for (let i = 0; i < g9.n; i++) {
|
||||
if (g9.zone[i] === ZONE.RES) resTiles.push([i % S, (i / S) | 0]);
|
||||
if (g9.zone[i] === ZONE.IND) indTiles.push([i % S, (i / S) | 0]);
|
||||
}
|
||||
ok(resTiles.length > 0 && indTiles.length > 0, `genesis zones both banks (res ${resTiles.length}, ind ${indTiles.length})`);
|
||||
let minDist = Infinity;
|
||||
for (const [rx, rz] of resTiles) for (const [ix, iz] of indTiles) {
|
||||
const d = Math.max(Math.abs(rx - ix), Math.abs(rz - iz));
|
||||
if (d < minDist) minDist = d;
|
||||
}
|
||||
ok(minDist >= 3, `industry set back from homes (min gap ${minDist})`);
|
||||
|
||||
// budget discipline: never more than one big-ticket purchase per pass
|
||||
const c10 = new City(555, 'Richville', );
|
||||
c10.money = 40000;
|
||||
for (let x = 8; x < 40; x++) c10.placeStruct(STRUCT.ROAD, x, 30);
|
||||
const rt = [];
|
||||
for (let x = 9; x < 30; x++) for (let z = 27; z <= 33; z++) rt.push([x, z]);
|
||||
c10.placeZone(ZONE.RES, rt);
|
||||
for (let i = 0; i < 6; i++) c10.tick();
|
||||
const a10 = new AutoBuilder(c10);
|
||||
let worstBig = 0;
|
||||
for (let pass = 0; pass < 4; pass++) {
|
||||
a10.run();
|
||||
worstBig = Math.max(worstBig, a10.bigSpends);
|
||||
}
|
||||
ok(worstBig <= 1, `at most one big-ticket per pass (worst ${worstBig})`);
|
||||
}
|
||||
|
||||
console.log(`\n${pass} passed, ${fail} failed`);
|
||||
process.exit(fail ? 1 : 0);
|
||||
|
||||
+7
-3
@@ -149,13 +149,17 @@ await safe('shot-built', () => shot(join(SHOTS,'x') || { path: join(SHOTS, '02-b
|
||||
|
||||
// ---- simulate ~14 months at fast speed ----
|
||||
await safe('speed2', () => page.evaluate(() => window.POLYCITY.setSpeed(2)));
|
||||
for (let k = 0; k < 11; k++) {
|
||||
// wait in GAME time, not wall time: environments render at wildly
|
||||
// different speeds, so poll until the city has lived >= 8 months
|
||||
for (let k = 0; k < 24; k++) {
|
||||
await sleep(2000);
|
||||
const s = await safe('probe' + k, () => page.evaluate(() => ({
|
||||
pop: window.POLYCITY.city.stats.pop,
|
||||
dev: [...window.POLYCITY.city.grid.level].filter(v => v > 0).length
|
||||
dev: [...window.POLYCITY.city.grid.level].filter(v => v > 0).length,
|
||||
m: window.POLYCITY.city.monthIndex
|
||||
})), 8000);
|
||||
LOG(`t+${(k + 1) * 2}s pop=${s?.pop ?? '?'} dev=${s?.dev ?? '?'}`);
|
||||
LOG(`probe${k}: month=${s?.m ?? '?'} pop=${s?.pop ?? '?'} dev=${s?.dev ?? '?'}`);
|
||||
if (s && s.m >= 8 && s.pop > 40) { LOG('sim target reached'); break; }
|
||||
}
|
||||
|
||||
const state = await safe('state', () => page.evaluate(() => {
|
||||
|
||||
Reference in New Issue
Block a user