Files
arcane-tycoon/tests/rendercheck.mjs
T
deepseek ac00687480 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
2026-08-23 06:59:21 +00:00

64 lines
3.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============ 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);