Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

fix: parse numeric atoms in parser (enables timing form interpolation)

+190 -2
+5 -2
oven/kidlisp-mini/eval.mjs
··· 114 114 advance(); 115 115 return { type: 'timing-once', ms: t.value }; 116 116 } else { 117 - // Regular atom 117 + // Regular atom - try to parse as number 118 118 const token = advance(); 119 - return token.value; 119 + const value = token.value; 120 + // Try to parse as float, fallback to string 121 + const num = parseFloat(value); 122 + return isNaN(num) ? value : num; 120 123 } 121 124 } 122 125
+92
oven/test_animation_frames.mjs
··· 1 + // Test animation frame progression and timing 2 + import { KidLispMini, parse } from './kidlisp-mini/eval.mjs'; 3 + import { TimingEngine } from './kidlisp-mini/timing.mjs'; 4 + 5 + const source = `fade:red-blue-black-blue-red 6 + (ink (? rainbow white 0) (1s... 24 64)) 7 + (line w/2 0 w/2 h) 8 + (spin (2s... -1.125 1.125)) 9 + (zoom 1.1) 10 + (0.5s (contrast 1.05)) 11 + (scroll (? -0.1 0 0.1) (? -0.1 0 0.1)) 12 + (ink (? cyan yellow magenta) 8) 13 + (circle w/2 h/2 (? 2 4 8))`; 14 + 15 + const ast = parse(source); 16 + const kidlisp = new KidLispMini(); 17 + const timing = new TimingEngine(42); // seed 18 + 19 + // Track API calls 20 + const apiCalls = []; 21 + const api = { 22 + screen: { width: 1280, height: 800 }, 23 + wipe: (r, g, b, a) => apiCalls.push({ fn: 'wipe', args: [r, g, b, a] }), 24 + ink: (r, g, b, a) => apiCalls.push({ fn: 'ink', args: [r, g, b, a] }), 25 + line: (x0, y0, x1, y1) => apiCalls.push({ fn: 'line', args: [x0, y0, x1, y1] }), 26 + circle: (cx, cy, r, mode) => apiCalls.push({ fn: 'circle', args: [cx, cy, r, mode] }), 27 + fadeBackground: (colors, frame) => apiCalls.push({ fn: 'fadeBackground', colorCount: colors.length, frame }), 28 + scroll: (dx, dy) => apiCalls.push({ fn: 'scroll', args: [dx, dy] }), 29 + spin: (angle) => apiCalls.push({ fn: 'spin', args: [angle] }), 30 + zoom: (factor) => apiCalls.push({ fn: 'zoom', args: [factor] }), 31 + contrast: (factor) => apiCalls.push({ fn: 'contrast', args: [factor] }), 32 + }; 33 + 34 + kidlisp.setApi(api); 35 + 36 + console.log('=== DEBUG: API and Environment ==='); 37 + console.log('API set:', !!kidlisp.api); 38 + console.log('API.ink:', !!kidlisp.api?.ink); 39 + console.log('globalEnv.ink:', !!kidlisp.globalEnv.ink); 40 + console.log(); 41 + 42 + console.log('=== FRAME-BY-FRAME ANIMATION TEST ===\n'); 43 + 44 + // Test frames 0, 30 (0.5s at 60fps), 60 (1s), 120 (2s) 45 + const testFrames = [0, 30, 60, 120]; 46 + 47 + testFrames.forEach(frameNum => { 48 + apiCalls.length = 0; // reset 49 + kidlisp.frameCount = frameNum; 50 + 51 + console.log(`Frame ${frameNum} (${(frameNum/60).toFixed(2)}s):`); 52 + 53 + // Evaluate only key expressions to see what's happening 54 + // expr 1: ink with timing (1s... 24 64) 55 + const inkExpr = ast[1]; 56 + const inkResult = kidlisp.evalExpr(inkExpr, kidlisp.globalEnv, api, frameNum); 57 + console.log(` ink evalExpr result: ${inkResult}`); 58 + const inkCall = apiCalls.find(c => c.fn === 'ink'); 59 + console.log(` ink API called: ${!!inkCall}`); 60 + console.log(` ink alpha arg: ${inkCall?.args[3]} (interpolating 24→64 over 1s)`); 61 + 62 + // expr 3: spin with timing (2s... -1.125 1.125) 63 + apiCalls.length = 0; 64 + const spinExpr = ast[3]; 65 + const spinResult = kidlisp.evalExpr(spinExpr, kidlisp.globalEnv, api, frameNum); 66 + const spinCall = apiCalls.find(c => c.fn === 'spin'); 67 + const expectedSpin = -1.125 + ((1.125 - (-1.125)) * ((frameNum * 16) % 2000) / 2000); 68 + const spinAngle = spinCall?.args[0]; 69 + console.log(` spin API called: ${!!spinCall}`); 70 + console.log(` spin angle: ${typeof spinAngle === 'number' ? spinAngle.toFixed(4) : spinAngle} (expected: ${expectedSpin.toFixed(4)})`); 71 + 72 + // expr 5: 0.5s (contrast 1.05) 73 + apiCalls.length = 0; 74 + const contrastExpr = ast[5]; 75 + const contrastResult = kidlisp.evalExpr(contrastExpr, kidlisp.globalEnv, api, frameNum); 76 + console.log(` 0.5s contrast result: ${contrastResult?.toFixed(4)} (should be 0→1 over 0.5s)`); 77 + 78 + console.log(); 79 + }); 80 + 81 + console.log('=== TIMING FORM EVALUATION ==='); 82 + console.log('Checking timing form handling...'); 83 + 84 + // Check if timing form [timing, startVal, endVal] is being evaluated 85 + const testTimingExpr = [{ type: 'timing-repeating', ms: 1000 }, 24, 64]; 86 + console.log('Test expr: [timing-repeating(1000ms), 24, 64]'); 87 + for (let f = 0; f <= 60; f += 15) { 88 + const result = kidlisp.evalExpr(testTimingExpr, kidlisp.globalEnv, api, f); 89 + const progress = (f * 16) % 1000 / 1000; 90 + const expected = 24 + (64 - 24) * progress; 91 + console.log(` Frame ${f}: result=${result?.toFixed(1)}, expected=${expected.toFixed(1)}`); 92 + }
+46
oven/test_ink_fix.mjs
··· 1 + // Test ink function with new color+alpha pattern 2 + import { KidLispMini, parse } from './kidlisp-mini/eval.mjs'; 3 + 4 + const source = `(ink (? white black) (1s... 32 224)) 5 + (circle w/2 h/2 100)`; 6 + 7 + const ast = parse(source); 8 + const kidlisp = new KidLispMini(); 9 + 10 + // Mock API to track calls 11 + const calls = []; 12 + const api = { 13 + screen: { width: 1280, height: 800 }, 14 + ink: (...args) => { 15 + calls.push({ fn: 'ink', args }); 16 + console.log(` ink(${args.map(a => Array.isArray(a) ? `[${a}]` : a).join(', ')})`); 17 + }, 18 + circle: (cx, cy, r) => { 19 + calls.push({ fn: 'circle', args: [cx, cy, r] }); 20 + console.log(` circle(${cx}, ${cy}, ${r})`); 21 + }, 22 + }; 23 + 24 + kidlisp.setApi(api); 25 + 26 + console.log('=== TEST: ink with color + alpha override ===\n'); 27 + 28 + // Test frames 29 + for (let frame of [0, 30, 60]) { 30 + calls.length = 0; 31 + console.log(`Frame ${frame} (${(frame/60).toFixed(2)}s):`); 32 + 33 + kidlisp.frameCount = frame; 34 + for (const expr of ast) { 35 + kidlisp.evalExpr(expr, kidlisp.globalEnv, api, frame); 36 + } 37 + 38 + const inkCall = calls.find(c => c.fn === 'ink'); 39 + if (inkCall) { 40 + const [color, alpha] = inkCall.args; 41 + console.log(` → Color: ${Array.isArray(color) ? `[${color.join(',')}]` : color}, Alpha: ${alpha}`); 42 + } 43 + console.log(); 44 + } 45 + 46 + console.log('✅ ink pattern test complete');
+47
oven/test_spin_timing.mjs
··· 1 + // Test spin with timing form 2 + import { KidLispMini, parse } from './kidlisp-mini/eval.mjs'; 3 + 4 + const source = `(spin (2s... -1.125 1.125))`; 5 + 6 + const ast = parse(source); 7 + console.log('Parsed AST:', JSON.stringify(ast, null, 2)); 8 + 9 + const kidlisp = new KidLispMini(); 10 + const calls = []; 11 + const api = { 12 + screen: { width: 1280, height: 800 }, 13 + spin: (angle) => { 14 + calls.push({ fn: 'spin', angle }); 15 + console.log(` spin(${angle.toFixed(4)})`); 16 + }, 17 + }; 18 + 19 + kidlisp.setApi(api); 20 + 21 + console.log('\n=== TEST: spin with timing form ===\n'); 22 + 23 + // Test frames at different points in the 2-second cycle 24 + const testFrames = [0, 30, 60, 120]; 25 + 26 + testFrames.forEach(frame => { 27 + calls.length = 0; 28 + const ms = 2000; 29 + const progress = (frame * 16) % ms / ms; 30 + const expectedAngle = -1.125 + (1.125 - (-1.125)) * progress; 31 + 32 + console.log(`Frame ${frame} (${(frame/60).toFixed(2)}s, progress=${progress.toFixed(3)}):`); 33 + 34 + kidlisp.frameCount = frame; 35 + const expr = ast[0]; 36 + console.log(` Expr: ${JSON.stringify(expr)}`); 37 + kidlisp.evalExpr(expr, kidlisp.globalEnv, api, frame); 38 + 39 + const call = calls[0]; 40 + if (call) { 41 + console.log(` Result: ${call.angle.toFixed(4)} (expected: ${expectedAngle.toFixed(4)})`); 42 + if (Math.abs(call.angle - expectedAngle) > 0.01) { 43 + console.log(` ⚠️ OFF BY ${(call.angle - expectedAngle).toFixed(4)}`); 44 + } 45 + } 46 + console.log(); 47 + });