Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

Study: Artery Boot Detection & Readiness Telemetry#

Date: 2026-02-21 Context: The test-line.mjs brush test runs before AC finishes booting. The notepat tests use waitForBoot() but it may not work correctly. This study maps the full boot signal graph to build reliable piece-readiness detection.


The Problem#

When an artery test calls client.jump('line'), the browser navigates to a new URL. The entire AC runtime re-boots from scratch — boot.mjs loads bios.mjs loads the piece. Tests that send pointer events before this completes get silently swallowed.

Current tests handle this two ways:

  1. Hardcoded sleep()test-line.mjs uses sleep(3000) after each jump
  2. waitForBoot() — polls window.currentPiece which is broken (see below)

Neither is reliable.


The Boot Signal Graph#

Here's the complete timeline of what happens when AC loads a piece, with every observable window.* signal annotated:

 NAVIGATION (client.jump sets window.location.href)
 │
 ├── boot.mjs starts
 │   ├── window.acBOOT_START_TIME = performance.now()
 │   ├── window._bootTimings = []
 │   ├── window.acSTARTING_PIECE = "prompt" (or from URL)
 │   ├── window.acHIDE_BOOT_LOG = hideBootLog    ← [SIGNAL A] boot.mjs loaded
 │   ├── window.acBOOT_LOG = bootLog
 │   ├── window.acAuthTiming = { bootStart, ... }
 │   │
 │   └── bios.mjs boot() begins
 │       ├── let currentPiece = null               ← LOCAL variable, NOT on window
 │       ├── window.acDISABLE_HUD_LABEL_CACHE = true
 │       ├── window.acDISK_SEND = function(...)
 │       ├── window.acPieceState = null
 │       ├── window.acGetState = () => (...)
 │       │
 │       ├── Disk worker/module loads
 │       │   └── window.acSEND = send              ← [SIGNAL B] worker connected
 │       │
 │       ├── Piece module loads
 │       │   └── currentPiece = content.path        ← LOCAL, invisible to tests
 │       │
 │       ├── "disk-loaded-and-booted" message
 │       │   ├── window.preloaded = true            ← [SIGNAL C] piece loaded
 │       │   ├── postMessage({ type: "ready" })
 │       │   └── setTimeout(500ms) → acHIDE_BOOT_LOG()
 │       │
 │       └── "piece-paint-ready" message
 │           └── window.acHIDE_BOOT_LOG()           ← [SIGNAL D] first paint done
 │
 FOR NOPAINT/BRUSH PIECES:
 │
 ├── Piece boot() calls nopaint_boot()
 │   ├── system.painting must exist
 │   ├── system.nopaint.buffer created
 │   └── system.nopaint initialized                ← [SIGNAL E] brush ready
 │
 FOR AUDIO PIECES:
 │
 └── User interaction triggers activateSound
     └── window.audioWorkletReady = true            ← [SIGNAL F] audio ready

The waitForBoot() Bug#

File: artery/artery.mjs:427-461

async waitForBoot(timeoutMs = 15000) {
  while (Date.now() - startTime < timeoutMs) {
    const bootStatus = await this.eval(`({
      hasBios: typeof window.currentPiece !== 'undefined',  // ← WRONG
      currentPiece: window.currentPiece || null,            // ← ALWAYS null
      ...
    })`);
    if (bootStatus.currentPiece) {  // ← NEVER true
      return true;
    }
    await new Promise(r => setTimeout(r, 100));
  }
  darkLog('⚠️  Boot timeout - proceeding anyway');  // ← ALWAYS hits this
  return false;
}

Why it's broken: currentPiece is declared as let currentPiece = null inside the boot() function closure in bios.mjs:804. It's never assigned to window.currentPiece. The check typeof window.currentPiece !== 'undefined' evaluates to false (it IS undefined), and bootStatus.currentPiece is always null.

Result: waitForBoot() always times out after 15 seconds, then proceeds anyway. Tests that call it simply wait 15 seconds for nothing.


Reliable Readiness Signals#

Signal A: window.acHIDE_BOOT_LOG (boot.mjs loaded)#

  • When: Very early — boot.mjs top-level code, before bios even starts
  • Checks: typeof window.acHIDE_BOOT_LOG === 'function'
  • Useful for: Confirming the page loaded at all
  • Not sufficient for: Piece readiness

Signal B: window.acSEND (disk worker connected)#

  • When: After bios creates the disk worker/module and connects
  • Checks: typeof window.acSEND === 'function'
  • Useful for: Confirming the runtime is operational
  • Not sufficient for: Knowing WHICH piece loaded

Signal C: window.preloaded (piece loaded and booted)#

  • When: After "disk-loaded-and-booted" message from the disk
  • Checks: window.preloaded === true
  • Useful for: Confirming the current piece finished its boot() function
  • Caveat: Reset to false on every piece navigation (bios.mjs:12767)

Signal D: Boot canvas hidden (first paint)#

  • When: After "piece-paint-ready" or 500ms fallback after disk-loaded
  • Checks: Boot canvas DOM element visibility (indirect)
  • Useful for: Confirming the piece has painted at least one frame

Signal E: Nopaint ready (brush pieces)#

  • When: After nopaint_boot() completes in the piece's boot() function
  • Checks: window.acSYSTEM?.nopaint?.buffer exists, window.acSYSTEM?.painting?.pixels?.length > 0
  • Caveat: Requires window.acSYSTEM to be exposed — need to verify this exists

Signal F: window.audioWorkletReady (audio pieces)#

  • When: After speaker AudioWorklet connects following user interaction
  • Checks: window.audioWorkletReady === true
  • Only relevant for: Audio-dependent tests

Full Cross-Examination: All 24 Artery Tests#

Audit Summary#

Every test-*.mjs file was examined for its boot/readiness strategy. Zero tests use window.preloaded. Most rely on hardcoded sleep() or the broken waitForBoot().

Test Wait Strategy Timeout Reconnects? activateAudio? Verdict
test-line.mjs waitForPiece() 10s poll No No FIXED — now uses signal-based polling
test-notepat-quick.mjs sleep(2000) 2s fixed No Yes FRAGILE — hardcoded sleep, then broken waitForBoot via activateAudio
test-notepat.mjs sleep(2000) + reconnect + activateAudio() 2s + 15s timeout Yes Yes BROKEN — waitForBoot always times out (15s wasted)
test-hiphop.mjs sleep(2000) + reconnect + activateAudio() 2s + 15s timeout Yes Yes BROKEN — same 15s waste
test-trapwaltz.mjs sleep(2000) + reconnect + activateAudio() 2s + 15s timeout Yes Yes BROKEN — same 15s waste
test-notepat-bach-prelude.mjs sleep(2000) + reconnect + activateAudio() 2s + 15s timeout Yes Yes BROKEN — same pattern
test-notepat-stability.mjs sleep(2000) + activateAudio() 2s + 15s timeout No Yes BROKEN — same pattern
test-notepat-latency.mjs sleep(2000) + reconnect + activateAudio() 2s + 15s timeout Yes Yes BROKEN — same pattern
test-metronome.mjs sleep(1000) + click() 1s fixed No No (manual click) FRAGILE — short sleep, manual audio via click
test-weather.mjs sleep(2000) 2s fixed No No FRAGILE — hardcoded
test-prompt-curtain.mjs cdp.waitForReady(15000) 15s poll No No OK — polls readyState+bios, sufficient for prompt
test-prompt-interaction.mjs None N/A No No RISKY — no wait at all, assumes already loaded
test-jump.mjs sleep(1000) 1s fixed No No FRAGILE — short hardcoded sleep
test-cards.mjs sleep(1000) 1s fixed No No FRAGILE — short hardcoded sleep
test-1v1-split.mjs sleep(2000) 2s fixed No No FRAGILE — hardcoded
test-1v1-multiplayer.mjs sleep(4000) 4s fixed No No FRAGILE — long but still arbitrary
test-1v1-interactive.mjs Custom Camdoll.isReady() Variable No No OK — has its own readiness check
test-1v1-udp.mjs N/A (diagnostic) N/A No No N/A — doesn't navigate
test-kidlisp.mjs Custom waitForReady() 10s poll No No OK — polls page readiness
test-kidlisp-access.mjs None N/A No No N/A — Monaco editor diagnostic
test-panel-open.mjs None N/A No No N/A — only tests panel open/close
test-perf-report.mjs None N/A No No N/A — reads perf data, no navigation
test-module-loader.mjs sleep(500) 500ms fixed No No FRAGILE — very short
test-news-youtube.mjs None N/A No No N/A — YouTube embed diagnostic
test-playlist-memory.mjs cdp.waitForReady(15000) 15s poll No No OK — polls readyState+bios

Verdict Categories#

FIXED (1): test-line.mjs — now uses waitForPiece() with window.preloaded polling.

BROKEN (6): All notepat/music tests call activateAudio() which calls the broken waitForBoot(). This always times out at 15 seconds because window.currentPiece doesn't exist. The fix to waitForBoot() (now polling window.preloaded) will automatically fix all 6.

FRAGILE (7): Use hardcoded sleep() with arbitrary durations. Work on fast systems, fail on slow ones. Should migrate to waitForPiece().

OK (4): Use proper polling (waitForReady, custom readiness checks, or waitForPiece).

N/A (6): Diagnostic/non-navigation tests that don't need boot waiting.

The Reconnect Pattern#

5 tests do client.close() + client.connect() after jump(). This is because CDP's WebSocket connection can break when window.location.href triggers a full page reload. The CDP target changes — the old WebSocket is dead.

Tests that reconnect:

  • test-notepat.mjs
  • test-hiphop.mjs
  • test-trapwaltz.mjs
  • test-notepat-bach-prelude.mjs
  • test-notepat-latency.mjs

Tests that don't reconnect may work because Artery's connect() auto-finds the new target, or because the CDP connection survives SPA-style navigations in certain environments.

Impact of the waitForBoot() Fix#

The fix changes waitForBoot() to poll window.preloaded === true + window.acSEND instead of the nonexistent window.currentPiece. This means:

  • 6 BROKEN tests will now boot-detect correctly (fast return instead of 15s timeout)
  • Typical boot time should be ~1-3 seconds, saving 12-14 seconds per test run
  • activateAudio() calls waitForBoot() internally, so all audio tests benefit automatically

Applied Fix (2026-02-21)#

1. Fixed waitForBoot() in artery/artery.mjs#

Changed from polling the nonexistent window.currentPiece to polling real signals:

async waitForBoot(timeoutMs = 15000) {
  while (Date.now() - startTime < timeoutMs) {
    const bootStatus = await this.eval(`({
      preloaded: window.preloaded === true,       // ← Signal C: piece booted
      hasSend: typeof window.acSEND === 'function', // ← Signal B: worker ready
      hasActivateSound: typeof window.activateSound === 'function',
      canvasExists: !!document.querySelector('canvas'),
      locationPath: window.location?.pathname || '',
      hasAudioContext: !!window.audioContext
    })`);

    if (bootStatus.preloaded && bootStatus.hasSend) {  // ← Real signals
      return true;
    }
    await new Promise(r => setTimeout(r, 100));
  }
  return false;
}

This automatically fixes the 6 BROKEN audio tests since activateAudio() calls waitForBoot() internally.

2. Added waitForPiece() to artery/artery.mjs#

New method for targeted piece-readiness detection:

async waitForPiece(expectedPath = null, timeoutMs = 10000) {
  while (Date.now() - startTime < timeoutMs) {
    const status = await this.eval(`({
      preloaded: window.preloaded === true,
      hasSend: typeof window.acSEND === 'function',
      path: window.location?.pathname || '',
      hasCanvas: !!document.querySelector('canvas'),
    })`);

    const pathOk = !expectedPath || status.path?.includes(expectedPath);
    if (status.preloaded && status.hasSend && pathOk) {
      return { ready: true, ...status };
    }
    await new Promise(r => setTimeout(r, 150));
  }
  return { ready: false, timedOut: true };
}

3. Updated test-line.mjs#

Replaced sleep(3000) + sleep(2000) with signal-based waiting:

await client.jump('new~128');
const newResult = await client.waitForPiece('new', 10000);  // Polls, returns fast

await client.jump('line');
const lineResult = await client.waitForPiece('line', 10000); // Polls, returns fast

Future: waitForNopaint() (brush pieces)#

Not yet implemented — requires verifying window.acSYSTEM is exposed at runtime:

async waitForNopaint(timeoutMs = 8000) {
  // Polls for system.painting + system.nopaint.buffer
  // Would provide brush-specific readiness
}

The window.preloaded Lifecycle#

This is the most useful signal. Here's its full lifecycle:

1. boot() starts           → window.preloaded is undefined (fresh page)
2. Piece navigated away    → window.preloaded = false      (bios.mjs:12767)
3. New piece loads + boots → window.preloaded = true        (bios.mjs:16642)
4. Next navigation         → window.preloaded = false       (reset again)

Key insight: Since client.jump() sets window.location.href, the entire page reloads. So window.preloaded starts as undefined, then becomes true once the piece is fully loaded. Polling for window.preloaded === true is the correct general-purpose check.


// 1. Navigate
await client.jump('new~128');

// 2. Wait for AC to fully boot with the new painting
const bootResult = await client.waitForPiece('new', 10000);
if (!bootResult.ready) throw new Error('Boot timeout on new~128');

// 3. Navigate to brush
await client.jump('line');

// 4. Wait for piece + nopaint to be ready
const pieceResult = await client.waitForPiece('line', 8000);
if (!pieceResult.ready) throw new Error('Piece timeout on line');

// 5. (Optional) Wait specifically for nopaint painting buffer
const nopaintResult = await client.waitForNopaint(5000);
if (!nopaintResult.ready) console.warn('Nopaint not fully initialized');

// 6. Now safe to draw
await drawStroke(client, points);

Open Questions#

  1. Is window.acSYSTEM exposed? Need to verify at runtime whether the system object is accessible from window scope. If not, we may need to expose it for testing.

  2. Does jump() (location.href change) cause a full page reload? If so, window.preloaded starts fresh each time, which is clean. If it's a SPA-style navigation, the signal lifecycle may be different.

  3. Should we add a dedicated window.acPIECE_READY signal? A simple boolean set right after currentPiece is assigned would solve the entire detection problem cleanly.

  4. Race condition on window.preloaded: If the piece loads extremely fast (cached), could preloaded become true before our first poll? In theory yes, but that's fine — we'd catch it on the first poll iteration.


Files Referenced#

File Key Lines Signal
boot.mjs 66, 172, 348, 351, 375 acBOOT_START_TIME, _bootTimings, acHIDE_BOOT_LOG, acBOOT_LOG, acAuthTiming
bios.mjs 651-654, 700-711, 804, 4283-4294, 12717, 12767, 16637-16677 acDISK_SEND, acSEND, acGetState, currentPiece (local), preloaded
artery.mjs 427-490 waitForBoot() (FIXED), waitForPiece() (NEW)
cdp.mjs 237-252 waitForReady() (DOM-only)
nopaint.mjs 67-91 nopaint_boot() initialization

Change Log#

  • 2026-02-21: Initial study. Fixed waitForBoot() to use window.preloaded. Added waitForPiece(). Updated test-line.mjs. Full 24-test audit.