@ferret/assets
Loading and caching of glTF, KTX2 and packed assets, .ferret packages and their security checks.
Classes
AssetCache class
class AssetCacheLoads glTF/GLB models once and hands out instances. The source stays in memory (not in the scene) while anything uses it;
each instance is its own copy of the nodes, skeletons and animation groups, with materials and geometry shared.
A URL, a data: URL, a blob URL or a File all work. Reference counting: release the url when the level that asked for it goes.
onProgress?: (p: LoadProgress) => void | Called with the bytes loaded so far, for a loading screen. |
constructor(readonly scene: BABYLON.Scene) | |
get size(): number | |
has(url: string): boolean | |
refs(url: string): number | |
async load(url: string, ext = guessExt(url)): Promise<void> | Load (or reuse) a model and take a reference to it. |
async preload(urls: string[]): Promise<void> | Load several models; resolves when all are in. |
async instantiate(url: string, name?: string): Promise<ModelInstance> | A new copy of a loaded model in the scene. The model must have been loaded (load). |
release(url: string): void | Drop one reference; at zero the source and its instances are freed. |
dispose(): void |
packages/assets/src/cache.ts:30
AssetStore class
class AssetStoreconstructor(o: StoreOptions = {}) | |
resolve(ref: string): string | Resolve a ref to an absolute URL and check the rules of section 6. Throws StreamError('origin'). |
require(ref: string, opts: RequireOptions = {}): Promise<Handle> | |
prefetch(refs: string[], opts: RequireOptions = {}): void | Ask for files nobody waits for. Failures are kept in status, not thrown. |
stats(): { queued: number; loading: number; done: number; failed: number; bytes: number } | What the store holds and is doing now: files by state and the bytes in them (for stream.status). |
status(ref: string): StoreStatus | |
plan(refs: { ref: string; size?: number }[]): { bytes: number; files: number; cached: boolean } | What asking for these refs would download now: files not yet held, their bytes (sizes given by the caller). |
release(ref: string): void | |
progress(group: string): StreamProgress | |
onProgress(fn: (group: string, p: StreamProgress) => void): () => void | |
cancelGroup(group: string): void | Abort everything queued or running that belongs to a group (a travel to another level). |
async byteSource(ref: string, o: { block?: number; maxBlocks?: number; signal?: AbortSignal } = {}): Promise<ByteSource> | A ByteSource over a URL read through the store: blocks of block bytes (read-ahead merges neighbouring ranges), kept in
memory, retried like any file. FerretPackage.open(source) then costs the bytes of the entries it reads, not the package. |
get cache(): PersistentCache | undefined | The persistent cache this store writes to, if any. |
async fetchFresh(ref: string, o: { signal?: AbortSignal } = {}): Promise<Uint8Array> | A mutable file (a catalog, an index): always asked of the server, never from the cache, retried like any file. Not queued, because what a travel needs next must not wait behind downloads. |
keep(key: string, bytes: Uint8Array, meta: { pkg?: string; version?: string; url?: string } = {}): void | Keep verified bytes under their hash (or another key) in the persistent cache. Never fails the caller: a full or broken cache is only a missed saving. |
async flushCache(): Promise<void> | Resolves when every cache write started so far has finished (tests, and before the page is suspended). |
cacheInfo(): Promise<CacheInfo | null> | |
pin(pkg: string): void | |
unpin(pkg: string): void | |
async pinPack(name: string, files: { ref: string; sha256: string; size?: number }[], o: { priority?: StreamPriority; signal?: AbortSignal } = {}): Promise<{ ok: number; missing: string[] }> | A pack of files the game wants always on the device (a transition's images, videos, sounds): fetched at low priority, kept in the
cache under the pack's name and pinned there, so the wait of a travel never depends on the network for them. Returns what could
not be fetched; status(ref) tells later whether one is there. |
async clear(pkg?: string): Promise<void> | Forget downloaded content: one package, or everything. |
packages/assets/src/store.ts:68
CacheStorageBackend class
class CacheStorageBackend implements CacheBackendCache Storage (caches): browsers and the Android WebView. A synthetic URL per key.
readonly name: "cache-storage" | |
constructor(private readonly cache: Cache, private readonly origin = 'https://ferret-cache.invalid/') | |
static async open(caches: CacheStorage, name = 'ferret-assets'): Promise<CacheStorageBackend> | |
async get(k: string) | |
async put(k: string, b: Uint8Array) | |
async delete(k: string) | |
async keys() |
packages/assets/src/persist.ts:23
CatalogError class
class CatalogError extends Errorconstructor(public readonly code: CatalogFailure, message: string, public readonly issues: PackageIssue[] = []) |
packages/assets/src/catalog.ts:58
ChunkedBackend class
class ChunkedBackend implements CacheBackendSplits big values into parts: Chromium refused a single Cache Storage entry above 128 MB (S0). A value of at most chunk bytes is stored as is; a bigger one as parts key~0..n and then a small record key!m. The record is written last, so a cut write leaves no half value visible (the orphan parts are removed by sweep).
constructor(private readonly inner: CacheBackend, private readonly chunk = 32 * 1048576) | |
get name() | |
async get(k: string) | |
async put(k: string, b: Uint8Array) | |
async delete(k: string) | |
async keys() | Logical keys only. |
async sweep() | Remove parts whose record is missing (a write that was cut). |
packages/assets/src/persist.ts:63
FerretPackage class
class FerretPackageget source(): PackageSource | the files as they can be read now: decrypted once the package is unlocked |
get encrypted(): boolean | |
get dirty(): boolean | the changes made through draft() that are not packed yet |
draft(): PackageDraft | Start editing: from now on this package reads through a draft, so writes (draft().set(path, data)) show in everything that
reads the package (a mounted level reloads with them). pack writes the result. |
async appendSave(base: ByteSource): Promise<{ at: number; tail: Uint8Array; dead: number; changed: string[]; commit(): void }> | Save a project (.fprj) by appending: the files changed through draft() since the last save are written after the
last byte of the file base (the package as it stands on disk, the bytes it was opened from plus the tails appended since), then a new
directory and end record. Write tail at offset at, flush, then call commit(); a save cut off anywhere leaves the previous content.
dead is the share of the file the saves left unused, so a tool can compactZip above a half. A project holds no hashes or signature, so
nothing else changes. Only a project may be saved this way; any other package is written whole by pack. |
async pack(o: PackOptions = {}): Promise<{ bytes: Uint8Array; manifest: PackageManifest }> | Build the .ferret bytes of the package as it is now. An encrypted package is sealed again with the password or key it was
unlocked with (pass decrypt for plain files, encrypt for another key). The old signature does not survive a change: pass
sign to sign the new package. |
get locked(): boolean | encrypted and the key not given yet: the names can be listed, the files cannot be read |
static async open(input: PackageInput, o: { fetch?: typeof fetch; /** the file's name (a Blob has none; a URL string is its own): `.fex` must be a game and `.fprj` a project */ fileName?: string } & Partial<KeyInput> = {}): Promise<FerretPackage> | Open a package: a .ferret file (a Blob or File, bytes, or a URL — read by range requests when the server allows), or a
folder (a URL ending in /, or a PackageSource). Fails when ferret.json is missing or invalid. |
async unlock(input: KeyInput): Promise<void> | Give the password or key of an encrypted package. Throws WrongKeyError when it does not fit. |
get id(): string | |
get version(): string | |
has(path: string): boolean | |
read(path: string): Promise<Uint8Array> | |
async readText(path: string): Promise<string> | |
async readJson<T = unknown>(path: string): Promise<T> | |
levels(): string[] | Ids of the levels: those provides names, else every levels/<id>/manifest.json. |
get hasCode(): boolean | Does the package hold code? (kinds assets and levels must not.) |
get codeEntry(): string | undefined | The module the code loader runs: code.entry, else a game's entry.code; undefined when the package names none. |
info(): PackageInfo | |
async verify(o: { trusted?: Iterable<string>; checksums?: boolean } = {}): Promise<VerifyResult> | Is the signature there, does it match, and is the key one the reader trusts? (default true) also reads every file and compares it with the manifest, which needs the package unlocked. Unsigned is not a failure here: says so, and what to do about it is the reader's decision. |
async codeAllowed(policy: CodePolicy = {}): Promise<{ allowed: boolean; reason?: string }> | May this package's code run? Packages without code always can. Otherwise the code must come from the game's own origin, or the package must carry a valid signature (from a trusted key, if the reader keeps a list). Returns the reason when it may not. |
async validate(o: { checksums?: boolean; engine?: EngineVersions; engineRequired?: boolean } = {}): Promise<PackageIssue[]> | Check the package: the manifest, that the files it names exist, that kinds without code hold none, and (checksums, default
true when the manifest has them) that every file has the sha256 the manifest gives. References between levels and assets are
checked by @ferret/levels (validatePackage), which knows the level manifest. |
packages/assets/src/package.ts:402
IndexedDbBackend class
class IndexedDbBackend implements CacheBackendIndexedDB blobs: one record per key.
readonly name: "indexeddb" | |
static open(idb: IDBFactory, name = 'ferret-assets'): Promise<IndexedDbBackend> | |
async get(k: string) | |
async put(k: string, b: Uint8Array) | |
async delete(k: string) | |
async keys() |
packages/assets/src/persist.ts:35
LockedError class
class LockedError extends ErrorOpening an encrypted package without its key, or reading a file of it.
constructor(m = 'the package is encrypted: give its password or key (unlock)') |
packages/assets/src/package.ts:383
MemoryBackend class
class MemoryBackend implements CacheBackendreadonly name: "memory" | |
async get(k: string) | |
async put(k: string, b: Uint8Array) | |
async delete(k: string) | |
async keys() |
packages/assets/src/persist.ts:13
PackageDraft class
class PackageDraft implements PackageSourceA package kept in memory, with changes on top of another source: what the editor works on and pack writes.
readonly kind: "memory" | |
constructor(readonly base: PackageSource | null = null) | |
get changed(): string[] | the paths changed (written or deleted) since the last commit |
get dirty(): boolean | |
commit(): void | Remember that everything now in the draft is kept somewhere (appended to the file): changed and dirty start again from nothing. The files stay readable. |
list(): string[] | every file path (the manifest included) |
has(p: string): boolean | |
size(p: string): number | bytes of the file in the package (uncompressed); 0 when unknown |
async read(p: string): Promise<Uint8Array> | |
set(path: string, data: Uint8Array | string): void | |
delete(path: string): void |
packages/assets/src/package.ts:336
PackageSet class
class PackageSetThe packages that are open together, so ferret://<id>/<path> and dependencies resolve.
add(pkg: FerretPackage): this | |
remove(id: string): void | |
get(id: string): FerretPackage | undefined | |
get all(): FerretPackage[] | |
resolve(ref: string, from: FerretPackage, base = ''): { pkg: FerretPackage; path: string } | null | Where a reference points: ferret://id/path in another package (which must be a declared dependency of from), or a path inside from. |
deps(pkg: FerretPackage, seen = new Set<string>()): DependencyReport[] | pkg.deps: every dependency of a package, found or not, and whether its version fits (and, down the tree, theirs). |
packages/assets/src/package.ts:684
PersistentCache class
class PersistentCacheconstructor(readonly backend: CacheBackend, private readonly o: PersistentCacheOptions = {}) | |
async flush(): Promise<void> | |
async has(key: string): Promise<boolean> | |
async get(key: string): Promise<Uint8Array | null> | |
async put(key: string, bytes: Uint8Array, meta: { pkg?: string; version?: string; url?: string } = {}): Promise<void> | Keep verified bytes. The blob goes in first and the index after it, so an interrupted write leaves a blob the index does not know (swept later) and never an entry without bytes. Makes room by evicting the least recently used unpinned packages once; throws QuotaError when that is not enough. |
pin(pkg: string): void | |
unpin(pkg: string): void | |
async clear(pkg?: string): Promise<void> | |
async info(): Promise<CacheInfo> | |
async putPartial(key: string, bytes: Uint8Array, etag: string): Promise<void> | Keep what arrived of a file the network cut, with the validator the server gave, so a later session can resume it. |
async getPartial(key: string): Promise<{ bytes: Uint8Array; etag: string } | null> | |
async dropPartial(key: string): Promise<void> | |
async cleanPartials(): Promise<void> | Remove old partial files and blobs that no index knows (a write cut between the blob and the index). |
static async requestPersistence(storage: Pick<StorageManager, 'persist' | 'persisted'> | undefined = (globalThis as { navigator?: Navigator }).navigator?.storage): Promise<boolean> | Ask the browser not to drop the cache under pressure; call it after the first level loaded. |
packages/assets/src/persist.ts:132
QuotaError class
class QuotaError extends Errorconstructor(public readonly needed: number) |
packages/assets/src/persist.ts:118
Sha256 class
class Sha256update(data: Uint8Array): this | |
hex(): string | The digest as hex. The hasher is spent afterwards. |
packages/assets/src/sha256.ts:10
StreamError class
class StreamError extends Errorconstructor(public readonly ref: string, public readonly reason: StreamFailure, message: string, public readonly status?: number) |
packages/assets/src/stream-error.ts:5
WrongKeyError class
class WrongKeyError extends Errorconstructor(m = 'wrong password or key') |
packages/assets/src/security.ts:135
Functions
appendToZip function
async function appendToZip(src: ByteSource, changes: { set?: ZipFile[]; remove?: string[] }): Promise<{ at: number; tail: Uint8Array; entries: Map<string, ZipEntryInfo>; dead: number }>Change a zip by appending: the new and changed files are written after the last byte, then a new directory that
lists every live file, then a new end record. Nothing before them is touched, so the cost of a save is the size of the change, not of the
project, and a save cut off at any byte leaves the previous directory the last complete one (readZipDirectory finds it). Replaced and
removed files stay in the file as dead bytes until compactZip. Write tail at offset at (the size of the file now), then flush.
dead is the share of the file that no directory entry uses.
packages/assets/src/zip.ts:256
blobSource function
function blobSource(blob: Blob): ByteSourcepackages/assets/src/zip.ts:14
buildCatalog function
async function buildCatalog(i: BuildCatalogInput): Promise<{ catalog: Catalog; files: Record<string, Uint8Array> }>pkg.catalog + pkg.publish: the catalog, and the files to put on the server, as { path: bytes }.
packages/assets/src/catalog.ts:262
bytesSource function
function bytesSource(bytes: Uint8Array): ByteSourcepackages/assets/src/zip.ts:17
canonicalManifest function
function canonicalManifest(m: object): Uint8ArrayThe bytes that are signed: the manifest without its signature, as JSON with sorted keys.
packages/assets/src/security.ts:70
checkCatalog function
async function checkCatalog(raw: unknown, policy: CatalogPolicy = {}): Promise<{ catalog: Catalog; unsigned: boolean; keyId?: string }>Validate, check the signature against the keys the game trusts, and the engine range. Throws CatalogError.
packages/assets/src/catalog.ts:126
checkEngine function
function checkEngine(engine: PackageManifest['engine'], running: EngineVersions, o: { required?: boolean } = {}): PackageIssue[]Compare the engine field of a manifest with the engine that is running. The ranges are those of
dependencies; Babylon is usually an exact version. Only the numbers count: a build stamp after + is ignored. A field that
is absent is no problem unless required is set (a finished game needs both). Returns the issues; empty means it fits.
packages/assets/src/package.ts:261
checkFileName function
function checkFileName(name: string, kind: PackageKind): PackageIssue[]Does the file name's extension agree with the package kind? .fex is a game and .fprj a project; other names are free.
packages/assets/src/package.ts:117
cleanPath function
function cleanPath(p: unknown): string | nullA path inside a package: a/b/c.ext, no .., no leading slash. Returns it normalised, or null.
packages/assets/src/package.ts:210
compactZip function
async function compactZip(src: ByteSource): Promise<Uint8Array>A zip with only its live files, copied as they are (no recompression), in the order they were first written: drops what appends left behind.
packages/assets/src/zip.ts:285
configureKtx2 function
function configureKtx2(o: Ktx2Options = {}): { base: string; workers: number }Point Babylon's KTX2 decoder at the files of ktx2/. Idempotent: the first call wins (the worker pool starts with the first KTX2 texture).
packages/assets/src/ktx2.ts:55
crc32 function
function crc32(data: Uint8Array): numberpackages/assets/src/zip.ts:183
createEncryption function
async function createEncryption(input: KeyInput, iterations = DEFAULT_ITERATIONS): Promise<{ encryption: PackageEncryption; key: CryptoKey }>Make the encryption block (and the key) for a new encrypted package.
packages/assets/src/security.ts:116
deflate function
function deflate: (b: Uint8Array<ArrayBufferLike>) => Promise<Uint8Array<ArrayBufferLike>>Deterministic (see deflate.ts): the platform's CompressionStream gives different bytes in each engine.
packages/assets/src/zip.ts:180
detectBackend function
async function detectBackend(env: CacheEnv = globalThis as unknown as CacheEnv, chunk?: number): Promise<CacheBackend>Pick the first backend that really writes, reads and deletes (a private window may offer the API and refuse the write).
packages/assets/src/persist.ts:100
downloadedContentItem function
function downloadedContentItem(store: AssetStore, o: { onChange?: () => void; close?: () => void } = {}): CacheMenuItem & { refresh(): Promise<void> }value() is read by a menu on every draw, so it shows the last size seen; refresh() reads the cache and calls onChange.
packages/assets/src/cache-menu.ts:17
etc1sTarget function
function etc1sTarget(caps: Ktx2Caps, alpha = true): Ktx2Targetthe block format Babylon picks for ETC1S data (the colour maps of --ktx2)
packages/assets/src/ktx2.ts:83
folderSource function
async function folderSource(io: FolderIO): Promise<PackageSource>packages/assets/src/package.ts:301
formatBytes function
function formatBytes(n: number): stringpackages/assets/src/cache-menu.ts:10
fromBase64 function
function fromBase64(b: string): Uint8Arraypackages/assets/src/security.ts:46
generateEncryptionKey function
function generateEncryptionKey(): stringpackages/assets/src/security.ts:105
generateSigningKey function
async function generateSigningKey(): Promise<SigningKey>packages/assets/src/security.ts:62
httpFolderSource function
async function httpFolderSource(base: string, fetchFn: typeof fetch = (u, i) => fetch(u, i)): Promise<PackageSource>A folder package on a server. The file names come from index (a list of paths) or, when the folder is served with an index
file named ferret-index.json, from that; otherwise from the files of its ferret.json.
packages/assets/src/package.ts:317
inflate function
function inflate: (b: Uint8Array<ArrayBufferLike>, max?: number) => Promise<Uint8Array<ArrayBufferLike>>packages/assets/src/zip.ts:178
isCompressedFormat function
function isCompressedFormat(path: string): booleanFiles that are compressed already are stored, so a package is read by slices without inflating (PLAN.md 4.9).
packages/assets/src/zip.ts:193
itemPath function
function itemPath: (id: string) => stringPath of the item file of an id inside a package.
packages/assets/src/library.ts:319
joinPath function
function joinPath(base: string, ref: string): string | nullResolve ref against the folder base (levels/yard/ + ../../assets/a.glb = assets/a.glb).
packages/assets/src/package.ts:221
keyIdOf function
async function keyIdOf(spki: Uint8Array): Promise<string>packages/assets/src/security.ts:58
ktx2Base function
function ktx2Base(o: Ktx2Options = {}): stringwhere the transcoder files are for this page (absolute: Babylon's worker is a blob, which resolves nothing relative)
packages/assets/src/ktx2.ts:46
ktx2GpuBytes function
function ktx2GpuBytes(width: number, height: number, target: Ktx2Target, mips = true): numberVideo memory of a texture of width x height with a full mip chain in target (4/3 of the top level, rounded up by the blocks).
packages/assets/src/ktx2.ts:100
ktx2Targets function
function ktx2Targets(engine: { getCaps(): Ktx2Caps }): { caps: Required<Ktx2Caps>; uastc: Ktx2Target; uastcNonColour: Ktx2Target; etc1s: Ktx2Target }The formats a running engine will transcode to: for logs, tests and the memory estimate (Engine.getCaps() is read).
packages/assets/src/ktx2.ts:93
levelIds function
function levelIds(files: string[]): string[]packages/assets/src/package.ts:611
loadCatalog function
async function loadCatalog(store: AssetStore, url: string, policy: CatalogPolicy = {}, o: { signal?: AbortSignal } = {}): Promise<LoadedCatalog>Fetch a catalog (never from the HTTP cache) and check it. When the network fails, the last good copy kept in the persistent cache is used and marked stale; with none, the failure is thrown. A good fresh copy replaces the kept one.
packages/assets/src/catalog.ts:150
openCatalogPackage function
async function openCatalogPackage(store: AssetStore, c: Catalog, pkgId: string, o: { tier?: string; signal?: AbortSignal } = {}): Promise<CatalogPackageSource>Open a package of the catalog. ferret.json is checked against the catalog's manifest hash, and every file read afterwards against the hash that manifest holds, so nothing the signed catalog does not vouch for is handed on. Files are looked up in the persistent cache by their hash first; a miss reads the package by range requests (only then is the network used) and keeps the file. The package's index is cached under the manifest hash, so a package seen once can be opened, and its cached files read, with the network gone.
packages/assets/src/catalog.ts:186
openEncryption function
async function openEncryption(e: PackageEncryption, input: KeyInput): Promise<CryptoKey>The key for an existing encrypted package; throws WrongKeyError when it does not open the package's check value.
packages/assets/src/security.ts:138
packageUrl function
function packageUrl(c: Catalog, pkgId: string, tier?: string): stringAbsolute URL of a package file for a tier (falls back to the plain one), relative to the catalog's base.
packages/assets/src/catalog.ts:169
packFiles function
async function packFiles(files: { path: string; data: Uint8Array | string }[], o: PackOptions = {}): Promise<{ bytes: Uint8Array; manifest: PackageManifest }>A folder's files (read from disk by the caller) packed into a package (ferret pack). The folder must hold ferret.json.
packages/assets/src/package.ts:743
packPackage function
async function packPackage(source: PackageSource, o: PackOptions = {}): Promise<{ bytes: Uint8Array; manifest: PackageManifest }>packages/assets/src/package.ts:644
parseFerretRef function
function parseFerretRef(ref: string): { pkg: string; path: string } | nullferret://<package>/<path> -> its parts, or null
packages/assets/src/library.ts:143
publishedPath function
function publishedPath: (hash: string, ext?: string) => stringWhere a published package file lives: content addressed, so it never changes and can be cached forever.
packages/assets/src/catalog.ts:259
readZipDirectory function
async function readZipDirectory(src: ByteSource): Promise<Map<string, ZipEntryInfo>>packages/assets/src/zip.ts:122
readZipEntry function
async function readZipEntry(src: ByteSource, info: ZipEntryInfo): Promise<Uint8Array>Read one file out of a zip: slice, inflate when needed, check the length and the CRC.
packages/assets/src/zip.ts:150
satisfies function
function satisfies(version: string, range: string): booleanDoes version fit range?
packages/assets/src/package.ts:248
seal function
async function seal(key: CryptoKey, path: string, plain: Uint8Array, compress: boolean): Promise<Uint8Array>flag(1) iv(12) ciphertext+tag: flag 1 = the plaintext was deflated first. The path is authenticated, not secret.
packages/assets/src/security.ts:153
segmentSource function
function segmentSource(parts: Uint8Array[]): ByteSourceOne file kept as pieces: the bytes it was opened from and the tails appended to it since. A save then costs the size of its tail, not a copy of
the file; slice joins only the pieces the range touches.
packages/assets/src/zip.ts:24
sha256 function
async function sha256(data: Uint8Array): Promise<string>packages/assets/src/package.ts:617
signCatalog function
async function signCatalog(catalog: Omit<Catalog, 'signature'> | Catalog, privateKey: string): Promise<Catalog>packages/assets/src/catalog.ts:111
signManifest function
async function signManifest(manifest: object, privateKey: string): Promise<PackageSignature>packages/assets/src/security.ts:76
tierSource function
function tierSource(source: PackageSource, game: GameBlock | undefined, tier: 'desktop' | 'mobile' | 'low'): PackageSourceA package seen through one quality tier: the files under the tier's folder (game.tiers.<tier>, ending in /) replace
the files of the same path in the root, and the tier folders themselves disappear from the listing. A tier the game does not name is the root.
packages/assets/src/package.ts:760
toBase64 function
function toBase64(u: Uint8Array): stringpackages/assets/src/security.ts:41
uastcTarget function
function uastcTarget(caps: Ktx2Caps, nonColour = false): Ktx2Targetthe block format Babylon picks for UASTC data (nonColour: a normal / roughness map, which skips the RGBA-less fallbacks)
packages/assets/src/ktx2.ts:75
unpackFiles function
async function unpackFiles(pkg: FerretPackage): Promise<{ path: string; data: Uint8Array }[]>The files of a package as a plain list, the manifest first, for writing out as a folder (ferret unpack). An encrypted package must be unlocked.
packages/assets/src/package.ts:735
unseal function
async function unseal(key: CryptoKey, path: string, sealed: Uint8Array): Promise<Uint8Array>packages/assets/src/security.ts:163
urlSource function
async function urlSource(url: string, fetchFn: typeof fetch = (u, i) => fetch(u, i)): Promise<ByteSource>A package on a server. Uses range requests when the server has them (only the pieces asked for travel); otherwise the whole file
is downloaded once. fetchFn replaces fetch (tests, a platform file API).
packages/assets/src/zip.ts:50
validateCatalog function
function validateCatalog(c: unknown): PackageIssue[]The problems of a catalog, each with a JSON path and a message (like the level manifest's). Empty when it is valid.
packages/assets/src/catalog.ts:67
validateCatalogAssets function
function validateCatalogAssets(assets: unknown, packages: Record<string, unknown> | undefined): PackageIssue[]Problems of the catalog's assets section. packages is the catalog's own list.
packages/assets/src/library.ts:338
validateGameBlock function
function validateGameBlock(g: unknown, base = '/game'): PackageIssue[]The game block against its schema; issue paths start at base (/game).
packages/assets/src/package.ts:88
validateLibraryItem function
function validateLibraryItem(item: unknown, o: ItemCheckOptions = {}): PackageIssue[]The problems of an item file (a JSON path and a message each). Empty when it is usable.
packages/assets/src/library.ts:189
validatePackageManifest function
function validatePackageManifest(m: unknown): PackageIssue[]Check a ferret.json against the schema. An empty list means it is usable.
packages/assets/src/package.ts:124
verifyManifestSignature function
async function verifyManifestSignature(manifest: { signature?: PackageSignature }): Promise<{ valid: boolean; keyId?: string; reason?: string }>Does the signature in the manifest match its content? (Says nothing about whether the key is one to trust.)
packages/assets/src/security.ts:87
writeZip function
async function writeZip(files: ZipFile[]): Promise<Uint8Array>Write a zip. Paths use /; files are written in the order given.
packages/assets/src/zip.ts:232
zipSource function
async function zipSource(src: ByteSource): Promise<PackageSource>packages/assets/src/package.ts:287
Interfaces
BodyPart interface
interface BodyPartA part of a body split out into a node of its own, so it can be hidden alone (the head from its own first-person camera).
node: string | |
triangles?: number |
packages/assets/src/library.ts:82
BuildCatalogInput interface
interface BuildCatalogInputid: string | |
version: string | |
base: string | |
engine?: string | |
packages: PackedPackage[] | |
levels: Record<string, CatalogLevel> | |
assets?: Record<string, CatalogAsset> | library items (see library.ts): the section is built in before the catalog is validated and signed |
signKey?: string |
packages/assets/src/catalog.ts:246
ByteSource interface
interface ByteSourceWhere bytes come from. A Blob (a File, a download), a byte array, or a URL read by HTTP range requests.
readonly size: number | |
slice(start: number, end: number): Promise<Uint8Array> |
packages/assets/src/zip.ts:9
CacheBackend interface
interface CacheBackendByte storage keyed by string. A key is a content hash, or u:<url>; the cache above decides what is kept.
readonly name: string | |
get(key: string): Promise<Uint8Array | null> | |
put(key: string, bytes: Uint8Array): Promise<void> | |
delete(key: string): Promise<void> | |
keys(): Promise<string[]> |
packages/assets/src/persist.ts:5
CacheEntry interface
interface CacheEntrysize: number | |
used: number | |
pkg: string | |
version?: string | |
url?: string |
packages/assets/src/persist.ts:116
CacheEnv interface
interface CacheEnvcaches?: CacheStorage | |
indexedDB?: IDBFactory |
packages/assets/src/persist.ts:97
CacheInfo interface
interface CacheInfobackend: string | |
used: number | |
cap: number | |
entries: number | |
packages: Record<string, { bytes: number; files: number; pinned: boolean }> |
packages/assets/src/persist.ts:117
CacheMenuItem interface
interface CacheMenuItemid: string | |
label: string | |
value: () => string | |
disabled?: boolean | |
submenu: { title: string; items: { id: string; label: string; action?: () => void }[] } |
packages/assets/src/cache-menu.ts:5
Catalog interface
interface Catalogformat: number | |
id: string | |
version: string | changes with every publish |
engine?: string | the Ferret versions this content needs (a range) |
base: string | |
packages: Record<string, CatalogPackage> | |
levels: Record<string, CatalogLevel> | |
assets?: Record<string, CatalogAsset> | library items: where to find a texture, material, character or clip set without opening a package |
signature?: PackageSignature |
packages/assets/src/catalog.ts:42
CatalogAsset interface
interface CatalogAssetThe assets section of a catalog: where to find an item without opening any package.
package: string | |
type: LibraryItemType | |
name: string | |
tags?: string[] | |
preview?: string | path of the small preview, relative to the catalog's base |
bytes?: Partial<Record<LibraryTier | 'all', number>> | bytes per tier (characters), or one all entry |
skeleton?: string | characters and clip sets: the skeleton id, so a clip set that fits a body is found without opening any package |
compatible?: string[] | clip sets: other skeletons whose bone names these clips bind to |
packages/assets/src/library.ts:322
CatalogLevel interface
interface CatalogLevelpackage: string | |
level: string | |
name?: string | |
tags?: string[] | |
data?: Record<string, unknown> | free: handed to the game, never interpreted by the engine |
prefetch?: string[] | started at idle priority once this level is running |
bundled?: boolean | inside the app: never fetched |
packages/assets/src/catalog.ts:29
CatalogPackage interface
interface CatalogPackageurl: string | relative to base, or absolute |
version: string | |
size: number | bytes of the whole file: the progress total |
sha256?: string | sha256 of the whole file (the publisher's check, pkg.verify of a downloaded copy) |
manifest: string | sha256 of the package's ferret.json: it holds the hash of every file, so this one hash vouches for all of them (packages are read by range, not whole) |
tiers?: Record<string, string> | another file per device tier, by tier name (low, high) |
mirrors?: string[] | the same file elsewhere, tried when the first copy fails its hash |
packages/assets/src/catalog.ts:13
CatalogPolicy interface
interface CatalogPolicytrustedKeys?: Iterable<string> | key ids the game ships. A signed catalog from another key is refused. |
allowUnsigned?: boolean | development builds only: accept an unsigned catalog (the UI shows a badge: unsigned in the result) |
engineVersion?: string | the engine's own version, checked against the catalog's engine range |
packages/assets/src/catalog.ts:116
CharacterItem interface
interface CharacterItem extends ItemBasetype: 'character' | |
kind: CharacterKind | |
tiers: Partial<Record<LibraryTier, TierFile>> | |
skeleton?: string | skeleton id (a clip set is for one skeleton) |
clips?: string[] | clip set item ids, resolved by the skeleton's bone names |
graph?: string | |
movement?: string | |
morphs?: string | |
height?: number | metres, measured from the bind pose |
facing?: '+Z' | '-Z' | '+X' | '-X' | the axis the toes point along in the model |
bodyParts?: Record<string, BodyPart> | body parts that sit in nodes of their own in the model file (head): same node name in every tier |
sockets?: Record<string, Socket> | named places to hold things: grip_r, grip_l, saddle (attach of the library) |
parts?: { id: string; tint?: [number, number, number, number] }[] | an assembled character: the ids of the parts it was made of |
packages/assets/src/library.ts:84
ClipsItem interface
interface ClipsItem extends ItemBasetype: 'clips' | |
skeleton: string | |
compatible?: string[] | other skeleton ids whose bone names make these clips bind without a retarget |
file: string | |
bytes: number | |
clips: { name: string; duration: number; additive?: boolean; loop?: boolean; notifies?: { name: string; time: number }[] }[] | per clip: its length, whether it loops, and the moments named in it (hit-start, hit-end, parry-start...; clips made in Ferret carry them, D4 of the fight demo) |
packages/assets/src/library.ts:107
CodePolicy interface
interface CodePolicyorigin?: boolean | the package comes from where the game itself does (its own site, or inside the signed OS Beroun app): its code is the game's |
trusted?: Iterable<string> | key ids the reader trusts; without it any valid signature is enough |
packages/assets/src/package.ts:385
DependencyReport interface
interface DependencyReportid: string | |
deps: { id: string; range: string; found?: string; ok: boolean; message?: string }[] | direct dependencies, in order, each with what was found |
packages/assets/src/package.ts:677
EngineVersions interface
interface EngineVersionsThe versions of the engine that is running (FERRET_VERSION and BABYLON_VERSION of @ferret/core).
ferret: string | |
babylon: string |
packages/assets/src/package.ts:254
FolderIO interface
interface FolderIOWhat a folder package needs: its file names and a way to read one. (Node's fs, a server's index, a platform file API.)
list(): Promise<string[]> | string[] | |
read(path: string): Promise<Uint8Array> |
packages/assets/src/package.ts:299
GameBlock interface
interface GameBlockWhat createGame is given, as data in the manifest of a game or a project.
input?: { actions?: Record<string, unknown>; bindings?: Record<string, unknown> } | |
fov?: { baseVertical?: number; minHorizontal?: number } | |
physicsLayers?: string[] | |
clearColor?: [number, number, number] | |
touchControls?: boolean | |
tiers?: Partial<Record<'desktop' | 'mobile' | 'low', string>> | quality variants of the levels in one package: a folder per tier, chosen by the player (desktop, mobile, low) |
packages/assets/src/package.ts:20
Handle interface
interface Handlereadonly ref: string | |
readonly bytes: Uint8Array | |
readonly size: number | |
blobUrl(): string |
packages/assets/src/store.ts:50
ItemCheckOptions interface
interface ItemCheckOptionsdependencies?: Iterable<string> | ids of the packages this one depends on (and its own id as own): ferret:// references outside them are refused |
own?: string | |
has?: (path: string) => boolean | does a path exist in the package? When given, files an item names are checked |
packages/assets/src/library.ts:180
Ktx2Caps interface
interface Ktx2Capsastc?: boolean | |
bptc?: boolean | |
s3tc?: boolean | |
pvrtc?: boolean | |
etc2?: boolean | |
etc1?: boolean |
packages/assets/src/ktx2.ts:33
Ktx2Options interface
interface Ktx2Optionsbase?: string | the folder of KTX2_FILES (default: ktx2/ under globalThis.FERRET_LIB, else lib/ferret/); relative to the page |
workers?: number | decoder workers (default: Babylon's: half the cores, at most 4); 0 decodes on the page's own thread |
packages/assets/src/ktx2.ts:24
LibrarySource interface
interface LibrarySourceWhere an item came from: informational, nothing reads it at run time.
project?: string | |
ue?: string | |
master?: string | |
exported?: string | |
params?: Record<string, unknown> |
packages/assets/src/library.ts:14
LoadedCatalog interface
interface LoadedCatalogcatalog: Catalog | |
url: string | |
stale: boolean | |
unsigned: boolean | |
keyId?: string |
packages/assets/src/catalog.ts:144
LoadProgress interface
interface LoadProgressurl: string | |
loaded: number | |
total: number |
packages/assets/src/cache.ts:14
MaterialDef interface
interface MaterialDefA superset of glTF's PBR material that maps to Babylon's PBRMaterial one to one.
model?: 'pbr' | 'unlit' | |
baseColor?: [number, number, number, number] | |
metallic?: number | |
roughness?: number | |
maps?: Partial<Record<MapSlot, string>> | ferret://<package>/<path> references to the maps; the package must be a dependency of the one holding the material |
normalScale?: number | |
uv?: { scale?: [number, number]; offset?: [number, number]; rotation?: number } | |
worldAligned?: { size: number } | projected by world position (triplanar), one repeat per size metres |
alpha?: { mode: AlphaMode; cutoff?: number } | |
doubleSided?: boolean | |
specular?: number | |
ior?: number | |
emissive?: [number, number, number] | |
emissiveStrength?: number |
packages/assets/src/library.ts:48
MaterialItem interface
interface MaterialItem extends ItemBase, MaterialDeftype: 'material' | |
variants?: Record<string, MaterialDef> | values that differ per variant, over the base values |
packages/assets/src/library.ts:67
ModelCollision interface
interface ModelCollisionHow a model collides when a game does not want its triangles: a box or a cylinder from the model's own base, in metres.
shape: 'box' | 'cylinder' | |
size: [number, number, number] | |
center: [number, number, number] |
packages/assets/src/library.ts:119
ModelInstance interface
interface ModelInstanceWhat a loaded model gives a game.
root: BABYLON.TransformNode | the model's top node(s): move this to place the model |
meshes: BABYLON.AbstractMesh[] | |
skeletons: BABYLON.Skeleton[] | |
animations: Record<string, BABYLON.AnimationGroup> | animation groups by name (stopped: a graph or a viewer plays them) |
dispose(): void | remove this instance from the scene; the cached source stays until released |
packages/assets/src/cache.ts:3
ModelItem interface
interface ModelItem extends ItemBaseA static mesh with its materials and textures in one glTF per tier (trees, rocks, props): placed by a game as many times as it likes (thin instances).
type: 'model' | |
tiers: Partial<Record<LibraryTier, TierFile>> | |
bounds: { min: [number, number, number]; max: [number, number, number] } | bounding box in metres of the model at its origin |
materials?: { name: string; alpha?: AlphaMode; doubleSided?: boolean }[] | the materials in the file and how they draw (mask for leaves) |
collision?: ModelCollision | |
up?: '+Y' | the axis that is up in the file (always +Y; written so a reader need not guess) |
packages/assets/src/library.ts:122
PackageEncryption interface
interface PackageEncryptionalg: 'AES-256-GCM' | |
kdf: 'PBKDF2-SHA256' | 'none' | |
salt?: string | base64 (kdf PBKDF2) |
iterations?: number | |
check: string | a sealed constant: opening it tells a wrong password from a damaged file (kdf none: the key's id) |
packages/assets/src/security.ts:24
PackageInfo interface
interface PackageInfoid: string | |
name: string | |
version: string | |
kind: PackageKind | |
format: number | |
files: number | |
bytes: number | |
levels: string[] | |
assets: string[] | |
behaviours: string[] | |
dependencies: Record<string, string> | |
hasCode: boolean | |
signed: boolean | |
encrypted: boolean | |
locked: boolean | |
entry?: PackageManifest['entry'] | |
codeEntry?: string | the module the code loader runs, if any (code.entry, else a game's entry.code) |
packages/assets/src/package.ts:369
PackageIssue interface
interface PackageIssuepath: string | |
message: string |
packages/assets/src/package.ts:80
PackageManifest interface
interface PackageManifestformat: number | |
id: string | lower case letters, digits, ., _, - |
name: string | |
version: string | semver major.minor.patch |
kind: PackageKind | |
engine?: { ferret?: string; babylon?: string; /** the parts of Babylon the game loads at start, and the ones a feature may ask for later (stage P6; without them the full build is loaded) */ parts?: string[]; lazyParts?: string[]; /** a standalone game: the exact release whose engine files are inside the package, under `engine/` */ bundled?: string } | the versions it was built and tested with |
dependencies?: Record<string, string> | other packages by id and version range (1.2.0, ^1.2.0, ~1.2.0, >=1.2.0, *) |
modules?: string[] | a game only: the engine modules its code and levels use, written by the compiler (a project has none) |
babylon?: string[] | a game only: the Babylon parts it uses, written by the compiler |
keep?: string[] | a game or a project: files the compiler must keep although no literal path reaches them (globs, * within a folder, ** across) |
game?: GameBlock | a game or a project: what createGame gets |
for?: { game: string; version: string } | a package of levels made for a game: which game and versions it fits |
entry?: { code?: string; level: string; spawn?: string; ui?: string } | a game or a project: the code module (a game: built, under code/; a project: the source), the level to start in, the spawn, the UI fragment |
code?: { entry: string } | a game or a library: the code module the code loader runs (one bundled ES module under code/ exporting install) |
provides?: PackageProvides | what it offers to others |
files?: Record<string, string> | sha256 (hex) of every file but this one |
encryption?: PackageEncryption | present when the files are encrypted (see ./security); ferret.json itself never is |
signature?: PackageSignature | a signature over the whole manifest (so over files) made by the publisher; checked by whoever decides to trust the code |
packages/assets/src/package.ts:31
PackageProvides interface
interface PackageProvidesWhat a package offers, so that a level or a program can be resolved to the packages it needs without running their code. The
lists after behaviours are names a library's code registers: physics capabilities, actuator kinds, flow sources, forms of the
motion language, commands and level manifest keys.
levels?: string[] | |
assets?: string[] | |
behaviours?: { name: string; params?: Record<string, unknown> }[] | |
capabilities?: string[] | |
kinds?: string[] | |
flowSources?: string[] | |
forms?: string[] | |
commands?: string[] | |
levelKeys?: string[] |
packages/assets/src/package.ts:72
PackageSignature interface
interface PackageSignaturealg: 'ES256' | |
key: string | the public key, SPKI in base64 |
keyId: string | first 16 bytes of the sha256 of the public key, in hex |
value: string | base64 of the raw (r || s) signature |
packages/assets/src/security.ts:14
PackageSource interface
interface PackageSourceReads the files of a package, whether it is a zip or a folder.
readonly kind: 'zip' | 'folder' | 'memory' | |
list(): string[] | every file path (the manifest included) |
has(path: string): boolean | |
size(path: string): number | bytes of the file in the package (uncompressed); 0 when unknown |
read(path: string): Promise<Uint8Array> |
packages/assets/src/package.ts:277
PackedPackage interface
interface PackedPackagebytes: Uint8Array | |
manifest: PackageManifest | |
tiers?: Record<string, Uint8Array> |
packages/assets/src/catalog.ts:244
PackOptions interface
interface PackOptionsBuild a .ferret from a source (a folder, another package, a draft): the manifest gets the sha256 of every file, the levels
and assets it provides when they are not named, and the zip is written the same way every time (sorted, no dates). Files that
are compressed already are stored, so a reader slices them out without inflating.
manifest?: Partial<PackageManifest> | |
engine?: PackageManifest['engine'] | |
encrypt?: KeyInput | seal every file but the manifest (a password, or a 32-byte key in base64) |
iterations?: number | PBKDF2 rounds for a password (default 310000; tests use less) |
sign?: { privateKey: string } | sign the finished manifest with this private key (base64 PKCS8, from generateSigningKey) |
decrypt?: boolean | pack an encrypted package's files as plain ones (without it, repacking an encrypted package needs encrypt) |
packages/assets/src/package.ts:631
PersistentCacheOptions interface
interface PersistentCacheOptionscap?: number | Hard cap in bytes (defaults: 400 MB desktop, 200 MB phone, 100 MB console: the caller picks). |
quota?: () => Promise<number | undefined> | The browser's quota (navigator.storage.estimate().quota) when it has one; the cache stays under 80% of it. |
now?: () => number | |
partialMaxAgeMs?: number | Partial downloads older than this are removed (24 h). |
packages/assets/src/persist.ts:120
RequireOptions interface
interface RequireOptionspriority?: StreamPriority | |
signal?: AbortSignal | |
sha256?: string | sha256 (hex) from a signed catalog or package manifest. Without it the file is not verified (development only). |
size?: number | Size in bytes when the catalog knows it: the progress total is exact from the start. |
mirrors?: string[] | |
pkg?: string | The package (or level) the file belongs to: the unit of eviction, pinning and clear. |
version?: string | |
group?: string | Files asked with the same group add up into one progress (a travel). |
packages/assets/src/store.ts:35
SigningKey interface
interface SigningKeypublicKey: string | |
privateKey: string | |
keyId: string |
packages/assets/src/security.ts:56
Socket interface
interface SocketA place on a body where something is held: a bone and an offset from it (metres, radians as a quaternion), kept in the item so every game puts a weapon in a hand the same way.
bone: string | |
position?: [number, number, number] | |
rotation?: [number, number, number, number] | |
grip?: { point: [number, number, number]; axis: [number, number, number]; radius: number; spacing?: number; edge?: [number, number, number] } | how the thing in this socket is held, in the frame of bone: the middle of the grip, its axis (toward the blade), its radius, the distance to the second hand (toward the pommel),
and for a blade the way its edge faces (across the axis; a two-edged blade cuts along it either way) |
packages/assets/src/library.ts:74
StoreOptions interface
interface StoreOptionsfetch?: typeof fetch | |
origins?: string[] | Origins files may come from (https://cdn.example.com). Empty or missing: any origin that passes the https rule. |
base?: string | Relative refs are resolved against this. |
concurrency?: number | |
stallMs?: number | No bytes for this long: the attempt is abandoned and retried (resumed when it can). |
retries?: number | |
backoffMs?: number | |
maxFileBytes?: number | A file bigger than this is refused (the catalog's size is the usual limit; this is the engine's cap). |
cache?: PersistentCache | The persistent cache; without one every file lives in memory for the session. |
random?: () => number | |
sleep?: (ms: number, signal?: AbortSignal) => Promise<void> |
packages/assets/src/store.ts:16
StreamProgress interface
interface StreamProgressloaded: number | |
total: number | |
files: number | |
filesDone: number | |
rate: number | |
eta: number |
packages/assets/src/store.ts:14
TextureItem interface
interface TextureItem extends ItemBasetype: 'texture' | |
maps: Partial<Record<'basecolor' | 'normal' | 'orm' | 'emissive', string>> | the maps present: path inside the package per channel (basecolor, normal, orm, emissive), per tier a folder or file set |
tiling?: number | metres of the world one repeat of the texture covers; absent when unknown |
seamless?: boolean | |
size?: number | pixels of the largest side at full size |
tiers?: Partial<Record<LibraryTier, { bytes: number; side: number }>> | |
aliases?: string[] | other names of the same image (the same pixels exported under another name) |
packages/assets/src/library.ts:29
TierFile interface
interface TierFilefile: string | |
bytes: number | |
triangles?: number | |
texCap?: number | |
bones?: number |
packages/assets/src/library.ts:16
VerifyResult interface
interface VerifyResultsigned: boolean | |
valid: boolean | the signature matches the manifest (and, when files could be read, every file matches the manifest) |
keyId?: string | |
trusted: boolean | valid, and the key is in (true for any valid signature when no list was given) |
problems: string[] |
packages/assets/src/package.ts:392
ZipEntryInfo interface
interface ZipEntryInfopath: string | |
size: number | |
compressedSize: number | |
method: 0 | 8 | |
crc: number | |
offset: number |
packages/assets/src/zip.ts:72
ZipFile interface
interface ZipFilepath: string | |
data: Uint8Array | |
compress?: boolean |
packages/assets/src/zip.ts:190
Types
AlphaMode type
type AlphaMode = 'opaque' | 'mask' | 'blend'packages/assets/src/library.ts:43
CatalogFailure type
type CatalogFailure = 'invalid' | 'signature' | 'untrusted' | 'unsigned' | 'engine' | 'network'packages/assets/src/catalog.ts:57
CatalogPackageSource type
type CatalogPackageSource = PackageSource & { manifest: PackageManifest; cached(paths: string[]): Promise<boolean>; /** `id@version`: the unit the cache evicts and pins */ tag: string }packages/assets/src/catalog.ts:240
CharacterKind type
type CharacterKind = 'human' | 'animal' | 'creature' | 'part'packages/assets/src/library.ts:10
KeyInput type
type KeyInput = { password: string } | { key: string }What opens an encrypted package: a password, or the 32-byte key in base64.
packages/assets/src/security.ts:35
Ktx2Target type
type Ktx2Target = 'ASTC4x4' | 'BC7' | 'ETC2' | 'ETC1' | 'BC3' | 'BC1' | 'PVRTC' | 'RGBA8'the block format a KTX2 map ends up in on a device
packages/assets/src/ktx2.ts:32
LibraryItem type
type LibraryItem = TextureItem | MaterialItem | CharacterItem | ClipsItem | ModelItempackages/assets/src/library.ts:134
LibraryItemType type
type LibraryItemType = 'texture' | 'material' | 'character' | 'clips' | 'model'packages/assets/src/library.ts:6
LibraryTier type
type LibraryTier = 'desktop' | 'mobile' | 'low'packages/assets/src/library.ts:8
MapSlot type
type MapSlot = 'baseColor' | 'normal' | 'orm' | 'emissive' | 'opacity'packages/assets/src/library.ts:44
PackageInput type
type PackageInput = Blob | Uint8Array | ArrayBuffer | string | ByteSource | PackageSourcepackages/assets/src/package.ts:380
PackageKind type
type PackageKind = 'game' | 'levels' | 'assets' | 'library' | 'project'packages/assets/src/package.ts:11
StoreStatus type
type StoreStatus = 'missing' | 'queued' | 'loading' | 'cached' | 'failed'packages/assets/src/store.ts:51
StreamFailure type
type StreamFailure = 'network' | 'http' | 'timeout' | 'integrity' | 'origin' | 'aborted' | 'size' | 'build'packages/assets/src/stream-error.ts:3
StreamPriority type
type StreamPriority = 'high' | 'normal' | 'low' | 'idle'packages/assets/src/store.ts:10
Constants
CATALOG_FORMAT const
const CATALOG_FORMAT: 1packages/assets/src/catalog.ts:11
CHARACTER_KINDS const
const CHARACTER_KINDS: readonly CharacterKind[]packages/assets/src/library.ts:11
GAME_TIERS const
const GAME_TIERS: readonly ["desktop", "mobile", "low"]packages/assets/src/package.ts:29
KTX2_BYTES_PER_TEXEL const
const KTX2_BYTES_PER_TEXEL: Record<Ktx2Target, number>video memory per texel (RGBA unless noted; BC1, ETC1 and PVRTC 4 bpp carry no alpha and take half a byte)
packages/assets/src/ktx2.ts:36
KTX2_FILES const
const KTX2_FILES: readonly ["babylon.ktx2Decoder.js", "msc_basis_transcoder.js", "msc_basis_transcoder.wasm", "zstddec.wasm", "uastc_astc.wasm", "uastc_bc7.wasm", "uastc_r8_unorm.wasm", "uastc_rg8_unorm.wasm", "uastc_rgba8_srgb_v2.wasm", "uastc_rgba8_unorm_v2.wasm"]the files ktx2/ holds, as the build copies them (tools/build.mjs, tools/modules/build.mjs, the game builds)
packages/assets/src/ktx2.ts:19
LIBRARY_ITEM_TYPES const
const LIBRARY_ITEM_TYPES: readonly LibraryItemType[]packages/assets/src/library.ts:7
LIBRARY_TIERS const
const LIBRARY_TIERS: readonly LibraryTier[]packages/assets/src/library.ts:9
MANIFEST_FILE const
const MANIFEST_FILE: "ferret.json"packages/assets/src/package.ts:10
MAP_SLOTS const
const MAP_SLOTS: readonly MapSlot[]packages/assets/src/library.ts:45
PACKAGE_EXTENSIONS const
const PACKAGE_EXTENSIONS: Partial<Record<PackageKind, string>>The file name extension a kind goes by. .fex is a finished game and .fprj a project in development; both are
.ferret packages. A name ending in .fex or .fprj must carry the matching kind; other names are not checked.
packages/assets/src/package.ts:17
PACKAGE_FORMAT const
const PACKAGE_FORMAT: 1packages/assets/src/package.ts:9
PROVIDED_NAMES const
const PROVIDED_NAMES: readonly ["capabilities", "kinds", "flowSources", "forms", "commands", "levelKeys"]The provides lists that are plain names.
packages/assets/src/package.ts:78
SEAL_OVERHEAD const
const SEAL_OVERHEAD: numberWhat a sealed file adds to its size.
packages/assets/src/security.ts:173
ZIP_LIMITS const
const ZIP_LIMITS: { maxEntries: number; maxEntryBytes: number; maxTotalBytes: number; maxJsonBytes: number; }Limits that keep a hostile package from exhausting memory: a zip bomb is a few kilobytes that inflate to
gigabytes, or a directory of millions of entries. An engine cap, checked on the declared sizes when the directory is read and on
the real output while a file inflates. Change them before reading (a game with huge cooked files may raise maxEntryBytes).
packages/assets/src/zip.ts:91