@vib3code/sdk 2.0.3-canary.89c05e0 → 2.0.3-canary.8d2fdcd

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 (64) hide show
  1. package/DOCS/AGENT_HARNESS_ARCHITECTURE.md +2 -0
  2. package/DOCS/ANDROID_DEPLOYMENT.md +59 -0
  3. package/DOCS/ARCHITECTURE.md +1 -0
  4. package/DOCS/CI_TESTING.md +2 -0
  5. package/DOCS/CLI_ONBOARDING.md +2 -0
  6. package/DOCS/CONTROL_REFERENCE.md +2 -0
  7. package/DOCS/CROSS_SITE_DESIGN_PATTERNS.md +2 -0
  8. package/DOCS/ENV_SETUP.md +2 -0
  9. package/DOCS/EPIC_SCROLL_EVENTS.md +2 -0
  10. package/DOCS/EXPANSION_DESIGN.md +2 -0
  11. package/DOCS/EXPANSION_DESIGN_ULTRA.md +2 -0
  12. package/DOCS/EXPORT_FORMATS.md +2 -0
  13. package/DOCS/GPU_DISPOSAL_GUIDE.md +2 -0
  14. package/DOCS/HANDOFF_LANDING_PAGE.md +2 -0
  15. package/DOCS/HANDOFF_SDK_DEVELOPMENT.md +2 -0
  16. package/DOCS/LICENSING_TIERS.md +2 -0
  17. package/DOCS/MASTER_PLAN_2026-01-31.md +2 -0
  18. package/DOCS/MULTIVIZ_CHOREOGRAPHY_PATTERNS.md +3 -1
  19. package/DOCS/OBS_SETUP_GUIDE.md +2 -0
  20. package/DOCS/OPTIMIZATION_PLAN_MATH.md +1 -0
  21. package/DOCS/PRODUCT_STRATEGY.md +2 -0
  22. package/DOCS/PROJECT_SETUP.md +2 -0
  23. package/DOCS/README.md +5 -3
  24. package/DOCS/REFERENCE_SCROLL_ANALYSIS.md +2 -0
  25. package/DOCS/RENDERER_LIFECYCLE.md +2 -0
  26. package/DOCS/REPO_MANIFEST.md +2 -0
  27. package/DOCS/ROADMAP.md +2 -0
  28. package/DOCS/SCROLL_TIMELINE_v3.md +2 -0
  29. package/DOCS/SITE_REFACTOR_PLAN.md +2 -0
  30. package/DOCS/STATUS.md +2 -0
  31. package/DOCS/SYSTEM_INVENTORY.md +2 -0
  32. package/DOCS/TELEMETRY_EXPORTS.md +2 -0
  33. package/DOCS/VISUAL_ANALYSIS_CLICKERSS.md +2 -0
  34. package/DOCS/VISUAL_ANALYSIS_FACETAD.md +2 -0
  35. package/DOCS/VISUAL_ANALYSIS_SIMONE.md +2 -0
  36. package/DOCS/VISUAL_ANALYSIS_TABLESIDE.md +2 -0
  37. package/DOCS/WEBGPU_STATUS.md +2 -0
  38. package/DOCS/XR_BENCHMARKS.md +2 -0
  39. package/DOCS/archive/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +1 -34
  40. package/DOCS/archive/DEV_TRACK_ANALYSIS.md +1 -80
  41. package/DOCS/archive/DEV_TRACK_PLAN_2026-01-07.md +1 -42
  42. package/DOCS/archive/SESSION_014_PLAN.md +1 -195
  43. package/DOCS/archive/SESSION_LOG_2026-01-07.md +1 -56
  44. package/DOCS/archive/STRATEGIC_BLUEPRINT_2026-01-07.md +1 -72
  45. package/DOCS/archive/SYSTEM_AUDIT_2026-01-30.md +1 -741
  46. package/DOCS/archive/WEBGPU_STATUS_2026-02-15_STALE.md +1 -38
  47. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-01-31.md +2 -0
  48. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-06.md +2 -0
  49. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-13.md +2 -0
  50. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-15.md +2 -0
  51. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-16.md +2 -0
  52. package/DOCS/dev-tracks/PERF_UPGRADE_2026-02-16.md +2 -0
  53. package/DOCS/dev-tracks/README.md +2 -0
  54. package/package.json +2 -4
  55. package/src/cli/index.js +59 -5
  56. package/src/export/SVGExporter.js +9 -5
  57. package/src/geometry/warp/HypersphereCore.js +53 -24
  58. package/src/math/Mat4x4.js +116 -86
  59. package/src/math/Projection.js +39 -4
  60. package/src/math/Rotor4D.js +31 -1
  61. package/src/math/Vec4.js +127 -30
  62. package/src/scene/Node4D.js +74 -24
  63. package/src/testing/ProjectionClass.test.js +38 -0
  64. package/tools/update_projection.py +109 -0
package/src/math/Vec4.js CHANGED
@@ -128,11 +128,19 @@ export class Vec4 {
128
128
  }
129
129
 
130
130
  /**
131
- * Add another vector (immutable)
131
+ * Add another vector (immutable unless target provided)
132
132
  * @param {Vec4} v
133
- * @returns {Vec4} New vector
134
- */
135
- add(v) {
133
+ * @param {Vec4} [target=null] - Optional target vector
134
+ * @returns {Vec4} New vector or target
135
+ */
136
+ add(v, target = null) {
137
+ if (target) {
138
+ target._x = this._x + v._x;
139
+ target._y = this._y + v._y;
140
+ target._z = this._z + v._z;
141
+ target._w = this._w + v._w;
142
+ return target;
143
+ }
136
144
  return new Vec4(
137
145
  this._x + v._x,
138
146
  this._y + v._y,
@@ -155,11 +163,19 @@ export class Vec4 {
155
163
  }
156
164
 
157
165
  /**
158
- * Subtract another vector (immutable)
166
+ * Subtract another vector (immutable unless target provided)
159
167
  * @param {Vec4} v
160
- * @returns {Vec4} New vector
161
- */
162
- sub(v) {
168
+ * @param {Vec4} [target=null] - Optional target vector
169
+ * @returns {Vec4} New vector or target
170
+ */
171
+ sub(v, target = null) {
172
+ if (target) {
173
+ target._x = this._x - v._x;
174
+ target._y = this._y - v._y;
175
+ target._z = this._z - v._z;
176
+ target._w = this._w - v._w;
177
+ return target;
178
+ }
163
179
  return new Vec4(
164
180
  this._x - v._x,
165
181
  this._y - v._y,
@@ -182,11 +198,19 @@ export class Vec4 {
182
198
  }
183
199
 
184
200
  /**
185
- * Multiply by scalar (immutable)
201
+ * Multiply by scalar (immutable unless target provided)
186
202
  * @param {number} s
187
- * @returns {Vec4} New vector
188
- */
189
- scale(s) {
203
+ * @param {Vec4} [target=null] - Optional target vector
204
+ * @returns {Vec4} New vector or target
205
+ */
206
+ scale(s, target = null) {
207
+ if (target) {
208
+ target._x = this._x * s;
209
+ target._y = this._y * s;
210
+ target._z = this._z * s;
211
+ target._w = this._w * s;
212
+ return target;
213
+ }
190
214
  return new Vec4(
191
215
  this._x * s,
192
216
  this._y * s,
@@ -211,9 +235,17 @@ export class Vec4 {
211
235
  /**
212
236
  * Component-wise multiply (Hadamard product)
213
237
  * @param {Vec4} v
214
- * @returns {Vec4} New vector
215
- */
216
- multiply(v) {
238
+ * @param {Vec4} [target=null] - Optional target vector
239
+ * @returns {Vec4} New vector or target
240
+ */
241
+ multiply(v, target = null) {
242
+ if (target) {
243
+ target._x = this._x * v._x;
244
+ target._y = this._y * v._y;
245
+ target._z = this._z * v._z;
246
+ target._w = this._w * v._w;
247
+ return target;
248
+ }
217
249
  return new Vec4(
218
250
  this._x * v._x,
219
251
  this._y * v._y,
@@ -223,10 +255,18 @@ export class Vec4 {
223
255
  }
224
256
 
225
257
  /**
226
- * Negate vector (immutable)
227
- * @returns {Vec4} New vector
258
+ * Negate vector (immutable unless target provided)
259
+ * @param {Vec4} [target=null] - Optional target vector
260
+ * @returns {Vec4} New vector or target
228
261
  */
229
- negate() {
262
+ negate(target = null) {
263
+ if (target) {
264
+ target._x = -this._x;
265
+ target._y = -this._y;
266
+ target._z = -this._z;
267
+ target._w = -this._w;
268
+ return target;
269
+ }
230
270
  return new Vec4(-this._x, -this._y, -this._z, -this._w);
231
271
  }
232
272
 
@@ -286,15 +326,23 @@ export class Vec4 {
286
326
  }
287
327
 
288
328
  /**
289
- * Normalize to unit length (immutable)
290
- * @returns {Vec4} New normalized vector
329
+ * Normalize to unit length (immutable unless target provided)
330
+ * @param {Vec4} [target=null] - Optional target vector
331
+ * @returns {Vec4} New normalized vector or target
291
332
  */
292
- normalize() {
333
+ normalize(target = null) {
293
334
  const len = this.length();
294
335
  if (len < 1e-10) {
336
+ if (target) {
337
+ target._x = 0;
338
+ target._y = 0;
339
+ target._z = 0;
340
+ target._w = 0;
341
+ return target;
342
+ }
295
343
  return new Vec4(0, 0, 0, 0);
296
344
  }
297
- return this.scale(1 / len);
345
+ return this.scale(1 / len, target);
298
346
  }
299
347
 
300
348
  /**
@@ -314,9 +362,17 @@ export class Vec4 {
314
362
  * Linear interpolation to another vector
315
363
  * @param {Vec4} v - Target vector
316
364
  * @param {number} t - Interpolation factor (0-1)
317
- * @returns {Vec4} New interpolated vector
318
- */
319
- lerp(v, t) {
365
+ * @param {Vec4} [target=null] - Optional target vector
366
+ * @returns {Vec4} New interpolated vector or target
367
+ */
368
+ lerp(v, t, target = null) {
369
+ if (target) {
370
+ target._x = this._x + (v._x - this._x) * t;
371
+ target._y = this._y + (v._y - this._y) * t;
372
+ target._z = this._z + (v._z - this._z) * t;
373
+ target._w = this._w + (v._w - this._w) * t;
374
+ return target;
375
+ }
320
376
  return new Vec4(
321
377
  this._x + (v._x - this._x) * t,
322
378
  this._y + (v._y - this._y) * t,
@@ -352,33 +408,74 @@ export class Vec4 {
352
408
  /**
353
409
  * Project 4D point to 3D using perspective projection
354
410
  * Projects from 4D to 3D by dividing by (d - w)
355
- * @param {number} d - Distance parameter (usually 2-5)
356
- * @param {object} [options] - Projection options (epsilon, distance)
411
+ * @param {number|object} d - Distance parameter (usually 2-5) or options object
412
+ * @param {object|Vec4} [options] - Projection options or target vector
413
+ * @param {Vec4} [target] - Target vector to store result
357
414
  * @returns {Vec4} Projected point (w component is 0)
358
415
  */
359
- projectPerspective(d = 2, options = {}) {
416
+ projectPerspective(d = 2, options = {}, target = null) {
360
417
  if (typeof d === 'object') {
418
+ // usage: projectPerspective({ distance: 2, ... }, target?)
419
+ if (options instanceof Vec4) {
420
+ target = options;
421
+ }
361
422
  options = d;
362
423
  d = options.distance ?? options.d ?? 2;
424
+ } else {
425
+ // usage: projectPerspective(d, options?, target?)
426
+ // usage: projectPerspective(d, target?)
427
+ if (options instanceof Vec4) {
428
+ target = options;
429
+ options = {};
430
+ }
363
431
  }
432
+
433
+ options = options || {};
434
+
364
435
  const epsilon = options.epsilon ?? 1e-5;
365
436
  const denom = d - this._w;
366
437
  const clamped = Math.abs(denom) < epsilon ? (denom >= 0 ? epsilon : -epsilon) : denom;
367
438
  const scale = 1 / clamped;
439
+
440
+ if (target) {
441
+ target._x = this._x * scale;
442
+ target._y = this._y * scale;
443
+ target._z = this._z * scale;
444
+ target._w = 0;
445
+ return target;
446
+ }
447
+
368
448
  return new Vec4(this._x * scale, this._y * scale, this._z * scale, 0);
369
449
  }
370
450
 
371
451
  /**
372
452
  * Project 4D point to 3D using stereographic projection
373
453
  * Maps 4D hypersphere to 3D space
374
- * @param {object} [options] - Projection options (epsilon)
454
+ * @param {object|Vec4} [options] - Projection options or target vector
455
+ * @param {Vec4} [target] - Target vector to store result
375
456
  * @returns {Vec4} Projected point (w component is 0)
376
457
  */
377
- projectStereographic(options = {}) {
458
+ projectStereographic(options = {}, target = null) {
459
+ if (options instanceof Vec4) {
460
+ target = options;
461
+ options = {};
462
+ }
463
+
464
+ options = options || {};
465
+
378
466
  const epsilon = options.epsilon ?? 1e-5;
379
467
  const denom = 1 - this._w;
380
468
  const clamped = Math.abs(denom) < epsilon ? (denom >= 0 ? epsilon : -epsilon) : denom;
381
469
  const scale = 1 / clamped;
470
+
471
+ if (target) {
472
+ target._x = this._x * scale;
473
+ target._y = this._y * scale;
474
+ target._z = this._z * scale;
475
+ target._w = 0;
476
+ return target;
477
+ }
478
+
382
479
  return new Vec4(this._x * scale, this._y * scale, this._z * scale, 0);
383
480
  }
384
481
 
@@ -500,29 +500,74 @@ export class Node4D {
500
500
  * @private
501
501
  */
502
502
  _updateLocalMatrix() {
503
- // Start with identity
504
- this._localMatrix = Mat4x4.identity();
503
+ // Ensure matrix exists
504
+ if (!this._localMatrix) {
505
+ this._localMatrix = new Mat4x4();
506
+ }
507
+
508
+ const m = this._localMatrix.data;
509
+ const s = this._scale;
510
+ const p = this._position;
511
+
512
+ // 1. Write rotation directly to local matrix (No allocation)
513
+ this._rotation.toMatrix(m);
514
+
515
+ // 2. Apply scale (Diagonal matrix multiplication on the right)
516
+ // M = M * S
517
+ // Columns of M are scaled by s.x, s.y, s.z, s.w
518
+
519
+ // Col 0
520
+ m[0] *= s.x; m[1] *= s.x; m[2] *= s.x; m[3] *= s.x;
521
+ // Col 1
522
+ m[4] *= s.y; m[5] *= s.y; m[6] *= s.y; m[7] *= s.y;
523
+ // Col 2
524
+ m[8] *= s.z; m[9] *= s.z; m[10] *= s.z; m[11] *= s.z;
525
+ // Col 3
526
+ m[12] *= s.w; m[13] *= s.w; m[14] *= s.w; m[15] *= s.w;
527
+
528
+ // 3. Apply translation (Matrix multiplication on the left)
529
+ // M = T * M
530
+ // T is standard 3D translation:
531
+ // [ 1 0 0 px ]
532
+ // [ 0 1 0 py ]
533
+ // [ 0 0 1 pz ]
534
+ // [ 0 0 0 1 ]
535
+ //
536
+ // Row 0 += px * Row 3
537
+ // Row 1 += py * Row 3
538
+ // Row 2 += pz * Row 3
539
+
540
+ const px = p.x;
541
+ const py = p.y;
542
+ const pz = p.z;
543
+
544
+ // Row 3 elements of M (which are used in the calculation)
545
+ const m3 = m[3];
546
+ const m7 = m[7];
547
+ const m11 = m[11];
548
+ const m15 = m[15];
549
+
550
+ if (px !== 0) {
551
+ m[0] += px * m3;
552
+ m[4] += px * m7;
553
+ m[8] += px * m11;
554
+ m[12] += px * m15;
555
+ }
556
+
557
+ if (py !== 0) {
558
+ m[1] += py * m3;
559
+ m[5] += py * m7;
560
+ m[9] += py * m11;
561
+ m[13] += py * m15;
562
+ }
563
+
564
+ if (pz !== 0) {
565
+ m[2] += pz * m3;
566
+ m[6] += pz * m7;
567
+ m[10] += pz * m11;
568
+ m[14] += pz * m15;
569
+ }
505
570
 
506
- // Apply scale
507
- const scaleMatrix = Mat4x4.identity();
508
- scaleMatrix.set(0, 0, this._scale.x);
509
- scaleMatrix.set(1, 1, this._scale.y);
510
- scaleMatrix.set(2, 2, this._scale.z);
511
- scaleMatrix.set(3, 3, this._scale.w);
512
-
513
- // Apply rotation (toMatrix returns Float32Array, wrap in Mat4x4)
514
- const rotationMatrix = new Mat4x4(this._rotation.toMatrix());
515
-
516
- // Apply translation (in 4D, translation is stored in last column, keep [3,3]=1)
517
- const translationMatrix = Mat4x4.identity();
518
- translationMatrix.set(0, 3, this._position.x);
519
- translationMatrix.set(1, 3, this._position.y);
520
- translationMatrix.set(2, 3, this._position.z);
521
- // Note: position.w is the 4th spatial coordinate, handled separately
522
- // Matrix[3,3] must remain 1 for proper transformation
523
-
524
- // Compose: T * R * S
525
- this._localMatrix = translationMatrix.multiply(rotationMatrix).multiply(scaleMatrix);
526
571
  this._localDirty = false;
527
572
  }
528
573
 
@@ -535,10 +580,15 @@ export class Node4D {
535
580
  this._updateLocalMatrix();
536
581
  }
537
582
 
583
+ // Ensure matrix exists
584
+ if (!this._worldMatrix) {
585
+ this._worldMatrix = new Mat4x4();
586
+ }
587
+
538
588
  if (this._parent) {
539
- this._worldMatrix = this._parent.worldMatrix.multiply(this._localMatrix);
589
+ this._parent.worldMatrix.multiply(this._localMatrix, this._worldMatrix);
540
590
  } else {
541
- this._worldMatrix = this._localMatrix.clone();
591
+ this._worldMatrix.copy(this._localMatrix);
542
592
  }
543
593
 
544
594
  this._worldDirty = false;
@@ -0,0 +1,38 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import Projection from '../math/Projection.js';
3
+ import Vec4 from '../math/Vec4.js';
4
+
5
+ describe('Projection Class', () => {
6
+ it('should project using perspective', () => {
7
+ const v = new Vec4(1, 1, 1, 0);
8
+ const p = Projection.perspective(v, 2);
9
+ expect(p.x).toBeCloseTo(0.5);
10
+ expect(p.y).toBeCloseTo(0.5);
11
+ expect(p.z).toBeCloseTo(0.5);
12
+ expect(p.w).toBe(0);
13
+ });
14
+
15
+ it('should support target vector in perspective', () => {
16
+ const v = new Vec4(1, 1, 1, 0);
17
+ const target = new Vec4();
18
+ const result = Projection.perspective(v, 2, {}, target);
19
+ expect(result).toBe(target);
20
+ expect(target.x).toBeCloseTo(0.5);
21
+ });
22
+
23
+ it('should project array using perspectiveArray', () => {
24
+ const vectors = [new Vec4(1, 1, 1, 0), new Vec4(2, 2, 2, 0)];
25
+ const result = Projection.perspectiveArray(vectors, 2);
26
+ expect(result.length).toBe(2);
27
+ expect(result[0].x).toBeCloseTo(0.5);
28
+ expect(result[1].x).toBeCloseTo(1.0);
29
+ });
30
+
31
+ it('should reuse target array in perspectiveArray', () => {
32
+ const vectors = [new Vec4(1, 1, 1, 0)];
33
+ const targetArray = [new Vec4()];
34
+ const result = Projection.perspectiveArray(vectors, 2, {}, targetArray);
35
+ expect(result).toBe(targetArray);
36
+ expect(targetArray[0].x).toBeCloseTo(0.5);
37
+ });
38
+ });
@@ -0,0 +1,109 @@
1
+ import re
2
+
3
+ file_path = 'src/math/Projection.js'
4
+
5
+ with open(file_path, 'r') as f:
6
+ content = f.read()
7
+
8
+ # Replace perspective JSDoc and Implementation
9
+ perspective_old = r""" * @param {Vec4} v - 4D point
10
+ * @param {number} d - Distance parameter (typically 1.5-5)
11
+ * @returns {Vec4} Projected point (w=0)
12
+ */
13
+ static perspective(v, d = 2, options = {}) {
14
+ if (typeof d === 'object') {
15
+ options = d;
16
+ d = options.d ?? 2;
17
+ }
18
+ const epsilon = options.epsilon ?? DEFAULT_EPSILON;
19
+ const denom = clampDenominator(d - v.w, epsilon);
20
+ const scale = 1 / denom;
21
+ return new Vec4(v.x * scale, v.y * scale, v.z * scale, 0);
22
+ }"""
23
+
24
+ perspective_new = r""" * @param {Vec4} v - 4D point
25
+ * @param {number} d - Distance parameter (typically 1.5-5)
26
+ * @param {object} [options] - Projection options
27
+ * @param {Vec4} [target] - Optional target vector to write result to
28
+ * @returns {Vec4} Projected point (w=0)
29
+ */
30
+ static perspective(v, d = 2, options = {}, target = null) {
31
+ if (typeof d === 'object') {
32
+ options = d;
33
+ d = options.d ?? 2;
34
+ }
35
+
36
+ // Handle options overload or direct target argument
37
+ if (!target && options && options.target) {
38
+ target = options.target;
39
+ }
40
+
41
+ const epsilon = (options && options.epsilon) ?? DEFAULT_EPSILON;
42
+ const denom = clampDenominator(d - v.w, epsilon);
43
+ const scale = 1 / denom;
44
+
45
+ if (target) {
46
+ return target.set(v.x * scale, v.y * scale, v.z * scale, 0);
47
+ }
48
+ return new Vec4(v.x * scale, v.y * scale, v.z * scale, 0);
49
+ }"""
50
+
51
+ if perspective_old in content:
52
+ content = content.replace(perspective_old, perspective_new)
53
+ else:
54
+ print("Could not find perspective implementation to replace.")
55
+ # Attempt more robust search if exact match fails? No, simpler is safer for now.
56
+
57
+ # Replace perspectiveArray JSDoc and Implementation
58
+ perspective_array_old = r""" /**
59
+ * Project array of Vec4s using perspective projection
60
+ * @param {Vec4[]} vectors
61
+ * @param {number} d
62
+ * @returns {Vec4[]}
63
+ */
64
+ static perspectiveArray(vectors, d = 2, options = {}) {
65
+ return vectors.map(v => Projection.perspective(v, d, options));
66
+ }"""
67
+
68
+ perspective_array_new = r""" /**
69
+ * Project array of Vec4s using perspective projection
70
+ * @param {Vec4[]} vectors
71
+ * @param {number} d
72
+ * @param {object} [options]
73
+ * @param {Vec4[]} [target] - Optional target array to write results to
74
+ * @returns {Vec4[]}
75
+ */
76
+ static perspectiveArray(vectors, d = 2, options = {}, target = null) {
77
+ // Handle options overload for 'd'
78
+ if (typeof d === 'object') {
79
+ options = d;
80
+ d = options.d ?? 2;
81
+ }
82
+
83
+ if (!target) {
84
+ return vectors.map(v => Projection.perspective(v, d, options));
85
+ }
86
+
87
+ const count = vectors.length;
88
+ // Iterate and reuse
89
+ for (let i = 0; i < count; i++) {
90
+ const out = target[i];
91
+ if (out) {
92
+ Projection.perspective(vectors[i], d, options, out);
93
+ } else {
94
+ target[i] = Projection.perspective(vectors[i], d, options);
95
+ }
96
+ }
97
+
98
+ return target;
99
+ }"""
100
+
101
+ if perspective_array_old in content:
102
+ content = content.replace(perspective_array_old, perspective_array_new)
103
+ else:
104
+ print("Could not find perspectiveArray implementation to replace.")
105
+
106
+ with open(file_path, 'w') as f:
107
+ f.write(content)
108
+
109
+ print("Updated Projection.js")