@peekling/runtime 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/AUTHORS +8 -0
  2. package/LICENSE +202 -0
  3. package/LICENSING.md +20 -0
  4. package/NOTICE +6 -0
  5. package/README.md +294 -0
  6. package/dist/configuration-validation.d.ts +19 -0
  7. package/dist/configuration-validation.d.ts.map +1 -0
  8. package/dist/configuration-validation.js +298 -0
  9. package/dist/content.d.ts +44 -0
  10. package/dist/content.d.ts.map +1 -0
  11. package/dist/content.js +738 -0
  12. package/dist/contracts.d.ts +10 -0
  13. package/dist/contracts.d.ts.map +1 -0
  14. package/dist/contracts.js +106 -0
  15. package/dist/defaults.d.ts +7 -0
  16. package/dist/defaults.d.ts.map +1 -0
  17. package/dist/defaults.js +6 -0
  18. package/dist/diagnostics.d.ts +25 -0
  19. package/dist/diagnostics.d.ts.map +1 -0
  20. package/dist/diagnostics.js +122 -0
  21. package/dist/direction.d.ts +3 -0
  22. package/dist/direction.d.ts.map +1 -0
  23. package/dist/direction.js +6 -0
  24. package/dist/errors.d.ts +15 -0
  25. package/dist/errors.d.ts.map +1 -0
  26. package/dist/errors.js +53 -0
  27. package/dist/events.d.ts +5 -0
  28. package/dist/events.d.ts.map +1 -0
  29. package/dist/events.js +60 -0
  30. package/dist/index.d.ts +6 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +3 -0
  33. package/dist/input.d.ts +31 -0
  34. package/dist/input.d.ts.map +1 -0
  35. package/dist/input.js +215 -0
  36. package/dist/json.d.ts +3 -0
  37. package/dist/json.d.ts.map +1 -0
  38. package/dist/json.js +109 -0
  39. package/dist/loader.d.ts +33 -0
  40. package/dist/loader.d.ts.map +1 -0
  41. package/dist/loader.js +477 -0
  42. package/dist/locomotion.d.ts +8 -0
  43. package/dist/locomotion.d.ts.map +1 -0
  44. package/dist/locomotion.js +29 -0
  45. package/dist/normalized.d.ts +5 -0
  46. package/dist/normalized.d.ts.map +1 -0
  47. package/dist/normalized.js +266 -0
  48. package/dist/overrides.d.ts +29 -0
  49. package/dist/overrides.d.ts.map +1 -0
  50. package/dist/overrides.js +296 -0
  51. package/dist/own-data.d.ts +18 -0
  52. package/dist/own-data.d.ts.map +1 -0
  53. package/dist/own-data.js +205 -0
  54. package/dist/pack-api.d.ts +9 -0
  55. package/dist/pack-api.d.ts.map +1 -0
  56. package/dist/pack-api.js +6 -0
  57. package/dist/pack-shared.d.ts +28 -0
  58. package/dist/pack-shared.d.ts.map +1 -0
  59. package/dist/pack-shared.js +408 -0
  60. package/dist/pack.d.ts +6 -0
  61. package/dist/pack.d.ts.map +1 -0
  62. package/dist/pack.js +316 -0
  63. package/dist/peekling.css +1 -0
  64. package/dist/peekling.css.sri +1 -0
  65. package/dist/peekling.js +7469 -0
  66. package/dist/peekling.js.sri +1 -0
  67. package/dist/peekling.min.js +2 -0
  68. package/dist/peekling.min.js.sri +1 -0
  69. package/dist/plan-compiler.d.ts +82 -0
  70. package/dist/plan-compiler.d.ts.map +1 -0
  71. package/dist/plan-compiler.js +724 -0
  72. package/dist/plan.d.ts +21 -0
  73. package/dist/plan.d.ts.map +1 -0
  74. package/dist/plan.js +287 -0
  75. package/dist/preflight-api.d.ts +3 -0
  76. package/dist/preflight-api.d.ts.map +1 -0
  77. package/dist/preflight-api.js +1 -0
  78. package/dist/preflight.d.ts +22 -0
  79. package/dist/preflight.d.ts.map +1 -0
  80. package/dist/preflight.js +158 -0
  81. package/dist/registry.d.ts +9 -0
  82. package/dist/registry.d.ts.map +1 -0
  83. package/dist/registry.js +24 -0
  84. package/dist/renderer.d.ts +16 -0
  85. package/dist/renderer.d.ts.map +1 -0
  86. package/dist/renderer.js +176 -0
  87. package/dist/resolver.d.ts +3 -0
  88. package/dist/resolver.d.ts.map +1 -0
  89. package/dist/resolver.js +30 -0
  90. package/dist/runtime-diagnostics.d.ts +3 -0
  91. package/dist/runtime-diagnostics.d.ts.map +1 -0
  92. package/dist/runtime-diagnostics.js +5 -0
  93. package/dist/runtime-validation.d.ts +6 -0
  94. package/dist/runtime-validation.d.ts.map +1 -0
  95. package/dist/runtime-validation.js +104 -0
  96. package/dist/runtime.d.ts +87 -0
  97. package/dist/runtime.d.ts.map +1 -0
  98. package/dist/runtime.js +1015 -0
  99. package/dist/sections.d.ts +15 -0
  100. package/dist/sections.d.ts.map +1 -0
  101. package/dist/sections.js +221 -0
  102. package/dist/styles.d.ts +18 -0
  103. package/dist/styles.d.ts.map +1 -0
  104. package/dist/styles.js +132 -0
  105. package/dist/tooling-preflight.d.ts +23 -0
  106. package/dist/tooling-preflight.d.ts.map +1 -0
  107. package/dist/tooling-preflight.js +207 -0
  108. package/dist/tooling-validation.d.ts +8 -0
  109. package/dist/tooling-validation.d.ts.map +1 -0
  110. package/dist/tooling-validation.js +94 -0
  111. package/dist/types.d.ts +403 -0
  112. package/dist/types.d.ts.map +1 -0
  113. package/dist/types.js +29 -0
  114. package/dist/visibility.d.ts +12 -0
  115. package/dist/visibility.d.ts.map +1 -0
  116. package/dist/visibility.js +333 -0
  117. package/dist/web-component.d.ts +31 -0
  118. package/dist/web-component.d.ts.map +1 -0
  119. package/dist/web-component.js +236 -0
  120. package/package.json +74 -0
package/dist/loader.js ADDED
@@ -0,0 +1,477 @@
1
+ import { packIssue, PackLoadError, PackValidationError, throwPackIssue, } from "./errors.js";
2
+ import { isNormalizedPack, validateNormalizedPack } from "./normalized.js";
3
+ import { parseManifestText, selectNativeDensity, validateNativePack, } from "./pack.js";
4
+ import { inspectImageStructure, SHA256_PATTERN } from "./pack-shared.js";
5
+ import { characterPackReference } from "./registry.js";
6
+ import { OwnDataError, snapshotPackData } from "./own-data.js";
7
+ import { compactRuntimeDiagnostics, runtimeMessage, } from "./runtime-diagnostics.js";
8
+ import { isHttpPath } from "./contracts.js";
9
+ const DEFAULT_RESOURCE_TIMEOUT_MS = 30_000;
10
+ export function resolveManifestUrl(input, base) {
11
+ if (!isHttpPath(input, base))
12
+ throwPackIssue("url.protocol", "Relative path or HTTPS required");
13
+ try {
14
+ return new URL(input, base).href;
15
+ }
16
+ catch {
17
+ throwPackIssue("url.invalid", "Invalid pack URL");
18
+ }
19
+ }
20
+ function requireMime(response, allowed) {
21
+ const mime = response.headers
22
+ .get("content-type")
23
+ ?.split(";", 1)[0]
24
+ ?.trim()
25
+ .toLowerCase();
26
+ if (!mime || !allowed.includes(mime)) {
27
+ throwPackIssue("mime.invalid", "Invalid Content-Type");
28
+ }
29
+ return mime;
30
+ }
31
+ function failureCategory(error) {
32
+ if (error instanceof DOMException && error.name === "AbortError") {
33
+ return "abort";
34
+ }
35
+ if (error instanceof PackLoadError)
36
+ return error.category;
37
+ if (error instanceof PackValidationError) {
38
+ for (const code of error.issueCodes) {
39
+ const suffix = code.slice(code.indexOf(".") + 1);
40
+ if (suffix === "hash")
41
+ return "hash";
42
+ if (code === "mime.invalid" || suffix === "mime")
43
+ return "mime";
44
+ if (code.startsWith("png.") ||
45
+ code.startsWith("webp.") ||
46
+ (code.startsWith("atlas.") &&
47
+ ["dimensions", "pixels", "alpha", "size"].includes(suffix))) {
48
+ return "image";
49
+ }
50
+ if (suffix === "size")
51
+ return "size";
52
+ if (suffix.startsWith("integrity-"))
53
+ return "integrity";
54
+ }
55
+ }
56
+ return "validation";
57
+ }
58
+ function isAbortFailure(error, signal) {
59
+ return (signal.aborted ||
60
+ (error instanceof DOMException && error.name === "AbortError"));
61
+ }
62
+ const packLoadMessage = compactRuntimeDiagnostics
63
+ ? (code) => code
64
+ : (code, value) => {
65
+ switch (code) {
66
+ case "manifest.network":
67
+ return value === 1
68
+ ? "Manifest response failed"
69
+ : "Manifest network request failed";
70
+ case "atlas.network":
71
+ return value === 1
72
+ ? "Atlas response failed"
73
+ : "Atlas network request failed";
74
+ case "manifest.timeout":
75
+ return "Manifest request timed out";
76
+ case "atlas.timeout":
77
+ return "Atlas request timed out";
78
+ case "manifest.http":
79
+ return `Manifest HTTP ${value}`;
80
+ case "atlas.http":
81
+ return `Atlas HTTP ${value}`;
82
+ case "atlas.decode":
83
+ return "Atlas decode failed";
84
+ case "atlas.unusable":
85
+ return "No usable atlas";
86
+ default:
87
+ return code;
88
+ }
89
+ };
90
+ function resourceDeadline(parent, timeoutMs, timeoutCode) {
91
+ const controller = new AbortController();
92
+ let timedOut = false;
93
+ const abort = () => controller.abort(parent.reason);
94
+ if (parent.aborted)
95
+ abort();
96
+ else
97
+ parent.addEventListener("abort", abort, { once: true });
98
+ const timer = globalThis.setTimeout(() => {
99
+ if (controller.signal.aborted)
100
+ return;
101
+ timedOut = true;
102
+ controller.abort(new DOMException(packLoadMessage(timeoutCode), "TimeoutError"));
103
+ }, timeoutMs);
104
+ return {
105
+ signal: controller.signal,
106
+ get timedOut() {
107
+ return timedOut;
108
+ },
109
+ close() {
110
+ globalThis.clearTimeout(timer);
111
+ parent.removeEventListener("abort", abort);
112
+ },
113
+ };
114
+ }
115
+ async function withResourceDeadline(parent, timeoutMs, timeoutCode, operation) {
116
+ const deadline = resourceDeadline(parent, timeoutMs, timeoutCode);
117
+ try {
118
+ return await operation(deadline.signal);
119
+ }
120
+ catch (error) {
121
+ if (deadline.timedOut &&
122
+ (error === deadline.signal.reason ||
123
+ (error instanceof DOMException &&
124
+ (error.name === "AbortError" || error.name === "TimeoutError")))) {
125
+ throw new PackLoadError(timeoutCode, "timeout", packLoadMessage(timeoutCode), error);
126
+ }
127
+ throw error;
128
+ }
129
+ finally {
130
+ deadline.close();
131
+ }
132
+ }
133
+ async function fetchResource(fetcher, url, signal, code) {
134
+ try {
135
+ return await fetcher(url, { signal });
136
+ }
137
+ catch (error) {
138
+ if (isAbortFailure(error, signal))
139
+ throw error;
140
+ throw new PackLoadError(code, "network", packLoadMessage(code), error);
141
+ }
142
+ }
143
+ async function readResponseBytes(response, limit, signal, code) {
144
+ try {
145
+ return await responseBytes(response, limit, signal);
146
+ }
147
+ catch (error) {
148
+ if (isAbortFailure(error, signal) ||
149
+ error instanceof PackLoadError ||
150
+ error instanceof PackValidationError) {
151
+ throw error;
152
+ }
153
+ throw new PackLoadError(code, "network", packLoadMessage(code, 1), error);
154
+ }
155
+ }
156
+ function inspectAtlasBytes(bytes, mime, normalized, expected) {
157
+ const geometry = inspectImageStructure(bytes, mime);
158
+ if (geometry.width !== expected.width ||
159
+ geometry.height !== expected.height) {
160
+ throwPackIssue("atlas.dimensions", `Atlas must be exactly ${expected.width}x${expected.height}`);
161
+ }
162
+ if (!normalized && !geometry.hasAlpha) {
163
+ throwPackIssue("atlas.alpha", "Native PNG atlas requires alpha");
164
+ }
165
+ }
166
+ async function matchesSha256(bytes, expected, manifest = false) {
167
+ const subject = manifest ? "Selected character manifest" : "Atlas";
168
+ const codePrefix = manifest ? "manifest" : "atlas";
169
+ if (!SHA256_PATTERN.test(expected)) {
170
+ throwPackIssue(`${codePrefix}.integrity-declaration`, `${subject} SHA-256 declaration is invalid`);
171
+ }
172
+ let digest;
173
+ try {
174
+ const subtle = globalThis.crypto?.subtle;
175
+ digest = subtle?.digest.bind(subtle);
176
+ }
177
+ catch { }
178
+ if (!digest) {
179
+ throwPackIssue(`${codePrefix}.integrity-unavailable`, `${subject} SHA-256 integrity verification is unavailable`);
180
+ }
181
+ try {
182
+ const computed = new Uint8Array(await digest("SHA-256", bytes));
183
+ return !computed.some((byte, index) => byte !== Number.parseInt(expected.slice(index * 2, index * 2 + 2), 16));
184
+ }
185
+ catch {
186
+ throwPackIssue(`${codePrefix}.integrity-failed`, `${subject} SHA-256 integrity verification failed`);
187
+ }
188
+ }
189
+ async function imageGeometry(image, objectUrl, signal) {
190
+ await new Promise((resolve, reject) => {
191
+ const cleanup = () => {
192
+ image.removeEventListener("load", load);
193
+ image.removeEventListener("error", error);
194
+ signal.removeEventListener("abort", abort);
195
+ };
196
+ const load = () => {
197
+ cleanup();
198
+ resolve();
199
+ };
200
+ const error = () => {
201
+ cleanup();
202
+ reject(new PackLoadError("atlas.decode", "decode", packLoadMessage("atlas.decode")));
203
+ };
204
+ const abort = () => {
205
+ cleanup();
206
+ image.removeAttribute("src");
207
+ reject(signal.reason);
208
+ };
209
+ image.addEventListener("load", load, { once: true });
210
+ image.addEventListener("error", error, { once: true });
211
+ signal.addEventListener("abort", abort, { once: true });
212
+ if (signal.aborted)
213
+ return abort();
214
+ image.src = objectUrl;
215
+ });
216
+ return { width: image.naturalWidth, height: image.naturalHeight };
217
+ }
218
+ async function responseBytes(response, limit, signal) {
219
+ const declared = Number(response.headers.get("content-length"));
220
+ if (Number.isFinite(declared) && declared > limit)
221
+ throwPackIssue("resource.size", "Resource too large");
222
+ if (!response.body) {
223
+ const value = new Uint8Array(await response.arrayBuffer());
224
+ if (value.byteLength > limit)
225
+ throwPackIssue("resource.size", "Resource too large");
226
+ return value;
227
+ }
228
+ const reader = response.body.getReader();
229
+ const chunks = [];
230
+ let size = 0;
231
+ try {
232
+ while (true) {
233
+ signal.throwIfAborted();
234
+ const { done, value } = await reader.read();
235
+ if (done)
236
+ break;
237
+ size += value.byteLength;
238
+ if (size > limit) {
239
+ await reader.cancel();
240
+ throwPackIssue("resource.size", "Resource too large");
241
+ }
242
+ chunks.push(value);
243
+ }
244
+ }
245
+ catch (error) {
246
+ try {
247
+ await reader.cancel(error);
248
+ }
249
+ catch {
250
+ // The original transport or abort failure is the useful diagnostic.
251
+ }
252
+ throw error;
253
+ }
254
+ finally {
255
+ reader.releaseLock();
256
+ }
257
+ const output = new Uint8Array(size);
258
+ let offset = 0;
259
+ for (const chunk of chunks) {
260
+ output.set(chunk, offset);
261
+ offset += chunk.byteLength;
262
+ }
263
+ return output;
264
+ }
265
+ export function requiredAtlasDensity(devicePixelRatio, renderScale, maxDensity = 4) {
266
+ const dpr = Number.isFinite(devicePixelRatio) && devicePixelRatio > 0
267
+ ? devicePixelRatio
268
+ : 1;
269
+ return Math.min(dpr * renderScale, maxDensity);
270
+ }
271
+ export async function loadNativePack(options) {
272
+ const resourceTimeoutMs = options.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;
273
+ if (options.pack === undefined &&
274
+ options.packUrl === undefined &&
275
+ options.character === undefined) {
276
+ throwPackIssue("manifest.selection", "Select an explicit character, packUrl, or pack");
277
+ }
278
+ let raw;
279
+ let manifestUrl = options.packUrl;
280
+ let manifestSha256;
281
+ if (options.pack !== undefined)
282
+ raw = options.pack;
283
+ else {
284
+ if (manifestUrl === undefined) {
285
+ const reference = characterPackReference(options.character);
286
+ manifestUrl = reference.url;
287
+ manifestSha256 = reference.sha256;
288
+ }
289
+ const requestedManifestUrl = resolveManifestUrl(manifestUrl, options.baseUrl);
290
+ manifestUrl = requestedManifestUrl;
291
+ const bytes = await withResourceDeadline(options.signal, resourceTimeoutMs, "manifest.timeout", async (signal) => {
292
+ const response = await fetchResource(options.fetch, requestedManifestUrl, signal, "manifest.network");
293
+ if (!response.ok)
294
+ throw new PackLoadError("manifest.http", "http", packLoadMessage("manifest.http", response.status));
295
+ requireMime(response, ["application/json"]);
296
+ return readResponseBytes(response, 64 * 1024, signal, "manifest.network");
297
+ });
298
+ if (manifestSha256 && !(await matchesSha256(bytes, manifestSha256, true))) {
299
+ throwPackIssue("manifest.hash", "Registered Peek manifest hash mismatch");
300
+ }
301
+ try {
302
+ raw = parseManifestText(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
303
+ }
304
+ catch (error) {
305
+ if (error instanceof PackValidationError)
306
+ throw error;
307
+ throwPackIssue("manifest.utf8", "Invalid manifest UTF-8");
308
+ }
309
+ }
310
+ try {
311
+ raw = snapshotPackData(raw);
312
+ }
313
+ catch (error) {
314
+ if (error instanceof OwnDataError) {
315
+ throw new PackValidationError([error.message], error.issueCode ? [error.issueCode] : []);
316
+ }
317
+ throw error;
318
+ }
319
+ const normalized = isNormalizedPack(raw);
320
+ const validate = normalized ? validateNormalizedPack : validateNativePack;
321
+ const first = validate(raw);
322
+ const preferred = options.densityOverride ??
323
+ requiredAtlasDensity(options.devicePixelRatio ?? 1, options.renderScale ?? first.defaultScale, options.maxDensity);
324
+ let selected = validate(raw, undefined, preferred);
325
+ let released = false;
326
+ let currentObjectUrl = "";
327
+ const resolveUrl = (src) => options.atlasUrl ??
328
+ (manifestUrl
329
+ ? new URL(src, new URL(manifestUrl, options.baseUrl)).href
330
+ : undefined);
331
+ const candidates = (required) => {
332
+ const variants = first.atlas.variants;
333
+ if (!variants?.length) {
334
+ const density = selected.atlas.density ?? 1;
335
+ if (options.densityOverride !== undefined &&
336
+ options.densityOverride !== density) {
337
+ throwPackIssue("atlas.override-density", "density must match the single atlas density");
338
+ }
339
+ if (density > (options.maxDensity ?? 4)) {
340
+ throwPackIssue("atlas.max-density", "Single atlas density exceeds maxDensity");
341
+ }
342
+ if (!selected.atlas.sha256) {
343
+ throwPackIssue("atlas.integrity-declaration", "Atlas SHA-256 declaration is missing");
344
+ }
345
+ return [
346
+ {
347
+ src: selected.atlas.src,
348
+ density,
349
+ sha256: selected.atlas.sha256,
350
+ },
351
+ ];
352
+ }
353
+ const available = variants.filter((item) => item.density <= (options.maxDensity ?? 4));
354
+ if (!available.length)
355
+ throwPackIssue("atlas.max-density", "No atlas variant at or below maxDensity");
356
+ if (options.atlasUrl !== undefined) {
357
+ if (options.densityOverride === undefined) {
358
+ throwPackIssue("atlas.override-density", "atlasUrl with variants requires an explicit density");
359
+ }
360
+ const overridden = available.find((item) => item.density === options.densityOverride);
361
+ if (!overridden) {
362
+ throwPackIssue("atlas.override-density", "atlasUrl density must name a declared variant at or below maxDensity");
363
+ }
364
+ return [overridden];
365
+ }
366
+ const target = selectNativeDensity(available.map((item) => item.density), required);
367
+ return available
368
+ .filter((item) => item.density <= target)
369
+ .sort((a, b) => b.density - a.density);
370
+ };
371
+ const fetchCandidate = async (candidate) => {
372
+ const assetUrl = resolveUrl(candidate.src);
373
+ if (!assetUrl)
374
+ throwPackIssue("atlas.url", "atlasUrl required");
375
+ const [mime, bytes] = await withResourceDeadline(options.signal, resourceTimeoutMs, "atlas.timeout", async (signal) => {
376
+ const response = await fetchResource(options.fetch, assetUrl, signal, "atlas.network");
377
+ if (!response.ok)
378
+ throw new PackLoadError("atlas.http", "http", packLoadMessage("atlas.http", response.status));
379
+ const mime = requireMime(response, normalized ? ["image/png", "image/webp"] : ["image/png"]);
380
+ const bytes = await readResponseBytes(response, normalized ? 32 * 1024 * 1024 : 4 * 1024 * 1024, signal, "atlas.network");
381
+ return [mime, bytes];
382
+ });
383
+ if (!(await matchesSha256(bytes, candidate.sha256))) {
384
+ throwPackIssue("atlas.hash", "Atlas hash mismatch");
385
+ }
386
+ const candidatePack = validate(raw, undefined, candidate.density);
387
+ inspectAtlasBytes(bytes, mime, normalized, {
388
+ width: candidatePack.atlas.columns * candidatePack.atlas.cellWidth,
389
+ height: candidatePack.atlas.rows * candidatePack.atlas.cellHeight,
390
+ });
391
+ const objectUrl = options.createObjectURL(new Blob([bytes]));
392
+ try {
393
+ const geometry = await imageGeometry(options.image(), objectUrl, options.signal);
394
+ const pack = validate(raw, { ...geometry, byteLength: bytes.byteLength }, candidate.density);
395
+ return { pack, objectUrl };
396
+ }
397
+ catch (error) {
398
+ options.revokeObjectURL(objectUrl);
399
+ throw error;
400
+ }
401
+ };
402
+ const loadWithFallback = async (required, upgrading) => {
403
+ let lastError;
404
+ const available = candidates(required);
405
+ for (const [index, candidate] of available.entries()) {
406
+ if (upgrading && candidate.density <= (selected.atlas.density ?? 1))
407
+ return undefined;
408
+ try {
409
+ return await fetchCandidate(candidate);
410
+ }
411
+ catch (error) {
412
+ if (options.signal.aborted) {
413
+ options.diagnostic?.(runtimeMessage("atlas.abort", "Atlas load aborted."), "abort");
414
+ throw error;
415
+ }
416
+ lastError = error;
417
+ const category = failureCategory(error);
418
+ const fallback = index < available.length - 1;
419
+ options.diagnostic?.(runtimeMessage(`atlas.${category}.${fallback ? "fallback" : "exhausted"}`, () => `Atlas ${candidate.density}x failed (${category}); ${fallback ? "falling back" : "no fallback remains"}.`), category);
420
+ }
421
+ }
422
+ if (upgrading)
423
+ return undefined;
424
+ throw (lastError ??
425
+ new PackLoadError("atlas.unusable", "validation", packLoadMessage("atlas.unusable")));
426
+ };
427
+ const initial = await loadWithFallback(preferred, false);
428
+ if (!initial)
429
+ throw new PackLoadError("atlas.unusable", "validation", packLoadMessage("atlas.unusable"));
430
+ selected = initial.pack;
431
+ currentObjectUrl = initial.objectUrl;
432
+ let desiredDensity = selected.atlas.density ?? 1;
433
+ let upgradeGeneration = 0;
434
+ const loaded = {
435
+ get content() {
436
+ return selected;
437
+ },
438
+ get atlasObjectUrl() {
439
+ return currentObjectUrl;
440
+ },
441
+ get loadedDensity() {
442
+ return selected.atlas.density ?? 1;
443
+ },
444
+ async upgrade(requiredDensity) {
445
+ if (released ||
446
+ options.atlasUrl !== undefined ||
447
+ requiredDensity <= loaded.loadedDensity)
448
+ return false;
449
+ desiredDensity = Math.max(desiredDensity, requiredDensity);
450
+ const generation = ++upgradeGeneration;
451
+ const next = await loadWithFallback(desiredDensity, true);
452
+ const nextDensity = next?.pack.atlas.density ?? 1;
453
+ if (!next ||
454
+ released ||
455
+ nextDensity <= loaded.loadedDensity ||
456
+ (generation !== upgradeGeneration && nextDensity < desiredDensity)) {
457
+ if (next)
458
+ options.revokeObjectURL(next.objectUrl);
459
+ return false;
460
+ }
461
+ const previous = currentObjectUrl;
462
+ selected = next.pack;
463
+ currentObjectUrl = next.objectUrl;
464
+ options.revokeObjectURL(previous);
465
+ return true;
466
+ },
467
+ release() {
468
+ if (released)
469
+ return;
470
+ released = true;
471
+ if (currentObjectUrl)
472
+ options.revokeObjectURL(currentObjectUrl);
473
+ currentObjectUrl = "";
474
+ },
475
+ };
476
+ return loaded;
477
+ }
@@ -0,0 +1,8 @@
1
+ import type { LocomotionKeyframe, StateDefinition } from "./types.js";
2
+ export declare function animationCycleMs(state: StateDefinition): number;
3
+ export declare function locomotionSample(keyframes: readonly Readonly<LocomotionKeyframe>[] | undefined, phase: number): {
4
+ advance: number;
5
+ lift: number;
6
+ };
7
+ export declare function locomotionAdvance(keyframes: readonly Readonly<LocomotionKeyframe>[] | undefined, fromCycles: number, toCycles: number): number;
8
+ //# sourceMappingURL=locomotion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locomotion.d.ts","sourceRoot":"","sources":["../src/locomotion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEtE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAI/D;AAED,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,SAAS,QAAQ,CAAC,kBAAkB,CAAC,EAAE,GAAG,SAAS,EAC9D,KAAK,EAAE,MAAM,GACZ;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAgBnC;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,SAAS,QAAQ,CAAC,kBAAkB,CAAC,EAAE,GAAG,SAAS,EAC9D,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,MAAM,CAUR"}
@@ -0,0 +1,29 @@
1
+ export function animationCycleMs(state) {
2
+ if (state.durations)
3
+ return state.durations.reduce((sum, value) => sum + value, 0);
4
+ return (state.frames.length / (state.fps ?? 1)) * 1000;
5
+ }
6
+ export function locomotionSample(keyframes, phase) {
7
+ const normalized = ((phase % 1) + 1) % 1;
8
+ if (!keyframes?.length)
9
+ return { advance: normalized, lift: 0 };
10
+ const nextIndex = keyframes.findIndex((item) => item.at >= normalized);
11
+ const next = keyframes[nextIndex < 0 ? keyframes.length - 1 : nextIndex];
12
+ const previous = keyframes[Math.max(0, (nextIndex < 0 ? keyframes.length - 1 : nextIndex) - 1)];
13
+ if (next.at === previous.at)
14
+ return { advance: next.advance, lift: next.lift };
15
+ const amount = (normalized - previous.at) / (next.at - previous.at);
16
+ return {
17
+ advance: previous.advance + (next.advance - previous.advance) * amount,
18
+ lift: previous.lift + (next.lift - previous.lift) * amount,
19
+ };
20
+ }
21
+ export function locomotionAdvance(keyframes, fromCycles, toCycles) {
22
+ const whole = Math.floor(toCycles) - Math.floor(fromCycles);
23
+ const cycleAdvance = keyframes?.length
24
+ ? keyframes.at(-1).advance - keyframes[0].advance
25
+ : 1;
26
+ return (whole * cycleAdvance +
27
+ locomotionSample(keyframes, toCycles).advance -
28
+ locomotionSample(keyframes, fromCycles).advance);
29
+ }
@@ -0,0 +1,5 @@
1
+ import { type AtlasGeometry, type NormalizedPack } from "./types.js";
2
+ export declare function isNormalizedPack(input: unknown): input is NormalizedPack;
3
+ export declare function validateNormalizedPack(input: unknown, geometry?: AtlasGeometry, preferredDensity?: number): NormalizedPack;
4
+ export declare function resolveCapabilityName(requested: string | undefined, pack: NormalizedPack): string | undefined;
5
+ //# sourceMappingURL=normalized.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalized.d.ts","sourceRoot":"","sources":["../src/normalized.ts"],"names":[],"mappings":"AAiBA,OAAO,EAEL,KAAK,aAAa,EAElB,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AAepB,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAKxE;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,EACd,QAAQ,CAAC,EAAE,aAAa,EACxB,gBAAgB,CAAC,EAAE,MAAM,GACxB,cAAc,CA+WhB;AAED,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,IAAI,EAAE,cAAc,GACnB,MAAM,GAAG,SAAS,CAKpB"}