17 KiB
Asset Management
Domain: asset runtime surface Function: normative
This chapter defines the runtime-facing asset model of PROMETEU.
1 Scope
PROMETEU asset management is bank-centric.
Assets are:
- cold bytes stored in the cartridge;
- described by cartridge metadata;
- materialized into host-managed banks;
- separate from VM heap ownership.
This chapter describes the runtime contract currently visible in the codebase. It is not a full tooling pipeline specification.
2 Core Principles
- asset residency is explicit;
- asset memory belongs to the machine, not to the VM heap;
- banks and slots are hardware/runtime concepts;
- loading and activation are explicit operations;
- asset memory does not participate in GC.
3 Cartridge Asset Artifact
The runtime currently consumes one primary cartridge asset artifact:
assets.pa: autocontained asset artifact.
assets.pa carries, inside the same binary:
- fixed binary prelude;
- JSON header;
- payload bytes.
The JSON header carries:
asset_table: metadata entries describing asset content;preload: optional initial residency requests consumed during cartridge initialization.
This chapter describes the runtime-facing asset contract. It does not define the Studio packer workflow or the shipper pipeline that publishes the cartridge.
3.1 assets.pa v1 envelope
assets.pa v1 is structured as:
[fixed binary prelude]
[json header]
[binary payload region]
The fixed binary prelude contains, at minimum:
magicschema_versionheader_lenpayload_offset
It may additionally include:
flagsreservedheader_checksum
3.2 Header and payload contract
The runtime loads:
asset_tablefrom the JSON header and keeps it live during cartridge execution;preloadfrom the JSON header and consumes it only during boot.
Payload bytes are addressed from the payload region using offsets relative to payload_offset, not relative to the start of the whole file.
4 Asset Table
Current runtime-facing asset metadata includes:
AssetEntry {
asset_id
asset_name
bank_type
offset
size
decoded_size
codec
metadata
}
This table describes content identity and storage layout, not live residency.
asset_id is the stable runtime-facing asset identity and uses 32-bit signed integer semantics compatible with Java int.
offset is relative to the start of the payload region inside assets.pa.
size is the serialized byte count stored in the payload region.
decoded_size is the byte count of the materialized runtime bank after decode, not necessarily the same as the serialized payload size.
codec identifies the generic transformation pipeline applied to the serialized payload slice before the asset becomes a resident bank.
codec does not define the bank-specific serialized layout itself. Specialized banks may still have normative decode rules even when codec = NONE.
4.1 GLYPH asset contract in v1
For BankType::GLYPH, the v1 runtime-facing contract is:
codec = NONE- serialized pixels use packed
u4palette indices - serialized palettes use
RGBA8888with canonical RGBA channel order palette_count = 64- runtime materialization may expand pixel indices to one
u8per pixel
For GLYPH, NONE means there is no additional generic codec layer beyond the bank contract itself.
For the current transition window:
RAWis a legacy deprecated alias ofNONE- newly published material must use
NONEas the canonical value
Even with codec = NONE, GLYPH still requires bank-specific decode from its serialized payload. The serialized byte layout therefore does not need to match the in-memory layout.
4.1.1 Metadata Normalization
Following DEC-0004, the AssetEntry.metadata field must be structured in segmented form to avoid ambiguity.
Required effective metadata fields for GLYPH at the root level:
tile_size: tile edge in pixels; valid values are8,16, or32width: total sheet width in pixelsheight: total sheet height in pixelspalette_count: number of serialized palettes for the bank
Optional informative subtrees:
metadata.codec: codec/compressor-specific configuration (for example dictionaries or compression flags).metadata.pipeline: informative metadata from the Studio build process (for example source hashes, timestamps, or tool versions).
Validation rules for GLYPH v1:
palette_countmust be64width * heightdefines the number of logical indexed pixels in the decoded sheet- extra metadata may exist, but the runtime contract must not depend on it to reconstruct the in-memory bank unless that data is defined at the root as an effective field.
4.1.2 Payload Layout
- packed indexed pixels for the full sheet, using
ceil(width * height / 2)bytes; - palette table, using
palette_count * 16 * 4bytes.
The tile-bank payload therefore separates serialized storage form from runtime memory form:
- serialized pixel plane: packed
4bpp - decoded pixel plane: expanded
u8indices, one entry per pixel - palette table:
64 * 16colors in RGBA8888 channel order
For GLYPH v1:
sizemust matchceil(width * height / 2) + (palette_count * 16 * 4)decoded_sizemust match(width * height) + (palette_count * 16 * 4)
RGB565 palette payloads are not compatible runtime input. Existing assets with two-byte RGB565 palette entries must be regenerated or converted by tooling before they are loaded by the runtime.
Palette indices are ordinary indices. Transparency is represented by the alpha
channel of the resolved RGBA8888 palette entry, not by reserving index 0.
4.2 SCENE asset contract in v1
For BankType::SCENE, the v1 runtime-facing contract is:
codec = NONE- payload magic is
SCNE - payload version is
1 - layer count is fixed at
4 - each layer carries its own glyph dependency
- the glyph dependency is serialized as canonical
AssetIdusing signed 32-bit little-endian width - the payload MUST describe cold asset dependencies only and MUST NOT encode runtime glyph slot topology
Even with codec = NONE, SCENE still has a bank-specific binary contract. The serialized layout is normative and distinct from any transient runtime residency state.
4.2.1 Layer Header Layout
Each scene layer header is:
flags: u8glyph_asset_id: i32(little-endian)tile_size: u8reserved: u16parallax_x: f32parallax_y: f32width: u32height: u32tile_count: u32reserved: u32
This makes the canonical per-layer header size 32 bytes.
Normative rules:
glyph_asset_idMUST be interpreted as the cold identity of the glyph dependency for that layer;glyph_asset_idMUST NOT be interpreted as a runtime slot id;- each layer MUST own exactly one glyph dependency reference;
- no shared dependency table exists in this contract;
tile_countMUST equalwidth * height.
4.2.2 Runtime Binding Semantics
SCENE assets do not own runtime residency.
Therefore:
- the runtime MUST resolve scene glyph dependencies through runtime-owned residency state;
- the runtime MUST maintain a runtime-owned mapping from committed glyph
AssetIdto glyph slot; - scene bind and scene draw MUST consult runtime residency, not serialized slot topology;
- a missing scene glyph dependency is not a passive asset-status condition.
If a scene glyph dependency cannot be resolved:
bind_sceneMUST fail as a fatal machine error with clear logging;- draw/composition MUST fail as a fatal machine error with clear logging;
- runtime MUST NOT continue scene composition after the missing dependency is detected.
5 Banks and Slots
The current runtime exposes bank types:
GLYPHSOUNDSSCENE
Assets are loaded into explicit slots identified by slot index at the public ABI boundary.
The runtime resolves bank context from asset_table using asset_id.
Internally, the runtime may still use a bank-qualified slot reference such as:
SlotRef { bank_type, index }
That internal representation is derived from the resolved AssetEntry, not supplied by the caller.
5.1 Canonical Bank Telemetry
The canonical visible bank telemetry contract is exposed by AssetManager.
The per-bank summary is slot-first and uses this shape:
BankTelemetry {
bank_type
used_slots
total_slots
}
Rules:
- the visible contract MUST NOT expose byte-oriented bank occupancy as the canonical summary;
- canonical bank names are
GLYPHandSOUNDS; - detailed occupancy inspection remains host-owned and slot-based through slot references, not bank byte totals;
- public runtime specs MUST NOT treat
bank.slot_infoor JSON bank inspection payloads as the long-term guest ABI afterDEC-0009; - any residual byte accounting MAY exist internally, but it is not part of the visible bank telemetry contract.
6 Load Lifecycle
The runtime asset manager exposes a staged lifecycle:
PENDINGLOADINGREADYCOMMITTEDCANCELEDERROR
High-level flow:
- request load of an asset into a slot;
- resolve the asset entry from live
asset_table; - open the payload slice in
assets.pa; - perform read/decode/materialization work;
- mark the load
READY; - explicitly
commit; - activate the resident asset in the slot.
The canonical payload paths are:
ROM -> open_slice -> CODEX/decode -> BankROM -> open_slice -> temporary in-memory blob -> CODEX/decode -> Bank
open_slice is the runtime-facing concept for opening a limited view over a payload slice. The runtime must not require the whole assets.pa payload to remain resident in RAM as its baseline operating mode.
OP_MODE selects between direct slice consumption and temporary materialization in memory.
For v1:
OP_MODEis derived fromcodec/CODEX;- explicit per-asset hinting is not part of the baseline contract.
TILESwithcodec = NONEmay still stage in memory before bank installation because bank-specific decode expands packed pixel indices into the resident representation.- during the migration window, runtime may accept legacy
RAWas an alias ofNONE.
The runtime does not treat asset installation as implicit side effect.
7 Residency and Ownership
Asset banks are host/runtime-owned memory.
Therefore:
- VM heap does not own asset residency;
- GC does not scan asset bank memory;
- shutting down a cartridge can release bank residency independently of VM heap behavior.
- the runtime must not keep the full
assets.papayload resident in RAM as a baseline requirement.
8 Bank Telemetry and Inspection Boundary
The runtime may maintain bank and slot statistics such as:
- total bytes;
- used bytes;
- free bytes;
- inflight bytes;
- slot occupancy;
- resident asset identity per slot.
These metrics support host-owned debugging, telemetry, and certification-oriented inspection.
Only bounded operational summaries belong to the public runtime contract.
Detailed per-slot inspection is valid for host tooling and runtime internals, but it must not be treated as a general guest-visible debug convenience API.
9 Preload
preload is stored in the JSON header of assets.pa.
The normative preload shape is:
PreloadEntry {
asset_id
slot
}
These preload entries are consumed during cartridge initialization so the asset manager can establish initial residency before normal execution flow.
Validation rules:
preloadis resolved byasset_id, not byasset_name;- every
preload.asset_idmust exist in the sameasset_table; - no two preload entries may resolve to the same
(bank_type, slot)pair; - legacy preload keyed by
asset_nameis invalid for the current contract.
Lifecycle rule:
preloadis boot-time input only;- it does not need to remain live after initialization completes.
Bootstrap rule:
- invalid preload is a structural cartridge error and must fail cartridge bootstrap before normal execution begins.
10 Relationship to Other Specs
13-cartridge.mddefines the cartridge package and the requirement thatassets.pacarries its own asset header.16-host-abi-and-syscalls.mddefines the syscall boundary used to manipulate assets.03-memory-stack-heap-and-allocation.mddefines the distinction between VM heap memory and host-owned memory.
11 Syscall Surface and Status Policy
asset follows status-first policy.
Fault boundary:
Trap: structural ABI misuse (type/arity/capability/shape mismatch);status: operational failure;Panic: internal invariant break only.
11.1 MVP syscall shape
asset.load(asset_id, slot) -> (status:int, handle:int)asset.status(handle) -> status:intasset.commit(handle) -> status:intasset.cancel(handle) -> status:int
Rules:
handleis returned whenloadstatus isOK;handlerepresents a stable bank slot target, not a worker thread;- a known handle remains queryable even when the slot is empty, has no active request, or has a superseded request;
- failed
loadreturnshandle = 0; commitandcancelmust not be silent no-op for unknown/invalid handle state.asset.loadresolves the target bank type fromasset_tableusingasset_id;- public callers must not supply
asset_nameorbank_typetoasset.load; - slot validation and residency/lifecycle rejection remain in
assetstatus space and are not delegated tobank.
11.2 Async work lane and backlog
Asset loading uses the runtime async work lane. This lane is separate from the VM/main lane and separate from the render worker lane.
The asset async lane is serial:
- it executes at most one active asset job at a time;
- it keeps an ordered backlog of pending requests;
- it must not create one OS thread per
asset.loadrequest.
Asset requests are keyed by the target bank_type/slot.
Rules:
- each
bank_type/slothas at most one current request; - a newer request for the same
bank_type/slotsupersedes the previous request; - if the previous request is queued, it is removed from the backlog;
- if the previous request is active, the lane should cancel cooperatively when the current phase supports cheap cancellation;
- if active work cannot stop cheaply, it may finish, but the result must be discarded when its generation no longer matches the target request generation;
- if the target already contains the requested
asset_idas a valid resident asset,asset.loadreturns a ready handle without adding a backlog entry.
The effective backlog size is bounded by the sum of targetable bank slots,
because only one current request can exist per bank_type/slot. The runtime
does not expose a guest-visible queue_full status for the normal asset
backlog path.
11.3 Handle state
An asset handle observes one stable bank slot target. Its observable state has two parts:
handle:
bank_type
slot
slot_state:
loaded_asset_id
resident_state
slot_generation
request_state:
requested_asset_id
request_generation
state
backlog_position
progress
slot_state describes what is currently resident in the target slot.
request_state describes the current or most recent request for that target.
Mutating operations such as commit, cancel, promote, demote, and move must act on the current request generation. They must not accidentally mutate a newer request through an older handle view.
11.4 Backlog inspection and ordering
The asset backlog surface may expose these status-first operations:
asset.backlog_info() -> (status, pending_count, active_handle, active_asset_id, active_bank_type, active_slot, active_progress)asset.backlog_position(handle) -> (status, state, position, progress)asset.backlog_move(handle, new_position) -> statusasset.backlog_promote(handle) -> statusasset.backlog_demote(handle) -> statusasset.target_status(bank_type, slot) -> (status, asset_id, handle, state, position, progress)
asset.backlog_promote(handle) is a shortcut for moving a queued request to
position 1, the first pending position after the active job.
asset.backlog_demote(handle) is a shortcut for moving a queued request to the
end of the pending backlog.
11.5 Progress and telemetry
Asset progress uses integer progress, not floating point. The preferred scale is
0..10000.
The initial phase model is:
queued -> 0
read -> 0..4000
decode -> 4000..9000
stage -> 9000..10000
ready -> 10000
If a phase cannot report internal progress, it keeps the previous progress mark and advances at phase completion. The runtime must not invent false precision for non-linear decode phases.
Minimum telemetry:
- current backlog depth;
- target/request position;
- active job progress;
- jobs submitted;
- jobs completed;
- jobs failed;
- jobs canceled;
- jobs superseded;
- job duration;
- percentiles by
bank_type; - lightweight percentiles or small-window samples by
asset_id.
Percentiles are updated when a job closes, not inside the inner decode loop.
11.6 Minimum status tables
asset.load request statuses:
0=OK3=ASSET_NOT_FOUND5=SLOT_INDEX_INVALID6=BACKEND_ERROR
asset.status lifecycle statuses:
0=PENDING1=LOADING2=READY3=COMMITTED4=CANCELED5=ERROR6=UNKNOWN_HANDLE7=QUEUED8=ACTIVE9=SUPERSEDED10=EMPTY11=INVALID12=BACKEND_UNAVAILABLE
asset.commit and asset.cancel operation statuses:
0=OK1=UNKNOWN_HANDLE2=INVALID_STATE3=SUPERSEDED