/** Phase 0 executable contract model for XZBT 0.1 GC4. */ const UINT32_RANGE = 0x1_0000_0000; const STEP_MS = 1000 / 60; function rotateLeft(value, count) { return ((value << count) | (value >>> (32 - count))) >>> 0; } function multiply32(left, right) { return Math.imul(left, right) >>> 0; } export function fnv1a32(text) { let hash = 0x811c9dc5; for (const byte of new TextEncoder().encode(text)) { hash ^= byte; hash = multiply32(hash, 0x01000193); } return hash >>> 0; } export function splitMix32(seed) { let state = seed >>> 0; return () => { state = (state + 0x9e3779b9) >>> 0; let value = state; value = multiply32(value ^ (value >>> 16), 0x21f0aaad); value = multiply32(value ^ (value >>> 15), 0x735a2d97); return (value ^ (value >>> 15)) >>> 0; }; } export function deriveStreamState(rootSeed, domain, stableInstanceKey) { if (!Number.isInteger(rootSeed) || rootSeed < 0 || rootSeed >= UINT32_RANGE) { throw new RangeError('Root seed must be an unsigned 32-bit integer.'); } const hash = fnv1a32(`xzbt-0.1\0${rootSeed}\0${domain}\0${stableInstanceKey}`); const expand = splitMix32(hash); const state = [expand(), expand(), expand(), expand()]; if (state.every((word) => word === 0)) state[3] = 1; return state; } export class Xoshiro128StarStar { constructor(state) { if (!Array.isArray(state) || state.length !== 4 || state.some((word) => !Number.isInteger(word))) { throw new TypeError('xoshiro128** state must contain four integer words.'); } this.state = state.map((word) => word >>> 0); if (this.state.every((word) => word === 0)) throw new RangeError('xoshiro128** state cannot be all zero.'); } nextUint32() { const state = this.state; const result = multiply32(rotateLeft(multiply32(state[1], 5), 7), 9); const temporary = (state[1] << 9) >>> 0; state[2] ^= state[0]; state[3] ^= state[1]; state[1] ^= state[2]; state[0] ^= state[3]; state[2] ^= temporary; state[3] = rotateLeft(state[3], 11); for (let index = 0; index < 4; index++) state[index] >>>= 0; return result >>> 0; } nextFloat() { return this.nextUint32() / UINT32_RANGE; } } export function createRandomStream(rootSeed, domain, stableInstanceKey) { return new Xoshiro128StarStar(deriveStreamState(rootSeed, domain, stableInstanceKey)); } export class FixedStepClock { constructor({ stepMs = STEP_MS, maxTicksPerTurn = 8, maxElapsedMs = 250 } = {}) { this.stepMs = stepMs; this.maxTicksPerTurn = maxTicksPerTurn; this.maxElapsedMs = maxElapsedMs; this.tickIndex = 0; this.accumulatorMs = 0; this.lastNowMs = undefined; this.pauseReasons = new Set(); } setPauseReason(reason, active, nowMs) { const wasPaused = this.pauseReasons.size > 0; if (active) this.pauseReasons.add(reason); else this.pauseReasons.delete(reason); const isPaused = this.pauseReasons.size > 0; if (wasPaused !== isPaused) this.lastNowMs = nowMs; } advance(nowMs) { if (this.lastNowMs === undefined) { this.lastNowMs = nowMs; return { ticks: [], stalled: false }; } if (this.pauseReasons.size > 0) { this.lastNowMs = nowMs; return { ticks: [], stalled: false }; } const observed = Math.max(0, nowMs - this.lastNowMs); this.lastNowMs = nowMs; const accepted = Math.min(observed, this.maxElapsedMs); this.accumulatorMs += accepted; const available = Math.floor((this.accumulatorMs + 1e-9) / this.stepMs); const count = Math.min(available, this.maxTicksPerTurn); const ticks = []; for (let index = 0; index < count; index++) ticks.push(++this.tickIndex); this.accumulatorMs -= count * this.stepMs; const stalled = observed > this.maxElapsedMs || available > this.maxTicksPerTurn; if (available > this.maxTicksPerTurn) { this.accumulatorMs = Math.min(this.accumulatorMs, this.stepMs - 1e-9); } return { ticks, stalled }; } } export function planAudioUnlock(invocations, logicalSeconds) { return invocations.map((invocation) => { if (invocation.start < logicalSeconds) { if (invocation.kind === 'one-shot') return { id: invocation.id, action: 'skip' }; if (invocation.end === undefined || invocation.end > logicalSeconds) { return { id: invocation.id, action: 'start-continuous', phase: logicalSeconds - invocation.start, rampMs: 20 }; } return { id: invocation.id, action: 'dispose' }; } return { id: invocation.id, action: 'schedule', delay: invocation.start - logicalSeconds }; }); } export const GC4_CONSTANTS = Object.freeze({ stepMs: STEP_MS, maxTicksPerTurn: 8, maxElapsedMs: 250, audioLookaheadMs: 100, unlockRampMs: 20 });