← back to Dead Agentabrams
snapshot before orb UFO/celestial + roaming-animal build
0feb28b9498158a34a86cafb50e6825e21404ffc · 2026-09-02 08:05:59 -0700 · Steve Abrams
Files touched
A scripts/screenrecord-5pass.js
Diff
commit 0feb28b9498158a34a86cafb50e6825e21404ffc
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 2 08:05:59 2026 -0700
snapshot before orb UFO/celestial + roaming-animal build
---
scripts/screenrecord-5pass.js | 554 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 554 insertions(+)
diff --git a/scripts/screenrecord-5pass.js b/scripts/screenrecord-5pass.js
new file mode 100644
index 0000000..af8493a
--- /dev/null
+++ b/scripts/screenrecord-5pass.js
@@ -0,0 +1,554 @@
+'use strict';
+// 5-pass screen recording + click-every-element debug agent
+// dead.agentabrams.com/room/ — Grateful Dead music room
+// Skill: screenrecord/SKILL.md
+// Usage: NODE_PATH=$HOME/.npm-global/lib/node_modules node scripts/screenrecord-5pass.js
+
+const { chromium } = require('playwright');
+const fs = require('fs');
+const path = require('path');
+
+const URL = 'https://dead.agentabrams.com/room/';
+const CHROME_EXE = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
+const LOG = '/Users/macstudio3/Projects/dead-agentabrams/screenrecord/debug-log.jsonl';
+const REC_BASE = '/Users/macstudio3/Projects/dead-agentabrams/screenrecord/rec';
+
+// Ensure dirs exist
+fs.mkdirSync(path.dirname(LOG), { recursive: true });
+for (let r = 0; r < 5; r++) fs.mkdirSync(path.join(REC_BASE, `run${r}`), { recursive: true });
+
+// Load prior log for run4 "errors-first" strategy
+const prior = fs.existsSync(LOG)
+ ? fs.readFileSync(LOG, 'utf8').trim().split('\n').filter(Boolean).map(l => {
+ try { return JSON.parse(l); } catch { return null; }
+ }).filter(Boolean)
+ : [];
+const priorErrors = new Set(prior.filter(r => r.errors && r.errors.length > 0).map(r => r.selector));
+
+console.log(`Prior log entries: ${prior.length}, prior errored selectors: ${priorErrors.size}`);
+
+// Append one JSONL line
+function append(obj) {
+ fs.appendFileSync(LOG, JSON.stringify(obj) + '\n');
+}
+
+// Deterministic seeded shuffle (run3)
+function seededShuffle(arr, seed) {
+ const s = [...arr];
+ let state = seed;
+ for (let i = s.length - 1; i > 0; i--) {
+ state = (state * 1664525 + 1013904223) & 0xffffffff;
+ const j = Math.abs(state) % (i + 1);
+ [s[i], s[j]] = [s[j], s[i]];
+ }
+ return s;
+}
+
+// Build click order for each run
+function orderFor(run, els) {
+ const byType = t => t === 'range';
+ switch(run) {
+ case 0: return [...els]; // DOM order
+ case 1: return [...els].reverse(); // reverse DOM
+ case 2: return [...els].sort((a, b) => byType(b.type) - byType(a.type)); // sliders first
+ case 3: return seededShuffle(els, 42 * 13 + run * 7); // seeded shuffle
+ case 4: {
+ // errors-first: put previously errored selectors at front
+ const bad = sel => priorErrors.has(sel) ? 0 : 1;
+ return [...els].sort((a, b) => bad(a.selector) - bad(b.selector));
+ }
+ }
+}
+
+// Measure how long archive.org fetch takes (31 parallel count queries)
+async function measureBarHeightTiming(page) {
+ return await page.evaluate(() => {
+ return new Promise(resolve => {
+ const bars = Array.from(document.querySelectorAll('.mr-yr .bar, .bar'));
+ if (!bars.length) { resolve({ barCount: 0, hasRealHeights: false }); return; }
+ // Check if bars have data-loaded or non-zero height from parent
+ const parentHeights = Array.from(document.querySelectorAll('.mr-yr')).map(b => b.style.height || window.getComputedStyle(b).height);
+ resolve({ barCount: bars.length, parentHeights: parentHeights.slice(0, 5) });
+ });
+ });
+}
+
+async function runPass(runIdx) {
+ console.log(`\n===== RUN ${runIdx} =====`);
+ const recDir = path.join(REC_BASE, `run${runIdx}`);
+
+ const browser = await chromium.launch({
+ executablePath: CHROME_EXE,
+ headless: true,
+ args: ['--no-sandbox', '--disable-web-security']
+ });
+
+ const ctx = await browser.newContext({
+ viewport: { width: 1600, height: 900 },
+ recordVideo: { dir: recDir, size: { width: 1600, height: 900 } },
+ userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
+ });
+
+ const page = await ctx.newPage();
+ const consoleErrors = [];
+ const pageErrors = [];
+ page.on('console', m => {
+ if (m.type() === 'error') consoleErrors.push(m.text().slice(0, 300));
+ });
+ page.on('pageerror', e => pageErrors.push(('' + e).slice(0, 300)));
+
+ // Track network requests to archive.org
+ const archiveRequests = [];
+ const archiveErrors = [];
+ page.on('requestfinished', async req => {
+ if (req.url().includes('archive.org')) {
+ try {
+ const resp = await req.response();
+ archiveRequests.push({ url: req.url().slice(0, 100), status: resp ? resp.status() : null });
+ } catch {
+ archiveRequests.push({ url: req.url().slice(0, 100), status: 'err' });
+ }
+ }
+ });
+ page.on('requestfailed', req => {
+ if (req.url().includes('archive.org')) archiveErrors.push({ url: req.url().slice(0, 100), err: req.failure()?.errorText });
+ });
+
+ const loadStart = Date.now();
+ await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 30000 });
+ const domReadyMs = Date.now() - loadStart;
+
+ // Wait for initial JS execution
+ await page.waitForTimeout(3000);
+ const postInitMs = Date.now() - loadStart;
+
+ // Check bars immediately after load (before archive.org fills heights)
+ const barsAtLoad = await page.evaluate(() => {
+ const bars = Array.from(document.querySelectorAll('.mr-yr'));
+ return bars.map(b => ({ style: b.style.height || 'none', computed: window.getComputedStyle(b).height })).slice(0, 3);
+ });
+
+ append({
+ run: runIdx, ts: new Date().toISOString(), selector: 'PAGE_LOAD', label: 'initial load',
+ action: 'navigate', ok: true,
+ effect: `domReady=${domReadyMs}ms postInit=${postInitMs}ms barsAtLoad=${JSON.stringify(barsAtLoad)}`,
+ errors: [...consoleErrors.splice(0), ...pageErrors.splice(0)]
+ });
+
+ // ---- Step 1: Click hamburger to reveal panel stack ----
+ const beforeHamburger = await page.screenshot({ clip: { x: 1400, y: 0, width: 200, height: 80 } });
+ let hamburgerOk = false;
+ try {
+ await page.click('#chromeBtn', { timeout: 5000 });
+ await page.waitForTimeout(800);
+ hamburgerOk = true;
+ } catch (e) {
+ consoleErrors.push('hamburger click failed: ' + e.message.slice(0, 100));
+ }
+ const afterHamburger = await page.screenshot({ clip: { x: 0, y: 0, width: 500, height: 400 } });
+ append({
+ run: runIdx, ts: new Date().toISOString(), selector: '#chromeBtn', label: 'hamburger ☰ Controls',
+ action: 'click', ok: hamburgerOk,
+ effect: `panel_revealed=${hamburgerOk}`,
+ errors: [...consoleErrors.splice(0), ...pageErrors.splice(0)]
+ });
+
+ // ---- Discover all interactive elements (now panel is open) ----
+ const elements = await page.evaluate(() => {
+ const els = [];
+ const seen = new Set();
+
+ // Priority elements: hamburger, MR panel controls, year bars, show controls
+ const selectors = [
+ { sel: '#chromeBtn', label: 'hamburger toggle' },
+ { sel: '#mrToggle', label: 'Music Room collapse toggle' },
+ { sel: '#mrSearch', label: 'search box' },
+ { sel: '#mrAll', label: 'All years button' },
+ { sel: '.mr-yr:nth-child(1)', label: 'year bar 1965' },
+ { sel: '.mr-yr:nth-child(6)', label: 'year bar 1970' },
+ { sel: '.mr-yr:nth-child(11)', label: 'year bar 1975' },
+ { sel: '.mr-yr:nth-child(16)', label: 'year bar 1980' },
+ { sel: '.mr-yr:nth-child(21)', label: 'year bar 1985' },
+ { sel: '.mr-yr:nth-child(26)', label: 'year bar 1990' },
+ { sel: '.mr-yr:nth-child(31)', label: 'year bar 1995' },
+ { sel: '#mrShow', label: 'show select dropdown' },
+ { sel: '#mrPrev', label: 'prev show ⏮' },
+ { sel: '#mrPlay', label: 'play button ▶' },
+ { sel: '#mrNext', label: 'next show ⏭' },
+ { sel: '#mrTracksToggle', label: 'Tracks toggle' },
+ { sel: '#mrSeek', label: 'seek slider' },
+ { sel: '#playBtn', label: 'scene pause/play' },
+ { sel: '#restartBtn', label: 'restart scene' },
+ { sel: '#reduceBtn', label: 'reduced motion' },
+ { sel: '#trailBtn', label: 'motion trails' },
+ { sel: '#reactBtn', label: 'react to music' },
+ { sel: '#pteroBtn', label: 'fly over dino' },
+ { sel: '#venueBtn', label: 'venue button' },
+ { sel: '#tempo', label: 'tempo slider' },
+ { sel: '#lantern', label: 'lantern slider' },
+ { sel: '#crowd', label: 'crowd slider' },
+ { sel: '#animals', label: 'animals slider' },
+ { sel: '#bears', label: 'bears slider' },
+ { sel: '#labelBtn', label: 'museum label toggle' },
+ { sel: '#backlink', label: 'back to main stage' },
+ { sel: '[id=""][class=""][text="I · Departure"]', label: 'chapter I departure' },
+ ];
+
+ // Also enumerate all remaining buttons dynamically
+ document.querySelectorAll('button, [role=button], header[id]').forEach(el => {
+ const id = el.id ? `#${el.id}` : null;
+ const cls = el.className ? `.${el.className.trim().split(/\s+/).join('.')}` : null;
+ const key = id || cls || el.tagName;
+ if (!seen.has(key)) {
+ seen.add(key);
+ const rect = el.getBoundingClientRect();
+ els.push({
+ selector: id || (cls ? (el.tagName.toLowerCase() + cls) : el.tagName.toLowerCase()),
+ label: el.textContent?.trim().slice(0, 50) || id || 'unlabeled',
+ type: el.tagName.toLowerCase() === 'input' ? el.type : 'button',
+ rect: { x: Math.round(rect.x), y: Math.round(rect.y), w: Math.round(rect.width), h: Math.round(rect.height) },
+ visible: rect.width > 0 && rect.height > 0
+ });
+ }
+ });
+
+ // Inputs
+ document.querySelectorAll('input, select').forEach(el => {
+ const id = el.id ? `#${el.id}` : null;
+ if (id && !seen.has(id)) {
+ seen.add(id);
+ const rect = el.getBoundingClientRect();
+ els.push({
+ selector: id,
+ label: el.id || 'input',
+ type: el.type || 'select',
+ rect: { x: Math.round(rect.x), y: Math.round(rect.y), w: Math.round(rect.width), h: Math.round(rect.height) },
+ visible: rect.width > 0 && rect.height > 0
+ });
+ }
+ });
+
+ return els;
+ });
+
+ const visibleEls = elements.filter(e => e.visible);
+ console.log(` Run ${runIdx}: ${visibleEls.length} visible elements, ${elements.length} total`);
+
+ // Apply run-specific ordering
+ const ordered = orderFor(runIdx, visibleEls);
+
+ // ---- Iterate and click/interact every element ----
+ for (const el of ordered) {
+ const errsBefore = [...consoleErrors];
+ const pageErrsBefore = [...pageErrors];
+
+ // Capture before-state for key elements
+ let beforeState = '';
+ try {
+ if (el.selector === '#mrAll') {
+ beforeState = await page.evaluate(() => {
+ const btn = document.querySelector('#mrAll');
+ return btn ? `classList=${btn.className} text=${btn.textContent?.trim()}` : 'not found';
+ });
+ } else if (el.selector && el.selector.includes('mr-yr')) {
+ beforeState = await page.evaluate(sel => {
+ const btn = document.querySelector(sel);
+ return btn ? `classList=${btn.className} aria=${btn.getAttribute('aria-pressed')||'none'}` : 'not found';
+ }, el.selector);
+ }
+ } catch {}
+
+ let ok = false;
+ let effect = '';
+ let action = 'click';
+ let errMsg = '';
+
+ try {
+ if (el.type === 'range') {
+ // Sliders: drag to middle, then to each extreme
+ action = 'drag';
+ const sliderEl = await page.$(el.selector);
+ if (sliderEl) {
+ await sliderEl.scrollIntoViewIfNeeded();
+ const box = await sliderEl.boundingBox();
+ if (box && box.width > 0) {
+ // drag to 50%
+ await page.mouse.move(box.x + box.width * 0.5, box.y + box.height * 0.5);
+ await page.mouse.click(box.x + box.width * 0.5, box.y + box.height * 0.5);
+ await page.waitForTimeout(200);
+ // drag to max
+ await page.mouse.move(box.x + box.width * 0.9, box.y + box.height * 0.5);
+ await page.mouse.click(box.x + box.width * 0.9, box.y + box.height * 0.5);
+ await page.waitForTimeout(200);
+ ok = true;
+ effect = `slider dragged box=${JSON.stringify(box)}`;
+ } else {
+ effect = `slider not visible/zero-width box=${JSON.stringify(box)}`;
+ }
+ }
+ } else if (el.selector === '#mrSearch') {
+ action = 'type';
+ const input = await page.$('#mrSearch');
+ if (input) {
+ await input.scrollIntoViewIfNeeded();
+ await input.click();
+ await input.fill('1972');
+ await page.waitForTimeout(500);
+ const results = await page.evaluate(() => {
+ const list = document.querySelectorAll('#mrList option, .mr-list option, .mr-result');
+ return list.length;
+ });
+ effect = `typed 1972, results visible: ${results}`;
+ await input.fill(''); // clear
+ ok = true;
+ }
+ } else if (el.selector === '#mrShow') {
+ action = 'select';
+ const select = await page.$('#mrShow');
+ if (select) {
+ await select.scrollIntoViewIfNeeded();
+ const opts = await page.evaluate(() => {
+ const s = document.querySelector('#mrShow');
+ return s ? Array.from(s.options).length : 0;
+ });
+ effect = `select has ${opts} options`;
+ // Pick option at index 2 if it exists
+ if (opts > 2) {
+ await page.selectOption('#mrShow', { index: 2 });
+ await page.waitForTimeout(500);
+ }
+ ok = true;
+ }
+ } else if (['#backlink'].includes(el.selector)) {
+ // Don't navigate away — just note it
+ action = 'skip-navigate';
+ ok = true;
+ effect = 'navigation link — skipped to avoid leaving page';
+ } else {
+ // Standard click
+ const elHandle = await page.$(el.selector);
+ if (elHandle) {
+ await elHandle.scrollIntoViewIfNeeded();
+ const box = await elHandle.boundingBox();
+ if (box) {
+ effect = `box=${Math.round(box.width)}x${Math.round(box.height)} at (${Math.round(box.x)},${Math.round(box.y)})`;
+ }
+ await elHandle.click({ timeout: 3000 });
+ await page.waitForTimeout(400);
+ ok = true;
+ } else {
+ effect = 'element not found in DOM at click time';
+ }
+ }
+ } catch (e) {
+ errMsg = e.message.slice(0, 200);
+ effect = `ERROR: ${errMsg}`;
+ }
+
+ // Capture after-state
+ let afterState = '';
+ try {
+ if (el.selector === '#mrAll') {
+ afterState = await page.evaluate(() => {
+ const btn = document.querySelector('#mrAll');
+ const yr = document.querySelector('#mrYear');
+ return btn ? `classList=${btn.className} yearInput=${yr?.value}` : 'not found';
+ });
+ } else if (el.type === 'range') {
+ afterState = await page.evaluate(sel => {
+ const inp = document.querySelector(sel);
+ return inp ? `value=${inp.value}` : 'not found';
+ }, el.selector);
+ } else if (el.selector === '#mrPlay' || el.selector === '#playBtn') {
+ afterState = await page.evaluate(() => {
+ const mp = document.querySelector('#mrPlay');
+ return mp ? `mrPlay text=${mp.textContent?.trim()}` : '';
+ });
+ }
+ } catch {}
+
+ const newErrs = consoleErrors.slice(errsBefore.length);
+ const newPageErrs = pageErrors.slice(pageErrsBefore.length);
+ const allNewErrs = [...newErrs, ...newPageErrs];
+ if (errMsg) allNewErrs.push(errMsg);
+
+ const logEntry = {
+ run: runIdx,
+ ts: new Date().toISOString(),
+ selector: el.selector,
+ label: el.label,
+ action,
+ ok,
+ effect: `${effect}${afterState ? ` | after:${afterState}` : ''}${beforeState ? ` | before:${beforeState}` : ''}`,
+ errors: allNewErrs
+ };
+
+ if (allNewErrs.length) {
+ console.log(` [RUN${runIdx}] ERROR ${el.label}: ${allNewErrs[0]}`);
+ } else {
+ console.log(` [RUN${runIdx}] OK ${el.label}: ${effect.slice(0, 80)}`);
+ }
+
+ append(logEntry);
+
+ // After clicking mrAll or a year bar, capture quick state snapshot
+ if (el.selector === '#mrAll' || (el.selector && el.selector.includes('mr-yr'))) {
+ try {
+ const yrState = await page.evaluate(() => {
+ const allBtn = document.querySelector('#mrAll');
+ const yearBars = Array.from(document.querySelectorAll('.mr-yr'));
+ const active = yearBars.filter(b => b.classList.contains('active') || b.getAttribute('aria-pressed') === 'true');
+ const resultList = document.querySelector('#mrList, .mr-results, [id*=List]');
+ return {
+ allBtnClasses: allBtn?.className,
+ activeBarCount: active.length,
+ activeBarTexts: active.map(b => b.textContent?.trim()).slice(0, 3),
+ resultListCount: resultList ? resultList.querySelectorAll('option, li, .mr-result').length : 'N/A'
+ };
+ });
+ append({
+ run: runIdx, ts: new Date().toISOString(),
+ selector: el.selector + ':state-snapshot', label: 'year map state after click',
+ action: 'snapshot', ok: true,
+ effect: JSON.stringify(yrState), errors: []
+ });
+ } catch {}
+ }
+ }
+
+ // ---- Post-pass: check for bar height timing ----
+ await page.waitForTimeout(5000); // wait extra for archive.org responses
+ const archiveStatus = await page.evaluate(() => {
+ const bars = Array.from(document.querySelectorAll('.mr-yr'));
+ const heights = bars.map(b => b.style.height || window.getComputedStyle(b).height);
+ const nonZero = heights.filter(h => h && h !== '0px' && h !== 'none');
+ const barSpans = Array.from(document.querySelectorAll('.mr-yr .bar, .bar'));
+ const spanHeights = barSpans.map(s => window.getComputedStyle(s).height);
+ return {
+ barCount: bars.length,
+ nonZeroHeightCount: nonZero.length,
+ sampleHeights: heights.slice(0, 5),
+ barSpanCount: barSpans.length,
+ sampleSpanHeights: spanHeights.slice(0, 5)
+ };
+ });
+
+ append({
+ run: runIdx, ts: new Date().toISOString(),
+ selector: 'YEAR_BARS', label: 'bar height timing check (5s after load)',
+ action: 'measure', ok: true,
+ effect: JSON.stringify(archiveStatus),
+ errors: [...archiveErrors.map(r => `archive.org FAIL: ${r.url} ${r.err}`)]
+ });
+
+ // ---- Check for horizontal overflow (all 31 bars visible?) ----
+ const overflowCheck = await page.evaluate(() => {
+ const container = document.querySelector('.mr-map, .year-map, [class*=map], [id*=map], .mr-yr')?.parentElement;
+ if (!container) return { found: false };
+ const cRect = container.getBoundingClientRect();
+ const bars = Array.from(container.querySelectorAll('.mr-yr'));
+ const lastBar = bars[bars.length - 1];
+ const lastRect = lastBar ? lastBar.getBoundingClientRect() : null;
+ return {
+ found: true,
+ containerW: Math.round(cRect.width),
+ containerScrollW: container.scrollWidth,
+ containerOverflows: container.scrollWidth > cRect.width,
+ barCount: bars.length,
+ firstBarX: bars[0] ? Math.round(bars[0].getBoundingClientRect().x) : null,
+ lastBarX: lastRect ? Math.round(lastRect.x + lastRect.width) : null,
+ lastBarVisible: lastRect ? lastRect.right <= cRect.right + 2 : null
+ };
+ });
+
+ append({
+ run: runIdx, ts: new Date().toISOString(),
+ selector: 'YEAR_MAP_CONTAINER', label: 'horizontal overflow check',
+ action: 'measure', ok: true,
+ effect: JSON.stringify(overflowCheck),
+ errors: []
+ });
+
+ // ---- Check "All years" reset after year selection ----
+ const resetCheck = await page.evaluate(() => {
+ const allBtn = document.querySelector('#mrAll');
+ const yearInput = document.querySelector('#mrYear');
+ return {
+ allBtnClasses: allBtn?.className,
+ yearInputVal: yearInput?.value,
+ allBtnText: allBtn?.textContent?.trim()
+ };
+ });
+
+ append({
+ run: runIdx, ts: new Date().toISOString(),
+ selector: 'RESET_STATE', label: 'all-years reset state check end of run',
+ action: 'measure', ok: true,
+ effect: JSON.stringify(resetCheck),
+ errors: []
+ });
+
+ // ---- Final screenshot for visual record ----
+ const screenshotPath = path.join(recDir, 'final-state.png');
+ await page.screenshot({ path: screenshotPath, fullPage: false });
+
+ // Close and let video finalize
+ await ctx.close();
+ await browser.close();
+
+ // Convert webm to mp4 if ffmpeg available
+ const recFiles = fs.readdirSync(recDir).filter(f => f.endsWith('.webm'));
+ for (const webm of recFiles) {
+ const webmPath = path.join(recDir, webm);
+ const mp4Path = webmPath.replace('.webm', '.mp4');
+ try {
+ const { execSync } = require('child_process');
+ execSync(`ffmpeg -y -i "${webmPath}" -c:v libx264 -preset fast -crf 28 "${mp4Path}" 2>/dev/null`, { timeout: 60000 });
+ console.log(` Converted: ${mp4Path}`);
+ } catch (e) {
+ console.log(` ffmpeg convert failed for ${webm}: ${e.message.slice(0, 100)}`);
+ }
+ }
+
+ // Summary
+ const runEntries = prior.filter(e => e.run === runIdx);
+ const runErrors = runEntries.filter(e => e.errors && e.errors.length > 0);
+ console.log(` Run ${runIdx} complete. Archive requests: ${archiveRequests.length}, errors: ${archiveErrors.length}`);
+ console.log(` Bar height status: ${JSON.stringify(archiveStatus)}`);
+
+ return {
+ runIdx,
+ archiveRequests: archiveRequests.length,
+ archiveErrors: archiveErrors.length,
+ archiveStatus,
+ overflowCheck,
+ resetCheck,
+ screenshotPath,
+ recDir,
+ recFiles: fs.readdirSync(recDir)
+ };
+}
+
+// Main
+(async () => {
+ const results = [];
+ for (let r = 0; r < 5; r++) {
+ const res = await runPass(r);
+ results.push(res);
+ // Brief pause between runs
+ if (r < 4) await new Promise(resolve => setTimeout(resolve, 2000));
+ }
+
+ console.log('\n===== ALL RUNS COMPLETE =====');
+ for (const res of results) {
+ console.log(`Run ${res.runIdx}: archive=${res.archiveRequests} reqs, ${res.archiveErrors} errs, overflow=${JSON.stringify(res.overflowCheck)}`);
+ }
+
+ // Output results as JSON for report generation
+ fs.writeFileSync('/Users/macstudio3/Projects/dead-agentabrams/screenrecord/run-results.json', JSON.stringify(results, null, 2));
+ console.log('\nRun results saved to screenrecord/run-results.json');
+ console.log('Log saved to ' + LOG);
+})().catch(e => {
+ console.error('FATAL:', e.message);
+ process.exit(1);
+});
← 0c6d91e room: interactive touring-history year map for archive brows
·
back to Dead Agentabrams
·
orb: song-change UFO split (5 saucers + light beams), celest 4ea4914 →