All checks were successful
Intrepid/Prometeu/Runtime/pipeline/head This commit looks good
Reviewed-on: #12 Co-authored-by: bQUARKz <bquarkz@gmail.com> Co-committed-by: bQUARKz <bquarkz@gmail.com>
208 lines
6.6 KiB
Markdown
208 lines
6.6 KiB
Markdown
---
|
|
id: LSN-0009
|
|
ticket: legacy-runtime-learn-import
|
|
title: Asset Management Mental Model
|
|
created: 2026-03-27
|
|
tags: [migration, tech-debt]
|
|
---
|
|
|
|
# Asset Management Mental Model
|
|
|
|
Status: pedagogical
|
|
Companion specs:
|
|
|
|
- [`../specs/13-cartridge.md`](../../specs/13-cartridge.md)
|
|
- [`../specs/15-asset-management.md`](../../specs/15-asset-management.md)
|
|
- [`../specs/04-gfx-peripheral.md`](../../specs/04-gfx-peripheral.md)
|
|
|
|
Origin decisions:
|
|
|
|
- [`../decisions/011-assets-pa-autocontained-runtime-contract.md`](../decisions/011-assets-pa-autocontained-runtime-contract.md)
|
|
- [`../decisions/013-asset-codec-none-vs-raw.md`](../decisions/013-asset-codec-none-vs-raw.md)
|
|
|
|
PROMETEU treats assets as a self-contained cart artifact, not as metadata scattered across `manifest.json`, the loader, and the runtime.
|
|
|
|
## One Runtime Artifact
|
|
|
|
The right mental model is:
|
|
|
|
- `manifest.json` identifies the cart and its capabilities;
|
|
- `assets.pa` carries the runtime-facing asset contract;
|
|
- the runtime consumes that contract without depending on auxiliary tooling artifacts.
|
|
|
|
That avoids mixing:
|
|
|
|
- cart editorial metadata;
|
|
- internal packer details;
|
|
- runtime loading policy.
|
|
|
|
## Why `assets.pa` Owns The Asset Contract
|
|
|
|
`assets.pa` puts into the same binary:
|
|
|
|
- a fixed prelude to locate the header;
|
|
- a JSON header that is easy to inspect;
|
|
- the cold binary payload of the assets.
|
|
|
|
That split exists to preserve two properties at the same time:
|
|
|
|
- fixed cost to open and validate the artifact;
|
|
- a readable header that is simple to debug.
|
|
|
|
The important point is not the format by itself. The important point is that the runtime finds everything it needs for assets behind one stable boundary.
|
|
|
|
## `preload` Is Not `asset_table`
|
|
|
|
Both live in the header, but they serve different roles:
|
|
|
|
- `asset_table` is the live asset-resolution index for the entire execution;
|
|
- `preload` is only a boot-time instruction for initial residency.
|
|
|
|
A short way to think about it:
|
|
|
|
- `asset_table` stays;
|
|
- `preload` passes.
|
|
|
|
That prevents the runtime from treating preload as permanent metadata when, in practice, it is only an initial loading impulse.
|
|
|
|
## Payload-Relative Thinking
|
|
|
|
`AssetEntry` offsets are relative to the start of the payload, not to the start of the whole file.
|
|
|
|
That choice preserves the semantics of the `asset` domain:
|
|
|
|
- `AssetEntry` describes where content sits inside the cold-byte region;
|
|
- the outer envelope can evolve without contaminating each entry with container details.
|
|
|
|
The runtime only needs to resolve:
|
|
|
|
```text
|
|
payload_base + entry.offset
|
|
```
|
|
|
|
## Slice-First Loading
|
|
|
|
The correct loading mental model is not:
|
|
|
|
```text
|
|
ROM -> load the whole assets.pa into RAM -> look up asset
|
|
```
|
|
|
|
The correct flow is:
|
|
|
|
```text
|
|
ROM -> open_slice -> CODEX/decode -> Bank
|
|
```
|
|
|
|
or, when the codec requires it:
|
|
|
|
```text
|
|
ROM -> open_slice -> temporary blob -> CODEX/decode -> Bank
|
|
```
|
|
|
|
That matters because PROMETEU was designed for low-cost hardware. Keeping the whole payload resident as a baseline wastes memory, couples IO to a monolithic blob, and weakens the runtime's real boundary.
|
|
|
|
## `OP_MODE` Belongs To The Runtime
|
|
|
|
The cart describes the asset. The runtime decides how to consume the slice.
|
|
|
|
In v1, that decision comes from the `codec`/CODEX:
|
|
|
|
- some assets can be read directly from the limited view;
|
|
- others require temporary materialization before decode.
|
|
|
|
The key point is to separate:
|
|
|
|
- the asset storage contract;
|
|
- the generic codec layer, when one exists;
|
|
- the operational strategy for reading and decode.
|
|
|
|
## Specialized Banks vs Generic Banks
|
|
|
|
The current asset model works better if you do not treat every bank as equally generic.
|
|
|
|
Some banks are specialized:
|
|
|
|
- `GLYPH`
|
|
- `SOUNDS`
|
|
|
|
For these, the bank contract already carries most of the important format rules. That means:
|
|
|
|
- serialized layout belongs primarily to the bank contract;
|
|
- metadata validation belongs primarily to the bank contract;
|
|
- `codec` may be absent or minimal.
|
|
|
|
Other banks may be more generic in the future, such as a `BLOB`-style bank.
|
|
|
|
For those, `codec` can do more real work and describe a broader generic transformation pipeline for the payload.
|
|
|
|
That is the right hierarchy:
|
|
|
|
- specialized bank first;
|
|
- generic codec layer second.
|
|
|
|
Not the other way around.
|
|
|
|
## Why `NONE` Matters
|
|
|
|
The move from `RAW` to `NONE` is not just a rename. It fixes the mental model.
|
|
|
|
`RAW` encouraged the wrong intuition that the bytes were somehow already "raw" in the runtime sense. But for specialized banks that is often false.
|
|
|
|
`NONE` is better because it says only this:
|
|
|
|
- there is no additional generic codec layer.
|
|
|
|
It does not say:
|
|
|
|
- there is no bank-specific decode;
|
|
- there is no transformation before residency;
|
|
- serialized bytes and resident memory are identical.
|
|
|
|
## Serialized Form Is Not Resident Form
|
|
|
|
One of the most important asset-management lessons in the current runtime is that `codec = NONE` does not mean "bitwise identical to in-memory layout".
|
|
|
|
The glyph-bank path is the concrete example:
|
|
|
|
- there is no additional generic codec layer for the asset;
|
|
- the serialized payload stores indexed pixels as packed `4bpp`;
|
|
- the palette table remains serialized as `RGB565` little-endian;
|
|
- the runtime expands pixels into one `u8` index per pixel after decode.
|
|
|
|
That is exactly why `codec` and bank contract must stay separate in the mental model:
|
|
|
|
- `codec` may be absent;
|
|
- the bank-specific decode may still be real and mandatory.
|
|
|
|
That is why `AssetEntry.size` and `AssetEntry.decoded_size` must be thought of as different concepts:
|
|
|
|
- `size` is transport cost inside `assets.pa`;
|
|
- `decoded_size` is resident-bank cost after materialization.
|
|
|
|
If those two numbers are treated as interchangeable, telemetry, budgets, and validation all become misleading.
|
|
|
|
## Glyph Banks Teach The Real Boundary
|
|
|
|
Glyph banks are useful because they show the real separation of concerns:
|
|
|
|
- `assets.pa` defines the serialized envelope and metadata needed to reconstruct the bank;
|
|
- the runtime validates that metadata against the expected v1 contract;
|
|
- the resident `GlyphBank` is a runtime object, not a direct view over the cold bytes.
|
|
|
|
This is the right PROMETEU mental model:
|
|
|
|
- cartridge asset bytes are transport;
|
|
- bank objects are execution state.
|
|
|
|
## Why This Fits PROMETEU
|
|
|
|
This model fits the project well because:
|
|
|
|
- it reduces dependence on metadata scattered through `manifest.json`;
|
|
- it separates `packer`, `shipper`, and runtime more cleanly;
|
|
- it allows early failure when `Capability::Asset` requires `assets.pa`;
|
|
- it preserves a runtime that can run on desktop and on low-cost dedicated hardware.
|
|
|
|
When thinking about assets in PROMETEU, think less in terms of "a manifest with attachments" and more in terms of "a cart with an asset artifact that explains itself".
|