assets API

← assets · version 0.5.0

28 public items, in source order. Generated from the registry index, which re-derives them from the package source at publish time — so a signature here is the one that version actually ships.

pub enum BlobKind { Bytes, Rgba, Png, Ogg, Wav, Ttf, Woff2 }

What a blob's bytes are, so a loader knows what to do with them without sniffing the content. Rgba is a texture page's own pixels — premultiplied, row-major, four bytes per texel — already decoded by the packer, so the runtime never learns how the art was made.

pub enum DefKind { Sprite, Light }

Whether a definition draws or lights. A light is a placed instance like any other, and this is what says which.

pub struct Blob { bl_key: text, bl_kind: BlobKind = Bytes, bl_bytes: vector<u8> }

Raw bytes under a key — a texture page's pixels, audio, a font, a source PNG.

One blob is the **paging unit**: store_load_key_text fetches the pages this record touches and nothing else. So size a blob like an atlas page rather than like one sprite — a store's minimum image is 8 KiB, and a finer key fetches a page's worth either way.

pub struct PageInfo { pi_name: text, pi_w: integer, pi_h: integer, pi_blob: text }

A texture page's SHAPE, and the blob key its pixels live under.

The dimensions are metadata and the pixels are bulk, so they live in different stores. A loader that needs to know a page is 64x32 — to build UVs, to size a GL texture, to decide what to prefetch — gets that from the boot load without fetching a single texel.

pub struct Cell { ce_key: text, ce_page: text, ce_x: integer, ce_y: integer, ce_w: integer, ce_h: integer, ce_ox: float = 0.0, ce_oy: float = 0.0, ce_proxy: vector<shapes::Rect> }

One sprite's rect within a page, plus the origin a stage places it by.

ce_proxy is the collision proxy DERIVED from this cell's own alpha at pack time (see cell_proxy), in cell-local coordinates. It is empty when nothing derived one — a pack built before proxies, or a cell with no opaque texels — and an empty proxy hits nothing, so a consumer that finds one absent falls back to the cell rect rather than colliding with everything.

pub struct Seq { sq_key: text, sq_page: text, sq_first: integer, sq_count: integer, sq_fps: float = 12.0, sq_mode: integer = 0, sq_cols: integer = 1, sq_rows: integer = 1 }

An animation: a run of cells laid out in one page's grid. Mirrors stage's Sequence, so a loader hands it straight over.

pub struct Def { df_name: text, df_kind: DefKind = Sprite, df_cell: text = "", df_w: float = 0.0, df_h: float = 0.0, df_ox: float = 0.0, df_oy: float = 0.0, df_layer: integer = 0, df_radius: float = 0.0, df_colour: integer = 0, df_power: float = 0.0 }

An object definition — GameMaker's *object*, the thing a room places.

A walk cycle is asset data and not code, so the animation table hangs off the definition (see anim_of) rather than living in a game's source. A light carries its radius, colour and power here and is placed exactly like a sprite.

pub struct Anim { an_key: text, an_def: text, an_action: text, an_facing: integer, an_seq: text }

One row of a definition's animation table: (action, facing) -> sequence. Build an_key as "{def}/{action}/{facing}", which is what anim_of looks up.

pub struct Scene { sc_name: text, sc_w: integer, sc_h: integer }

A room: a named space that placed instances live in.

pub struct Placed { pd_scene: text, pd_order: integer, pd_def: text, pd_x: float = 0.0, pd_y: float = 0.0, pd_depth: float = 0.0, pd_layer: integer = 0, pd_rot: float = 0.0, pd_sx: float = 1.0, pd_sy: float = 1.0, pd_alpha: float = 1.0, pd_colour: integer = 0, pd_visible: boolean = true }

A placed instance — GameMaker's *room instance*.

Definitions plus these are what a scene IS; a flat node dump cannot say that two goblins are the same goblin. The fields mirror stage::Place, so loading a scene is a copy rather than a translation.

pub struct Pack { pages: hash<PageInfo[pi_name]>, cells: hash<Cell[ce_key]>, seqs: hash<Seq[sq_key]>, defs: hash<Def[df_name]>, anims: hash<Anim[an_key]>, scenes: hash<Scene[sc_name]>, placed: index<Placed[pd_scene, pd_order]> }

A pack's METADATA half — every collection a game reads at boot.

Binding through this container is what puts all seven collections in one file: a keyed field shares its container's store, so one persist call writes one file and a reader that declares the same type gets all of it. Read it WHOLE; it is small, every part is needed from the first frame, and being a wrapper struct it cannot be paged anyway. The bytes are the other store's job.

pub fn new_pack() -> Pack

An empty pack — every collection present and none of them holding anything.

Both a packer and a loader start here: a loader declares the pack it is about to fill, then hands it to pack_read.

pub fn meta_path(base: text) -> text

Where a pack's metadata half lives, given the name the pack is known by.

base is a local path or an http(s):// URL — the two halves are named the same way either side of the wire, which is what lets a game develop against a file and ship against a CDN with only the base changed.

pub fn blobs_path(base: text) -> text

Where a pack's bulk half lives. Pass this to prefetch to page a blob out of it.

pub fn blob_put(b: &hash<Blob[bl_key]>, key: text, kind: BlobKind, bytes: const vector<u8>)

Put a blob under its key, ready to be written. A blob already under the key is replaced, which is what a packer re-running over changed art wants.

pub fn pack_write(p: const Pack, blobs: const hash<Blob[bl_key]>, base: text) -> boolean

Write both halves of a pack, answering whether both landed.

A pack is final by the time it ships, so this copies rather than binds: a copied image lays each collection out in its own order, which is what makes one keyed lookup one RUN of pages instead of a scatter — the difference between a range read that pays for a page and one that pays for the file. Writing needs a desktop; a browser reads packs and does not build them.

pub fn pack_read(p: Pack, base: text) -> boolean

Read a pack's metadata half in full, answering whether it arrived.

Give it a pack from new_pack. base is a local path or an http(s):// / file:// URL, and that is the only thing that changes between developing against a file and shipping against a CDN. The bytes are NOT read here — ask for those by key, through prefetch, at a load or level boundary.

A URL is fetched from a source you TRUST: the image is structurally validated, so a corrupt file is refused rather than adopted, but its contents are not authenticated. Serve packs from your own origin, or verify the download yourself with store_load_url before reading it.

pub fn anim_of(p: const Pack, def: text, action: text, facing: integer) -> text

The sequence an action and a facing map to, or "" when the definition has none.

This is the animation table's whole point: anim_of(pack, "goblin", "walk", 2) answers "goblin_walk_n" with nothing in a game's source holding the mapping, and an action the definition does not have answers empty rather than a plausible wrong sequence.

pub fn keys_near(p: const Pack, scene: text, cx: float, cy: float, radius: float) -> vector<text>

Every blob key a scene's instances draw from within radius of (cx, cy).

Ask for this at a load or a level boundary, never inside a frame: a range read is a round trip and a frame is 16 ms, so a frame that DISCOVERS it needs an asset stutters visibly. A negative radius means the whole scene — what a small room and a loading screen both want. Duplicates are dropped, because a page shared by forty instances is still one fetch.

pub fn prefetch(into: hash<Blob[bl_key]>, source: text, keys: const vector<text>) -> integer

Pull keys into into, and answer how many actually COST something.

source is blobs_path(base). A key already resident is skipped, so the answer is the number of round trips this call paid for: what a level boundary wants to log, and what a frame must be able to assert is zero. A key the pack does not hold is skipped too — an absent asset costs a lookup, not a record.

pub fn opaque_texels(ot_px: const vector<u8>, ot_page: const PageInfo, ot_c: const Cell) -> integer

How many texels of this cell are opaque — the thing a proxy stands for, and the denominator of proxy_overshoot.

pub fn cell_proxy(c: const Cell, page: const PageInfo, px: const vector<u8>, bands: integer = PROXY_BANDS) -> vector<shapes::Rect>

Derive c's collision proxy from the page's own alpha, in cell-local coordinates.

Banded on the TIGHTER axis: the derivation runs both ways and keeps the smaller total area. 17 of the 36 corpus sprites band better by column than by row, so choosing per sprite rather than fixing an axis takes the median overshoot from +29.2 % to +22.8 % — for the cost of running a pass twice at pack time, which is a cost nothing at runtime pays.

A cell with no opaque texels derives an EMPTY proxy, and an empty proxy hits nothing. That is the answer a fully transparent cell should give; a single box around it would collide with whatever stood there.

pub fn proxy_overshoot(po_boxes: const vector<shapes::Rect>, po_solid: integer) -> float

How much bigger a derived proxy is than the art it stands for.

The number F7 gates on: <= 1.0 is at most twice the art. Reported per sprite rather than asserted in aggregate, because one adversarial shape — three thin diagonal arrows — misses the bound at every band count, and a bound nothing can violate is not a bound.

pub fn texels(words: const vector<integer>) -> vector<u8>

The RGBA bytes of a row-major buffer of 0xAARRGGBB words.

Both routes into a Tile end here. A graphics::Canvas holds exactly these words in data, and a PNG decoded by imaging answers them one pixel at a time through Pixel.rgba() — so a sprite rendered in this process and the same sprite read back off disk become the same texels by the same arithmetic, rather than by two conversions that agree until one of them is edited.

Alpha is carried STRAIGHT, not premultiplied: see page_grid.

pub struct Tile { ti_key: text, ti_w: integer, ti_h: integer, ti_px: vector<u8>, ti_ox: float = 0.0, ti_oy: float = 0.0 }

One sprite on its way into a page: the key it will be known by, its size, and its texels.

ti_px is ti_w * ti_h * 4 bytes of STRAIGHT-alpha RGBA, row-major. Where those bytes came from is deliberately not this package's business — a decoded PNG and a canvas rendered in this process are the same tile here, which is what lets a sprite authored as text skip the file it used to have to become.

pub fn page_grid(p: Pack, blobs: &hash<Blob[bl_key]>, page: text, blob: text, tiles: const vector<Tile>, cols: integer = 0, pad: integer = 0) -> boolean

Lay tiles out in a grid page, blit their texels into it, and record one Cell per tile — with each cell's collision proxy DERIVED from the alpha as it lands.

The page is sized from the widest and tallest tile, so every cell shares one grid step; pad puts transparent texels between them, which is what a sampler that filters needs to stop a neighbour bleeding in. Answers false — writing nothing — when a tile's byte count disagrees with the size it declares, because a packer that quietly places a truncated sprite is the failure this exists to prevent.

The pixels land under blob as Bytes: STRAIGHT alpha, not premultiplied, because that is what a blend of SRC_ALPHA / ONE_MINUS_SRC_ALPHA expects. BlobKind::Rgba promises premultiplied texels and these are not.

pub fn layout_descriptor() -> text

The pack's own types, field by field, as the store records them.

Read this when the fingerprints below disagree: it names the type and the field whose position moved, which the single number cannot. Derived by reflection from the schema itself, so it cannot drift from it.

pub fn layout_fingerprint() -> integer

One number naming this build's pack layout — compare it across targets before a pack is read.

The struct definition IS the file layout, so a field that sits four bytes further along on one target reads a NEIGHBOUR's value on the other, silently, and the file looks corrupt rather than the layout looking wrong. A game that reads packs built elsewhere should compare this once at boot and refuse rather than draw garbage. (A 32-bit FNV-1a over layout_descriptor, masked at every step because an unbounded multiply would reach loft's null.)