Files
deepseek 0e7c07533e TINY SHOP — cozy 3D shop-management game (Three.js vertical slice)
- Procedural 3D world: dollhouse shop, town, day/night, weather, seasons
- Customer AI with personalities (story NPCs, thieves, weekly regulars)
- Economy: suppliers, negotiation, pricing psychology, daily accounting
- Staff with traits/loyalty, 8 expansion levels, furniture & decoration
- Events with choices, quests, achievements, 4 difficulties, rival shop
- Animated daily report, analytics, save/load (3 slots + autosave)
- Procedural music & SFX (WebAudio), zero external assets
- Test harnesses: simtest (node), verify/check/e2e (headless browser)
2026-08-23 06:58:24 +00:00

98 lines
3.7 KiB
JavaScript

import puppeteer from 'puppeteer';
import { PNG } from 'pngjs';
import fs from 'fs';
const URL = 'http://127.0.0.1:4939';
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox', '--enable-unsafe-swiftshader', '--use-gl=angle', '--use-angle=swiftshader'],
});
const page = await browser.newPage();
await page.setViewport({ width: 1024, height: 600 });
const errors = [];
const logs = [];
page.on('console', (msg) => {
const t = `[${msg.type()}] ${msg.text()}`;
logs.push(t);
if (msg.type() === 'error') errors.push(t);
});
page.on('pageerror', (err) => errors.push('[pageerror] ' + err.message));
await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 45000 });
await new Promise(r => setTimeout(r, 4000));
// sanity: dom state
const state = await page.evaluate(() => ({
title: document.title,
hasMenu: !!document.getElementById('menu'),
hasNewBtn: !!document.getElementById('m-new'),
uiHidden: document.getElementById('ui')?.classList.contains('hidden'),
canvases: [...document.querySelectorAll('canvas')].map(c => ({ w: c.width, h: c.height, id: c.id || '(main)' })),
webgl: (() => { try { const c = document.createElement('canvas'); return !!c.getContext('webgl2'); } catch { return false; } })(),
}));
console.log('STATE:', JSON.stringify(state, null, 1));
async function analyze(file, label) {
const buf = fs.readFileSync(file);
const png = PNG.sync.read(buf);
const { width: w, height: h, data } = png;
const region = (x0, y0, x1, y1) => {
let r = 0, g = 0, b = 0, n = 0;
for (let y = y0; y < y1; y += 4) for (let x = x0; x < x1; x += 4) {
const i = (y * w + x) * 4;
r += data[i]; g += data[i + 1]; b += data[i + 2]; n++;
}
return [Math.round(r / n), Math.round(g / n), Math.round(b / n)];
};
const px = (x, y) => { const i = (y * w + x) * 4; return [data[i], data[i + 1], data[i + 2]]; };
// variance sample across whole frame
let sum = 0, sum2 = 0, n = 0;
for (let y = 0; y < h; y += 6) for (let x = 0; x < w; x += 6) {
const [r, g, b] = px(x, y);
const l = (r + g + b) / 3;
sum += l; sum2 += l * l; n++;
}
const mean = sum / n;
const std = Math.sqrt(sum2 / n - mean * mean);
console.log(`${label}: mean=${mean.toFixed(1)} std=${std.toFixed(1)} top=${region(0, 0, w, 60)} center=${region(w / 3, h / 3, 2 * w / 3, 2 * h / 3)} bottom=${region(0, h - 80, w, h)}`);
}
await page.screenshot({ path: '/tmp/shot_menu.png' });
await analyze('/tmp/shot_menu.png', 'MENU ');
// proceed into game
try {
await page.evaluate(() => document.getElementById('m-new').click());
await new Promise(r => setTimeout(r, 500));
await page.evaluate(() => {
const inp = document.querySelector('#wiz-name');
if (inp) inp.value = 'The Gilded Carrot';
document.querySelector('#wiz-start')?.click();
});
await new Promise(r => setTimeout(r, 2500));
await page.screenshot({ path: '/tmp/shot_game.png' });
await analyze('/tmp/shot_game.png', 'GAME ');
await page.evaluate(() => window.__TS.G.timeScale = 3);
await new Promise(r => setTimeout(r, 6000));
await page.screenshot({ path: '/tmp/shot_busy.png' });
await analyze('/tmp/shot_busy.png', 'BUSY ');
const gameInfo = await page.evaluate(() => ({
day: window.__TS.G.shop.day,
clock: Math.round(window.__TS.G.shop.minutes),
customers: window.__TS.G.customers.length,
gold: Math.round(window.__TS.G.gold),
phase: window.__TS.G.shop.phase,
isOpen: window.__TS.G.shop.isOpen,
}));
console.log('SIM:', JSON.stringify(gameInfo));
} catch (e) {
console.log('FLOW ERROR:', e.message);
}
console.log('ERRORS(' + errors.length + '):');
errors.slice(0, 12).forEach(e => console.log(' ' + e.slice(0, 300)));
await browser.close();