// 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();