Skip to content

@gameable/assets ​

Classes ​

AssetError ​

An asset operation that could not be completed.

Extends ​

  • Error

Constructors ​

Constructor ​
ts
new AssetError(
   assetId, 
   message, 
   options?
): AssetError;

Build an asset error.

Parameters ​
ParameterTypeDescription
assetIdstringAsset id involved.
messagestringWhat went wrong.
options?ErrorOptionsStandard Error options, used to keep the cause.
Returns ​

AssetError

Overrides ​
ts
Error.constructor

Properties ​

assetId ​
ts
readonly assetId: string;

The id involved, when there is one.


ManifestError ​

A manifest that failed validation.

path is a JSON-pointer-ish location such as assets[2].collider.radius, so the message alone is enough to find and fix the problem.

Example ​

ts
import { ManifestError, parseManifest } from 'gameable/assets';

try {
  parseManifest({ version: 1 });
} catch (err) {
  if (err instanceof ManifestError) console.error(err.path, err.message);
}

Extends ​

  • Error

Constructors ​

Constructor ​
ts
new ManifestError(path, message): ManifestError;

Build a manifest validation error.

Parameters ​
ParameterTypeDescription
pathstringLocation of the offending value, for example assets[0].id.
messagestringWhat is wrong with it.
Returns ​

ManifestError

Overrides ​
ts
Error.constructor

Properties ​

path ​
ts
readonly path: string;

Location of the offending value inside the manifest.

Interfaces ​

AssetCollider ​

Static collision proxy registered with physics when the asset loads.

Properties ​

halfExtents? ​
ts
readonly optional halfExtents?: Vec3;

Box half extents. Required when shape is box.

height? ​
ts
readonly optional height?: number;

Capsule height. Required for capsule.

layer? ​
ts
readonly optional layer?: ColliderLayer;

Physics layer. Defaults to static.

offset? ​
ts
readonly optional offset?: Vec3;

Offset from the entity origin.

radius? ​
ts
readonly optional radius?: number;

Sphere or capsule radius. Required for sphere and capsule.

shape ​
ts
readonly shape: ColliderShape;

Collider shape.

src? ​
ts
readonly optional src?: string;

Path to collision geometry, for shapes that need their own mesh.


AssetEntry ​

One manifest entry.

Properties ​

collider? ​
ts
readonly optional collider?: AssetCollider;

Static collision proxy.

id ​
ts
readonly id: string;

Stable identifier used by game logic. Renaming it is a breaking change.

poster? ​
ts
readonly optional poster?: string;

A still shown until a video's first frame (a cutscene's poster). Relative path or absolute URL like src. Only on video entries.

rig? ​
ts
readonly optional rig?: AssetRig;

Character rig configuration. Required when type is character.

src ​
ts
readonly src: string;

Relative path (resolved against baseUrl) or absolute URL.

tags? ​
ts
readonly optional tags?: readonly string[];

Free-form labels for grouping and preload policies.

type ​
ts
readonly type: AssetType;

What the host should load this as.


AssetLoaderContext ​

Everything a loader is given besides the URL.

Properties ​

manifest ​
ts
readonly manifest: AssetManifest;

The manifest the entry came from.

registry ​
ts
readonly registry: AssetRegistry;

The registry doing the loading, for loaders that need sibling assets.

signal? ​
ts
readonly optional signal?: AbortSignal;

Abort signal, when the caller supplied one.


AssetLook ​

How an exported studio character (aosrig-splat) is drawn, when the manifest says: the point shape ('size-aware', the engine's point while small on screen and the studio's once large; 'engine', three's own 2-sigma point; 'studio', the studio page's; 'full-opacity', the small-point rule alone) and where its colours add up ('stored', in the file's own space as the studio adds them; 'linear', in linear light). Nothing asked = 'size-aware' and 'stored', chosen by Isaac on the looks sheet of 30 Sep 2026; a manifest entry overrides the package's own look, and the bridge's extras override both.

Properties ​

blend? ​
ts
readonly optional blend?: "stored" | "linear";
pointShape? ​
ts
readonly optional pointShape?: "engine" | "studio" | "full-opacity" | "size-aware";

AssetManifest ​

A parsed, validated assets.json.

Properties ​

assets ​
ts
readonly assets: readonly AssetEntry[];

Every asset the game can reference.

baseUrl ​
ts
readonly baseUrl: string;

Prefix joined to every relative src.

version ​
ts
readonly version: 1;

Manifest schema version. 1 is the only value today.


AssetProgress ​

One step of a AssetRegistry.preload run.

Properties ​

error? ​
ts
readonly optional error?: unknown;

The failure, when ok is false.

id ​
ts
readonly id: string;

Id that just finished.

loaded ​
ts
readonly loaded: number;

Entries finished so far, including failures.

ok ​
ts
readonly ok: boolean;

Whether the entry loaded successfully.

total ​
ts
readonly total: number;

Entries in this run.


AssetRegistry ​

Resolves ids to handles, entries, URLs and loaded objects.

Properties ​

ids ​
ts
readonly ids: readonly string[];

Every declared id, in manifest order.

manifest ​
ts
readonly manifest: AssetManifest;

The manifest this registry was built from.

Methods ​

add() ​
ts
add(more): string[];

Add another manifest's entries while the engine runs: a world package, or anything else that arrives after the game's own manifest was read.

Each entry gets the next handle, so every handle already given out stays what it was, and each src resolves against the added manifest's own baseUrl. An id the registry already holds keeps its first entry and is returned among the skipped, so loading the same world twice is harmless.

Parameters ​
ParameterTypeDescription
moreAssetManifestA parsed manifest (parseManifest).
Returns ​

string[]

The ids that were already declared and were left as they were.

dispose() ​
ts
dispose(): void;

Drop every cached object and in-flight promise.

Returns ​

void

Nothing.

entry() ​
ts
entry(idOrHandle): AssetEntry | undefined;

The manifest entry for an id or a handle.

Parameters ​
ParameterTypeDescription
idOrHandlestring | numberAsset id or handle.
Returns ​

AssetEntry | undefined

The entry, or undefined when unknown.

get() ​
ts
get(idOrHandle): unknown;

The already-loaded object for an id.

Parameters ​
ParameterTypeDescription
idOrHandlestring | numberAsset id or handle.
Returns ​

unknown

The object, or undefined when it is not loaded yet.

hasLoader() ​
ts
hasLoader(type): boolean;

Whether a loader is registered for a type.

Parameters ​
ParameterTypeDescription
typestringAsset type.
Returns ​

boolean

True when a loader exists.

idOf() ​
ts
idOf(handle): string | undefined;

The id behind a handle.

Parameters ​
ParameterTypeDescription
handlenumberHandle from resolve.
Returns ​

string | undefined

The id, or undefined for 0 and out-of-range handles.

load() ​
ts
load(idOrHandle): Promise<unknown>;

Load an asset, or return the promise for one already loading or loaded.

Repeat calls for the same id share one promise — the in-flight one while it is loading, then the resolved one forever after — so a hot path may call this every frame without allocating. An unknown id rejects with a shared AssetError, which is deliberately the same error object every time.

Parameters ​
ParameterTypeDescription
idOrHandlestring | numberAsset id or handle.
Returns ​

Promise<unknown>

The loaded object.

onProgress() ​
ts
onProgress(cb): () => void;

Subscribe to preload progress.

Parameters ​
ParameterTypeDescription
cb(progress) => voidCalled once per finished entry.
Returns ​

An unsubscribe function.

() => void

poster() ​
ts
poster(idOrHandle): string | undefined;

The resolved URL of a video entry's poster (the still shown until its first frame).

Parameters ​
ParameterTypeDescription
idOrHandlestring | numberAsset id or handle.
Returns ​

string | undefined

The URL, or undefined when unknown or when the entry has no poster.

preload() ​
ts
preload(tag?): Promise<void>;

Load every asset, or every asset carrying tag.

Parameters ​
ParameterTypeDescription
tag?stringOptional tag filter.
Returns ​

Promise<void>

Resolves once the whole set has settled.

registerLoader() ​
ts
registerLoader(type, loader): void;

Register (or replace) the loader for an asset type.

This is how gameable/splat and gameable/character add the splat and character types without core depending on them.

Parameters ​
ParameterTypeDescription
typestringAsset type the loader handles.
loaderAssetLoaderThe loader.
Returns ​

void

Nothing.

resolve() ​
ts
resolve(id): number;

The stable handle for an id.

Parameters ​
ParameterTypeDescription
idstringAsset id.
Returns ​

number

A 1-based handle, or 0 when the id is not declared.

url() ​
ts
url(idOrHandle): string | undefined;

The resolved URL for an id or a handle.

Parameters ​
ParameterTypeDescription
idOrHandlestring | numberAsset id or handle.
Returns ​

string | undefined

The URL, or undefined when unknown.


AssetRig ​

Character rig configuration.

Properties ​

backend ​
ts
readonly backend: RigBackend;

Which backend drives the character.

'skinned' means src is itself a glTF/GLB carrying JOINTS_0/WEIGHTS_0 and its animations, so no AssetRig.pack is involved.

clips? ​
ts
readonly optional clips?: readonly string[];

More clip files the character plays besides its own (aosrig-splat version 2: files of its clips.json format), each resolved like pack: beside src, or absolute.

controlNames? ​
ts
readonly optional controlNames?: readonly string[];

Ordered rig control names; the index is the wire format.

expressionSpace? ​
ts
readonly optional expressionSpace?: ExpressionSpace;

Layout of the expression vector.

light? ​
ts
readonly optional light?: string;

A lighter copy of the same character (aosrig-splat: its character.json, resolved like pack): drawn first, so the character stands sooner, src taking over once it is down, or all a phone loads. The character bridge's loading order does this by itself.

look? ​
ts
readonly optional look?: AssetLook;

How an aosrig-splat character is drawn, when the game says (see AssetLook).

motion? ​
ts
readonly optional motion?: string | false;

How an aosrig-splat character moves: false turns the movement system off for this character (it walks by the clips' blend, as before); a path names its own motion set (a soma-motion file, text or the compact binary), resolved like pack, when its character.json does not. Absent: the package's own set if it names one, laid over the engine's shared set.

pack? ​
ts
readonly optional pack?: string;

Path to the baked .aosrig pack. Required when backend is gnm.

vertexCount? ​
ts
readonly optional vertexCount?: number;

Vertices the rig stage produces per frame.


AudioLoaderOptions ​

Options accepted by createAudioLoader.

Extended by ​

Properties ​

fetch? ​
ts
readonly optional fetch?: {
  (input, init?): Promise<Response>;
  (input, init?): Promise<Response>;
};

fetch implementation. Defaults to the global one.

Call Signature ​
ts
(input, init?): Promise<Response>;

MDN Reference

Parameters ​
ParameterType
inputURL | RequestInfo
init?RequestInit
Returns ​

Promise<Response>

Call Signature ​
ts
(input, init?): Promise<Response>;

MDN Reference

Parameters ​
ParameterType
inputstring | URL | Request
init?RequestInit
Returns ​

Promise<Response>


CreateAssetRegistryOptions ​

Options accepted by createAssetRegistry.

Properties ​

loaders? ​
ts
readonly optional loaders?: Readonly<Record<string, AssetLoader>>;

Loaders by asset type. More can be added later with registerLoader.

manifest ​
ts
readonly manifest: AssetManifest;

The validated manifest.

signal? ​
ts
readonly optional signal?: AbortSignal;

Abort signal handed to every loader.


DefaultLoaderOptions ​

Options accepted by createDefaultLoaders.

Extends ​

Properties ​

dracoDecoderPath? ​
ts
readonly optional dracoDecoderPath?: string;

Directory holding draco_decoder.js / .wasm, served by your app.

Defaults to DEFAULT_DRACO_DECODER_PATH. Copy the files from three/examples/jsm/libs/draco/ into your public/ directory, or point this at a CDN.

Inherited from ​

GltfLoaderOptions.dracoDecoderPath

fetch? ​
ts
readonly optional fetch?: {
  (input, init?): Promise<Response>;
  (input, init?): Promise<Response>;
};

fetch implementation. Defaults to the global one.

Call Signature ​
ts
(input, init?): Promise<Response>;

MDN Reference

Parameters ​
ParameterType
inputURL | RequestInfo
init?RequestInit
Returns ​

Promise<Response>

Call Signature ​
ts
(input, init?): Promise<Response>;

MDN Reference

Parameters ​
ParameterType
inputstring | URL | Request
init?RequestInit
Returns ​

Promise<Response>

Inherited from ​

AudioLoaderOptions.fetch

ktx2TranscoderPath? ​
ts
readonly optional ktx2TranscoderPath?: string;

Directory holding the Basis Universal transcoder. When omitted, KTX2 textures are not supported and no KTX2Loader is constructed.

Inherited from ​

GltfLoaderOptions.ktx2TranscoderPath

renderer? ​
ts
readonly optional renderer?: WebGPURenderer;

An initialised renderer. Required for KTX2: detectSupport needs to know which compressed texture formats the backend exposes.

Inherited from ​

GltfLoaderOptions.renderer


DefaultLoaders ​

The default loader set plus the handle needed to release three's workers.

Properties ​

loaders ​
ts
readonly loaders: Record<string, AssetLoader>;

Pass straight to createAssetRegistry({ loaders }).

Methods ​

dispose() ​
ts
dispose(): void;

Terminate any worker pools the loaders started.

Returns ​

void

Nothing.


ExpressionSegment ​

A named contiguous span of an expression vector.

Properties ​

length ​
ts
readonly length: number;

Number of components in the span.

name ​
ts
readonly name: string;

Segment name, for example left_eye.

offset ​
ts
readonly offset: number;

First index of the span.


ExpressionSpace ​

Layout of the expression vector a character rig consumes.

Properties ​

arkitMap? ​
ts
readonly optional arkitMap?: string;

Path to an ARKit-52 to this-space mapping, when kind is not arkit52.

dim ​
ts
readonly dim: number;

Number of components in the vector.

kind ​
ts
readonly kind: ExpressionSpaceKind;

Which well-known expression space this is.

names? ​
ts
readonly optional names?: readonly string[];

Optional per-component names.

segments? ​
ts
readonly optional segments?: readonly ExpressionSegment[];

Optional named spans of the vector.


GltfLoaderHandle ​

A gltf loader plus the handles needed to release its workers.

Properties ​

load ​
ts
readonly load: AssetLoader;

Register this under the gltf asset type.

Methods ​

dispose() ​
ts
dispose(): void;

Terminate the Draco and KTX2 worker pools.

Returns ​

void

Nothing.


GltfLoaderOptions ​

Options accepted by createGltfLoader.

Extended by ​

Properties ​

dracoDecoderPath? ​
ts
readonly optional dracoDecoderPath?: string;

Directory holding draco_decoder.js / .wasm, served by your app.

Defaults to DEFAULT_DRACO_DECODER_PATH. Copy the files from three/examples/jsm/libs/draco/ into your public/ directory, or point this at a CDN.

ktx2TranscoderPath? ​
ts
readonly optional ktx2TranscoderPath?: string;

Directory holding the Basis Universal transcoder. When omitted, KTX2 textures are not supported and no KTX2Loader is constructed.

renderer? ​
ts
readonly optional renderer?: WebGPURenderer;

An initialised renderer. Required for KTX2: detectSupport needs to know which compressed texture formats the backend exposes.


LoadManifestOptions ​

Options accepted by loadManifest.

Extends ​

Properties ​

baseUrl? ​
ts
readonly optional baseUrl?: string;

Fallback baseUrl when the manifest does not declare one.

Inherited from ​

ParseManifestOptions.baseUrl

fetch? ​
ts
readonly optional fetch?: {
  (input, init?): Promise<Response>;
  (input, init?): Promise<Response>;
};

fetch implementation. Defaults to the global one.

Call Signature ​
ts
(input, init?): Promise<Response>;

MDN Reference

Parameters ​
ParameterType
inputURL | RequestInfo
init?RequestInit
Returns ​

Promise<Response>

Call Signature ​
ts
(input, init?): Promise<Response>;

MDN Reference

Parameters ​
ParameterType
inputstring | URL | Request
init?RequestInit
Returns ​

Promise<Response>

signal? ​
ts
readonly optional signal?: AbortSignal;

Abort signal forwarded to fetch.


ParseManifestOptions ​

Options accepted by parseManifest.

Extended by ​

Properties ​

baseUrl? ​
ts
readonly optional baseUrl?: string;

Fallback baseUrl when the manifest does not declare one.

Type Aliases ​

AssetLoader ​

ts
type AssetLoader = (url, entry, ctx) => Promise<unknown>;

Turns one asset entry into a loaded object.

One loader is registered per AssetEntry.type. What it resolves to is the loader's business: gltf yields a GLTF, audio an ArrayBuffer, splat and character whatever their packages define.

Parameters ​

ParameterType
urlstring
entryAssetEntry
ctxAssetLoaderContext

Returns ​

Promise<unknown>


AssetLoaderMap ​

ts
type AssetLoaderMap = Readonly<Record<string, AssetLoader>>;

Loaders keyed by asset type.


AssetType ​

ts
type AssetType = "splat" | "gltf" | "character" | "audio" | "video";

What the host should load an entry as. video is a clip the page plays over the stage (a cutscene): the host never decodes it itself, a <video> does.


ColliderLayer ​

ts
type ColliderLayer = "static" | "dynamic" | "trigger";

Physics layer a collider belongs to.


ColliderShape ​

ts
type ColliderShape = "mesh" | "box" | "sphere" | "capsule" | "convex-hull" | "heightfield";

Collision proxy shapes understood by the physics module.


ExpressionSpaceKind ​

ts
type ExpressionSpaceKind = "arkit52" | "gnm" | "gnm68";

Coordinate space a character's expression vector is expressed in.


RigBackend ​

ts
type RigBackend = "orl" | "gnm" | "skinned" | "aosrig-splat";

Which RigBackend drives a character.

orl and gnm are splat head rigs and need a baked pack; skinned is a whole body — a glTF/GLB with JOINTS_0/WEIGHTS_0 and its clips embedded, so it needs no pack at all.


Vec3 ​

ts
type Vec3 = readonly [number, number, number];

A three-component vector, as written in the manifest.

Variables ​

DEFAULT_DRACO_DECODER_PATH ​

ts
const DEFAULT_DRACO_DECODER_PATH: "/draco/" = '/draco/';

Where DRACOLoader looks for its decoder, when none is configured.


PACKAGE ​

ts
const PACKAGE: "@gameable/assets";

Package identity marker.

Example ​

ts
import { PACKAGE } from 'gameable/assets';

console.log(PACKAGE); // 'gameable/assets'

Functions ​

createAssetRegistry() ​

ts
function createAssetRegistry(options): AssetRegistry;

Build an asset registry over a validated manifest.

Parameters ​

ParameterTypeDescription
optionsCreateAssetRegistryOptionsManifest, loaders and an optional abort signal.

Returns ​

AssetRegistry

The registry.

Example ​

ts
import { createAssetRegistry, parseManifest } from 'gameable/assets';

const manifest = parseManifest({
  version: 1,
  baseUrl: '/assets/',
  assets: [{ id: 'shot', type: 'audio', src: 'sfx/shot.ogg', tags: ['sfx'] }],
});
const assets = createAssetRegistry({ manifest, loaders: {} });
console.log(assets.resolve('shot')); // 1
console.log(assets.resolve('nope')); // 0

createAudioLoader() ​

ts
function createAudioLoader(options?): AssetLoader;

Build the audio asset loader.

It resolves to the raw ArrayBuffer. Turning that into an AudioBuffer is gameable/audio's job, because decoding needs an AudioContext and the registry must stay usable without one.

Parameters ​

ParameterTypeDescription
optionsAudioLoaderOptionsfetch override.

Returns ​

AssetLoader

An asset loader resolving to an ArrayBuffer.

Example ​

ts
import { createAssetRegistry, createAudioLoader, parseManifest } from 'gameable/assets';

const manifest = parseManifest({ version: 1, assets: [{ id: 'shot', type: 'audio', src: 'shot.ogg' }] });
const assets = createAssetRegistry({ manifest, loaders: { audio: createAudioLoader() } });
const bytes = (await assets.load('shot')) as ArrayBuffer;
console.log(bytes.byteLength > 0);

createDefaultLoaders() ​

ts
function createDefaultLoaders(options?): DefaultLoaders;

The loaders createEngine registers: gltf and audio.

Parameters ​

ParameterTypeDescription
optionsDefaultLoaderOptionsDraco/KTX2 paths and a fetch override.

Returns ​

DefaultLoaders

The loader map and its dispose.

Example ​

ts
import { createAssetRegistry, createDefaultLoaders, parseManifest } from 'gameable/assets';

const defaults = createDefaultLoaders({ dracoDecoderPath: '/draco/' });
const manifest = parseManifest({ version: 1, assets: [] });
const assets = createAssetRegistry({ manifest, loaders: defaults.loaders });
console.log(assets.hasLoader('audio')); // true

createGltfLoader() ​

ts
function createGltfLoader(options?): GltfLoaderHandle;

Build the gltf asset loader.

The underlying GLTFLoader, DRACOLoader and optional KTX2Loader are constructed lazily, on the first asset that needs them.

Parameters ​

ParameterTypeDescription
optionsGltfLoaderOptionsDraco decoder path, optional KTX2 transcoder path and renderer.

Returns ​

GltfLoaderHandle

The loader function and its dispose.

Example ​

ts
import { createAssetRegistry, createGltfLoader, parseManifest } from 'gameable/assets';

const gltf = createGltfLoader({ dracoDecoderPath: '/draco/' });
const manifest = parseManifest({ version: 1, assets: [{ id: 'rock', type: 'gltf', src: 'rock.glb' }] });
const assets = createAssetRegistry({ manifest, loaders: { gltf: gltf.load } });
console.log(assets.hasLoader('gltf')); // true

findEntry() ​

ts
function findEntry(manifest, id): AssetEntry | undefined;

Look an entry up by id.

The lookup table is built once per manifest object and cached against it, so this is a Map hit rather than a scan after the first call.

Parameters ​

ParameterTypeDescription
manifestAssetManifestManifest to search.
idstringAsset id.

Returns ​

AssetEntry | undefined

The entry, or undefined when the id is unknown.

Example ​

ts
import { findEntry, parseManifest } from 'gameable/assets';

const manifest = parseManifest({ version: 1, assets: [{ id: 'shot', type: 'audio', src: 'a.ogg' }] });
console.log(findEntry(manifest, 'shot')?.src); // 'a.ogg'

joinUrl() ​

ts
function joinUrl(baseUrl, src): string;

Join a base prefix and a relative source path.

Parameters ​

ParameterTypeDescription
baseUrlstringPrefix; may be empty, a path, or an absolute URL.
srcstringEntry src.

Returns ​

string

The joined URL.

Example ​

ts
import { joinUrl } from 'gameable/assets';

console.log(joinUrl('https://cdn.example/a/b/', '../c.glb')); // 'https://cdn.example/a/c.glb'

loadManifest() ​

ts
function loadManifest(url, options?): Promise<AssetManifest>;

Fetch and validate an assets.json.

When the document omits baseUrl, it defaults to the manifest's own directory, exactly as the JSON Schema says.

Parameters ​

ParameterTypeDescription
urlstringWhere the manifest lives.
optionsLoadManifestOptionsfetch override, abort signal and baseUrl fallback.

Returns ​

Promise<AssetManifest>

The validated manifest.

Example ​

ts
import { loadManifest } from 'gameable/assets';

const manifest = await loadManifest('/assets/assets.json');
console.log(manifest.assets.length);

parseManifest() ​

ts
function parseManifest(json, options?): AssetManifest;

Parse and validate an assets.json document.

Every failure throws a ManifestError naming the exact path, and the first failure wins: a manifest is either completely valid or rejected.

Parameters ​

ParameterTypeDescription
jsonunknownThe parsed JSON document.
optionsParseManifestOptionsFallback baseUrl when the manifest omits one.

Returns ​

AssetManifest

A frozen, validated manifest.

Example ​

ts
import { parseManifest } from 'gameable/assets';

const manifest = parseManifest({
  version: 1,
  baseUrl: '/assets/',
  assets: [{ id: 'arena', type: 'splat', src: 'arena.spz', tags: ['world'] }],
});
console.log(manifest.assets[0]?.id); // 'arena'

resolveAssetUrl() ​

ts
function resolveAssetUrl(manifest, entry): string;

Resolve an entry's src against the manifest's baseUrl.

Absolute URLs (https://…, data:…) and protocol-relative URLs (//host/x) are returned untouched, as are root-relative paths (/x.glb) when baseUrl is itself relative. When baseUrl carries a scheme, resolution goes through URL, so ../ segments collapse correctly.

Parameters ​

ParameterTypeDescription
manifestAssetManifestManifest supplying baseUrl.
entrystring | AssetEntryAn entry, or the id of one.

Returns ​

string

The absolute or root-relative URL to fetch.

Example ​

ts
import { parseManifest, resolveAssetUrl } from 'gameable/assets';

const manifest = parseManifest({
  version: 1,
  baseUrl: '/assets',
  assets: [{ id: 'arena', type: 'splat', src: 'arena.spz' }],
});
console.log(resolveAssetUrl(manifest, 'arena')); // '/assets/arena.spz'