@auraindustry/aurajs 0.0.5 → 0.0.6

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.
@@ -14,6 +14,8 @@ import { extname, relative, resolve, sep } from 'node:path';
14
14
  export const BUILD_MANIFEST_SCHEMA = 'aurajs.build-manifest.v1';
15
15
  export const WEB_BUILD_MANIFEST_SCHEMA = 'aurajs.web-build-manifest.v1';
16
16
  export const WEB_RUNTIME_CONFIG_SCHEMA = 'aurajs.web-runtime-config.v1';
17
+ export const WEB_CAPABILITY_DECLARATION_SCHEMA = 'aurajs.web-capability-declaration.v1';
18
+ const PROJECT_CAPABILITY_DECLARATION_FILE = 'aura.capabilities.json';
17
19
 
18
20
  export function writeBuildManifest(options = {}) {
19
21
  const outRoot = resolve(options.outRoot || process.cwd());
@@ -76,9 +78,14 @@ export function writeWebBuildArtifacts(options = {}) {
76
78
  outRoot,
77
79
  });
78
80
 
81
+ const capabilityDeclaration = readProjectCapabilityDeclaration({
82
+ projectRoot,
83
+ modules: options.modules || {},
84
+ });
79
85
  const runtimeConfig = buildRuntimeConfig({
80
86
  windowConfig: options.windowConfig || {},
81
87
  modules: options.modules || {},
88
+ capabilityDeclaration,
82
89
  });
83
90
  const runtimeConfigPath = resolve(outRoot, 'runtime-config.json');
84
91
  writeCanonicalJson(runtimeConfigPath, runtimeConfig);
@@ -156,6 +163,13 @@ function buildRuntimeConfig(options = {}) {
156
163
  const modules = options.modules && typeof options.modules === 'object'
157
164
  ? options.modules
158
165
  : {};
166
+ const capabilityDeclaration = normalizeWebCapabilityDeclaration(
167
+ options.capabilityDeclaration,
168
+ normalizeOptionalModules(modules),
169
+ options.capabilityDeclaration && typeof options.capabilityDeclaration === 'object'
170
+ ? options.capabilityDeclaration.source || null
171
+ : null,
172
+ );
159
173
 
160
174
  return {
161
175
  schema: WEB_RUNTIME_CONFIG_SCHEMA,
@@ -176,9 +190,79 @@ function buildRuntimeConfig(options = {}) {
176
190
  network: modules.network === true,
177
191
  multiplayer: modules.multiplayer === true,
178
192
  },
193
+ capabilities: capabilityDeclaration,
194
+ };
195
+ }
196
+
197
+ function normalizeRequiredApis(entries) {
198
+ if (!Array.isArray(entries)) {
199
+ return [];
200
+ }
201
+ return [...new Set(entries
202
+ .filter((entry) => typeof entry === 'string')
203
+ .map((entry) => entry.trim())
204
+ .filter(Boolean))]
205
+ .sort((a, b) => a.localeCompare(b));
206
+ }
207
+
208
+ function normalizeOptionalModules(modules) {
209
+ const source = modules && typeof modules === 'object' ? modules : {};
210
+ return {
211
+ physics: source.physics === true,
212
+ network: source.network === true || source.net === true,
213
+ multiplayer: source.multiplayer === true,
214
+ steam: source.steam === true,
215
+ };
216
+ }
217
+
218
+ function normalizeWebCapabilityDeclaration(value, baseModules = {}, source = null) {
219
+ const declaration = value && typeof value === 'object' && !Array.isArray(value)
220
+ ? value
221
+ : {};
222
+ if (
223
+ declaration.schema != null
224
+ && declaration.schema !== 'aurajs.create-capabilities.v1'
225
+ && declaration.schema !== 'aurajs.capability-declaration.v1'
226
+ && declaration.schema !== WEB_CAPABILITY_DECLARATION_SCHEMA
227
+ ) {
228
+ throw new Error(
229
+ `${source || 'Capability declaration'} must use `
230
+ + '`aurajs.create-capabilities.v1`, `aurajs.capability-declaration.v1`, '
231
+ + `or \`${WEB_CAPABILITY_DECLARATION_SCHEMA}\`.`,
232
+ );
233
+ }
234
+
235
+ const declaredModules = normalizeOptionalModules(declaration.optionalModules);
236
+ return {
237
+ schema: WEB_CAPABILITY_DECLARATION_SCHEMA,
238
+ source,
239
+ requiredApis: normalizeRequiredApis(declaration.requiredApis),
240
+ optionalModules: {
241
+ physics: baseModules.physics || declaredModules.physics,
242
+ network: baseModules.network || declaredModules.network,
243
+ multiplayer: baseModules.multiplayer || declaredModules.multiplayer,
244
+ steam: baseModules.steam || declaredModules.steam,
245
+ },
179
246
  };
180
247
  }
181
248
 
249
+ function readProjectCapabilityDeclaration({ projectRoot, modules }) {
250
+ const declarationPath = resolve(projectRoot, PROJECT_CAPABILITY_DECLARATION_FILE);
251
+ const baseModules = normalizeOptionalModules(modules);
252
+ if (!existsSync(declarationPath)) {
253
+ return normalizeWebCapabilityDeclaration({}, baseModules, null);
254
+ }
255
+
256
+ let parsed;
257
+ try {
258
+ parsed = JSON.parse(readFileSync(declarationPath, 'utf8'));
259
+ } catch (error) {
260
+ throw new Error(`Failed to parse ${PROJECT_CAPABILITY_DECLARATION_FILE}: ${error.message}`);
261
+ }
262
+
263
+ return normalizeWebCapabilityDeclaration(parsed, baseModules, PROJECT_CAPABILITY_DECLARATION_FILE);
264
+ }
265
+
182
266
  function emitWebAssets({ projectRoot, outRoot }) {
183
267
  const sourceRoot = resolve(projectRoot, 'assets');
184
268
  const assetsOutRoot = resolve(outRoot, 'assets');
@@ -203,6 +287,7 @@ function emitWebAssets({ projectRoot, outRoot }) {
203
287
  copyFileSync(sourcePath, outputPath);
204
288
  entries.push({
205
289
  path: `assets/${outputName}`,
290
+ sourcePath: sourceRelative,
206
291
  bytes: sourceBytes.length,
207
292
  sha256: contentHash,
208
293
  mediaType: mediaTypeForExtension(extension),
@@ -349,6 +434,7 @@ const WEB_LOADER_SOURCE = `
349
434
  };
350
435
  let cachedManifest = null;
351
436
  let cachedRuntimeConfig = null;
437
+ let cachedRootUrl = '.';
352
438
  let mountedRuntime = null;
353
439
  let auraRuntime = null;
354
440
 
@@ -392,6 +478,33 @@ const WEB_LOADER_SOURCE = `
392
478
  return error;
393
479
  }
394
480
 
481
+ function createUnsupportedRuntimeError(namespaceName, methodName, reasonCode, details) {
482
+ const normalizedDetails = details && typeof details === 'object' ? details : {};
483
+ return createError(
484
+ reasonCode,
485
+ 'aura.' + namespaceName + '.' + methodName + '() is not supported in browser runtime. [reason:' + reasonCode + ']',
486
+ 'runtime',
487
+ false,
488
+ Object.assign(
489
+ {
490
+ runtime: 'web',
491
+ namespace: namespaceName,
492
+ method: methodName
493
+ },
494
+ normalizedDetails
495
+ )
496
+ );
497
+ }
498
+
499
+ function createUnsupportedStateResult(methodName, reasonCode) {
500
+ return {
501
+ ok: false,
502
+ reasonCode: reasonCode,
503
+ detail: 'aura.state.' + methodName + '() is not supported in browser runtime. [reason:' + reasonCode + ']',
504
+ runtime: 'web'
505
+ };
506
+ }
507
+
395
508
  function normalizeError(error, fallbackReasonCode, fallbackMessage, fallbackLayer, fallbackRetryable) {
396
509
  if (error && typeof error === 'object' && typeof error.reasonCode === 'string') {
397
510
  return error;
@@ -525,6 +638,45 @@ const WEB_LOADER_SOURCE = `
525
638
  return numeric;
526
639
  }
527
640
 
641
+ function toFinite(value, fallback) {
642
+ const numeric = Number(value);
643
+ return Number.isFinite(numeric) ? numeric : fallback;
644
+ }
645
+
646
+ function toPositive(value, fallback) {
647
+ const numeric = Number(value);
648
+ return Number.isFinite(numeric) && numeric > 0 ? numeric : fallback;
649
+ }
650
+
651
+ function clamp01(value) {
652
+ const numeric = Number(value);
653
+ if (!Number.isFinite(numeric)) return 1;
654
+ if (numeric <= 0) return 0;
655
+ if (numeric >= 1) return 1;
656
+ return numeric;
657
+ }
658
+
659
+ function isObject(value) {
660
+ return value != null && typeof value === 'object';
661
+ }
662
+
663
+ function normalizeMouseButton(value) {
664
+ const numeric = Number(value);
665
+ if (!Number.isFinite(numeric) || numeric < 0) return 0;
666
+ return Math.floor(numeric);
667
+ }
668
+
669
+ function sanitizeAssetSourcePath(value) {
670
+ return String(value || '')
671
+ .replace(/\\\\/g, '/')
672
+ .replace(/^\\.\\//, '')
673
+ .replace(/^\\/+/, '');
674
+ }
675
+
676
+ function isImageMediaType(value) {
677
+ return typeof value === 'string' && value.startsWith('image/');
678
+ }
679
+
528
680
  function normalizeTextAlign(value) {
529
681
  if (value === 'center' || value === 'right' || value === 'left') {
530
682
  return value;
@@ -614,6 +766,122 @@ const WEB_LOADER_SOURCE = `
614
766
  }
615
767
  }
616
768
 
769
+ function normalizeCapabilityDeclaration(value) {
770
+ const declaration = value && typeof value === 'object' ? value : {};
771
+ const requiredApis = Array.isArray(declaration.requiredApis)
772
+ ? Array.from(new Set(declaration.requiredApis
773
+ .filter(function (entry) { return typeof entry === 'string'; })
774
+ .map(function (entry) { return entry.trim(); })
775
+ .filter(Boolean)))
776
+ .sort(function (a, b) { return a.localeCompare(b); })
777
+ : [];
778
+ const declaredModules = declaration.optionalModules && typeof declaration.optionalModules === 'object'
779
+ ? declaration.optionalModules
780
+ : {};
781
+ return {
782
+ schema: typeof declaration.schema === 'string'
783
+ ? declaration.schema
784
+ : 'aurajs.web-capability-declaration.v1',
785
+ source: typeof declaration.source === 'string' ? declaration.source : null,
786
+ requiredApis: requiredApis,
787
+ optionalModules: {
788
+ physics: declaredModules.physics === true,
789
+ network: declaredModules.network === true,
790
+ multiplayer: declaredModules.multiplayer === true,
791
+ steam: declaredModules.steam === true
792
+ }
793
+ };
794
+ }
795
+
796
+ function resolveDeclaredApi(root, apiPath) {
797
+ if (typeof apiPath !== 'string' || apiPath.length === 0) {
798
+ return { found: false, value: undefined };
799
+ }
800
+ const segments = apiPath.split('.').filter(Boolean);
801
+ let current = root;
802
+ for (const segment of segments) {
803
+ if (
804
+ current == null
805
+ || (typeof current !== 'object' && typeof current !== 'function')
806
+ || !(segment in current)
807
+ ) {
808
+ return { found: false, value: undefined };
809
+ }
810
+ current = current[segment];
811
+ }
812
+ return { found: current != null, value: current };
813
+ }
814
+
815
+ function collectCapabilityFailures(runtimeConfig, auraRef) {
816
+ const declaration = normalizeCapabilityDeclaration(runtimeConfig && runtimeConfig.capabilities);
817
+ const failures = [];
818
+ const root = { aura: auraRef && typeof auraRef === 'object' ? auraRef : {} };
819
+
820
+ for (const moduleName of ['physics', 'network', 'multiplayer', 'steam']) {
821
+ if (declaration.optionalModules[moduleName] === true) {
822
+ failures.push({
823
+ kind: 'optionalModule',
824
+ module: moduleName,
825
+ reasonCode: 'web_optional_module_unsupported'
826
+ });
827
+ }
828
+ }
829
+
830
+ for (const apiPath of declaration.requiredApis) {
831
+ const resolved = resolveDeclaredApi(root, apiPath);
832
+ if (!resolved.found) {
833
+ failures.push({
834
+ kind: 'requiredApi',
835
+ apiPath: apiPath,
836
+ reasonCode: 'web_required_api_missing'
837
+ });
838
+ }
839
+ }
840
+
841
+ return {
842
+ declaration: declaration,
843
+ failures: failures
844
+ };
845
+ }
846
+
847
+ function ensureCapabilityDeclarationSatisfied(runtimeConfig, auraRef) {
848
+ const report = collectCapabilityFailures(runtimeConfig, auraRef);
849
+ if (report.failures.length === 0) {
850
+ return;
851
+ }
852
+
853
+ const missingRequiredApis = [];
854
+ const unsupportedOptionalModules = [];
855
+ for (const failure of report.failures) {
856
+ if (failure.kind === 'requiredApi') {
857
+ missingRequiredApis.push(failure.apiPath);
858
+ } else if (failure.kind === 'optionalModule') {
859
+ unsupportedOptionalModules.push(failure.module);
860
+ }
861
+ }
862
+
863
+ let reasonCode = 'web_capability_gate_failed';
864
+ if (missingRequiredApis.length > 0 && unsupportedOptionalModules.length === 0) {
865
+ reasonCode = 'web_required_api_missing';
866
+ } else if (unsupportedOptionalModules.length > 0 && missingRequiredApis.length === 0) {
867
+ reasonCode = 'web_optional_module_unsupported';
868
+ }
869
+
870
+ throw createError(
871
+ reasonCode,
872
+ 'Browser runtime does not satisfy the declared capability contract.',
873
+ 'loader',
874
+ false,
875
+ {
876
+ schema: report.declaration.schema,
877
+ source: report.declaration.source,
878
+ missingRequiredApis: missingRequiredApis,
879
+ unsupportedOptionalModules: unsupportedOptionalModules,
880
+ failures: report.failures
881
+ }
882
+ );
883
+ }
884
+
617
885
  function loadScript(path) {
618
886
  return new Promise((resolvePromise, rejectPromise) => {
619
887
  const script = document.createElement('script');
@@ -633,6 +901,8 @@ const WEB_LOADER_SOURCE = `
633
901
  let currentCanvasConfig = currentRuntimeConfig.canvas && typeof currentRuntimeConfig.canvas === 'object'
634
902
  ? currentRuntimeConfig.canvas
635
903
  : {};
904
+ let currentManifest = { assets: [] };
905
+ let currentRootUrl = '.';
636
906
 
637
907
  const auraRef = globalRef.aura && typeof globalRef.aura === 'object'
638
908
  ? globalRef.aura
@@ -644,7 +914,23 @@ const WEB_LOADER_SOURCE = `
644
914
  pendingPressed: new Set(),
645
915
  pendingReleased: new Set(),
646
916
  framePressed: new Set(),
647
- frameReleased: new Set()
917
+ frameReleased: new Set(),
918
+ mouseDown: new Set(),
919
+ pendingMousePressed: new Set(),
920
+ pendingMouseReleased: new Set(),
921
+ frameMousePressed: new Set(),
922
+ frameMouseReleased: new Set(),
923
+ pendingMouseDeltaX: 0,
924
+ pendingMouseDeltaY: 0,
925
+ frameMouseDeltaX: 0,
926
+ frameMouseDeltaY: 0
927
+ };
928
+
929
+ const assetState = {
930
+ bySourcePath: new Map(),
931
+ byOutputPath: new Map(),
932
+ loaded: new Map(),
933
+ storageFallback: new Map()
648
934
  };
649
935
 
650
936
  const runtime = {
@@ -658,11 +944,19 @@ const WEB_LOADER_SOURCE = `
658
944
  width: normalizeCanvasSize(currentCanvasConfig.width, 1280),
659
945
  height: normalizeCanvasSize(currentCanvasConfig.height, 720),
660
946
  transformDepth: 0,
947
+ worldTransformActive: false,
948
+ cursorVisible: true,
949
+ cursorLocked: false,
661
950
  listenersAttached: false,
662
951
  keydownListener: null,
663
952
  keyupListener: null,
953
+ focusListener: null,
664
954
  blurListener: null,
665
- resizeListener: null
955
+ resizeListener: null,
956
+ mousemoveListener: null,
957
+ mousedownListener: null,
958
+ mouseupListener: null,
959
+ wheelListener: null
666
960
  };
667
961
 
668
962
  function ensureStyleObject(node) {
@@ -681,6 +975,292 @@ const WEB_LOADER_SOURCE = `
681
975
  return runtime.context2d;
682
976
  }
683
977
 
978
+ function normalizeAssetKey(value) {
979
+ return sanitizeAssetSourcePath(value);
980
+ }
981
+
982
+ function indexManifestAssets(manifest, rootUrl) {
983
+ currentManifest = manifest && typeof manifest === 'object' ? manifest : { assets: [] };
984
+ currentRootUrl = typeof rootUrl === 'string' && rootUrl.length > 0
985
+ ? rootUrl.replace(/\\/$/, '')
986
+ : '.';
987
+ assetState.bySourcePath.clear();
988
+ assetState.byOutputPath.clear();
989
+ const entries = Array.isArray(currentManifest.assets) ? currentManifest.assets : [];
990
+ for (const entry of entries) {
991
+ if (!entry || typeof entry !== 'object') continue;
992
+ const outputPath = normalizePath(entry.path);
993
+ if (outputPath.length > 0) {
994
+ assetState.byOutputPath.set(outputPath, entry);
995
+ }
996
+ const sourcePath = normalizeAssetKey(entry.sourcePath || outputPath);
997
+ if (sourcePath.length > 0) {
998
+ assetState.bySourcePath.set(sourcePath, entry);
999
+ }
1000
+ }
1001
+ }
1002
+
1003
+ function buildAssetUrl(entryPath) {
1004
+ const normalized = normalizePath(entryPath);
1005
+ const root = currentRootUrl && currentRootUrl.length > 0 ? currentRootUrl : '.';
1006
+ return root + '/' + normalized;
1007
+ }
1008
+
1009
+ function resolveAssetEntry(source) {
1010
+ const normalizedSource = normalizeAssetKey(source);
1011
+ if (normalizedSource.length === 0) return null;
1012
+ if (assetState.bySourcePath.has(normalizedSource)) {
1013
+ return assetState.bySourcePath.get(normalizedSource);
1014
+ }
1015
+ const outputPath = normalizePath(normalizedSource);
1016
+ if (assetState.byOutputPath.has(outputPath)) {
1017
+ return assetState.byOutputPath.get(outputPath);
1018
+ }
1019
+ return null;
1020
+ }
1021
+
1022
+ function resolveAssetSourcePath(source) {
1023
+ if (typeof source === 'string') {
1024
+ return normalizeAssetKey(source);
1025
+ }
1026
+ if (source && typeof source === 'object') {
1027
+ if (typeof source.sourcePath === 'string') return normalizeAssetKey(source.sourcePath);
1028
+ if (typeof source.path === 'string') return normalizeAssetKey(source.path);
1029
+ if (typeof source.name === 'string') return normalizeAssetKey(source.name);
1030
+ }
1031
+ return '';
1032
+ }
1033
+
1034
+ function resolveLoadedAsset(source) {
1035
+ const key = resolveAssetSourcePath(source);
1036
+ if (key.length === 0) return null;
1037
+ return assetState.loaded.get(key) || null;
1038
+ }
1039
+
1040
+ function rememberLoadedAsset(sourcePath, handle) {
1041
+ const key = normalizeAssetKey(sourcePath);
1042
+ if (key.length === 0 || !handle || typeof handle !== 'object') return handle;
1043
+ handle.sourcePath = key;
1044
+ if (typeof handle.path !== 'string' || handle.path.length === 0) {
1045
+ handle.path = key;
1046
+ }
1047
+ assetState.loaded.set(key, handle);
1048
+ return handle;
1049
+ }
1050
+
1051
+ async function fetchAssetResponse(entry) {
1052
+ if (!entry || typeof entry !== 'object') return null;
1053
+ const assetUrl = buildAssetUrl(entry.path);
1054
+ let response;
1055
+ try {
1056
+ response = await fetch(assetUrl, { cache: 'force-cache' });
1057
+ } catch (_) {
1058
+ return null;
1059
+ }
1060
+ if (!response || response.ok !== true) {
1061
+ return null;
1062
+ }
1063
+ return response;
1064
+ }
1065
+
1066
+ async function readAssetBytes(entry) {
1067
+ const response = await fetchAssetResponse(entry);
1068
+ if (!response || typeof response.arrayBuffer !== 'function') {
1069
+ return null;
1070
+ }
1071
+ try {
1072
+ return new Uint8Array(await response.arrayBuffer());
1073
+ } catch (_) {
1074
+ return null;
1075
+ }
1076
+ }
1077
+
1078
+ async function readAssetText(entry) {
1079
+ const response = await fetchAssetResponse(entry);
1080
+ if (!response || typeof response.text !== 'function') {
1081
+ return null;
1082
+ }
1083
+ try {
1084
+ return await response.text();
1085
+ } catch (_) {
1086
+ return null;
1087
+ }
1088
+ }
1089
+
1090
+ async function loadImageHandle(sourcePath, entry) {
1091
+ const existing = resolveLoadedAsset(sourcePath);
1092
+ if (existing && existing.image) {
1093
+ return existing;
1094
+ }
1095
+ const ImageCtor = typeof globalRef.Image === 'function' ? globalRef.Image : null;
1096
+ const handle = existing || {
1097
+ kind: 'image',
1098
+ path: sourcePath,
1099
+ sourcePath: sourcePath,
1100
+ resolvedPath: entry && typeof entry.path === 'string' ? normalizePath(entry.path) : sourcePath,
1101
+ mediaType: entry && typeof entry.mediaType === 'string' ? entry.mediaType : 'image/png',
1102
+ image: null,
1103
+ width: 0,
1104
+ height: 0
1105
+ };
1106
+ if (!ImageCtor || !entry) {
1107
+ return rememberLoadedAsset(sourcePath, handle);
1108
+ }
1109
+ try {
1110
+ const image = await new Promise(function (resolvePromise, rejectPromise) {
1111
+ const node = new ImageCtor();
1112
+ node.onload = function () { resolvePromise(node); };
1113
+ node.onerror = function () { rejectPromise(new Error('image_load_failed')); };
1114
+ node.src = buildAssetUrl(entry.path);
1115
+ });
1116
+ handle.image = image;
1117
+ handle.width = normalizeCanvasSize(image.naturalWidth || image.width, 1);
1118
+ handle.height = normalizeCanvasSize(image.naturalHeight || image.height, 1);
1119
+ } catch (_) {}
1120
+ return rememberLoadedAsset(sourcePath, handle);
1121
+ }
1122
+
1123
+ function createBrowserSoundHandle(sourcePath, entry, bytes) {
1124
+ return rememberLoadedAsset(sourcePath, {
1125
+ kind: 'sound',
1126
+ path: sourcePath,
1127
+ sourcePath: sourcePath,
1128
+ resolvedPath: entry && typeof entry.path === 'string' ? normalizePath(entry.path) : sourcePath,
1129
+ mediaType: entry && typeof entry.mediaType === 'string' ? entry.mediaType : 'audio/ogg',
1130
+ bytes: bytes instanceof Uint8Array ? bytes : new Uint8Array(),
1131
+ playbackSupported: false,
1132
+ reasonCode: 'web_audio_playback_unsupported',
1133
+ detail: 'Browser runtime resolves sound assets but does not support aura.audio playback. [reason:web_audio_playback_unsupported]'
1134
+ });
1135
+ }
1136
+
1137
+ async function loadAssetRecord(source) {
1138
+ if (Array.isArray(source)) {
1139
+ const loaded = [];
1140
+ for (const entry of source) {
1141
+ const next = await loadAssetRecord(entry);
1142
+ if (next) loaded.push(next);
1143
+ }
1144
+ return loaded;
1145
+ }
1146
+ const sourcePath = resolveAssetSourcePath(source);
1147
+ if (sourcePath.length === 0) return null;
1148
+ const existing = resolveLoadedAsset(sourcePath);
1149
+ if (existing) return existing;
1150
+ const entry = resolveAssetEntry(sourcePath);
1151
+ if (!entry) return null;
1152
+ if (isImageMediaType(entry.mediaType)) {
1153
+ return await loadImageHandle(sourcePath, entry);
1154
+ }
1155
+ const bytes = await readAssetBytes(entry);
1156
+ if (entry.mediaType && entry.mediaType.startsWith('audio/')) {
1157
+ return createBrowserSoundHandle(sourcePath, entry, bytes);
1158
+ }
1159
+ if (!bytes) {
1160
+ return rememberLoadedAsset(sourcePath, {
1161
+ kind: 'asset',
1162
+ path: sourcePath,
1163
+ sourcePath: sourcePath,
1164
+ resolvedPath: normalizePath(entry.path),
1165
+ mediaType: entry.mediaType,
1166
+ bytes: new Uint8Array()
1167
+ });
1168
+ }
1169
+ const handle = {
1170
+ kind: 'asset',
1171
+ path: sourcePath,
1172
+ sourcePath: sourcePath,
1173
+ resolvedPath: normalizePath(entry.path),
1174
+ mediaType: entry.mediaType,
1175
+ bytes: bytes
1176
+ };
1177
+ if (entry.mediaType === 'application/json') {
1178
+ const text = typeof TextDecoder === 'function'
1179
+ ? new TextDecoder('utf-8').decode(bytes)
1180
+ : String.fromCharCode.apply(null, Array.from(bytes));
1181
+ handle.text = text;
1182
+ try {
1183
+ handle.json = JSON.parse(text);
1184
+ } catch (_) {
1185
+ handle.json = null;
1186
+ }
1187
+ } else if (typeof entry.mediaType === 'string' && entry.mediaType.startsWith('text/')) {
1188
+ handle.text = typeof TextDecoder === 'function'
1189
+ ? new TextDecoder('utf-8').decode(bytes)
1190
+ : String.fromCharCode.apply(null, Array.from(bytes));
1191
+ }
1192
+ return rememberLoadedAsset(sourcePath, handle);
1193
+ }
1194
+
1195
+ function normalizeMousePosition(event) {
1196
+ const source = event && typeof event === 'object' ? event : {};
1197
+ if (Number.isFinite(Number(source.offsetX)) && Number.isFinite(Number(source.offsetY))) {
1198
+ return {
1199
+ x: Number(source.offsetX),
1200
+ y: Number(source.offsetY)
1201
+ };
1202
+ }
1203
+ const canvas = runtime.canvas;
1204
+ const rect = canvas && typeof canvas.getBoundingClientRect === 'function'
1205
+ ? canvas.getBoundingClientRect()
1206
+ : null;
1207
+ const clientX = toFinite(source.clientX, auraRef.input.mouse.x);
1208
+ const clientY = toFinite(source.clientY, auraRef.input.mouse.y);
1209
+ if (rect) {
1210
+ return {
1211
+ x: clientX - toFinite(rect.left, 0),
1212
+ y: clientY - toFinite(rect.top, 0)
1213
+ };
1214
+ }
1215
+ return {
1216
+ x: clientX,
1217
+ y: clientY
1218
+ };
1219
+ }
1220
+
1221
+ function syncMousePosition(event) {
1222
+ const point = normalizeMousePosition(event);
1223
+ const movementX = Number(event && event.movementX);
1224
+ const movementY = Number(event && event.movementY);
1225
+ const deltaX = Number.isFinite(movementX) ? movementX : point.x - auraRef.input.mouse.x;
1226
+ const deltaY = Number.isFinite(movementY) ? movementY : point.y - auraRef.input.mouse.y;
1227
+ inputState.pendingMouseDeltaX += deltaX;
1228
+ inputState.pendingMouseDeltaY += deltaY;
1229
+ auraRef.input.mouse.x = point.x;
1230
+ auraRef.input.mouse.y = point.y;
1231
+ }
1232
+
1233
+ function clearMouseDeltaState() {
1234
+ inputState.pendingMouseDeltaX = 0;
1235
+ inputState.pendingMouseDeltaY = 0;
1236
+ inputState.frameMouseDeltaX = 0;
1237
+ inputState.frameMouseDeltaY = 0;
1238
+ }
1239
+
1240
+ function clearTransientInputState() {
1241
+ inputState.pendingPressed.clear();
1242
+ inputState.pendingReleased.clear();
1243
+ inputState.framePressed.clear();
1244
+ inputState.frameReleased.clear();
1245
+ inputState.pendingMousePressed.clear();
1246
+ inputState.pendingMouseReleased.clear();
1247
+ inputState.frameMousePressed.clear();
1248
+ inputState.frameMouseReleased.clear();
1249
+ clearMouseDeltaState();
1250
+ }
1251
+
1252
+ function clearHeldInputState() {
1253
+ inputState.down.clear();
1254
+ inputState.mouseDown.clear();
1255
+ clearTransientInputState();
1256
+ }
1257
+
1258
+ function applyCursorAppearance() {
1259
+ const style = ensureStyleObject(runtime.canvas);
1260
+ if (!style) return;
1261
+ style.cursor = (!runtime.cursorVisible || runtime.cursorLocked) ? 'none' : 'default';
1262
+ }
1263
+
684
1264
  function syncCanvasSize(notifyResize) {
685
1265
  let width = runtime.configuredWidth;
686
1266
  let height = runtime.configuredHeight;
@@ -736,11 +1316,54 @@ const WEB_LOADER_SOURCE = `
736
1316
  }
737
1317
  }
738
1318
  runtime.transformDepth = 0;
1319
+ runtime.worldTransformActive = false;
739
1320
  ctx.globalAlpha = 1;
740
1321
  ctx.textAlign = 'left';
741
1322
  ctx.textBaseline = 'top';
742
1323
  }
743
1324
 
1325
+ function applyWorldTransform() {
1326
+ const ctx = ensureCanvasContext();
1327
+ if (!ctx || runtime.worldTransformActive) return ctx;
1328
+ const camera = auraRef.camera && typeof auraRef.camera === 'object' ? auraRef.camera : null;
1329
+ if (camera) {
1330
+ const zoom = normalizePositiveNumber(camera.zoom, 1);
1331
+ const rotation = Number(camera.rotation) || 0;
1332
+ const x = Number(camera.x) || 0;
1333
+ const y = Number(camera.y) || 0;
1334
+ if (typeof ctx.scale === 'function' && zoom !== 1) {
1335
+ ctx.scale(zoom, zoom);
1336
+ }
1337
+ if (typeof ctx.rotate === 'function' && rotation !== 0) {
1338
+ ctx.rotate(rotation);
1339
+ }
1340
+ if (typeof ctx.translate === 'function' && (x !== 0 || y !== 0)) {
1341
+ ctx.translate(-x, -y);
1342
+ }
1343
+ }
1344
+ runtime.worldTransformActive = true;
1345
+ return ctx;
1346
+ }
1347
+
1348
+ function ensureWorldTransform() {
1349
+ const ctx = ensureCanvasContext();
1350
+ if (!ctx) return null;
1351
+ return runtime.worldTransformActive ? ctx : applyWorldTransform();
1352
+ }
1353
+
1354
+ function normalizeTextOptions(sizeOrOptions, colorMaybe) {
1355
+ if (sizeOrOptions && typeof sizeOrOptions === 'object' && !Array.isArray(sizeOrOptions)) {
1356
+ return sizeOrOptions;
1357
+ }
1358
+ if (Number.isFinite(Number(sizeOrOptions))) {
1359
+ return {
1360
+ size: Number(sizeOrOptions),
1361
+ color: colorMaybe
1362
+ };
1363
+ }
1364
+ return {};
1365
+ }
1366
+
744
1367
  function applyFont(options) {
745
1368
  const ctx = ensureCanvasContext();
746
1369
  const source = options && typeof options === 'object' ? options : {};
@@ -756,6 +1379,505 @@ const WEB_LOADER_SOURCE = `
756
1379
  return { size, align: normalizeTextAlign(source.align) };
757
1380
  }
758
1381
 
1382
+ function withScreenSpace(callback) {
1383
+ const ctx = ensureCanvasContext();
1384
+ if (!ctx || typeof callback !== 'function') return null;
1385
+ if (typeof ctx.save === 'function') {
1386
+ ctx.save();
1387
+ }
1388
+ if (typeof ctx.setTransform === 'function') {
1389
+ ctx.setTransform(runtime.pixelRatio, 0, 0, runtime.pixelRatio, 0, 0);
1390
+ } else {
1391
+ if (typeof ctx.resetTransform === 'function') {
1392
+ ctx.resetTransform();
1393
+ }
1394
+ if (typeof ctx.scale === 'function') {
1395
+ ctx.scale(runtime.pixelRatio, runtime.pixelRatio);
1396
+ }
1397
+ }
1398
+ try {
1399
+ return callback(ctx);
1400
+ } finally {
1401
+ if (typeof ctx.restore === 'function') {
1402
+ ctx.restore();
1403
+ }
1404
+ }
1405
+ }
1406
+
1407
+ function resolveStorageBackend() {
1408
+ return globalRef.localStorage && typeof globalRef.localStorage.getItem === 'function'
1409
+ ? globalRef.localStorage
1410
+ : null;
1411
+ }
1412
+
1413
+ function storageKey(key) {
1414
+ return 'aurajs:' + String(key || '');
1415
+ }
1416
+
1417
+ function readStorage(key) {
1418
+ const normalizedKey = String(key || '');
1419
+ if (normalizedKey.length === 0) return null;
1420
+ const backend = resolveStorageBackend();
1421
+ const namespaced = storageKey(normalizedKey);
1422
+ let raw = null;
1423
+ if (backend) {
1424
+ try {
1425
+ raw = backend.getItem(namespaced);
1426
+ } catch (_) {
1427
+ raw = null;
1428
+ }
1429
+ } else if (assetState.storageFallback.has(namespaced)) {
1430
+ raw = assetState.storageFallback.get(namespaced);
1431
+ }
1432
+ if (raw == null) return null;
1433
+ try {
1434
+ return JSON.parse(raw);
1435
+ } catch (_) {
1436
+ return raw;
1437
+ }
1438
+ }
1439
+
1440
+ function writeStorage(key, value) {
1441
+ const normalizedKey = String(key || '');
1442
+ if (normalizedKey.length === 0) return false;
1443
+ const payload = JSON.stringify(value);
1444
+ const backend = resolveStorageBackend();
1445
+ const namespaced = storageKey(normalizedKey);
1446
+ if (backend) {
1447
+ try {
1448
+ backend.setItem(namespaced, payload);
1449
+ return true;
1450
+ } catch (_) {
1451
+ return false;
1452
+ }
1453
+ }
1454
+ assetState.storageFallback.set(namespaced, payload);
1455
+ return true;
1456
+ }
1457
+
1458
+ function deleteStorage(key) {
1459
+ const normalizedKey = String(key || '');
1460
+ if (normalizedKey.length === 0) return false;
1461
+ const namespaced = storageKey(normalizedKey);
1462
+ const backend = resolveStorageBackend();
1463
+ if (backend) {
1464
+ try {
1465
+ backend.removeItem(namespaced);
1466
+ return true;
1467
+ } catch (_) {
1468
+ return false;
1469
+ }
1470
+ }
1471
+ return assetState.storageFallback.delete(namespaced);
1472
+ }
1473
+
1474
+ const camera = auraRef.camera && typeof auraRef.camera === 'object' ? auraRef.camera : {};
1475
+ let cameraBaseX = Number(camera.x) || 0;
1476
+ let cameraBaseY = Number(camera.y) || 0;
1477
+ let cameraBaseZoom = normalizePositiveNumber(camera.zoom, 1);
1478
+ let cameraBaseRotation = Number(camera.rotation) || 0;
1479
+ let cameraShakeX = 0;
1480
+ let cameraShakeY = 0;
1481
+ let cameraFollowState = null;
1482
+ let cameraDeadzone = null;
1483
+ let cameraBounds = null;
1484
+ let nextCameraEffectId = 1;
1485
+ let nextCameraListenerId = 1;
1486
+ const cameraEffects = [];
1487
+ const cameraEffectListeners = [];
1488
+
1489
+ function applyCameraBounds() {
1490
+ if (!cameraBounds) return;
1491
+ const maxX = cameraBounds.x + cameraBounds.width;
1492
+ const maxY = cameraBounds.y + cameraBounds.height;
1493
+ cameraBaseX = Math.max(cameraBounds.x, Math.min(cameraBaseX, maxX));
1494
+ cameraBaseY = Math.max(cameraBounds.y, Math.min(cameraBaseY, maxY));
1495
+ }
1496
+
1497
+ function normalizeFollowOptions(options) {
1498
+ if (options == null) {
1499
+ return {
1500
+ lerpX: 1,
1501
+ lerpY: 1,
1502
+ offsetX: 0,
1503
+ offsetY: 0
1504
+ };
1505
+ }
1506
+ if (!isObject(options)) return null;
1507
+ return {
1508
+ lerpX: clamp01(options.lerpX),
1509
+ lerpY: clamp01(options.lerpY),
1510
+ offsetX: toFinite(options.offsetX, 0),
1511
+ offsetY: toFinite(options.offsetY, 0)
1512
+ };
1513
+ }
1514
+
1515
+ function normalizeDeadzone(value) {
1516
+ const input = isObject(value) ? value : null;
1517
+ const zoneWidth = toFinite(input && input.width, Number.NaN);
1518
+ const zoneHeight = toFinite(input && input.height, Number.NaN);
1519
+ if (!(zoneWidth > 0) || !(zoneHeight > 0)) return null;
1520
+ return {
1521
+ x: toFinite(input && input.x, 0),
1522
+ y: toFinite(input && input.y, 0),
1523
+ width: zoneWidth,
1524
+ height: zoneHeight
1525
+ };
1526
+ }
1527
+
1528
+ function normalizeBounds(value) {
1529
+ const input = isObject(value) ? value : null;
1530
+ const zoneWidth = toFinite(input && input.width, Number.NaN);
1531
+ const zoneHeight = toFinite(input && input.height, Number.NaN);
1532
+ if (!(zoneWidth >= 0) || !(zoneHeight >= 0)) return null;
1533
+ return {
1534
+ x: toFinite(input && input.x, 0),
1535
+ y: toFinite(input && input.y, 0),
1536
+ width: zoneWidth,
1537
+ height: zoneHeight
1538
+ };
1539
+ }
1540
+
1541
+ function resolveFollowTarget(target) {
1542
+ let source = target;
1543
+ if (typeof source === 'function') {
1544
+ try {
1545
+ source = source();
1546
+ } catch (_) {
1547
+ return null;
1548
+ }
1549
+ }
1550
+ if (!isObject(source)) return null;
1551
+ const x = toFinite(source.x, Number.NaN);
1552
+ const y = toFinite(source.y, Number.NaN);
1553
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
1554
+ return { x: x, y: y };
1555
+ }
1556
+
1557
+ function emitCameraEffectEvent(event) {
1558
+ if (cameraEffectListeners.length === 0) return;
1559
+ const ordered = cameraEffectListeners.slice().sort(function (a, b) {
1560
+ return (a.order - b.order) || (a.id - b.id);
1561
+ });
1562
+ for (const listener of ordered) {
1563
+ try {
1564
+ listener.callback(event);
1565
+ } catch (_) {}
1566
+ }
1567
+ }
1568
+
1569
+ Object.defineProperties(camera, {
1570
+ x: {
1571
+ enumerable: true,
1572
+ configurable: false,
1573
+ get: function () { return cameraBaseX + cameraShakeX; },
1574
+ set: function (value) {
1575
+ const numeric = Number(value);
1576
+ if (Number.isFinite(numeric)) cameraBaseX = numeric;
1577
+ }
1578
+ },
1579
+ y: {
1580
+ enumerable: true,
1581
+ configurable: false,
1582
+ get: function () { return cameraBaseY + cameraShakeY; },
1583
+ set: function (value) {
1584
+ const numeric = Number(value);
1585
+ if (Number.isFinite(numeric)) cameraBaseY = numeric;
1586
+ }
1587
+ },
1588
+ zoom: {
1589
+ enumerable: true,
1590
+ configurable: false,
1591
+ get: function () { return cameraBaseZoom; },
1592
+ set: function (value) {
1593
+ const numeric = Number(value);
1594
+ if (Number.isFinite(numeric) && numeric > 0) cameraBaseZoom = numeric;
1595
+ }
1596
+ },
1597
+ rotation: {
1598
+ enumerable: true,
1599
+ configurable: false,
1600
+ get: function () { return cameraBaseRotation; },
1601
+ set: function (value) {
1602
+ const numeric = Number(value);
1603
+ if (Number.isFinite(numeric)) cameraBaseRotation = numeric;
1604
+ }
1605
+ }
1606
+ });
1607
+
1608
+ camera.getState = function () {
1609
+ return {
1610
+ x: cameraBaseX + cameraShakeX,
1611
+ y: cameraBaseY + cameraShakeY,
1612
+ zoom: cameraBaseZoom,
1613
+ rotation: cameraBaseRotation,
1614
+ following: !!cameraFollowState,
1615
+ activeEffects: cameraEffects.length,
1616
+ deadzone: cameraDeadzone ? { x: cameraDeadzone.x, y: cameraDeadzone.y, width: cameraDeadzone.width, height: cameraDeadzone.height } : null,
1617
+ bounds: cameraBounds ? { x: cameraBounds.x, y: cameraBounds.y, width: cameraBounds.width, height: cameraBounds.height } : null
1618
+ };
1619
+ };
1620
+
1621
+ camera.follow = function (target, options) {
1622
+ if (!(typeof target === 'function' || isObject(target))) {
1623
+ return { ok: false, reasonCode: 'invalid_follow_target' };
1624
+ }
1625
+ const normalized = normalizeFollowOptions(options);
1626
+ if (!normalized) return { ok: false, reasonCode: 'invalid_follow_options' };
1627
+ cameraFollowState = {
1628
+ target: target,
1629
+ lerpX: normalized.lerpX,
1630
+ lerpY: normalized.lerpY,
1631
+ offsetX: normalized.offsetX,
1632
+ offsetY: normalized.offsetY
1633
+ };
1634
+ return { ok: true, reasonCode: 'camera_follow_started' };
1635
+ };
1636
+
1637
+ camera.stopFollow = function () {
1638
+ const stopped = !!cameraFollowState;
1639
+ cameraFollowState = null;
1640
+ return { ok: true, stopped: stopped, reasonCode: 'camera_follow_stopped' };
1641
+ };
1642
+
1643
+ camera.setDeadzone = function (value, maybeHeight) {
1644
+ const normalized = isObject(value)
1645
+ ? normalizeDeadzone(value)
1646
+ : normalizeDeadzone({ width: value, height: maybeHeight });
1647
+ if (!normalized) return { ok: false, reasonCode: 'invalid_deadzone' };
1648
+ cameraDeadzone = normalized;
1649
+ return { ok: true, reasonCode: 'camera_deadzone_set' };
1650
+ };
1651
+
1652
+ camera.clearDeadzone = function () {
1653
+ cameraDeadzone = null;
1654
+ return { ok: true, reasonCode: 'camera_deadzone_cleared' };
1655
+ };
1656
+
1657
+ camera.setBounds = function (xOrBounds, y, boundsWidth, boundsHeight) {
1658
+ const normalized = isObject(xOrBounds)
1659
+ ? normalizeBounds(xOrBounds)
1660
+ : normalizeBounds({ x: xOrBounds, y: y, width: boundsWidth, height: boundsHeight });
1661
+ if (!normalized) return { ok: false, reasonCode: 'invalid_bounds' };
1662
+ cameraBounds = normalized;
1663
+ applyCameraBounds();
1664
+ return { ok: true, reasonCode: 'camera_bounds_set' };
1665
+ };
1666
+
1667
+ camera.clearBounds = function () {
1668
+ cameraBounds = null;
1669
+ return { ok: true, reasonCode: 'camera_bounds_cleared' };
1670
+ };
1671
+
1672
+ camera.pan = function (x, y, options) {
1673
+ const targetX = Number(x);
1674
+ const targetY = Number(y);
1675
+ if (!Number.isFinite(targetX) || !Number.isFinite(targetY)) {
1676
+ return { ok: false, reasonCode: 'invalid_pan_target' };
1677
+ }
1678
+ if (options != null && !isObject(options)) return { ok: false, reasonCode: 'invalid_pan_options' };
1679
+ const duration = toPositive(options && options.duration, 0.25);
1680
+ if (!(duration > 0)) return { ok: false, reasonCode: 'invalid_pan_duration' };
1681
+ const effectId = nextCameraEffectId++;
1682
+ cameraEffects.push({
1683
+ id: effectId,
1684
+ type: 'pan',
1685
+ duration: duration,
1686
+ elapsed: 0,
1687
+ startX: cameraBaseX,
1688
+ startY: cameraBaseY,
1689
+ targetX: targetX,
1690
+ targetY: targetY
1691
+ });
1692
+ return { ok: true, effectId: effectId, reasonCode: 'camera_pan_started' };
1693
+ };
1694
+ camera.panTo = camera.pan;
1695
+
1696
+ camera.zoomTo = function (value, options) {
1697
+ const targetZoom = Number(value);
1698
+ if (!Number.isFinite(targetZoom) || !(targetZoom > 0)) {
1699
+ return { ok: false, reasonCode: 'invalid_zoom_target' };
1700
+ }
1701
+ if (options != null && !isObject(options)) return { ok: false, reasonCode: 'invalid_zoom_options' };
1702
+ const duration = toPositive(options && options.duration, 0.25);
1703
+ if (!(duration > 0)) return { ok: false, reasonCode: 'invalid_zoom_duration' };
1704
+ const effectId = nextCameraEffectId++;
1705
+ cameraEffects.push({
1706
+ id: effectId,
1707
+ type: 'zoom',
1708
+ duration: duration,
1709
+ elapsed: 0,
1710
+ startZoom: cameraBaseZoom,
1711
+ targetZoom: targetZoom
1712
+ });
1713
+ return { ok: true, effectId: effectId, reasonCode: 'camera_zoom_started' };
1714
+ };
1715
+
1716
+ camera.rotateTo = function (value, options) {
1717
+ const targetRotation = Number(value);
1718
+ if (!Number.isFinite(targetRotation)) {
1719
+ return { ok: false, reasonCode: 'invalid_rotation_target' };
1720
+ }
1721
+ if (options != null && !isObject(options)) return { ok: false, reasonCode: 'invalid_rotation_options' };
1722
+ const duration = toPositive(options && options.duration, 0.25);
1723
+ if (!(duration > 0)) return { ok: false, reasonCode: 'invalid_rotation_duration' };
1724
+ const effectId = nextCameraEffectId++;
1725
+ cameraEffects.push({
1726
+ id: effectId,
1727
+ type: 'rotate',
1728
+ duration: duration,
1729
+ elapsed: 0,
1730
+ startRotation: cameraBaseRotation,
1731
+ targetRotation: targetRotation
1732
+ });
1733
+ return { ok: true, effectId: effectId, reasonCode: 'camera_rotation_started' };
1734
+ };
1735
+
1736
+ camera.shake = function (options) {
1737
+ const source = options == null ? {} : options;
1738
+ if (!isObject(source)) return { ok: false, reasonCode: 'invalid_shake_options' };
1739
+ const sharedIntensity = Number.isFinite(Number(source.intensity))
1740
+ ? Number(source.intensity)
1741
+ : null;
1742
+ const intensityX = toFinite(source.intensityX, sharedIntensity != null ? sharedIntensity : 6);
1743
+ const intensityY = toFinite(source.intensityY, sharedIntensity != null ? sharedIntensity : 6);
1744
+ if (!(intensityX >= 0) || !(intensityY >= 0)) {
1745
+ return { ok: false, reasonCode: 'invalid_shake_intensity' };
1746
+ }
1747
+ const duration = toPositive(source.duration, 0.3);
1748
+ if (!(duration > 0)) return { ok: false, reasonCode: 'invalid_shake_duration' };
1749
+ const frequency = toPositive(source.frequency, 30);
1750
+ if (!(frequency > 0)) return { ok: false, reasonCode: 'invalid_shake_frequency' };
1751
+ const effectId = nextCameraEffectId++;
1752
+ cameraEffects.push({
1753
+ id: effectId,
1754
+ type: 'shake',
1755
+ duration: duration,
1756
+ elapsed: 0,
1757
+ intensityX: intensityX,
1758
+ intensityY: intensityY,
1759
+ frequency: frequency,
1760
+ seed: effectId * 0.61803398875
1761
+ });
1762
+ return { ok: true, effectId: effectId, reasonCode: 'camera_shake_started' };
1763
+ };
1764
+
1765
+ camera.clearEffects = function () {
1766
+ const cleared = cameraEffects.length;
1767
+ cameraEffects.length = 0;
1768
+ cameraShakeX = 0;
1769
+ cameraShakeY = 0;
1770
+ return { ok: true, cleared: cleared, reasonCode: 'camera_effects_cleared' };
1771
+ };
1772
+
1773
+ camera.onEffectComplete = function (callback, order) {
1774
+ if (typeof callback !== 'function') {
1775
+ return { ok: false, reasonCode: 'invalid_effect_callback' };
1776
+ }
1777
+ const listener = {
1778
+ id: nextCameraListenerId++,
1779
+ callback: callback,
1780
+ order: Number.isFinite(Number(order)) ? Number(order) : 0
1781
+ };
1782
+ cameraEffectListeners.push(listener);
1783
+ return { ok: true, listenerId: listener.id, reasonCode: 'camera_effect_listener_registered' };
1784
+ };
1785
+
1786
+ camera.offEffectComplete = function (listenerId) {
1787
+ if (!Number.isInteger(listenerId) || listenerId <= 0) return false;
1788
+ const index = cameraEffectListeners.findIndex(function (entry) {
1789
+ return entry.id === listenerId;
1790
+ });
1791
+ if (index < 0) return false;
1792
+ cameraEffectListeners.splice(index, 1);
1793
+ return true;
1794
+ };
1795
+
1796
+ camera.update = function (dt) {
1797
+ const delta = Number(dt);
1798
+ if (!Number.isFinite(delta) || !(delta > 0)) {
1799
+ return { ok: false, reasonCode: 'invalid_dt' };
1800
+ }
1801
+
1802
+ cameraShakeX = 0;
1803
+ cameraShakeY = 0;
1804
+
1805
+ if (cameraFollowState) {
1806
+ const targetPoint = resolveFollowTarget(cameraFollowState.target);
1807
+ if (targetPoint) {
1808
+ let targetX = targetPoint.x + cameraFollowState.offsetX;
1809
+ let targetY = targetPoint.y + cameraFollowState.offsetY;
1810
+
1811
+ if (cameraDeadzone) {
1812
+ const left = cameraBaseX + cameraDeadzone.x;
1813
+ const right = left + cameraDeadzone.width;
1814
+ const top = cameraBaseY + cameraDeadzone.y;
1815
+ const bottom = top + cameraDeadzone.height;
1816
+
1817
+ if (targetX < left) targetX = targetX - cameraDeadzone.x;
1818
+ else if (targetX > right) targetX = targetX - cameraDeadzone.x - cameraDeadzone.width;
1819
+ else targetX = cameraBaseX;
1820
+
1821
+ if (targetY < top) targetY = targetY - cameraDeadzone.y;
1822
+ else if (targetY > bottom) targetY = targetY - cameraDeadzone.y - cameraDeadzone.height;
1823
+ else targetY = cameraBaseY;
1824
+ }
1825
+
1826
+ cameraBaseX += (targetX - cameraBaseX) * cameraFollowState.lerpX;
1827
+ cameraBaseY += (targetY - cameraBaseY) * cameraFollowState.lerpY;
1828
+ }
1829
+ }
1830
+
1831
+ const completedEffects = [];
1832
+ for (const effect of cameraEffects) {
1833
+ effect.elapsed += delta;
1834
+ const progress = effect.duration <= 0 ? 1 : Math.min(effect.elapsed / effect.duration, 1);
1835
+ if (effect.type === 'pan') {
1836
+ cameraBaseX = effect.startX + ((effect.targetX - effect.startX) * progress);
1837
+ cameraBaseY = effect.startY + ((effect.targetY - effect.startY) * progress);
1838
+ } else if (effect.type === 'zoom') {
1839
+ cameraBaseZoom = effect.startZoom + ((effect.targetZoom - effect.startZoom) * progress);
1840
+ } else if (effect.type === 'rotate') {
1841
+ cameraBaseRotation = effect.startRotation + ((effect.targetRotation - effect.startRotation) * progress);
1842
+ } else if (effect.type === 'shake') {
1843
+ const amplitude = 1 - progress;
1844
+ const angle = (effect.seed + (effect.elapsed * effect.frequency)) * 6.283185307179586;
1845
+ cameraShakeX += Math.sin(angle) * effect.intensityX * amplitude;
1846
+ cameraShakeY += Math.cos(angle * 1.17) * effect.intensityY * amplitude;
1847
+ }
1848
+ if (progress >= 1) completedEffects.push(effect);
1849
+ }
1850
+
1851
+ if (completedEffects.length > 0) {
1852
+ for (const completed of completedEffects) {
1853
+ const index = cameraEffects.indexOf(completed);
1854
+ if (index >= 0) cameraEffects.splice(index, 1);
1855
+ }
1856
+ completedEffects.sort(function (a, b) { return a.id - b.id; });
1857
+ for (const completed of completedEffects) {
1858
+ emitCameraEffectEvent({
1859
+ type: 'effect_complete',
1860
+ effectType: completed.type,
1861
+ effectId: completed.id,
1862
+ reasonCode: 'camera_effect_complete'
1863
+ });
1864
+ }
1865
+ }
1866
+
1867
+ applyCameraBounds();
1868
+
1869
+ return {
1870
+ ok: true,
1871
+ reasonCode: 'camera_updated',
1872
+ x: cameraBaseX + cameraShakeX,
1873
+ y: cameraBaseY + cameraShakeY,
1874
+ zoom: cameraBaseZoom,
1875
+ rotation: cameraBaseRotation,
1876
+ following: !!cameraFollowState,
1877
+ activeEffects: cameraEffects.length
1878
+ };
1879
+ };
1880
+
759
1881
  function attachListeners() {
760
1882
  if (runtime.listenersAttached) return;
761
1883
  runtime.listenersAttached = true;
@@ -782,12 +1904,44 @@ const WEB_LOADER_SOURCE = `
782
1904
  }
783
1905
  };
784
1906
 
1907
+ runtime.mousemoveListener = function (event) {
1908
+ syncMousePosition(event);
1909
+ };
1910
+
1911
+ runtime.mousedownListener = function (event) {
1912
+ syncMousePosition(event);
1913
+ const button = normalizeMouseButton(event && event.button);
1914
+ if (!inputState.mouseDown.has(button)) {
1915
+ inputState.pendingMousePressed.add(button);
1916
+ }
1917
+ inputState.mouseDown.add(button);
1918
+ };
1919
+
1920
+ runtime.mouseupListener = function (event) {
1921
+ syncMousePosition(event);
1922
+ const button = normalizeMouseButton(event && event.button);
1923
+ inputState.mouseDown.delete(button);
1924
+ inputState.pendingMouseReleased.add(button);
1925
+ };
1926
+
1927
+ runtime.wheelListener = function (event) {
1928
+ if (event && Number.isFinite(Number(event.deltaY))) {
1929
+ auraRef.input.mouse.scroll += Number(event.deltaY);
1930
+ }
1931
+ };
1932
+
1933
+ runtime.focusListener = function () {
1934
+ clearMouseDeltaState();
1935
+ if (typeof auraRef.onFocus === 'function') {
1936
+ auraRef.onFocus();
1937
+ }
1938
+ };
1939
+
785
1940
  runtime.blurListener = function () {
786
- inputState.down.clear();
787
- inputState.pendingPressed.clear();
788
- inputState.pendingReleased.clear();
789
- inputState.framePressed.clear();
790
- inputState.frameReleased.clear();
1941
+ clearHeldInputState();
1942
+ if (typeof auraRef.onBlur === 'function') {
1943
+ auraRef.onBlur();
1944
+ }
791
1945
  };
792
1946
 
793
1947
  runtime.resizeListener = function () {
@@ -797,6 +1951,11 @@ const WEB_LOADER_SOURCE = `
797
1951
  if (typeof globalRef.addEventListener === 'function') {
798
1952
  globalRef.addEventListener('keydown', runtime.keydownListener);
799
1953
  globalRef.addEventListener('keyup', runtime.keyupListener);
1954
+ globalRef.addEventListener('focus', runtime.focusListener);
1955
+ globalRef.addEventListener('mousemove', runtime.mousemoveListener);
1956
+ globalRef.addEventListener('mousedown', runtime.mousedownListener);
1957
+ globalRef.addEventListener('mouseup', runtime.mouseupListener);
1958
+ globalRef.addEventListener('wheel', runtime.wheelListener);
800
1959
  globalRef.addEventListener('blur', runtime.blurListener);
801
1960
  globalRef.addEventListener('resize', runtime.resizeListener);
802
1961
  }
@@ -808,6 +1967,11 @@ const WEB_LOADER_SOURCE = `
808
1967
  if (typeof globalRef.removeEventListener === 'function') {
809
1968
  globalRef.removeEventListener('keydown', runtime.keydownListener);
810
1969
  globalRef.removeEventListener('keyup', runtime.keyupListener);
1970
+ globalRef.removeEventListener('focus', runtime.focusListener);
1971
+ globalRef.removeEventListener('mousemove', runtime.mousemoveListener);
1972
+ globalRef.removeEventListener('mousedown', runtime.mousedownListener);
1973
+ globalRef.removeEventListener('mouseup', runtime.mouseupListener);
1974
+ globalRef.removeEventListener('wheel', runtime.wheelListener);
811
1975
  globalRef.removeEventListener('blur', runtime.blurListener);
812
1976
  globalRef.removeEventListener('resize', runtime.resizeListener);
813
1977
  }
@@ -824,6 +1988,9 @@ const WEB_LOADER_SOURCE = `
824
1988
  auraRef.rgba = function (r, g, b, a) {
825
1989
  return createUnitColor(r, g, b, a == null ? 1 : a);
826
1990
  };
1991
+ auraRef.rgb = function (r, g, b) {
1992
+ return createUnitColor(r, g, b, 1);
1993
+ };
827
1994
  auraRef.color = auraRef.rgba;
828
1995
  auraRef.Color = auraRef.Color && typeof auraRef.Color === 'object'
829
1996
  ? auraRef.Color
@@ -844,6 +2011,19 @@ const WEB_LOADER_SOURCE = `
844
2011
  transparent: createByteColor(0, 0, 0, 0)
845
2012
  };
846
2013
 
2014
+ auraRef.math = auraRef.math && typeof auraRef.math === 'object' ? auraRef.math : {};
2015
+ auraRef.math.clamp = typeof auraRef.math.clamp === 'function'
2016
+ ? auraRef.math.clamp
2017
+ : function (value, min, max) {
2018
+ const numeric = Number(value);
2019
+ const minValue = Number(min);
2020
+ const maxValue = Number(max);
2021
+ if (!Number.isFinite(numeric) || !Number.isFinite(minValue) || !Number.isFinite(maxValue)) {
2022
+ return Number.isFinite(numeric) ? numeric : 0;
2023
+ }
2024
+ return Math.min(maxValue, Math.max(minValue, numeric));
2025
+ };
2026
+
847
2027
  auraRef.window = auraRef.window && typeof auraRef.window === 'object' ? auraRef.window : {};
848
2028
  auraRef.window.width = runtime.width;
849
2029
  auraRef.window.height = runtime.height;
@@ -864,6 +2044,26 @@ const WEB_LOADER_SOURCE = `
864
2044
  auraRef.window.setFullscreen = function () {
865
2045
  return false;
866
2046
  };
2047
+ auraRef.window.setCursorVisible = function (visible) {
2048
+ runtime.cursorVisible = !!visible;
2049
+ applyCursorAppearance();
2050
+ return true;
2051
+ };
2052
+ auraRef.window.setCursorLocked = function (locked) {
2053
+ runtime.cursorLocked = !!locked;
2054
+ clearMouseDeltaState();
2055
+ applyCursorAppearance();
2056
+ if (runtime.canvas && typeof runtime.canvas.requestPointerLock === 'function' && runtime.cursorLocked) {
2057
+ runtime.canvas.requestPointerLock();
2058
+ } else if (
2059
+ !runtime.cursorLocked &&
2060
+ globalRef.document &&
2061
+ typeof globalRef.document.exitPointerLock === 'function'
2062
+ ) {
2063
+ globalRef.document.exitPointerLock();
2064
+ }
2065
+ return true;
2066
+ };
867
2067
  auraRef.window.getSize = function () {
868
2068
  return { width: runtime.width, height: runtime.height };
869
2069
  };
@@ -884,6 +2084,8 @@ const WEB_LOADER_SOURCE = `
884
2084
  auraRef.collide = auraRef.collide && typeof auraRef.collide === 'object' ? auraRef.collide : auraRef.collision;
885
2085
  auraRef.collide.rectRect = auraRef.collision.rectRect;
886
2086
 
2087
+ auraRef.camera = camera;
2088
+
887
2089
  auraRef.input = auraRef.input && typeof auraRef.input === 'object' ? auraRef.input : {};
888
2090
  auraRef.input.isDown = function (name) {
889
2091
  const key = normalizeKeyName(name);
@@ -906,39 +2108,390 @@ const WEB_LOADER_SOURCE = `
906
2108
  auraRef.input.isKeyReleased = function (name) {
907
2109
  return auraRef.input.isReleased(name);
908
2110
  };
2111
+ auraRef.input.isGamepadConnected = typeof auraRef.input.isGamepadConnected === 'function'
2112
+ ? auraRef.input.isGamepadConnected
2113
+ : function () { return false; };
909
2114
  auraRef.input.mouse = auraRef.input.mouse && typeof auraRef.input.mouse === 'object' ? auraRef.input.mouse : {};
910
2115
  auraRef.input.mouse.x = Number(auraRef.input.mouse.x) || 0;
911
2116
  auraRef.input.mouse.y = Number(auraRef.input.mouse.y) || 0;
912
2117
  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; };
2118
+ auraRef.input.mouse.isDown = function (button) {
2119
+ return inputState.mouseDown.has(normalizeMouseButton(button));
2120
+ };
2121
+ auraRef.input.mouse.isPressed = function (button) {
2122
+ return inputState.frameMousePressed.has(normalizeMouseButton(button));
2123
+ };
2124
+ auraRef.input.mouse.isReleased = function (button) {
2125
+ return inputState.frameMouseReleased.has(normalizeMouseButton(button));
2126
+ };
2127
+ auraRef.input.isMouseDown = function (button) {
2128
+ return auraRef.input.mouse.isDown(button);
2129
+ };
2130
+ auraRef.input.isMousePressed = function (button) {
2131
+ return auraRef.input.mouse.isPressed(button);
2132
+ };
2133
+ auraRef.input.isMouseReleased = function (button) {
2134
+ return auraRef.input.mouse.isReleased(button);
2135
+ };
916
2136
  auraRef.input.getMousePosition = function () {
917
2137
  return { x: auraRef.input.mouse.x, y: auraRef.input.mouse.y };
918
2138
  };
2139
+ auraRef.input.getMouseDelta = function () {
2140
+ return { x: inputState.frameMouseDeltaX, y: inputState.frameMouseDeltaY };
2141
+ };
2142
+
2143
+ auraRef.state = auraRef.state && typeof auraRef.state === 'object' ? auraRef.state : {};
2144
+ auraRef.state.export = typeof auraRef.state.export === 'function'
2145
+ ? auraRef.state.export
2146
+ : function () {
2147
+ return createUnsupportedStateResult('export', 'web_state_export_unsupported');
2148
+ };
2149
+ auraRef.state.apply = typeof auraRef.state.apply === 'function'
2150
+ ? auraRef.state.apply
2151
+ : function () {
2152
+ return createUnsupportedStateResult('apply', 'web_state_apply_unsupported');
2153
+ };
2154
+ auraRef.state.diff = typeof auraRef.state.diff === 'function'
2155
+ ? auraRef.state.diff
2156
+ : function () {
2157
+ return createUnsupportedStateResult('diff', 'web_state_diff_unsupported');
2158
+ };
2159
+ auraRef.state.patch = typeof auraRef.state.patch === 'function'
2160
+ ? auraRef.state.patch
2161
+ : function () {
2162
+ return createUnsupportedStateResult('patch', 'web_state_patch_unsupported');
2163
+ };
2164
+ auraRef.state.exportState = typeof auraRef.state.exportState === 'function'
2165
+ ? auraRef.state.exportState
2166
+ : function (options) {
2167
+ return auraRef.state.export(options);
2168
+ };
2169
+ auraRef.state.applyState = typeof auraRef.state.applyState === 'function'
2170
+ ? auraRef.state.applyState
2171
+ : function (payload, options) {
2172
+ return auraRef.state.apply(payload, options);
2173
+ };
2174
+ auraRef.state.diffState = typeof auraRef.state.diffState === 'function'
2175
+ ? auraRef.state.diffState
2176
+ : function (beforePayload, afterPayload) {
2177
+ return auraRef.state.diff(beforePayload, afterPayload);
2178
+ };
2179
+ auraRef.state.patchState = typeof auraRef.state.patchState === 'function'
2180
+ ? auraRef.state.patchState
2181
+ : function (payload, patchPayload, options) {
2182
+ return auraRef.state.patch(payload, patchPayload, options);
2183
+ };
2184
+
2185
+ auraRef.net = auraRef.net && typeof auraRef.net === 'object' ? auraRef.net : {};
2186
+ auraRef.net.connect = typeof auraRef.net.connect === 'function'
2187
+ ? auraRef.net.connect
2188
+ : function () {
2189
+ throw createUnsupportedRuntimeError('net', 'connect', 'web_net_connect_unsupported');
2190
+ };
2191
+ auraRef.net.websocket = typeof auraRef.net.websocket === 'function'
2192
+ ? auraRef.net.websocket
2193
+ : function () {
2194
+ throw createUnsupportedRuntimeError('net', 'websocket', 'web_net_websocket_unsupported');
2195
+ };
2196
+ auraRef.net.fetch = typeof auraRef.net.fetch === 'function'
2197
+ ? auraRef.net.fetch
2198
+ : function () {
2199
+ throw createUnsupportedRuntimeError('net', 'fetch', 'web_net_fetch_unsupported');
2200
+ };
2201
+ auraRef.net.get = typeof auraRef.net.get === 'function'
2202
+ ? auraRef.net.get
2203
+ : function () {
2204
+ throw createUnsupportedRuntimeError('net', 'get', 'web_net_get_unsupported');
2205
+ };
2206
+ auraRef.net.post = typeof auraRef.net.post === 'function'
2207
+ ? auraRef.net.post
2208
+ : function () {
2209
+ throw createUnsupportedRuntimeError('net', 'post', 'web_net_post_unsupported');
2210
+ };
2211
+
2212
+ auraRef.storage = auraRef.storage && typeof auraRef.storage === 'object' ? auraRef.storage : {};
2213
+ auraRef.storage.save = typeof auraRef.storage.save === 'function'
2214
+ ? auraRef.storage.save
2215
+ : function (key, value) { return writeStorage(key, value); };
2216
+ auraRef.storage.load = typeof auraRef.storage.load === 'function'
2217
+ ? auraRef.storage.load
2218
+ : function (key) { return readStorage(key); };
2219
+ auraRef.storage.delete = typeof auraRef.storage.delete === 'function'
2220
+ ? auraRef.storage.delete
2221
+ : function (key) { return deleteStorage(key); };
2222
+ auraRef.storage.keys = typeof auraRef.storage.keys === 'function'
2223
+ ? auraRef.storage.keys
2224
+ : function () {
2225
+ const prefix = 'aurajs:';
2226
+ const backend = resolveStorageBackend();
2227
+ const keys = [];
2228
+ if (backend && Number.isInteger(backend.length) && typeof backend.key === 'function') {
2229
+ for (let index = 0; index < backend.length; index += 1) {
2230
+ const nextKey = backend.key(index);
2231
+ if (typeof nextKey === 'string' && nextKey.startsWith(prefix)) {
2232
+ keys.push(nextKey.slice(prefix.length));
2233
+ }
2234
+ }
2235
+ keys.sort(function (a, b) { return a.localeCompare(b); });
2236
+ return keys;
2237
+ }
2238
+ for (const key of assetState.storageFallback.keys()) {
2239
+ if (key.startsWith(prefix)) {
2240
+ keys.push(key.slice(prefix.length));
2241
+ }
2242
+ }
2243
+ keys.sort(function (a, b) { return a.localeCompare(b); });
2244
+ return keys;
2245
+ };
2246
+ auraRef.storage.set = typeof auraRef.storage.set === 'function'
2247
+ ? auraRef.storage.set
2248
+ : function (key, value) { return writeStorage(key, value); };
2249
+ auraRef.storage.get = typeof auraRef.storage.get === 'function'
2250
+ ? auraRef.storage.get
2251
+ : function (key, fallback) {
2252
+ const value = readStorage(key);
2253
+ return value == null ? fallback : value;
2254
+ };
2255
+
2256
+ auraRef.audio = auraRef.audio && typeof auraRef.audio === 'object' ? auraRef.audio : {};
2257
+ if (auraRef.audio.supported !== true) {
2258
+ auraRef.audio.supported = false;
2259
+ }
2260
+ if (typeof auraRef.audio.reasonCode !== 'string') {
2261
+ auraRef.audio.reasonCode = 'web_audio_playback_unsupported';
2262
+ }
2263
+ if (typeof auraRef.audio.runtime !== 'string') {
2264
+ auraRef.audio.runtime = 'web';
2265
+ }
2266
+ auraRef.audio.play = typeof auraRef.audio.play === 'function'
2267
+ ? auraRef.audio.play
2268
+ : function () {
2269
+ throw createUnsupportedRuntimeError('audio', 'play', 'web_audio_play_unsupported', { playbackSupported: false });
2270
+ };
2271
+ auraRef.audio.stop = typeof auraRef.audio.stop === 'function'
2272
+ ? auraRef.audio.stop
2273
+ : function () {
2274
+ throw createUnsupportedRuntimeError('audio', 'stop', 'web_audio_stop_unsupported', { playbackSupported: false });
2275
+ };
2276
+ auraRef.audio.pause = typeof auraRef.audio.pause === 'function'
2277
+ ? auraRef.audio.pause
2278
+ : function () {
2279
+ throw createUnsupportedRuntimeError('audio', 'pause', 'web_audio_pause_unsupported', { playbackSupported: false });
2280
+ };
2281
+ auraRef.audio.resume = typeof auraRef.audio.resume === 'function'
2282
+ ? auraRef.audio.resume
2283
+ : function () {
2284
+ throw createUnsupportedRuntimeError('audio', 'resume', 'web_audio_resume_unsupported', { playbackSupported: false });
2285
+ };
2286
+ auraRef.audio.setVolume = typeof auraRef.audio.setVolume === 'function'
2287
+ ? auraRef.audio.setVolume
2288
+ : function () {
2289
+ throw createUnsupportedRuntimeError('audio', 'setVolume', 'web_audio_set_volume_unsupported', { playbackSupported: false });
2290
+ };
2291
+ auraRef.audio.setMasterVolume = typeof auraRef.audio.setMasterVolume === 'function'
2292
+ ? auraRef.audio.setMasterVolume
2293
+ : function () {
2294
+ throw createUnsupportedRuntimeError('audio', 'setMasterVolume', 'web_audio_set_master_volume_unsupported', { playbackSupported: false });
2295
+ };
2296
+ auraRef.audio.stopAll = typeof auraRef.audio.stopAll === 'function'
2297
+ ? auraRef.audio.stopAll
2298
+ : function () {
2299
+ throw createUnsupportedRuntimeError('audio', 'stopAll', 'web_audio_stop_all_unsupported', { playbackSupported: false });
2300
+ };
2301
+ auraRef.audio.setBusVolume = typeof auraRef.audio.setBusVolume === 'function'
2302
+ ? auraRef.audio.setBusVolume
2303
+ : function () {
2304
+ throw createUnsupportedRuntimeError('audio', 'setBusVolume', 'web_audio_set_bus_volume_unsupported', { playbackSupported: false });
2305
+ };
2306
+ auraRef.audio.assignBus = typeof auraRef.audio.assignBus === 'function'
2307
+ ? auraRef.audio.assignBus
2308
+ : function () {
2309
+ throw createUnsupportedRuntimeError('audio', 'assignBus', 'web_audio_assign_bus_unsupported', { playbackSupported: false });
2310
+ };
2311
+ auraRef.audio.fadeTrack = typeof auraRef.audio.fadeTrack === 'function'
2312
+ ? auraRef.audio.fadeTrack
2313
+ : function () {
2314
+ throw createUnsupportedRuntimeError('audio', 'fadeTrack', 'web_audio_fade_track_unsupported', { playbackSupported: false });
2315
+ };
2316
+ auraRef.audio.fadeBus = typeof auraRef.audio.fadeBus === 'function'
2317
+ ? auraRef.audio.fadeBus
2318
+ : function () {
2319
+ throw createUnsupportedRuntimeError('audio', 'fadeBus', 'web_audio_fade_bus_unsupported', { playbackSupported: false });
2320
+ };
2321
+ auraRef.audio.crossfade = typeof auraRef.audio.crossfade === 'function'
2322
+ ? auraRef.audio.crossfade
2323
+ : function () {
2324
+ throw createUnsupportedRuntimeError('audio', 'crossfade', 'web_audio_crossfade_unsupported', { playbackSupported: false });
2325
+ };
2326
+ auraRef.audio.update = typeof auraRef.audio.update === 'function'
2327
+ ? auraRef.audio.update
2328
+ : function () {
2329
+ throw createUnsupportedRuntimeError('audio', 'update', 'web_audio_update_unsupported', { playbackSupported: false });
2330
+ };
2331
+ auraRef.audio.clearEnvelopes = typeof auraRef.audio.clearEnvelopes === 'function'
2332
+ ? auraRef.audio.clearEnvelopes
2333
+ : function () {
2334
+ throw createUnsupportedRuntimeError('audio', 'clearEnvelopes', 'web_audio_clear_envelopes_unsupported', { playbackSupported: false });
2335
+ };
2336
+ auraRef.audio.getMixerState = typeof auraRef.audio.getMixerState === 'function'
2337
+ ? auraRef.audio.getMixerState
2338
+ : function () {
2339
+ throw createUnsupportedRuntimeError('audio', 'getMixerState', 'web_audio_get_mixer_state_unsupported', { playbackSupported: false });
2340
+ };
2341
+ auraRef.audio.play3d = typeof auraRef.audio.play3d === 'function'
2342
+ ? auraRef.audio.play3d
2343
+ : function () {
2344
+ throw createUnsupportedRuntimeError('audio', 'play3d', 'web_audio_play3d_unsupported', { playbackSupported: false });
2345
+ };
2346
+ auraRef.audio.setListenerTransform = typeof auraRef.audio.setListenerTransform === 'function'
2347
+ ? auraRef.audio.setListenerTransform
2348
+ : function () {
2349
+ throw createUnsupportedRuntimeError('audio', 'setListenerTransform', 'web_audio_set_listener_transform_unsupported', { playbackSupported: false });
2350
+ };
2351
+ auraRef.audio.attachListener = typeof auraRef.audio.attachListener === 'function'
2352
+ ? auraRef.audio.attachListener
2353
+ : function () {
2354
+ throw createUnsupportedRuntimeError('audio', 'attachListener', 'web_audio_attach_listener_unsupported', { playbackSupported: false });
2355
+ };
2356
+ auraRef.audio.detachListener = typeof auraRef.audio.detachListener === 'function'
2357
+ ? auraRef.audio.detachListener
2358
+ : function () {
2359
+ throw createUnsupportedRuntimeError('audio', 'detachListener', 'web_audio_detach_listener_unsupported', { playbackSupported: false });
2360
+ };
2361
+ auraRef.audio.setEmitterTransform = typeof auraRef.audio.setEmitterTransform === 'function'
2362
+ ? auraRef.audio.setEmitterTransform
2363
+ : function () {
2364
+ throw createUnsupportedRuntimeError('audio', 'setEmitterTransform', 'web_audio_set_emitter_transform_unsupported', { playbackSupported: false });
2365
+ };
2366
+ auraRef.audio.attachEmitter = typeof auraRef.audio.attachEmitter === 'function'
2367
+ ? auraRef.audio.attachEmitter
2368
+ : function () {
2369
+ throw createUnsupportedRuntimeError('audio', 'attachEmitter', 'web_audio_attach_emitter_unsupported', { playbackSupported: false });
2370
+ };
2371
+ auraRef.audio.detachEmitter = typeof auraRef.audio.detachEmitter === 'function'
2372
+ ? auraRef.audio.detachEmitter
2373
+ : function () {
2374
+ throw createUnsupportedRuntimeError('audio', 'detachEmitter', 'web_audio_detach_emitter_unsupported', { playbackSupported: false });
2375
+ };
2376
+ auraRef.audio.updateSpatial = typeof auraRef.audio.updateSpatial === 'function'
2377
+ ? auraRef.audio.updateSpatial
2378
+ : function () {
2379
+ throw createUnsupportedRuntimeError('audio', 'updateSpatial', 'web_audio_update_spatial_unsupported', { playbackSupported: false });
2380
+ };
2381
+ auraRef.audio.getSpatialState = typeof auraRef.audio.getSpatialState === 'function'
2382
+ ? auraRef.audio.getSpatialState
2383
+ : function () {
2384
+ throw createUnsupportedRuntimeError('audio', 'getSpatialState', 'web_audio_get_spatial_state_unsupported', { playbackSupported: false });
2385
+ };
919
2386
 
920
2387
  auraRef.assets = auraRef.assets && typeof auraRef.assets === 'object' ? auraRef.assets : {};
921
2388
  auraRef.assets.load = typeof auraRef.assets.load === 'function'
922
2389
  ? auraRef.assets.load
923
- : async function () { return true; };
2390
+ : async function (source) {
2391
+ return await loadAssetRecord(source);
2392
+ };
924
2393
  auraRef.assets.exists = typeof auraRef.assets.exists === 'function'
925
2394
  ? auraRef.assets.exists
926
- : function () { return false; };
2395
+ : function (name) {
2396
+ return !!resolveAssetEntry(name);
2397
+ };
927
2398
  auraRef.assets.image = typeof auraRef.assets.image === 'function'
928
2399
  ? auraRef.assets.image
929
- : function (name) { return { kind: 'image', name: String(name || '') }; };
2400
+ : function (name) {
2401
+ const sourcePath = resolveAssetSourcePath(name);
2402
+ const loaded = resolveLoadedAsset(sourcePath);
2403
+ if (loaded) return loaded;
2404
+ const entry = resolveAssetEntry(sourcePath);
2405
+ return rememberLoadedAsset(sourcePath, {
2406
+ kind: 'image',
2407
+ path: sourcePath,
2408
+ sourcePath: sourcePath,
2409
+ resolvedPath: entry && typeof entry.path === 'string' ? normalizePath(entry.path) : sourcePath,
2410
+ mediaType: entry && typeof entry.mediaType === 'string' ? entry.mediaType : 'image/png',
2411
+ image: null,
2412
+ width: 0,
2413
+ height: 0
2414
+ });
2415
+ };
930
2416
  auraRef.assets.sound = typeof auraRef.assets.sound === 'function'
931
2417
  ? auraRef.assets.sound
932
- : function (name) { return { kind: 'sound', name: String(name || '') }; };
2418
+ : function (name) {
2419
+ const sourcePath = resolveAssetSourcePath(name);
2420
+ const loaded = resolveLoadedAsset(sourcePath);
2421
+ if (loaded) return loaded;
2422
+ const entry = resolveAssetEntry(sourcePath);
2423
+ return createBrowserSoundHandle(sourcePath, entry, null);
2424
+ };
933
2425
  auraRef.assets.text = typeof auraRef.assets.text === 'function'
934
2426
  ? auraRef.assets.text
935
- : function () { return ''; };
2427
+ : function (name) {
2428
+ const loaded = resolveLoadedAsset(name);
2429
+ return loaded && typeof loaded.text === 'string' ? loaded.text : '';
2430
+ };
936
2431
  auraRef.assets.json = typeof auraRef.assets.json === 'function'
937
2432
  ? auraRef.assets.json
938
- : function () { return {}; };
2433
+ : function (name) {
2434
+ const loaded = resolveLoadedAsset(name);
2435
+ return loaded && loaded.json && typeof loaded.json === 'object' ? loaded.json : {};
2436
+ };
939
2437
  auraRef.assets.bytes = typeof auraRef.assets.bytes === 'function'
940
2438
  ? auraRef.assets.bytes
941
- : function () { return new Uint8Array(); };
2439
+ : function (name) {
2440
+ const loaded = resolveLoadedAsset(name);
2441
+ return loaded && loaded.bytes instanceof Uint8Array ? loaded.bytes : new Uint8Array();
2442
+ };
2443
+ auraRef.assets.loadText = typeof auraRef.assets.loadText === 'function'
2444
+ ? auraRef.assets.loadText
2445
+ : async function (name) {
2446
+ const loaded = await loadAssetRecord(name);
2447
+ return loaded && typeof loaded.text === 'string' ? loaded.text : '';
2448
+ };
2449
+ auraRef.assets.loadJson = typeof auraRef.assets.loadJson === 'function'
2450
+ ? auraRef.assets.loadJson
2451
+ : async function (name) {
2452
+ const loaded = await loadAssetRecord(name);
2453
+ return loaded && loaded.json && typeof loaded.json === 'object' ? loaded.json : null;
2454
+ };
2455
+
2456
+ function drawResolvedImage(source, x, y, options, useSpriteFrame) {
2457
+ const ctx = ensureWorldTransform();
2458
+ if (!ctx || typeof ctx.drawImage !== 'function') return false;
2459
+ const handle = source && typeof source === 'object' && source.image
2460
+ ? source
2461
+ : auraRef.assets.image(source);
2462
+ if (!handle || !handle.image) return false;
2463
+ const opts = options && typeof options === 'object' ? { ...options } : {};
2464
+ const width = normalizePositiveNumber(opts.width, handle.width || handle.image.naturalWidth || handle.image.width || 1);
2465
+ const height = normalizePositiveNumber(opts.height, handle.height || handle.image.naturalHeight || handle.image.height || 1);
2466
+ const alpha = Number.isFinite(Number(opts.alpha)) ? Math.max(0, Math.min(1, Number(opts.alpha))) : 1;
2467
+ const frameX = useSpriteFrame ? Math.max(0, Number(opts.frameX) || 0) : 0;
2468
+ const frameY = useSpriteFrame ? Math.max(0, Number(opts.frameY) || 0) : 0;
2469
+ const frameW = useSpriteFrame
2470
+ ? normalizePositiveNumber(opts.frameW, handle.image.naturalWidth || handle.image.width || width)
2471
+ : (handle.image.naturalWidth || handle.image.width || width);
2472
+ const frameH = useSpriteFrame
2473
+ ? normalizePositiveNumber(opts.frameH, handle.image.naturalHeight || handle.image.height || height)
2474
+ : (handle.image.naturalHeight || handle.image.height || height);
2475
+ const drawX = Number(x) || 0;
2476
+ const drawY = Number(y) || 0;
2477
+ const flipX = opts.flipX === true;
2478
+ const flipY = opts.flipY === true;
2479
+ if (typeof ctx.save === 'function') ctx.save();
2480
+ ctx.globalAlpha = alpha;
2481
+ if (flipX || flipY) {
2482
+ if (typeof ctx.translate === 'function') {
2483
+ ctx.translate(drawX + (flipX ? width : 0), drawY + (flipY ? height : 0));
2484
+ }
2485
+ if (typeof ctx.scale === 'function') {
2486
+ ctx.scale(flipX ? -1 : 1, flipY ? -1 : 1);
2487
+ }
2488
+ ctx.drawImage(handle.image, frameX, frameY, frameW, frameH, 0, 0, width, height);
2489
+ } else {
2490
+ ctx.drawImage(handle.image, frameX, frameY, frameW, frameH, drawX, drawY, width, height);
2491
+ }
2492
+ if (typeof ctx.restore === 'function') ctx.restore();
2493
+ return true;
2494
+ }
942
2495
 
943
2496
  auraRef.draw2d = auraRef.draw2d && typeof auraRef.draw2d === 'object' ? auraRef.draw2d : {};
944
2497
  auraRef.draw2d.clear = function (colorOrR, g, b, a) {
@@ -951,9 +2504,10 @@ const WEB_LOADER_SOURCE = `
951
2504
  ctx.clearRect(0, 0, runtime.width, runtime.height);
952
2505
  ctx.fillStyle = colorToCss(fillColor, createUnitColor(0, 0, 0, 1));
953
2506
  ctx.fillRect(0, 0, runtime.width, runtime.height);
2507
+ applyWorldTransform();
954
2508
  };
955
2509
  auraRef.draw2d.rect = function (x, y, w, h, color) {
956
- const ctx = ensureCanvasContext();
2510
+ const ctx = ensureWorldTransform();
957
2511
  if (!ctx) return;
958
2512
  ctx.strokeStyle = colorToCss(color, defaultColor);
959
2513
  ctx.lineWidth = 1;
@@ -961,13 +2515,13 @@ const WEB_LOADER_SOURCE = `
961
2515
  };
962
2516
  auraRef.draw2d.rectOutline = auraRef.draw2d.rect;
963
2517
  auraRef.draw2d.rectFill = function (x, y, w, h, color) {
964
- const ctx = ensureCanvasContext();
2518
+ const ctx = ensureWorldTransform();
965
2519
  if (!ctx) return;
966
2520
  ctx.fillStyle = colorToCss(color, defaultColor);
967
2521
  ctx.fillRect(Number(x) || 0, Number(y) || 0, Number(w) || 0, Number(h) || 0);
968
2522
  };
969
2523
  auraRef.draw2d.circle = function (x, y, radius, color) {
970
- const ctx = ensureCanvasContext();
2524
+ const ctx = ensureWorldTransform();
971
2525
  if (!ctx) return;
972
2526
  ctx.beginPath();
973
2527
  ctx.arc(Number(x) || 0, Number(y) || 0, Math.max(0, Number(radius) || 0), 0, Math.PI * 2);
@@ -976,7 +2530,7 @@ const WEB_LOADER_SOURCE = `
976
2530
  ctx.stroke();
977
2531
  };
978
2532
  auraRef.draw2d.circleFill = function (x, y, radius, color) {
979
- const ctx = ensureCanvasContext();
2533
+ const ctx = ensureWorldTransform();
980
2534
  if (!ctx) return;
981
2535
  ctx.beginPath();
982
2536
  ctx.arc(Number(x) || 0, Number(y) || 0, Math.max(0, Number(radius) || 0), 0, Math.PI * 2);
@@ -984,7 +2538,7 @@ const WEB_LOADER_SOURCE = `
984
2538
  ctx.fill();
985
2539
  };
986
2540
  auraRef.draw2d.line = function (x1, y1, x2, y2, color, width) {
987
- const ctx = ensureCanvasContext();
2541
+ const ctx = ensureWorldTransform();
988
2542
  if (!ctx) return;
989
2543
  ctx.beginPath();
990
2544
  ctx.moveTo(Number(x1) || 0, Number(y1) || 0);
@@ -993,17 +2547,20 @@ const WEB_LOADER_SOURCE = `
993
2547
  ctx.lineWidth = normalizePositiveNumber(width, 1);
994
2548
  ctx.stroke();
995
2549
  };
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);
2550
+ auraRef.draw2d.text = function (text, x, y, sizeOrOptions, colorMaybe) {
2551
+ const options = normalizeTextOptions(sizeOrOptions, colorMaybe);
2552
+ return withScreenSpace(function (ctx) {
2553
+ const config = applyFont(options);
2554
+ const source = options && typeof options === 'object' ? options : {};
2555
+ ctx.fillStyle = colorToCss(source.color, defaultColor);
2556
+ ctx.textAlign = config.align;
2557
+ ctx.textBaseline = 'top';
2558
+ ctx.fillText(String(text == null ? '' : text), Number(x) || 0, Number(y) || 0);
2559
+ return true;
2560
+ });
1005
2561
  };
1006
- auraRef.draw2d.measureText = function (text, options) {
2562
+ auraRef.draw2d.measureText = function (text, sizeOrOptions, colorMaybe) {
2563
+ const options = normalizeTextOptions(sizeOrOptions, colorMaybe);
1007
2564
  const source = options && typeof options === 'object' ? options : {};
1008
2565
  const size = normalizePositiveNumber(source.size, 16);
1009
2566
  const ctx = ensureCanvasContext();
@@ -1011,21 +2568,27 @@ const WEB_LOADER_SOURCE = `
1011
2568
  const fallbackWidth = String(text == null ? '' : text).length * size * 0.6;
1012
2569
  return { width: Number(fallbackWidth.toFixed(3)), height: size };
1013
2570
  }
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 };
2571
+ const measuredWidth = withScreenSpace(function () {
2572
+ applyFont(options);
2573
+ const metrics = ctx.measureText(String(text == null ? '' : text));
2574
+ return Number.isFinite(metrics && metrics.width)
2575
+ ? metrics.width
2576
+ : String(text == null ? '' : text).length * size * 0.6;
2577
+ });
2578
+ return { width: Number(Number(measuredWidth || 0).toFixed(3)), height: size };
1020
2579
  };
1021
2580
  auraRef.draw2d.image = typeof auraRef.draw2d.image === 'function'
1022
2581
  ? auraRef.draw2d.image
1023
- : function () { return true; };
2582
+ : function (source, x, y, options) {
2583
+ return drawResolvedImage(source, x, y, options, false);
2584
+ };
1024
2585
  auraRef.draw2d.sprite = typeof auraRef.draw2d.sprite === 'function'
1025
2586
  ? auraRef.draw2d.sprite
1026
- : function () { return true; };
2587
+ : function (source, x, y, options) {
2588
+ return drawResolvedImage(source, x, y, options, true);
2589
+ };
1027
2590
  auraRef.draw2d.pushTransform = function () {
1028
- const ctx = ensureCanvasContext();
2591
+ const ctx = ensureWorldTransform();
1029
2592
  if (!ctx || typeof ctx.save !== 'function') return;
1030
2593
  runtime.transformDepth += 1;
1031
2594
  ctx.save();
@@ -1039,20 +2602,20 @@ const WEB_LOADER_SOURCE = `
1039
2602
  auraRef.draw2d.push = auraRef.draw2d.pushTransform;
1040
2603
  auraRef.draw2d.pop = auraRef.draw2d.popTransform;
1041
2604
  auraRef.draw2d.translate = function (x, y) {
1042
- const ctx = ensureCanvasContext();
2605
+ const ctx = ensureWorldTransform();
1043
2606
  if (!ctx || typeof ctx.translate !== 'function') return;
1044
2607
  ctx.translate(Number(x) || 0, Number(y) || 0);
1045
2608
  };
1046
2609
  auraRef.draw2d.rotate = function (angle) {
1047
- const ctx = ensureCanvasContext();
2610
+ const ctx = ensureWorldTransform();
1048
2611
  if (!ctx || typeof ctx.rotate !== 'function') return;
1049
2612
  ctx.rotate(Number(angle) || 0);
1050
2613
  };
1051
2614
  auraRef.draw2d.scale = function (x, y) {
1052
- const ctx = ensureCanvasContext();
2615
+ const ctx = ensureWorldTransform();
1053
2616
  if (!ctx || typeof ctx.scale !== 'function') return;
1054
- const scaleX = normalizePositiveNumber(x, 1);
1055
- const scaleY = normalizePositiveNumber(y, scaleX);
2617
+ const scaleX = Number.isFinite(Number(x)) ? Number(x) : 1;
2618
+ const scaleY = Number.isFinite(Number(y)) ? Number(y) : scaleX;
1056
2619
  ctx.scale(scaleX, scaleY);
1057
2620
  };
1058
2621
 
@@ -1068,6 +2631,9 @@ const WEB_LOADER_SOURCE = `
1068
2631
  runtime.resizeMode = currentCanvasConfig.resizeMode === 'fixed' ? 'fixed' : 'fit-container';
1069
2632
  syncCanvasSize(false);
1070
2633
  },
2634
+ setManifest(nextManifest, rootUrl) {
2635
+ indexManifestAssets(nextManifest, rootUrl);
2636
+ },
1071
2637
  mount(canvas, mountTarget) {
1072
2638
  runtime.canvas = canvas || runtime.canvas;
1073
2639
  runtime.mountTarget = mountTarget || runtime.mountTarget;
@@ -1075,14 +2641,15 @@ const WEB_LOADER_SOURCE = `
1075
2641
  syncCanvasSize(false);
1076
2642
  resetDrawState();
1077
2643
  attachListeners();
1078
- inputState.down.clear();
1079
- inputState.pendingPressed.clear();
1080
- inputState.pendingReleased.clear();
1081
- inputState.framePressed.clear();
1082
- inputState.frameReleased.clear();
2644
+ clearHeldInputState();
2645
+ auraRef.input.mouse.scroll = 0;
2646
+ applyCursorAppearance();
1083
2647
  if (runtime.canvas && typeof runtime.canvas.focus === 'function') {
1084
2648
  runtime.canvas.focus();
1085
2649
  }
2650
+ if (typeof auraRef.onFocus === 'function') {
2651
+ auraRef.onFocus();
2652
+ }
1086
2653
  return true;
1087
2654
  },
1088
2655
  beginFrame() {
@@ -1090,6 +2657,14 @@ const WEB_LOADER_SOURCE = `
1090
2657
  inputState.frameReleased = new Set(inputState.pendingReleased);
1091
2658
  inputState.pendingPressed.clear();
1092
2659
  inputState.pendingReleased.clear();
2660
+ inputState.frameMousePressed = new Set(inputState.pendingMousePressed);
2661
+ inputState.frameMouseReleased = new Set(inputState.pendingMouseReleased);
2662
+ inputState.pendingMousePressed.clear();
2663
+ inputState.pendingMouseReleased.clear();
2664
+ inputState.frameMouseDeltaX = inputState.pendingMouseDeltaX;
2665
+ inputState.frameMouseDeltaY = inputState.pendingMouseDeltaY;
2666
+ inputState.pendingMouseDeltaX = 0;
2667
+ inputState.pendingMouseDeltaY = 0;
1093
2668
  resetDrawState();
1094
2669
  },
1095
2670
  endFrame() {
@@ -1103,15 +2678,17 @@ const WEB_LOADER_SOURCE = `
1103
2678
  },
1104
2679
  unmount() {
1105
2680
  detachListeners();
1106
- inputState.down.clear();
1107
- inputState.pendingPressed.clear();
1108
- inputState.pendingReleased.clear();
1109
- inputState.framePressed.clear();
1110
- inputState.frameReleased.clear();
2681
+ clearHeldInputState();
2682
+ runtime.cursorLocked = false;
2683
+ if (globalRef.document && typeof globalRef.document.exitPointerLock === 'function') {
2684
+ globalRef.document.exitPointerLock();
2685
+ }
2686
+ applyCursorAppearance();
1111
2687
  runtime.mountTarget = null;
1112
2688
  runtime.canvas = null;
1113
2689
  runtime.context2d = null;
1114
2690
  runtime.transformDepth = 0;
2691
+ runtime.worldTransformActive = false;
1115
2692
  return true;
1116
2693
  }
1117
2694
  };
@@ -1293,6 +2870,7 @@ const WEB_LOADER_SOURCE = `
1293
2870
  const rootUrl = typeof opts.rootUrl === 'string' && opts.rootUrl.length > 0
1294
2871
  ? opts.rootUrl.replace(/\\/$/, '')
1295
2872
  : '.';
2873
+ cachedRootUrl = rootUrl;
1296
2874
  setState('loading', null, null);
1297
2875
 
1298
2876
  cachedManifest = await readJson(rootUrl + '/web-build-manifest.json', 'web_manifest_missing', 'web_manifest_parse_failed');
@@ -1305,6 +2883,9 @@ const WEB_LOADER_SOURCE = `
1305
2883
  } else if (typeof auraRuntime.setRuntimeConfig === 'function') {
1306
2884
  auraRuntime.setRuntimeConfig(cachedRuntimeConfig);
1307
2885
  }
2886
+ if (auraRuntime && typeof auraRuntime.setManifest === 'function') {
2887
+ auraRuntime.setManifest(cachedManifest, cachedRootUrl);
2888
+ }
1308
2889
 
1309
2890
  const bundleEntry = normalizePath(cachedManifest.entrypoints.bundle);
1310
2891
  if (bundleEntry.length === 0) {
@@ -1350,6 +2931,13 @@ const WEB_LOADER_SOURCE = `
1350
2931
  if (!auraRuntime) {
1351
2932
  auraRuntime = createBrowserAuraSurface(cachedRuntimeConfig || {});
1352
2933
  }
2934
+ if (auraRuntime && typeof auraRuntime.setManifest === 'function') {
2935
+ auraRuntime.setManifest(cachedManifest || {}, cachedRootUrl);
2936
+ }
2937
+ ensureCapabilityDeclarationSatisfied(
2938
+ cachedRuntimeConfig || {},
2939
+ auraRuntime && auraRuntime.aura ? auraRuntime.aura : globalRef.aura,
2940
+ );
1353
2941
  if (typeof auraRuntime.mount === 'function') {
1354
2942
  auraRuntime.mount(canvas, targetNode);
1355
2943
  }