FERRETGame engine over Babylon.js
Ferret

@ferret/assets

Loading and caching of glTF, KTX2 and packed assets, .ferret packages and their security checks.

160 exports · source in packages/assets/src

Classes

AssetCache class

class AssetCache

Loads 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) => voidCalled 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): voidDrop one reference; at zero the source and its instances are freed.
dispose(): void

packages/assets/src/cache.ts:30

AssetStore class

class AssetStore
constructor(o: StoreOptions = {})
resolve(ref: string): stringResolve 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 = {}): voidAsk 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): voidAbort 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 | undefinedThe 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 } = {}): voidKeep 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 CacheBackend

Cache 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 Error
constructor(public readonly code: CatalogFailure, message: string, public readonly issues: PackageIssue[] = [])

packages/assets/src/catalog.ts:58

ChunkedBackend class

class ChunkedBackend implements CacheBackend

Splits 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 FerretPackage
get source(): PackageSourcethe files as they can be read now: decrypted once the package is unlocked
get encrypted(): boolean
get dirty(): booleanthe changes made through draft() that are not packed yet
draft(): PackageDraftStart 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(): booleanencrypted 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(): booleanDoes the package hold code? (kinds assets and levels must not.)
get codeEntry(): string | undefinedThe 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 CacheBackend

IndexedDB 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 Error

Opening 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 CacheBackend
readonly 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 PackageSource

A 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(): voidRemember 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): numberbytes 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 PackageSet

The 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 } | nullWhere 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 PersistentCache
constructor(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 Error
constructor(public readonly needed: number)

packages/assets/src/persist.ts:118

Sha256 class

class Sha256
update(data: Uint8Array): this
hex(): stringThe digest as hex. The hasher is spent afterwards.

packages/assets/src/sha256.ts:10

StreamError class

class StreamError extends Error
constructor(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 Error
constructor(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): ByteSource

packages/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): ByteSource

packages/assets/src/zip.ts:17

canonicalManifest function

function canonicalManifest(m: object): Uint8Array

The 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 | null

A 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): number

packages/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): Ktx2Target

the 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): string

packages/assets/src/cache-menu.ts:10

fromBase64 function

function fromBase64(b: string): Uint8Array

packages/assets/src/security.ts:46

generateEncryptionKey function

function generateEncryptionKey(): string

packages/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): boolean

Files 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) => string

Path 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 | null

Resolve 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 = {}): string

where 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): number

Video 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): string

Absolute 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 } | null

ferret://<package>/<path> -> its parts, or null

packages/assets/src/library.ts:143

publishedPath function

function publishedPath: (hash: string, ext?: string) => string

Where 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): boolean

Does 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[]): ByteSource

One 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'): PackageSource

A 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): string

packages/assets/src/security.ts:41

uastcTarget function

function uastcTarget(caps: Ktx2Caps, nonColour = false): Ktx2Target

the 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

zipDeadShare function

async function zipDeadShare(src: ByteSource): Promise<number>

The share (0..1) of a zip that no live file and no current directory uses: what compactZip would drop.

packages/assets/src/zip.ts:277

zipSource function

async function zipSource(src: ByteSource): Promise<PackageSource>

packages/assets/src/package.ts:287

Interfaces

BodyPart interface

interface BodyPart

A 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 BuildCatalogInput
id: 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 ByteSource

Where 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 CacheBackend

Byte 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 CacheEntry
size: number
used: number
pkg: string
version?: string
url?: string

packages/assets/src/persist.ts:116

CacheEnv interface

interface CacheEnv
caches?: CacheStorage
indexedDB?: IDBFactory

packages/assets/src/persist.ts:97

CacheInfo interface

interface CacheInfo
backend: 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 CacheMenuItem
id: 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 Catalog
format: number
id: string
version: stringchanges with every publish
engine?: stringthe 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 CatalogAsset

The assets section of a catalog: where to find an item without opening any package.

package: string
type: LibraryItemType
name: string
tags?: string[]
preview?: stringpath 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?: stringcharacters 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 CatalogLevel
package: 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?: booleaninside the app: never fetched

packages/assets/src/catalog.ts:29

CatalogPackage interface

interface CatalogPackage
url: stringrelative to base, or absolute
version: string
size: numberbytes of the whole file: the progress total
sha256?: stringsha256 of the whole file (the publisher's check, pkg.verify of a downloaded copy)
manifest: stringsha256 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 CatalogPolicy
trustedKeys?: Iterable<string>key ids the game ships. A signed catalog from another key is refused.
allowUnsigned?: booleandevelopment builds only: accept an unsigned catalog (the UI shows a badge: unsigned in the result)
engineVersion?: stringthe engine's own version, checked against the catalog's engine range

packages/assets/src/catalog.ts:116

CharacterItem interface

interface CharacterItem extends ItemBase
type: 'character'
kind: CharacterKind
tiers: Partial<Record<LibraryTier, TierFile>>
skeleton?: stringskeleton 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?: numbermetres, 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 ItemBase
type: '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 CodePolicy
origin?: booleanthe 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 DependencyReport
id: 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 EngineVersions

The 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 FolderIO

What 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 GameBlock

What 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 Handle
readonly ref: string
readonly bytes: Uint8Array
readonly size: number
blobUrl(): string

packages/assets/src/store.ts:50

ItemCheckOptions interface

interface ItemCheckOptions
dependencies?: 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) => booleandoes a path exist in the package? When given, files an item names are checked

packages/assets/src/library.ts:180

Ktx2Caps interface

interface Ktx2Caps
astc?: boolean
bptc?: boolean
s3tc?: boolean
pvrtc?: boolean
etc2?: boolean
etc1?: boolean

packages/assets/src/ktx2.ts:33

Ktx2Options interface

interface Ktx2Options
base?: stringthe folder of KTX2_FILES (default: ktx2/ under globalThis.FERRET_LIB, else lib/ferret/); relative to the page
workers?: numberdecoder 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 LibrarySource

Where 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 LoadedCatalog
catalog: Catalog
url: string
stale: boolean
unsigned: boolean
keyId?: string

packages/assets/src/catalog.ts:144

LoadProgress interface

interface LoadProgress
url: string
loaded: number
total: number

packages/assets/src/cache.ts:14

MaterialDef interface

interface MaterialDef

A 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, MaterialDef
type: 'material'
variants?: Record<string, MaterialDef>values that differ per variant, over the base values

packages/assets/src/library.ts:67

ModelCollision interface

interface ModelCollision

How 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 ModelInstance

What a loaded model gives a game.

root: BABYLON.TransformNodethe 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(): voidremove this instance from the scene; the cached source stays until released

packages/assets/src/cache.ts:3

ModelItem interface

interface ModelItem extends ItemBase

A 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 PackageEncryption
alg: 'AES-256-GCM'
kdf: 'PBKDF2-SHA256' | 'none'
salt?: stringbase64 (kdf PBKDF2)
iterations?: number
check: stringa 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 PackageInfo
id: 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?: stringthe module the code loader runs, if any (code.entry, else a game's entry.code)

packages/assets/src/package.ts:369

PackageIssue interface

interface PackageIssue
path: string
message: string

packages/assets/src/package.ts:80

PackageManifest interface

interface PackageManifest
format: number
id: stringlower case letters, digits, ., _, -
name: string
version: stringsemver 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?: GameBlocka 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?: PackageProvideswhat it offers to others
files?: Record<string, string>sha256 (hex) of every file but this one
encryption?: PackageEncryptionpresent when the files are encrypted (see ./security); ferret.json itself never is
signature?: PackageSignaturea 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 PackageProvides

What 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 PackageSignature
alg: 'ES256'
key: stringthe public key, SPKI in base64
keyId: stringfirst 16 bytes of the sha256 of the public key, in hex
value: stringbase64 of the raw (r || s) signature

packages/assets/src/security.ts:14

PackageSource interface

interface PackageSource

Reads 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): numberbytes of the file in the package (uncompressed); 0 when unknown
read(path: string): Promise<Uint8Array>

packages/assets/src/package.ts:277

PackedPackage interface

interface PackedPackage
bytes: Uint8Array
manifest: PackageManifest
tiers?: Record<string, Uint8Array>

packages/assets/src/catalog.ts:244

PackOptions interface

interface PackOptions

Build 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?: KeyInputseal every file but the manifest (a password, or a 32-byte key in base64)
iterations?: numberPBKDF2 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?: booleanpack 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 PersistentCacheOptions
cap?: numberHard 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?: numberPartial downloads older than this are removed (24 h).

packages/assets/src/persist.ts:120

RequireOptions interface

interface RequireOptions
priority?: StreamPriority
signal?: AbortSignal
sha256?: stringsha256 (hex) from a signed catalog or package manifest. Without it the file is not verified (development only).
size?: numberSize in bytes when the catalog knows it: the progress total is exact from the start.
mirrors?: string[]
pkg?: stringThe package (or level) the file belongs to: the unit of eviction, pinning and clear.
version?: string
group?: stringFiles asked with the same group add up into one progress (a travel).

packages/assets/src/store.ts:35

SigningKey interface

interface SigningKey
publicKey: string
privateKey: string
keyId: string

packages/assets/src/security.ts:56

Socket interface

interface Socket

A 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 StoreOptions
fetch?: typeof fetch
origins?: string[]Origins files may come from (https://cdn.example.com). Empty or missing: any origin that passes the https rule.
base?: stringRelative refs are resolved against this.
concurrency?: number
stallMs?: numberNo bytes for this long: the attempt is abandoned and retried (resumed when it can).
retries?: number
backoffMs?: number
maxFileBytes?: numberA file bigger than this is refused (the catalog's size is the usual limit; this is the engine's cap).
cache?: PersistentCacheThe 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 StreamProgress
loaded: number
total: number
files: number
filesDone: number
rate: number
eta: number

packages/assets/src/store.ts:14

TextureItem interface

interface TextureItem extends ItemBase
type: '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?: numbermetres of the world one repeat of the texture covers; absent when unknown
seamless?: boolean
size?: numberpixels 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 TierFile
file: string
bytes: number
triangles?: number
texCap?: number
bones?: number

packages/assets/src/library.ts:16

VerifyResult interface

interface VerifyResult
signed: boolean
valid: booleanthe signature matches the manifest (and, when files could be read, every file matches the manifest)
keyId?: string
trusted: booleanvalid, 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 ZipEntryInfo
path: string
size: number
compressedSize: number
method: 0 | 8
crc: number
offset: number

packages/assets/src/zip.ts:72

ZipFile interface

interface ZipFile
path: 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 | ModelItem

packages/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 | PackageSource

packages/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: 1

packages/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: 1

packages/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: number

What 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