Dino Isle Online: multiplayer dino survival game (The Isle-like)
- Zero-dependency Node server: HTTP static + hand-rolled RFC6455 WebSocket + 20Hz authoritative simulation - Seeded island worldgen (tiles, forest decor, spawns) - 4 playable species, needs (food/water/stamina), rest, growth stages Hatchling->Apex - AI fauna: critters, fish shoals, Dryosaurus/Psittacosaurus herds; carcass feasting - PvP with knockback, kill feed, chat, leaderboard, minimap, day/night cycle - True top-down procedural dino renderer shared by menu cards and in-game - Developer mode (` key): god-mode, x6 XP, instant evolve, spawn AI, Alt+Click tp - Headless smoke tests, real-browser visual tests, pixel match checks
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
// Decode screenshots in headless chromium and verify expected pixel signatures.
|
||||
'use strict';
|
||||
import { createRequire } from 'node:module';
|
||||
import { readFileSync } from 'node:fs';
|
||||
const require = createRequire('/root/kidcraft/node_modules/');
|
||||
const { chromium } = require('playwright-core');
|
||||
const EXE = '/root/.cache/ms-playwright/chromium_headless_shell-1234/chrome-headless-shell-linux64/chrome-headless-shell';
|
||||
|
||||
let failures = 0;
|
||||
const ok = (c, l) => { console.log(`${c ? 'PASS' : 'FAIL'} ${l}`); if (!c) failures++; };
|
||||
|
||||
async function stats(page, path, x0, y0, x1, y1) {
|
||||
const b64 = readFileSync(path).toString('base64');
|
||||
return await page.evaluate(async ({ b64, x0, y0, x1, y1 }) => {
|
||||
const img = new Image();
|
||||
img.src = 'data:image/png;base64,' + b64;
|
||||
await new Promise(r => { img.onload = r; });
|
||||
const cv = document.createElement('canvas');
|
||||
cv.width = img.width; cv.height = img.height;
|
||||
const cx = cv.getContext('2d');
|
||||
cx.drawImage(img, 0, 0);
|
||||
const d = cx.getImageData(x0, y0, x1 - x0, y1 - y0).data;
|
||||
let blue = 0, green = 0, tan = 0, red = 0, dark = 0, n = 0;
|
||||
for (let i = 0; i < d.length; i += 4) {
|
||||
const [r, g, b] = [d[i], d[i + 1], d[i + 2]]; n++;
|
||||
if (b > r + 20 && b > g + 10) blue++;
|
||||
else if (g > r + 15 && g > b + 15) green++;
|
||||
else if (r > 150 && g > 130 && b < g) tan++;
|
||||
else if (r > 140 && r > g + 50 && r > b + 50) red++;
|
||||
if (r + g + b < 120) dark++;
|
||||
}
|
||||
return { w: img.width, h: img.height, frac: { blue: blue / n, green: green / n, tan: tan / n, red: red / n, dark: dark / n } };
|
||||
}, { b64, x0, y0, x1, y1 });
|
||||
}
|
||||
|
||||
const browser = await chromium.launch({ executablePath: EXE, args: ['--no-sandbox'] });
|
||||
const page = await browser.newPage();
|
||||
await page.goto('about:blank');
|
||||
|
||||
// ---- menu ----
|
||||
const menu = await stats(page, '/tmp/shot-menu.png', 0, 0, 1280, 800);
|
||||
ok(menu.w === 1280 && menu.h === 800, `menu screenshot size ${menu.w}x${menu.h}`);
|
||||
ok(menu.frac.dark < 0.98, 'menu is not a black screen');
|
||||
|
||||
// ---- gameplay full frame ----
|
||||
const game = await stats(page, '/tmp/shot-game-a.png', 0, 0, 1280, 800);
|
||||
ok(game.frac.blue > 0.05 || game.frac.green > 0.05, `island terrain painted (blue ${game.frac.blue.toFixed(2)}, green ${game.frac.green.toFixed(2)})`);
|
||||
// bottom-left vitals region should contain red-ish HP fill
|
||||
const vitals = await stats(page, '/tmp/shot-game-a.png', 16, 800 - 160, 256, 790);
|
||||
ok(vitals.frac.red > 0.02, `HP bar red present (${vitals.frac.red.toFixed(3)})`);
|
||||
// top-right minimap region not empty
|
||||
const mm = await stats(page, '/tmp/shot-game-a.png', 1116, 14, 1266, 178);
|
||||
ok(mm.frac.dark < 0.95 && (mm.frac.blue > 0.03 || mm.frac.green > 0.03), 'minimap rendered island');
|
||||
|
||||
await browser.close();
|
||||
console.log(failures === 0 ? '\nPIXEL CHECKS PASSED' : `\n${failures} PIXEL CHECK(S) FAILED`);
|
||||
process.exit(failures ? 1 : 0);
|
||||
Reference in New Issue
Block a user