IMPERIUM ARCANUM — web-based Caesar 3-like city builder with heroes, magic & monsters

- Full classic layer: roads/walkers, 11-tier housing evolution, food chain,
  industry chains, trade routes, taxes/wages, ratings, fires & collapse
- Fantasy layer: heroes (XP/levels/auras), mana & 7 spells, monsters/invasions,
  walls/towers/legions/golems, campaign of 5 scenarios + endless sandbox
- Canvas2D isometric engine with procedural sprites, zero runtime deps
- Save/load slots + autosave, WebAudio synth music/SFX, HUD/menus/minimap
- Tests: headless sim smoke test + Playwright E2E (build/combat/save)
This commit is contained in:
deepseek
2026-08-23 07:00:05 +00:00
commit b642726428
38 changed files with 7288 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
// Reproduce: real mouse clicks to build a road + house
import { chromium } from 'playwright-core';
const exe = '/root/.cache/ms-playwright/chromium_headless_shell-1148/chrome-linux/headless_shell';
const BASE = process.env.BASE || 'http://127.0.0.1:4174/';
const errors = [];
const browser = await chromium.launch({ executablePath: exe, args: ['--no-sandbox'] });
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
page.on('console', m => { if (m.type() === 'error') errors.push(m.text()); });
page.on('pageerror', e => errors.push('PAGEERROR: ' + e.message));
await page.goto(BASE, { waitUntil: 'networkidle' });
await page.waitForTimeout(600);
await page.click('#mm-sandbox');
await page.waitForTimeout(900);
// helper: convert tile -> screen px using exposed camera
async function tileToScreen(tx, ty) {
return page.evaluate(([x, y]) => {
const app = window.__IA.app;
const [wx, wy] = app.cam.tileToWorld(x, y);
const [sx, sy] = app.cam.worldToScreen(wx, wy);
return [sx, sy];
}, [tx, ty]);
}
async function clickTile(x, y) {
const [sx, sy] = await tileToScreen(x, y);
await page.mouse.move(sx, sy);
await page.waitForTimeout(80);
await page.mouse.click(sx, sy);
await page.waitForTimeout(60);
}
// 1) pick road tool via UI card
await page.click('#cattabs .cattab[data-cat="housing"]');
await page.waitForTimeout(200);
const cards = await page.$$('#buildcards .bcard');
console.log('cards:', cards.length);
for (const c of cards) console.log(' card:', (await c.textContent()).trim().slice(0, 30));
// find road card by title text
let roadCard = null;
for (const c of cards) { if ((await c.textContent()).includes('Road')) { roadCard = c; break; } }
await roadCard.click();
await page.waitForTimeout(150);
const toolState1 = await page.evaluate(() => JSON.stringify(window.__IA.app.tool));
console.log('tool after road select:', toolState1);
// center camera on map middle
await page.evaluate(() => {
const app = window.__IA.app;
const mid = app.cam.tileToWorld(app.city.W / 2, app.city.H / 2);
app.cam.cx = mid[0]; app.cam.cy = mid[1];
});
await page.waitForTimeout(100);
// place road at center tile
const c0 = Math.floor(await page.evaluate(() => window.__IA.app.city.W / 2));
await clickTile(c0, c0);
const afterRoad = await page.evaluate(([x, y]) => {
const t = window.__IA.app.city.tiles[y * window.__IA.app.city.W + x];
return { road: t.road, funds: window.__IA.app.city.funds };
}, [c0, c0]);
console.log('after road click:', JSON.stringify(afterRoad));
// 2) select house card and click NEXT to the road
let houseCard = null;
for (const c of await page.$$('#buildcards .bcard')) { if ((await c.textContent()).includes('House')) { houseCard = c; break; } }
await houseCard.click();
await page.waitForTimeout(150);
console.log('tool after house select:', await page.evaluate(() => JSON.stringify(window.__IA.app.tool)));
await clickTile(c0, c0 - 1); // directly above road
const afterHouse = await page.evaluate(([x, y]) => {
const c = window.__IA.app.city;
const t = c.tiles[y * c.W + x];
const b = t.bld ? c.buildings.get(t.bld) : null;
return { bld: t.bld, def: b?.def ?? null, funds: Math.round(c.funds) };
}, [c0, c0 - 1]);
console.log('after house click:', JSON.stringify(afterHouse));
// ghost validity debug at that spot
const ghostDbg = await page.evaluate(([x, y]) => {
const app = window.__IA.app;
// replicate canPlace pieces
const c = app.city;
const t = c.tiles[y * c.W + x];
let roadAdj = false;
for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
const n = c.tiles[(y + dy) * c.W + (x + dx)];
if (n && (n.road || n.plaza)) roadAdj = true;
}
return { terrain: t.terrain, bldHere: t.bld, roadAdj, tool: app.tool, ghost: app.view.ghost };
}, [c0, c0 - 1]);
console.log('ghost debug:', JSON.stringify(ghostDbg));
await page.screenshot({ path: '/tmp/build_repro.png' });
console.log('errors:', errors.length ? errors.slice(0, 5) : 0);
await browser.close();