801 lines
24 KiB
Markdown
801 lines
24 KiB
Markdown
# GFX Peripheral (Graphics System)
|
||
|
||
Domain: virtual hardware: graphics
|
||
Function: normative
|
||
|
||
Didactic companion: [`../learn/mental-model-gfx.md`](../runtime/learn/mental-model-gfx.md)
|
||
|
||
## 1. Overview
|
||
|
||
The **GFX** peripheral is responsible for generating images in PROMETEU.
|
||
|
||
`DEC-0030` defines the current logical render boundary. PROMETEU render
|
||
production is split from render implementation:
|
||
|
||
```text
|
||
domain buffers during logical frame
|
||
-> RenderManager closes buffers
|
||
-> RenderSubmission snapshot
|
||
-> render surface / implementation consumes submission
|
||
-> RGBA8888 surface publication
|
||
```
|
||
|
||
The GFX peripheral remains the classic local raster implementation for
|
||
PROMETEU's 2D output. Logical render APIs do not target `Gfx` or the
|
||
framebuffer directly.
|
||
|
||
`RenderSubmission` is the closed snapshot passed across the render boundary. It
|
||
MUST contain a frame id, the active app mode, and one typed packet:
|
||
`Game2DFramePacket` for `AppMode::Game` or `ShellUiFramePacket` for
|
||
`AppMode::Shell`. Once closed, producers MUST treat the submission as
|
||
immutable. The runtime backpressure policy is latest-complete-submission-wins;
|
||
it MUST NOT accumulate an unbounded frame queue.
|
||
|
||
The platform layer is the runtime-facing implementation boundary for render
|
||
handoff. Runtime and firmware code publish completed frames through typed
|
||
platform services such as `RenderSubmissionSink`, and they access Game 2D
|
||
composition through `Game2DFrameComposer`. They MUST NOT depend on a monolithic
|
||
hardware bridge, mutable `Hardware`, mutable `Gfx`, or live `FrameComposer`
|
||
reference as the render handoff contract.
|
||
|
||
The current 2D graphics model is based on:
|
||
|
||
- framebuffer
|
||
- tilemaps
|
||
- tile banks
|
||
- priority-based sprites
|
||
- composition by drawing order
|
||
|
||
---
|
||
|
||
## 2. Resolution and Framebuffer
|
||
|
||
### Base resolution
|
||
|
||
- **480 × 270 pixels**
|
||
- aspect ratio close to 16:9
|
||
- scalable by the host (nearest-neighbor)
|
||
|
||
### Pixel format
|
||
|
||
- **RGBA8888**
|
||
- 8 bits Red
|
||
- 8 bits Green
|
||
- 8 bits Blue
|
||
- 8 bits Alpha
|
||
- canonical raw channel order: **RGBA**
|
||
|
||
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.
|
||
|
||
### Published owned frame
|
||
|
||
The canonical worker-published frame value is `OwnedRgba8888Frame`.
|
||
|
||
`OwnedRgba8888Frame` contains:
|
||
|
||
- the logical `FrameId`;
|
||
- render ownership metadata;
|
||
- width and height in pixels;
|
||
- stride in pixels;
|
||
- an owned `Vec<u32>` containing packed RGBA8888 pixels in canonical RGBA
|
||
channel order.
|
||
|
||
The pixel vector is owned by the published frame. Consumers may copy it into a
|
||
host upload buffer, retain it as the latest published frame, or repeat it for a
|
||
host redraw. Consumers MUST NOT interpret the frame as a native texture,
|
||
swapchain image, window handle, or host GPU resource.
|
||
|
||
---
|
||
|
||
## 3. Double Buffering
|
||
|
||
The render surface implementation maintains the concrete buffers needed to
|
||
publish a frame. The classic software implementation uses:
|
||
|
||
- **Back Buffer** — where the frame is built
|
||
- **Front Buffer** — where the frame is displayed
|
||
|
||
Per-frame flow:
|
||
|
||
1. Game or Shell code mutates mode-specific domain buffers during the logical frame.
|
||
2. `RenderManager` closes the active buffers into an immutable `RenderSubmission`.
|
||
3. The render surface implementation consumes the submission and rasterizes to its back buffer.
|
||
4. The render surface publishes the completed RGBA8888 surface.
|
||
5. The host displays the published surface.
|
||
|
||
This guarantees:
|
||
|
||
- no tearing
|
||
- clear per-frame synchronization
|
||
- deterministic behavior
|
||
|
||
---
|
||
|
||
## 4. Asynchronous Render Boundary
|
||
|
||
`DEC-0031` defines the asynchronous render boundary. The contract applies even
|
||
when the current implementation consumes submissions synchronously.
|
||
|
||
### 4.1 Complete packet contract
|
||
|
||
`RenderSubmissionPacket::Game2D` MUST be a complete frame description for the
|
||
Game 2D render consumer. The consumer MUST NOT bypass the packet by consulting
|
||
live VM state, live `FrameComposer` state, or mutable runtime state.
|
||
|
||
For Game 2D, the required composition order is:
|
||
|
||
```text
|
||
composer scene/layers/sprites/HUD
|
||
-> buffered gfx2d primitives
|
||
-> publication/present
|
||
```
|
||
|
||
`gfx2d.*` primitives are therefore a final Game 2D overlay. This is true for
|
||
both active-scene frames and no-scene frames.
|
||
|
||
### 4.2 Resource boundary
|
||
|
||
Submissions MUST remain small and owned. Heavy resident resources such as glyph
|
||
banks, scene banks, asset payloads, and viewport cache materializations MUST
|
||
NOT be copied into each submission.
|
||
|
||
Submissions SHALL carry stable resource IDs or handles. The render consumer may
|
||
resolve those IDs through read-only resource APIs. Resource installation,
|
||
resolver updates, viewport cache refreshes, and bank residency changes belong
|
||
to the logical/runtime side before handoff, or to an owning service. They MUST
|
||
NOT require the render consumer to hold mutable VM, `Hardware`, `Gfx`, or
|
||
`FrameComposer` references.
|
||
|
||
Read-only resource APIs expose compact lookup by ID. They do not expose mutable
|
||
bank state, `Arc` ownership as part of the contract, copies of whole banks, or
|
||
snapshot payloads. The implementation may choose its internal sharing mechanism,
|
||
but the render boundary contract is ID-based read-only access.
|
||
|
||
Local host implementations may keep a concrete hardware object internally as a
|
||
platform implementation detail. That object is not part of the runtime-facing
|
||
render boundary; the boundary is the typed platform service set.
|
||
|
||
The runtime does not guarantee visual integrity if a developer or framework
|
||
replaces resources behind an in-flight submission in a way that violates asset
|
||
discipline.
|
||
|
||
### 4.3 Handoff
|
||
|
||
The first asynchronous Game render model uses a single pending slot with
|
||
latest-wins semantics:
|
||
|
||
- publishing a new submission replaces the previous pending submission;
|
||
- replacement before consumption is counted as a render drop;
|
||
- the render consumer takes ownership of the pending submission;
|
||
- the VM/producer MUST NOT block on render consumption, raster completion, or
|
||
present completion.
|
||
|
||
The consumer status is telemetry. It is not a semantic ACK to the VM.
|
||
|
||
The real render worker consumes owned `RenderSubmission` values from this
|
||
single-slot handoff and publishes owned `OwnedRgba8888Frame` values. The
|
||
producer path MUST remain non-blocking with respect to worker rasterization and
|
||
host present. A slow worker can cause replacement/drop telemetry, but it MUST
|
||
NOT create an unbounded queue or stall VM logical frame production.
|
||
|
||
### 4.4 Frame pacing
|
||
|
||
Game logical frames are paced by the runtime frame scheduler, not by render
|
||
worker ACK. The intended cadence is one Game logical frame per frame tick and
|
||
at most one pending submission ahead of the consumer.
|
||
|
||
`FRAME_SYNC` remains the canonical end of a VM logical frame. Cycle/time budget
|
||
is evidence for certification, watchdog, diagnostics, and overrun reporting; it
|
||
MUST NOT be used as the normal mechanism for cutting a logical frame short.
|
||
|
||
If a Game logical frame overruns the display cadence, logical frames remain
|
||
sequential. The render consumer may repeat the last valid frame, and telemetry
|
||
records the overrun/repeat. The VM MUST NOT produce catch-up frames to skip
|
||
from frame `N` to frame `N+k`.
|
||
|
||
Repeating a frame means reusing the latest published `OwnedRgba8888Frame`
|
||
without recomposing, rerunning VM code, or consuming a new submission. Repeat
|
||
behavior is presentation cadence behavior, not guest-visible execution.
|
||
|
||
### 4.5 AppMode policy
|
||
|
||
Render execution policy is explicit by pipeline/AppMode:
|
||
|
||
- `AppMode::Game` is frame-paced and may use a render worker when the
|
||
host/runtime supports it, with local synchronous fallback.
|
||
- `AppMode::Shell` is lifecycle-driven and local/synchronous by default.
|
||
- Shell VM-backed apps follow Shell lifecycle; they do not declare an
|
||
independent frame-paced game workload under this contract.
|
||
- Splash, crash, and hub/system screens follow Shell/local policy unless a
|
||
later decision defines a more specific policy.
|
||
|
||
### 4.6 Ownership and epoch
|
||
|
||
Every submission that may cross an asynchronous boundary MUST carry render
|
||
ownership metadata: at minimum frame identity, app mode, app identity where
|
||
available, and render epoch/generation.
|
||
|
||
Foreground visual-owner transitions MUST advance the active render epoch or
|
||
equivalent generation through a central runtime render manager. The render
|
||
consumer MUST check ownership before present and MUST discard stale submissions
|
||
whose ownership no longer matches the active owner.
|
||
|
||
Transitions that invalidate stale render work include:
|
||
|
||
- Game to Shell/Hub;
|
||
- Shell/Hub to Game;
|
||
- crash screen takeover;
|
||
- splash or system screen takeover;
|
||
- cartridge or app swap, even if `AppMode` remains unchanged;
|
||
- shutdown/stop.
|
||
|
||
The same physical surface may be reused by multiple visual owners, but logical
|
||
ownership MUST remain explicit.
|
||
|
||
Game pause/resume, foreground stack behavior, and coexistence of a paused Game
|
||
with VM-backed Shell apps are defined by the foreground/lifecycle contract. The
|
||
render boundary's responsibility is narrower: foreground owner changes MUST
|
||
advance ownership, stale submissions MUST be discarded, and visual return to a
|
||
resumed Game MUST wait for a valid submission from the current ownership epoch.
|
||
|
||
### 4.7 Render telemetry
|
||
|
||
Asynchronous render is best-effort observable, not a VM-visible handshake.
|
||
Render drops, stale epoch discards, repeated presents, render errors, and
|
||
present errors MUST be recorded for host diagnostics, debugging, profiling, and
|
||
certification evidence. VM program semantics MUST NOT depend on whether a
|
||
submission was consumed or presented.
|
||
|
||
Minimum render telemetry includes:
|
||
|
||
- produced submissions;
|
||
- replaced-before-consume submissions;
|
||
- consumed submissions;
|
||
- presented frames;
|
||
- repeated presents;
|
||
- render errors;
|
||
- present errors;
|
||
- stale epoch discards;
|
||
- shutdown discards;
|
||
- last produced, consumed, presented, dropped, and error frame IDs;
|
||
- active render epoch.
|
||
|
||
### 4.8 Shutdown and typed failures
|
||
|
||
Render worker shutdown is bounded and observable. A shutdown request MUST
|
||
wake a waiting worker, discard pending submissions that will not be consumed,
|
||
and either join within the configured timeout or report a typed shutdown
|
||
failure.
|
||
|
||
Worker backend failures, sink publication failures, stale ownership discards,
|
||
panic capture, and shutdown timeout are typed render worker outcomes. They are
|
||
recorded through telemetry and diagnostics; they do not become VM-visible ACKs.
|
||
|
||
---
|
||
|
||
## 5. PROMETEU Graphical Structure
|
||
|
||
The graphical world is composed of:
|
||
|
||
- Up to **16 Tile Banks**
|
||
- **4 Tile Layers** (scrollable)
|
||
- **1 HUD Layer** (fixed, always on top)
|
||
- Sprites with priority between layers
|
||
|
||
### 5.1 Tile Banks
|
||
|
||
- There are up to **16 banks**
|
||
- Each bank has a fixed tile size:
|
||
- 8×8, 16×16, or 32×32
|
||
- A bank is a graphics library:
|
||
- environment
|
||
- characters
|
||
- UI
|
||
- effects
|
||
- `assets.pa` tile-bank payloads use a serialized representation distinct from runtime memory:
|
||
- serialized pixels are `4bpp` packed in payload order
|
||
- runtime memory may expand pixels to one `u8` palette index per pixel after decode
|
||
|
||
### 5.2 Layers
|
||
|
||
- There are:
|
||
- 4 Tile Layers
|
||
- 1 HUD Layer
|
||
- Each layer points to **a single bank**
|
||
- Sprites can use **any bank**
|
||
- HUD:
|
||
- does not scroll
|
||
- maximum priority
|
||
- generally uses 8×8 tiles
|
||
|
||
---
|
||
|
||
## 6. Internal Model of a Tile Layer
|
||
|
||
A Tile Layer **is not a bitmap of pixels**.
|
||
It is composed of:
|
||
|
||
- A **logical Tilemap** (tile indices)
|
||
- A **Border Cache** (window of visible tiles)
|
||
- A **Scroll Offset**
|
||
|
||
### Structure:
|
||
|
||
- `bank_id`
|
||
- `tile_size`
|
||
- `tilemap` (large matrix)
|
||
- `scroll_x`, `scroll_y`
|
||
- `cache_origin_x`, `cache_origin_y`
|
||
- `cache_tiles[w][h]`
|
||
|
||
---
|
||
|
||
## 7. Logical Tilemap
|
||
|
||
The tilemap represents the world:
|
||
|
||
Each cell contains:
|
||
|
||
- `tile_id`
|
||
- `flip_x`
|
||
- `flip_y`
|
||
- `priority` (optional)
|
||
- `palette_id` (optional)
|
||
|
||
The tilemap can be much larger than the screen.
|
||
|
||
---
|
||
|
||
## 8. Border Cache (Tile Cache)
|
||
|
||
The cache is a window of tiles around the camera.
|
||
|
||
Example:
|
||
|
||
- Screen: 480×270
|
||
- 16×16 tiles → approximately 30×17 visible
|
||
- Current runtime cache sizing with 16×16 layers: 35×21 tiles
|
||
|
||
It stores tiles already resolved from the tilemap.
|
||
|
||
---
|
||
|
||
## 9. Cache Update
|
||
|
||
Every frame:
|
||
|
||
1. Calculate:
|
||
- `tile_x = scroll_x / tile_size`
|
||
- `tile_y = scroll_y / tile_size`
|
||
- `offset_x = scroll_x % tile_size`
|
||
- `offset_y = scroll_y % tile_size`
|
||
|
||
2. If `tile_x` changed:
|
||
- Advance `cache_origin_x`
|
||
- Reload only the new column
|
||
|
||
3. If `tile_y` changed:
|
||
- Advance `cache_origin_y`
|
||
- Reload only the new line
|
||
|
||
Only **one row and/or column** is updated per frame.
|
||
|
||
---
|
||
|
||
## 10. Cache as Ring Buffer
|
||
|
||
The cache is circular:
|
||
|
||
- Does not physically move data
|
||
- Only moves logical indices
|
||
|
||
Access:
|
||
- `real_x = (cache_origin_x + logical_x) % cache_width`
|
||
- `real_y = (cache_origin_y + logical_y) % cache_height`
|
||
|
||
---
|
||
|
||
## 11. Canonical Game Projection
|
||
|
||
Game mode uses a typed Game 2D submission. `composer.*` owns high-level Game 2D
|
||
frame composition: scene binding, camera, sprites, HUD, and frame orchestration.
|
||
`gfx2d.*` owns Game 2D primitives only. Both domains are mutable while the
|
||
logical frame is being produced and are closed by `RenderManager` into a
|
||
`Game2DFramePacket`.
|
||
|
||
For each Game 2D packet:
|
||
|
||
1. For each Tile Layer, in order:
|
||
- Rasterize visible tiles from the cache
|
||
- Apply scroll, flip, and transparency
|
||
- Write to the render surface's working buffer
|
||
|
||
2. Draw sprites:
|
||
- With priority between layers
|
||
- Drawing order defines depth
|
||
|
||
3. Draw HUD layer last
|
||
|
||
4. Draw buffered `gfx2d.*` primitives according to the Game 2D packet contract.
|
||
|
||
This section describes only the Game 2D packet rendering path. Shell/system UI
|
||
uses `gfxui.*` and `ShellUiFramePacket`; it is never part of Game HUD or
|
||
`composer.*`.
|
||
|
||
---
|
||
|
||
## 12. Drawing Order and Priority
|
||
|
||
- There is no Z-buffer
|
||
- There is no automatic sorting
|
||
- Whoever draws later is in front
|
||
|
||
Base order:
|
||
|
||
1. Tile Layer 0
|
||
2. Tile Layer 1
|
||
3. Tile Layer 2
|
||
4. Tile Layer 3
|
||
5. Sprites (by priority between layers)
|
||
6. HUD Layer
|
||
7. Buffered `gfx2d.*` Game primitives
|
||
|
||
Normative boundary:
|
||
|
||
- Items 1 through 6 belong to `composer.*` Game-frame composition.
|
||
- Item 7 belongs to `gfx2d.*`.
|
||
- `gfx2d.*` primitives MUST NOT be interpreted as scene, sprite, camera, HUD,
|
||
or frame orchestration.
|
||
- Shell/system UI belongs to `gfxui.*` and `ShellUiFramePacket`, not Game HUD.
|
||
|
||
---
|
||
|
||
## 13. Transparency
|
||
|
||
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 resolved_color.alpha == 0:
|
||
skip
|
||
else:
|
||
draw_or_blend(resolved_color)
|
||
```
|
||
|
||
|
||
---
|
||
|
||
## 14. Color Math (Discrete Blending)
|
||
|
||
Inspired by the SNES.
|
||
|
||
Official modes:
|
||
|
||
- `BLEND_NONE`
|
||
- `BLEND_HALF`
|
||
- `BLEND_HALF_PLUS`
|
||
- `BLEND_HALF_MINUS`
|
||
- `BLEND_FULL`
|
||
|
||
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:
|
||
|
||
- integer
|
||
- cheap
|
||
- deterministic
|
||
|
||
---
|
||
|
||
## 15. Where Blend is Applied
|
||
|
||
- Blending occurs during drawing
|
||
- For canonical game composition, the result goes to the back buffer during composition
|
||
- For `gfx2d.*` and `gfxui.*` primitives, the result is applied when the render
|
||
surface consumes the closed packet for the active app mode
|
||
- There is no automatic GPU-style post-processing pipeline
|
||
|
||
---
|
||
|
||
## 16. What the GFX DOES NOT support
|
||
|
||
By design:
|
||
|
||
- Shaders
|
||
- Modern GPU pipeline
|
||
- HDR
|
||
- Gamma correction
|
||
- RGB565 compatibility framebuffers
|
||
- fade fields, fade syscalls, or fade packet members
|
||
- multi-format backend selection
|
||
- render-thread ownership as part of this contract
|
||
|
||
---
|
||
|
||
## 17. Performance Rule
|
||
|
||
- Layers:
|
||
- only update the border when crossing a tile
|
||
- never redraw the entire world
|
||
- Rasterization:
|
||
- always per frame, only the visible area
|
||
- Sprites:
|
||
- always redrawn per frame
|
||
|
||
---
|
||
|
||
## 18. Transitions and Removed Fade Contract
|
||
|
||
The previous special fade model is not part of the canonical render contract.
|
||
Current render packets, syscalls, and ABI domains MUST NOT expose scene fade,
|
||
HUD fade, fade levels, fade colors, or equivalent inherited fade state.
|
||
|
||
Future visual transitions between Shell and Game belong to `RenderManager`.
|
||
They are not owned by `composer.*`, `gfx2d.*`, `gfxui.*`, `Game2DFramePacket`,
|
||
or `ShellUiFramePacket`.
|
||
|
||
Rules:
|
||
|
||
- render domain packets MUST NOT contain fade fields;
|
||
- public render syscalls MUST NOT expose fade controls;
|
||
- render implementations MUST NOT treat fade as an inherited post-processing
|
||
habit;
|
||
- transition work requires an explicit `RenderManager` contract or a later
|
||
decision.
|
||
|
||
---
|
||
|
||
## 19. Palette System
|
||
|
||
### 19.1. Overview
|
||
|
||
PROMETEU uses **exclusively** palette-indexed graphics.
|
||
|
||
There is no direct RGB-per-pixel mode.
|
||
Every graphical pixel is an **index** pointing to a real color in a palette.
|
||
|
||
---
|
||
|
||
### 19.2. Pixel Format
|
||
|
||
Each pixel of a tile or sprite is:
|
||
|
||
- **4 bits per pixel (4bpp)**
|
||
- values: `0..15`
|
||
|
||
Fixed rule:
|
||
|
||
- Indices `0..15` are ordinary valid palette indices
|
||
- Transparency comes from the alpha channel of the resolved palette entry
|
||
|
||
---
|
||
|
||
### 19.3. Palette Structure
|
||
|
||
Each **Tile Bank** contains:
|
||
|
||
- **64 palettes** in runtime-facing v1
|
||
- Each palette has:
|
||
- **16 colors**
|
||
- each color in **RGBA8888** with canonical RGBA channel order
|
||
|
||
Size:
|
||
|
||
- 1 palette = 16 × 4 bytes = **64 bytes**
|
||
- 64 palettes = **4 KB per bank**
|
||
- 16 banks = **64 KB maximum palettes**
|
||
|
||
---
|
||
|
||
### 19.4. Palette Association
|
||
|
||
#### Fundamental Rule
|
||
|
||
- Each **tile** uses **a single palette**
|
||
- Each **sprite** uses **a single palette**
|
||
- The palette must be provided **explicitly** in every draw
|
||
|
||
There is no palette swap within the same tile or sprite.
|
||
|
||
---
|
||
|
||
### 19.5. Where the Palette is Defined
|
||
|
||
#### Tilemap
|
||
|
||
Each tilemap cell contains:
|
||
|
||
- `tile_id`
|
||
- `palette_id (u8)`
|
||
- `flip_x`
|
||
- `flip_y`
|
||
|
||
Runtime-facing validity rule for v1:
|
||
|
||
- `palette_id` values are valid only in the range `0..63`
|
||
|
||
#### Sprite
|
||
|
||
Each sprite draw contains:
|
||
|
||
- `bank_id`
|
||
- `tile_id`
|
||
- `palette_id (u8)`
|
||
- `x`, `y`
|
||
- `flip_x`, `flip_y`
|
||
- `priority`
|
||
|
||
Runtime-facing validity rule for v1:
|
||
|
||
- `palette_id` values are valid only in the range `0..63`
|
||
|
||
---
|
||
|
||
### 19.6. Color Resolution
|
||
|
||
The pipeline works like this:
|
||
|
||
1. Read indexed pixel from tile (value 0..15)
|
||
2. Resolve:
|
||
- real_color = palette[palette_id][index]
|
||
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)
|
||
color = bank.palettes[palette_id][pixel_index]
|
||
if color.alpha == 0:
|
||
skip
|
||
else:
|
||
draw_or_blend(color)
|
||
```
|
||
|
||
---
|
||
|
||
### 19.7. Organization of Tile Banks
|
||
|
||
Tile Banks are "strong assets":
|
||
|
||
- Tiles and palettes live together
|
||
- Export/import always carries:
|
||
- tiles + palettes
|
||
- In `assets.pa` v1, the serialized payload is:
|
||
- packed indexed pixels for the whole sheet
|
||
- followed by the palette table for the same bank
|
||
- The hardware does not impose semantic organization:
|
||
- grouping is the creator's decision
|
||
- Tooling and scripts can create conventions:
|
||
- e.g.: palettes 0..15 = enemies
|
||
- 16..31 = scenery
|
||
- etc.
|
||
|
||
Runtime-facing v1 baseline:
|
||
|
||
- sheet pixels are authored and resolved as indexed values `0..15`
|
||
- serialized tile-bank payload uses packed `u4` pixel indices
|
||
- runtime may materialize the decoded bank as expanded `u8` pixel indices plus palette table
|
||
|
||
---
|
||
|
||
### 19.8. Metrics for Certification (CAP)
|
||
|
||
The system can measure:
|
||
|
||
- `palettes_loaded_total`
|
||
- `palettes_referenced_this_frame`
|
||
- `tiles_drawn_by_palette_id`
|
||
- `sprites_drawn_by_palette_id`
|
||
|
||
---
|
||
|
||
## 20. Syscall Return and Fault Policy
|
||
|
||
Graphics-related public ABI in v1 is split between:
|
||
|
||
- `composer.*` for Game 2D high-level frame composition;
|
||
- `gfx2d.*` for Game 2D primitives only;
|
||
- `gfxui.*` for Shell UI primitives only.
|
||
|
||
`composer.*` and `gfx2d.*` are available to `AppMode::Game`.
|
||
`gfxui.*` is available to `AppMode::Shell`. Renderers and capabilities may
|
||
differ by app mode.
|
||
|
||
Only operations with real operational rejection paths return explicit status values.
|
||
|
||
Fault boundary:
|
||
|
||
- `Trap`: structural ABI misuse (type/arity/capability/shape mismatch);
|
||
- `status`: operational failure;
|
||
- `Panic`: internal runtime invariant break only.
|
||
|
||
### 20.1 Return-shape matrix in v1
|
||
|
||
| Syscall | Return | Policy basis |
|
||
| ----------------------- | ------------- | --------------------------------------------------- |
|
||
| `gfx2d.clear` | `void` | no real operational failure path in v1 |
|
||
| `gfx2d.fill_rect` | `void` | no real operational failure path in v1 |
|
||
| `gfx2d.draw_line` | `void` | no real operational failure path in v1 |
|
||
| `gfx2d.draw_circle` | `void` | no real operational failure path in v1 |
|
||
| `gfx2d.draw_disc` | `void` | no real operational failure path in v1 |
|
||
| `gfx2d.draw_square` | `void` | no real operational failure path in v1 |
|
||
| `gfx2d.draw_text` | `void` | no real operational failure path in v1 |
|
||
| `gfxui.*` primitives | `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 |
|
||
| `composer.emit_sprite` | `status:int` | explicit orchestration-domain operational rejection |
|
||
|
||
### 20.1.a Primitive domain semantics
|
||
|
||
The primitive domains have stable operational meaning:
|
||
|
||
- `gfx2d.*` is Game 2D primitive command buffering;
|
||
- `gfx2d.*` is screen-space and primitive-only relative to `composer.*`;
|
||
- `gfx2d.*` is outside scene, camera, sprites, HUD, and Game 2D frame orchestration;
|
||
- `gfxui.*` is Shell UI primitive command buffering;
|
||
- `gfxui.*` does not contain widget or layout policy.
|
||
|
||
Callers MUST NOT rely on stable immediate writes to the working back buffer as
|
||
the public contract for primitive drawing. Primitive calls mutate domain command
|
||
buffers that close into the active typed submission.
|
||
|
||
### 20.1.b Scene dependency fatal boundary
|
||
|
||
`composer.bind_scene` remains a status-returning syscall in the public ABI, but scene glyph dependency absence is outside the accepted passive operational-error model.
|
||
|
||
Rules:
|
||
|
||
- the target scene slot may still be rejected through ordinary status-returning behavior when the scene itself is unavailable;
|
||
- missing glyph dependencies referenced by a resident scene are not a passive `status:int` case;
|
||
- if scene activation discovers that a layer dependency cannot be resolved to a committed glyph asset, the machine MUST fail fatally and emit a clear log;
|
||
- if scene composition later discovers that a layer dependency can no longer be resolved, the machine MUST fail fatally and emit a clear log;
|
||
- runtime MUST NOT continue canonical scene composition after such a dependency failure.
|
||
|
||
### 20.2 `composer.emit_sprite`
|
||
|
||
`composer.emit_sprite` returns `status:int`.
|
||
|
||
ABI:
|
||
1. `glyph_id: int` — glyph index within the bank
|
||
2. `palette_id: int` — palette index
|
||
3. `x: int` — x coordinate
|
||
4. `y: int` — y coordinate
|
||
5. `layer: int` — composition layer reference
|
||
6. `bank_id: int` — glyph bank index
|
||
7. `flip_x: bool` — horizontal flip
|
||
8. `flip_y: bool` — vertical flip
|
||
9. `priority: int` — within-layer ordering priority
|
||
|
||
Minimum status table:
|
||
|
||
- `0` = `OK`
|
||
- `1` = `SCENE_UNAVAILABLE`
|
||
- `2` = `INVALID_ARG_RANGE`
|
||
- `3` = `BANK_INVALID`
|
||
- `4` = `LAYER_INVALID`
|
||
- `5` = `SPRITE_OVERFLOW`
|
||
|
||
Operational notes:
|
||
|
||
- the canonical public sprite contract is frame-emission based;
|
||
- no caller-provided sprite index exists in the v1 canonical ABI;
|
||
- no `active` flag exists in the v1 canonical ABI;
|
||
- overflow remains non-fatal and must not escalate to trap in v1.
|