@nexart/codemode-sdk 1.8.3 → 1.9.0

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