import { defineComponent, h, type Component } from "vue"; import type { OriginScene } from "../src"; export function rng(seed: number) { let a = seed >>> 0; const next = () => { a = (a + 0x6d2b79f5) >>> 0; let t = a; t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; return { next, int: (n: number) => Math.floor(next() * n), pick: (items: readonly T[]) => items[Math.floor(next() * items.length)]!, chance: (p: number) => next() < p, }; } export function component(name: string): Component { return defineComponent({ name, render: () => h("div", name) }); } /** Deterministic rAF + clock shared by both implementations. */ export function installFrames() { let now = 1000; let queue: FrameRequestCallback[] = []; window.requestAnimationFrame = ((cb: FrameRequestCallback) => { queue.push(cb); return queue.length; }) as typeof window.requestAnimationFrame; globalThis.requestAnimationFrame = window.requestAnimationFrame; const perf = performance; perf.now = () => now; return { advance(ms = 16) { now += ms; const run = queue; queue = []; for (const cb of run) cb(now); }, pending: () => queue.length, }; } export async function flush(rounds = 6) { for (let i = 0; i < rounds; i += 1) await new Promise((resolve) => setTimeout(resolve, 0)); } /** Implementation-agnostic, comparable snapshot of a scene. */ export function snapshot(scene: OriginScene) { const nodes = scene.nodes.value; return { nodes: nodes.map((node) => ({ key: node.key, sequence: node.sequence, view: node.view.key, previousNodeKey: node.previousNodeKey, state: node.state, incomingOperationId: node.incomingOperationId, history: node.history.map((view) => view.key), })), operations: scene.operations.value.map((operation) => ({ id: operation.id, originKey: operation.originKey, targetKey: operation.targetKey, entryTargetKey: operation.entryTargetKey, choreography: operation.choreography.name, placement: operation.placement, history: operation.history, progress: operation.progress, velocity: operation.velocity, phase: operation.phase, intent: operation.intent, })), roots: [...scene.roots.value], styles: Object.fromEntries( nodes.map((node) => [node.key, scene.styleForNode(node.key)]), ), interactive: Object.fromEntries( nodes.map((node) => [node.key, scene.isNodeInteractive(node.key)]), ), contexts: Object.fromEntries( nodes.map((node) => { const context = scene.contextFor(node.key); return [ node.key, { nodeKey: context.nodeKey, view: context.view.key, canGoBack: context.canGoBack, previous: context.previous?.key, history: context.history.map((view) => view.key), }, ]; }), ), missingStyle: scene.styleForNode("missing::0"), missingInteractive: scene.isNodeInteractive("missing::0"), }; } export async function settle(promise: Promise) { return promise.then( (value) => ({ ok: true as const, value }), (error: Error) => ({ ok: false as const, error: error.message }), ); }