// WebWorker for safe shader compilation and execution interface WorkerMessage { id: string; type: 'compile' | 'render'; code?: string; width?: number; height?: number; time?: number; renderMode?: string; valueMode?: string; // 'integer' or 'float' hueShift?: number; // Hue shift in degrees (0-360) startY?: number; // Y offset for tile rendering fullWidth?: number; // Full canvas width for center calculations fullHeight?: number; // Full canvas height for center calculations mouseX?: number; mouseY?: number; mousePressed?: boolean; mouseVX?: number; mouseVY?: number; mouseClickTime?: number; touchCount?: number; touch0X?: number; touch0Y?: number; touch1X?: number; touch1Y?: number; pinchScale?: number; pinchRotation?: number; accelX?: number; accelY?: number; accelZ?: number; gyroX?: number; gyroY?: number; gyroZ?: number; audioLevel?: number; bassLevel?: number; midLevel?: number; trebleLevel?: number; bpm?: number; } interface WorkerResponse { id: string; type: 'compiled' | 'rendered' | 'error'; success: boolean; imageData?: ImageData; error?: string; } import { LRUCache } from './utils/LRUCache'; import { calculateColorDirect } from './utils/colorModes'; import { PERFORMANCE } from './utils/constants'; type ShaderFunction = (...args: number[]) => number; class ShaderWorker { private compiledFunction: ShaderFunction | null = null; private lastCode: string = ''; private imageDataCache: LRUCache = new LRUCache( PERFORMANCE.IMAGE_DATA_CACHE_SIZE ); private compilationCache: LRUCache = new LRUCache( PERFORMANCE.COMPILATION_CACHE_SIZE ); private feedbackBuffer: Float32Array | null = null; constructor() { self.onmessage = (e: MessageEvent) => { this.handleMessage(e.data); }; } private handleMessage(message: WorkerMessage): void { try { switch (message.type) { case 'compile': this.compileShader(message.id, message.code!); break; case 'render': this.renderShader( message.id, message.width!, message.height!, message.time!, message.renderMode || 'classic', message.valueMode || 'integer', message, message.startY || 0 ); break; } } catch (error) { this.postError( message.id, error instanceof Error ? error.message : 'Unknown error' ); } } private compileShader(id: string, code: string): void { const codeHash = this.hashCode(code); if (code === this.lastCode && this.compiledFunction) { this.postMessage({ id, type: 'compiled', success: true }); return; } // Check compilation cache const cachedFunction = this.compilationCache.get(codeHash); if (cachedFunction) { this.compiledFunction = cachedFunction; this.lastCode = code; this.postMessage({ id, type: 'compiled', success: true }); return; } try { const safeCode = this.sanitizeCode(code); // Check if expression is static (contains no variables) const isStatic = this.isStaticExpression(safeCode); if (isStatic) { // Pre-compute static value const staticValue = this.evaluateStaticExpression(safeCode); this.compiledFunction = () => staticValue; } else { this.compiledFunction = new Function( 'x', 'y', 't', 'i', 'r', 'a', 'u', 'v', 'c', 'f', 'd', 'n', 'b', 'mouseX', 'mouseY', 'mousePressed', 'mouseVX', 'mouseVY', 'mouseClickTime', 'touchCount', 'touch0X', 'touch0Y', 'touch1X', 'touch1Y', 'pinchScale', 'pinchRotation', 'accelX', 'accelY', 'accelZ', 'gyroX', 'gyroY', 'gyroZ', 'audioLevel', 'bassLevel', 'midLevel', 'trebleLevel', 'bpm', ` // Timeout protection const startTime = performance.now(); let iterations = 0; function checkTimeout() { iterations++; if (iterations % ${PERFORMANCE.TIMEOUT_CHECK_INTERVAL} === 0 && performance.now() - startTime > ${PERFORMANCE.MAX_SHADER_TIMEOUT_MS}) { throw new Error('Shader timeout'); } } return (function() { checkTimeout(); return ${safeCode}; })(); ` ) as ShaderFunction; } // Cache the compiled function if (this.compiledFunction) { this.compilationCache.set(codeHash, this.compiledFunction); } this.lastCode = code; this.postMessage({ id, type: 'compiled', success: true }); } catch (error) { this.compiledFunction = null; this.postError( id, error instanceof Error ? error.message : 'Compilation failed' ); } } private isStaticExpression(code: string): boolean { // Check if code contains any variables using regex for better accuracy const variablePattern = /\b(x|y|t|i|r|a|u|v|c|f|d|n|b|bpm|mouse[XY]|mousePressed|mouseV[XY]|mouseClickTime|touchCount|touch[01][XY]|pinchScale|pinchRotation|accel[XYZ]|gyro[XYZ]|audioLevel|bassLevel|midLevel|trebleLevel)\b/; return !variablePattern.test(code); } private evaluateStaticExpression(code: string): number { try { // Safely evaluate numeric expression const result = new Function(`return ${code}`)(); return isFinite(result) ? result : 0; } catch (error) { return 0; } } private hashCode(str: string): string { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; // Convert to 32-bit integer } return hash.toString(36); } private renderShader( id: string, width: number, height: number, time: number, renderMode: string, valueMode: string, message: WorkerMessage, startY: number = 0 ): void { if (!this.compiledFunction) { this.postError(id, 'No compiled shader'); return; } const imageData = this.getOrCreateImageData(width, height); const data = imageData.data; const startTime = performance.now(); const maxRenderTime = PERFORMANCE.MAX_RENDER_TIME_MS; // Initialize feedback buffer if needed if (!this.feedbackBuffer || this.feedbackBuffer.length !== width * height) { this.feedbackBuffer = new Float32Array(width * height); } try { // Use tiled rendering for better timeout handling this.renderTiled( data, width, height, time, renderMode, valueMode, message, startTime, maxRenderTime, startY ); this.postMessage({ id, type: 'rendered', success: true, imageData }); } catch (error) { this.postError( id, error instanceof Error ? error.message : 'Render failed' ); } } private renderTiled( data: Uint8ClampedArray, width: number, height: number, time: number, renderMode: string, valueMode: string, message: WorkerMessage, startTime: number, maxRenderTime: number, yOffset: number = 0 ): void { const tileSize = PERFORMANCE.DEFAULT_TILE_SIZE; const tilesX = Math.ceil(width / tileSize); const tilesY = Math.ceil(height / tileSize); for (let tileY = 0; tileY < tilesY; tileY++) { for (let tileX = 0; tileX < tilesX; tileX++) { // Check timeout before each tile if (performance.now() - startTime > maxRenderTime) { const startX = tileX * tileSize; const startY = tileY * tileSize; this.fillRemainingPixels(data, width, height, startY, startX); return; } const tileStartX = tileX * tileSize; const tileStartY = tileY * tileSize; const tileEndX = Math.min(tileStartX + tileSize, width); const tileEndY = Math.min(tileStartY + tileSize, height); this.renderTile( data, width, tileStartX, tileStartY, tileEndX, tileEndY, time, renderMode, valueMode, message, yOffset ); } } } private renderTile( data: Uint8ClampedArray, width: number, startX: number, startY: number, endX: number, endY: number, time: number, renderMode: string, valueMode: string, message: WorkerMessage, yOffset: number = 0 ): void { // Get full canvas dimensions for special modes (use provided full dimensions or fall back) const fullWidth = message.fullWidth || width; const fullHeight = message.fullHeight || message.height! + yOffset; for (let y = startY; y < endY; y++) { for (let x = startX; x < endX; x++) { const i = (y * width + x) * 4; const pixelIndex = y * width + x; // Adjust y coordinate to account for tile offset const actualY = y + yOffset; try { // Calculate additional coordinate variables const u = x / fullWidth; const v = actualY / fullHeight; const centerX = fullWidth / 2; const centerY = fullHeight / 2; const radius = Math.sqrt( (x - centerX) ** 2 + (actualY - centerY) ** 2 ); const angle = Math.atan2(actualY - centerY, x - centerX); const maxDistance = Math.sqrt(centerX ** 2 + centerY ** 2); const normalizedDistance = radius / maxDistance; const frameCount = Math.floor(time * 60); const manhattanDistance = Math.abs(x - centerX) + Math.abs(actualY - centerY); const noise = (Math.sin(x * 0.1) * Math.cos(actualY * 0.1) + 1) * 0.5; const feedbackValue = this.feedbackBuffer ? this.feedbackBuffer[pixelIndex] || 0 : 0; const value = this.compiledFunction!( x, actualY, time, pixelIndex, radius, angle, u, v, normalizedDistance, frameCount, manhattanDistance, noise, feedbackValue, message.mouseX || 0, message.mouseY || 0, message.mousePressed ? 1 : 0, message.mouseVX || 0, message.mouseVY || 0, message.mouseClickTime || 0, message.touchCount || 0, message.touch0X || 0, message.touch0Y || 0, message.touch1X || 0, message.touch1Y || 0, message.pinchScale || 1, message.pinchRotation || 0, message.accelX || 0, message.accelY || 0, message.accelZ || 0, message.gyroX || 0, message.gyroY || 0, message.gyroZ || 0, message.audioLevel || 0, message.bassLevel || 0, message.midLevel || 0, message.trebleLevel || 0, message.bpm || 120 ); const safeValue = isFinite(value) ? value : 0; const [r, g, b] = this.calculateColor( safeValue, renderMode, valueMode, message.hueShift || 0, x, actualY, fullWidth, fullHeight ); data[i] = r; data[i + 1] = g; data[i + 2] = b; data[i + 3] = 255; // Update feedback buffer with current processed value if (this.feedbackBuffer) { this.feedbackBuffer[pixelIndex] = safeValue; } } catch (error) { data[i] = 0; data[i + 1] = 0; data[i + 2] = 0; data[i + 3] = 255; } } } } private fillRemainingPixels( data: Uint8ClampedArray, width: number, height: number, startY: number, startX: number ): void { for (let remainingY = startY; remainingY < height; remainingY++) { const xStart = remainingY === startY ? startX : 0; for (let remainingX = xStart; remainingX < width; remainingX++) { const i = (remainingY * width + remainingX) * 4; data[i] = 0; data[i + 1] = 0; data[i + 2] = 0; data[i + 3] = 255; } } } private getOrCreateImageData(width: number, height: number): ImageData { const key = `${width}x${height}`; let imageData = this.imageDataCache.get(key); if (!imageData) { imageData = new ImageData(width, height); this.imageDataCache.set(key, imageData); } return imageData; } private calculateColor( value: number, renderMode: string, valueMode: string = 'integer', hueShift: number = 0, x: number = 0, y: number = 0, width: number = 1, height: number = 1 ): [number, number, number] { let processedValue: number; switch (valueMode) { case 'float': // Float mode: treat value as 0.0-1.0, invert it (like original bitfield shaders) processedValue = Math.max(0, Math.min(1, Math.abs(value))); // Clamp to 0-1 processedValue = 1 - processedValue; // Invert (like original) processedValue = Math.floor(processedValue * 255); // Convert to 0-255 break; case 'polar': { // Polar mode: angular patterns with value-based rotation and radius influence const centerX = width / 2; const centerY = height / 2; const dx = x - centerX; const dy = y - centerY; const radius = Math.sqrt(dx * dx + dy * dy); const angle = Math.atan2(dy, dx); // -π to π const normalizedAngle = (angle + Math.PI) / (2 * Math.PI); // 0 to 1 // Combine angle with radius and value for complex patterns const radiusNorm = radius / Math.max(centerX, centerY); const spiralEffect = (normalizedAngle + radiusNorm * 0.5 + Math.abs(value) * 0.02) % 1; const polarValue = Math.sin(spiralEffect * Math.PI * 8) * 0.5 + 0.5; // Create wave pattern processedValue = Math.floor(polarValue * 255); break; } case 'distance': { // Distance mode: concentric patterns with value-based frequency and phase const distCenterX = width / 2; const distCenterY = height / 2; const distance = Math.sqrt( (x - distCenterX) ** 2 + (y - distCenterY) ** 2 ); const maxDistance = Math.sqrt(distCenterX ** 2 + distCenterY ** 2); const normalizedDistance = distance / maxDistance; // 0 to 1 // Create concentric waves with value-controlled frequency and phase const frequency = 8 + Math.abs(value) * 0.1; // Variable frequency const phase = Math.abs(value) * 0.05; // Value affects phase shift const concentricWave = Math.sin(normalizedDistance * Math.PI * frequency + phase) * 0.5 + 0.5; // Add some radial falloff for more interesting patterns const falloff = 1 - Math.pow(normalizedDistance, 0.8); const distanceValue = concentricWave * falloff; processedValue = Math.floor(distanceValue * 255); break; } case 'wave': { // Wave mode: interference patterns from multiple wave sources const baseFreq = 0.08; const valueScale = Math.abs(value) * 0.001 + 1; // Scale frequency by value let waveSum = 0; // Create wave sources at strategic positions for interesting interference const sources = [ { x: width * 0.3, y: height * 0.3 }, { x: width * 0.7, y: height * 0.3 }, { x: width * 0.5, y: height * 0.7 }, { x: width * 0.2, y: height * 0.8 }, ]; for (const source of sources) { const dist = Math.sqrt((x - source.x) ** 2 + (y - source.y) ** 2); const wave = Math.sin( dist * baseFreq * valueScale + Math.abs(value) * 0.02 ); const amplitude = 1 / (1 + dist * 0.002); // Distance-based amplitude falloff waveSum += wave * amplitude; } // Normalize and enhance contrast const waveValue = Math.tanh(waveSum) * 0.5 + 0.5; // tanh for better contrast processedValue = Math.floor(waveValue * 255); break; } case 'fractal': { // Fractal mode: recursive pattern generation const scale = 0.01; let fractalValue = 0; let amplitude = 1; const octaves = 4; for (let i = 0; i < octaves; i++) { const frequency = Math.pow(2, i) * scale; const noise = Math.sin((x + Math.abs(value) * 0.1) * frequency) * Math.cos((y + Math.abs(value) * 0.1) * frequency); fractalValue += noise * amplitude; amplitude *= 0.5; } processedValue = Math.floor((fractalValue + 1) * 0.5 * 255); break; } case 'cellular': { // Cellular automata-inspired patterns const cellSize = 16; const cellX = Math.floor(x / cellSize); const cellY = Math.floor(y / cellSize); const cellHash = (cellX * 73856093) ^ (cellY * 19349663) ^ Math.floor(Math.abs(value)); // Generate cellular pattern based on neighbors let neighbors = 0; for (let dx = -1; dx <= 1; dx++) { for (let dy = -1; dy <= 1; dy++) { if (dx === 0 && dy === 0) continue; const neighborHash = ((cellX + dx) * 73856093) ^ ((cellY + dy) * 19349663) ^ Math.floor(Math.abs(value)); if (neighborHash % 256 > 128) neighbors++; } } const cellState = cellHash % 256 > 128 ? 1 : 0; const evolution = neighbors >= 3 && neighbors <= 5 ? 1 : cellState; processedValue = evolution * 255; break; } case 'noise': { // Perlin-like noise pattern const noiseScale = 0.02; const nx = x * noiseScale + Math.abs(value) * 0.001; const ny = y * noiseScale + Math.abs(value) * 0.001; // Simple noise approximation using sine waves const noise1 = Math.sin(nx * 6.28) * Math.cos(ny * 6.28); const noise2 = Math.sin(nx * 12.56) * Math.cos(ny * 12.56) * 0.5; const noise3 = Math.sin(nx * 25.12) * Math.cos(ny * 25.12) * 0.25; const combinedNoise = (noise1 + noise2 + noise3) / 1.75; processedValue = Math.floor((combinedNoise + 1) * 0.5 * 255); break; } case 'warp': { // Warp mode: space deformation based on value const centerX = width / 2; const centerY = height / 2; // Create warping field based on value const warpStrength = Math.abs(value) * 0.001; const warpFreq = 0.02; // Calculate warped coordinates const warpX = x + Math.sin(y * warpFreq + Math.abs(value) * 0.01) * warpStrength * 100; const warpY = y + Math.cos(x * warpFreq + Math.abs(value) * 0.01) * warpStrength * 100; // Create barrel/lens distortion const dx = warpX - centerX; const dy = warpY - centerY; const dist = Math.sqrt(dx * dx + dy * dy); const maxDist = Math.sqrt(centerX * centerX + centerY * centerY); const normDist = dist / maxDist; // Apply non-linear space deformation const deform = 1 + Math.sin(normDist * Math.PI + Math.abs(value) * 0.05) * 0.3; const deformedX = centerX + dx * deform; const deformedY = centerY + dy * deform; // Sample from deformed space const finalValue = (deformedX + deformedY + Math.abs(value)) % 256; processedValue = Math.floor(Math.abs(finalValue)); break; } case 'flow': { // Flow field mode: large-scale fluid dynamics simulation const centerX = width / 2; const centerY = height / 2; // Create multiple flow sources influenced by value const flowSources = [ { x: centerX + Math.sin(Math.abs(value) * 0.01) * 200, y: centerY + Math.cos(Math.abs(value) * 0.01) * 200, strength: 1 + Math.abs(value) * 0.01, }, { x: centerX + Math.cos(Math.abs(value) * 0.015) * 150, y: centerY + Math.sin(Math.abs(value) * 0.015) * 150, strength: -0.8 + Math.sin(Math.abs(value) * 0.02) * 0.5, }, { x: centerX + Math.sin(Math.abs(value) * 0.008) * 300, y: centerY + Math.cos(Math.abs(value) * 0.012) * 250, strength: 0.6 + Math.cos(Math.abs(value) * 0.018) * 0.4, }, ]; // Calculate flow field at this point let flowX = 0; let flowY = 0; for (const source of flowSources) { const dx = x - source.x; const dy = y - source.y; const distance = Math.sqrt(dx * dx + dy * dy); const normalizedDist = Math.max(distance, 1); // Avoid division by zero // Create flow vectors (potential field + curl) const flowStrength = source.strength / (normalizedDist * 0.01); // Radial component (attraction/repulsion) flowX += (dx / normalizedDist) * flowStrength; flowY += (dy / normalizedDist) * flowStrength; // Curl component (rotation) - creates vortices const curlStrength = source.strength * 0.5; flowX += ((-dy / normalizedDist) * curlStrength) / normalizedDist; flowY += ((dx / normalizedDist) * curlStrength) / normalizedDist; } // Add global flow influenced by value const globalFlowAngle = Math.abs(value) * 0.02; flowX += Math.cos(globalFlowAngle) * (Math.abs(value) * 0.1); flowY += Math.sin(globalFlowAngle) * (Math.abs(value) * 0.1); // Add turbulence const turbScale = 0.05; const turbulence = Math.sin(x * turbScale + Math.abs(value) * 0.01) * Math.cos(y * turbScale + Math.abs(value) * 0.015) * (Math.abs(value) * 0.02); flowX += turbulence; flowY += turbulence * 0.7; // Simulate particle flowing through the field let particleX = x; let particleY = y; // Multiple flow steps for more interesting trajectories for (let step = 0; step < 5; step++) { // Sample flow field at current particle position let localFlowX = 0; let localFlowY = 0; for (const source of flowSources) { const dx = particleX - source.x; const dy = particleY - source.y; const distance = Math.sqrt(dx * dx + dy * dy); const normalizedDist = Math.max(distance, 1); const flowStrength = source.strength / (normalizedDist * 0.01); localFlowX += (dx / normalizedDist) * flowStrength; localFlowY += (dy / normalizedDist) * flowStrength; // Curl const curlStrength = source.strength * 0.5; localFlowX += ((-dy / normalizedDist) * curlStrength) / normalizedDist; localFlowY += ((dx / normalizedDist) * curlStrength) / normalizedDist; } // Move particle const stepSize = 0.5; particleX += localFlowX * stepSize; particleY += localFlowY * stepSize; } // Calculate final value based on particle's final position and flow magnitude const flowMagnitude = Math.sqrt(flowX * flowX + flowY * flowY); const particleDistance = Math.sqrt( (particleX - x) * (particleX - x) + (particleY - y) * (particleY - y) ); // Combine flow magnitude with particle trajectory const flowValue = (flowMagnitude * 10 + particleDistance * 2) % 256; const enhanced = Math.sin(flowValue * 0.05 + Math.abs(value) * 0.01) * 0.5 + 0.5; processedValue = Math.floor(enhanced * 255); break; } case 'spiral': { // Creates logarithmic spirals based on the shader value const centerX = width / 2; const centerY = height / 2; const dx = x - centerX; const dy = y - centerY; const radius = Math.sqrt(dx * dx + dy * dy); const spiralTightness = 1 + Math.abs(value) * 0.01; const spiralValue = Math.atan2(dy, dx) + Math.log(Math.max(radius, 1)) * spiralTightness; processedValue = Math.floor((Math.sin(spiralValue) * 0.5 + 0.5) * 255); break; } case 'turbulence': { // Multi-octave turbulence with value-controlled chaos let turbulence = 0; const chaos = Math.abs(value) * 0.001; for (let i = 0; i < 4; i++) { const freq = Math.pow(2, i) * (0.01 + chaos); turbulence += Math.abs(Math.sin(x * freq) * Math.cos(y * freq)) / Math.pow(2, i); } processedValue = Math.floor(Math.min(turbulence, 1) * 255); break; } case 'crystal': { // Crystalline lattice patterns const latticeSize = 32 + Math.abs(value) * 0.1; const gridX = Math.floor(x / latticeSize); const gridY = Math.floor(y / latticeSize); const crystal = Math.sin(gridX + gridY + Math.abs(value) * 0.01) * Math.cos(gridX * gridY + Math.abs(value) * 0.005); processedValue = Math.floor((crystal * 0.5 + 0.5) * 255); break; } case 'marble': { // Marble-like veining patterns const noiseFreq = 0.005 + Math.abs(value) * 0.00001; const turbulence = Math.sin(x * noiseFreq) * Math.cos(y * noiseFreq) + Math.sin(x * noiseFreq * 2) * Math.cos(y * noiseFreq * 2) * 0.5; const marble = Math.sin((x + turbulence * 50) * 0.02 + Math.abs(value) * 0.001); processedValue = Math.floor((marble * 0.5 + 0.5) * 255); break; } case 'quantum': { // Quantum uncertainty visualization const centerX = width / 2; const centerY = height / 2; const uncertainty = Math.abs(value) * 0.001; const probability = Math.exp(-( (x - centerX) ** 2 + (y - centerY) ** 2 ) / (2 * (100 + uncertainty * 1000) ** 2)); const quantum = probability * (1 + Math.sin(x * y * uncertainty) * 0.5); processedValue = Math.floor(Math.min(quantum, 1) * 255); break; } case 'logarithmic': { // Simple mathematical transform: logarithmic scaling const logValue = Math.log(1 + Math.abs(value)); processedValue = Math.floor((logValue / Math.log(256)) * 255); break; } case 'mirror': { // Mirror/kaleidoscope effect - creates symmetrical patterns const centerX = width / 2; const centerY = height / 2; const dx = Math.abs(x - centerX); const dy = Math.abs(y - centerY); const mirrorX = centerX + (dx % centerX); const mirrorY = centerY + (dy % centerY); const mirrorDistance = Math.sqrt(mirrorX * mirrorX + mirrorY * mirrorY); const mirrorValue = (Math.abs(value) + mirrorDistance) % 256; processedValue = mirrorValue; break; } case 'rings': { // Concentric rings with value-controlled spacing and interference const centerX = width / 2; const centerY = height / 2; const distance = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2); const ringSpacing = 20 + Math.abs(value) * 0.1; const rings = Math.sin((distance / ringSpacing) * Math.PI * 2); const interference = Math.sin((distance + Math.abs(value)) * 0.05); processedValue = Math.floor(((rings * interference) * 0.5 + 0.5) * 255); break; } case 'mesh': { // Grid/mesh patterns with value-controlled density and rotation const angle = Math.abs(value) * 0.001; const rotX = x * Math.cos(angle) - y * Math.sin(angle); const rotY = x * Math.sin(angle) + y * Math.cos(angle); const gridSize = 16 + Math.abs(value) * 0.05; const gridX = Math.sin((rotX / gridSize) * Math.PI * 2); const gridY = Math.sin((rotY / gridSize) * Math.PI * 2); const mesh = Math.max(Math.abs(gridX), Math.abs(gridY)); processedValue = Math.floor(mesh * 255); break; } case 'glitch': { // Digital glitch/corruption effects const seed = Math.floor(x + y * width + Math.abs(value)); const random = ((seed * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff; const glitchThreshold = 0.95 - Math.abs(value) * 0.0001; let glitchValue = Math.abs(value) % 256; if (random > glitchThreshold) { // Digital corruption: bit shifts, XOR, scrambling glitchValue = (glitchValue << 1) ^ (glitchValue >> 3) ^ ((x + y) & 0xFF); } processedValue = glitchValue % 256; break; } default: // Integer mode: treat value as 0-255 (original behavior) processedValue = Math.abs(value) % 256; break; } // Use direct calculation to support hue shift return calculateColorDirect(processedValue, renderMode, hueShift); } private sanitizeCode(code: string): string { // Auto-prefix Math functions const mathFunctions = [ 'abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'random', 'round', 'sin', 'sqrt', 'tan', 'trunc', 'sign', 'cbrt', 'hypot', 'imul', 'fround', 'clz32', 'acosh', 'asinh', 'atanh', 'cosh', 'sinh', 'tanh', 'expm1', 'log1p', 'log10', 'log2', ]; let processedCode = code; // Replace standalone math functions with Math.function mathFunctions.forEach((func) => { const regex = new RegExp(`\\b${func}\\(`, 'g'); processedCode = processedCode.replace(regex, `Math.${func}(`); }); // Add Math constants processedCode = processedCode.replace(/\bPI\b/g, 'Math.PI'); processedCode = processedCode.replace(/\bE\b/g, 'Math.E'); processedCode = processedCode.replace(/\bLN2\b/g, 'Math.LN2'); processedCode = processedCode.replace(/\bLN10\b/g, 'Math.LN10'); processedCode = processedCode.replace(/\bLOG2E\b/g, 'Math.LOG2E'); processedCode = processedCode.replace(/\bLOG10E\b/g, 'Math.LOG10E'); processedCode = processedCode.replace(/\bSQRT1_2\b/g, 'Math.SQRT1_2'); processedCode = processedCode.replace(/\bSQRT2\b/g, 'Math.SQRT2'); return processedCode; } private postMessage(response: WorkerResponse): void { self.postMessage(response); } private postError(id: string, error: string): void { this.postMessage({ id, type: 'error', success: false, error }); } } // Initialize worker new ShaderWorker();