@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.
- package/CHANGELOG.md +89 -0
- package/README.md +121 -2
- package/dist/cjs/browser.cjs +3077 -0
- package/dist/cjs/browser.js +3042 -0
- package/dist/cjs/core.cjs +1998 -0
- package/dist/cjs/core.js +1966 -0
- package/dist/cjs/node.cjs +3245 -0
- package/dist/cjs/node.js +3208 -0
- package/dist/esm/browser.cjs +3077 -0
- package/dist/esm/browser.js +3042 -0
- package/dist/esm/core.cjs +1998 -0
- package/dist/esm/core.js +1966 -0
- package/dist/esm/node.cjs +3245 -0
- package/dist/esm/node.js +3208 -0
- package/dist/types/sdk/codemode/attestation.d.ts +24 -0
- package/dist/types/sdk/codemode/attestation.d.ts.map +1 -0
- package/dist/types/sdk/codemode/builder-manifest.d.ts.map +1 -0
- package/dist/types/sdk/codemode/canonicalJson.d.ts +16 -0
- package/dist/types/sdk/codemode/canonicalJson.d.ts.map +1 -0
- package/dist/{sdk → types/sdk}/codemode/core-index.d.ts +5 -1
- package/dist/types/sdk/codemode/core-index.d.ts.map +1 -0
- package/dist/types/sdk/codemode/engine.d.ts.map +1 -0
- package/dist/{sdk → types/sdk}/codemode/entry/browser.d.ts +1 -1
- package/dist/types/sdk/codemode/entry/browser.d.ts.map +1 -0
- package/dist/types/sdk/codemode/entry/node.d.ts.map +1 -0
- package/dist/types/sdk/codemode/execute.d.ts.map +1 -0
- package/dist/types/sdk/codemode/execution-sandbox.d.ts.map +1 -0
- package/dist/types/sdk/codemode/loop-engine.d.ts.map +1 -0
- package/dist/types/sdk/codemode/nodeReceipt.d.ts +65 -0
- package/dist/types/sdk/codemode/nodeReceipt.d.ts.map +1 -0
- package/dist/types/sdk/codemode/p5-runtime.d.ts.map +1 -0
- package/dist/{sdk → types/sdk}/codemode/runtime.d.ts +1 -1
- package/dist/types/sdk/codemode/runtime.d.ts.map +1 -0
- package/dist/types/sdk/codemode/snapshot.d.ts +72 -0
- package/dist/types/sdk/codemode/snapshot.d.ts.map +1 -0
- package/dist/types/sdk/codemode/sound-bridge.d.ts.map +1 -0
- package/dist/types/sdk/codemode/soundart-engine.d.ts.map +1 -0
- package/dist/types/sdk/codemode/soundart-sketches/chladniBloom.d.ts.map +1 -0
- package/dist/types/sdk/codemode/soundart-sketches/dualVortex.d.ts.map +1 -0
- package/dist/types/sdk/codemode/soundart-sketches/geometryIllusion.d.ts.map +1 -0
- package/dist/types/sdk/codemode/soundart-sketches/index.d.ts.map +1 -0
- package/dist/types/sdk/codemode/soundart-sketches/isoflow.d.ts.map +1 -0
- package/dist/types/sdk/codemode/soundart-sketches/loomWeave.d.ts.map +1 -0
- package/dist/types/sdk/codemode/soundart-sketches/noiseTerraces.d.ts.map +1 -0
- package/dist/types/sdk/codemode/soundart-sketches/orb.d.ts.map +1 -0
- package/dist/types/sdk/codemode/soundart-sketches/pixelGlyphs.d.ts.map +1 -0
- package/dist/types/sdk/codemode/soundart-sketches/prismFlowFields.d.ts.map +1 -0
- package/dist/types/sdk/codemode/soundart-sketches/radialBurst.d.ts.map +1 -0
- package/dist/types/sdk/codemode/soundart-sketches/resonantSoundBodies.d.ts.map +1 -0
- package/dist/types/sdk/codemode/soundart-sketches/rings.d.ts.map +1 -0
- package/dist/types/sdk/codemode/soundart-sketches/squares.d.ts.map +1 -0
- package/dist/types/sdk/codemode/soundart-sketches/waveStripes.d.ts.map +1 -0
- package/dist/types/sdk/codemode/static-engine.d.ts.map +1 -0
- package/dist/{sdk → types/sdk}/codemode/types.d.ts +96 -0
- package/dist/types/sdk/codemode/types.d.ts.map +1 -0
- package/dist/{sdk → types/sdk}/codemode/version.d.ts +2 -2
- package/dist/types/sdk/codemode/version.d.ts.map +1 -0
- package/dist/types/shared/soundSnapshot.d.ts.map +1 -0
- package/examples/sketch-minimal.js +27 -0
- package/examples/sketch-vars.js +59 -0
- package/examples/sketch.js +24 -0
- package/package.json +29 -23
- package/dist/sdk/codemode/builder-manifest.d.ts.map +0 -1
- package/dist/sdk/codemode/builder-manifest.js +0 -97
- package/dist/sdk/codemode/core-index.d.ts.map +0 -1
- package/dist/sdk/codemode/core-index.js +0 -28
- package/dist/sdk/codemode/engine.d.ts.map +0 -1
- package/dist/sdk/codemode/engine.js +0 -67
- package/dist/sdk/codemode/entry/browser.d.ts.map +0 -1
- package/dist/sdk/codemode/entry/browser.js +0 -69
- package/dist/sdk/codemode/entry/node.d.ts.map +0 -1
- package/dist/sdk/codemode/entry/node.js +0 -35
- package/dist/sdk/codemode/execute.d.ts.map +0 -1
- package/dist/sdk/codemode/execute.js +0 -283
- package/dist/sdk/codemode/execution-sandbox.d.ts.map +0 -1
- package/dist/sdk/codemode/execution-sandbox.js +0 -207
- package/dist/sdk/codemode/loop-engine.d.ts.map +0 -1
- package/dist/sdk/codemode/loop-engine.js +0 -229
- package/dist/sdk/codemode/p5-runtime.d.ts.map +0 -1
- package/dist/sdk/codemode/p5-runtime.js +0 -1033
- package/dist/sdk/codemode/runtime.d.ts.map +0 -1
- package/dist/sdk/codemode/runtime.js +0 -220
- package/dist/sdk/codemode/sound-bridge.d.ts.map +0 -1
- package/dist/sdk/codemode/sound-bridge.js +0 -128
- package/dist/sdk/codemode/soundart-engine.d.ts.map +0 -1
- package/dist/sdk/codemode/soundart-engine.js +0 -173
- package/dist/sdk/codemode/soundart-sketches/chladniBloom.d.ts.map +0 -1
- package/dist/sdk/codemode/soundart-sketches/chladniBloom.js +0 -53
- package/dist/sdk/codemode/soundart-sketches/dualVortex.d.ts.map +0 -1
- package/dist/sdk/codemode/soundart-sketches/dualVortex.js +0 -67
- package/dist/sdk/codemode/soundart-sketches/geometryIllusion.d.ts.map +0 -1
- package/dist/sdk/codemode/soundart-sketches/geometryIllusion.js +0 -89
- package/dist/sdk/codemode/soundart-sketches/index.d.ts.map +0 -1
- package/dist/sdk/codemode/soundart-sketches/index.js +0 -72
- package/dist/sdk/codemode/soundart-sketches/isoflow.d.ts.map +0 -1
- package/dist/sdk/codemode/soundart-sketches/isoflow.js +0 -60
- package/dist/sdk/codemode/soundart-sketches/loomWeave.d.ts.map +0 -1
- package/dist/sdk/codemode/soundart-sketches/loomWeave.js +0 -59
- package/dist/sdk/codemode/soundart-sketches/noiseTerraces.d.ts.map +0 -1
- package/dist/sdk/codemode/soundart-sketches/noiseTerraces.js +0 -53
- package/dist/sdk/codemode/soundart-sketches/orb.d.ts.map +0 -1
- package/dist/sdk/codemode/soundart-sketches/orb.js +0 -50
- package/dist/sdk/codemode/soundart-sketches/pixelGlyphs.d.ts.map +0 -1
- package/dist/sdk/codemode/soundart-sketches/pixelGlyphs.js +0 -72
- package/dist/sdk/codemode/soundart-sketches/prismFlowFields.d.ts.map +0 -1
- package/dist/sdk/codemode/soundart-sketches/prismFlowFields.js +0 -51
- package/dist/sdk/codemode/soundart-sketches/radialBurst.d.ts.map +0 -1
- package/dist/sdk/codemode/soundart-sketches/radialBurst.js +0 -60
- package/dist/sdk/codemode/soundart-sketches/resonantSoundBodies.d.ts.map +0 -1
- package/dist/sdk/codemode/soundart-sketches/resonantSoundBodies.js +0 -89
- package/dist/sdk/codemode/soundart-sketches/rings.d.ts.map +0 -1
- package/dist/sdk/codemode/soundart-sketches/rings.js +0 -89
- package/dist/sdk/codemode/soundart-sketches/squares.d.ts.map +0 -1
- package/dist/sdk/codemode/soundart-sketches/squares.js +0 -52
- package/dist/sdk/codemode/soundart-sketches/waveStripes.d.ts.map +0 -1
- package/dist/sdk/codemode/soundart-sketches/waveStripes.js +0 -44
- package/dist/sdk/codemode/static-engine.d.ts.map +0 -1
- package/dist/sdk/codemode/static-engine.js +0 -157
- package/dist/sdk/codemode/types.d.ts.map +0 -1
- package/dist/sdk/codemode/types.js +0 -34
- package/dist/sdk/codemode/version.d.ts.map +0 -1
- package/dist/sdk/codemode/version.js +0 -17
- package/dist/shared/soundSnapshot.d.ts.map +0 -1
- package/dist/shared/soundSnapshot.js +0 -128
- /package/dist/{sdk → types/sdk}/codemode/builder-manifest.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/engine.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/entry/node.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/execute.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/execution-sandbox.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/loop-engine.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/p5-runtime.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/sound-bridge.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/soundart-engine.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/soundart-sketches/chladniBloom.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/soundart-sketches/dualVortex.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/soundart-sketches/geometryIllusion.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/soundart-sketches/index.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/soundart-sketches/isoflow.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/soundart-sketches/loomWeave.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/soundart-sketches/noiseTerraces.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/soundart-sketches/orb.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/soundart-sketches/pixelGlyphs.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/soundart-sketches/prismFlowFields.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/soundart-sketches/radialBurst.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/soundart-sketches/resonantSoundBodies.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/soundart-sketches/rings.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/soundart-sketches/squares.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/soundart-sketches/waveStripes.d.ts +0 -0
- /package/dist/{sdk → types/sdk}/codemode/static-engine.d.ts +0 -0
- /package/dist/{shared → types/shared}/soundSnapshot.d.ts +0 -0
package/dist/esm/core.js
ADDED
|
@@ -0,0 +1,1966 @@
|
|
|
1
|
+
import { hashes, verify } from '@noble/ed25519';
|
|
2
|
+
import { sha512 } from '@noble/hashes/sha2.js';
|
|
3
|
+
|
|
4
|
+
// version.ts
|
|
5
|
+
var SDK_VERSION = "1.9.0";
|
|
6
|
+
var PROTOCOL_VERSION = "1.2.0";
|
|
7
|
+
var PROTOCOL_PHASE = 3;
|
|
8
|
+
|
|
9
|
+
// types.ts
|
|
10
|
+
var PROTOCOL_IDENTITY = {
|
|
11
|
+
protocol: "nexart",
|
|
12
|
+
engine: "codemode",
|
|
13
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
14
|
+
phase: PROTOCOL_PHASE,
|
|
15
|
+
deterministic: true
|
|
16
|
+
};
|
|
17
|
+
var DEFAULT_VARS = {
|
|
18
|
+
VAR: Object.freeze([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
|
19
|
+
};
|
|
20
|
+
var DEFAULT_CONFIG = {
|
|
21
|
+
width: 1950,
|
|
22
|
+
height: 2400,
|
|
23
|
+
duration: 2,
|
|
24
|
+
fps: 30,
|
|
25
|
+
minDuration: 1,
|
|
26
|
+
maxDuration: 4
|
|
27
|
+
};
|
|
28
|
+
var CodeVerifyCode = {
|
|
29
|
+
OK: "OK",
|
|
30
|
+
CERTIFICATE_HASH_MISMATCH: "CERTIFICATE_HASH_MISMATCH",
|
|
31
|
+
SNAPSHOT_HASH_MISMATCH: "SNAPSHOT_HASH_MISMATCH",
|
|
32
|
+
RENDER_HASH_MISMATCH: "RENDER_HASH_MISMATCH",
|
|
33
|
+
INVALID_SHA256_FORMAT: "INVALID_SHA256_FORMAT",
|
|
34
|
+
CANONICALIZATION_ERROR: "CANONICALIZATION_ERROR",
|
|
35
|
+
SCHEMA_ERROR: "SCHEMA_ERROR",
|
|
36
|
+
NODE_RECEIPT_MISSING: "NODE_RECEIPT_MISSING",
|
|
37
|
+
NODE_RECEIPT_KEY_NOT_FOUND: "NODE_RECEIPT_KEY_NOT_FOUND",
|
|
38
|
+
NODE_RECEIPT_INVALID_SIGNATURE: "NODE_RECEIPT_INVALID_SIGNATURE",
|
|
39
|
+
NODE_RECEIPT_KEY_FORMAT_UNSUPPORTED: "NODE_RECEIPT_KEY_FORMAT_UNSUPPORTED",
|
|
40
|
+
UNKNOWN_ERROR: "UNKNOWN_ERROR"
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// p5-runtime.ts
|
|
44
|
+
var CODE_MODE_PROTOCOL_VERSION = PROTOCOL_VERSION;
|
|
45
|
+
var CODE_MODE_PROTOCOL_PHASE = PROTOCOL_PHASE;
|
|
46
|
+
var CODE_MODE_ENFORCEMENT = "HARD";
|
|
47
|
+
function createSeededRNG(seed = 123456) {
|
|
48
|
+
let a = seed >>> 0;
|
|
49
|
+
return () => {
|
|
50
|
+
a += 1831565813;
|
|
51
|
+
let t = Math.imul(a ^ a >>> 15, a | 1);
|
|
52
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
53
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
54
|
+
};
|
|
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(
|
|
94
|
+
lerp(
|
|
95
|
+
lerp(grad(permutation[AA], x, y, z), grad(permutation[BA], x - 1, y, z), u),
|
|
96
|
+
lerp(grad(permutation[AB], x, y - 1, z), grad(permutation[BB], x - 1, y - 1, z), u),
|
|
97
|
+
v
|
|
98
|
+
),
|
|
99
|
+
lerp(
|
|
100
|
+
lerp(grad(permutation[AA + 1], x, y, z - 1), grad(permutation[BA + 1], x - 1, y, z - 1), u),
|
|
101
|
+
lerp(grad(permutation[AB + 1], x, y - 1, z - 1), grad(permutation[BB + 1], x - 1, y - 1, z - 1), u),
|
|
102
|
+
v
|
|
103
|
+
),
|
|
104
|
+
w
|
|
105
|
+
) + 1) / 2;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function createP5Runtime(canvas, width, height, config) {
|
|
109
|
+
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
110
|
+
if (!ctx) throw new Error("Failed to get 2D context");
|
|
111
|
+
let currentFill = "rgba(255, 255, 255, 1)";
|
|
112
|
+
let currentStroke = "rgba(0, 0, 0, 1)";
|
|
113
|
+
let strokeEnabled = true;
|
|
114
|
+
let fillEnabled = true;
|
|
115
|
+
let currentStrokeWeight = 1;
|
|
116
|
+
let colorModeSettings = { mode: "RGB", maxR: 255, maxG: 255, maxB: 255, maxA: 255 };
|
|
117
|
+
let shapeVertices = [];
|
|
118
|
+
let pixelData = null;
|
|
119
|
+
let imageDataObj = null;
|
|
120
|
+
let currentTextSize = 12;
|
|
121
|
+
let currentTextFont = "sans-serif";
|
|
122
|
+
let currentTextAlignH = "left";
|
|
123
|
+
let currentTextAlignV = "alphabetic";
|
|
124
|
+
let randomSeedValue = config?.seed ?? Math.floor(Math.random() * 2147483647);
|
|
125
|
+
let rng = createSeededRNG(randomSeedValue);
|
|
126
|
+
let noiseSeedValue = config?.seed ?? 0;
|
|
127
|
+
let noiseFunc = createSeededNoise(noiseSeedValue);
|
|
128
|
+
let noiseOctaves = 4;
|
|
129
|
+
let noiseFalloff = 0.5;
|
|
130
|
+
const parseCssColor = (str) => {
|
|
131
|
+
const s = str.trim();
|
|
132
|
+
if (s.startsWith("#")) {
|
|
133
|
+
const hex = s.slice(1);
|
|
134
|
+
if (hex.length === 3) {
|
|
135
|
+
const r = parseInt(hex[0] + hex[0], 16);
|
|
136
|
+
const g = parseInt(hex[1] + hex[1], 16);
|
|
137
|
+
const b = parseInt(hex[2] + hex[2], 16);
|
|
138
|
+
return { r, g, b, a: 1 };
|
|
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
|
+
} else if (hex.length === 8) {
|
|
145
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
146
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
147
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
148
|
+
const a = parseInt(hex.slice(6, 8), 16) / 255;
|
|
149
|
+
return { r, g, b, a };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
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
|
+
const rgbaMatch = s.match(/^rgba\s*\(\s*(\d+)\s*[,\s]\s*(\d+)\s*[,\s]\s*(\d+)\s*[,\/\s]\s*([\d.]+)\s*\)$/i);
|
|
162
|
+
if (rgbaMatch) {
|
|
163
|
+
return {
|
|
164
|
+
r: parseInt(rgbaMatch[1]),
|
|
165
|
+
g: parseInt(rgbaMatch[2]),
|
|
166
|
+
b: parseInt(rgbaMatch[3]),
|
|
167
|
+
a: parseFloat(rgbaMatch[4])
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const hslMatch = s.match(/^hsla?\s*\(\s*([\d.]+)\s*[,\s]\s*([\d.]+)%?\s*[,\s]\s*([\d.]+)%?\s*(?:[,\/\s]\s*([\d.]+))?\s*\)$/i);
|
|
171
|
+
if (hslMatch) {
|
|
172
|
+
const h = parseFloat(hslMatch[1]) / 360;
|
|
173
|
+
const sat = parseFloat(hslMatch[2]) / 100;
|
|
174
|
+
const l = parseFloat(hslMatch[3]) / 100;
|
|
175
|
+
const a = hslMatch[4] ? parseFloat(hslMatch[4]) : 1;
|
|
176
|
+
let r, g, b;
|
|
177
|
+
if (sat === 0) {
|
|
178
|
+
r = g = b = l;
|
|
179
|
+
} else {
|
|
180
|
+
const hue2rgb = (p3, q2, t) => {
|
|
181
|
+
if (t < 0) t += 1;
|
|
182
|
+
if (t > 1) t -= 1;
|
|
183
|
+
if (t < 1 / 6) return p3 + (q2 - p3) * 6 * t;
|
|
184
|
+
if (t < 1 / 2) return q2;
|
|
185
|
+
if (t < 2 / 3) return p3 + (q2 - p3) * (2 / 3 - t) * 6;
|
|
186
|
+
return p3;
|
|
187
|
+
};
|
|
188
|
+
const q = l < 0.5 ? l * (1 + sat) : l + sat - l * sat;
|
|
189
|
+
const p2 = 2 * l - q;
|
|
190
|
+
r = hue2rgb(p2, q, h + 1 / 3);
|
|
191
|
+
g = hue2rgb(p2, q, h);
|
|
192
|
+
b = hue2rgb(p2, q, h - 1 / 3);
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
r: Math.round(r * 255),
|
|
196
|
+
g: Math.round(g * 255),
|
|
197
|
+
b: Math.round(b * 255),
|
|
198
|
+
a
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
};
|
|
203
|
+
const parseColor = (...args) => {
|
|
204
|
+
if (args.length === 0) return "rgba(0, 0, 0, 1)";
|
|
205
|
+
const { mode, maxR, maxG, maxB, maxA } = colorModeSettings;
|
|
206
|
+
if (args.length === 1) {
|
|
207
|
+
const val = args[0];
|
|
208
|
+
if (typeof val === "string") {
|
|
209
|
+
const parsed = parseCssColor(val);
|
|
210
|
+
if (parsed) {
|
|
211
|
+
return `rgba(${parsed.r}, ${parsed.g}, ${parsed.b}, ${parsed.a})`;
|
|
212
|
+
}
|
|
213
|
+
return val;
|
|
214
|
+
}
|
|
215
|
+
if (mode === "HSB") {
|
|
216
|
+
return `hsla(${val}, 100%, 50%, 1)`;
|
|
217
|
+
}
|
|
218
|
+
const gray = Math.round(val / maxR * 255);
|
|
219
|
+
return `rgba(${gray}, ${gray}, ${gray}, 1)`;
|
|
220
|
+
}
|
|
221
|
+
if (args.length === 2) {
|
|
222
|
+
const [gray, alpha] = args;
|
|
223
|
+
const g = Math.round(gray / maxR * 255);
|
|
224
|
+
const a = alpha / maxA;
|
|
225
|
+
return `rgba(${g}, ${g}, ${g}, ${a})`;
|
|
226
|
+
}
|
|
227
|
+
if (args.length === 3) {
|
|
228
|
+
const [r, g, b] = args;
|
|
229
|
+
if (mode === "HSB") {
|
|
230
|
+
return `hsla(${r / maxR * 360}, ${g / maxG * 100}%, ${b / maxB * 100}%, 1)`;
|
|
231
|
+
}
|
|
232
|
+
return `rgba(${Math.round(r / maxR * 255)}, ${Math.round(g / maxG * 255)}, ${Math.round(b / maxB * 255)}, 1)`;
|
|
233
|
+
}
|
|
234
|
+
if (args.length === 4) {
|
|
235
|
+
const [r, g, b, a] = args;
|
|
236
|
+
if (mode === "HSB") {
|
|
237
|
+
return `hsla(${r / maxR * 360}, ${g / maxG * 100}%, ${b / maxB * 100}%, ${a / maxA})`;
|
|
238
|
+
}
|
|
239
|
+
return `rgba(${Math.round(r / maxR * 255)}, ${Math.round(g / maxG * 255)}, ${Math.round(b / maxB * 255)}, ${a / maxA})`;
|
|
240
|
+
}
|
|
241
|
+
return "rgba(0, 0, 0, 1)";
|
|
242
|
+
};
|
|
243
|
+
const p = {
|
|
244
|
+
width,
|
|
245
|
+
height,
|
|
246
|
+
frameCount: 0,
|
|
247
|
+
// Constants
|
|
248
|
+
PI: Math.PI,
|
|
249
|
+
TWO_PI: Math.PI * 2,
|
|
250
|
+
TAU: Math.PI * 2,
|
|
251
|
+
HALF_PI: Math.PI / 2,
|
|
252
|
+
QUARTER_PI: Math.PI / 4,
|
|
253
|
+
// Shape mode constants
|
|
254
|
+
CORNER: "corner",
|
|
255
|
+
CENTER: "center",
|
|
256
|
+
CORNERS: "corners",
|
|
257
|
+
RADIUS: "radius",
|
|
258
|
+
ROUND: "round",
|
|
259
|
+
SQUARE: "butt",
|
|
260
|
+
PROJECT: "square",
|
|
261
|
+
MITER: "miter",
|
|
262
|
+
BEVEL: "bevel",
|
|
263
|
+
CLOSE: "close",
|
|
264
|
+
PIE: "pie",
|
|
265
|
+
CHORD: "chord",
|
|
266
|
+
OPEN: "open",
|
|
267
|
+
// Blend mode constants (v1.1)
|
|
268
|
+
NORMAL: "source-over",
|
|
269
|
+
ADD: "lighter",
|
|
270
|
+
MULTIPLY: "multiply",
|
|
271
|
+
SCREEN: "screen",
|
|
272
|
+
// Text alignment constants
|
|
273
|
+
LEFT: "left",
|
|
274
|
+
RIGHT: "right",
|
|
275
|
+
TOP: "top",
|
|
276
|
+
BOTTOM: "bottom",
|
|
277
|
+
BASELINE: "alphabetic",
|
|
278
|
+
// Canvas operations
|
|
279
|
+
background: (...args) => {
|
|
280
|
+
ctx.save();
|
|
281
|
+
ctx.fillStyle = parseColor(...args);
|
|
282
|
+
ctx.fillRect(0, 0, width, height);
|
|
283
|
+
ctx.restore();
|
|
284
|
+
},
|
|
285
|
+
clear: () => {
|
|
286
|
+
ctx.clearRect(0, 0, width, height);
|
|
287
|
+
},
|
|
288
|
+
blendMode: (mode) => {
|
|
289
|
+
const modeMap = {
|
|
290
|
+
"source-over": "source-over",
|
|
291
|
+
"NORMAL": "source-over",
|
|
292
|
+
"lighter": "lighter",
|
|
293
|
+
"ADD": "lighter",
|
|
294
|
+
"multiply": "multiply",
|
|
295
|
+
"MULTIPLY": "multiply",
|
|
296
|
+
"screen": "screen",
|
|
297
|
+
"SCREEN": "screen"
|
|
298
|
+
};
|
|
299
|
+
const compositeOp = modeMap[mode];
|
|
300
|
+
if (!compositeOp) {
|
|
301
|
+
throw new Error(`[Code Mode Protocol Error] Unsupported blend mode: ${mode}. Supported: NORMAL, ADD, MULTIPLY, SCREEN`);
|
|
302
|
+
}
|
|
303
|
+
ctx.globalCompositeOperation = compositeOp;
|
|
304
|
+
},
|
|
305
|
+
// Color functions
|
|
306
|
+
fill: (...args) => {
|
|
307
|
+
fillEnabled = true;
|
|
308
|
+
currentFill = parseColor(...args);
|
|
309
|
+
ctx.fillStyle = currentFill;
|
|
310
|
+
},
|
|
311
|
+
noFill: () => {
|
|
312
|
+
fillEnabled = false;
|
|
313
|
+
},
|
|
314
|
+
stroke: (...args) => {
|
|
315
|
+
strokeEnabled = true;
|
|
316
|
+
currentStroke = parseColor(...args);
|
|
317
|
+
ctx.strokeStyle = currentStroke;
|
|
318
|
+
},
|
|
319
|
+
noStroke: () => {
|
|
320
|
+
strokeEnabled = false;
|
|
321
|
+
},
|
|
322
|
+
strokeWeight: (weight) => {
|
|
323
|
+
currentStrokeWeight = weight;
|
|
324
|
+
ctx.lineWidth = weight;
|
|
325
|
+
},
|
|
326
|
+
strokeCap: (cap) => {
|
|
327
|
+
const capMap = {
|
|
328
|
+
"round": "round",
|
|
329
|
+
"ROUND": "round",
|
|
330
|
+
"square": "butt",
|
|
331
|
+
"SQUARE": "butt",
|
|
332
|
+
"project": "square",
|
|
333
|
+
"PROJECT": "square",
|
|
334
|
+
"butt": "butt"
|
|
335
|
+
};
|
|
336
|
+
ctx.lineCap = capMap[cap] || "round";
|
|
337
|
+
},
|
|
338
|
+
strokeJoin: (join) => {
|
|
339
|
+
const joinMap = {
|
|
340
|
+
"miter": "miter",
|
|
341
|
+
"MITER": "miter",
|
|
342
|
+
"bevel": "bevel",
|
|
343
|
+
"BEVEL": "bevel",
|
|
344
|
+
"round": "round",
|
|
345
|
+
"ROUND": "round"
|
|
346
|
+
};
|
|
347
|
+
ctx.lineJoin = joinMap[join] || "miter";
|
|
348
|
+
},
|
|
349
|
+
colorMode: (mode, max1, max2, max3, maxA) => {
|
|
350
|
+
colorModeSettings = {
|
|
351
|
+
mode: mode.toUpperCase(),
|
|
352
|
+
maxR: max1 ?? 255,
|
|
353
|
+
maxG: max2 ?? max1 ?? 255,
|
|
354
|
+
maxB: max3 ?? max1 ?? 255,
|
|
355
|
+
maxA: maxA ?? 255
|
|
356
|
+
};
|
|
357
|
+
},
|
|
358
|
+
color: (...args) => parseColor(...args),
|
|
359
|
+
lerpColor: (c1, c2, amt) => {
|
|
360
|
+
const color1 = parseCssColor(c1) || { r: 0, g: 0, b: 0, a: 1 };
|
|
361
|
+
const color2 = parseCssColor(c2) || { r: 255, g: 255, b: 255, a: 1 };
|
|
362
|
+
const r = Math.round(color1.r + (color2.r - color1.r) * amt);
|
|
363
|
+
const g = Math.round(color1.g + (color2.g - color1.g) * amt);
|
|
364
|
+
const b = Math.round(color1.b + (color2.b - color1.b) * amt);
|
|
365
|
+
const a = color1.a + (color2.a - color1.a) * amt;
|
|
366
|
+
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
367
|
+
},
|
|
368
|
+
red: (color) => {
|
|
369
|
+
const parsed = parseCssColor(color);
|
|
370
|
+
return parsed ? parsed.r : 0;
|
|
371
|
+
},
|
|
372
|
+
green: (color) => {
|
|
373
|
+
const parsed = parseCssColor(color);
|
|
374
|
+
return parsed ? parsed.g : 0;
|
|
375
|
+
},
|
|
376
|
+
blue: (color) => {
|
|
377
|
+
const parsed = parseCssColor(color);
|
|
378
|
+
return parsed ? parsed.b : 0;
|
|
379
|
+
},
|
|
380
|
+
alpha: (color) => {
|
|
381
|
+
const parsed = parseCssColor(color);
|
|
382
|
+
return parsed ? parsed.a * 255 : 255;
|
|
383
|
+
},
|
|
384
|
+
brightness: (color) => {
|
|
385
|
+
const parsed = parseCssColor(color);
|
|
386
|
+
if (!parsed) return 0;
|
|
387
|
+
return Math.max(parsed.r, parsed.g, parsed.b) / 255 * 100;
|
|
388
|
+
},
|
|
389
|
+
saturation: (color) => {
|
|
390
|
+
const parsed = parseCssColor(color);
|
|
391
|
+
if (!parsed) return 0;
|
|
392
|
+
const max = Math.max(parsed.r, parsed.g, parsed.b);
|
|
393
|
+
const min = Math.min(parsed.r, parsed.g, parsed.b);
|
|
394
|
+
if (max === 0) return 0;
|
|
395
|
+
return (max - min) / max * 100;
|
|
396
|
+
},
|
|
397
|
+
hue: (color) => {
|
|
398
|
+
const parsed = parseCssColor(color);
|
|
399
|
+
if (!parsed) return 0;
|
|
400
|
+
const { r, g, b } = parsed;
|
|
401
|
+
const max = Math.max(r, g, b);
|
|
402
|
+
const min = Math.min(r, g, b);
|
|
403
|
+
if (max === min) return 0;
|
|
404
|
+
let h = 0;
|
|
405
|
+
const d = max - min;
|
|
406
|
+
switch (max) {
|
|
407
|
+
case r:
|
|
408
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
409
|
+
break;
|
|
410
|
+
case g:
|
|
411
|
+
h = ((b - r) / d + 2) / 6;
|
|
412
|
+
break;
|
|
413
|
+
case b:
|
|
414
|
+
h = ((r - g) / d + 4) / 6;
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
return h * 360;
|
|
418
|
+
},
|
|
419
|
+
// Shape functions
|
|
420
|
+
ellipse: (x, y, w, h) => {
|
|
421
|
+
const rw = w / 2;
|
|
422
|
+
const rh = (h ?? w) / 2;
|
|
423
|
+
ctx.beginPath();
|
|
424
|
+
ctx.ellipse(x, y, rw, rh, 0, 0, Math.PI * 2);
|
|
425
|
+
if (fillEnabled) ctx.fill();
|
|
426
|
+
if (strokeEnabled) ctx.stroke();
|
|
427
|
+
},
|
|
428
|
+
circle: (x, y, d) => {
|
|
429
|
+
p.ellipse(x, y, d, d);
|
|
430
|
+
},
|
|
431
|
+
rect: (x, y, w, h, r) => {
|
|
432
|
+
const height2 = h ?? w;
|
|
433
|
+
ctx.beginPath();
|
|
434
|
+
if (r && r > 0) {
|
|
435
|
+
ctx.roundRect(x, y, w, height2, r);
|
|
436
|
+
} else {
|
|
437
|
+
ctx.rect(x, y, w, height2);
|
|
438
|
+
}
|
|
439
|
+
if (fillEnabled) ctx.fill();
|
|
440
|
+
if (strokeEnabled) ctx.stroke();
|
|
441
|
+
},
|
|
442
|
+
square: (x, y, s, r) => {
|
|
443
|
+
p.rect(x, y, s, s, r);
|
|
444
|
+
},
|
|
445
|
+
line: (x1, y1, x2, y2) => {
|
|
446
|
+
ctx.beginPath();
|
|
447
|
+
ctx.moveTo(x1, y1);
|
|
448
|
+
ctx.lineTo(x2, y2);
|
|
449
|
+
if (strokeEnabled) ctx.stroke();
|
|
450
|
+
},
|
|
451
|
+
point: (x, y) => {
|
|
452
|
+
ctx.beginPath();
|
|
453
|
+
ctx.arc(x, y, currentStrokeWeight / 2, 0, Math.PI * 2);
|
|
454
|
+
ctx.fillStyle = currentStroke;
|
|
455
|
+
ctx.fill();
|
|
456
|
+
},
|
|
457
|
+
triangle: (x1, y1, x2, y2, x3, y3) => {
|
|
458
|
+
ctx.beginPath();
|
|
459
|
+
ctx.moveTo(x1, y1);
|
|
460
|
+
ctx.lineTo(x2, y2);
|
|
461
|
+
ctx.lineTo(x3, y3);
|
|
462
|
+
ctx.closePath();
|
|
463
|
+
if (fillEnabled) ctx.fill();
|
|
464
|
+
if (strokeEnabled) ctx.stroke();
|
|
465
|
+
},
|
|
466
|
+
quad: (x1, y1, x2, y2, x3, y3, x4, y4) => {
|
|
467
|
+
ctx.beginPath();
|
|
468
|
+
ctx.moveTo(x1, y1);
|
|
469
|
+
ctx.lineTo(x2, y2);
|
|
470
|
+
ctx.lineTo(x3, y3);
|
|
471
|
+
ctx.lineTo(x4, y4);
|
|
472
|
+
ctx.closePath();
|
|
473
|
+
if (fillEnabled) ctx.fill();
|
|
474
|
+
if (strokeEnabled) ctx.stroke();
|
|
475
|
+
},
|
|
476
|
+
arc: (x, y, w, h, start, stop, mode) => {
|
|
477
|
+
ctx.beginPath();
|
|
478
|
+
ctx.ellipse(x, y, w / 2, h / 2, 0, start, stop);
|
|
479
|
+
if (mode === "pie" || mode === "PIE") {
|
|
480
|
+
ctx.lineTo(x, y);
|
|
481
|
+
ctx.closePath();
|
|
482
|
+
} else if (mode === "chord" || mode === "CHORD") {
|
|
483
|
+
ctx.closePath();
|
|
484
|
+
}
|
|
485
|
+
if (fillEnabled) ctx.fill();
|
|
486
|
+
if (strokeEnabled) ctx.stroke();
|
|
487
|
+
},
|
|
488
|
+
// Bezier and curve functions
|
|
489
|
+
bezier: (x1, y1, cx1, cy1, cx2, cy2, x2, y2) => {
|
|
490
|
+
ctx.beginPath();
|
|
491
|
+
ctx.moveTo(x1, y1);
|
|
492
|
+
ctx.bezierCurveTo(cx1, cy1, cx2, cy2, x2, y2);
|
|
493
|
+
if (strokeEnabled) ctx.stroke();
|
|
494
|
+
},
|
|
495
|
+
curve: (x1, y1, x2, y2, x3, y3, x4, y4) => {
|
|
496
|
+
const tension = 1 / 6;
|
|
497
|
+
const cp1x = x2 + (x3 - x1) * tension;
|
|
498
|
+
const cp1y = y2 + (y3 - y1) * tension;
|
|
499
|
+
const cp2x = x3 - (x4 - x2) * tension;
|
|
500
|
+
const cp2y = y3 - (y4 - y2) * tension;
|
|
501
|
+
ctx.beginPath();
|
|
502
|
+
ctx.moveTo(x2, y2);
|
|
503
|
+
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x3, y3);
|
|
504
|
+
if (strokeEnabled) ctx.stroke();
|
|
505
|
+
},
|
|
506
|
+
// Shape helpers (v1.1)
|
|
507
|
+
polygon: (cx, cy, radius, sides, rotation = 0) => {
|
|
508
|
+
ctx.beginPath();
|
|
509
|
+
for (let i = 0; i < sides; i++) {
|
|
510
|
+
const angle = rotation + i / sides * Math.PI * 2 - Math.PI / 2;
|
|
511
|
+
const x = cx + Math.cos(angle) * radius;
|
|
512
|
+
const y = cy + Math.sin(angle) * radius;
|
|
513
|
+
if (i === 0) {
|
|
514
|
+
ctx.moveTo(x, y);
|
|
515
|
+
} else {
|
|
516
|
+
ctx.lineTo(x, y);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
ctx.closePath();
|
|
520
|
+
if (fillEnabled) ctx.fill();
|
|
521
|
+
if (strokeEnabled) ctx.stroke();
|
|
522
|
+
},
|
|
523
|
+
star: (cx, cy, innerRadius, outerRadius, points, rotation = 0) => {
|
|
524
|
+
ctx.beginPath();
|
|
525
|
+
const totalPoints = points * 2;
|
|
526
|
+
for (let i = 0; i < totalPoints; i++) {
|
|
527
|
+
const angle = rotation + i / totalPoints * Math.PI * 2 - Math.PI / 2;
|
|
528
|
+
const radius = i % 2 === 0 ? outerRadius : innerRadius;
|
|
529
|
+
const x = cx + Math.cos(angle) * radius;
|
|
530
|
+
const y = cy + Math.sin(angle) * radius;
|
|
531
|
+
if (i === 0) {
|
|
532
|
+
ctx.moveTo(x, y);
|
|
533
|
+
} else {
|
|
534
|
+
ctx.lineTo(x, y);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
ctx.closePath();
|
|
538
|
+
if (fillEnabled) ctx.fill();
|
|
539
|
+
if (strokeEnabled) ctx.stroke();
|
|
540
|
+
},
|
|
541
|
+
// Vertex-based shapes
|
|
542
|
+
beginShape: () => {
|
|
543
|
+
shapeVertices = [];
|
|
544
|
+
},
|
|
545
|
+
vertex: (x, y) => {
|
|
546
|
+
shapeVertices.push({ x, y, type: "vertex" });
|
|
547
|
+
},
|
|
548
|
+
curveVertex: (x, y) => {
|
|
549
|
+
shapeVertices.push({ x, y, type: "curve" });
|
|
550
|
+
},
|
|
551
|
+
bezierVertex: (cx1, cy1, cx2, cy2, x, y) => {
|
|
552
|
+
shapeVertices.push({ x, y, type: "bezier", cx1, cy1, cx2, cy2 });
|
|
553
|
+
},
|
|
554
|
+
endShape: (mode) => {
|
|
555
|
+
if (shapeVertices.length === 0) return;
|
|
556
|
+
ctx.beginPath();
|
|
557
|
+
let started = false;
|
|
558
|
+
let curveBuffer = [];
|
|
559
|
+
const tension = 1 / 6;
|
|
560
|
+
const flushCurveBuffer = () => {
|
|
561
|
+
if (curveBuffer.length >= 4) {
|
|
562
|
+
if (!started) {
|
|
563
|
+
ctx.moveTo(curveBuffer[1].x, curveBuffer[1].y);
|
|
564
|
+
started = true;
|
|
565
|
+
} else {
|
|
566
|
+
ctx.lineTo(curveBuffer[1].x, curveBuffer[1].y);
|
|
567
|
+
}
|
|
568
|
+
for (let i = 1; i < curveBuffer.length - 2; i++) {
|
|
569
|
+
const p0 = curveBuffer[i - 1];
|
|
570
|
+
const p1 = curveBuffer[i];
|
|
571
|
+
const p2 = curveBuffer[i + 1];
|
|
572
|
+
const p3 = curveBuffer[i + 2];
|
|
573
|
+
const cp1x = p1.x + (p2.x - p0.x) * tension;
|
|
574
|
+
const cp1y = p1.y + (p2.y - p0.y) * tension;
|
|
575
|
+
const cp2x = p2.x - (p3.x - p1.x) * tension;
|
|
576
|
+
const cp2y = p2.y - (p3.y - p1.y) * tension;
|
|
577
|
+
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
curveBuffer = [];
|
|
581
|
+
};
|
|
582
|
+
for (let i = 0; i < shapeVertices.length; i++) {
|
|
583
|
+
const v = shapeVertices[i];
|
|
584
|
+
if (v.type === "curve") {
|
|
585
|
+
curveBuffer.push({ x: v.x, y: v.y });
|
|
586
|
+
} else {
|
|
587
|
+
if (curveBuffer.length > 0) {
|
|
588
|
+
flushCurveBuffer();
|
|
589
|
+
}
|
|
590
|
+
if (v.type === "vertex") {
|
|
591
|
+
if (!started) {
|
|
592
|
+
ctx.moveTo(v.x, v.y);
|
|
593
|
+
started = true;
|
|
594
|
+
} else {
|
|
595
|
+
ctx.lineTo(v.x, v.y);
|
|
596
|
+
}
|
|
597
|
+
} else if (v.type === "bezier" && started) {
|
|
598
|
+
ctx.bezierCurveTo(v.cx1, v.cy1, v.cx2, v.cy2, v.x, v.y);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (curveBuffer.length > 0) {
|
|
603
|
+
flushCurveBuffer();
|
|
604
|
+
}
|
|
605
|
+
if (mode === "close" || mode === "CLOSE") {
|
|
606
|
+
ctx.closePath();
|
|
607
|
+
}
|
|
608
|
+
if (fillEnabled) ctx.fill();
|
|
609
|
+
if (strokeEnabled) ctx.stroke();
|
|
610
|
+
shapeVertices = [];
|
|
611
|
+
},
|
|
612
|
+
// Transform functions
|
|
613
|
+
push: () => {
|
|
614
|
+
ctx.save();
|
|
615
|
+
},
|
|
616
|
+
pop: () => {
|
|
617
|
+
ctx.restore();
|
|
618
|
+
ctx.fillStyle = currentFill;
|
|
619
|
+
ctx.strokeStyle = currentStroke;
|
|
620
|
+
ctx.lineWidth = currentStrokeWeight;
|
|
621
|
+
},
|
|
622
|
+
translate: (x, y) => {
|
|
623
|
+
ctx.translate(x, y);
|
|
624
|
+
},
|
|
625
|
+
rotate: (angle) => {
|
|
626
|
+
ctx.rotate(angle);
|
|
627
|
+
},
|
|
628
|
+
scale: (sx, sy) => {
|
|
629
|
+
ctx.scale(sx, sy ?? sx);
|
|
630
|
+
},
|
|
631
|
+
resetMatrix: () => {
|
|
632
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
633
|
+
},
|
|
634
|
+
shearX: (angle) => {
|
|
635
|
+
ctx.transform(1, 0, Math.tan(angle), 1, 0, 0);
|
|
636
|
+
},
|
|
637
|
+
shearY: (angle) => {
|
|
638
|
+
ctx.transform(1, Math.tan(angle), 0, 1, 0, 0);
|
|
639
|
+
},
|
|
640
|
+
// Math functions - SEEDED for determinism
|
|
641
|
+
random: (min, max) => {
|
|
642
|
+
if (Array.isArray(min)) {
|
|
643
|
+
return min[Math.floor(rng() * min.length)];
|
|
644
|
+
}
|
|
645
|
+
if (min === void 0) return rng();
|
|
646
|
+
if (max === void 0) return rng() * min;
|
|
647
|
+
return min + rng() * (max - min);
|
|
648
|
+
},
|
|
649
|
+
randomSeed: (seed) => {
|
|
650
|
+
randomSeedValue = seed;
|
|
651
|
+
rng = createSeededRNG(seed);
|
|
652
|
+
},
|
|
653
|
+
randomGaussian: (mean = 0, sd = 1) => {
|
|
654
|
+
const u1 = rng();
|
|
655
|
+
const u2 = rng();
|
|
656
|
+
const z0 = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
657
|
+
return z0 * sd + mean;
|
|
658
|
+
},
|
|
659
|
+
noise: (x, y, z) => {
|
|
660
|
+
let total = 0;
|
|
661
|
+
let frequency = 1;
|
|
662
|
+
let amplitude = 1;
|
|
663
|
+
let maxValue = 0;
|
|
664
|
+
for (let i = 0; i < noiseOctaves; i++) {
|
|
665
|
+
total += noiseFunc(x * frequency, (y ?? 0) * frequency, (z ?? 0) * frequency) * amplitude;
|
|
666
|
+
maxValue += amplitude;
|
|
667
|
+
amplitude *= noiseFalloff;
|
|
668
|
+
frequency *= 2;
|
|
669
|
+
}
|
|
670
|
+
return total / maxValue;
|
|
671
|
+
},
|
|
672
|
+
noiseSeed: (seed) => {
|
|
673
|
+
noiseSeedValue = seed;
|
|
674
|
+
noiseFunc = createSeededNoise(seed);
|
|
675
|
+
},
|
|
676
|
+
noiseDetail: (lod, falloff) => {
|
|
677
|
+
noiseOctaves = Math.max(1, Math.min(8, lod));
|
|
678
|
+
if (falloff !== void 0) {
|
|
679
|
+
noiseFalloff = Math.max(0, Math.min(1, falloff));
|
|
680
|
+
}
|
|
681
|
+
},
|
|
682
|
+
// Noise extensions (v1.1) - use seeded noise internally
|
|
683
|
+
fbm: (x, y, octaves = 4, falloff = 0.5) => {
|
|
684
|
+
let total = 0;
|
|
685
|
+
let frequency = 1;
|
|
686
|
+
let amplitude = 1;
|
|
687
|
+
let maxValue = 0;
|
|
688
|
+
for (let i = 0; i < octaves; i++) {
|
|
689
|
+
total += noiseFunc(x * frequency, y * frequency) * amplitude;
|
|
690
|
+
maxValue += amplitude;
|
|
691
|
+
amplitude *= falloff;
|
|
692
|
+
frequency *= 2;
|
|
693
|
+
}
|
|
694
|
+
return total / maxValue;
|
|
695
|
+
},
|
|
696
|
+
ridgedNoise: (x, y) => {
|
|
697
|
+
let total = 0;
|
|
698
|
+
let frequency = 1;
|
|
699
|
+
let amplitude = 1;
|
|
700
|
+
let maxValue = 0;
|
|
701
|
+
for (let i = 0; i < 4; i++) {
|
|
702
|
+
const n = noiseFunc(x * frequency, y * frequency);
|
|
703
|
+
total += (1 - Math.abs(n * 2 - 1)) * amplitude;
|
|
704
|
+
maxValue += amplitude;
|
|
705
|
+
amplitude *= 0.5;
|
|
706
|
+
frequency *= 2;
|
|
707
|
+
}
|
|
708
|
+
return total / maxValue;
|
|
709
|
+
},
|
|
710
|
+
curlNoise: (x, y) => {
|
|
711
|
+
const eps = 1e-4;
|
|
712
|
+
const n1 = noiseFunc(x + eps, y);
|
|
713
|
+
const n2 = noiseFunc(x - eps, y);
|
|
714
|
+
const n3 = noiseFunc(x, y + eps);
|
|
715
|
+
const n4 = noiseFunc(x, y - eps);
|
|
716
|
+
const dx = (n1 - n2) / (2 * eps);
|
|
717
|
+
const dy = (n3 - n4) / (2 * eps);
|
|
718
|
+
return { x: -dy, y: dx };
|
|
719
|
+
},
|
|
720
|
+
map: (value, start1, stop1, start2, stop2) => {
|
|
721
|
+
return start2 + (stop2 - start2) * ((value - start1) / (stop1 - start1));
|
|
722
|
+
},
|
|
723
|
+
constrain: (n, low, high) => {
|
|
724
|
+
return Math.max(low, Math.min(high, n));
|
|
725
|
+
},
|
|
726
|
+
lerp: (start, stop, amt) => {
|
|
727
|
+
return start + (stop - start) * amt;
|
|
728
|
+
},
|
|
729
|
+
dist: (x1, y1, x2, y2) => {
|
|
730
|
+
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
|
|
731
|
+
},
|
|
732
|
+
mag: (x, y) => {
|
|
733
|
+
return Math.sqrt(x * x + y * y);
|
|
734
|
+
},
|
|
735
|
+
norm: (value, start, stop) => {
|
|
736
|
+
return (value - start) / (stop - start);
|
|
737
|
+
},
|
|
738
|
+
// Trig functions
|
|
739
|
+
sin: Math.sin,
|
|
740
|
+
cos: Math.cos,
|
|
741
|
+
tan: Math.tan,
|
|
742
|
+
asin: Math.asin,
|
|
743
|
+
acos: Math.acos,
|
|
744
|
+
atan: Math.atan,
|
|
745
|
+
atan2: Math.atan2,
|
|
746
|
+
radians: (degrees) => degrees * (Math.PI / 180),
|
|
747
|
+
degrees: (radians) => radians * (180 / Math.PI),
|
|
748
|
+
// Utility functions
|
|
749
|
+
abs: Math.abs,
|
|
750
|
+
ceil: Math.ceil,
|
|
751
|
+
floor: Math.floor,
|
|
752
|
+
round: Math.round,
|
|
753
|
+
sqrt: Math.sqrt,
|
|
754
|
+
pow: Math.pow,
|
|
755
|
+
exp: Math.exp,
|
|
756
|
+
log: Math.log,
|
|
757
|
+
min: Math.min,
|
|
758
|
+
max: Math.max,
|
|
759
|
+
int: (n) => Math.floor(n),
|
|
760
|
+
sq: (n) => n * n,
|
|
761
|
+
fract: (n) => n - Math.floor(n),
|
|
762
|
+
sign: (n) => n > 0 ? 1 : n < 0 ? -1 : 0,
|
|
763
|
+
// Vector helpers (v1.1) - plain objects, no mutation
|
|
764
|
+
vec: (x, y) => ({ x, y }),
|
|
765
|
+
vecAdd: (a, b) => ({
|
|
766
|
+
x: a.x + b.x,
|
|
767
|
+
y: a.y + b.y
|
|
768
|
+
}),
|
|
769
|
+
vecSub: (a, b) => ({
|
|
770
|
+
x: a.x - b.x,
|
|
771
|
+
y: a.y - b.y
|
|
772
|
+
}),
|
|
773
|
+
vecMult: (v, s) => ({
|
|
774
|
+
x: v.x * s,
|
|
775
|
+
y: v.y * s
|
|
776
|
+
}),
|
|
777
|
+
vecMag: (v) => Math.sqrt(v.x * v.x + v.y * v.y),
|
|
778
|
+
vecNorm: (v) => {
|
|
779
|
+
const m = Math.sqrt(v.x * v.x + v.y * v.y);
|
|
780
|
+
return m === 0 ? { x: 0, y: 0 } : { x: v.x / m, y: v.y / m };
|
|
781
|
+
},
|
|
782
|
+
vecDist: (a, b) => {
|
|
783
|
+
const dx = b.x - a.x;
|
|
784
|
+
const dy = b.y - a.y;
|
|
785
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
786
|
+
},
|
|
787
|
+
// Easing functions (v1.1) - pure functions, t ∈ [0,1] → [0,1]
|
|
788
|
+
easeIn: (t) => t * t,
|
|
789
|
+
easeOut: (t) => t * (2 - t),
|
|
790
|
+
easeInOut: (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
|
|
791
|
+
easeCubic: (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
|
|
792
|
+
easeExpo: (t) => {
|
|
793
|
+
if (t === 0) return 0;
|
|
794
|
+
if (t === 1) return 1;
|
|
795
|
+
if (t < 0.5) {
|
|
796
|
+
return Math.pow(2, 20 * t - 10) / 2;
|
|
797
|
+
}
|
|
798
|
+
return (2 - Math.pow(2, -20 * t + 10)) / 2;
|
|
799
|
+
},
|
|
800
|
+
// Text functions
|
|
801
|
+
text: (str, x, y) => {
|
|
802
|
+
ctx.font = `${currentTextSize}px ${currentTextFont}`;
|
|
803
|
+
ctx.textAlign = currentTextAlignH;
|
|
804
|
+
ctx.textBaseline = currentTextAlignV;
|
|
805
|
+
if (fillEnabled) {
|
|
806
|
+
ctx.fillStyle = currentFill;
|
|
807
|
+
ctx.fillText(String(str), x, y);
|
|
808
|
+
}
|
|
809
|
+
if (strokeEnabled) {
|
|
810
|
+
ctx.strokeStyle = currentStroke;
|
|
811
|
+
ctx.strokeText(String(str), x, y);
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
textSize: (size) => {
|
|
815
|
+
currentTextSize = size;
|
|
816
|
+
ctx.font = `${currentTextSize}px ${currentTextFont}`;
|
|
817
|
+
},
|
|
818
|
+
textFont: (font) => {
|
|
819
|
+
currentTextFont = font;
|
|
820
|
+
ctx.font = `${currentTextSize}px ${currentTextFont}`;
|
|
821
|
+
},
|
|
822
|
+
textAlign: (horizAlign, vertAlign) => {
|
|
823
|
+
const hMap = {
|
|
824
|
+
"left": "left",
|
|
825
|
+
"LEFT": "left",
|
|
826
|
+
"center": "center",
|
|
827
|
+
"CENTER": "center",
|
|
828
|
+
"right": "right",
|
|
829
|
+
"RIGHT": "right"
|
|
830
|
+
};
|
|
831
|
+
const vMap = {
|
|
832
|
+
"top": "top",
|
|
833
|
+
"TOP": "top",
|
|
834
|
+
"bottom": "bottom",
|
|
835
|
+
"BOTTOM": "bottom",
|
|
836
|
+
"center": "middle",
|
|
837
|
+
"CENTER": "middle",
|
|
838
|
+
"baseline": "alphabetic",
|
|
839
|
+
"BASELINE": "alphabetic"
|
|
840
|
+
};
|
|
841
|
+
currentTextAlignH = hMap[horizAlign] || "left";
|
|
842
|
+
if (vertAlign) {
|
|
843
|
+
currentTextAlignV = vMap[vertAlign] || "alphabetic";
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
textWidth: (str) => {
|
|
847
|
+
ctx.font = `${currentTextSize}px ${currentTextFont}`;
|
|
848
|
+
return ctx.measureText(String(str)).width;
|
|
849
|
+
},
|
|
850
|
+
// Pixel manipulation (v1.2)
|
|
851
|
+
loadPixels: () => {
|
|
852
|
+
imageDataObj = ctx.getImageData(0, 0, width, height);
|
|
853
|
+
pixelData = imageDataObj.data;
|
|
854
|
+
p.pixels = pixelData;
|
|
855
|
+
},
|
|
856
|
+
updatePixels: () => {
|
|
857
|
+
if (imageDataObj && pixelData) {
|
|
858
|
+
ctx.putImageData(imageDataObj, 0, 0);
|
|
859
|
+
}
|
|
860
|
+
},
|
|
861
|
+
pixels: null,
|
|
862
|
+
get: (x, y) => {
|
|
863
|
+
const imgData = ctx.getImageData(Math.floor(x), Math.floor(y), 1, 1);
|
|
864
|
+
return [imgData.data[0], imgData.data[1], imgData.data[2], imgData.data[3]];
|
|
865
|
+
},
|
|
866
|
+
set: (x, y, c) => {
|
|
867
|
+
const fx = Math.floor(x);
|
|
868
|
+
const fy = Math.floor(y);
|
|
869
|
+
if (Array.isArray(c)) {
|
|
870
|
+
const imgData = ctx.createImageData(1, 1);
|
|
871
|
+
imgData.data[0] = c[0];
|
|
872
|
+
imgData.data[1] = c[1];
|
|
873
|
+
imgData.data[2] = c[2];
|
|
874
|
+
imgData.data[3] = c[3] ?? 255;
|
|
875
|
+
ctx.putImageData(imgData, fx, fy);
|
|
876
|
+
} else {
|
|
877
|
+
const parsed = parseCssColor(c);
|
|
878
|
+
if (parsed) {
|
|
879
|
+
const imgData = ctx.createImageData(1, 1);
|
|
880
|
+
imgData.data[0] = parsed.r;
|
|
881
|
+
imgData.data[1] = parsed.g;
|
|
882
|
+
imgData.data[2] = parsed.b;
|
|
883
|
+
imgData.data[3] = Math.round(parsed.a * 255);
|
|
884
|
+
ctx.putImageData(imgData, fx, fy);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
// Offscreen graphics (v1.2)
|
|
889
|
+
createGraphics: (w, h) => {
|
|
890
|
+
const offscreenCanvas = document.createElement("canvas");
|
|
891
|
+
offscreenCanvas.width = w;
|
|
892
|
+
offscreenCanvas.height = h;
|
|
893
|
+
const pg = createP5Runtime(offscreenCanvas, w, h, config);
|
|
894
|
+
pg._canvas = offscreenCanvas;
|
|
895
|
+
return pg;
|
|
896
|
+
},
|
|
897
|
+
// Draw image or graphics object to canvas
|
|
898
|
+
image: (src, x, y, w, h) => {
|
|
899
|
+
const srcCanvas = src._canvas || src;
|
|
900
|
+
if (srcCanvas instanceof HTMLCanvasElement) {
|
|
901
|
+
const dw = w ?? srcCanvas.width;
|
|
902
|
+
const dh = h ?? srcCanvas.height;
|
|
903
|
+
ctx.drawImage(srcCanvas, x, y, dw, dh);
|
|
904
|
+
}
|
|
905
|
+
},
|
|
906
|
+
// Loop control (no-ops for SDK)
|
|
907
|
+
noLoop: () => {
|
|
908
|
+
},
|
|
909
|
+
loop: () => {
|
|
910
|
+
},
|
|
911
|
+
redraw: () => {
|
|
912
|
+
},
|
|
913
|
+
frameRate: (fps) => {
|
|
914
|
+
},
|
|
915
|
+
// totalFrames placeholder (injected by engine)
|
|
916
|
+
totalFrames: 0
|
|
917
|
+
};
|
|
918
|
+
return p;
|
|
919
|
+
}
|
|
920
|
+
function injectTimeVariables(p, time) {
|
|
921
|
+
p.frameCount = time.frameCount;
|
|
922
|
+
p.t = time.t;
|
|
923
|
+
p.time = time.time;
|
|
924
|
+
p.tGlobal = time.tGlobal;
|
|
925
|
+
p.totalFrames = time.totalFrames;
|
|
926
|
+
}
|
|
927
|
+
var VAR_COUNT = 10;
|
|
928
|
+
var VAR_MIN = 0;
|
|
929
|
+
var VAR_MAX = 100;
|
|
930
|
+
function createProtocolVAR(vars) {
|
|
931
|
+
const normalizedVars = [];
|
|
932
|
+
for (let i = 0; i < VAR_COUNT; i++) {
|
|
933
|
+
normalizedVars[i] = vars?.[i] ?? 0;
|
|
934
|
+
}
|
|
935
|
+
const frozenVars = Object.freeze(normalizedVars);
|
|
936
|
+
return new Proxy(frozenVars, {
|
|
937
|
+
set(_target, prop, _value) {
|
|
938
|
+
const propName = typeof prop === "symbol" ? prop.toString() : prop;
|
|
939
|
+
throw new Error(
|
|
940
|
+
`[Code Mode Protocol Error] VAR is read-only. Cannot write to VAR[${propName}]. VAR[0..9] are protocol inputs, not sketch state.`
|
|
941
|
+
);
|
|
942
|
+
},
|
|
943
|
+
deleteProperty(_target, prop) {
|
|
944
|
+
const propName = typeof prop === "symbol" ? prop.toString() : prop;
|
|
945
|
+
throw new Error(
|
|
946
|
+
`[Code Mode Protocol Error] VAR is read-only. Cannot delete VAR[${propName}].`
|
|
947
|
+
);
|
|
948
|
+
},
|
|
949
|
+
defineProperty(_target, prop) {
|
|
950
|
+
const propName = typeof prop === "symbol" ? prop.toString() : prop;
|
|
951
|
+
throw new Error(
|
|
952
|
+
`[Code Mode Protocol Error] Cannot define new VAR properties. VAR is fixed at 10 elements (VAR[0..9]). Attempted: ${propName}`
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
function injectProtocolVariables(p, vars) {
|
|
958
|
+
p.VAR = createProtocolVAR(vars);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// execution-sandbox.ts
|
|
962
|
+
function createForbiddenStub(name) {
|
|
963
|
+
const stub = function() {
|
|
964
|
+
throw new Error(`[Code Mode Protocol Error] Forbidden API: ${name}`);
|
|
965
|
+
};
|
|
966
|
+
return new Proxy(stub, {
|
|
967
|
+
get(_target, prop) {
|
|
968
|
+
if (prop === Symbol.toPrimitive || prop === "toString" || prop === "valueOf") {
|
|
969
|
+
return () => {
|
|
970
|
+
throw new Error(`[Code Mode Protocol Error] Forbidden API: ${name}`);
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
throw new Error(`[Code Mode Protocol Error] Forbidden API: ${name}.${String(prop)}`);
|
|
974
|
+
},
|
|
975
|
+
apply() {
|
|
976
|
+
throw new Error(`[Code Mode Protocol Error] Forbidden API: ${name}()`);
|
|
977
|
+
},
|
|
978
|
+
construct() {
|
|
979
|
+
throw new Error(`[Code Mode Protocol Error] Forbidden API: new ${name}()`);
|
|
980
|
+
},
|
|
981
|
+
set() {
|
|
982
|
+
throw new Error(`[Code Mode Protocol Error] Forbidden API: ${name} (assignment blocked)`);
|
|
983
|
+
},
|
|
984
|
+
has() {
|
|
985
|
+
return true;
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
function createForbiddenObject(name) {
|
|
990
|
+
return new Proxy({}, {
|
|
991
|
+
get(_target, prop) {
|
|
992
|
+
if (prop === Symbol.toPrimitive || prop === "toString" || prop === "valueOf") {
|
|
993
|
+
return () => {
|
|
994
|
+
throw new Error(`[Code Mode Protocol Error] Forbidden API: ${name}`);
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
throw new Error(`[Code Mode Protocol Error] Forbidden API: ${name}.${String(prop)}`);
|
|
998
|
+
},
|
|
999
|
+
set() {
|
|
1000
|
+
throw new Error(`[Code Mode Protocol Error] Forbidden API: ${name} (assignment blocked)`);
|
|
1001
|
+
},
|
|
1002
|
+
has() {
|
|
1003
|
+
return true;
|
|
1004
|
+
},
|
|
1005
|
+
apply() {
|
|
1006
|
+
throw new Error(`[Code Mode Protocol Error] Forbidden API: ${name}()`);
|
|
1007
|
+
},
|
|
1008
|
+
construct() {
|
|
1009
|
+
throw new Error(`[Code Mode Protocol Error] Forbidden API: new ${name}()`);
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
var FORBIDDEN_APIS = {
|
|
1014
|
+
Date: createForbiddenStub("Date"),
|
|
1015
|
+
performance: createForbiddenObject("performance"),
|
|
1016
|
+
process: createForbiddenObject("process"),
|
|
1017
|
+
navigator: createForbiddenObject("navigator"),
|
|
1018
|
+
globalThis: createForbiddenObject("globalThis"),
|
|
1019
|
+
crypto: createForbiddenObject("crypto"),
|
|
1020
|
+
setTimeout: createForbiddenStub("setTimeout"),
|
|
1021
|
+
setInterval: createForbiddenStub("setInterval"),
|
|
1022
|
+
clearTimeout: createForbiddenStub("clearTimeout"),
|
|
1023
|
+
clearInterval: createForbiddenStub("clearInterval"),
|
|
1024
|
+
requestAnimationFrame: createForbiddenStub("requestAnimationFrame"),
|
|
1025
|
+
cancelAnimationFrame: createForbiddenStub("cancelAnimationFrame"),
|
|
1026
|
+
fetch: createForbiddenStub("fetch"),
|
|
1027
|
+
XMLHttpRequest: createForbiddenStub("XMLHttpRequest"),
|
|
1028
|
+
WebSocket: createForbiddenStub("WebSocket"),
|
|
1029
|
+
document: createForbiddenObject("document"),
|
|
1030
|
+
window: createForbiddenObject("window"),
|
|
1031
|
+
self: createForbiddenObject("self"),
|
|
1032
|
+
top: createForbiddenObject("top"),
|
|
1033
|
+
parent: createForbiddenObject("parent"),
|
|
1034
|
+
frames: createForbiddenObject("frames"),
|
|
1035
|
+
location: createForbiddenObject("location"),
|
|
1036
|
+
history: createForbiddenObject("history"),
|
|
1037
|
+
localStorage: createForbiddenObject("localStorage"),
|
|
1038
|
+
sessionStorage: createForbiddenObject("sessionStorage"),
|
|
1039
|
+
indexedDB: createForbiddenObject("indexedDB"),
|
|
1040
|
+
caches: createForbiddenObject("caches"),
|
|
1041
|
+
Notification: createForbiddenStub("Notification"),
|
|
1042
|
+
Worker: createForbiddenStub("Worker"),
|
|
1043
|
+
SharedWorker: createForbiddenStub("SharedWorker"),
|
|
1044
|
+
ServiceWorker: createForbiddenObject("ServiceWorker"),
|
|
1045
|
+
Blob: createForbiddenStub("Blob"),
|
|
1046
|
+
File: createForbiddenStub("File"),
|
|
1047
|
+
FileReader: createForbiddenStub("FileReader"),
|
|
1048
|
+
URL: createForbiddenStub("URL"),
|
|
1049
|
+
URLSearchParams: createForbiddenStub("URLSearchParams"),
|
|
1050
|
+
Headers: createForbiddenStub("Headers"),
|
|
1051
|
+
Request: createForbiddenStub("Request"),
|
|
1052
|
+
Response: createForbiddenStub("Response"),
|
|
1053
|
+
EventSource: createForbiddenStub("EventSource"),
|
|
1054
|
+
Image: createForbiddenStub("Image"),
|
|
1055
|
+
Audio: createForbiddenStub("Audio"),
|
|
1056
|
+
Video: createForbiddenStub("Video"),
|
|
1057
|
+
eval: createForbiddenStub("eval"),
|
|
1058
|
+
Function: createForbiddenStub("Function")
|
|
1059
|
+
};
|
|
1060
|
+
function createSafeMath() {
|
|
1061
|
+
const safeMath = Object.create(Math);
|
|
1062
|
+
Object.defineProperty(safeMath, "random", {
|
|
1063
|
+
get() {
|
|
1064
|
+
throw new Error("[Code Mode Protocol Error] Forbidden API: Math.random() \u2014 use random() instead (seeded)");
|
|
1065
|
+
},
|
|
1066
|
+
configurable: false,
|
|
1067
|
+
enumerable: true
|
|
1068
|
+
});
|
|
1069
|
+
return Object.freeze(safeMath);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// static-engine.ts
|
|
1073
|
+
var nodeCanvasModule = null;
|
|
1074
|
+
async function getNodeCanvas() {
|
|
1075
|
+
if (nodeCanvasModule) return nodeCanvasModule;
|
|
1076
|
+
if (typeof window === "undefined") {
|
|
1077
|
+
try {
|
|
1078
|
+
const { createRequire } = await import('module');
|
|
1079
|
+
const require2 = createRequire(import.meta.url);
|
|
1080
|
+
nodeCanvasModule = require2("canvas");
|
|
1081
|
+
return nodeCanvasModule;
|
|
1082
|
+
} catch {
|
|
1083
|
+
return null;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
return null;
|
|
1087
|
+
}
|
|
1088
|
+
async function createRuntimeCanvas(width, height) {
|
|
1089
|
+
if (typeof document !== "undefined" && typeof document.createElement === "function") {
|
|
1090
|
+
const canvas = document.createElement("canvas");
|
|
1091
|
+
canvas.width = width;
|
|
1092
|
+
canvas.height = height;
|
|
1093
|
+
return canvas;
|
|
1094
|
+
}
|
|
1095
|
+
const nodeCanvas = await getNodeCanvas();
|
|
1096
|
+
if (nodeCanvas && nodeCanvas.createCanvas) {
|
|
1097
|
+
return nodeCanvas.createCanvas(width, height);
|
|
1098
|
+
}
|
|
1099
|
+
throw new Error(
|
|
1100
|
+
"[Code Mode Protocol Error] Headless canvas unavailable. Install `canvas` for oracle execution."
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
async function runStaticMode(config, options) {
|
|
1104
|
+
const { code, seed, vars, onPreview, onProgress, onComplete, onError, returnImageData } = options;
|
|
1105
|
+
const width = config.width ?? DEFAULT_CONFIG.width;
|
|
1106
|
+
const height = config.height ?? DEFAULT_CONFIG.height;
|
|
1107
|
+
try {
|
|
1108
|
+
onProgress?.({
|
|
1109
|
+
phase: "setup",
|
|
1110
|
+
percent: 0,
|
|
1111
|
+
message: "Initializing canvas..."
|
|
1112
|
+
});
|
|
1113
|
+
const canvas = await createRuntimeCanvas(width, height);
|
|
1114
|
+
const p = createP5Runtime(canvas, width, height, { seed });
|
|
1115
|
+
injectTimeVariables(p, {
|
|
1116
|
+
frameCount: 0,
|
|
1117
|
+
t: 0,
|
|
1118
|
+
time: 0,
|
|
1119
|
+
tGlobal: 0,
|
|
1120
|
+
totalFrames: 1
|
|
1121
|
+
// Static mode has 1 frame
|
|
1122
|
+
});
|
|
1123
|
+
injectProtocolVariables(p, vars);
|
|
1124
|
+
onProgress?.({
|
|
1125
|
+
phase: "setup",
|
|
1126
|
+
percent: 10,
|
|
1127
|
+
message: "Parsing code..."
|
|
1128
|
+
});
|
|
1129
|
+
const setupMatch = code.match(/function\s+setup\s*\(\s*\)\s*\{([\s\S]*?)\}(?=\s*function|\s*$)/);
|
|
1130
|
+
const setupCode = setupMatch ? setupMatch[1].trim() : code;
|
|
1131
|
+
const forbiddenPatterns = ["setTimeout", "setInterval", "requestAnimationFrame"];
|
|
1132
|
+
for (const pattern of forbiddenPatterns) {
|
|
1133
|
+
if (code.includes(pattern)) {
|
|
1134
|
+
throw new Error(`Forbidden async timing function: ${pattern}`);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
onProgress?.({
|
|
1138
|
+
phase: "rendering",
|
|
1139
|
+
percent: 30,
|
|
1140
|
+
message: "Executing setup()..."
|
|
1141
|
+
});
|
|
1142
|
+
const safeMath = createSafeMath();
|
|
1143
|
+
const forbiddenKeys = Object.keys(FORBIDDEN_APIS);
|
|
1144
|
+
const wrappedSetup = new Function(
|
|
1145
|
+
"p",
|
|
1146
|
+
"frameCount",
|
|
1147
|
+
"t",
|
|
1148
|
+
"time",
|
|
1149
|
+
"tGlobal",
|
|
1150
|
+
"VAR",
|
|
1151
|
+
"Math",
|
|
1152
|
+
...forbiddenKeys,
|
|
1153
|
+
`with(p) { ${setupCode} }`
|
|
1154
|
+
);
|
|
1155
|
+
const forbiddenValues = forbiddenKeys.map((k) => FORBIDDEN_APIS[k]);
|
|
1156
|
+
wrappedSetup(p, 0, 0, 0, 0, p.VAR, safeMath, ...forbiddenValues);
|
|
1157
|
+
onPreview?.(canvas);
|
|
1158
|
+
onProgress?.({
|
|
1159
|
+
phase: "encoding",
|
|
1160
|
+
percent: 70,
|
|
1161
|
+
message: returnImageData ? "Capturing ImageData..." : "Capturing PNG..."
|
|
1162
|
+
});
|
|
1163
|
+
const ctx = canvas.getContext("2d");
|
|
1164
|
+
if (!ctx) {
|
|
1165
|
+
throw new Error("Failed to acquire 2D context");
|
|
1166
|
+
}
|
|
1167
|
+
const imageData = ctx.getImageData(0, 0, width, height);
|
|
1168
|
+
if (returnImageData) {
|
|
1169
|
+
onProgress?.({
|
|
1170
|
+
phase: "complete",
|
|
1171
|
+
percent: 100,
|
|
1172
|
+
message: "Complete"
|
|
1173
|
+
});
|
|
1174
|
+
onComplete({
|
|
1175
|
+
type: "image",
|
|
1176
|
+
imageData
|
|
1177
|
+
});
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
const blob = await new Promise((resolve, reject) => {
|
|
1181
|
+
canvas.toBlob(
|
|
1182
|
+
(b) => b ? resolve(b) : reject(new Error("Failed to capture PNG")),
|
|
1183
|
+
"image/png"
|
|
1184
|
+
);
|
|
1185
|
+
});
|
|
1186
|
+
onProgress?.({
|
|
1187
|
+
phase: "complete",
|
|
1188
|
+
percent: 100,
|
|
1189
|
+
message: "Complete"
|
|
1190
|
+
});
|
|
1191
|
+
onComplete({
|
|
1192
|
+
type: "image",
|
|
1193
|
+
blob
|
|
1194
|
+
});
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1197
|
+
onError?.(err);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// loop-engine.ts
|
|
1202
|
+
var isCancelled = false;
|
|
1203
|
+
function cancelLoopMode() {
|
|
1204
|
+
isCancelled = true;
|
|
1205
|
+
}
|
|
1206
|
+
async function runLoopMode(config, options) {
|
|
1207
|
+
const { code, seed, vars, onPreview, onProgress, onComplete, onError } = options;
|
|
1208
|
+
const width = config.width ?? DEFAULT_CONFIG.width;
|
|
1209
|
+
const height = config.height ?? DEFAULT_CONFIG.height;
|
|
1210
|
+
const duration = Math.max(
|
|
1211
|
+
DEFAULT_CONFIG.minDuration,
|
|
1212
|
+
Math.min(DEFAULT_CONFIG.maxDuration, config.duration ?? DEFAULT_CONFIG.duration)
|
|
1213
|
+
);
|
|
1214
|
+
const fps = config.fps ?? DEFAULT_CONFIG.fps;
|
|
1215
|
+
const totalFrames = Math.floor(duration * fps);
|
|
1216
|
+
isCancelled = false;
|
|
1217
|
+
try {
|
|
1218
|
+
onProgress?.({
|
|
1219
|
+
phase: "setup",
|
|
1220
|
+
percent: 0,
|
|
1221
|
+
message: "Initializing canvas..."
|
|
1222
|
+
});
|
|
1223
|
+
const canvas = document.createElement("canvas");
|
|
1224
|
+
canvas.width = width;
|
|
1225
|
+
canvas.height = height;
|
|
1226
|
+
const p = createP5Runtime(canvas, width, height, { seed });
|
|
1227
|
+
injectProtocolVariables(p, vars);
|
|
1228
|
+
const hasDrawFunction = /function\s+draw\s*\(\s*\)/.test(code);
|
|
1229
|
+
if (!hasDrawFunction) {
|
|
1230
|
+
throw new Error("Loop Mode requires a draw() function.");
|
|
1231
|
+
}
|
|
1232
|
+
const forbiddenPatterns = [
|
|
1233
|
+
{ pattern: /noLoop\s*\(\s*\)/, name: "noLoop()" },
|
|
1234
|
+
{ pattern: /setTimeout\s*\(/, name: "setTimeout" },
|
|
1235
|
+
{ pattern: /setInterval\s*\(/, name: "setInterval" },
|
|
1236
|
+
{ pattern: /requestAnimationFrame\s*\(/, name: "requestAnimationFrame" }
|
|
1237
|
+
];
|
|
1238
|
+
for (const { pattern, name } of forbiddenPatterns) {
|
|
1239
|
+
if (pattern.test(code)) {
|
|
1240
|
+
throw new Error(`Forbidden function in Loop Mode: ${name}`);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
onProgress?.({
|
|
1244
|
+
phase: "setup",
|
|
1245
|
+
percent: 5,
|
|
1246
|
+
message: "Parsing code..."
|
|
1247
|
+
});
|
|
1248
|
+
const setupMatch = code.match(/function\s+setup\s*\(\s*\)\s*\{([\s\S]*?)\}(?=\s*function|\s*$)/);
|
|
1249
|
+
const drawMatch = code.match(/function\s+draw\s*\(\s*\)\s*\{([\s\S]*?)\}(?=\s*function|\s*$)/);
|
|
1250
|
+
const setupCode = setupMatch ? setupMatch[1].trim() : "";
|
|
1251
|
+
const drawCode = drawMatch ? drawMatch[1].trim() : "";
|
|
1252
|
+
if (!drawCode) {
|
|
1253
|
+
throw new Error("Loop Mode requires a draw() function with content.");
|
|
1254
|
+
}
|
|
1255
|
+
p.totalFrames = totalFrames;
|
|
1256
|
+
const safeMath = createSafeMath();
|
|
1257
|
+
const forbiddenKeys = Object.keys(FORBIDDEN_APIS);
|
|
1258
|
+
const wrappedSetup = new Function(
|
|
1259
|
+
"p",
|
|
1260
|
+
"frameCount",
|
|
1261
|
+
"t",
|
|
1262
|
+
"time",
|
|
1263
|
+
"tGlobal",
|
|
1264
|
+
"VAR",
|
|
1265
|
+
"totalFrames",
|
|
1266
|
+
"Math",
|
|
1267
|
+
...forbiddenKeys,
|
|
1268
|
+
`with(p) { ${setupCode} }`
|
|
1269
|
+
);
|
|
1270
|
+
const wrappedDraw = new Function(
|
|
1271
|
+
"p",
|
|
1272
|
+
"frameCount",
|
|
1273
|
+
"t",
|
|
1274
|
+
"time",
|
|
1275
|
+
"tGlobal",
|
|
1276
|
+
"VAR",
|
|
1277
|
+
"totalFrames",
|
|
1278
|
+
"Math",
|
|
1279
|
+
...forbiddenKeys,
|
|
1280
|
+
`with(p) { ${drawCode} }`
|
|
1281
|
+
);
|
|
1282
|
+
const forbiddenValues = forbiddenKeys.map((k) => FORBIDDEN_APIS[k]);
|
|
1283
|
+
onProgress?.({
|
|
1284
|
+
phase: "setup",
|
|
1285
|
+
percent: 10,
|
|
1286
|
+
message: "Executing setup()..."
|
|
1287
|
+
});
|
|
1288
|
+
wrappedSetup(p, 0, 0, 0, 0, p.VAR, totalFrames, safeMath, ...forbiddenValues);
|
|
1289
|
+
const frames = [];
|
|
1290
|
+
onProgress?.({
|
|
1291
|
+
phase: "rendering",
|
|
1292
|
+
frame: 0,
|
|
1293
|
+
totalFrames,
|
|
1294
|
+
percent: 10,
|
|
1295
|
+
message: `Rendering frames (0/${totalFrames})...`
|
|
1296
|
+
});
|
|
1297
|
+
for (let frame = 0; frame < totalFrames; frame++) {
|
|
1298
|
+
if (isCancelled) {
|
|
1299
|
+
throw new Error("Rendering cancelled");
|
|
1300
|
+
}
|
|
1301
|
+
const t = frame / totalFrames;
|
|
1302
|
+
const time = t * duration;
|
|
1303
|
+
p.frameCount = frame;
|
|
1304
|
+
p.clear();
|
|
1305
|
+
p.blendMode("NORMAL");
|
|
1306
|
+
wrappedDraw(p, frame, t, time, t, p.VAR, totalFrames, safeMath, ...forbiddenValues);
|
|
1307
|
+
const blob = await new Promise((resolve, reject) => {
|
|
1308
|
+
canvas.toBlob(
|
|
1309
|
+
(b) => b ? resolve(b) : reject(new Error(`Failed to capture frame ${frame}`)),
|
|
1310
|
+
"image/png"
|
|
1311
|
+
);
|
|
1312
|
+
});
|
|
1313
|
+
frames.push(blob);
|
|
1314
|
+
if (frame === 0) {
|
|
1315
|
+
onPreview?.(canvas);
|
|
1316
|
+
}
|
|
1317
|
+
const percent = 10 + Math.floor(frame / totalFrames * 60);
|
|
1318
|
+
onProgress?.({
|
|
1319
|
+
phase: "rendering",
|
|
1320
|
+
frame: frame + 1,
|
|
1321
|
+
totalFrames,
|
|
1322
|
+
percent,
|
|
1323
|
+
message: `Rendering frames (${frame + 1}/${totalFrames})...`
|
|
1324
|
+
});
|
|
1325
|
+
if (frame % 10 === 0) {
|
|
1326
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
onProgress?.({
|
|
1330
|
+
phase: "encoding",
|
|
1331
|
+
frame: totalFrames,
|
|
1332
|
+
totalFrames,
|
|
1333
|
+
percent: 70,
|
|
1334
|
+
message: "Encoding video..."
|
|
1335
|
+
});
|
|
1336
|
+
const videoBlob = await encodeFramesToMP4(frames, fps, width, height, (progress) => {
|
|
1337
|
+
const percent = 70 + Math.floor(progress * 30);
|
|
1338
|
+
onProgress?.({
|
|
1339
|
+
phase: "encoding",
|
|
1340
|
+
frame: totalFrames,
|
|
1341
|
+
totalFrames,
|
|
1342
|
+
percent,
|
|
1343
|
+
message: `Encoding video (${Math.floor(progress * 100)}%)...`
|
|
1344
|
+
});
|
|
1345
|
+
});
|
|
1346
|
+
onProgress?.({
|
|
1347
|
+
phase: "complete",
|
|
1348
|
+
frame: totalFrames,
|
|
1349
|
+
totalFrames,
|
|
1350
|
+
percent: 100,
|
|
1351
|
+
message: "Complete"
|
|
1352
|
+
});
|
|
1353
|
+
const result = {
|
|
1354
|
+
type: "video",
|
|
1355
|
+
blob: videoBlob,
|
|
1356
|
+
frames: totalFrames,
|
|
1357
|
+
duration
|
|
1358
|
+
};
|
|
1359
|
+
onComplete(result);
|
|
1360
|
+
} catch (error) {
|
|
1361
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1362
|
+
onError?.(err);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
async function encodeFramesToMP4(frames, fps, width, height, onProgress) {
|
|
1366
|
+
const frameDataUrls = [];
|
|
1367
|
+
for (let i = 0; i < frames.length; i++) {
|
|
1368
|
+
const reader = new FileReader();
|
|
1369
|
+
const dataUrl = await new Promise((resolve, reject) => {
|
|
1370
|
+
reader.onload = () => resolve(reader.result);
|
|
1371
|
+
reader.onerror = reject;
|
|
1372
|
+
reader.readAsDataURL(frames[i]);
|
|
1373
|
+
});
|
|
1374
|
+
frameDataUrls.push(dataUrl);
|
|
1375
|
+
onProgress?.(i / frames.length * 0.3);
|
|
1376
|
+
}
|
|
1377
|
+
const response = await fetch("/api/encode-loop", {
|
|
1378
|
+
method: "POST",
|
|
1379
|
+
headers: { "Content-Type": "application/json" },
|
|
1380
|
+
body: JSON.stringify({
|
|
1381
|
+
frames: frameDataUrls,
|
|
1382
|
+
fps,
|
|
1383
|
+
width,
|
|
1384
|
+
height
|
|
1385
|
+
})
|
|
1386
|
+
});
|
|
1387
|
+
if (!response.ok) {
|
|
1388
|
+
const errorText = await response.text();
|
|
1389
|
+
throw new Error(`Video encoding failed: ${errorText}`);
|
|
1390
|
+
}
|
|
1391
|
+
onProgress?.(0.8);
|
|
1392
|
+
const data = await response.json();
|
|
1393
|
+
if (!data.video) {
|
|
1394
|
+
throw new Error("No video data returned from encoder");
|
|
1395
|
+
}
|
|
1396
|
+
const binaryString = atob(data.video.split(",")[1] || data.video);
|
|
1397
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
1398
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
1399
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
1400
|
+
}
|
|
1401
|
+
onProgress?.(1);
|
|
1402
|
+
return new Blob([bytes], { type: "video/mp4" });
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
// execute.ts
|
|
1406
|
+
function normalizeVars(vars) {
|
|
1407
|
+
if (!vars || !Array.isArray(vars)) {
|
|
1408
|
+
console.log("[CodeMode] No vars provided, using defaults [0,0,0,0,0,0,0,0,0,0]");
|
|
1409
|
+
return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
1410
|
+
}
|
|
1411
|
+
if (vars.length > 10) {
|
|
1412
|
+
throw new Error(`[Code Mode Protocol Error] VAR array must have at most 10 elements, got ${vars.length}`);
|
|
1413
|
+
}
|
|
1414
|
+
const result = [];
|
|
1415
|
+
for (let i = 0; i < vars.length; i++) {
|
|
1416
|
+
const v = vars[i];
|
|
1417
|
+
if (typeof v !== "number" || !Number.isFinite(v)) {
|
|
1418
|
+
throw new Error(`[Code Mode Protocol Error] VAR[${i}] must be a finite number, got ${typeof v === "number" ? v : typeof v}`);
|
|
1419
|
+
}
|
|
1420
|
+
if (v < 0 || v > 100) {
|
|
1421
|
+
throw new Error(`[Code Mode Protocol Error] VAR[${i}] = ${v} is out of range. Values must be 0-100.`);
|
|
1422
|
+
}
|
|
1423
|
+
result.push(v);
|
|
1424
|
+
}
|
|
1425
|
+
while (result.length < 10) {
|
|
1426
|
+
result.push(0);
|
|
1427
|
+
}
|
|
1428
|
+
return result;
|
|
1429
|
+
}
|
|
1430
|
+
function validateInput(input) {
|
|
1431
|
+
if (!input.source || typeof input.source !== "string") {
|
|
1432
|
+
throw new Error("[Code Mode Protocol Error] source is required and must be a string");
|
|
1433
|
+
}
|
|
1434
|
+
if (typeof input.width !== "number" || input.width <= 0) {
|
|
1435
|
+
throw new Error("[Code Mode Protocol Error] width must be a positive number");
|
|
1436
|
+
}
|
|
1437
|
+
if (typeof input.height !== "number" || input.height <= 0) {
|
|
1438
|
+
throw new Error("[Code Mode Protocol Error] height must be a positive number");
|
|
1439
|
+
}
|
|
1440
|
+
if (typeof input.seed !== "number") {
|
|
1441
|
+
throw new Error("[Code Mode Protocol Error] seed is required and must be a number");
|
|
1442
|
+
}
|
|
1443
|
+
if (input.mode !== "static" && input.mode !== "loop") {
|
|
1444
|
+
throw new Error('[Code Mode Protocol Error] mode must be "static" or "loop"');
|
|
1445
|
+
}
|
|
1446
|
+
if (input.mode === "loop") {
|
|
1447
|
+
if (typeof input.totalFrames !== "number" || input.totalFrames <= 0) {
|
|
1448
|
+
throw new Error("[Code Mode Protocol Error] totalFrames is required for loop mode and must be a positive number");
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
const forbiddenPatterns = [
|
|
1452
|
+
// Async timing (breaks determinism)
|
|
1453
|
+
{ pattern: /setTimeout\s*\(/, name: "setTimeout" },
|
|
1454
|
+
{ pattern: /setInterval\s*\(/, name: "setInterval" },
|
|
1455
|
+
{ pattern: /requestAnimationFrame\s*\(/, name: "requestAnimationFrame" },
|
|
1456
|
+
// Time-based entropy (breaks determinism)
|
|
1457
|
+
{ pattern: /Date\.now\s*\(/, name: "Date.now() \u2014 use time variable instead" },
|
|
1458
|
+
{ pattern: /new\s+Date\s*\(/, name: "new Date() \u2014 use time variable instead" },
|
|
1459
|
+
// Unseeded random (use random() instead)
|
|
1460
|
+
{ pattern: /Math\.random\s*\(/, name: "Math.random() \u2014 use random() instead (seeded)" },
|
|
1461
|
+
// External IO (breaks determinism)
|
|
1462
|
+
{ pattern: /fetch\s*\(/, name: "fetch() \u2014 external IO forbidden" },
|
|
1463
|
+
{ pattern: /XMLHttpRequest/, name: "XMLHttpRequest \u2014 external IO forbidden" },
|
|
1464
|
+
// Canvas is pre-initialized
|
|
1465
|
+
{ pattern: /createCanvas\s*\(/, name: "createCanvas() \u2014 canvas is pre-initialized" },
|
|
1466
|
+
// DOM manipulation forbidden
|
|
1467
|
+
{ pattern: /document\./, name: "DOM access \u2014 document.* forbidden" },
|
|
1468
|
+
{ pattern: /window\./, name: "DOM access \u2014 window.* forbidden" },
|
|
1469
|
+
// External imports forbidden
|
|
1470
|
+
{ pattern: /\bimport\s+/, name: "import \u2014 external imports forbidden" },
|
|
1471
|
+
{ pattern: /\brequire\s*\(/, name: "require() \u2014 external imports forbidden" }
|
|
1472
|
+
];
|
|
1473
|
+
for (const { pattern, name } of forbiddenPatterns) {
|
|
1474
|
+
if (pattern.test(input.source)) {
|
|
1475
|
+
throw new Error(`[Code Mode Protocol Error] Forbidden pattern: ${name}`);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
if (input.mode === "loop") {
|
|
1479
|
+
if (!/function\s+draw\s*\(\s*\)/.test(input.source)) {
|
|
1480
|
+
throw new Error("[Code Mode Protocol Error] Loop mode requires a draw() function");
|
|
1481
|
+
}
|
|
1482
|
+
if (/noLoop\s*\(\s*\)/.test(input.source)) {
|
|
1483
|
+
throw new Error("[Code Mode Protocol Error] noLoop() is forbidden in Loop mode");
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
function createMetadata(input, vars) {
|
|
1488
|
+
return {
|
|
1489
|
+
...PROTOCOL_IDENTITY,
|
|
1490
|
+
seed: input.seed,
|
|
1491
|
+
vars,
|
|
1492
|
+
width: input.width,
|
|
1493
|
+
height: input.height,
|
|
1494
|
+
mode: input.mode,
|
|
1495
|
+
...input.mode === "loop" && input.totalFrames ? { totalFrames: input.totalFrames } : {}
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
async function executeStatic(input, vars) {
|
|
1499
|
+
console.log("[CodeMode] Rendered via @nexart/codemode-sdk (Protocol v1.2.0)");
|
|
1500
|
+
console.log("[CodeMode] Execution: Static mode \u2014 delegating to static-engine");
|
|
1501
|
+
return new Promise((resolve, reject) => {
|
|
1502
|
+
runStaticMode(
|
|
1503
|
+
{
|
|
1504
|
+
width: input.width,
|
|
1505
|
+
height: input.height
|
|
1506
|
+
},
|
|
1507
|
+
{
|
|
1508
|
+
code: input.source,
|
|
1509
|
+
seed: input.seed,
|
|
1510
|
+
vars,
|
|
1511
|
+
onComplete: (result) => {
|
|
1512
|
+
resolve({
|
|
1513
|
+
image: "blob" in result ? result.blob : void 0,
|
|
1514
|
+
frames: "imageData" in result ? [result.imageData] : void 0,
|
|
1515
|
+
metadata: createMetadata(input, vars)
|
|
1516
|
+
});
|
|
1517
|
+
},
|
|
1518
|
+
onError: (error) => {
|
|
1519
|
+
reject(error);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
);
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
async function executeLoop(input, vars) {
|
|
1526
|
+
console.log("[CodeMode] Rendered via @nexart/codemode-sdk (Protocol v1.2.0)");
|
|
1527
|
+
console.log(`[CodeMode] Execution: Loop mode \u2014 delegating to loop-engine (${input.totalFrames} frames)`);
|
|
1528
|
+
const fps = DEFAULT_CONFIG.fps;
|
|
1529
|
+
const duration = (input.totalFrames || 60) / fps;
|
|
1530
|
+
return new Promise((resolve, reject) => {
|
|
1531
|
+
runLoopMode(
|
|
1532
|
+
{
|
|
1533
|
+
width: input.width,
|
|
1534
|
+
height: input.height,
|
|
1535
|
+
duration,
|
|
1536
|
+
fps
|
|
1537
|
+
},
|
|
1538
|
+
{
|
|
1539
|
+
code: input.source,
|
|
1540
|
+
seed: input.seed,
|
|
1541
|
+
vars,
|
|
1542
|
+
onComplete: (result) => {
|
|
1543
|
+
resolve({
|
|
1544
|
+
video: "blob" in result && result.type === "video" ? result.blob : void 0,
|
|
1545
|
+
metadata: createMetadata(input, vars)
|
|
1546
|
+
});
|
|
1547
|
+
},
|
|
1548
|
+
onError: (error) => {
|
|
1549
|
+
reject(error);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
);
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
async function executeCodeMode(input) {
|
|
1556
|
+
validateInput(input);
|
|
1557
|
+
const vars = normalizeVars(input.vars);
|
|
1558
|
+
console.log("[CodeMode] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
1559
|
+
console.log("[CodeMode] Protocol v1.2.0 \u2014 Phase 3 \u2014 HARD Enforcement");
|
|
1560
|
+
console.log(`[CodeMode] Mode: ${input.mode}`);
|
|
1561
|
+
console.log(`[CodeMode] Seed: ${input.seed}`);
|
|
1562
|
+
console.log(`[CodeMode] VAR: [${vars.join(", ")}]`);
|
|
1563
|
+
console.log("[CodeMode] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
1564
|
+
if (input.mode === "static") {
|
|
1565
|
+
return executeStatic(input, vars);
|
|
1566
|
+
} else {
|
|
1567
|
+
return executeLoop(input, vars);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
function validateCodeModeSource(source, mode) {
|
|
1571
|
+
const errors = [];
|
|
1572
|
+
const forbiddenPatterns = [
|
|
1573
|
+
{ pattern: /setTimeout\s*\(/, name: "setTimeout" },
|
|
1574
|
+
{ pattern: /setInterval\s*\(/, name: "setInterval" },
|
|
1575
|
+
{ pattern: /requestAnimationFrame\s*\(/, name: "requestAnimationFrame" },
|
|
1576
|
+
{ pattern: /Date\.now\s*\(/, name: "Date.now() \u2014 use time variable instead" },
|
|
1577
|
+
{ pattern: /new\s+Date\s*\(/, name: "new Date() \u2014 use time variable instead" },
|
|
1578
|
+
{ pattern: /Math\.random\s*\(/, name: "Math.random() \u2014 use random() instead (seeded)" },
|
|
1579
|
+
{ pattern: /fetch\s*\(/, name: "fetch() \u2014 external IO forbidden" },
|
|
1580
|
+
{ pattern: /XMLHttpRequest/, name: "XMLHttpRequest \u2014 external IO forbidden" },
|
|
1581
|
+
{ pattern: /createCanvas\s*\(/, name: "createCanvas() \u2014 canvas is pre-initialized" },
|
|
1582
|
+
{ pattern: /document\./, name: "DOM access \u2014 document.* forbidden" },
|
|
1583
|
+
{ pattern: /window\./, name: "DOM access \u2014 window.* forbidden" },
|
|
1584
|
+
{ pattern: /\bimport\s+/, name: "import \u2014 external imports forbidden" },
|
|
1585
|
+
{ pattern: /\brequire\s*\(/, name: "require() \u2014 external imports forbidden" }
|
|
1586
|
+
];
|
|
1587
|
+
for (const { pattern, name } of forbiddenPatterns) {
|
|
1588
|
+
if (pattern.test(source)) {
|
|
1589
|
+
errors.push(`Forbidden pattern: ${name}`);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
if (mode === "loop") {
|
|
1593
|
+
if (!/function\s+draw\s*\(\s*\)/.test(source)) {
|
|
1594
|
+
errors.push("Loop mode requires a draw() function");
|
|
1595
|
+
}
|
|
1596
|
+
if (/noLoop\s*\(\s*\)/.test(source)) {
|
|
1597
|
+
errors.push("noLoop() is forbidden in Loop mode");
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
return {
|
|
1601
|
+
valid: errors.length === 0,
|
|
1602
|
+
errors
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
// engine.ts
|
|
1607
|
+
function createEngine(config) {
|
|
1608
|
+
const resolvedConfig = {
|
|
1609
|
+
mode: config.mode,
|
|
1610
|
+
width: config.width ?? DEFAULT_CONFIG.width,
|
|
1611
|
+
height: config.height ?? DEFAULT_CONFIG.height,
|
|
1612
|
+
duration: config.duration ?? DEFAULT_CONFIG.duration,
|
|
1613
|
+
fps: config.fps ?? DEFAULT_CONFIG.fps
|
|
1614
|
+
};
|
|
1615
|
+
let isRunning = false;
|
|
1616
|
+
const run = async (options) => {
|
|
1617
|
+
if (isRunning) {
|
|
1618
|
+
throw new Error("Engine is already running. Call stop() first.");
|
|
1619
|
+
}
|
|
1620
|
+
isRunning = true;
|
|
1621
|
+
try {
|
|
1622
|
+
if (resolvedConfig.mode === "static") {
|
|
1623
|
+
await runStaticMode(resolvedConfig, options);
|
|
1624
|
+
} else if (resolvedConfig.mode === "loop") {
|
|
1625
|
+
await runLoopMode(resolvedConfig, options);
|
|
1626
|
+
} else {
|
|
1627
|
+
throw new Error(`Unknown mode: ${resolvedConfig.mode}`);
|
|
1628
|
+
}
|
|
1629
|
+
} finally {
|
|
1630
|
+
isRunning = false;
|
|
1631
|
+
}
|
|
1632
|
+
};
|
|
1633
|
+
const stop = () => {
|
|
1634
|
+
if (resolvedConfig.mode === "loop") {
|
|
1635
|
+
cancelLoopMode();
|
|
1636
|
+
}
|
|
1637
|
+
isRunning = false;
|
|
1638
|
+
};
|
|
1639
|
+
const getConfig = () => {
|
|
1640
|
+
return { ...resolvedConfig };
|
|
1641
|
+
};
|
|
1642
|
+
return {
|
|
1643
|
+
run,
|
|
1644
|
+
stop,
|
|
1645
|
+
getConfig
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// canonicalJson.ts
|
|
1650
|
+
function toCanonicalJson(value) {
|
|
1651
|
+
if (value === null) return "null";
|
|
1652
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
1653
|
+
if (typeof value === "number") {
|
|
1654
|
+
if (!isFinite(value)) throw new Error(`toCanonicalJson: non-finite number ${value}`);
|
|
1655
|
+
return JSON.stringify(value);
|
|
1656
|
+
}
|
|
1657
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
1658
|
+
if (Array.isArray(value)) {
|
|
1659
|
+
return "[" + value.map(toCanonicalJson).join(",") + "]";
|
|
1660
|
+
}
|
|
1661
|
+
if (typeof value === "object") {
|
|
1662
|
+
const obj = value;
|
|
1663
|
+
const keys = Object.keys(obj).sort();
|
|
1664
|
+
return "{" + keys.filter((k) => obj[k] !== void 0 && typeof obj[k] !== "function").map((k) => `${JSON.stringify(k)}:${toCanonicalJson(obj[k])}`).join(",") + "}";
|
|
1665
|
+
}
|
|
1666
|
+
throw new Error(`toCanonicalJson: unsupported type ${typeof value}`);
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
// nodeReceipt.ts
|
|
1670
|
+
hashes.sha512 = sha512;
|
|
1671
|
+
function base64urlToBytes(s) {
|
|
1672
|
+
const pad = s.length % 4;
|
|
1673
|
+
const base64 = s.replace(/-/g, "+").replace(/_/g, "/") + (pad ? "=".repeat(4 - pad) : "");
|
|
1674
|
+
if (typeof Buffer !== "undefined") {
|
|
1675
|
+
return new Uint8Array(Buffer.from(base64, "base64"));
|
|
1676
|
+
}
|
|
1677
|
+
const binary = atob(base64);
|
|
1678
|
+
return Uint8Array.from(binary, (c) => c.charCodeAt(0));
|
|
1679
|
+
}
|
|
1680
|
+
async function verifyNodeReceiptSignature(params) {
|
|
1681
|
+
try {
|
|
1682
|
+
const { receipt, signatureB64Url, key } = params;
|
|
1683
|
+
let pubKeyBytes;
|
|
1684
|
+
if (key.jwk) {
|
|
1685
|
+
if (key.jwk.kty !== "OKP" || key.jwk.crv !== "Ed25519") {
|
|
1686
|
+
return {
|
|
1687
|
+
ok: false,
|
|
1688
|
+
code: CodeVerifyCode.NODE_RECEIPT_KEY_FORMAT_UNSUPPORTED,
|
|
1689
|
+
details: [
|
|
1690
|
+
`JWK must have kty=OKP and crv=Ed25519, got kty=${key.jwk.kty} crv=${key.jwk.crv}`
|
|
1691
|
+
]
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
pubKeyBytes = base64urlToBytes(key.jwk.x);
|
|
1695
|
+
} else if (key.rawB64Url) {
|
|
1696
|
+
pubKeyBytes = base64urlToBytes(key.rawB64Url);
|
|
1697
|
+
} else if (key.spkiB64) {
|
|
1698
|
+
const spkiBytes = base64urlToBytes(key.spkiB64);
|
|
1699
|
+
if (spkiBytes.length < 32) {
|
|
1700
|
+
return {
|
|
1701
|
+
ok: false,
|
|
1702
|
+
code: CodeVerifyCode.NODE_RECEIPT_KEY_FORMAT_UNSUPPORTED,
|
|
1703
|
+
details: ["SPKI key too short to extract Ed25519 public key"]
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
pubKeyBytes = spkiBytes.slice(spkiBytes.length - 32);
|
|
1707
|
+
} else {
|
|
1708
|
+
return {
|
|
1709
|
+
ok: false,
|
|
1710
|
+
code: CodeVerifyCode.NODE_RECEIPT_KEY_FORMAT_UNSUPPORTED,
|
|
1711
|
+
details: ["No usable key provided: supply jwk, rawB64Url, or spkiB64"]
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
if (pubKeyBytes.length !== 32) {
|
|
1715
|
+
return {
|
|
1716
|
+
ok: false,
|
|
1717
|
+
code: CodeVerifyCode.NODE_RECEIPT_KEY_FORMAT_UNSUPPORTED,
|
|
1718
|
+
details: [`Ed25519 public key must be 32 bytes, got ${pubKeyBytes.length}`]
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1721
|
+
const sigBytes = base64urlToBytes(signatureB64Url);
|
|
1722
|
+
if (sigBytes.length !== 64) {
|
|
1723
|
+
return {
|
|
1724
|
+
ok: false,
|
|
1725
|
+
code: CodeVerifyCode.NODE_RECEIPT_INVALID_SIGNATURE,
|
|
1726
|
+
details: [`Ed25519 signature must be 64 bytes, got ${sigBytes.length}`]
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
const msgBytes = new TextEncoder().encode(toCanonicalJson(receipt));
|
|
1730
|
+
const isValid = await verify(sigBytes, msgBytes, pubKeyBytes);
|
|
1731
|
+
if (!isValid) {
|
|
1732
|
+
return {
|
|
1733
|
+
ok: false,
|
|
1734
|
+
code: CodeVerifyCode.NODE_RECEIPT_INVALID_SIGNATURE,
|
|
1735
|
+
details: ["Ed25519 signature verification failed"]
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
return { ok: true, code: CodeVerifyCode.OK };
|
|
1739
|
+
} catch (err) {
|
|
1740
|
+
return {
|
|
1741
|
+
ok: false,
|
|
1742
|
+
code: CodeVerifyCode.NODE_RECEIPT_INVALID_SIGNATURE,
|
|
1743
|
+
details: [err instanceof Error ? err.message : String(err)]
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
async function fetchNodeKeys(nodeUrl) {
|
|
1748
|
+
const url = `${nodeUrl.replace(/\/+$/, "")}/.well-known/nexart-node.json`;
|
|
1749
|
+
const response = await fetch(url);
|
|
1750
|
+
if (!response.ok) {
|
|
1751
|
+
throw new Error(`Failed to fetch node keys from ${url}: HTTP ${response.status}`);
|
|
1752
|
+
}
|
|
1753
|
+
const data = await response.json();
|
|
1754
|
+
if (typeof data !== "object" || data === null) {
|
|
1755
|
+
throw new Error("Node keys response is not an object");
|
|
1756
|
+
}
|
|
1757
|
+
const doc = data;
|
|
1758
|
+
if (typeof doc.nodeId !== "string" || !Array.isArray(doc.keys)) {
|
|
1759
|
+
throw new Error("Node keys document missing required fields (nodeId, keys)");
|
|
1760
|
+
}
|
|
1761
|
+
return data;
|
|
1762
|
+
}
|
|
1763
|
+
function selectNodeKey(doc, kid) {
|
|
1764
|
+
if (kid) {
|
|
1765
|
+
const found = doc.keys.find((k) => k.kid === kid);
|
|
1766
|
+
if (!found) {
|
|
1767
|
+
return {
|
|
1768
|
+
error: {
|
|
1769
|
+
ok: false,
|
|
1770
|
+
code: CodeVerifyCode.NODE_RECEIPT_KEY_NOT_FOUND,
|
|
1771
|
+
details: [`Key with kid="${kid}" not found in node keys document`]
|
|
1772
|
+
}
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
return { key: found };
|
|
1776
|
+
}
|
|
1777
|
+
if (doc.activeKid) {
|
|
1778
|
+
const found = doc.keys.find((k) => k.kid === doc.activeKid);
|
|
1779
|
+
if (!found) {
|
|
1780
|
+
return {
|
|
1781
|
+
error: {
|
|
1782
|
+
ok: false,
|
|
1783
|
+
code: CodeVerifyCode.NODE_RECEIPT_KEY_NOT_FOUND,
|
|
1784
|
+
details: [`activeKid="${doc.activeKid}" not found in keys array`]
|
|
1785
|
+
}
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
return { key: found };
|
|
1789
|
+
}
|
|
1790
|
+
if (doc.keys.length === 0) {
|
|
1791
|
+
return {
|
|
1792
|
+
error: {
|
|
1793
|
+
ok: false,
|
|
1794
|
+
code: CodeVerifyCode.NODE_RECEIPT_KEY_NOT_FOUND,
|
|
1795
|
+
details: ["No keys available in node keys document"]
|
|
1796
|
+
}
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
return { key: doc.keys[0] };
|
|
1800
|
+
}
|
|
1801
|
+
function extractReceiptAndSignature(bundle) {
|
|
1802
|
+
if (typeof bundle !== "object" || bundle === null) return null;
|
|
1803
|
+
const b = bundle;
|
|
1804
|
+
if (typeof b.receipt === "object" && b.receipt !== null && typeof b.signature === "string") {
|
|
1805
|
+
return {
|
|
1806
|
+
receipt: b.receipt,
|
|
1807
|
+
signatureB64Url: b.signature,
|
|
1808
|
+
attestorKeyId: typeof b.attestorKeyId === "string" ? b.attestorKeyId : void 0
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
if (typeof b.attestation === "object" && b.attestation !== null) {
|
|
1812
|
+
const att = b.attestation;
|
|
1813
|
+
if (typeof att.receipt === "object" && att.receipt !== null && typeof att.signature === "string") {
|
|
1814
|
+
return {
|
|
1815
|
+
receipt: att.receipt,
|
|
1816
|
+
signatureB64Url: att.signature,
|
|
1817
|
+
attestorKeyId: typeof att.attestorKeyId === "string" ? att.attestorKeyId : void 0
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
if (typeof b.meta === "object" && b.meta !== null) {
|
|
1822
|
+
const meta = b.meta;
|
|
1823
|
+
if (typeof meta.attestation === "object" && meta.attestation !== null) {
|
|
1824
|
+
const att = meta.attestation;
|
|
1825
|
+
if (typeof att.receipt === "object" && att.receipt !== null && typeof att.signature === "string") {
|
|
1826
|
+
return {
|
|
1827
|
+
receipt: att.receipt,
|
|
1828
|
+
signatureB64Url: att.signature,
|
|
1829
|
+
attestorKeyId: typeof att.attestorKeyId === "string" ? att.attestorKeyId : void 0
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
return null;
|
|
1835
|
+
}
|
|
1836
|
+
async function verifyBundleAttestation(bundle, options) {
|
|
1837
|
+
const extracted = extractReceiptAndSignature(bundle);
|
|
1838
|
+
if (!extracted) {
|
|
1839
|
+
return {
|
|
1840
|
+
ok: false,
|
|
1841
|
+
code: CodeVerifyCode.NODE_RECEIPT_MISSING,
|
|
1842
|
+
details: ["No signed receipt found in bundle (expected bundle.receipt + bundle.signature or bundle.attestation envelope)"]
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
const nodeId = extracted.receipt.nodeId;
|
|
1846
|
+
const resolvedKid = options.kid ?? extracted.attestorKeyId ?? extracted.receipt.attestorKeyId;
|
|
1847
|
+
function ctx() {
|
|
1848
|
+
const lines = [];
|
|
1849
|
+
if (nodeId) lines.push(`nodeId: ${nodeId}`);
|
|
1850
|
+
if (resolvedKid) lines.push(`kid: ${resolvedKid}`);
|
|
1851
|
+
return lines;
|
|
1852
|
+
}
|
|
1853
|
+
if (typeof bundle.certificateHash === "string") {
|
|
1854
|
+
const bundleCertHash = bundle.certificateHash;
|
|
1855
|
+
if (extracted.receipt.certificateHash !== bundleCertHash) {
|
|
1856
|
+
return {
|
|
1857
|
+
ok: false,
|
|
1858
|
+
code: CodeVerifyCode.CERTIFICATE_HASH_MISMATCH,
|
|
1859
|
+
details: [
|
|
1860
|
+
"Receipt certificateHash does not match bundle certificateHash",
|
|
1861
|
+
`receipt.certificateHash: ${extracted.receipt.certificateHash}`,
|
|
1862
|
+
`bundle.certificateHash: ${bundleCertHash}`,
|
|
1863
|
+
...ctx()
|
|
1864
|
+
]
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
let keysDoc;
|
|
1869
|
+
try {
|
|
1870
|
+
keysDoc = await fetchNodeKeys(options.nodeUrl);
|
|
1871
|
+
} catch (err) {
|
|
1872
|
+
return {
|
|
1873
|
+
ok: false,
|
|
1874
|
+
code: CodeVerifyCode.NODE_RECEIPT_KEY_NOT_FOUND,
|
|
1875
|
+
details: [...ctx(), err instanceof Error ? err.message : String(err)]
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
const selected = selectNodeKey(keysDoc, resolvedKid);
|
|
1879
|
+
if (selected.error) {
|
|
1880
|
+
return {
|
|
1881
|
+
...selected.error,
|
|
1882
|
+
details: [...ctx(), ...selected.error.details ?? []]
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
const keyEntry = selected.key;
|
|
1886
|
+
const keyParam = {};
|
|
1887
|
+
if (keyEntry.publicKeyJwk) keyParam.jwk = keyEntry.publicKeyJwk;
|
|
1888
|
+
else if (keyEntry.publicKey) keyParam.rawB64Url = keyEntry.publicKey;
|
|
1889
|
+
else if (keyEntry.publicKeySpkiB64) keyParam.spkiB64 = keyEntry.publicKeySpkiB64;
|
|
1890
|
+
else {
|
|
1891
|
+
return {
|
|
1892
|
+
ok: false,
|
|
1893
|
+
code: CodeVerifyCode.NODE_RECEIPT_KEY_FORMAT_UNSUPPORTED,
|
|
1894
|
+
details: [
|
|
1895
|
+
`Key kid="${keyEntry.kid}" has no usable public key field (publicKeyJwk, publicKey, or publicKeySpkiB64)`,
|
|
1896
|
+
...ctx()
|
|
1897
|
+
]
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
const sigResult = await verifyNodeReceiptSignature({
|
|
1901
|
+
receipt: extracted.receipt,
|
|
1902
|
+
signatureB64Url: extracted.signatureB64Url,
|
|
1903
|
+
key: keyParam
|
|
1904
|
+
});
|
|
1905
|
+
const contextLines = ctx();
|
|
1906
|
+
if (contextLines.length === 0) return sigResult;
|
|
1907
|
+
return sigResult.ok ? { ok: true, code: CodeVerifyCode.OK, details: contextLines } : { ...sigResult, details: [...contextLines, ...sigResult.details ?? []] };
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
// attestation.ts
|
|
1911
|
+
function getAttestationReceipt(bundle) {
|
|
1912
|
+
if (typeof bundle !== "object" || bundle === null) return null;
|
|
1913
|
+
const b = bundle;
|
|
1914
|
+
if (typeof b.receipt === "object" && b.receipt !== null && typeof b.signature === "string") {
|
|
1915
|
+
const r = b.receipt;
|
|
1916
|
+
return {
|
|
1917
|
+
attestationId: r.attestationId,
|
|
1918
|
+
attestedAt: r.attestedAt,
|
|
1919
|
+
nodeId: r.nodeId,
|
|
1920
|
+
attestorKeyId: r.attestorKeyId,
|
|
1921
|
+
nodeRuntimeHash: r.nodeRuntimeHash,
|
|
1922
|
+
certificateHash: r.certificateHash,
|
|
1923
|
+
protocolVersion: r.protocolVersion,
|
|
1924
|
+
receipt: r,
|
|
1925
|
+
signature: b.signature
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
if (typeof b.attestation === "object" && b.attestation !== null) {
|
|
1929
|
+
const att = b.attestation;
|
|
1930
|
+
if (typeof att.receipt === "object" && att.receipt !== null && typeof att.signature === "string") {
|
|
1931
|
+
const r = att.receipt;
|
|
1932
|
+
return {
|
|
1933
|
+
attestationId: r.attestationId,
|
|
1934
|
+
attestedAt: r.attestedAt,
|
|
1935
|
+
nodeId: r.nodeId,
|
|
1936
|
+
attestorKeyId: r.attestorKeyId,
|
|
1937
|
+
nodeRuntimeHash: r.nodeRuntimeHash,
|
|
1938
|
+
certificateHash: r.certificateHash,
|
|
1939
|
+
protocolVersion: r.protocolVersion,
|
|
1940
|
+
receipt: r,
|
|
1941
|
+
signature: att.signature
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
if (typeof b.attestationId === "string") {
|
|
1946
|
+
return {
|
|
1947
|
+
attestationId: b.attestationId,
|
|
1948
|
+
attestedAt: typeof b.attestedAt === "string" ? b.attestedAt : "",
|
|
1949
|
+
nodeId: typeof b.nodeId === "string" ? b.nodeId : void 0,
|
|
1950
|
+
attestorKeyId: typeof b.attestorKeyId === "string" ? b.attestorKeyId : void 0,
|
|
1951
|
+
nodeRuntimeHash: typeof b.nodeRuntimeHash === "string" ? b.nodeRuntimeHash : void 0,
|
|
1952
|
+
certificateHash: typeof b.certificateHash === "string" ? b.certificateHash : void 0,
|
|
1953
|
+
protocolVersion: typeof b.protocolVersion === "string" ? b.protocolVersion : void 0
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
return null;
|
|
1957
|
+
}
|
|
1958
|
+
function hasAttestation(bundle) {
|
|
1959
|
+
return getAttestationReceipt(bundle) !== null;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
// core-index.ts
|
|
1963
|
+
var SDK_VERSION2 = SDK_VERSION;
|
|
1964
|
+
var SDK_NAME = "@nexart/codemode-sdk";
|
|
1965
|
+
|
|
1966
|
+
export { CODE_MODE_ENFORCEMENT, CODE_MODE_PROTOCOL_PHASE, CODE_MODE_PROTOCOL_VERSION, CodeVerifyCode, DEFAULT_CONFIG, DEFAULT_VARS, PROTOCOL_IDENTITY, PROTOCOL_PHASE, PROTOCOL_VERSION, SDK_NAME, SDK_VERSION2 as SDK_VERSION, VAR_COUNT, VAR_MAX, VAR_MIN, cancelLoopMode, createEngine, createP5Runtime, createProtocolVAR, executeCodeMode, fetchNodeKeys, getAttestationReceipt, hasAttestation, injectTimeVariables, runLoopMode, runStaticMode, selectNodeKey, toCanonicalJson, validateCodeModeSource, verifyBundleAttestation, verifyNodeReceiptSignature };
|