prometeu-runtime/docs/specs/runtime/15-asset-management.md

328 lines
9.8 KiB
Markdown

# 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
1. asset residency is explicit;
2. asset memory belongs to the machine, not to the VM heap;
3. banks and slots are hardware/runtime concepts;
4. loading and activation are explicit operations;
5. 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:
```text
[fixed binary prelude]
[json header]
[binary payload region]
```
The fixed binary prelude contains, at minimum:
- `magic`
- `schema_version`
- `header_len`
- `payload_offset`
It may additionally include:
- `flags`
- `reserved`
- `header_checksum`
### 3.2 Header and payload contract
The runtime loads:
- `asset_table` from the JSON header and keeps it live during cartridge execution;
- `preload` from 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:
```text
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 `TILES` asset contract in v1
For `BankType::TILES`, the runtime-facing v1 contract is:
- `codec = NONE`
- serialized pixels use packed `u4` palette indices
- serialized palettes use `RGB565` (`u16`, little-endian)
- `palette_count = 64`
- runtime materialization may expand pixel indices to one `u8` per pixel
`NONE` for `TILES` means there is no additional generic codec layer beyond the bank contract itself.
For the current transition window:
- `RAW` is a deprecated legacy alias of `NONE`
- new published material must use `NONE` as the canonical value
Even with `codec = NONE`, `TILES` still requires deterministic bank-specific decode from its serialized payload. The serialized byte layout is therefore not required to be identical to the in-memory layout.
Required `AssetEntry.metadata` fields for `TILES`:
- `tile_size`: tile edge in pixels; valid values are `8`, `16`, or `32`
- `width`: full bank sheet width in pixels
- `height`: full bank sheet height in pixels
- `palette_count`: number of palettes serialized for the bank
Validation rules for `TILES` v1:
- `palette_count` must be `64`
- `width * height` defines the number of logical indexed pixels in the decoded sheet
- additional metadata may exist, but the runtime contract must not depend on it to reconstruct the bank in memory
Serialized payload layout for `TILES` v1:
1. packed indexed pixels for the full sheet, using `ceil(width * height / 2)` bytes;
2. palette table, using `palette_count * 16 * 2` bytes.
The tile-bank payload therefore separates serialized storage form from runtime memory form:
- serialized pixel plane: packed `4bpp`
- decoded pixel plane: expanded `u8` indices, one entry per pixel
- palette table: `64 * 16` colors in `RGB565`
For `TILES` v1:
- `size` must match `ceil(width * height / 2) + (palette_count * 16 * 2)`
- `decoded_size` must match `(width * height) + (palette_count * 16 * 2)`
## 5 Banks and Slots
The current runtime exposes bank types:
- `TILES`
- `SOUNDS`
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:
```text
SlotRef { bank_type, index }
```
That internal representation is derived from the resolved `AssetEntry`, not supplied by the caller.
## 6 Load Lifecycle
The runtime asset manager exposes a staged lifecycle:
- `PENDING`
- `LOADING`
- `READY`
- `COMMITTED`
- `CANCELED`
- `ERROR`
High-level flow:
1. request load of an asset into a slot;
2. resolve the asset entry from live `asset_table`;
3. open the payload slice in `assets.pa`;
4. perform read/decode/materialization work;
5. mark the load `READY`;
6. explicitly `commit`;
7. activate the resident asset in the slot.
The canonical payload paths are:
- `ROM -> open_slice -> CODEX/decode -> Bank`
- `ROM -> 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_MODE` is derived from `codec`/CODEX;
- explicit per-asset hinting is not part of the baseline contract.
- `TILES` with `codec = NONE` may 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 `RAW` as an alias of `NONE`.
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.pa` payload resident in RAM as a baseline requirement.
## 8 Bank Telemetry
The runtime surfaces bank and slot statistics such as:
- total bytes;
- used bytes;
- free bytes;
- inflight bytes;
- slot occupancy;
- resident asset identity per slot.
These metrics support debugging, telemetry, and certification-oriented inspection.
## 9 Preload
`preload` is stored in the JSON header of `assets.pa`.
The normative preload shape is:
```text
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:
- `preload` is resolved by `asset_id`, not by `asset_name`;
- every `preload.asset_id` must exist in the same `asset_table`;
- no two preload entries may resolve to the same `(bank_type, slot)` pair;
- legacy preload keyed by `asset_name` is invalid for the current contract.
Lifecycle rule:
- `preload` is 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.md`](13-cartridge.md) defines the cartridge package and the requirement that `assets.pa` carries its own asset header.
- [`16-host-abi-and-syscalls.md`](16-host-abi-and-syscalls.md) defines the syscall boundary used to manipulate assets.
- [`03-memory-stack-heap-and-allocation.md`](03-memory-stack-heap-and-allocation.md) defines 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:int`
- `asset.commit(handle) -> status:int`
- `asset.cancel(handle) -> status:int`
Rules:
- `handle` is valid only when `load` status is `OK`;
- failed `load` returns `handle = 0`;
- `commit` and `cancel` must not be silent no-op for unknown/invalid handle state.
- `asset.load` resolves the target bank type from `asset_table` using `asset_id`;
- public callers must not supply `asset_name` or `bank_type` to `asset.load`;
- slot validation and residency/lifecycle rejection remain in `asset` status space and are not delegated to `bank`.
### 11.2 Minimum status tables
`asset.load` request statuses:
- `0` = `OK`
- `3` = `ASSET_NOT_FOUND`
- `5` = `SLOT_INDEX_INVALID`
- `6` = `BACKEND_ERROR`
`asset.status` lifecycle statuses:
- `0` = `PENDING`
- `1` = `LOADING`
- `2` = `READY`
- `3` = `COMMITTED`
- `4` = `CANCELED`
- `5` = `ERROR`
- `6` = `UNKNOWN_HANDLE`
`asset.commit` and `asset.cancel` operation statuses:
- `0` = `OK`
- `1` = `UNKNOWN_HANDLE`
- `2` = `INVALID_STATE`