[object Object]

← back to Dead Agentabrams

snapshot before orb UFO/celestial + roaming-animal build

0feb28b9498158a34a86cafb50e6825e21404ffc · 2026-09-02 08:05:59 -0700 · Steve Abrams

Files touched

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 →