"""Dev console confirmation, keyboard isolation, HUD, time controls and saved badge.""" import pathlib, sys sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent / 'tools')) from use_venv import ensure_playwright; ensure_playwright() from browser import launch, consume_gpu_option consume_gpu_option() ROOT = pathlib.Path(__file__).resolve().parent.parent SHOTS = ROOT / 'shots' SHOTS.mkdir(exist_ok=True) fails = [] def check(name, ok): print(('PASS ' if ok else 'FAIL ') + name) if not ok: fails.append(name) with launch() as (browser, page, url): errors = [] page.on('pageerror', lambda e: errors.append(str(e))) page.add_init_script("localStorage.setItem('deepgrain.settings', JSON.stringify({look:'pixel', sv:2}));") page.goto(url) page.wait_for_function("!$('title').hidden") page.evaluate("startNewWorld(424242, 512, 384, 'Console test')") page.wait_for_function('GAME.running && !GAME.loading') page.evaluate("document.querySelectorAll('.card, .toast').forEach(e => e.remove())") page.keyboard.press('`') check('backtick opens the first-use warning', page.is_visible('#m-dialog') and not page.is_visible('#dev-console')) page.screenshot(path=str(SHOTS / 't66-warning.png')) check('warning explains permanent marking and future achievements', 'permanently' in page.inner_text('#dlg-body') and 'achievements' in page.inner_text('#dlg-body')) page.get_by_role('button', name='Cancel', exact=True).click() check('cancel leaves the world unmarked and console closed', page.evaluate("!devWorld && $('dev-console').hidden && !GAME.paused")) page.keyboard.press('`') page.get_by_role('button', name='Enable dev console', exact=True).click() page.wait_for_function("!$('dev-console').hidden && document.activeElement === $('dev-input')") check('accepting marks the world and resumes play', page.evaluate('devWorld && !GAME.paused')) check('build material chip stays stable across HUD refreshes', page.evaluate("""() => { selectTool(2); renderSelCard(); const chip = $('selcard').querySelector('[data-wall-picker]'); renderSelCard(); const stable = chip === $('selcard').querySelector('[data-wall-picker]'); selectTool(0); return stable; }""")) old_money = page.evaluate('money') page.fill('#dev-input', 'money 1000'); page.keyboard.press('Enter') page.wait_for_function(f"money === {old_money + 1000} && $('wallet').textContent.includes(fmtMoney(money))") check('money command changes the wallet HUD', True) tool = page.evaluate('GAME.tool') page.fill('#dev-input', ''); page.keyboard.type('1234wasd') check('typing game keys does not switch tools or hold movement', page.evaluate(f'GAME.tool === {tool} && keys.size === 0')) page.keyboard.press('Escape') check('Escape closes console without opening pause menu', page.evaluate("$('dev-console').hidden && $('m-pause').hidden && !GAME.paused")) page.keyboard.press('`') check('marked worlds reopen without the warning', page.evaluate("!$('dev-console').hidden && $('m-dialog').hidden")) page.keyboard.press('`') check('backtick closes even with input focused', not page.is_visible('#dev-console')) page.keyboard.press('`') def command(text): page.fill('#dev-input', text); page.keyboard.press('Enter') command('pause') tick = page.evaluate('tick') page.wait_for_timeout(150) check('dev pause freezes simulation ticks', page.evaluate('tick') == tick) command('step 5') page.wait_for_function(f'tick === {tick + 5} && devSteps === 0') page.wait_for_timeout(150) check('step advances exactly five ticks then stays paused', page.evaluate('tick') == tick + 5) # Drive the actual frame accumulator with controlled elapsed time and no scheduled frames. counts = page.evaluate('''() => { requestAnimationFrame = () => 0; GAME.paused = false; devPaused = false; SET.fpsCap = 0; SET.maxSteps = 3; const counts = []; for (const speed of [0.5, 1, 2, 8]) { devSpeed = speed; acc = 0; const before = tick; const t = performance.now(); lastT = t - 34; lastRender = t - 34; frame(t); counts.push(tick - before); } return counts; }''') check('speed scales fixed ticks and respects maxSteps', counts == [1, 2, 3, 3]) page.evaluate('devCommand("debt clear"); runDevCommand("money 1"); openNotebook("Claims")') page.wait_for_function("$('nb-body').textContent.includes('paid off')") check('cleared debt is reflected in Claims', True) page.evaluate('closeAllModals(); runDevCommand("debt 123"); openNotebook("Market")') check('new debt is reflected in the Market', '$123' in page.inner_text('#nb-body')) page.evaluate('closeAllModals()') page.evaluate("async () => { await saveWorld('main', true); await renderWorlds(); }") page.evaluate("openModal('m-worlds')") check('world list entry stores and draws the dev badge', page.locator('#worlds-list .dev-badge').is_visible() and page.inner_text('#worlds-list .dev-badge') == 'Dev console') page.screenshot(path=str(SHOTS / 't66-worlds.png')) page.evaluate('closeAllModals()') check('saving and loading preserves mark and log, resets runtime controls', page.evaluate('''async () => { const sv = await worldNewest(GAME.worldId); const count = devLog.length; await startFromSave(sv); return devWorld && devLog.length === count && devSpeed === 1 && !devPaused && !devFree && $('dev-console').hidden; }''')) check('no browser errors', not errors) browser.close() sys.exit(1 if fails else 0)