@nexart/ui-renderer 0.8.3 → 0.8.5

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.3
3
+ Version: 0.8.5
4
4
 
5
5
  **Lightweight Preview Runtime for NexArt Protocol**
6
6
 
@@ -19,6 +19,34 @@ Version: 0.8.3
19
19
 
20
20
  ---
21
21
 
22
+ ## v0.8.5 — Correct Animation Loop Semantics
23
+
24
+ Fixed animation loop to use proper soft-gating without resets.
25
+
26
+ - **No resets inside loop**: Budget and frameCount are never reset mid-animation
27
+ - **Soft budget gating**: Budget exhaustion skips draw(), preserving last frame
28
+ - **Modulo-based looping**: `t = (frameCount % totalFrames) / totalFrames` for natural animation cycles
29
+ - **Canvas preservation**: Skipped frames keep current pixels, no black flashes
30
+
31
+ **Correct Pattern:**
32
+ ```javascript
33
+ function loop() {
34
+ requestAnimationFrame(loop);
35
+
36
+ if (!budget.canRenderFrame()) return; // Preserve pixels
37
+
38
+ runtime.draw(); // Only changes canvas here
39
+ budget.recordFrame();
40
+ }
41
+ ```
42
+
43
+ **Behavior:**
44
+ - Renders up to 30 frames (budget limit)
45
+ - After budget exhausted: RAF continues, draw() skipped, last frame stays visible
46
+ - Restart via `stop()` → `start()` to reset and continue
47
+
48
+ ---
49
+
22
50
  ## v0.8.3 — Animation Loop Fix
23
51
 
24
52
  Fixed critical bug where preview only rendered a single frame.
@@ -241,7 +269,7 @@ import { getCapabilities } from '@nexart/ui-renderer';
241
269
 
242
270
  const caps = getCapabilities();
243
271
  // {
244
- // version: '0.8.3',
272
+ // version: '0.8.5',
245
273
  // isCanonical: false,
246
274
  // isArchival: false,
247
275
  // previewBudget: { MAX_FRAMES: 30, MAX_TOTAL_TIME_MS: 500, FRAME_STRIDE: 3 },
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @nexart/ui-renderer
3
- * Version: 0.8.3
3
+ * Version: 0.8.5
4
4
  *
5
5
  * Lightweight Preview Runtime for NexArt Protocol
6
6
  *
@@ -46,7 +46,7 @@ export { calculateScaledDimensions, applyScaledDimensions, type ScaledDimensions
46
46
  export type { NexArtSystemInput, NexArtSystem, DeclarativeSystemInput, DeclarativeSystem, CodeSystem, NexArtCodeSystem, UnifiedSystemInput, UnifiedSystem, UnifiedElement, BackgroundElement, PrimitiveElement, SketchElement, BackgroundPreset, PrimitiveName, ColorPalette, MotionSpeed, StrokeWeightAuto, LoopConfig, DeclarativeElement, SystemElement, DotsElement, LinesElement, WavesElement, GridElement, FlowFieldElement, OrbitsElement, BackgroundConfig, MotionConfig, PreviewOptions, ValidationResult, } from './types';
47
47
  export { AESTHETIC_DEFAULTS, SDK_VERSION as TYPE_SDK_VERSION } from './types';
48
48
  export type { Capabilities, PrimitiveCapability, ParameterSpec, } from './capabilities';
49
- export declare const SDK_VERSION = "0.8.3";
49
+ export declare const SDK_VERSION = "0.8.5";
50
50
  export declare const PROTOCOL_VERSION = "0.8";
51
51
  export declare const IS_CANONICAL = false;
52
52
  export declare const IS_ARCHIVAL = false;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @nexart/ui-renderer
3
- * Version: 0.8.3
3
+ * Version: 0.8.5
4
4
  *
5
5
  * Lightweight Preview Runtime for NexArt Protocol
6
6
  *
@@ -44,7 +44,7 @@ export { PREVIEW_BUDGET, CANVAS_LIMITS, } from './preview/preview-types';
44
44
  export { createFrameBudget, canRenderFrame, recordFrame, resetBudget, shouldSkipFrame, } from './preview/frame-budget';
45
45
  export { calculateScaledDimensions, applyScaledDimensions, } from './preview/canvas-scaler';
46
46
  export { AESTHETIC_DEFAULTS, SDK_VERSION as TYPE_SDK_VERSION } from './types';
47
- export const SDK_VERSION = '0.8.3';
47
+ export const SDK_VERSION = '0.8.5';
48
48
  export const PROTOCOL_VERSION = '0.8';
49
49
  export const IS_CANONICAL = false;
50
50
  export const IS_ARCHIVAL = false;
@@ -1 +1 @@
1
- {"version":3,"file":"code-renderer.d.ts","sourceRoot":"","sources":["../../src/preview/code-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AA0BjE,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,CAkQd"}
1
+ {"version":3,"file":"code-renderer.d.ts","sourceRoot":"","sources":["../../src/preview/code-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AA0BjE,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,CAyQd"}
@@ -177,8 +177,10 @@ export function renderCodeModeSystem(system, canvas, options = {}) {
177
177
  // ║ ANIMATION LOOP INVARIANT — DO NOT CHANGE ║
178
178
  // ║ ║
179
179
  // ║ requestAnimationFrame(loop) MUST be called on EVERY tick. ║
180
- // ║ Budget gates ONLY whether draw() executes, NOT whether loop runs. ║
181
- // ║ Never return before scheduling RAF. Never conditionally schedule.
180
+ // ║ Budget gates ONLY whether draw() executes, NOT loop continuation. ║
181
+ // ║ Never reset frameCount or budget inside the loop.
182
+ // ║ Canvas only changes when draw() runs — skipped frames keep pixels. ║
183
+ // ║ Looping uses modulo math (t = frame % total), not counter resets. ║
182
184
  // ╚═══════════════════════════════════════════════════════════════════════╝
183
185
  const loop = () => {
184
186
  // ALWAYS schedule next frame first — loop never terminates on its own
@@ -186,28 +188,32 @@ export function renderCodeModeSystem(system, canvas, options = {}) {
186
188
  // Early exit checks (after RAF is scheduled)
187
189
  if (!isRunning || isDestroyed)
188
190
  return;
189
- // Budget check — skip draw but keep loop alive
191
+ // Budget check — if exhausted, skip draw but keep last frame visible
190
192
  if (!canRenderFrame(budget)) {
191
- return; // Loop continues, just don't draw
193
+ return; // Preserve current canvas, no clear, no reset
192
194
  }
193
195
  frameCount++;
194
- if (!shouldSkipFrame(frameCount)) {
195
- if (runtime) {
196
- runtime.frameCount = frameCount;
197
- runtime.t = (frameCount % totalFrames) / totalFrames;
198
- runtime.time = runtime.t;
199
- runtime.tGlobal = runtime.t;
200
- }
201
- try {
202
- clearCanvasIgnoringTransform(ctx, canvas);
203
- if (drawFn)
204
- drawFn();
205
- drawBadge();
206
- recordFrame(budget);
207
- }
208
- catch (error) {
209
- console.warn('[UIRenderer] Draw error:', error);
210
- }
196
+ // Frame stride check — skip some frames for performance
197
+ if (shouldSkipFrame(frameCount)) {
198
+ return; // Skip but don't clear — preserve last drawn frame
199
+ }
200
+ // Update runtime timing using modulo for natural looping
201
+ if (runtime) {
202
+ runtime.frameCount = frameCount;
203
+ runtime.t = (frameCount % totalFrames) / totalFrames;
204
+ runtime.time = runtime.t;
205
+ runtime.tGlobal = runtime.t;
206
+ }
207
+ try {
208
+ // Clear canvas and draw (only when actually rendering)
209
+ clearCanvasIgnoringTransform(ctx, canvas);
210
+ if (drawFn)
211
+ drawFn();
212
+ drawBadge();
213
+ recordFrame(budget);
214
+ }
215
+ catch (error) {
216
+ console.warn('[UIRenderer] Draw error:', error);
211
217
  }
212
218
  };
213
219
  animationId = requestAnimationFrame(loop);
@@ -1 +1 @@
1
- {"version":3,"file":"preview-engine.d.ts","sourceRoot":"","sources":["../../src/preview/preview-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EAErB,MAAM,iBAAiB,CAAC;AAqNzB;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,eAAe,CAEhF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,mBAAmB,CAKpF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAKxC"}
1
+ {"version":3,"file":"preview-engine.d.ts","sourceRoot":"","sources":["../../src/preview/preview-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EAErB,MAAM,iBAAiB,CAAC;AA2NzB;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,eAAe,CAEhF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,mBAAmB,CAKpF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAKxC"}
@@ -136,8 +136,10 @@ class PreviewEngine {
136
136
  // ║ ANIMATION LOOP INVARIANT — DO NOT CHANGE ║
137
137
  // ║ ║
138
138
  // ║ requestAnimationFrame MUST be called on EVERY tick. ║
139
- // ║ Budget gates ONLY whether draw() executes, NOT whether loop runs. ║
140
- // ║ Never return before scheduling RAF. Never conditionally schedule.
139
+ // ║ Budget gates ONLY whether draw() executes, NOT loop continuation. ║
140
+ // ║ Never reset frameCount or budget inside the loop.
141
+ // ║ Canvas only changes when draw() runs — skipped frames keep pixels. ║
142
+ // ║ Looping uses modulo math (t = frame % total), not counter resets. ║
141
143
  // ╚═══════════════════════════════════════════════════════════════════════╝
142
144
  scheduleNextFrame() {
143
145
  // ALWAYS schedule next frame first — loop never terminates on its own
@@ -146,28 +148,31 @@ class PreviewEngine {
146
148
  // Early exit checks (after RAF is scheduled)
147
149
  if (!this.running)
148
150
  return;
149
- // Budget check — skip draw but keep loop alive
151
+ // Budget check — if exhausted, skip draw but keep last frame visible
150
152
  if (!canRenderFrame(this.budget)) {
151
- return; // Loop continues, just don't draw
153
+ return; // Preserve current canvas, no clear, no reset
152
154
  }
153
155
  this.internalFrameCount++;
154
- if (!shouldSkipFrame(this.internalFrameCount)) {
155
- try {
156
- if (this.runtime) {
157
- this.runtime.frameCount = this.internalFrameCount;
158
- const totalFrames = this.config.totalFrames ?? 120;
159
- this.runtime.t = this.internalFrameCount / totalFrames;
160
- this.runtime.time = this.runtime.t;
161
- this.runtime.tGlobal = this.runtime.t;
162
- }
163
- if (this.drawFn) {
164
- this.drawFn();
165
- }
166
- recordFrame(this.budget);
156
+ // Frame stride check — skip some frames for performance
157
+ if (shouldSkipFrame(this.internalFrameCount)) {
158
+ return; // Skip but don't clear — preserve last drawn frame
159
+ }
160
+ // Update runtime timing using modulo for natural looping
161
+ try {
162
+ if (this.runtime) {
163
+ this.runtime.frameCount = this.internalFrameCount;
164
+ const totalFrames = this.config.totalFrames ?? 120;
165
+ this.runtime.t = (this.internalFrameCount % totalFrames) / totalFrames;
166
+ this.runtime.time = this.runtime.t;
167
+ this.runtime.tGlobal = this.runtime.t;
167
168
  }
168
- catch (error) {
169
- console.warn('[PreviewEngine] Draw error:', error);
169
+ if (this.drawFn) {
170
+ this.drawFn();
170
171
  }
172
+ recordFrame(this.budget);
173
+ }
174
+ catch (error) {
175
+ console.warn('[PreviewEngine] Draw error:', error);
171
176
  }
172
177
  });
173
178
  }
@@ -1 +1 @@
1
- {"version":3,"file":"unified-renderer.d.ts","sourceRoot":"","sources":["../../src/preview/unified-renderer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAkB,cAAc,EAAoE,MAAM,UAAU,CAAC;AAmBhJ,MAAM,WAAW,eAAe;IAC9B,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;AA6ED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE,cAAmB,GAC3B,eAAe,CA6QjB"}
1
+ {"version":3,"file":"unified-renderer.d.ts","sourceRoot":"","sources":["../../src/preview/unified-renderer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAkB,cAAc,EAAoE,MAAM,UAAU,CAAC;AAmBhJ,MAAM,WAAW,eAAe;IAC9B,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;AA6ED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE,cAAmB,GAC3B,eAAe,CAmRjB"}
@@ -241,8 +241,10 @@ export function renderUnifiedSystem(system, canvas, options = {}) {
241
241
  // ║ ANIMATION LOOP INVARIANT — DO NOT CHANGE ║
242
242
  // ║ ║
243
243
  // ║ requestAnimationFrame(loop) MUST be called on EVERY tick. ║
244
- // ║ Budget gates ONLY whether draw() executes, NOT whether loop runs. ║
245
- // ║ Never return before scheduling RAF. Never conditionally schedule.
244
+ // ║ Budget gates ONLY whether draw() executes, NOT loop continuation. ║
245
+ // ║ Never reset frameCount or budget inside the loop.
246
+ // ║ Canvas only changes when draw() runs — skipped frames keep pixels. ║
247
+ // ║ Looping uses modulo math (t = frame % total), not counter resets. ║
246
248
  // ╚═══════════════════════════════════════════════════════════════════════╝
247
249
  const loop = () => {
248
250
  // ALWAYS schedule next frame first — loop never terminates on its own
@@ -250,23 +252,26 @@ export function renderUnifiedSystem(system, canvas, options = {}) {
250
252
  // Early exit checks (after RAF is scheduled)
251
253
  if (!isRunning || isDestroyed)
252
254
  return;
253
- // Budget check — skip draw but keep loop alive
255
+ // Budget check — if exhausted, skip draw but keep last frame visible
254
256
  if (!canRenderFrame(budget)) {
255
- return; // Loop continues, just don't draw
257
+ return; // Preserve current canvas, no clear, no reset
256
258
  }
257
259
  frameCount++;
258
- if (!shouldSkipFrame(frameCount)) {
259
- const t = (frameCount % totalFrames) / totalFrames;
260
- p.randomSeed(system.seed);
261
- p.noiseSeed(system.seed);
262
- try {
263
- runDraw(p, frameCount, t);
264
- drawBadge();
265
- recordFrame(budget);
266
- }
267
- catch (error) {
268
- console.warn('[UIRenderer] Draw error:', error);
269
- }
260
+ // Frame stride check — skip some frames for performance
261
+ if (shouldSkipFrame(frameCount)) {
262
+ return; // Skip but don't clear — preserve last drawn frame
263
+ }
264
+ // Calculate t using modulo for natural looping
265
+ const t = (frameCount % totalFrames) / totalFrames;
266
+ p.randomSeed(system.seed);
267
+ p.noiseSeed(system.seed);
268
+ try {
269
+ runDraw(p, frameCount, t);
270
+ drawBadge();
271
+ recordFrame(budget);
272
+ }
273
+ catch (error) {
274
+ console.warn('[UIRenderer] Draw error:', error);
270
275
  }
271
276
  };
272
277
  animationId = requestAnimationFrame(loop);
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @nexart/ui-renderer v0.8.3 - Type Definitions
2
+ * @nexart/ui-renderer v0.8.5 - Type Definitions
3
3
  *
4
4
  * Lightweight Preview Runtime for NexArt Protocol.
5
5
  * This SDK is non-canonical and for preview only.
@@ -9,7 +9,7 @@
9
9
  * - Max total time: 500ms
10
10
  * - Max canvas dimension: 900px
11
11
  */
12
- export declare const SDK_VERSION = "0.8.3";
12
+ export declare const SDK_VERSION = "0.8.5";
13
13
  export declare const AESTHETIC_DEFAULTS: {
14
14
  readonly background: {
15
15
  readonly r: 246;
package/dist/types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @nexart/ui-renderer v0.8.3 - Type Definitions
2
+ * @nexart/ui-renderer v0.8.5 - Type Definitions
3
3
  *
4
4
  * Lightweight Preview Runtime for NexArt Protocol.
5
5
  * This SDK is non-canonical and for preview only.
@@ -9,7 +9,7 @@
9
9
  * - Max total time: 500ms
10
10
  * - Max canvas dimension: 900px
11
11
  */
12
- export const SDK_VERSION = '0.8.3';
12
+ export const SDK_VERSION = '0.8.5';
13
13
  export const AESTHETIC_DEFAULTS = {
14
14
  background: { r: 246, g: 245, b: 242 },
15
15
  foreground: { r: 45, g: 45, b: 45 },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexart/ui-renderer",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
4
  "description": "Lightweight Preview Runtime for NexArt Protocol. Non-canonical, performance-optimized with budget limits (max 30 frames, 500ms, 900px canvas).",
5
5
  "license": "MIT",
6
6
  "type": "module",