@viewscript/renderer 0.1.0-202605140639

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/dist/ast/types.d.ts +403 -0
  2. package/dist/ast/types.js +33 -0
  3. package/dist/compiler/chunk-splitter.d.ts +98 -0
  4. package/dist/compiler/chunk-splitter.js +361 -0
  5. package/dist/index.d.ts +55 -0
  6. package/dist/index.js +17 -0
  7. package/dist/rasterizer/__tests__/error-distribution.test.d.ts +7 -0
  8. package/dist/rasterizer/__tests__/error-distribution.test.js +322 -0
  9. package/dist/rasterizer/canvas-mapper.d.ts +280 -0
  10. package/dist/rasterizer/canvas-mapper.js +414 -0
  11. package/dist/rasterizer/error-distribution.d.ts +143 -0
  12. package/dist/rasterizer/error-distribution.js +231 -0
  13. package/dist/rasterizer/gradient-mapper.d.ts +223 -0
  14. package/dist/rasterizer/gradient-mapper.js +352 -0
  15. package/dist/rasterizer/topology-rounding.d.ts +151 -0
  16. package/dist/rasterizer/topology-rounding.js +347 -0
  17. package/dist/runtime/__tests__/event-backpressure.test.d.ts +10 -0
  18. package/dist/runtime/__tests__/event-backpressure.test.js +190 -0
  19. package/dist/runtime/event-backpressure.d.ts +393 -0
  20. package/dist/runtime/event-backpressure.js +458 -0
  21. package/dist/runtime/render-loop.d.ts +277 -0
  22. package/dist/runtime/render-loop.js +435 -0
  23. package/dist/runtime/wasm-resource-manager.d.ts +122 -0
  24. package/dist/runtime/wasm-resource-manager.js +253 -0
  25. package/dist/runtime/wgpu-renderer-adapter.d.ts +168 -0
  26. package/dist/runtime/wgpu-renderer-adapter.js +230 -0
  27. package/dist/semantic/__tests__/semantic-translator.test.d.ts +4 -0
  28. package/dist/semantic/__tests__/semantic-translator.test.js +203 -0
  29. package/dist/semantic/semantic-translator.d.ts +229 -0
  30. package/dist/semantic/semantic-translator.js +398 -0
  31. package/package.json +28 -0
  32. package/playwright-report/data/0bafe4e0863f0e244bba68a838f73241f8f2efaa.md +226 -0
  33. package/playwright-report/data/9281aca8abfb06c6cecb35d5ddd13d61f8c752d8.md +226 -0
  34. package/playwright-report/index.html +90 -0
  35. package/playwright.config.ts +160 -0
  36. package/screenshot-chrome.png +0 -0
  37. package/screenshots/visual-demo-verification.png +0 -0
  38. package/screenshots/visual-demo.png +0 -0
  39. package/src/ast/types.ts +473 -0
  40. package/src/compiler/chunk-splitter.ts +534 -0
  41. package/src/index.ts +62 -0
  42. package/src/rasterizer/__tests__/error-distribution.test.ts +382 -0
  43. package/src/rasterizer/canvas-mapper.ts +677 -0
  44. package/src/rasterizer/error-distribution.ts +344 -0
  45. package/src/rasterizer/gradient-mapper.ts +563 -0
  46. package/src/rasterizer/topology-rounding.ts +499 -0
  47. package/src/runtime/__tests__/event-backpressure.test.ts +254 -0
  48. package/src/runtime/event-backpressure.ts +622 -0
  49. package/src/runtime/render-loop.ts +660 -0
  50. package/src/runtime/wasm-resource-manager.ts +349 -0
  51. package/src/runtime/wgpu-renderer-adapter.ts +318 -0
  52. package/src/semantic/__tests__/semantic-translator.test.ts +263 -0
  53. package/src/semantic/semantic-translator.ts +637 -0
  54. package/test-results/.last-run.json +4 -0
  55. package/tests/e2e/async-race.spec.ts +612 -0
  56. package/tests/e2e/bilayer-sync.spec.ts +405 -0
  57. package/tests/e2e/failures/.gitkeep +0 -0
  58. package/tests/e2e/fullstack.spec.ts +681 -0
  59. package/tests/e2e/g1-continuity.spec.ts +703 -0
  60. package/tests/e2e/golden/.gitkeep +0 -0
  61. package/tests/e2e/golden/conic-color-wheel.raw +0 -0
  62. package/tests/e2e/golden/conic-color-wheel.sha256 +1 -0
  63. package/tests/e2e/golden/conic-rotated.raw +0 -0
  64. package/tests/e2e/golden/conic-rotated.sha256 +1 -0
  65. package/tests/e2e/golden/linear-45deg.raw +0 -0
  66. package/tests/e2e/golden/linear-45deg.sha256 +1 -0
  67. package/tests/e2e/golden/linear-horizontal.raw +0 -0
  68. package/tests/e2e/golden/linear-horizontal.sha256 +1 -0
  69. package/tests/e2e/golden/linear-multi-stop.raw +0 -0
  70. package/tests/e2e/golden/linear-multi-stop.sha256 +1 -0
  71. package/tests/e2e/golden/radial-circle-center.raw +0 -0
  72. package/tests/e2e/golden/radial-circle-center.sha256 +1 -0
  73. package/tests/e2e/golden/radial-offset.raw +0 -0
  74. package/tests/e2e/golden/radial-offset.sha256 +1 -0
  75. package/tests/e2e/golden/tile-mirror.raw +0 -0
  76. package/tests/e2e/golden/tile-mirror.sha256 +1 -0
  77. package/tests/e2e/golden/tile-repeat.raw +0 -0
  78. package/tests/e2e/golden/tile-repeat.sha256 +1 -0
  79. package/tests/e2e/gradient-animation.spec.ts +606 -0
  80. package/tests/e2e/memory-stability.spec.ts +396 -0
  81. package/tests/e2e/path-topology.spec.ts +674 -0
  82. package/tests/e2e/performance-profile.spec.ts +501 -0
  83. package/tests/e2e/screenshot.spec.ts +60 -0
  84. package/tests/e2e/test-harness.html +1005 -0
  85. package/tests/e2e/text-layout.spec.ts +451 -0
  86. package/tests/e2e/visual-demo.html +340 -0
  87. package/tests/e2e/visual-regression.spec.ts +335 -0
  88. package/tsconfig.json +12 -0
  89. package/vitest.config.ts +8 -0
@@ -0,0 +1,393 @@
1
+ /**
2
+ * Q-Dimension Event Backpressure Control
3
+ *
4
+ * High-frequency DOM events (mousemove, scroll, pointermove) can fire
5
+ * hundreds of times per second. Without backpressure, each event would
6
+ * trigger a full constraint graph evaluation, overwhelming the solver.
7
+ *
8
+ * ## Strategy: Latest-Only Sampling with Frame Alignment
9
+ *
10
+ * ```
11
+ * Event Stream (60+ events/frame):
12
+ * ─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─►
13
+ * │ │ │ │ │ │ │ │ │ │ │ │
14
+ * └─┴─┴─┴─┬─┴─┴─┴─┴─┬─┴─┴─► Frame boundaries
15
+ * │ │
16
+ * ▼ ▼
17
+ * Sampled: [latest] [latest] ← One value per frame
18
+ * ```
19
+ *
20
+ * This module provides:
21
+ * 1. Per-entity, per-component event coalescing
22
+ * 2. Frame-aligned sampling (sync with rAF)
23
+ * 3. Configurable throttle strategies
24
+ * 4. Priority queues for critical events (click > mousemove)
25
+ */
26
+ import type { EntityId } from '../ast/types';
27
+ /**
28
+ * Semantic T-vector state keys.
29
+ *
30
+ * Q-dimension events MUST target these state keys, NOT spatial coordinates.
31
+ * P-dimension spatial coordinates (X, Y, Z) are derived from constraints
32
+ * that reference T-vector state, never directly from Q-dimension input.
33
+ *
34
+ * ## The Ouroboros Prevention Principle
35
+ *
36
+ * If Q-dimension input (e.g., MouseEvent.clientX) were allowed to directly
37
+ * mutate P-dimension spatial coordinates, it would create a self-referential
38
+ * loop: mouse position → entity position → canvas render → mouse position...
39
+ *
40
+ * Instead, Q-dimension input mutates T-vector STATE, and the constraint
41
+ * solver derives spatial coordinates as a FUNCTION of that state.
42
+ */
43
+ export type TStateKey = 'hover' | 'pressed' | 'focused' | 'scroll_x' | 'scroll_y' | 'drag_progress' | 'animation_t' | 'gesture_phase';
44
+ /**
45
+ * Raw Q-dimension event from DOM.
46
+ *
47
+ * CRITICAL INVARIANT: Q-dimension events target T-vector STATE keys only.
48
+ * They NEVER directly modify P-dimension spatial coordinates (X, Y, Z).
49
+ */
50
+ export interface QDimensionEvent {
51
+ /** Source entity that fired the event */
52
+ entityId: EntityId;
53
+ /** DOM event type */
54
+ eventType: QEventType;
55
+ /**
56
+ * Target T-vector state key.
57
+ *
58
+ * ARCHITECTURAL CONSTRAINT: This is a semantic state key, NOT a spatial
59
+ * coordinate. P-dimension coordinates are derived via constraint evaluation.
60
+ */
61
+ targetState: TStateKey;
62
+ /**
63
+ * State value (interpretation depends on targetState).
64
+ *
65
+ * - Boolean states: 0 = false, 1 = true
66
+ * - Normalized states: 0.0 to 1.0
67
+ * - Phase states: discrete integers
68
+ */
69
+ value: number;
70
+ /** Event timestamp (performance.now()) */
71
+ timestamp: number;
72
+ /** Priority (higher = more important) */
73
+ priority: EventPriority;
74
+ }
75
+ export type QEventType = 'click' | 'pointerdown' | 'pointerup' | 'pointermove' | 'scroll' | 'wheel' | 'keydown' | 'keyup' | 'focus' | 'blur';
76
+ export declare enum EventPriority {
77
+ /** Immediate: click, keydown (user intent) */
78
+ CRITICAL = 3,
79
+ /** High: pointerdown/up (gesture start/end) */
80
+ HIGH = 2,
81
+ /** Normal: scroll, wheel (continuous) */
82
+ NORMAL = 1,
83
+ /** Low: pointermove (high frequency, lossy OK) */
84
+ LOW = 0
85
+ }
86
+ /**
87
+ * Coalesced event ready for frame processing.
88
+ *
89
+ * This represents a T-vector state mutation, NOT a spatial coordinate change.
90
+ */
91
+ export interface CoalescedEvent {
92
+ entityId: EntityId;
93
+ /** The T-vector state key being mutated */
94
+ state: TStateKey;
95
+ /** The new state value */
96
+ value: number;
97
+ timestamp: number;
98
+ }
99
+ /**
100
+ * Backpressure configuration.
101
+ */
102
+ export interface BackpressureConfig {
103
+ /** Max events to process per frame */
104
+ maxEventsPerFrame: number;
105
+ /** Throttle interval for LOW priority events (ms) */
106
+ lowPriorityThrottleMs: number;
107
+ /** Enable event coalescing (latest-only for same entity+component) */
108
+ enableCoalescing: boolean;
109
+ }
110
+ /**
111
+ * Double-buffered event accumulator.
112
+ *
113
+ * ## Data Structure
114
+ *
115
+ * ```
116
+ * writeBuffer (current frame events):
117
+ * Map<CoalesceKey, QDimensionEvent>
118
+ * - Key: "entity42:x"
119
+ * - Value: Latest event for that entity+component
120
+ * - Overwrites: Yes (latest-only sampling)
121
+ *
122
+ * priorityQueue (critical events):
123
+ * Array<QDimensionEvent>
124
+ * - Never coalesced (click, keydown must all fire)
125
+ * - Processed first
126
+ *
127
+ * pendingAsyncEvents (async callback events):
128
+ * Array<QDimensionEvent>
129
+ * - Events from async callbacks (fetch, setTimeout, promises)
130
+ * - Isolated from sync events to prevent race conditions
131
+ * - Merged at tick start via mergeAsyncEvents()
132
+ * ```
133
+ *
134
+ * ## Async Atomicity (Phase 2 Remediation)
135
+ *
136
+ * Events from async callbacks (fetch handlers, setTimeout, etc.) arrive
137
+ * outside the rAF tick boundary. Without isolation, they could interleave
138
+ * with sync events causing non-deterministic ordering.
139
+ *
140
+ * Solution: Async events are buffered separately and merged atomically
141
+ * at the START of each tick, before any sync event processing.
142
+ */
143
+ export declare class EventBuffer {
144
+ private config;
145
+ /** Coalesced events (latest-only per entity+component) */
146
+ private writeBuffer;
147
+ /** Non-coalesced critical events (click, keydown) */
148
+ private priorityQueue;
149
+ /** Last event time per key (for throttling) */
150
+ private lastEventTime;
151
+ /**
152
+ * Pending async events (from fetch, setTimeout, promises).
153
+ *
154
+ * These are isolated from sync events and merged at tick start
155
+ * to ensure deterministic ordering.
156
+ */
157
+ private pendingAsyncEvents;
158
+ constructor(config?: Partial<BackpressureConfig>);
159
+ /**
160
+ * Push a Q-dimension event into the buffer.
161
+ *
162
+ * ## Coalescing Rules
163
+ *
164
+ * 1. CRITICAL priority: Always queued, never coalesced
165
+ * 2. HIGH/NORMAL/LOW: Coalesced by entity+state (latest wins)
166
+ * 3. LOW with throttle: Dropped if within throttle window
167
+ */
168
+ push(event: QDimensionEvent): void;
169
+ /**
170
+ * Flush buffer for frame processing.
171
+ *
172
+ * Returns events in priority order:
173
+ * 1. All CRITICAL events (in order received)
174
+ * 2. Coalesced events (limited by maxEventsPerFrame)
175
+ *
176
+ * ## Frame Alignment
177
+ *
178
+ * This method is called once per rAF tick. Events that arrive
179
+ * after flush() but before next tick accumulate in the buffer.
180
+ */
181
+ flush(): CoalescedEvent[];
182
+ /**
183
+ * Get current buffer sizes (for debugging/metrics).
184
+ */
185
+ getStats(): {
186
+ priorityQueueSize: number;
187
+ coalescedSize: number;
188
+ asyncPendingSize: number;
189
+ };
190
+ /**
191
+ * Push an event from an async callback.
192
+ *
193
+ * Use this method for events originating from:
194
+ * - fetch() handlers
195
+ * - setTimeout / setInterval callbacks
196
+ * - Promise .then() / .catch() handlers
197
+ * - WebSocket message handlers
198
+ * - IndexedDB callbacks
199
+ * - Any other async context
200
+ *
201
+ * ## Why Separate From push()?
202
+ *
203
+ * Async callbacks can fire at any time, potentially mid-tick or between
204
+ * ticks. If mixed with sync events without ordering guarantees, the result
205
+ * is non-deterministic constraint evaluation.
206
+ *
207
+ * By isolating async events, we ensure:
208
+ * 1. All async events are processed in FIFO order
209
+ * 2. They are merged BEFORE sync events at tick start
210
+ * 3. The tick sees a consistent snapshot of async state
211
+ *
212
+ * ## Example
213
+ *
214
+ * ```typescript
215
+ * fetch('/api/data').then(response => {
216
+ * // WRONG: buffer.push(event) - could race with sync events
217
+ * // CORRECT: buffer.pushAsync(event) - isolated until tick start
218
+ * buffer.pushAsync({
219
+ * entityId: 42,
220
+ * eventType: 'custom',
221
+ * targetState: 'animation_t',
222
+ * value: response.progress,
223
+ * timestamp: performance.now(),
224
+ * priority: EventPriority.NORMAL,
225
+ * });
226
+ * });
227
+ * ```
228
+ */
229
+ pushAsync(event: QDimensionEvent): void;
230
+ /**
231
+ * Merge pending async events into the main buffers.
232
+ *
233
+ * MUST be called at the START of each tick, BEFORE flush().
234
+ *
235
+ * This ensures:
236
+ * 1. Async events are processed before sync events from the same frame
237
+ * 2. No async events can arrive mid-flush (atomicity)
238
+ * 3. Deterministic ordering: async (FIFO) → sync (coalesced/priority)
239
+ *
240
+ * ## Call Site
241
+ *
242
+ * AtomicRenderLoop.tick():
243
+ * ```typescript
244
+ * function tick(timestamp) {
245
+ * // FIRST: Merge async events atomically
246
+ * eventBuffer.mergeAsyncEvents();
247
+ *
248
+ * // THEN: Flush and process all events
249
+ * const mutations = eventBuffer.flush();
250
+ * // ... rest of tick
251
+ * }
252
+ * ```
253
+ */
254
+ mergeAsyncEvents(): void;
255
+ }
256
+ /**
257
+ * DOM event controller with automatic backpressure.
258
+ *
259
+ * ## Data Flow
260
+ *
261
+ * ```
262
+ * DOM Event (mousemove)
263
+ * │
264
+ * ▼
265
+ * EventController.handleEvent()
266
+ * │
267
+ * ├─▶ Compute T-vector value (from event data)
268
+ * │
269
+ * ├─▶ Wrap as QDimensionEvent
270
+ * │
271
+ * ▼
272
+ * EventBuffer.push()
273
+ * │
274
+ * ├─▶ Throttle check (LOW priority)
275
+ * │
276
+ * ├─▶ Coalesce (latest-only)
277
+ * │
278
+ * ▼
279
+ * Buffer accumulates until next rAF
280
+ * │
281
+ * ▼
282
+ * AtomicRenderLoop.tick()
283
+ * │
284
+ * ├─▶ EventBuffer.flush()
285
+ * │
286
+ * ▼
287
+ * CoalescedEvent[] → ConstraintSolver
288
+ * ```
289
+ */
290
+ export declare class EventController {
291
+ private buffer;
292
+ private entityElements;
293
+ private boundHandlers;
294
+ constructor(buffer: EventBuffer);
295
+ /**
296
+ * Register a DOM element for event handling.
297
+ */
298
+ registerElement(entityId: EntityId, element: HTMLElement): void;
299
+ /**
300
+ * Bind an event type to a T-vector state key.
301
+ *
302
+ * ## Ouroboros Prevention
303
+ *
304
+ * The valueMapper function MUST return a SEMANTIC state value, not a
305
+ * raw spatial coordinate. For example:
306
+ *
307
+ * CORRECT (semantic state):
308
+ * - `'hover'` → (e) => e.type === 'pointerenter' ? 1 : 0
309
+ * - `'scroll_y'` → (e) => e.target.scrollTop / e.target.scrollHeight
310
+ * - `'drag_progress'` → (e) => computeNormalizedDragProgress(e)
311
+ *
312
+ * FORBIDDEN (spatial coordinate - would violate P/Q boundary):
313
+ * - `'x'` → (e) => e.clientX // NEVER DO THIS
314
+ * - `'y'` → (e) => e.clientY // NEVER DO THIS
315
+ *
316
+ * @param entityId - Entity to bind the event to
317
+ * @param eventType - DOM event type
318
+ * @param targetState - T-vector state key (semantic, not spatial)
319
+ * @param valueMapper - Function to compute state value from DOM event
320
+ */
321
+ bindEvent(entityId: EntityId, eventType: QEventType, targetState: TStateKey, valueMapper: (event: Event) => number): void;
322
+ /**
323
+ * Unbind all events for an entity.
324
+ */
325
+ unbindEntity(entityId: EntityId): void;
326
+ /**
327
+ * Map event type to priority.
328
+ */
329
+ private getPriority;
330
+ }
331
+ /**
332
+ * ## Integration with Render Loop
333
+ *
334
+ * ```typescript
335
+ * const buffer = new EventBuffer({ maxEventsPerFrame: 50 });
336
+ * const controller = new EventController(buffer);
337
+ *
338
+ * // Register entity's DOM element
339
+ * controller.registerElement(entity.id, domElement);
340
+ *
341
+ * // CORRECT: Bind semantic state (hover) to T-vector
342
+ * controller.bindEvent(
343
+ * entity.id,
344
+ * 'pointerenter',
345
+ * 'hover',
346
+ * () => 1 // hover = true
347
+ * );
348
+ * controller.bindEvent(
349
+ * entity.id,
350
+ * 'pointerleave',
351
+ * 'hover',
352
+ * () => 0 // hover = false
353
+ * );
354
+ *
355
+ * // CORRECT: Bind normalized scroll position
356
+ * controller.bindEvent(
357
+ * entity.id,
358
+ * 'scroll',
359
+ * 'scroll_y',
360
+ * (e: Event) => {
361
+ * const target = e.target as HTMLElement;
362
+ * const maxScroll = target.scrollHeight - target.clientHeight;
363
+ * return maxScroll > 0 ? target.scrollTop / maxScroll : 0;
364
+ * }
365
+ * );
366
+ *
367
+ * // In render loop tick():
368
+ * const events = buffer.flush();
369
+ *
370
+ * // Events are T-vector STATE mutations, not spatial coordinates
371
+ * // The constraint solver evaluates P-dimension coordinates AS A FUNCTION
372
+ * // of T-vector state (via constraints like: A.x = 100 when T.hover = 1)
373
+ * const stateMutations = events.map(e => ({
374
+ * entityId: e.entityId,
375
+ * state: e.state, // Semantic state key (hover, scroll_y, etc.)
376
+ * value: e.value, // State value
377
+ * timestamp: e.timestamp,
378
+ * }));
379
+ *
380
+ * // Constraint solver derives X, Y, Z from T-vector state
381
+ * constraintSolver.evaluateWithTVector(stateMutations);
382
+ * ```
383
+ *
384
+ * ## Architectural Guarantee: P/Q Boundary Preservation
385
+ *
386
+ * By restricting Q-dimension events to T-vector STATE keys (not spatial
387
+ * coordinates), we ensure that:
388
+ *
389
+ * 1. Mouse coordinates (clientX/Y) are NEVER directly assigned to P-dimension
390
+ * 2. P-dimension coordinates are always derived via constraint evaluation
391
+ * 3. The constraint graph is the SINGLE SOURCE OF TRUTH for spatial layout
392
+ * 4. LEAN 4 decidability proofs remain valid (no floating-point pollution)
393
+ */