Arcane Tycoon — Heroes & Magic theme park tycoon game
Complete browser game inspired by OpenRCT2 with fantasy twist: - Custom roller coaster designer with physics-based ratings + on-ride POV - 10 animated rides, 7 shops, 16 scenery items, path network & guest AI - Heroes guild vs monster invasions (5 classes, XP/gear/bosses) - Magic spell system (8 spells), research tree, economy/marketing/loans - Day-night cycle, weather, park rating, awards, 4 scenarios - Save/load slots + autosave, procedural WebAudio SFX/music - Isometric canvas renderer, minimap, diagnostics overlay - Test suites: smoke(13), linkcheck, inputcheck, framecheck, rendercheck, flow
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
// ============ rendercheck.mjs — regression test for terrain culling ============
|
||||
let fails = 0;
|
||||
const ok = (cond, msg) => { console.log((cond ? '✔ ' : '✘ ') + msg); if (!cond) fails++; };
|
||||
|
||||
globalThis.window = globalThis;
|
||||
globalThis.requestAnimationFrame = () => 0;
|
||||
globalThis.document = {
|
||||
getElementById: () => null, createElement: () => ({ style: {}, classList: { add() {}, remove() {} }, addEventListener() {}, appendChild() {} }),
|
||||
addEventListener() {}, body: { appendChild() {} },
|
||||
};
|
||||
globalThis.localStorage = { getItem: () => null, setItem() {}, removeItem() {} };
|
||||
|
||||
const R = await import('../js/render/renderer.js');
|
||||
const { visibleBounds, worldToScreen, screenToWorld } = R;
|
||||
ok(typeof visibleBounds === 'function', 'visibleBounds exported');
|
||||
|
||||
// --- camera translation regression: panning MUST shift screen coords ---
|
||||
const camA = { x: 20, y: 20, zoom: 1 };
|
||||
const camB = { x: 30, y: 26, zoom: 1 };
|
||||
const [ax] = worldToScreen(camA, 1280, 800, 10, 10);
|
||||
const [bx] = worldToScreen(camB, 1280, 800, 10, 10);
|
||||
ok(Math.abs(bx - ax) > 50, `worldToScreen shifts with cam.x (${ax.toFixed(0)} → ${bx.toFixed(0)})`);
|
||||
const [, ay2] = worldToScreen(camA, 1280, 800, 10, 10);
|
||||
const camC = { x: 20, y: 30, zoom: 1 };
|
||||
const [, cy2] = worldToScreen(camC, 1280, 800, 10, 10);
|
||||
ok(Math.abs(cy2 - ay2) > 50, `worldToScreen shifts with cam.y (${ay2.toFixed(0)} → ${cy2.toFixed(0)})`);
|
||||
|
||||
// --- roundtrip screen→world→screen identity ---
|
||||
for (const cz of [0.5, 1, 1.7]) {
|
||||
const c = { x: 25.3, y: 41.8, zoom: cz };
|
||||
const [sx, sy] = worldToScreen(c, 1280, 800, 12.4, 33.9, 0); // ground plane
|
||||
const [wx, wy] = screenToWorld(c, 1280, 800, sx, sy);
|
||||
ok(wx === 12 && wy === 33,
|
||||
`roundtrip zoom ${cz}: (${sx.toFixed(0)},${sy.toFixed(0)}) → (${wx},${wy}) expect (12,33)`);
|
||||
}
|
||||
|
||||
// --- visibleBounds must follow the camera ---
|
||||
const bA = visibleBounds({ x: 5, y: 5, zoom: 1 }, 1280, 800, 80);
|
||||
const bB = visibleBounds({ x: 40, y: 40, zoom: 1 }, 1280, 800, 80);
|
||||
ok(bB.x0 > bA.x0 + 10 && bB.y0 > bA.y0 + 10, `bounds track camera (x0 ${bA.x0}→${bB.x0}, y0 ${bA.y0}→${bB.y0})`);
|
||||
|
||||
const cases = [
|
||||
{ name: 'center of 52-map, zoom 1', cam: { x: 26, y: 26, zoom: 1 }, cw: 1280, ch: 800 },
|
||||
{ name: 'entrance area, zoom 1', cam: { x: 28, y: 44, zoom: 1 }, cw: 1280, ch: 800 },
|
||||
{ name: 'zoomed out 0.5', cam: { x: 26, y: 26, zoom: 0.5 }, cw: 1280, ch: 800 },
|
||||
{ name: 'zoomed in 2', cam: { x: 26, y: 26, zoom: 2 }, cw: 1920, ch: 1080 },
|
||||
];
|
||||
for (const c of cases) {
|
||||
const b = visibleBounds(c.cam, c.cw, c.ch, 80);
|
||||
const spanX = b.x1 - b.x0, spanY = b.y1 - b.y0;
|
||||
// exact iso-diamond coverage: ((cw+2p)/(TW2·z) + (ch+2p)/(TH2·z)) / 2 per axis
|
||||
const p = 80;
|
||||
const expectSpan = ((c.cw + 2 * p) / (32 * c.cam.zoom) + (c.ch + 2 * p) / (16 * c.cam.zoom)) / 2;
|
||||
ok(spanX >= expectSpan - 2 && spanY >= expectSpan - 2,
|
||||
`${c.name}: span ${spanX}×${spanY} tiles (expect ≈${Math.round(expectSpan)})`);
|
||||
}
|
||||
|
||||
// the old bug produced a y-span of roughly 10 rows on a 1280×800 @zoom1 view
|
||||
const b = visibleBounds({ x: 26, y: 26, zoom: 1 }, 1280, 800, 80);
|
||||
ok(b.y1 - b.y0 > 40, `vertical coverage fixed (${b.y1 - b.y0} rows, was ~10 before)`);
|
||||
|
||||
console.log(fails ? `\n${fails} RENDER FAILURES` : '\nRENDER CULLING OK');
|
||||
process.exit(fails ? 1 : 0);
|
||||
Reference in New Issue
Block a user