@nexart/codemode-sdk 1.8.1 → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/LICENSE.md +5 -5
  3. package/README.md +72 -11
  4. package/dist/sdk/codemode/core-index.d.ts +6 -4
  5. package/dist/sdk/codemode/core-index.d.ts.map +1 -1
  6. package/dist/sdk/codemode/core-index.js +6 -4
  7. package/dist/{entry → sdk/codemode/entry}/browser.d.ts +5 -2
  8. package/dist/sdk/codemode/entry/browser.d.ts.map +1 -0
  9. package/dist/{entry → sdk/codemode/entry}/browser.js +10 -4
  10. package/dist/{entry → sdk/codemode/entry}/node.d.ts +2 -1
  11. package/dist/sdk/codemode/entry/node.d.ts.map +1 -0
  12. package/dist/{entry → sdk/codemode/entry}/node.js +2 -1
  13. package/dist/{runtime.d.ts → sdk/codemode/runtime.d.ts} +2 -2
  14. package/dist/sdk/codemode/runtime.d.ts.map +1 -0
  15. package/dist/{runtime.js → sdk/codemode/runtime.js} +3 -2
  16. package/dist/sdk/codemode/static-engine.d.ts.map +1 -1
  17. package/dist/sdk/codemode/static-engine.js +24 -11
  18. package/dist/sdk/codemode/version.d.ts +18 -0
  19. package/dist/sdk/codemode/version.d.ts.map +1 -0
  20. package/dist/sdk/codemode/version.js +17 -0
  21. package/examples/agent-quickstart.ts +159 -0
  22. package/examples/preflight-test.ts +1 -1
  23. package/package.json +2 -1
  24. package/dist/builder-manifest.d.ts +0 -79
  25. package/dist/builder-manifest.d.ts.map +0 -1
  26. package/dist/builder-manifest.js +0 -97
  27. package/dist/core-index.d.ts +0 -21
  28. package/dist/core-index.d.ts.map +0 -1
  29. package/dist/core-index.js +0 -26
  30. package/dist/engine.d.ts +0 -24
  31. package/dist/engine.d.ts.map +0 -1
  32. package/dist/engine.js +0 -67
  33. package/dist/entry/browser.d.ts.map +0 -1
  34. package/dist/entry/node.d.ts.map +0 -1
  35. package/dist/execute.d.ts +0 -46
  36. package/dist/execute.d.ts.map +0 -1
  37. package/dist/execute.js +0 -283
  38. package/dist/execution-sandbox.d.ts +0 -107
  39. package/dist/execution-sandbox.d.ts.map +0 -1
  40. package/dist/execution-sandbox.js +0 -207
  41. package/dist/index.d.ts +0 -31
  42. package/dist/index.d.ts.map +0 -1
  43. package/dist/index.js +0 -63
  44. package/dist/loop-engine.d.ts +0 -22
  45. package/dist/loop-engine.d.ts.map +0 -1
  46. package/dist/loop-engine.js +0 -229
  47. package/dist/noise-bridge.d.ts +0 -44
  48. package/dist/noise-bridge.d.ts.map +0 -1
  49. package/dist/noise-bridge.js +0 -68
  50. package/dist/noise-engine.d.ts +0 -74
  51. package/dist/noise-engine.d.ts.map +0 -1
  52. package/dist/noise-engine.js +0 -132
  53. package/dist/noise-sketches/fractalNoise.d.ts +0 -11
  54. package/dist/noise-sketches/fractalNoise.d.ts.map +0 -1
  55. package/dist/noise-sketches/fractalNoise.js +0 -121
  56. package/dist/noise-sketches/index.d.ts +0 -21
  57. package/dist/noise-sketches/index.d.ts.map +0 -1
  58. package/dist/noise-sketches/index.js +0 -28
  59. package/dist/p5-runtime.d.ts +0 -75
  60. package/dist/p5-runtime.d.ts.map +0 -1
  61. package/dist/p5-runtime.js +0 -1031
  62. package/dist/runtime.d.ts.map +0 -1
  63. package/dist/sdk/codemode/index.d.ts +0 -31
  64. package/dist/sdk/codemode/index.d.ts.map +0 -1
  65. package/dist/sdk/codemode/index.js +0 -63
  66. package/dist/sdk/codemode/noise-bridge.d.ts +0 -44
  67. package/dist/sdk/codemode/noise-bridge.d.ts.map +0 -1
  68. package/dist/sdk/codemode/noise-bridge.js +0 -68
  69. package/dist/sdk/codemode/noise-engine.d.ts +0 -74
  70. package/dist/sdk/codemode/noise-engine.d.ts.map +0 -1
  71. package/dist/sdk/codemode/noise-engine.js +0 -132
  72. package/dist/sdk/codemode/noise-sketches/fractalNoise.d.ts +0 -11
  73. package/dist/sdk/codemode/noise-sketches/fractalNoise.d.ts.map +0 -1
  74. package/dist/sdk/codemode/noise-sketches/fractalNoise.js +0 -121
  75. package/dist/sdk/codemode/noise-sketches/index.d.ts +0 -21
  76. package/dist/sdk/codemode/noise-sketches/index.d.ts.map +0 -1
  77. package/dist/sdk/codemode/noise-sketches/index.js +0 -28
  78. package/dist/shared/noiseSnapshot.d.ts +0 -59
  79. package/dist/shared/noiseSnapshot.d.ts.map +0 -1
  80. package/dist/shared/noiseSnapshot.js +0 -72
  81. package/dist/sound-bridge.d.ts +0 -89
  82. package/dist/sound-bridge.d.ts.map +0 -1
  83. package/dist/sound-bridge.js +0 -128
  84. package/dist/soundart-engine.d.ts +0 -87
  85. package/dist/soundart-engine.d.ts.map +0 -1
  86. package/dist/soundart-engine.js +0 -173
  87. package/dist/soundart-sketches/chladniBloom.d.ts +0 -3
  88. package/dist/soundart-sketches/chladniBloom.d.ts.map +0 -1
  89. package/dist/soundart-sketches/chladniBloom.js +0 -53
  90. package/dist/soundart-sketches/dualVortex.d.ts +0 -3
  91. package/dist/soundart-sketches/dualVortex.d.ts.map +0 -1
  92. package/dist/soundart-sketches/dualVortex.js +0 -67
  93. package/dist/soundart-sketches/geometryIllusion.d.ts +0 -3
  94. package/dist/soundart-sketches/geometryIllusion.d.ts.map +0 -1
  95. package/dist/soundart-sketches/geometryIllusion.js +0 -89
  96. package/dist/soundart-sketches/index.d.ts +0 -39
  97. package/dist/soundart-sketches/index.d.ts.map +0 -1
  98. package/dist/soundart-sketches/index.js +0 -72
  99. package/dist/soundart-sketches/isoflow.d.ts +0 -3
  100. package/dist/soundart-sketches/isoflow.d.ts.map +0 -1
  101. package/dist/soundart-sketches/isoflow.js +0 -60
  102. package/dist/soundart-sketches/loomWeave.d.ts +0 -3
  103. package/dist/soundart-sketches/loomWeave.d.ts.map +0 -1
  104. package/dist/soundart-sketches/loomWeave.js +0 -59
  105. package/dist/soundart-sketches/noiseTerraces.d.ts +0 -3
  106. package/dist/soundart-sketches/noiseTerraces.d.ts.map +0 -1
  107. package/dist/soundart-sketches/noiseTerraces.js +0 -53
  108. package/dist/soundart-sketches/orb.d.ts +0 -3
  109. package/dist/soundart-sketches/orb.d.ts.map +0 -1
  110. package/dist/soundart-sketches/orb.js +0 -50
  111. package/dist/soundart-sketches/pixelGlyphs.d.ts +0 -3
  112. package/dist/soundart-sketches/pixelGlyphs.d.ts.map +0 -1
  113. package/dist/soundart-sketches/pixelGlyphs.js +0 -72
  114. package/dist/soundart-sketches/prismFlowFields.d.ts +0 -3
  115. package/dist/soundart-sketches/prismFlowFields.d.ts.map +0 -1
  116. package/dist/soundart-sketches/prismFlowFields.js +0 -51
  117. package/dist/soundart-sketches/radialBurst.d.ts +0 -3
  118. package/dist/soundart-sketches/radialBurst.d.ts.map +0 -1
  119. package/dist/soundart-sketches/radialBurst.js +0 -60
  120. package/dist/soundart-sketches/resonantSoundBodies.d.ts +0 -3
  121. package/dist/soundart-sketches/resonantSoundBodies.d.ts.map +0 -1
  122. package/dist/soundart-sketches/resonantSoundBodies.js +0 -89
  123. package/dist/soundart-sketches/rings.d.ts +0 -11
  124. package/dist/soundart-sketches/rings.d.ts.map +0 -1
  125. package/dist/soundart-sketches/rings.js +0 -89
  126. package/dist/soundart-sketches/squares.d.ts +0 -3
  127. package/dist/soundart-sketches/squares.d.ts.map +0 -1
  128. package/dist/soundart-sketches/squares.js +0 -52
  129. package/dist/soundart-sketches/waveStripes.d.ts +0 -3
  130. package/dist/soundart-sketches/waveStripes.d.ts.map +0 -1
  131. package/dist/soundart-sketches/waveStripes.js +0 -44
  132. package/dist/static-engine.d.ts +0 -20
  133. package/dist/static-engine.d.ts.map +0 -1
  134. package/dist/static-engine.js +0 -157
  135. package/dist/types.d.ts +0 -191
  136. package/dist/types.d.ts.map +0 -1
  137. package/dist/types.js +0 -32
@@ -1,1031 +0,0 @@
1
- /**
2
- * NexArt Code Mode Runtime SDK - p5-like Runtime
3
- *
4
- * ╔══════════════════════════════════════════════════════════════════════════╗
5
- * ║ CODE MODE PROTOCOL v1.2.0 (Phase 3) — STABLE ║
6
- * ║ ║
7
- * ║ Status: HARD PROTOCOL ENFORCEMENT ║
8
- * ║ This is the stable, canonical execution surface. ║
9
- * ║ SDKs, ByX, and external builders can depend on this API. ║
10
- * ║ ║
11
- * ║ Phase 1 Surface: ║
12
- * ║ - VAR[0..9]: 10 read-only protocol variables (0-100 range) ║
13
- * ║ - Drawing: line, rect, ellipse, circle, triangle, quad, arc, etc. ║
14
- * ║ - Style: fill, stroke, colorMode, strokeWeight ║
15
- * ║ - Transform: push, pop, translate, rotate, scale ║
16
- * ║ - Random: random(), randomSeed(), randomGaussian() (seeded) ║
17
- * ║ - Noise: noise(), noiseSeed(), noiseDetail() (seeded) ║
18
- * ║ - Math: map, constrain, lerp, lerpColor, dist, mag, norm ║
19
- * ║ - Color: Full CSS format support, color extraction functions ║
20
- * ║ - Time: frameCount, t, time, tGlobal ║
21
- * ║ ║
22
- * ║ Determinism Guarantees: ║
23
- * ║ - Same code + same seed + same VARs = identical output ║
24
- * ║ - No external state, no browser entropy, no time-based drift ║
25
- * ║ - Randomness ONLY from: random(), noise() (both seeded) ║
26
- * ║ ║
27
- * ║ ⚠️ Future changes require Phase 2+ ║
28
- * ╚══════════════════════════════════════════════════════════════════════════╝
29
- */
30
- /**
31
- * Code Mode Protocol Version
32
- * This constant defines the locked protocol version.
33
- * Changes to the execution surface require a version bump.
34
- */
35
- export const CODE_MODE_PROTOCOL_VERSION = '1.2.0';
36
- export const CODE_MODE_PROTOCOL_PHASE = 3;
37
- export const CODE_MODE_ENFORCEMENT = 'HARD';
38
- /**
39
- * Create a seeded random number generator (Mulberry32)
40
- * Same algorithm used in SoundArt for consistency
41
- */
42
- function createSeededRNG(seed = 123456) {
43
- let a = seed >>> 0;
44
- return () => {
45
- a += 0x6D2B79F5;
46
- let t = Math.imul(a ^ (a >>> 15), a | 1);
47
- t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
48
- return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
49
- };
50
- }
51
- /**
52
- * Create improved Perlin-like noise with seeding support
53
- */
54
- function createSeededNoise(seed = 0) {
55
- const permutation = [];
56
- const rng = createSeededRNG(seed);
57
- for (let i = 0; i < 256; i++) {
58
- permutation[i] = i;
59
- }
60
- for (let i = 255; i > 0; i--) {
61
- const j = Math.floor(rng() * (i + 1));
62
- [permutation[i], permutation[j]] = [permutation[j], permutation[i]];
63
- }
64
- for (let i = 0; i < 256; i++) {
65
- permutation[256 + i] = permutation[i];
66
- }
67
- const fade = (t) => t * t * t * (t * (t * 6 - 15) + 10);
68
- const lerp = (a, b, t) => a + t * (b - a);
69
- const grad = (hash, x, y, z) => {
70
- const h = hash & 15;
71
- const u = h < 8 ? x : y;
72
- const v = h < 4 ? y : h === 12 || h === 14 ? x : z;
73
- return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
74
- };
75
- return (x, y = 0, z = 0) => {
76
- const X = Math.floor(x) & 255;
77
- const Y = Math.floor(y) & 255;
78
- const Z = Math.floor(z) & 255;
79
- x -= Math.floor(x);
80
- y -= Math.floor(y);
81
- z -= Math.floor(z);
82
- const u = fade(x);
83
- const v = fade(y);
84
- const w = fade(z);
85
- const A = permutation[X] + Y;
86
- const AA = permutation[A] + Z;
87
- const AB = permutation[A + 1] + Z;
88
- const B = permutation[X + 1] + Y;
89
- const BA = permutation[B] + Z;
90
- const BB = permutation[B + 1] + Z;
91
- return (lerp(lerp(lerp(grad(permutation[AA], x, y, z), grad(permutation[BA], x - 1, y, z), u), lerp(grad(permutation[AB], x, y - 1, z), grad(permutation[BB], x - 1, y - 1, z), u), v), lerp(lerp(grad(permutation[AA + 1], x, y, z - 1), grad(permutation[BA + 1], x - 1, y, z - 1), u), lerp(grad(permutation[AB + 1], x, y - 1, z - 1), grad(permutation[BB + 1], x - 1, y - 1, z - 1), u), v), w) + 1) / 2;
92
- };
93
- }
94
- export function createP5Runtime(canvas, width, height, config) {
95
- const ctx = canvas.getContext('2d', { willReadFrequently: true });
96
- if (!ctx)
97
- throw new Error('Failed to get 2D context');
98
- let currentFill = 'rgba(255, 255, 255, 1)';
99
- let currentStroke = 'rgba(0, 0, 0, 1)';
100
- let strokeEnabled = true;
101
- let fillEnabled = true;
102
- let currentStrokeWeight = 1;
103
- let colorModeSettings = { mode: 'RGB', maxR: 255, maxG: 255, maxB: 255, maxA: 255 };
104
- let shapeStarted = false;
105
- let shapeVertices = [];
106
- // Pixel array for pixel manipulation
107
- let pixelData = null;
108
- let imageDataObj = null;
109
- // Text state
110
- let currentTextSize = 12;
111
- let currentTextFont = 'sans-serif';
112
- let currentTextAlignH = 'left';
113
- let currentTextAlignV = 'alphabetic';
114
- // Seeded random state
115
- let randomSeedValue = config?.seed ?? Math.floor(Math.random() * 2147483647);
116
- let rng = createSeededRNG(randomSeedValue);
117
- // Seeded noise state
118
- let noiseSeedValue = config?.seed ?? 0;
119
- let noiseFunc = createSeededNoise(noiseSeedValue);
120
- let noiseOctaves = 4;
121
- let noiseFalloff = 0.5;
122
- /**
123
- * Parse CSS color string to normalized RGBA values
124
- * Supports: hex (#RGB, #RRGGBB, #RRGGBBAA), rgb(), rgba(), hsl(), hsla()
125
- */
126
- const parseCssColor = (str) => {
127
- const s = str.trim();
128
- // Hex format: #RGB, #RRGGBB, #RRGGBBAA
129
- if (s.startsWith('#')) {
130
- const hex = s.slice(1);
131
- if (hex.length === 3) {
132
- const r = parseInt(hex[0] + hex[0], 16);
133
- const g = parseInt(hex[1] + hex[1], 16);
134
- const b = parseInt(hex[2] + hex[2], 16);
135
- return { r, g, b, a: 1 };
136
- }
137
- else if (hex.length === 6) {
138
- const r = parseInt(hex.slice(0, 2), 16);
139
- const g = parseInt(hex.slice(2, 4), 16);
140
- const b = parseInt(hex.slice(4, 6), 16);
141
- return { r, g, b, a: 1 };
142
- }
143
- else if (hex.length === 8) {
144
- const r = parseInt(hex.slice(0, 2), 16);
145
- const g = parseInt(hex.slice(2, 4), 16);
146
- const b = parseInt(hex.slice(4, 6), 16);
147
- const a = parseInt(hex.slice(6, 8), 16) / 255;
148
- return { r, g, b, a };
149
- }
150
- }
151
- // rgb(r, g, b) or rgb(r g b)
152
- const rgbMatch = s.match(/^rgb\s*\(\s*(\d+)\s*[,\s]\s*(\d+)\s*[,\s]\s*(\d+)\s*\)$/i);
153
- if (rgbMatch) {
154
- return {
155
- r: parseInt(rgbMatch[1]),
156
- g: parseInt(rgbMatch[2]),
157
- b: parseInt(rgbMatch[3]),
158
- a: 1
159
- };
160
- }
161
- // rgba(r, g, b, a) or rgba(r g b / a)
162
- const rgbaMatch = s.match(/^rgba\s*\(\s*(\d+)\s*[,\s]\s*(\d+)\s*[,\s]\s*(\d+)\s*[,\/\s]\s*([\d.]+)\s*\)$/i);
163
- if (rgbaMatch) {
164
- return {
165
- r: parseInt(rgbaMatch[1]),
166
- g: parseInt(rgbaMatch[2]),
167
- b: parseInt(rgbaMatch[3]),
168
- a: parseFloat(rgbaMatch[4])
169
- };
170
- }
171
- // hsl(h, s%, l%) or hsla(h, s%, l%, a)
172
- const hslMatch = s.match(/^hsla?\s*\(\s*([\d.]+)\s*[,\s]\s*([\d.]+)%?\s*[,\s]\s*([\d.]+)%?\s*(?:[,\/\s]\s*([\d.]+))?\s*\)$/i);
173
- if (hslMatch) {
174
- const h = parseFloat(hslMatch[1]) / 360;
175
- const sat = parseFloat(hslMatch[2]) / 100;
176
- const l = parseFloat(hslMatch[3]) / 100;
177
- const a = hslMatch[4] ? parseFloat(hslMatch[4]) : 1;
178
- // HSL to RGB conversion
179
- let r, g, b;
180
- if (sat === 0) {
181
- r = g = b = l;
182
- }
183
- else {
184
- const hue2rgb = (p, q, t) => {
185
- if (t < 0)
186
- t += 1;
187
- if (t > 1)
188
- t -= 1;
189
- if (t < 1 / 6)
190
- return p + (q - p) * 6 * t;
191
- if (t < 1 / 2)
192
- return q;
193
- if (t < 2 / 3)
194
- return p + (q - p) * (2 / 3 - t) * 6;
195
- return p;
196
- };
197
- const q = l < 0.5 ? l * (1 + sat) : l + sat - l * sat;
198
- const p = 2 * l - q;
199
- r = hue2rgb(p, q, h + 1 / 3);
200
- g = hue2rgb(p, q, h);
201
- b = hue2rgb(p, q, h - 1 / 3);
202
- }
203
- return {
204
- r: Math.round(r * 255),
205
- g: Math.round(g * 255),
206
- b: Math.round(b * 255),
207
- a
208
- };
209
- }
210
- return null;
211
- };
212
- const parseColor = (...args) => {
213
- if (args.length === 0)
214
- return 'rgba(0, 0, 0, 1)';
215
- const { mode, maxR, maxG, maxB, maxA } = colorModeSettings;
216
- if (args.length === 1) {
217
- const val = args[0];
218
- if (typeof val === 'string') {
219
- // Try to parse CSS color formats
220
- const parsed = parseCssColor(val);
221
- if (parsed) {
222
- return `rgba(${parsed.r}, ${parsed.g}, ${parsed.b}, ${parsed.a})`;
223
- }
224
- // Return as-is for named colors (canvas handles them)
225
- return val;
226
- }
227
- if (mode === 'HSB') {
228
- return `hsla(${val}, 100%, 50%, 1)`;
229
- }
230
- const gray = Math.round((val / maxR) * 255);
231
- return `rgba(${gray}, ${gray}, ${gray}, 1)`;
232
- }
233
- if (args.length === 2) {
234
- const [gray, alpha] = args;
235
- const g = Math.round((gray / maxR) * 255);
236
- const a = alpha / maxA;
237
- return `rgba(${g}, ${g}, ${g}, ${a})`;
238
- }
239
- if (args.length === 3) {
240
- const [r, g, b] = args;
241
- if (mode === 'HSB') {
242
- return `hsla(${(r / maxR) * 360}, ${(g / maxG) * 100}%, ${(b / maxB) * 100}%, 1)`;
243
- }
244
- return `rgba(${Math.round((r / maxR) * 255)}, ${Math.round((g / maxG) * 255)}, ${Math.round((b / maxB) * 255)}, 1)`;
245
- }
246
- if (args.length === 4) {
247
- const [r, g, b, a] = args;
248
- if (mode === 'HSB') {
249
- return `hsla(${(r / maxR) * 360}, ${(g / maxG) * 100}%, ${(b / maxB) * 100}%, ${a / maxA})`;
250
- }
251
- return `rgba(${Math.round((r / maxR) * 255)}, ${Math.round((g / maxG) * 255)}, ${Math.round((b / maxB) * 255)}, ${a / maxA})`;
252
- }
253
- return 'rgba(0, 0, 0, 1)';
254
- };
255
- const p = {
256
- width,
257
- height,
258
- frameCount: 0,
259
- // Constants
260
- PI: Math.PI,
261
- TWO_PI: Math.PI * 2,
262
- TAU: Math.PI * 2,
263
- HALF_PI: Math.PI / 2,
264
- QUARTER_PI: Math.PI / 4,
265
- // Shape mode constants
266
- CORNER: 'corner',
267
- CENTER: 'center',
268
- CORNERS: 'corners',
269
- RADIUS: 'radius',
270
- ROUND: 'round',
271
- SQUARE: 'butt',
272
- PROJECT: 'square',
273
- MITER: 'miter',
274
- BEVEL: 'bevel',
275
- CLOSE: 'close',
276
- PIE: 'pie',
277
- CHORD: 'chord',
278
- OPEN: 'open',
279
- // Blend mode constants (v1.1)
280
- NORMAL: 'source-over',
281
- ADD: 'lighter',
282
- MULTIPLY: 'multiply',
283
- SCREEN: 'screen',
284
- // Text alignment constants
285
- LEFT: 'left',
286
- RIGHT: 'right',
287
- TOP: 'top',
288
- BOTTOM: 'bottom',
289
- BASELINE: 'alphabetic',
290
- // Canvas operations
291
- background: (...args) => {
292
- ctx.save();
293
- ctx.fillStyle = parseColor(...args);
294
- ctx.fillRect(0, 0, width, height);
295
- ctx.restore();
296
- },
297
- clear: () => {
298
- ctx.clearRect(0, 0, width, height);
299
- },
300
- blendMode: (mode) => {
301
- const modeMap = {
302
- 'source-over': 'source-over',
303
- 'NORMAL': 'source-over',
304
- 'lighter': 'lighter',
305
- 'ADD': 'lighter',
306
- 'multiply': 'multiply',
307
- 'MULTIPLY': 'multiply',
308
- 'screen': 'screen',
309
- 'SCREEN': 'screen',
310
- };
311
- const compositeOp = modeMap[mode];
312
- if (!compositeOp) {
313
- throw new Error(`[Code Mode Protocol Error] Unsupported blend mode: ${mode}. Supported: NORMAL, ADD, MULTIPLY, SCREEN`);
314
- }
315
- ctx.globalCompositeOperation = compositeOp;
316
- },
317
- // Color functions
318
- fill: (...args) => {
319
- fillEnabled = true;
320
- currentFill = parseColor(...args);
321
- ctx.fillStyle = currentFill;
322
- },
323
- noFill: () => {
324
- fillEnabled = false;
325
- },
326
- stroke: (...args) => {
327
- strokeEnabled = true;
328
- currentStroke = parseColor(...args);
329
- ctx.strokeStyle = currentStroke;
330
- },
331
- noStroke: () => {
332
- strokeEnabled = false;
333
- },
334
- strokeWeight: (weight) => {
335
- currentStrokeWeight = weight;
336
- ctx.lineWidth = weight;
337
- },
338
- strokeCap: (cap) => {
339
- const capMap = {
340
- 'round': 'round',
341
- 'ROUND': 'round',
342
- 'square': 'butt',
343
- 'SQUARE': 'butt',
344
- 'project': 'square',
345
- 'PROJECT': 'square',
346
- 'butt': 'butt',
347
- };
348
- ctx.lineCap = capMap[cap] || 'round';
349
- },
350
- strokeJoin: (join) => {
351
- const joinMap = {
352
- 'miter': 'miter',
353
- 'MITER': 'miter',
354
- 'bevel': 'bevel',
355
- 'BEVEL': 'bevel',
356
- 'round': 'round',
357
- 'ROUND': 'round',
358
- };
359
- ctx.lineJoin = joinMap[join] || 'miter';
360
- },
361
- colorMode: (mode, max1, max2, max3, maxA) => {
362
- colorModeSettings = {
363
- mode: mode.toUpperCase(),
364
- maxR: max1 ?? 255,
365
- maxG: max2 ?? max1 ?? 255,
366
- maxB: max3 ?? max1 ?? 255,
367
- maxA: maxA ?? 255,
368
- };
369
- },
370
- color: (...args) => parseColor(...args),
371
- lerpColor: (c1, c2, amt) => {
372
- // Parse both colors
373
- const color1 = parseCssColor(c1) || { r: 0, g: 0, b: 0, a: 1 };
374
- const color2 = parseCssColor(c2) || { r: 255, g: 255, b: 255, a: 1 };
375
- // Linearly interpolate each channel
376
- const r = Math.round(color1.r + (color2.r - color1.r) * amt);
377
- const g = Math.round(color1.g + (color2.g - color1.g) * amt);
378
- const b = Math.round(color1.b + (color2.b - color1.b) * amt);
379
- const a = color1.a + (color2.a - color1.a) * amt;
380
- return `rgba(${r}, ${g}, ${b}, ${a})`;
381
- },
382
- red: (color) => {
383
- const parsed = parseCssColor(color);
384
- return parsed ? parsed.r : 0;
385
- },
386
- green: (color) => {
387
- const parsed = parseCssColor(color);
388
- return parsed ? parsed.g : 0;
389
- },
390
- blue: (color) => {
391
- const parsed = parseCssColor(color);
392
- return parsed ? parsed.b : 0;
393
- },
394
- alpha: (color) => {
395
- const parsed = parseCssColor(color);
396
- return parsed ? parsed.a * 255 : 255;
397
- },
398
- brightness: (color) => {
399
- const parsed = parseCssColor(color);
400
- if (!parsed)
401
- return 0;
402
- return Math.max(parsed.r, parsed.g, parsed.b) / 255 * 100;
403
- },
404
- saturation: (color) => {
405
- const parsed = parseCssColor(color);
406
- if (!parsed)
407
- return 0;
408
- const max = Math.max(parsed.r, parsed.g, parsed.b);
409
- const min = Math.min(parsed.r, parsed.g, parsed.b);
410
- if (max === 0)
411
- return 0;
412
- return ((max - min) / max) * 100;
413
- },
414
- hue: (color) => {
415
- const parsed = parseCssColor(color);
416
- if (!parsed)
417
- return 0;
418
- const { r, g, b } = parsed;
419
- const max = Math.max(r, g, b);
420
- const min = Math.min(r, g, b);
421
- if (max === min)
422
- return 0;
423
- let h = 0;
424
- const d = max - min;
425
- switch (max) {
426
- case r:
427
- h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
428
- break;
429
- case g:
430
- h = ((b - r) / d + 2) / 6;
431
- break;
432
- case b:
433
- h = ((r - g) / d + 4) / 6;
434
- break;
435
- }
436
- return h * 360;
437
- },
438
- // Shape functions
439
- ellipse: (x, y, w, h) => {
440
- const rw = w / 2;
441
- const rh = (h ?? w) / 2;
442
- ctx.beginPath();
443
- ctx.ellipse(x, y, rw, rh, 0, 0, Math.PI * 2);
444
- if (fillEnabled)
445
- ctx.fill();
446
- if (strokeEnabled)
447
- ctx.stroke();
448
- },
449
- circle: (x, y, d) => {
450
- p.ellipse(x, y, d, d);
451
- },
452
- rect: (x, y, w, h, r) => {
453
- const height = h ?? w;
454
- ctx.beginPath();
455
- if (r && r > 0) {
456
- ctx.roundRect(x, y, w, height, r);
457
- }
458
- else {
459
- ctx.rect(x, y, w, height);
460
- }
461
- if (fillEnabled)
462
- ctx.fill();
463
- if (strokeEnabled)
464
- ctx.stroke();
465
- },
466
- square: (x, y, s, r) => {
467
- p.rect(x, y, s, s, r);
468
- },
469
- line: (x1, y1, x2, y2) => {
470
- ctx.beginPath();
471
- ctx.moveTo(x1, y1);
472
- ctx.lineTo(x2, y2);
473
- if (strokeEnabled)
474
- ctx.stroke();
475
- },
476
- point: (x, y) => {
477
- ctx.beginPath();
478
- ctx.arc(x, y, currentStrokeWeight / 2, 0, Math.PI * 2);
479
- ctx.fillStyle = currentStroke;
480
- ctx.fill();
481
- },
482
- triangle: (x1, y1, x2, y2, x3, y3) => {
483
- ctx.beginPath();
484
- ctx.moveTo(x1, y1);
485
- ctx.lineTo(x2, y2);
486
- ctx.lineTo(x3, y3);
487
- ctx.closePath();
488
- if (fillEnabled)
489
- ctx.fill();
490
- if (strokeEnabled)
491
- ctx.stroke();
492
- },
493
- quad: (x1, y1, x2, y2, x3, y3, x4, y4) => {
494
- ctx.beginPath();
495
- ctx.moveTo(x1, y1);
496
- ctx.lineTo(x2, y2);
497
- ctx.lineTo(x3, y3);
498
- ctx.lineTo(x4, y4);
499
- ctx.closePath();
500
- if (fillEnabled)
501
- ctx.fill();
502
- if (strokeEnabled)
503
- ctx.stroke();
504
- },
505
- arc: (x, y, w, h, start, stop, mode) => {
506
- ctx.beginPath();
507
- ctx.ellipse(x, y, w / 2, h / 2, 0, start, stop);
508
- if (mode === 'pie' || mode === 'PIE') {
509
- ctx.lineTo(x, y);
510
- ctx.closePath();
511
- }
512
- else if (mode === 'chord' || mode === 'CHORD') {
513
- ctx.closePath();
514
- }
515
- if (fillEnabled)
516
- ctx.fill();
517
- if (strokeEnabled)
518
- ctx.stroke();
519
- },
520
- // Bezier and curve functions
521
- bezier: (x1, y1, cx1, cy1, cx2, cy2, x2, y2) => {
522
- ctx.beginPath();
523
- ctx.moveTo(x1, y1);
524
- ctx.bezierCurveTo(cx1, cy1, cx2, cy2, x2, y2);
525
- if (strokeEnabled)
526
- ctx.stroke();
527
- },
528
- curve: (x1, y1, x2, y2, x3, y3, x4, y4) => {
529
- // Catmull-Rom spline conversion to cubic bezier
530
- // The curve is drawn from (x2,y2) to (x3,y3) using (x1,y1) and (x4,y4) as control points
531
- const tension = 1 / 6;
532
- const cp1x = x2 + (x3 - x1) * tension;
533
- const cp1y = y2 + (y3 - y1) * tension;
534
- const cp2x = x3 - (x4 - x2) * tension;
535
- const cp2y = y3 - (y4 - y2) * tension;
536
- ctx.beginPath();
537
- ctx.moveTo(x2, y2);
538
- ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x3, y3);
539
- if (strokeEnabled)
540
- ctx.stroke();
541
- },
542
- // Shape helpers (v1.1)
543
- polygon: (cx, cy, radius, sides, rotation = 0) => {
544
- ctx.beginPath();
545
- for (let i = 0; i < sides; i++) {
546
- const angle = rotation + (i / sides) * Math.PI * 2 - Math.PI / 2;
547
- const x = cx + Math.cos(angle) * radius;
548
- const y = cy + Math.sin(angle) * radius;
549
- if (i === 0) {
550
- ctx.moveTo(x, y);
551
- }
552
- else {
553
- ctx.lineTo(x, y);
554
- }
555
- }
556
- ctx.closePath();
557
- if (fillEnabled)
558
- ctx.fill();
559
- if (strokeEnabled)
560
- ctx.stroke();
561
- },
562
- star: (cx, cy, innerRadius, outerRadius, points, rotation = 0) => {
563
- ctx.beginPath();
564
- const totalPoints = points * 2;
565
- for (let i = 0; i < totalPoints; i++) {
566
- const angle = rotation + (i / totalPoints) * Math.PI * 2 - Math.PI / 2;
567
- const radius = i % 2 === 0 ? outerRadius : innerRadius;
568
- const x = cx + Math.cos(angle) * radius;
569
- const y = cy + Math.sin(angle) * radius;
570
- if (i === 0) {
571
- ctx.moveTo(x, y);
572
- }
573
- else {
574
- ctx.lineTo(x, y);
575
- }
576
- }
577
- ctx.closePath();
578
- if (fillEnabled)
579
- ctx.fill();
580
- if (strokeEnabled)
581
- ctx.stroke();
582
- },
583
- // Vertex-based shapes
584
- beginShape: () => {
585
- shapeVertices = [];
586
- shapeStarted = false;
587
- },
588
- vertex: (x, y) => {
589
- shapeVertices.push({ x, y, type: 'vertex' });
590
- },
591
- curveVertex: (x, y) => {
592
- shapeVertices.push({ x, y, type: 'curve' });
593
- },
594
- bezierVertex: (cx1, cy1, cx2, cy2, x, y) => {
595
- shapeVertices.push({ x, y, type: 'bezier', cx1, cy1, cx2, cy2 });
596
- },
597
- endShape: (mode) => {
598
- if (shapeVertices.length === 0)
599
- return;
600
- ctx.beginPath();
601
- // Process vertices in sequence, handling mixed types correctly
602
- let started = false;
603
- let curveBuffer = [];
604
- const tension = 1 / 6;
605
- const flushCurveBuffer = () => {
606
- if (curveBuffer.length >= 4) {
607
- // Draw Catmull-Rom spline from buffered curve vertices
608
- // First and last points are control points only
609
- if (!started) {
610
- ctx.moveTo(curveBuffer[1].x, curveBuffer[1].y);
611
- started = true;
612
- }
613
- else {
614
- ctx.lineTo(curveBuffer[1].x, curveBuffer[1].y);
615
- }
616
- for (let i = 1; i < curveBuffer.length - 2; i++) {
617
- const p0 = curveBuffer[i - 1];
618
- const p1 = curveBuffer[i];
619
- const p2 = curveBuffer[i + 1];
620
- const p3 = curveBuffer[i + 2];
621
- const cp1x = p1.x + (p2.x - p0.x) * tension;
622
- const cp1y = p1.y + (p2.y - p0.y) * tension;
623
- const cp2x = p2.x - (p3.x - p1.x) * tension;
624
- const cp2y = p2.y - (p3.y - p1.y) * tension;
625
- ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y);
626
- }
627
- }
628
- curveBuffer = [];
629
- };
630
- for (let i = 0; i < shapeVertices.length; i++) {
631
- const v = shapeVertices[i];
632
- if (v.type === 'curve') {
633
- curveBuffer.push({ x: v.x, y: v.y });
634
- }
635
- else {
636
- // Flush any pending curve vertices before processing non-curve vertex
637
- if (curveBuffer.length > 0) {
638
- flushCurveBuffer();
639
- }
640
- if (v.type === 'vertex') {
641
- if (!started) {
642
- ctx.moveTo(v.x, v.y);
643
- started = true;
644
- }
645
- else {
646
- ctx.lineTo(v.x, v.y);
647
- }
648
- }
649
- else if (v.type === 'bezier' && started) {
650
- ctx.bezierCurveTo(v.cx1, v.cy1, v.cx2, v.cy2, v.x, v.y);
651
- }
652
- }
653
- }
654
- // Flush any remaining curve vertices at the end
655
- if (curveBuffer.length > 0) {
656
- flushCurveBuffer();
657
- }
658
- if (mode === 'close' || mode === 'CLOSE') {
659
- ctx.closePath();
660
- }
661
- if (fillEnabled)
662
- ctx.fill();
663
- if (strokeEnabled)
664
- ctx.stroke();
665
- shapeVertices = [];
666
- shapeStarted = false;
667
- },
668
- // Transform functions
669
- push: () => {
670
- ctx.save();
671
- },
672
- pop: () => {
673
- ctx.restore();
674
- ctx.fillStyle = currentFill;
675
- ctx.strokeStyle = currentStroke;
676
- ctx.lineWidth = currentStrokeWeight;
677
- },
678
- translate: (x, y) => {
679
- ctx.translate(x, y);
680
- },
681
- rotate: (angle) => {
682
- ctx.rotate(angle);
683
- },
684
- scale: (sx, sy) => {
685
- ctx.scale(sx, sy ?? sx);
686
- },
687
- resetMatrix: () => {
688
- ctx.setTransform(1, 0, 0, 1, 0, 0);
689
- },
690
- shearX: (angle) => {
691
- ctx.transform(1, 0, Math.tan(angle), 1, 0, 0);
692
- },
693
- shearY: (angle) => {
694
- ctx.transform(1, Math.tan(angle), 0, 1, 0, 0);
695
- },
696
- // Math functions - SEEDED for determinism
697
- random: (min, max) => {
698
- // Support random() with arrays
699
- if (Array.isArray(min)) {
700
- return min[Math.floor(rng() * min.length)];
701
- }
702
- if (min === undefined)
703
- return rng();
704
- if (max === undefined)
705
- return rng() * min;
706
- return min + rng() * (max - min);
707
- },
708
- randomSeed: (seed) => {
709
- randomSeedValue = seed;
710
- rng = createSeededRNG(seed);
711
- },
712
- randomGaussian: (mean = 0, sd = 1) => {
713
- // Box-Muller transform for Gaussian distribution
714
- const u1 = rng();
715
- const u2 = rng();
716
- const z0 = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
717
- return z0 * sd + mean;
718
- },
719
- noise: (x, y, z) => {
720
- // Use seeded Perlin noise with octaves
721
- let total = 0;
722
- let frequency = 1;
723
- let amplitude = 1;
724
- let maxValue = 0;
725
- for (let i = 0; i < noiseOctaves; i++) {
726
- total += noiseFunc(x * frequency, (y ?? 0) * frequency, (z ?? 0) * frequency) * amplitude;
727
- maxValue += amplitude;
728
- amplitude *= noiseFalloff;
729
- frequency *= 2;
730
- }
731
- return total / maxValue;
732
- },
733
- noiseSeed: (seed) => {
734
- noiseSeedValue = seed;
735
- noiseFunc = createSeededNoise(seed);
736
- },
737
- noiseDetail: (lod, falloff) => {
738
- noiseOctaves = Math.max(1, Math.min(8, lod));
739
- if (falloff !== undefined) {
740
- noiseFalloff = Math.max(0, Math.min(1, falloff));
741
- }
742
- },
743
- // Noise extensions (v1.1) - use seeded noise internally
744
- fbm: (x, y, octaves = 4, falloff = 0.5) => {
745
- let total = 0;
746
- let frequency = 1;
747
- let amplitude = 1;
748
- let maxValue = 0;
749
- for (let i = 0; i < octaves; i++) {
750
- total += noiseFunc(x * frequency, y * frequency) * amplitude;
751
- maxValue += amplitude;
752
- amplitude *= falloff;
753
- frequency *= 2;
754
- }
755
- return total / maxValue;
756
- },
757
- ridgedNoise: (x, y) => {
758
- // Ridged noise: absolute value creates ridge-like patterns
759
- let total = 0;
760
- let frequency = 1;
761
- let amplitude = 1;
762
- let maxValue = 0;
763
- for (let i = 0; i < 4; i++) {
764
- const n = noiseFunc(x * frequency, y * frequency);
765
- total += (1 - Math.abs(n * 2 - 1)) * amplitude;
766
- maxValue += amplitude;
767
- amplitude *= 0.5;
768
- frequency *= 2;
769
- }
770
- return total / maxValue;
771
- },
772
- curlNoise: (x, y) => {
773
- // Curl noise: compute gradient and rotate 90 degrees
774
- const eps = 0.0001;
775
- const n1 = noiseFunc(x + eps, y);
776
- const n2 = noiseFunc(x - eps, y);
777
- const n3 = noiseFunc(x, y + eps);
778
- const n4 = noiseFunc(x, y - eps);
779
- const dx = (n1 - n2) / (2 * eps);
780
- const dy = (n3 - n4) / (2 * eps);
781
- // Rotate 90 degrees: (dx, dy) -> (-dy, dx)
782
- return { x: -dy, y: dx };
783
- },
784
- map: (value, start1, stop1, start2, stop2) => {
785
- return start2 + (stop2 - start2) * ((value - start1) / (stop1 - start1));
786
- },
787
- constrain: (n, low, high) => {
788
- return Math.max(low, Math.min(high, n));
789
- },
790
- lerp: (start, stop, amt) => {
791
- return start + (stop - start) * amt;
792
- },
793
- dist: (x1, y1, x2, y2) => {
794
- return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
795
- },
796
- mag: (x, y) => {
797
- return Math.sqrt(x * x + y * y);
798
- },
799
- norm: (value, start, stop) => {
800
- return (value - start) / (stop - start);
801
- },
802
- // Trig functions
803
- sin: Math.sin,
804
- cos: Math.cos,
805
- tan: Math.tan,
806
- asin: Math.asin,
807
- acos: Math.acos,
808
- atan: Math.atan,
809
- atan2: Math.atan2,
810
- radians: (degrees) => degrees * (Math.PI / 180),
811
- degrees: (radians) => radians * (180 / Math.PI),
812
- // Utility functions
813
- abs: Math.abs,
814
- ceil: Math.ceil,
815
- floor: Math.floor,
816
- round: Math.round,
817
- sqrt: Math.sqrt,
818
- pow: Math.pow,
819
- exp: Math.exp,
820
- log: Math.log,
821
- min: Math.min,
822
- max: Math.max,
823
- int: (n) => Math.floor(n),
824
- sq: (n) => n * n,
825
- fract: (n) => n - Math.floor(n),
826
- sign: (n) => n > 0 ? 1 : n < 0 ? -1 : 0,
827
- // Vector helpers (v1.1) - plain objects, no mutation
828
- vec: (x, y) => ({ x, y }),
829
- vecAdd: (a, b) => ({
830
- x: a.x + b.x,
831
- y: a.y + b.y
832
- }),
833
- vecSub: (a, b) => ({
834
- x: a.x - b.x,
835
- y: a.y - b.y
836
- }),
837
- vecMult: (v, s) => ({
838
- x: v.x * s,
839
- y: v.y * s
840
- }),
841
- vecMag: (v) => Math.sqrt(v.x * v.x + v.y * v.y),
842
- vecNorm: (v) => {
843
- const m = Math.sqrt(v.x * v.x + v.y * v.y);
844
- return m === 0 ? { x: 0, y: 0 } : { x: v.x / m, y: v.y / m };
845
- },
846
- vecDist: (a, b) => {
847
- const dx = b.x - a.x;
848
- const dy = b.y - a.y;
849
- return Math.sqrt(dx * dx + dy * dy);
850
- },
851
- // Easing functions (v1.1) - pure functions, t ∈ [0,1] → [0,1]
852
- easeIn: (t) => t * t,
853
- easeOut: (t) => t * (2 - t),
854
- easeInOut: (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
855
- easeCubic: (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
856
- easeExpo: (t) => {
857
- if (t === 0)
858
- return 0;
859
- if (t === 1)
860
- return 1;
861
- if (t < 0.5) {
862
- return Math.pow(2, 20 * t - 10) / 2;
863
- }
864
- return (2 - Math.pow(2, -20 * t + 10)) / 2;
865
- },
866
- // Text functions
867
- text: (str, x, y) => {
868
- ctx.font = `${currentTextSize}px ${currentTextFont}`;
869
- ctx.textAlign = currentTextAlignH;
870
- ctx.textBaseline = currentTextAlignV;
871
- if (fillEnabled) {
872
- ctx.fillStyle = currentFill;
873
- ctx.fillText(String(str), x, y);
874
- }
875
- if (strokeEnabled) {
876
- ctx.strokeStyle = currentStroke;
877
- ctx.strokeText(String(str), x, y);
878
- }
879
- },
880
- textSize: (size) => {
881
- currentTextSize = size;
882
- ctx.font = `${currentTextSize}px ${currentTextFont}`;
883
- },
884
- textFont: (font) => {
885
- currentTextFont = font;
886
- ctx.font = `${currentTextSize}px ${currentTextFont}`;
887
- },
888
- textAlign: (horizAlign, vertAlign) => {
889
- const hMap = {
890
- 'left': 'left', 'LEFT': 'left',
891
- 'center': 'center', 'CENTER': 'center',
892
- 'right': 'right', 'RIGHT': 'right',
893
- };
894
- const vMap = {
895
- 'top': 'top', 'TOP': 'top',
896
- 'bottom': 'bottom', 'BOTTOM': 'bottom',
897
- 'center': 'middle', 'CENTER': 'middle',
898
- 'baseline': 'alphabetic', 'BASELINE': 'alphabetic',
899
- };
900
- currentTextAlignH = hMap[horizAlign] || 'left';
901
- if (vertAlign) {
902
- currentTextAlignV = vMap[vertAlign] || 'alphabetic';
903
- }
904
- },
905
- textWidth: (str) => {
906
- ctx.font = `${currentTextSize}px ${currentTextFont}`;
907
- return ctx.measureText(String(str)).width;
908
- },
909
- // Pixel manipulation (v1.2)
910
- loadPixels: () => {
911
- imageDataObj = ctx.getImageData(0, 0, width, height);
912
- pixelData = imageDataObj.data;
913
- p.pixels = pixelData;
914
- },
915
- updatePixels: () => {
916
- if (imageDataObj && pixelData) {
917
- ctx.putImageData(imageDataObj, 0, 0);
918
- }
919
- },
920
- pixels: null,
921
- get: (x, y) => {
922
- const imgData = ctx.getImageData(Math.floor(x), Math.floor(y), 1, 1);
923
- return [imgData.data[0], imgData.data[1], imgData.data[2], imgData.data[3]];
924
- },
925
- set: (x, y, c) => {
926
- const fx = Math.floor(x);
927
- const fy = Math.floor(y);
928
- if (Array.isArray(c)) {
929
- const imgData = ctx.createImageData(1, 1);
930
- imgData.data[0] = c[0];
931
- imgData.data[1] = c[1];
932
- imgData.data[2] = c[2];
933
- imgData.data[3] = c[3] ?? 255;
934
- ctx.putImageData(imgData, fx, fy);
935
- }
936
- else {
937
- const parsed = parseCssColor(c);
938
- if (parsed) {
939
- const imgData = ctx.createImageData(1, 1);
940
- imgData.data[0] = parsed.r;
941
- imgData.data[1] = parsed.g;
942
- imgData.data[2] = parsed.b;
943
- imgData.data[3] = Math.round(parsed.a * 255);
944
- ctx.putImageData(imgData, fx, fy);
945
- }
946
- }
947
- },
948
- // Offscreen graphics (v1.2)
949
- createGraphics: (w, h) => {
950
- const offscreenCanvas = document.createElement('canvas');
951
- offscreenCanvas.width = w;
952
- offscreenCanvas.height = h;
953
- const pg = createP5Runtime(offscreenCanvas, w, h, config);
954
- // Add reference to canvas for image() drawing
955
- pg._canvas = offscreenCanvas;
956
- return pg;
957
- },
958
- // Draw image or graphics object to canvas
959
- image: (src, x, y, w, h) => {
960
- const srcCanvas = src._canvas || src;
961
- if (srcCanvas instanceof HTMLCanvasElement) {
962
- const dw = w ?? srcCanvas.width;
963
- const dh = h ?? srcCanvas.height;
964
- ctx.drawImage(srcCanvas, x, y, dw, dh);
965
- }
966
- },
967
- // Loop control (no-ops for SDK)
968
- noLoop: () => { },
969
- loop: () => { },
970
- redraw: () => { },
971
- frameRate: (fps) => { },
972
- // totalFrames placeholder (injected by engine)
973
- totalFrames: 0,
974
- };
975
- return p;
976
- }
977
- export function injectTimeVariables(p, time) {
978
- p.frameCount = time.frameCount;
979
- p.t = time.t;
980
- p.time = time.time;
981
- p.tGlobal = time.tGlobal;
982
- p.totalFrames = time.totalFrames;
983
- }
984
- /**
985
- * VAR Protocol Constants (Phase 1 — Protocol v1.0.0)
986
- * SDK v1.0.2: VAR input is optional (0-10 elements), but runtime always has 10
987
- */
988
- export const VAR_COUNT = 10; // Exactly 10 protocol variables: VAR[0..9]
989
- export const VAR_MIN = 0; // Minimum value
990
- export const VAR_MAX = 100; // Maximum value (normalized range)
991
- /**
992
- * Create a protected, read-only VAR array for protocol execution.
993
- *
994
- * SDK v1.0.2 Rules (Protocol v1.0.0):
995
- * - Input accepts 0-10 elements
996
- * - Runtime VAR is ALWAYS 10 elements (padded with zeros)
997
- * - Values are numeric, must be in 0-100 range (validated upstream)
998
- * - Read-only: writes throw descriptive errors
999
- * - Available in both setup() and draw()
1000
- */
1001
- export function createProtocolVAR(vars) {
1002
- // Create frozen 10-element array (upstream normalizeVars ensures this)
1003
- const normalizedVars = [];
1004
- for (let i = 0; i < VAR_COUNT; i++) {
1005
- normalizedVars[i] = vars?.[i] ?? 0;
1006
- }
1007
- // Freeze the array to prevent modifications
1008
- const frozenVars = Object.freeze(normalizedVars);
1009
- // Wrap in Proxy for descriptive error messages on write attempts
1010
- return new Proxy(frozenVars, {
1011
- set(_target, prop, _value) {
1012
- const propName = typeof prop === 'symbol' ? prop.toString() : prop;
1013
- throw new Error(`[Code Mode Protocol Error] VAR is read-only. ` +
1014
- `Cannot write to VAR[${propName}]. ` +
1015
- `VAR[0..9] are protocol inputs, not sketch state.`);
1016
- },
1017
- deleteProperty(_target, prop) {
1018
- const propName = typeof prop === 'symbol' ? prop.toString() : prop;
1019
- throw new Error(`[Code Mode Protocol Error] VAR is read-only. ` +
1020
- `Cannot delete VAR[${propName}].`);
1021
- },
1022
- defineProperty(_target, prop) {
1023
- const propName = typeof prop === 'symbol' ? prop.toString() : prop;
1024
- throw new Error(`[Code Mode Protocol Error] Cannot define new VAR properties. ` +
1025
- `VAR is fixed at 10 elements (VAR[0..9]). Attempted: ${propName}`);
1026
- },
1027
- });
1028
- }
1029
- export function injectProtocolVariables(p, vars) {
1030
- p.VAR = createProtocolVAR(vars);
1031
- }