Served with isolation headers; start-screen capability notices; tests on the real GPU (t88 phase 1)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 19:11:42 -04:00
co-authored by Claude Opus 5.5
parent 9781aa3ee1
commit b2c9ad9533
22 changed files with 376 additions and 91 deletions
+28 -15
View File
@@ -4,20 +4,19 @@
import json, sys, pathlib, re
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent / 'tools'))
from use_venv import ensure_playwright; ensure_playwright()
from playwright.sync_api import sync_playwright
from browser import launch, consume_gpu_option
consume_gpu_option()
root = pathlib.Path(__file__).resolve().parent.parent
out = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else root / 'shots' / 'play'); out.mkdir(parents=True, exist_ok=True)
url = (root / 'dist' / 'index.html').as_uri()
fails = []
def check(name, cond, extra=''):
print(('PASS ' if cond else 'FAIL ') + name + (f' [{extra}]' if extra != '' else ''))
if not cond: fails.append(name)
with sync_playwright() as p:
b = p.chromium.launch()
ctx = b.new_context(viewport={'width': 1280, 'height': 800})
pg = ctx.new_page()
with launch(viewport={'width': 1280, 'height': 800}) as (b, pg, url):
ctx = pg.context
logs = []
pg.add_init_script("if (!localStorage.getItem('deepgrain.settings')) localStorage.setItem('deepgrain.settings', JSON.stringify({look:'pixel', sv:2}));")
pg.on('pageerror', lambda e: logs.append(f'PAGEERROR: {e}'))
pg.on('console', lambda m: logs.append(f'{m.type}: {m.text}') if m.type == 'error' else None)
pg.goto(url); pg.evaluate("SET.fog = false"); pg.wait_for_timeout(300)
@@ -61,13 +60,13 @@ with sync_playwright() as p:
check('Continue loads the migrated seed and playtime', pg.evaluate('worldSeed === 12345 && GAME.playTime >= 3661 && !!GAME.worldId'))
pg.evaluate("""() => {
window.__pack = packBuffers;
packBuffers = async b => { await new Promise(r => setTimeout(r, 600)); return __pack(b); };
packBuffers = async b => { await new Promise(r => { window.__finishSave = r; }); return __pack(b); };
window.__saving = saveWorld('main', true);
}""")
pg.locator('#saving').wait_for(state='visible')
pg.screenshot(path=str(out / 'saving-progress.png'))
check('slow save shows the saving chip', pg.is_visible('#saving'))
pg.evaluate('async () => { await __saving; packBuffers = __pack; }')
pg.evaluate('async () => { __finishSave(); await __saving; packBuffers = __pack; }')
check('saving chip hides when the save ends', not pg.is_visible('#saving'))
pg.evaluate('quitToTitle()')
names = {}
@@ -248,7 +247,8 @@ with sync_playwright() as p:
led = ev(camera_offset)
check('camera leads toward the pointer when enabled', all(b - a > 2 for a, b in zip(centred, led)), led)
pg.mouse.move(*mouse)
ev(f'GAME.paused = {json.dumps(paused)}; saveSettings()')
# Keep world-to-screen targets fixed after checking both camera modes.
ev(f'GAME.paused = {json.dumps(paused)}; SET.cameraLead = false; saveSettings(); snapCamera()')
def cell_to_screen(x, y):
return ev(f'(() => {{ const dpr = R.canvas.width / innerWidth; const s = worldToScreenPx({x}+0.5, {y}+0.5); return [s.x/dpr, s.y/dpr]; }})()')
def blk(bx, by): return cell_to_screen(bx * 4 + 2, by * 4 + 2)
@@ -281,7 +281,11 @@ with sync_playwright() as p:
# 1. drill: rock breaks loose, nothing enters the bag
pg.mouse.move(*cell_to_screen(bx + 6, gy + 3)); settle(900)
sx, sy = cell_to_screen(bx + 6, gy + 3)
hold(sx, sy, 3500)
pg.mouse.move(sx, sy); pg.mouse.down()
try:
pg.wait_for_function('stats.dug > 20', timeout=15000)
finally:
pg.mouse.up()
settle(700)
check('drill digs with the mouse', ev('stats.dug') > 20, ev('stats.dug'))
check('drill does not fill the bag', ev('bagTotal') == 0, ev('bagTotal'))
@@ -307,7 +311,8 @@ with sync_playwright() as p:
ev("SET.cardSeconds = 0; $('cards').replaceChildren()") # centred cards cover the mouse work area; discovery was checked above
# 2. vacuum, left button: lock onto what you click
pg.keyboard.press('2'); settle(100)
pg.keyboard.press('2')
pg.wait_for_function("GAME.tool === 1 && $('selcard').querySelector('.name')?.textContent === 'Pneumatic nozzle'")
check('Pneumatic nozzle card title and width match the selected tool', ev("GAME.tool === 1 && $('selcard').querySelector('.name').textContent === 'Pneumatic nozzle' && $('selcard').firstElementChild.querySelector('.chip').textContent === `width ${GAME.brush[1]}` && !$('selcard').querySelector('.sub .chip')"))
check('Pneumatic nozzle name appears in the tool list and hotbar title', ev("TOOLS[1].name === 'Pneumatic nozzle' && $('hotbar').children[1].title.startsWith('Pneumatic nozzle')"))
tgt = ev(f'''(() => {{ for (let r = 0; r < 30; r++) for (let dy = -r; dy <= r; dy++) for (let dx = -r; dx <= r; dx++) {{
@@ -492,6 +497,7 @@ with sync_playwright() as p:
check('Shift lights every loose bag row', ev("blowAllList().every(m => [...$('baglist').querySelectorAll('.bagrow.on .nm')].some(e => e.textContent === MD[m].name))"))
pg.screenshot(path=str(out / '03b-blow-all-preview.png'))
pg.keyboard.up('Shift'); settle(150)
pg.wait_for_function("!$('selcard').querySelector('.vacchoice').textContent.includes('Everything') && document.querySelectorAll('#baglist .bagrow.on').length === 1")
check('releasing Shift restores the chosen blow material', ev("$('selcard').querySelector('.vacchoice').textContent.includes(MD[COAL].name) && !$('selcard').querySelector('.vacchoice').textContent.includes('Everything')"))
check('releasing Shift lights only coal', ev("(() => { const rows = [...$('baglist').querySelectorAll('.bagrow.on')]; return rows.length === 1 && rows[0].querySelector('.nm').textContent === MD[COAL].name; })()"))
bot_at = ev('[bot.x, bot.y]')
@@ -1156,6 +1162,12 @@ with sync_playwright() as p:
navigator.getGamepads = () => [window.__pad];
}''')
pg.keyboard.press('1'); settle(100)
# The preceding play-through can leave only air below the bot.
ev('''() => {
GAME.drillTune = 0;
const x0 = Math.floor(bot.x + bot.w / 2), y0 = Math.ceil(bot.y + bot.h) + 2;
for (let y = y0; y < y0 + 8; y++) for (let x = x0 - 4; x <= x0 + 4; x++) setCell(y * W + x, DIRT, 20, F_NAT);
}''')
d0 = ev('stats.dug')
ev("__pad.axes[3] = 0.9; __pad.buttons[7].pressed = true; __pad.buttons[7].value = 1;")
settle(1500)
@@ -1206,10 +1218,11 @@ with sync_playwright() as p:
pg.keyboard.press('Escape'); settle(150)
# Fog has its own paused scene; existing play checks keep the whole world visible.
fb = p.chromium.launch(args=['--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'])
fp = fb.new_page(viewport={'width': 1280, 'height': 800})
fog_context = b.new_context(viewport={'width': 1280, 'height': 800})
fp = fog_context.new_page()
if pg.evaluate('!!window.__allowSoftwareGL'):
fp.add_init_script('window.__allowSoftwareGL = true')
fp.on('pageerror', lambda e: logs.append(f'PAGEERROR(fog): {e}'))
fp.add_init_script('window.__allowSoftwareGL = true')
fp.goto(url)
fp.evaluate('startNewWorld(4242, 512, 384)')
fp.wait_for_function('GAME.running')
@@ -1318,7 +1331,7 @@ with sync_playwright() as p:
check('fog draws in the smooth look', fp.evaluate('R.smoothOn && SM.ok'))
check('unseen smooth grains have no terrain shape', fp.evaluate("(() => {const k=(235-R.vy0)*R.bw+300-R.vx0;return R.px[k]===0x04000000 && R.backPx[k]===0 && R.treePx[k]===0 && R.fieldPx[k]===255})()"))
fp.screenshot(path=str(out / 'fog-smooth.png'))
fb.close()
fog_context.close()
# 11. the largest world from the title screen
pg2 = ctx.new_page()