- 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)
99 lines
3.9 KiB
JavaScript
99 lines
3.9 KiB
JavaScript
// 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();
|