implements PLN-0067

This commit is contained in:
bQUARKz 2026-05-23 19:15:14 +01:00
parent 4d5818c244
commit 1256d33a72
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
7 changed files with 103 additions and 52 deletions

View File

@ -35,4 +35,4 @@
{"type":"discussion","id":"DSC-0032","status":"done","ticket":"system-os-lifecycle-process-task-contract","title":"Agenda - SystemOS Lifecycle, Process and Task Contract","created_at":"2026-05-14","updated_at":"2026-05-15","tags":["runtime","os","lifecycle","process","task","shell","firmware"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0041","file":"discussion/lessons/DSC-0032-system-os-lifecycle-process-task-contract/LSN-0041-systemos-lifecycle-authority.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]}
{"type":"discussion","id":"DSC-0033","status":"done","ticket":"system-os-service-ownership-and-module-layout","title":"Agenda - SystemOS Service Ownership and Module Layout","created_at":"2026-05-14","updated_at":"2026-05-15","tags":["runtime","os","services","module-layout","vm","window-manager","logging"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0042","file":"discussion/lessons/DSC-0033-system-os-service-ownership-and-module-layout/LSN-0042-systemos-service-ownership-boundary.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]}
{"type":"discussion","id":"DSC-0036","status":"done","ticket":"prometeu-hub-ui-direction","title":"Agenda - Prometeu Hub UI Direction","created_at":"2026-05-15","updated_at":"2026-05-22","tags":["hub","ui","shell","system-apps","lifecycle","design-system"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0045","file":"discussion/lessons/DSC-0036-prometeu-hub-ui-direction/LSN-0045-hub-ui-slices-should-prove-os-boundaries.md","status":"done","created_at":"2026-05-22","updated_at":"2026-05-22"}]}
{"type":"discussion","id":"DSC-0037","status":"in_progress","ticket":"rgba8888-framebuffer-and-pixel-format-direction","title":"Agenda - RGBA8888 Framebuffer and Pixel Format Direction","created_at":"2026-05-22","updated_at":"2026-05-23","tags":["gfx","framebuffer","rgb565","rgba8888","renderer","assets","host","backend"],"agendas":[{"id":"AGD-0037","file":"AGD-0037-rgba8888-framebuffer-and-pixel-format-direction.md","status":"accepted","created_at":"2026-05-22","updated_at":"2026-05-23"}],"decisions":[{"id":"DEC-0029","file":"DEC-0029-rgba8888-runtime-pixel-format-contract.md","status":"accepted","created_at":"2026-05-23","updated_at":"2026-05-23","ref_agenda":"AGD-0037"}],"plans":[{"id":"PLN-0067","file":"PLN-0067-rgba8888-published-contracts-and-specs.md","status":"open","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0068","file":"PLN-0068-rgba8888-color-type-hal-and-abi-surface.md","status":"open","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0069","file":"PLN-0069-rgba8888-cpu-renderer-and-composition.md","status":"open","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0070","file":"PLN-0070-rgba8888-asset-palettes-tooling-and-fixtures.md","status":"open","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0071","file":"PLN-0071-rgba8888-host-presentation-path.md","status":"open","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0072","file":"PLN-0072-rgba8888-residue-removal-and-end-to-end-validation.md","status":"open","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0037","status":"in_progress","ticket":"rgba8888-framebuffer-and-pixel-format-direction","title":"Agenda - RGBA8888 Framebuffer and Pixel Format Direction","created_at":"2026-05-22","updated_at":"2026-05-23","tags":["gfx","framebuffer","rgb565","rgba8888","renderer","assets","host","backend"],"agendas":[{"id":"AGD-0037","file":"AGD-0037-rgba8888-framebuffer-and-pixel-format-direction.md","status":"accepted","created_at":"2026-05-22","updated_at":"2026-05-23"}],"decisions":[{"id":"DEC-0029","file":"DEC-0029-rgba8888-runtime-pixel-format-contract.md","status":"accepted","created_at":"2026-05-23","updated_at":"2026-05-23","ref_agenda":"AGD-0037"}],"plans":[{"id":"PLN-0067","file":"PLN-0067-rgba8888-published-contracts-and-specs.md","status":"done","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0068","file":"PLN-0068-rgba8888-color-type-hal-and-abi-surface.md","status":"open","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0069","file":"PLN-0069-rgba8888-cpu-renderer-and-composition.md","status":"open","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0070","file":"PLN-0070-rgba8888-asset-palettes-tooling-and-fixtures.md","status":"open","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0071","file":"PLN-0071-rgba8888-host-presentation-path.md","status":"open","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0072","file":"PLN-0072-rgba8888-residue-removal-and-end-to-end-validation.md","status":"open","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]}],"lessons":[]}

View File

@ -2,7 +2,7 @@
id: PLN-0067
ticket: rgba8888-framebuffer-and-pixel-format-direction
title: RGBA8888 Published Contracts and Specs
status: open
status: done
created: 2026-05-23
ref_decisions: [DEC-0029]
tags: [gfx, framebuffer, rgb565, rgba8888, renderer, assets, host, backend]

View File

@ -35,7 +35,7 @@ These are treated as VM values with stable layout semantics.
| Type | Description |
| ------- | --------------------------------- |
| `vec2` | 2D vector (x, y) |
| `color` | Packed color value |
| `color` | Packed RGBA8888 color value |
| `pixel` | Combination of position and color |
These values:
@ -44,6 +44,9 @@ These values:
- are copied by value;
- do not require heap allocation by themselves.
The canonical `color` raw channel order is RGBA. RGB565 is not a public VM
color contract.
### Heap values
Heap-resident entities are referenced indirectly through handles.

View File

@ -29,13 +29,20 @@ It is an explicit 2D graphics device based on:
### Pixel format
- **RGB565**
- 5 bits Red
- 6 bits Green
- 5 bits Blue
- no alpha channel
- **RGBA8888**
- 8 bits Red
- 8 bits Green
- 8 bits Blue
- 8 bits Alpha
- canonical raw channel order: **RGBA**
Transparency is handled via **color key**.
The framebuffer alpha channel may carry meaningful runtime output. The runtime
MUST NOT force the published front buffer alpha channel to `255` as a blanket
contract.
Color values in the runtime, HAL, host-facing framebuffer, and GFX ABI are
logical RGBA8888 values. RGB565 is not a supported runtime framebuffer,
palette, host presentation, or compatibility contract.
---
@ -232,16 +239,21 @@ Normative boundary:
---
## 12. Transparency (Color Key)
## 12. Transparency
- One RGB565 value is reserved as TRANSPARENT_KEY
- Pixels with this color are not drawn
Transparency is represented by the alpha channel of the resolved RGBA8888
color.
Palette indices are ordinary indices. The runtime MUST NOT reserve palette
index `0` as a special transparent index. A palette entry may still be authored
as transparent by setting its alpha channel to `0`, but that is ordinary palette
data rather than a special index rule.
```
if src == TRANSPARENT_KEY:
if resolved_color.alpha == 0:
skip
else:
draw
draw_or_blend(resolved_color)
```
@ -259,8 +271,9 @@ Official modes:
- `BLEND_HALF_MINUS`
- `BLEND_FULL`
No continuous alpha.
No arbitrary blending.
Alpha is available through RGBA8888 colors. Discrete blend modes remain part of
the classic GFX contract, and later optimization work may specialize opaque,
masked, and alpha paths.
Everything is:
@ -283,12 +296,13 @@ Everything is:
By design:
- Continuous alpha
- RGBA framebuffer
- Shaders
- Modern GPU pipeline
- HDR
- Gamma correction
- RGB565 compatibility framebuffers
- multi-format backend selection
- render-thread ownership as part of this contract
---
@ -314,8 +328,7 @@ controls:
In v1, deferred `gfx.*` overlay/debug primitives are drained after both fades and therefore are not themselves part of scene or HUD fade application.
The fade is implemented without continuous per-pixel alpha and without floats.
It uses a **discrete integer level** (0..31), which in practice produces an
The fade uses a **discrete integer level** (0..31), which in practice produces an
"almost continuous" visual result in Prometeu's `480x270` pixel-art framebuffer.
---
@ -327,32 +340,32 @@ Each fade is represented by:
- `fade_level: u8` in the range **[0..31]**
- `0` → fully replaced by the fade color
- `31` → fully visible (no fade)
- `fade_color: RGB565`
- `fade_color: RGBA8888`
- color the image will be blended into
Registers:
- `SCENE_FADE_LEVEL` (0..31)
- `SCENE_FADE_COLOR` (RGB565)
- `SCENE_FADE_COLOR` (RGBA8888)
- `HUD_FADE_LEVEL` (0..31)
- `HUD_FADE_COLOR` (RGB565)
- `HUD_FADE_COLOR` (RGBA8888)
Common cases:
- Fade-out: `fade_color = BLACK`
- Flash/teleport: `fade_color = WHITE`
- Special effects: any RGB565 color
- Special effects: any RGBA8888 color
---
### 17.2 Fade Operation (Blending with Arbitrary Color)
For each RGB565 pixel `src` and fade color `fc`, the final pixel `dst` is calculated per channel.
For each RGBA8888 pixel `src` and fade color `fc`, the final pixel `dst` is calculated per channel.
1) Extract components:
- `src_r5`, `src_g6`, `src_b5`
- `fc_r5`, `fc_g6`, `fc_b5`
- `src_r8`, `src_g8`, `src_b8`, `src_a8`
- `fc_r8`, `fc_g8`, `fc_b8`, `fc_a8`
2) Apply integer blending:
@ -360,17 +373,18 @@ For each RGB565 pixel `src` and fade color `fc`, the final pixel `dst` is calcul
src_weight = fade_level // 0..31
fc_weight = 31 - fade_level
r5 = (src_r5 * src_weight + fc_r5 * fc_weight) / 31
g6 = (src_g6 * src_weight + fc_g6 * fc_weight) / 31
b5 = (src_b5 * src_weight + fc_b5 * fc_weight) / 31
r8 = (src_r8 * src_weight + fc_r8 * fc_weight) / 31
g8 = (src_g8 * src_weight + fc_g8 * fc_weight) / 31
b8 = (src_b8 * src_weight + fc_b8 * fc_weight) / 31
a8 = (src_a8 * src_weight + fc_a8 * fc_weight) / 31
```
- `src_r5`, `src_g6`, `src_b5`
- `fc_r5`, `fc_g6`, `fc_b5`
- `src_r8`, `src_g8`, `src_b8`, `src_a8`
- `fc_r8`, `fc_g8`, `fc_b8`, `fc_a8`
3) Repack:
```
dst = pack_rgb565(r5, g6, b5)
dst = pack_rgba8888_rgba(r8, g8, b8, a8)
```
@ -425,8 +439,8 @@ Each pixel of a tile or sprite is:
Fixed rule:
- Index `0` = TRANSPARENT
- Indices `1..15` = valid palette colors
- Indices `0..15` are ordinary valid palette indices
- Transparency comes from the alpha channel of the resolved palette entry
---
@ -437,13 +451,13 @@ Each **Tile Bank** contains:
- **64 palettes** in runtime-facing v1
- Each palette has:
- **16 colors**
- each color in **RGB565 (`u16`, little-endian in serialized assets)**
- each color in **RGBA8888** with canonical RGBA channel order
Size:
- 1 palette = 16 × 2 bytes = **32 bytes**
- 64 palettes = **2 KB per bank**
- 16 banks = **32 KB maximum palettes**
- 1 palette = 16 × 4 bytes = **64 bytes**
- 64 palettes = **4 KB per bank**
- 16 banks = **64 KB maximum palettes**
---
@ -496,22 +510,22 @@ Runtime-facing validity rule for v1:
The pipeline works like this:
1. Read indexed pixel from tile (value 0..15)
2. If index == 0 → transparent pixel
3. Otherwise:
2. Resolve:
- real_color = palette[palette_id][index]
4. Apply:
3. Apply:
- flip
- discrete blend
- alpha/skip behavior from the resolved RGBA8888 color
- writing to back buffer
In other words:
```
pixel_index = tile_pixel(x,y)
if pixel_index == 0:
color = bank.palettes[palette_id][pixel_index]
if color.alpha == 0:
skip
else:
color = bank.palettes[palette_id][pixel_index]
draw(color)
draw_or_blend(color)
```
---
@ -578,7 +592,6 @@ Fault boundary:
| `gfx.draw_disc` | `void` | no real operational failure path in v1 |
| `gfx.draw_square` | `void` | no real operational failure path in v1 |
| `gfx.draw_text` | `void` | no real operational failure path in v1 |
| `gfx.clear_565` | `void` | no real operational failure path in v1 |
| `composer.bind_scene` | `status:int` | status-returning API, but missing scene glyph dependencies are fatal runtime errors |
| `composer.unbind_scene` | `status:int` | explicit orchestration-domain operational result |
| `composer.set_camera` | `void` | no real operational failure path in v1 |

View File

@ -116,7 +116,7 @@ Hardware differences:
The graphics system:
- operates on a logical framebuffer
- operates on a logical RGBA8888 framebuffer
- uses an indexed palette
- does not depend on a specific GPU
@ -127,6 +127,10 @@ The platform layer:
- **may overlay technical HUDs without modifying the logical framebuffer**
- may transport the logical framebuffer into a host presentation surface where a host-only overlay layer is composed
The host presentation layer MUST treat RGBA8888 as the canonical logical
framebuffer format. RGB565 conversion is not part of the normal host
presentation contract.
Host presentation SHOULD be driven by published logical frames and explicit host-owned invalidation, not by perpetual redraw polling.
In particular:

View File

@ -114,7 +114,7 @@ For `BankType::GLYPH`, the v1 runtime-facing contract is:
- `codec = NONE`
- serialized pixels use packed `u4` palette indices
- serialized palettes use `RGB565` (`u16`, little-endian)
- serialized palettes use `RGBA8888` with canonical RGBA channel order
- `palette_count = 64`
- runtime materialization may expand pixel indices to one `u8` per pixel
@ -152,18 +152,25 @@ Validation rules for `GLYPH` v1:
#### 4.1.2 Payload Layout
1. packed indexed pixels for the full sheet, using `ceil(width * height / 2)` bytes;
2. palette table, using `palette_count * 16 * 2` bytes.
2. palette table, using `palette_count * 16 * 4` 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`
- palette table: `64 * 16` colors in RGBA8888 channel order
For `GLYPH` v1:
- `size` must match `ceil(width * height / 2) + (palette_count * 16 * 2)`
- `decoded_size` must match `(width * height) + (palette_count * 16 * 2)`
- `size` must match `ceil(width * height / 2) + (palette_count * 16 * 4)`
- `decoded_size` must 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

View File

@ -220,6 +220,30 @@ For `asset.load`:
- JSON-on-the-wire bank inspection payloads are not valid public ABI;
- `bank.info` returns stack values, not textual structured payloads.
### GFX surface (`gfx`, v1)
The public GFX ABI uses format-neutral operation names. API and syscall names
MUST NOT encode RGB565 or any other pixel format suffix.
Canonical operations in v1 include:
- `gfx.clear(color) -> void`
- `gfx.fill_rect(x, y, w, h, color) -> void`
- `gfx.draw_line(x0, y0, x1, y1, color) -> void`
- `gfx.draw_circle(x, y, radius, color) -> void`
- `gfx.draw_disc(x, y, radius, color) -> void`
- `gfx.draw_square(x, y, size, color) -> void`
- `gfx.draw_text(x, y, text, color) -> void`
Rules:
- `color` arguments are packed RGBA8888 values using RGBA channel order.
- RGB565 raw values are not a public ABI contract.
- `Gfx*565` syscall names and `gfx.*_565` canonical identities are not valid
current ABI.
- The runtime framebuffer exposed across the host boundary is RGBA8888, not
`u16` RGB565.
### Composition surface (`composer`, v1)
The canonical frame-orchestration public ABI uses module `composer`.