@auraindustry/aurajs 0.0.3 → 0.0.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 ADDED
@@ -0,0 +1,36 @@
1
+ # @auraindustry/aurajs
2
+
3
+ > Alpha status: AuraJS is currently in alpha and is not safe for production use yet.
4
+
5
+ Write games in JavaScript and build native binaries for macOS, Linux, and Windows.
6
+
7
+ ## Create A New Game
8
+
9
+ ```bash
10
+ npm install -g auramaxx
11
+ auramaxx create my-game
12
+ cd my-game
13
+ npm run dev
14
+ ```
15
+
16
+ ## Publish And Play
17
+
18
+ In a scaffolded AuraJS game:
19
+
20
+ ```bash
21
+ npm run publish
22
+ npm run play
23
+ ```
24
+
25
+ `npm run publish` maps to `npx auramaxx publish` in generated projects.
26
+
27
+ ## API Surfaces
28
+
29
+ - React embedding: `@auraindustry/aurajs/react`
30
+ - Web runtime helpers: `@auraindustry/aurajs/web`
31
+
32
+ ## Docs
33
+
34
+ - Repo README: <https://github.com/Aura-Industry/aurajs>
35
+ - Core API: <https://github.com/Aura-Industry/aurajs/blob/main/packages/aurascript/docs/api-contract-v1.md>
36
+ - 3D API: <https://github.com/Aura-Industry/aurajs/blob/main/packages/aurascript/docs/api-contract-3d-v2.md>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@auraindustry/aurajs",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Write games in JavaScript, build native binaries.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -309,12 +309,25 @@ const WEB_INDEX_HTML = [
309
309
  ' <meta charset="utf-8" />',
310
310
  ' <meta name="viewport" content="width=device-width, initial-scale=1" />',
311
311
  ' <title>AuraJS Web Build</title>',
312
+ ' <style>',
313
+ ' html, body { margin: 0; width: 100%; height: 100%; overflow: hidden; background: #060912; }',
314
+ ' #aura-root { width: 100vw; height: 100vh; display: grid; place-items: center; }',
315
+ ' #aura-canvas { display: block; max-width: 100%; max-height: 100%; }',
316
+ ' </style>',
312
317
  '</head>',
313
318
  '<body>',
314
319
  ' <div id="aura-root">',
315
320
  ' <canvas id="aura-canvas"></canvas>',
316
321
  ' </div>',
317
322
  ' <script src="./js/aura-web-loader.js"></script>',
323
+ ' <script>',
324
+ ' window.addEventListener("load", function () {',
325
+ ' if (!window.AuraWebLoader || typeof window.AuraWebLoader.mount !== "function") return;',
326
+ ' window.AuraWebLoader.mount("#aura-root", { rootUrl: ".", mount: "#aura-root" }).catch(function (error) {',
327
+ ' console.error(error);',
328
+ ' });',
329
+ ' });',
330
+ ' </script>',
318
331
  '</body>',
319
332
  '</html>',
320
333
  ].join('\n');
@@ -337,6 +350,7 @@ const WEB_LOADER_SOURCE = `
337
350
  let cachedManifest = null;
338
351
  let cachedRuntimeConfig = null;
339
352
  let mountedRuntime = null;
353
+ let auraRuntime = null;
340
354
 
341
355
  function normalizeCount(value) {
342
356
  const numeric = Number(value);
@@ -434,6 +448,135 @@ const WEB_LOADER_SOURCE = `
434
448
  return path.replace(/\\\\/g, '/').replace(/^\\.\\//, '');
435
449
  }
436
450
 
451
+ function clampUnit(value, fallback) {
452
+ const numeric = Number(value);
453
+ if (!Number.isFinite(numeric)) return fallback;
454
+ if (numeric <= 0) return 0;
455
+ if (numeric >= 1) return 1;
456
+ return numeric;
457
+ }
458
+
459
+ function createUnitColor(r, g, b, a) {
460
+ return {
461
+ r: clampUnit(r, 0),
462
+ g: clampUnit(g, 0),
463
+ b: clampUnit(b, 0),
464
+ a: clampUnit(a == null ? 1 : a, 1)
465
+ };
466
+ }
467
+
468
+ function createByteColor(r, g, b, a) {
469
+ return {
470
+ r: Math.max(0, Math.min(255, Math.round(Number(r) || 0))),
471
+ g: Math.max(0, Math.min(255, Math.round(Number(g) || 0))),
472
+ b: Math.max(0, Math.min(255, Math.round(Number(b) || 0))),
473
+ a: Math.max(0, Math.min(255, Math.round(Number.isFinite(Number(a)) ? Number(a) : 255)))
474
+ };
475
+ }
476
+
477
+ function normalizeColor(value, fallback) {
478
+ const source = value && typeof value === 'object' ? value : null;
479
+ const base = fallback && typeof fallback === 'object' ? fallback : createUnitColor(1, 1, 1, 1);
480
+ if (!source) {
481
+ return createUnitColor(base.r, base.g, base.b, base.a);
482
+ }
483
+
484
+ const rawR = Number(source.r);
485
+ const rawG = Number(source.g);
486
+ const rawB = Number(source.b);
487
+ const rawA = source.a == null ? base.a : Number(source.a);
488
+ const treatAsByteRange = [rawR, rawG, rawB, rawA].some((component) => Number.isFinite(component) && component > 1);
489
+
490
+ if (treatAsByteRange) {
491
+ return createUnitColor(
492
+ Number.isFinite(rawR) ? rawR / 255 : base.r,
493
+ Number.isFinite(rawG) ? rawG / 255 : base.g,
494
+ Number.isFinite(rawB) ? rawB / 255 : base.b,
495
+ Number.isFinite(rawA) ? rawA / 255 : base.a
496
+ );
497
+ }
498
+
499
+ return createUnitColor(
500
+ Number.isFinite(rawR) ? rawR : base.r,
501
+ Number.isFinite(rawG) ? rawG : base.g,
502
+ Number.isFinite(rawB) ? rawB : base.b,
503
+ Number.isFinite(rawA) ? rawA : base.a
504
+ );
505
+ }
506
+
507
+ function colorToCss(value, fallback) {
508
+ const normalized = normalizeColor(value, fallback);
509
+ return 'rgba('
510
+ + Math.round(normalized.r * 255) + ', '
511
+ + Math.round(normalized.g * 255) + ', '
512
+ + Math.round(normalized.b * 255) + ', '
513
+ + normalized.a + ')';
514
+ }
515
+
516
+ function normalizeCanvasSize(value, fallback) {
517
+ const numeric = Number(value);
518
+ if (!Number.isFinite(numeric) || numeric <= 0) return fallback;
519
+ return Math.max(1, Math.floor(numeric));
520
+ }
521
+
522
+ function normalizePositiveNumber(value, fallback) {
523
+ const numeric = Number(value);
524
+ if (!Number.isFinite(numeric) || numeric <= 0) return fallback;
525
+ return numeric;
526
+ }
527
+
528
+ function normalizeTextAlign(value) {
529
+ if (value === 'center' || value === 'right' || value === 'left') {
530
+ return value;
531
+ }
532
+ return 'left';
533
+ }
534
+
535
+ function normalizeKeyName(value) {
536
+ if (typeof value !== 'string' || value.length === 0) {
537
+ return null;
538
+ }
539
+ const key = value.toLowerCase();
540
+ switch (key) {
541
+ case ' ':
542
+ case 'spacebar':
543
+ return 'space';
544
+ case 'left':
545
+ case 'arrowleft':
546
+ return 'arrowleft';
547
+ case 'right':
548
+ case 'arrowright':
549
+ return 'arrowright';
550
+ case 'up':
551
+ case 'arrowup':
552
+ return 'arrowup';
553
+ case 'down':
554
+ case 'arrowdown':
555
+ return 'arrowdown';
556
+ case 'return':
557
+ return 'enter';
558
+ default:
559
+ return key;
560
+ }
561
+ }
562
+
563
+ function rectIntersects(a, b) {
564
+ const ax = Number(a && a.x);
565
+ const ay = Number(a && a.y);
566
+ const aw = Number(a && a.w);
567
+ const ah = Number(a && a.h);
568
+ const bx = Number(b && b.x);
569
+ const by = Number(b && b.y);
570
+ const bw = Number(b && b.w);
571
+ const bh = Number(b && b.h);
572
+
573
+ if (![ax, ay, aw, ah, bx, by, bw, bh].every((value) => Number.isFinite(value))) {
574
+ return false;
575
+ }
576
+
577
+ return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by;
578
+ }
579
+
437
580
  function resolveTarget(target) {
438
581
  if (typeof target === 'string') {
439
582
  return document.querySelector(target);
@@ -484,17 +627,503 @@ const WEB_LOADER_SOURCE = `
484
627
  });
485
628
  }
486
629
 
487
- function normalizePositiveNumber(value, fallback) {
488
- const numeric = Number(value);
489
- if (!Number.isFinite(numeric) || numeric <= 0) return fallback;
490
- return numeric;
630
+ function createBrowserAuraSurface(runtimeConfig) {
631
+ const defaultColor = createUnitColor(1, 1, 1, 1);
632
+ let currentRuntimeConfig = runtimeConfig && typeof runtimeConfig === 'object' ? runtimeConfig : {};
633
+ let currentCanvasConfig = currentRuntimeConfig.canvas && typeof currentRuntimeConfig.canvas === 'object'
634
+ ? currentRuntimeConfig.canvas
635
+ : {};
636
+
637
+ const auraRef = globalRef.aura && typeof globalRef.aura === 'object'
638
+ ? globalRef.aura
639
+ : {};
640
+ globalRef.aura = auraRef;
641
+
642
+ const inputState = {
643
+ down: new Set(),
644
+ pendingPressed: new Set(),
645
+ pendingReleased: new Set(),
646
+ framePressed: new Set(),
647
+ frameReleased: new Set()
648
+ };
649
+
650
+ const runtime = {
651
+ canvas: null,
652
+ mountTarget: null,
653
+ context2d: null,
654
+ configuredWidth: normalizeCanvasSize(currentCanvasConfig.width, 1280),
655
+ configuredHeight: normalizeCanvasSize(currentCanvasConfig.height, 720),
656
+ resizeMode: currentCanvasConfig.resizeMode === 'fixed' ? 'fixed' : 'fit-container',
657
+ pixelRatio: 1,
658
+ width: normalizeCanvasSize(currentCanvasConfig.width, 1280),
659
+ height: normalizeCanvasSize(currentCanvasConfig.height, 720),
660
+ transformDepth: 0,
661
+ listenersAttached: false,
662
+ keydownListener: null,
663
+ keyupListener: null,
664
+ blurListener: null,
665
+ resizeListener: null
666
+ };
667
+
668
+ function ensureStyleObject(node) {
669
+ if (!node || typeof node !== 'object') return null;
670
+ if (!node.style || typeof node.style !== 'object') {
671
+ node.style = {};
672
+ }
673
+ return node.style;
674
+ }
675
+
676
+ function ensureCanvasContext() {
677
+ if (!runtime.canvas) return null;
678
+ if (!runtime.context2d && typeof runtime.canvas.getContext === 'function') {
679
+ runtime.context2d = runtime.canvas.getContext('2d');
680
+ }
681
+ return runtime.context2d;
682
+ }
683
+
684
+ function syncCanvasSize(notifyResize) {
685
+ let width = runtime.configuredWidth;
686
+ let height = runtime.configuredHeight;
687
+ if (runtime.resizeMode === 'fit-container') {
688
+ const containerWidth = runtime.mountTarget
689
+ ? normalizeCanvasSize(runtime.mountTarget.clientWidth, 0)
690
+ : 0;
691
+ const containerHeight = runtime.mountTarget
692
+ ? normalizeCanvasSize(runtime.mountTarget.clientHeight, 0)
693
+ : 0;
694
+ if (containerWidth > 0 && containerHeight > 0) {
695
+ width = containerWidth;
696
+ height = containerHeight;
697
+ }
698
+ }
699
+
700
+ runtime.width = normalizeCanvasSize(width, runtime.configuredWidth);
701
+ runtime.height = normalizeCanvasSize(height, runtime.configuredHeight);
702
+ runtime.pixelRatio = Math.min(Math.max(normalizePositiveNumber(globalRef.devicePixelRatio, 1), 1), 2);
703
+
704
+ auraRef.window.width = runtime.width;
705
+ auraRef.window.height = runtime.height;
706
+ auraRef.window.pixelRatio = runtime.pixelRatio;
707
+
708
+ if (runtime.canvas) {
709
+ runtime.canvas.width = Math.max(1, Math.round(runtime.width * runtime.pixelRatio));
710
+ runtime.canvas.height = Math.max(1, Math.round(runtime.height * runtime.pixelRatio));
711
+ runtime.canvas.tabIndex = 0;
712
+ const style = ensureStyleObject(runtime.canvas);
713
+ if (style) {
714
+ style.width = runtime.width + 'px';
715
+ style.height = runtime.height + 'px';
716
+ style.display = 'block';
717
+ }
718
+ }
719
+
720
+ if (notifyResize && typeof auraRef.onResize === 'function') {
721
+ auraRef.onResize(runtime.width, runtime.height);
722
+ }
723
+ }
724
+
725
+ function resetDrawState() {
726
+ const ctx = ensureCanvasContext();
727
+ if (!ctx) return;
728
+ if (typeof ctx.setTransform === 'function') {
729
+ ctx.setTransform(runtime.pixelRatio, 0, 0, runtime.pixelRatio, 0, 0);
730
+ } else {
731
+ if (typeof ctx.resetTransform === 'function') {
732
+ ctx.resetTransform();
733
+ }
734
+ if (typeof ctx.scale === 'function') {
735
+ ctx.scale(runtime.pixelRatio, runtime.pixelRatio);
736
+ }
737
+ }
738
+ runtime.transformDepth = 0;
739
+ ctx.globalAlpha = 1;
740
+ ctx.textAlign = 'left';
741
+ ctx.textBaseline = 'top';
742
+ }
743
+
744
+ function applyFont(options) {
745
+ const ctx = ensureCanvasContext();
746
+ const source = options && typeof options === 'object' ? options : {};
747
+ const size = normalizePositiveNumber(source.size, 16);
748
+ const family = typeof source.font === 'string' && source.font.trim().length > 0
749
+ ? source.font.trim()
750
+ : 'sans-serif';
751
+ if (ctx) {
752
+ ctx.font = size + 'px ' + family;
753
+ ctx.textAlign = normalizeTextAlign(source.align);
754
+ ctx.textBaseline = 'top';
755
+ }
756
+ return { size, align: normalizeTextAlign(source.align) };
757
+ }
758
+
759
+ function attachListeners() {
760
+ if (runtime.listenersAttached) return;
761
+ runtime.listenersAttached = true;
762
+
763
+ runtime.keydownListener = function (event) {
764
+ const key = normalizeKeyName(event && (event.key || event.code));
765
+ if (!key) return;
766
+ if (!inputState.down.has(key)) {
767
+ inputState.pendingPressed.add(key);
768
+ }
769
+ inputState.down.add(key);
770
+ if (event && typeof event.preventDefault === 'function' && (key === 'space' || key.startsWith('arrow'))) {
771
+ event.preventDefault();
772
+ }
773
+ };
774
+
775
+ runtime.keyupListener = function (event) {
776
+ const key = normalizeKeyName(event && (event.key || event.code));
777
+ if (!key) return;
778
+ inputState.down.delete(key);
779
+ inputState.pendingReleased.add(key);
780
+ if (event && typeof event.preventDefault === 'function' && (key === 'space' || key.startsWith('arrow'))) {
781
+ event.preventDefault();
782
+ }
783
+ };
784
+
785
+ runtime.blurListener = function () {
786
+ inputState.down.clear();
787
+ inputState.pendingPressed.clear();
788
+ inputState.pendingReleased.clear();
789
+ inputState.framePressed.clear();
790
+ inputState.frameReleased.clear();
791
+ };
792
+
793
+ runtime.resizeListener = function () {
794
+ syncCanvasSize(true);
795
+ };
796
+
797
+ if (typeof globalRef.addEventListener === 'function') {
798
+ globalRef.addEventListener('keydown', runtime.keydownListener);
799
+ globalRef.addEventListener('keyup', runtime.keyupListener);
800
+ globalRef.addEventListener('blur', runtime.blurListener);
801
+ globalRef.addEventListener('resize', runtime.resizeListener);
802
+ }
803
+ }
804
+
805
+ function detachListeners() {
806
+ if (!runtime.listenersAttached) return;
807
+ runtime.listenersAttached = false;
808
+ if (typeof globalRef.removeEventListener === 'function') {
809
+ globalRef.removeEventListener('keydown', runtime.keydownListener);
810
+ globalRef.removeEventListener('keyup', runtime.keyupListener);
811
+ globalRef.removeEventListener('blur', runtime.blurListener);
812
+ globalRef.removeEventListener('resize', runtime.resizeListener);
813
+ }
814
+ }
815
+
816
+ auraRef.setup = typeof auraRef.setup === 'function' ? auraRef.setup : null;
817
+ auraRef.update = typeof auraRef.update === 'function' ? auraRef.update : null;
818
+ auraRef.draw = typeof auraRef.draw === 'function' ? auraRef.draw : null;
819
+ auraRef.onResize = typeof auraRef.onResize === 'function' ? auraRef.onResize : null;
820
+ auraRef.onFocus = typeof auraRef.onFocus === 'function' ? auraRef.onFocus : null;
821
+ auraRef.onBlur = typeof auraRef.onBlur === 'function' ? auraRef.onBlur : null;
822
+ auraRef.onQuit = typeof auraRef.onQuit === 'function' ? auraRef.onQuit : null;
823
+
824
+ auraRef.rgba = function (r, g, b, a) {
825
+ return createUnitColor(r, g, b, a == null ? 1 : a);
826
+ };
827
+ auraRef.color = auraRef.rgba;
828
+ auraRef.Color = auraRef.Color && typeof auraRef.Color === 'object'
829
+ ? auraRef.Color
830
+ : {
831
+ WHITE: createUnitColor(1, 1, 1, 1),
832
+ BLACK: createUnitColor(0, 0, 0, 1),
833
+ RED: createUnitColor(1, 0, 0, 1),
834
+ YELLOW: createUnitColor(1, 1, 0, 1),
835
+ TRANSPARENT: createUnitColor(0, 0, 0, 0)
836
+ };
837
+ auraRef.colors = auraRef.colors && typeof auraRef.colors === 'object'
838
+ ? auraRef.colors
839
+ : {
840
+ white: createByteColor(255, 255, 255, 255),
841
+ black: createByteColor(0, 0, 0, 255),
842
+ red: createByteColor(255, 0, 0, 255),
843
+ yellow: createByteColor(255, 255, 0, 255),
844
+ transparent: createByteColor(0, 0, 0, 0)
845
+ };
846
+
847
+ auraRef.window = auraRef.window && typeof auraRef.window === 'object' ? auraRef.window : {};
848
+ auraRef.window.width = runtime.width;
849
+ auraRef.window.height = runtime.height;
850
+ auraRef.window.pixelRatio = runtime.pixelRatio;
851
+ auraRef.window.fps = 60;
852
+ auraRef.window.setTitle = function (title) {
853
+ if (typeof title === 'string') {
854
+ document.title = title;
855
+ }
856
+ };
857
+ auraRef.window.setSize = function (width, height) {
858
+ runtime.configuredWidth = normalizeCanvasSize(width, runtime.configuredWidth);
859
+ runtime.configuredHeight = normalizeCanvasSize(height, runtime.configuredHeight);
860
+ syncCanvasSize(true);
861
+ resetDrawState();
862
+ return true;
863
+ };
864
+ auraRef.window.setFullscreen = function () {
865
+ return false;
866
+ };
867
+ auraRef.window.getSize = function () {
868
+ return { width: runtime.width, height: runtime.height };
869
+ };
870
+ auraRef.window.getPixelRatio = function () {
871
+ return runtime.pixelRatio;
872
+ };
873
+ auraRef.window.getFPS = function () {
874
+ return auraRef.window.fps;
875
+ };
876
+ auraRef.window.close = function () {
877
+ return true;
878
+ };
879
+
880
+ auraRef.collision = auraRef.collision && typeof auraRef.collision === 'object' ? auraRef.collision : {};
881
+ auraRef.collision.rectRect = function (a, b) {
882
+ return rectIntersects(a, b);
883
+ };
884
+ auraRef.collide = auraRef.collide && typeof auraRef.collide === 'object' ? auraRef.collide : auraRef.collision;
885
+ auraRef.collide.rectRect = auraRef.collision.rectRect;
886
+
887
+ auraRef.input = auraRef.input && typeof auraRef.input === 'object' ? auraRef.input : {};
888
+ auraRef.input.isDown = function (name) {
889
+ const key = normalizeKeyName(name);
890
+ return key ? inputState.down.has(key) : false;
891
+ };
892
+ auraRef.input.isPressed = function (name) {
893
+ const key = normalizeKeyName(name);
894
+ return key ? inputState.framePressed.has(key) : false;
895
+ };
896
+ auraRef.input.isReleased = function (name) {
897
+ const key = normalizeKeyName(name);
898
+ return key ? inputState.frameReleased.has(key) : false;
899
+ };
900
+ auraRef.input.isKeyDown = function (name) {
901
+ return auraRef.input.isDown(name);
902
+ };
903
+ auraRef.input.isKeyPressed = function (name) {
904
+ return auraRef.input.isPressed(name);
905
+ };
906
+ auraRef.input.isKeyReleased = function (name) {
907
+ return auraRef.input.isReleased(name);
908
+ };
909
+ auraRef.input.mouse = auraRef.input.mouse && typeof auraRef.input.mouse === 'object' ? auraRef.input.mouse : {};
910
+ auraRef.input.mouse.x = Number(auraRef.input.mouse.x) || 0;
911
+ auraRef.input.mouse.y = Number(auraRef.input.mouse.y) || 0;
912
+ auraRef.input.mouse.scroll = Number(auraRef.input.mouse.scroll) || 0;
913
+ auraRef.input.mouse.isDown = function () { return false; };
914
+ auraRef.input.mouse.isPressed = function () { return false; };
915
+ auraRef.input.mouse.isReleased = function () { return false; };
916
+ auraRef.input.getMousePosition = function () {
917
+ return { x: auraRef.input.mouse.x, y: auraRef.input.mouse.y };
918
+ };
919
+
920
+ auraRef.assets = auraRef.assets && typeof auraRef.assets === 'object' ? auraRef.assets : {};
921
+ auraRef.assets.load = typeof auraRef.assets.load === 'function'
922
+ ? auraRef.assets.load
923
+ : async function () { return true; };
924
+ auraRef.assets.exists = typeof auraRef.assets.exists === 'function'
925
+ ? auraRef.assets.exists
926
+ : function () { return false; };
927
+ auraRef.assets.image = typeof auraRef.assets.image === 'function'
928
+ ? auraRef.assets.image
929
+ : function (name) { return { kind: 'image', name: String(name || '') }; };
930
+ auraRef.assets.sound = typeof auraRef.assets.sound === 'function'
931
+ ? auraRef.assets.sound
932
+ : function (name) { return { kind: 'sound', name: String(name || '') }; };
933
+ auraRef.assets.text = typeof auraRef.assets.text === 'function'
934
+ ? auraRef.assets.text
935
+ : function () { return ''; };
936
+ auraRef.assets.json = typeof auraRef.assets.json === 'function'
937
+ ? auraRef.assets.json
938
+ : function () { return {}; };
939
+ auraRef.assets.bytes = typeof auraRef.assets.bytes === 'function'
940
+ ? auraRef.assets.bytes
941
+ : function () { return new Uint8Array(); };
942
+
943
+ auraRef.draw2d = auraRef.draw2d && typeof auraRef.draw2d === 'object' ? auraRef.draw2d : {};
944
+ auraRef.draw2d.clear = function (colorOrR, g, b, a) {
945
+ const ctx = ensureCanvasContext();
946
+ if (!ctx) return;
947
+ const fillColor = arguments.length > 1
948
+ ? createUnitColor(colorOrR, g, b, a == null ? 1 : a)
949
+ : normalizeColor(colorOrR, createUnitColor(0, 0, 0, 1));
950
+ resetDrawState();
951
+ ctx.clearRect(0, 0, runtime.width, runtime.height);
952
+ ctx.fillStyle = colorToCss(fillColor, createUnitColor(0, 0, 0, 1));
953
+ ctx.fillRect(0, 0, runtime.width, runtime.height);
954
+ };
955
+ auraRef.draw2d.rect = function (x, y, w, h, color) {
956
+ const ctx = ensureCanvasContext();
957
+ if (!ctx) return;
958
+ ctx.strokeStyle = colorToCss(color, defaultColor);
959
+ ctx.lineWidth = 1;
960
+ ctx.strokeRect(Number(x) || 0, Number(y) || 0, Number(w) || 0, Number(h) || 0);
961
+ };
962
+ auraRef.draw2d.rectOutline = auraRef.draw2d.rect;
963
+ auraRef.draw2d.rectFill = function (x, y, w, h, color) {
964
+ const ctx = ensureCanvasContext();
965
+ if (!ctx) return;
966
+ ctx.fillStyle = colorToCss(color, defaultColor);
967
+ ctx.fillRect(Number(x) || 0, Number(y) || 0, Number(w) || 0, Number(h) || 0);
968
+ };
969
+ auraRef.draw2d.circle = function (x, y, radius, color) {
970
+ const ctx = ensureCanvasContext();
971
+ if (!ctx) return;
972
+ ctx.beginPath();
973
+ ctx.arc(Number(x) || 0, Number(y) || 0, Math.max(0, Number(radius) || 0), 0, Math.PI * 2);
974
+ ctx.strokeStyle = colorToCss(color, defaultColor);
975
+ ctx.lineWidth = 1;
976
+ ctx.stroke();
977
+ };
978
+ auraRef.draw2d.circleFill = function (x, y, radius, color) {
979
+ const ctx = ensureCanvasContext();
980
+ if (!ctx) return;
981
+ ctx.beginPath();
982
+ ctx.arc(Number(x) || 0, Number(y) || 0, Math.max(0, Number(radius) || 0), 0, Math.PI * 2);
983
+ ctx.fillStyle = colorToCss(color, defaultColor);
984
+ ctx.fill();
985
+ };
986
+ auraRef.draw2d.line = function (x1, y1, x2, y2, color, width) {
987
+ const ctx = ensureCanvasContext();
988
+ if (!ctx) return;
989
+ ctx.beginPath();
990
+ ctx.moveTo(Number(x1) || 0, Number(y1) || 0);
991
+ ctx.lineTo(Number(x2) || 0, Number(y2) || 0);
992
+ ctx.strokeStyle = colorToCss(color, defaultColor);
993
+ ctx.lineWidth = normalizePositiveNumber(width, 1);
994
+ ctx.stroke();
995
+ };
996
+ auraRef.draw2d.text = function (text, x, y, options) {
997
+ const ctx = ensureCanvasContext();
998
+ if (!ctx) return;
999
+ const config = applyFont(options);
1000
+ const source = options && typeof options === 'object' ? options : {};
1001
+ ctx.fillStyle = colorToCss(source.color, defaultColor);
1002
+ ctx.textAlign = config.align;
1003
+ ctx.textBaseline = 'top';
1004
+ ctx.fillText(String(text == null ? '' : text), Number(x) || 0, Number(y) || 0);
1005
+ };
1006
+ auraRef.draw2d.measureText = function (text, options) {
1007
+ const source = options && typeof options === 'object' ? options : {};
1008
+ const size = normalizePositiveNumber(source.size, 16);
1009
+ const ctx = ensureCanvasContext();
1010
+ if (!ctx) {
1011
+ const fallbackWidth = String(text == null ? '' : text).length * size * 0.6;
1012
+ return { width: Number(fallbackWidth.toFixed(3)), height: size };
1013
+ }
1014
+ applyFont(options);
1015
+ const metrics = ctx.measureText(String(text == null ? '' : text));
1016
+ const measuredWidth = Number.isFinite(metrics && metrics.width)
1017
+ ? metrics.width
1018
+ : String(text == null ? '' : text).length * size * 0.6;
1019
+ return { width: Number(measuredWidth.toFixed(3)), height: size };
1020
+ };
1021
+ auraRef.draw2d.image = typeof auraRef.draw2d.image === 'function'
1022
+ ? auraRef.draw2d.image
1023
+ : function () { return true; };
1024
+ auraRef.draw2d.sprite = typeof auraRef.draw2d.sprite === 'function'
1025
+ ? auraRef.draw2d.sprite
1026
+ : function () { return true; };
1027
+ auraRef.draw2d.pushTransform = function () {
1028
+ const ctx = ensureCanvasContext();
1029
+ if (!ctx || typeof ctx.save !== 'function') return;
1030
+ runtime.transformDepth += 1;
1031
+ ctx.save();
1032
+ };
1033
+ auraRef.draw2d.popTransform = function () {
1034
+ const ctx = ensureCanvasContext();
1035
+ if (!ctx || typeof ctx.restore !== 'function' || runtime.transformDepth <= 0) return;
1036
+ runtime.transformDepth -= 1;
1037
+ ctx.restore();
1038
+ };
1039
+ auraRef.draw2d.push = auraRef.draw2d.pushTransform;
1040
+ auraRef.draw2d.pop = auraRef.draw2d.popTransform;
1041
+ auraRef.draw2d.translate = function (x, y) {
1042
+ const ctx = ensureCanvasContext();
1043
+ if (!ctx || typeof ctx.translate !== 'function') return;
1044
+ ctx.translate(Number(x) || 0, Number(y) || 0);
1045
+ };
1046
+ auraRef.draw2d.rotate = function (angle) {
1047
+ const ctx = ensureCanvasContext();
1048
+ if (!ctx || typeof ctx.rotate !== 'function') return;
1049
+ ctx.rotate(Number(angle) || 0);
1050
+ };
1051
+ auraRef.draw2d.scale = function (x, y) {
1052
+ const ctx = ensureCanvasContext();
1053
+ if (!ctx || typeof ctx.scale !== 'function') return;
1054
+ const scaleX = normalizePositiveNumber(x, 1);
1055
+ const scaleY = normalizePositiveNumber(y, scaleX);
1056
+ ctx.scale(scaleX, scaleY);
1057
+ };
1058
+
1059
+ return {
1060
+ aura: auraRef,
1061
+ setRuntimeConfig(nextRuntimeConfig) {
1062
+ currentRuntimeConfig = nextRuntimeConfig && typeof nextRuntimeConfig === 'object' ? nextRuntimeConfig : {};
1063
+ currentCanvasConfig = currentRuntimeConfig.canvas && typeof currentRuntimeConfig.canvas === 'object'
1064
+ ? currentRuntimeConfig.canvas
1065
+ : {};
1066
+ runtime.configuredWidth = normalizeCanvasSize(currentCanvasConfig.width, runtime.configuredWidth);
1067
+ runtime.configuredHeight = normalizeCanvasSize(currentCanvasConfig.height, runtime.configuredHeight);
1068
+ runtime.resizeMode = currentCanvasConfig.resizeMode === 'fixed' ? 'fixed' : 'fit-container';
1069
+ syncCanvasSize(false);
1070
+ },
1071
+ mount(canvas, mountTarget) {
1072
+ runtime.canvas = canvas || runtime.canvas;
1073
+ runtime.mountTarget = mountTarget || runtime.mountTarget;
1074
+ ensureCanvasContext();
1075
+ syncCanvasSize(false);
1076
+ resetDrawState();
1077
+ attachListeners();
1078
+ inputState.down.clear();
1079
+ inputState.pendingPressed.clear();
1080
+ inputState.pendingReleased.clear();
1081
+ inputState.framePressed.clear();
1082
+ inputState.frameReleased.clear();
1083
+ if (runtime.canvas && typeof runtime.canvas.focus === 'function') {
1084
+ runtime.canvas.focus();
1085
+ }
1086
+ return true;
1087
+ },
1088
+ beginFrame() {
1089
+ inputState.framePressed = new Set(inputState.pendingPressed);
1090
+ inputState.frameReleased = new Set(inputState.pendingReleased);
1091
+ inputState.pendingPressed.clear();
1092
+ inputState.pendingReleased.clear();
1093
+ resetDrawState();
1094
+ },
1095
+ endFrame() {
1096
+ const ctx = ensureCanvasContext();
1097
+ if (ctx && typeof ctx.restore === 'function') {
1098
+ while (runtime.transformDepth > 0) {
1099
+ runtime.transformDepth -= 1;
1100
+ ctx.restore();
1101
+ }
1102
+ }
1103
+ },
1104
+ unmount() {
1105
+ detachListeners();
1106
+ inputState.down.clear();
1107
+ inputState.pendingPressed.clear();
1108
+ inputState.pendingReleased.clear();
1109
+ inputState.framePressed.clear();
1110
+ inputState.frameReleased.clear();
1111
+ runtime.mountTarget = null;
1112
+ runtime.canvas = null;
1113
+ runtime.context2d = null;
1114
+ runtime.transformDepth = 0;
1115
+ return true;
1116
+ }
1117
+ };
491
1118
  }
492
1119
 
493
1120
  function createDeterministicBootstrap() {
494
1121
  return async function bootstrap(input) {
495
- const auraRef = globalRef.aura && typeof globalRef.aura === 'object'
496
- ? globalRef.aura
497
- : (globalRef.aura = {});
1122
+ const auraRef = auraRuntime && auraRuntime.aura
1123
+ ? auraRuntime.aura
1124
+ : (globalRef.aura && typeof globalRef.aura === 'object'
1125
+ ? globalRef.aura
1126
+ : (globalRef.aura = {}));
498
1127
  const runtimeConfig = input && typeof input.runtimeConfig === 'object' && input.runtimeConfig
499
1128
  ? input.runtimeConfig
500
1129
  : {};
@@ -571,6 +1200,9 @@ const WEB_LOADER_SOURCE = `
571
1200
  runtimeState.lastDeltaSeconds = fixedDeltaSeconds;
572
1201
 
573
1202
  try {
1203
+ if (auraRuntime && typeof auraRuntime.beginFrame === 'function') {
1204
+ auraRuntime.beginFrame();
1205
+ }
574
1206
  if (typeof auraRef.update === 'function') {
575
1207
  auraRef.update(fixedDeltaSeconds);
576
1208
  runtimeState.updateCalls += 1;
@@ -579,6 +1211,9 @@ const WEB_LOADER_SOURCE = `
579
1211
  auraRef.draw();
580
1212
  runtimeState.drawCalls += 1;
581
1213
  }
1214
+ if (auraRuntime && typeof auraRuntime.endFrame === 'function') {
1215
+ auraRuntime.endFrame();
1216
+ }
582
1217
  } catch (error) {
583
1218
  runtimeState.running = false;
584
1219
  syncLifecycle();
@@ -616,6 +1251,9 @@ const WEB_LOADER_SOURCE = `
616
1251
  cancelFrame(runtimeState.rafHandle);
617
1252
  runtimeState.rafHandle = null;
618
1253
  }
1254
+ if (auraRuntime && typeof auraRuntime.unmount === 'function') {
1255
+ auraRuntime.unmount();
1256
+ }
619
1257
  syncLifecycle();
620
1258
  return true;
621
1259
  }
@@ -662,6 +1300,12 @@ const WEB_LOADER_SOURCE = `
662
1300
  ensureManifestValid(cachedManifest);
663
1301
  ensureRuntimeConfigValid(cachedRuntimeConfig);
664
1302
 
1303
+ if (!auraRuntime) {
1304
+ auraRuntime = createBrowserAuraSurface(cachedRuntimeConfig);
1305
+ } else if (typeof auraRuntime.setRuntimeConfig === 'function') {
1306
+ auraRuntime.setRuntimeConfig(cachedRuntimeConfig);
1307
+ }
1308
+
665
1309
  const bundleEntry = normalizePath(cachedManifest.entrypoints.bundle);
666
1310
  if (bundleEntry.length === 0) {
667
1311
  throw createError('web_entrypoint_missing', 'Bundle entrypoint is empty.', 'loader', false, {});
@@ -703,6 +1347,13 @@ const WEB_LOADER_SOURCE = `
703
1347
  throw createError('web_loader_mount_failed', 'Canvas target was not found.', 'loader', true, {});
704
1348
  }
705
1349
 
1350
+ if (!auraRuntime) {
1351
+ auraRuntime = createBrowserAuraSurface(cachedRuntimeConfig || {});
1352
+ }
1353
+ if (typeof auraRuntime.mount === 'function') {
1354
+ auraRuntime.mount(canvas, targetNode);
1355
+ }
1356
+
706
1357
  const bootstrap = resolveBootstrap();
707
1358
  if (typeof bootstrap !== 'function') {
708
1359
  throw createError('web_runtime_bootstrap_missing', 'Bundle did not expose required bootstrap function.', 'runtime', false, {});