@nexart/ui-renderer 0.8.6 → 0.8.8

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @nexart/ui-renderer
2
2
 
3
- Version: 0.8.6
3
+ Version: 0.8.7
4
4
 
5
5
  **Lightweight Preview Runtime for NexArt Protocol**
6
6
 
@@ -19,31 +19,35 @@ Version: 0.8.6
19
19
 
20
20
  ---
21
21
 
22
- ## v0.8.6Continuous FPS-Capped Animation
22
+ ## v0.8.7Live Runtime Binding Fix
23
23
 
24
- Replaced time/frame budget with simple FPS throttle for continuous animation.
24
+ Fixed critical bug where time-varying properties were frozen in loop mode.
25
25
 
26
- - **No execution budget**: Removed 30-frame/500ms limits
27
- - **FPS cap**: Renders at 8 FPS (125ms per frame) for smooth, efficient animation
28
- - **Continuous animation**: Loops indefinitely until `stop()` is called
29
- - **No flashing**: Canvas only updates when draw() runs
26
+ - **Live binding**: `frameCount`, `t`, `time`, `tGlobal`, `totalFrames` now update every frame
27
+ - **Proxy + with pattern**: Uses a Proxy scope with `with()` for live property access
28
+ - **Animations work**: Sketches see correct values on every frame
30
29
 
31
- **Animation Pattern:**
30
+ **Animation Semantics:**
31
+ In loop mode, all time variables update every frame:
32
+ - `frameCount`: Increments each frame
33
+ - `t`: Normalized time `(frameCount % totalFrames) / totalFrames` (range [0,1))
34
+ - `time`: Alias for `t`
35
+ - `tGlobal`: Alias for `t`
36
+ - `totalFrames`: Total frames in the loop (default 120)
37
+
38
+ **Test Sketch:**
32
39
  ```javascript
33
- function loop() {
34
- requestAnimationFrame(loop);
35
-
36
- if (!shouldRenderFrame(throttle)) return; // FPS cap
37
-
38
- runtime.draw();
39
- recordFrame(throttle);
40
+ function draw() {
41
+ background(0);
42
+ fill(255);
43
+ circle(
44
+ width / 2 + sin(frameCount * 0.1) * 300,
45
+ height / 2,
46
+ 120
47
+ );
40
48
  }
41
49
  ```
42
-
43
- **Behavior:**
44
- - Renders continuously at 8 FPS
45
- - Modulo-based looping: `t = (frameCount % totalFrames) / totalFrames`
46
- - Stops only when `stop()` is called
50
+ Expected: Circle moves horizontally, motion is continuous.
47
51
 
48
52
  ---
49
53
 
@@ -269,7 +273,7 @@ import { getCapabilities } from '@nexart/ui-renderer';
269
273
 
270
274
  const caps = getCapabilities();
271
275
  // {
272
- // version: '0.8.6',
276
+ // version: '0.8.7',
273
277
  // isCanonical: false,
274
278
  // isArchival: false,
275
279
  // previewBudget: { MAX_FRAMES: 30, MAX_TOTAL_TIME_MS: 500, FRAME_STRIDE: 3 },
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @nexart/ui-renderer v0.8.6 - Code Mode Renderer
2
+ * @nexart/ui-renderer v0.8.8 - Code Mode Renderer
3
3
  *
4
4
  * ╔══════════════════════════════════════════════════════════════════════════╗
5
5
  * ║ PREVIEW RENDERER — LIGHTWEIGHT, NON-AUTHORITATIVE ║
@@ -7,7 +7,7 @@
7
7
  * ║ This renderer is a preview-only runtime. ║
8
8
  * ║ It does not guarantee determinism or protocol compliance. ║
9
9
  * ║ ║
10
- * ║ Performance: FPS-capped at 8 FPS, canvas max 900px
10
+ * ║ Performance: Native requestAnimationFrame (~60 FPS), canvas max 900px
11
11
  * ║ Animation: Runs continuously until stop() is called ║
12
12
  * ║ ║
13
13
  * ║ For minting, export, or validation: use @nexart/codemode-sdk ║
@@ -1 +1 @@
1
- {"version":3,"file":"code-renderer.d.ts","sourceRoot":"","sources":["../../src/preview/code-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAwBjE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,EAAE,KAAK,CAAC;IACnB,UAAU,EAAE,KAAK,CAAC;CACnB;AAwBD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE,cAAmB,GAC3B,YAAY,CAmQd"}
1
+ {"version":3,"file":"code-renderer.d.ts","sourceRoot":"","sources":["../../src/preview/code-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAiBjE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,EAAE,KAAK,CAAC;IACnB,UAAU,EAAE,KAAK,CAAC;CACnB;AAwBD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE,cAAmB,GAC3B,YAAY,CA4Sd"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @nexart/ui-renderer v0.8.6 - Code Mode Renderer
2
+ * @nexart/ui-renderer v0.8.8 - Code Mode Renderer
3
3
  *
4
4
  * ╔══════════════════════════════════════════════════════════════════════════╗
5
5
  * ║ PREVIEW RENDERER — LIGHTWEIGHT, NON-AUTHORITATIVE ║
@@ -7,14 +7,13 @@
7
7
  * ║ This renderer is a preview-only runtime. ║
8
8
  * ║ It does not guarantee determinism or protocol compliance. ║
9
9
  * ║ ║
10
- * ║ Performance: FPS-capped at 8 FPS, canvas max 900px
10
+ * ║ Performance: Native requestAnimationFrame (~60 FPS), canvas max 900px
11
11
  * ║ Animation: Runs continuously until stop() is called ║
12
12
  * ║ ║
13
13
  * ║ For minting, export, or validation: use @nexart/codemode-sdk ║
14
14
  * ╚══════════════════════════════════════════════════════════════════════════╝
15
15
  */
16
- import { PREVIEW_FPS, CANVAS_LIMITS, } from './preview-types';
17
- import { createFpsThrottle, shouldRenderFrame, recordFrame, resetThrottle, } from './frame-budget';
16
+ import { CANVAS_LIMITS, } from './preview-types';
18
17
  import { calculateScaledDimensions, applyScaledDimensions, reapplyContextScale, clearCanvasIgnoringTransform, } from './canvas-scaler';
19
18
  import { createPreviewRuntime } from './preview-runtime';
20
19
  const PROTOCOL_VERSION = '1.2.0';
@@ -42,8 +41,8 @@ function normalizeVars(vars) {
42
41
  return result;
43
42
  }
44
43
  export function renderCodeModeSystem(system, canvas, options = {}) {
45
- console.log('[UIRenderer] Preview mode → FPS-capped continuous animation');
46
- console.log(`[UIRenderer] Target: ${PREVIEW_FPS.TARGET_FPS} FPS, canvas max ${CANVAS_LIMITS.MAX_DIMENSION}px`);
44
+ console.log('[UIRenderer] Preview mode → native requestAnimationFrame (~60 FPS)');
45
+ console.log(`[UIRenderer] Canvas max ${CANVAS_LIMITS.MAX_DIMENSION}px`);
47
46
  if (activeRendererInstance) {
48
47
  activeRendererInstance.destroy();
49
48
  activeRendererInstance = null;
@@ -61,7 +60,6 @@ export function renderCodeModeSystem(system, canvas, options = {}) {
61
60
  let animationId = null;
62
61
  let isRunning = false;
63
62
  let isDestroyed = false;
64
- const throttle = createFpsThrottle();
65
63
  const normalizedVars = normalizeVars(system.vars);
66
64
  let runtime = null;
67
65
  let setupFn = null;
@@ -85,19 +83,68 @@ export function renderCodeModeSystem(system, canvas, options = {}) {
85
83
  const totalFrames = system.totalFrames ?? 120;
86
84
  runtime.totalFrames = totalFrames;
87
85
  try {
88
- const globalVars = Object.keys(runtime);
89
- const globalValues = Object.values(runtime);
90
- const wrappedSource = `
91
- ${system.source}
92
- if (typeof setup === 'function') __registerSetup(setup);
93
- if (typeof draw === 'function') __registerDraw(draw);
94
- `;
86
+ // ╔═══════════════════════════════════════════════════════════════════════╗
87
+ // ║ LIVE RUNTIME BINDING — v0.8.7 FIX ║
88
+ // ║ ║
89
+ // ║ Time-varying properties (frameCount, t, time, tGlobal, totalFrames) ║
90
+ // ║ must be accessed via getters that read from the live runtime object. ║
91
+ // ║ ║
92
+ // ║ We use a Proxy + with() pattern to enable bare-name access to live ║
93
+ // ║ runtime properties. The proxy's get trap reads from runtime for ║
94
+ // ║ time-varying props, and from a static cache for everything else. ║
95
+ // ╚═══════════════════════════════════════════════════════════════════════╝
96
+ // Time-varying properties that need live access
97
+ const liveProps = new Set(['frameCount', 't', 'time', 'tGlobal', 'totalFrames']);
98
+ // Cache static properties (functions, constants)
99
+ const staticCache = {};
100
+ for (const key of Object.keys(runtime)) {
101
+ if (!liveProps.has(key)) {
102
+ staticCache[key] = runtime[key];
103
+ }
104
+ }
95
105
  const registerSetup = (fn) => { setupFn = fn; };
96
106
  const registerDraw = (fn) => { drawFn = fn; };
97
- globalVars.push('__registerSetup', '__registerDraw');
98
- globalValues.push(registerSetup, registerDraw);
99
- const fn = new Function(...globalVars, wrappedSource);
100
- fn(...globalValues);
107
+ // Build the set of all known keys (for has trap)
108
+ const knownKeys = new Set([
109
+ ...liveProps,
110
+ ...Object.keys(staticCache),
111
+ '__registerSetup',
112
+ '__registerDraw'
113
+ ]);
114
+ // Create a proxy that provides live access to time-varying properties
115
+ // IMPORTANT: has() must return true ONLY for known keys
116
+ // Otherwise globals (Math, window, etc.) are masked and become undefined
117
+ const runtimeRef = runtime;
118
+ const scope = new Proxy({}, {
119
+ has: (_, prop) => knownKeys.has(prop),
120
+ get: (_, prop) => {
121
+ // Time-varying properties - read live from runtime
122
+ if (liveProps.has(prop)) {
123
+ return runtimeRef[prop];
124
+ }
125
+ // Special functions
126
+ if (prop === '__registerSetup')
127
+ return registerSetup;
128
+ if (prop === '__registerDraw')
129
+ return registerDraw;
130
+ // Static properties from cache
131
+ if (prop in staticCache)
132
+ return staticCache[prop];
133
+ // Fall through to undefined (should not hit this if has() is correct)
134
+ return undefined;
135
+ }
136
+ });
137
+ // Use with() to make the proxy scope available to bare variable names
138
+ // Note: with() is safe here since we control the scope completely
139
+ const wrappedSource = `
140
+ with (__scope) {
141
+ ${system.source}
142
+ if (typeof setup === 'function') __registerSetup(setup);
143
+ if (typeof draw === 'function') __registerDraw(draw);
144
+ }
145
+ `;
146
+ const fn = new Function('__scope', wrappedSource);
147
+ fn(scope);
101
148
  }
102
149
  catch (error) {
103
150
  console.warn('[UIRenderer] Compile error:', error);
@@ -168,15 +215,14 @@ export function renderCodeModeSystem(system, canvas, options = {}) {
168
215
  if (setupFn) {
169
216
  setupFn();
170
217
  }
171
- resetThrottle(throttle);
172
218
  isRunning = true;
173
219
  // ╔═══════════════════════════════════════════════════════════════════════╗
174
- // ║ ANIMATION LOOP — CONTINUOUS FPS-CAPPED RENDERING
220
+ // ║ ANIMATION LOOP — NATIVE requestAnimationFrame (~60 FPS)
175
221
  // ║ ║
176
- // ║ requestAnimationFrame runs continuously until stop() is called.
177
- // ║ FPS throttle (8 FPS) prevents excessive CPU usage.
222
+ // ║ v0.8.8: Removed FPS throttle for smooth rendering matching NexArt.
223
+ // ║ Browser handles frame pacing naturally via requestAnimationFrame.
178
224
  // ║ Looping uses modulo math: t = (frame % total) / total ║
179
- // ║ Canvas cleared before each draw; skipped frames preserve pixels.
225
+ // ║ Canvas cleared before each draw() call.
180
226
  // ╚═══════════════════════════════════════════════════════════════════════╝
181
227
  const loop = () => {
182
228
  // Schedule next frame first — loop runs until stop()
@@ -184,10 +230,6 @@ export function renderCodeModeSystem(system, canvas, options = {}) {
184
230
  // Exit if stopped or destroyed
185
231
  if (!isRunning || isDestroyed)
186
232
  return;
187
- // FPS throttle — skip if not enough time has passed
188
- if (!shouldRenderFrame(throttle)) {
189
- return; // Preserve current canvas
190
- }
191
233
  frameCount++;
192
234
  // Update runtime timing using modulo for natural looping
193
235
  if (runtime) {
@@ -202,7 +244,6 @@ export function renderCodeModeSystem(system, canvas, options = {}) {
202
244
  if (drawFn)
203
245
  drawFn();
204
246
  drawBadge();
205
- recordFrame(throttle);
206
247
  }
207
248
  catch (error) {
208
249
  console.warn('[UIRenderer] Draw error:', error);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nexart/ui-renderer",
3
- "version": "0.8.6",
4
- "description": "Lightweight Preview Runtime for NexArt Protocol. Non-canonical, FPS-capped continuous animation (8 FPS, max 900px canvas).",
3
+ "version": "0.8.8",
4
+ "description": "Lightweight Preview Runtime for NexArt Protocol. Non-canonical, native requestAnimationFrame (~60 FPS, max 900px canvas).",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",