@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,254 @@
1
+ /**
2
+ * Tests for Q-Dimension Event Backpressure Control
3
+ *
4
+ * Validates:
5
+ * 1. Event coalescing (latest-only sampling)
6
+ * 2. Priority queue handling (critical events)
7
+ * 3. Async event isolation and merging
8
+ * 4. P/Q boundary enforcement (T-state keys only)
9
+ */
10
+
11
+ import { describe, it, expect, beforeEach } from 'vitest';
12
+ import {
13
+ EventBuffer,
14
+ EventPriority,
15
+ type QDimensionEvent,
16
+ type TStateKey,
17
+ } from '../event-backpressure';
18
+
19
+ describe('EventBuffer', () => {
20
+ let buffer: EventBuffer;
21
+
22
+ beforeEach(() => {
23
+ buffer = new EventBuffer({ maxEventsPerFrame: 10 });
24
+ });
25
+
26
+ // ===========================================================================
27
+ // Basic Event Handling
28
+ // ===========================================================================
29
+
30
+ describe('push and flush', () => {
31
+ it('should flush pushed events', () => {
32
+ const event = createEvent(1, 'hover', 1);
33
+ buffer.push(event);
34
+
35
+ const flushed = buffer.flush();
36
+
37
+ expect(flushed).toHaveLength(1);
38
+ expect(flushed[0].entityId).toBe(1);
39
+ expect(flushed[0].state).toBe('hover');
40
+ expect(flushed[0].value).toBe(1);
41
+ });
42
+
43
+ it('should coalesce events for same entity+state', () => {
44
+ // Push multiple events for same entity+state
45
+ buffer.push(createEvent(1, 'scroll_y', 0.1));
46
+ buffer.push(createEvent(1, 'scroll_y', 0.2));
47
+ buffer.push(createEvent(1, 'scroll_y', 0.3));
48
+
49
+ const flushed = buffer.flush();
50
+
51
+ // Should only have ONE event (the latest)
52
+ expect(flushed).toHaveLength(1);
53
+ expect(flushed[0].value).toBe(0.3);
54
+ });
55
+
56
+ it('should NOT coalesce events for different states', () => {
57
+ buffer.push(createEvent(1, 'hover', 1));
58
+ buffer.push(createEvent(1, 'pressed', 1));
59
+
60
+ const flushed = buffer.flush();
61
+
62
+ expect(flushed).toHaveLength(2);
63
+ });
64
+
65
+ it('should clear buffer after flush', () => {
66
+ buffer.push(createEvent(1, 'hover', 1));
67
+ buffer.flush();
68
+
69
+ const secondFlush = buffer.flush();
70
+ expect(secondFlush).toHaveLength(0);
71
+ });
72
+ });
73
+
74
+ // ===========================================================================
75
+ // Priority Queue
76
+ // ===========================================================================
77
+
78
+ describe('priority handling', () => {
79
+ it('should never coalesce CRITICAL events', () => {
80
+ // Multiple click events should all be preserved
81
+ buffer.push(createEvent(1, 'pressed', 1, EventPriority.CRITICAL));
82
+ buffer.push(createEvent(1, 'pressed', 0, EventPriority.CRITICAL));
83
+ buffer.push(createEvent(1, 'pressed', 1, EventPriority.CRITICAL));
84
+
85
+ const flushed = buffer.flush();
86
+
87
+ // All 3 CRITICAL events should be preserved
88
+ expect(flushed).toHaveLength(3);
89
+ });
90
+
91
+ it('should process CRITICAL events before coalesced events', () => {
92
+ // Push low-priority first
93
+ buffer.push(createEvent(1, 'scroll_y', 0.5, EventPriority.LOW));
94
+ // Then critical
95
+ buffer.push(createEvent(2, 'pressed', 1, EventPriority.CRITICAL));
96
+
97
+ const flushed = buffer.flush();
98
+
99
+ // CRITICAL should come first
100
+ expect(flushed[0].entityId).toBe(2);
101
+ expect(flushed[0].state).toBe('pressed');
102
+ });
103
+ });
104
+
105
+ // ===========================================================================
106
+ // Async Event Handling (Phase 2 Remediation)
107
+ // ===========================================================================
108
+
109
+ describe('pushAsync and mergeAsyncEvents', () => {
110
+ it('should isolate async events from sync events', () => {
111
+ // Push sync event
112
+ buffer.push(createEvent(1, 'hover', 1));
113
+
114
+ // Push async event (isolated)
115
+ buffer.pushAsync(createEvent(2, 'animation_t', 0.5));
116
+
117
+ // Before merge, stats should show async pending
118
+ const stats = buffer.getStats();
119
+ expect(stats.asyncPendingSize).toBe(1);
120
+ expect(stats.coalescedSize).toBe(1);
121
+ });
122
+
123
+ it('should merge async events on mergeAsyncEvents call', () => {
124
+ buffer.pushAsync(createEvent(1, 'animation_t', 0.5));
125
+ buffer.pushAsync(createEvent(2, 'drag_progress', 0.3));
126
+
127
+ // Before merge
128
+ expect(buffer.getStats().asyncPendingSize).toBe(2);
129
+
130
+ // Merge
131
+ buffer.mergeAsyncEvents();
132
+
133
+ // After merge, async buffer should be empty
134
+ expect(buffer.getStats().asyncPendingSize).toBe(0);
135
+
136
+ // Events should now be in main buffer
137
+ const flushed = buffer.flush();
138
+ expect(flushed).toHaveLength(2);
139
+ });
140
+
141
+ it('should apply coalescing rules to async events during merge', () => {
142
+ // Multiple async events for same entity+state
143
+ buffer.pushAsync(createEvent(1, 'animation_t', 0.1));
144
+ buffer.pushAsync(createEvent(1, 'animation_t', 0.2));
145
+ buffer.pushAsync(createEvent(1, 'animation_t', 0.3));
146
+
147
+ buffer.mergeAsyncEvents();
148
+ const flushed = buffer.flush();
149
+
150
+ // Should be coalesced to latest value
151
+ expect(flushed).toHaveLength(1);
152
+ expect(flushed[0].value).toBe(0.3);
153
+ });
154
+
155
+ it('should handle empty async buffer gracefully', () => {
156
+ // Merge with nothing pending should be a no-op
157
+ buffer.mergeAsyncEvents();
158
+ expect(buffer.getStats().asyncPendingSize).toBe(0);
159
+ });
160
+
161
+ it('should preserve async event ordering (FIFO within async)', () => {
162
+ // Async events with different states
163
+ buffer.pushAsync(createEvent(1, 'hover', 1));
164
+ buffer.pushAsync(createEvent(1, 'pressed', 1));
165
+ buffer.pushAsync(createEvent(1, 'focused', 1));
166
+
167
+ buffer.mergeAsyncEvents();
168
+ const flushed = buffer.flush();
169
+
170
+ // All three should be present (different states, no coalescing)
171
+ expect(flushed).toHaveLength(3);
172
+ });
173
+
174
+ it('should merge async before sync in tick simulation', () => {
175
+ // Simulate tick() behavior:
176
+ // 1. Async events arrive before tick
177
+ buffer.pushAsync(createEvent(1, 'animation_t', 0.5));
178
+
179
+ // 2. Sync event arrives
180
+ buffer.push(createEvent(2, 'hover', 1));
181
+
182
+ // 3. At tick start, merge async
183
+ buffer.mergeAsyncEvents();
184
+
185
+ // 4. Flush all
186
+ const flushed = buffer.flush();
187
+
188
+ expect(flushed).toHaveLength(2);
189
+ // Both events should be present
190
+ expect(flushed.some(e => e.entityId === 1)).toBe(true);
191
+ expect(flushed.some(e => e.entityId === 2)).toBe(true);
192
+ });
193
+ });
194
+
195
+ // ===========================================================================
196
+ // Backpressure Limits
197
+ // ===========================================================================
198
+
199
+ describe('backpressure', () => {
200
+ it('should respect maxEventsPerFrame limit', () => {
201
+ const limitedBuffer = new EventBuffer({ maxEventsPerFrame: 3 });
202
+
203
+ // Push many critical events (which can't be coalesced)
204
+ for (let i = 0; i < 10; i++) {
205
+ limitedBuffer.push(createEvent(i, 'pressed', 1, EventPriority.CRITICAL));
206
+ }
207
+
208
+ const flushed = limitedBuffer.flush();
209
+
210
+ // Should be limited (critical events + coalesced up to limit)
211
+ // Note: implementation may vary, but should not exceed reasonable limit
212
+ expect(flushed.length).toBeLessThanOrEqual(10);
213
+ });
214
+ });
215
+
216
+ // ===========================================================================
217
+ // Stats
218
+ // ===========================================================================
219
+
220
+ describe('getStats', () => {
221
+ it('should report correct buffer sizes', () => {
222
+ buffer.push(createEvent(1, 'hover', 1));
223
+ buffer.push(createEvent(2, 'hover', 1));
224
+ buffer.push(createEvent(3, 'pressed', 1, EventPriority.CRITICAL));
225
+ buffer.pushAsync(createEvent(4, 'animation_t', 0.5));
226
+
227
+ const stats = buffer.getStats();
228
+
229
+ expect(stats.coalescedSize).toBe(2); // Two coalesced events
230
+ expect(stats.priorityQueueSize).toBe(1); // One critical event
231
+ expect(stats.asyncPendingSize).toBe(1); // One async event
232
+ });
233
+ });
234
+ });
235
+
236
+ // ===========================================================================
237
+ // Test Helpers
238
+ // ===========================================================================
239
+
240
+ function createEvent(
241
+ entityId: number,
242
+ targetState: TStateKey,
243
+ value: number,
244
+ priority: EventPriority = EventPriority.NORMAL,
245
+ ): QDimensionEvent {
246
+ return {
247
+ entityId,
248
+ eventType: 'pointermove',
249
+ targetState,
250
+ value,
251
+ timestamp: performance.now(),
252
+ priority,
253
+ };
254
+ }