implements PLN-0040 scene glyph spec and wire layout

This commit is contained in:
bQUARKz 2026-04-24 10:57:48 +01:00
parent c52f196b7a
commit 26686c288c
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
6 changed files with 91 additions and 13 deletions

View File

@ -53,7 +53,11 @@ pub const SCENE_PAYLOAD_MAGIC_V1: [u8; 4] = *b"SCNE";
pub const SCENE_PAYLOAD_VERSION_V1: u16 = 1; pub const SCENE_PAYLOAD_VERSION_V1: u16 = 1;
pub const SCENE_LAYER_COUNT_V1: usize = 4; pub const SCENE_LAYER_COUNT_V1: usize = 4;
pub const SCENE_HEADER_BYTES_V1: usize = 12; pub const SCENE_HEADER_BYTES_V1: usize = 12;
pub const SCENE_LAYER_HEADER_BYTES_V1: usize = 28; // Per-layer header:
// flags:u8 + glyph_asset_id:i32 + tile_size:u8 + reserved:u16
// + parallax_x:f32 + parallax_y:f32 + width:u32 + height:u32
// + tile_count:u32 + reserved:u32
pub const SCENE_LAYER_HEADER_BYTES_V1: usize = 32;
pub const SCENE_TILE_RECORD_BYTES_V1: usize = 4; pub const SCENE_TILE_RECORD_BYTES_V1: usize = 4;
pub const SCENE_DECODED_LAYER_OVERHEAD_BYTES_V1: usize = 16; pub const SCENE_DECODED_LAYER_OVERHEAD_BYTES_V1: usize = 16;

View File

@ -21,7 +21,7 @@
{"type":"discussion","id":"DSC-0026","status":"done","ticket":"render-all-scene-cache-and-camera-integration","title":"Integrate render_all with Scene Cache and Camera","created_at":"2026-04-14","updated_at":"2026-04-18","tags":["gfx","runtime","render","camera","scene"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0031","file":"lessons/DSC-0026-render-all-scene-cache-and-camera-integration/LSN-0031-frame-composition-belongs-above-the-render-backend.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]} {"type":"discussion","id":"DSC-0026","status":"done","ticket":"render-all-scene-cache-and-camera-integration","title":"Integrate render_all with Scene Cache and Camera","created_at":"2026-04-14","updated_at":"2026-04-18","tags":["gfx","runtime","render","camera","scene"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0031","file":"lessons/DSC-0026-render-all-scene-cache-and-camera-integration/LSN-0031-frame-composition-belongs-above-the-render-backend.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]}
{"type":"discussion","id":"DSC-0027","status":"done","ticket":"frame-composer-public-syscall-surface","title":"Agenda - FrameComposer Public Syscall Surface","created_at":"2026-04-17","updated_at":"2026-04-18","tags":["gfx","runtime","syscall","abi","frame-composer","scene","camera","sprites"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0032","file":"lessons/DSC-0027-frame-composer-public-syscall-surface/LSN-0032-public-abi-must-follow-the-canonical-service-boundary.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]} {"type":"discussion","id":"DSC-0027","status":"done","ticket":"frame-composer-public-syscall-surface","title":"Agenda - FrameComposer Public Syscall Surface","created_at":"2026-04-17","updated_at":"2026-04-18","tags":["gfx","runtime","syscall","abi","frame-composer","scene","camera","sprites"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0032","file":"lessons/DSC-0027-frame-composer-public-syscall-surface/LSN-0032-public-abi-must-follow-the-canonical-service-boundary.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]}
{"type":"discussion","id":"DSC-0028","status":"done","ticket":"deferred-overlay-and-primitive-composition","title":"Deferred Overlay and Primitive Composition over FrameComposer","created_at":"2026-04-18","updated_at":"2026-04-18","tags":["gfx","runtime","render","frame-composer","overlay","primitives","hud"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0033","file":"lessons/DSC-0028-deferred-overlay-and-primitive-composition/LSN-0033-debug-primitives-should-be-a-final-overlay-not-part-of-game-composition.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]} {"type":"discussion","id":"DSC-0028","status":"done","ticket":"deferred-overlay-and-primitive-composition","title":"Deferred Overlay and Primitive Composition over FrameComposer","created_at":"2026-04-18","updated_at":"2026-04-18","tags":["gfx","runtime","render","frame-composer","overlay","primitives","hud"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0033","file":"lessons/DSC-0028-deferred-overlay-and-primitive-composition/LSN-0033-debug-primitives-should-be-a-final-overlay-not-part-of-game-composition.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]}
{"type":"discussion","id":"DSC-0029","status":"open","ticket":"scene-bank-glyph-runtime-binding-leak","title":"Scene Bank Glyph Runtime Binding Leak","created_at":"2026-04-24","updated_at":"2026-04-24","tags":["gfx","runtime","asset","scene","glyph","format","architecture"],"agendas":[{"id":"AGD-0029","file":"AGD-0029-scene-bank-glyph-runtime-binding-leak.md","status":"accepted","created_at":"2026-04-24","updated_at":"2026-04-24"}],"decisions":[{"id":"DEC-0021","file":"DEC-0021-scene-bank-glyph-dependencies-must-bind-by-asset-id.md","status":"in_progress","created_at":"2026-04-24","updated_at":"2026-04-24","ref_agenda":"AGD-0029"}],"plans":[{"id":"PLN-0040","file":"PLN-0040-scene-glyph-dependency-spec-and-wire-layout.md","status":"review","created_at":"2026-04-24","updated_at":"2026-04-24","ref_decisions":["DEC-0021"]},{"id":"PLN-0041","file":"PLN-0041-scene-glyph-runtime-resolution-and-fatal-paths.md","status":"review","created_at":"2026-04-24","updated_at":"2026-04-24","ref_decisions":["DEC-0021"]},{"id":"PLN-0042","file":"PLN-0042-scene-glyph-tooling-and-fixture-migration.md","status":"review","created_at":"2026-04-24","updated_at":"2026-04-24","ref_decisions":["DEC-0021"]}],"lessons":[]} {"type":"discussion","id":"DSC-0029","status":"open","ticket":"scene-bank-glyph-runtime-binding-leak","title":"Scene Bank Glyph Runtime Binding Leak","created_at":"2026-04-24","updated_at":"2026-04-24","tags":["gfx","runtime","asset","scene","glyph","format","architecture"],"agendas":[{"id":"AGD-0029","file":"AGD-0029-scene-bank-glyph-runtime-binding-leak.md","status":"accepted","created_at":"2026-04-24","updated_at":"2026-04-24"}],"decisions":[{"id":"DEC-0021","file":"DEC-0021-scene-bank-glyph-dependencies-must-bind-by-asset-id.md","status":"in_progress","created_at":"2026-04-24","updated_at":"2026-04-24","ref_agenda":"AGD-0029"}],"plans":[{"id":"PLN-0040","file":"PLN-0040-scene-glyph-dependency-spec-and-wire-layout.md","status":"done","created_at":"2026-04-24","updated_at":"2026-04-24","ref_decisions":["DEC-0021"]},{"id":"PLN-0041","file":"PLN-0041-scene-glyph-runtime-resolution-and-fatal-paths.md","status":"review","created_at":"2026-04-24","updated_at":"2026-04-24","ref_decisions":["DEC-0021"]},{"id":"PLN-0042","file":"PLN-0042-scene-glyph-tooling-and-fixture-migration.md","status":"review","created_at":"2026-04-24","updated_at":"2026-04-24","ref_decisions":["DEC-0021"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0014","status":"done","ticket":"perf-vm-allocation-and-copy-pressure","title":"Agenda - [PERF] VM Allocation and Copy Pressure","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0035","file":"lessons/DSC-0014-perf-vm-allocation-and-copy-pressure/LSN-0035-first-materialization-is-not-the-same-as-hot-path-copy-pressure.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20"}]} {"type":"discussion","id":"DSC-0014","status":"done","ticket":"perf-vm-allocation-and-copy-pressure","title":"Agenda - [PERF] VM Allocation and Copy Pressure","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0035","file":"lessons/DSC-0014-perf-vm-allocation-and-copy-pressure/LSN-0035-first-materialization-is-not-the-same-as-hot-path-copy-pressure.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20"}]}
{"type":"discussion","id":"DSC-0015","status":"abandoned","ticket":"perf-cartridge-boot-and-program-ownership","title":"Agenda - [PERF] Cartridge Boot and Program Ownership","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[{"id":"AGD-0014","file":"workflow/agendas/AGD-0014-perf-cartridge-boot-and-program-ownership.md","status":"abandoned","created_at":"2026-03-27","updated_at":"2026-04-20","_override_reason":"User explicitly chose to close the discussion without decision because FS->memory copy for the program is already acceptable."}],"decisions":[],"plans":[],"lessons":[],"_override_reason":"User explicitly chose to abandon the discussion without creating a decision because FS->memory copy for the program is already acceptable."} {"type":"discussion","id":"DSC-0015","status":"abandoned","ticket":"perf-cartridge-boot-and-program-ownership","title":"Agenda - [PERF] Cartridge Boot and Program Ownership","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[{"id":"AGD-0014","file":"workflow/agendas/AGD-0014-perf-cartridge-boot-and-program-ownership.md","status":"abandoned","created_at":"2026-03-27","updated_at":"2026-04-20","_override_reason":"User explicitly chose to close the discussion without decision because FS->memory copy for the program is already acceptable."}],"decisions":[],"plans":[],"lessons":[],"_override_reason":"User explicitly chose to abandon the discussion without creating a decision because FS->memory copy for the program is already acceptable."}
{"type":"discussion","id":"DSC-0016","status":"done","ticket":"tilemap-empty-cell-vs-tile-id-zero","title":"Tilemap Empty Cell vs Tile ID Zero","created_at":"2026-03-27","updated_at":"2026-04-09","tags":[],"agendas":[{"id":"AGD-0015","file":"workflow/agendas/AGD-0015-tilemap-empty-cell-vs-tile-id-zero.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-09"}],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0022","file":"lessons/DSC-0016-tilemap-empty-cell-semantics/LSN-0022-tilemap-empty-cell-convergence.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} {"type":"discussion","id":"DSC-0016","status":"done","ticket":"tilemap-empty-cell-vs-tile-id-zero","title":"Tilemap Empty Cell vs Tile ID Zero","created_at":"2026-03-27","updated_at":"2026-04-09","tags":[],"agendas":[{"id":"AGD-0015","file":"workflow/agendas/AGD-0015-tilemap-empty-cell-vs-tile-id-zero.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-09"}],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0022","file":"lessons/DSC-0016-tilemap-empty-cell-semantics/LSN-0022-tilemap-empty-cell-convergence.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]}

View File

@ -2,9 +2,9 @@
id: PLN-0040 id: PLN-0040
ticket: scene-bank-glyph-runtime-binding-leak ticket: scene-bank-glyph-runtime-binding-leak
title: Scene glyph dependency spec and wire layout migration title: Scene glyph dependency spec and wire layout migration
status: review status: done
created: 2026-04-24 created: 2026-04-24
completed: completed: 2026-04-24
tags: [gfx, runtime, asset, scene, glyph, format, spec] tags: [gfx, runtime, asset, scene, glyph, format, spec]
--- ---
@ -102,4 +102,3 @@ Remove proposed/legacy wording that frames missing dependencies as recoverable s
- Spec-only edits can drift from the runtime implementation if later plans are not executed promptly. - Spec-only edits can drift from the runtime implementation if later plans are not executed promptly.
- Scene payload layout changes may require touching multiple docs; missing one will leave contradictory contract text. - Scene payload layout changes may require touching multiple docs; missing one will leave contradictory contract text.
- If HAL constants are updated here before runtime code, temporary compile or test fallout may appear in later plans and must be handled intentionally. - If HAL constants are updated here before runtime code, temporary compile or test fallout may appear in later plans and must be handled intentionally.

View File

@ -579,7 +579,7 @@ Fault boundary:
| `gfx.draw_square` | `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.draw_text` | `void` | no real operational failure path in v1 |
| `gfx.clear_565` | `void` | no real operational failure path in v1 | | `gfx.clear_565` | `void` | no real operational failure path in v1 |
| `composer.bind_scene` | `status:int` | explicit orchestration-domain operational result | | `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.unbind_scene` | `status:int` | explicit orchestration-domain operational result |
| `composer.set_camera` | `void` | no real operational failure path in v1 | | `composer.set_camera` | `void` | no real operational failure path in v1 |
| `composer.emit_sprite` | `status:int` | explicit orchestration-domain operational rejection | | `composer.emit_sprite` | `status:int` | explicit orchestration-domain operational rejection |
@ -596,6 +596,18 @@ The public `gfx.*` primitive family remains valid in v1, but its stable operatio
This means callers MUST NOT rely on stable immediate writes to the working back buffer as the public contract for `gfx.draw_text(...)` or sibling primitives. This means callers MUST NOT rely on stable immediate writes to the working back buffer as the public contract for `gfx.draw_text(...)` or sibling primitives.
### 19.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.
### 19.2 `composer.emit_sprite` ### 19.2 `composer.emit_sprite`
`composer.emit_sprite` returns `status:int`. `composer.emit_sprite` returns `status:int`.

View File

@ -108,9 +108,9 @@ This table describes content identity and storage layout, not live residency.
`codec` does not define the bank-specific serialized layout itself. Specialized banks may still have normative decode rules even when `codec = NONE`. `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 ### 4.1 `GLYPH` asset contract in v1
For `BankType::TILES`, the v1 runtime-facing contract is: For `BankType::GLYPH`, the v1 runtime-facing contract is:
- `codec = NONE` - `codec = NONE`
- serialized pixels use packed `u4` palette indices - serialized pixels use packed `u4` palette indices
@ -118,20 +118,20 @@ For `BankType::TILES`, the v1 runtime-facing contract is:
- `palette_count = 64` - `palette_count = 64`
- runtime materialization may expand pixel indices to one `u8` per pixel - runtime materialization may expand pixel indices to one `u8` per pixel
For `TILES`, `NONE` means there is no additional generic codec layer beyond the bank contract itself. For `GLYPH`, `NONE` means there is no additional generic codec layer beyond the bank contract itself.
For the current transition window: For the current transition window:
- `RAW` is a legacy deprecated alias of `NONE` - `RAW` is a legacy deprecated alias of `NONE`
- newly published material must use `NONE` as the canonical value - newly published material must use `NONE` as the canonical value
Even with `codec = NONE`, `TILES` still requires bank-specific decode from its serialized payload. The serialized byte layout therefore does not need to match the in-memory layout. 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 #### 4.1.1 Metadata Normalization
Following `DEC-0004`, the `AssetEntry.metadata` field must be structured in segmented form to avoid ambiguity. Following `DEC-0004`, the `AssetEntry.metadata` field must be structured in segmented form to avoid ambiguity.
Required effective metadata fields for `TILES` at the root level: Required effective metadata fields for `GLYPH` at the root level:
- `tile_size`: tile edge in pixels; valid values are `8`, `16`, or `32` - `tile_size`: tile edge in pixels; valid values are `8`, `16`, or `32`
- `width`: total sheet width in pixels - `width`: total sheet width in pixels
@ -143,7 +143,7 @@ Optional informative subtrees:
- `metadata.codec`: codec/compressor-specific configuration (for example dictionaries or compression flags). - `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). - `metadata.pipeline`: informative metadata from the Studio build process (for example source hashes, timestamps, or tool versions).
Validation rules for `TILES` v1: Validation rules for `GLYPH` v1:
- `palette_count` must be `64` - `palette_count` must be `64`
- `width * height` defines the number of logical indexed pixels in the decoded sheet - `width * height` defines the number of logical indexed pixels in the decoded sheet
@ -160,17 +160,74 @@ The tile-bank payload therefore separates serialized storage form from runtime m
- decoded pixel plane: expanded `u8` indices, one entry per pixel - decoded pixel plane: expanded `u8` indices, one entry per pixel
- palette table: `64 * 16` colors in `RGB565` - palette table: `64 * 16` colors in `RGB565`
For `TILES` v1: For `GLYPH` v1:
- `size` must match `ceil(width * height / 2) + (palette_count * 16 * 2)` - `size` must match `ceil(width * height / 2) + (palette_count * 16 * 2)`
- `decoded_size` must match `(width * height) + (palette_count * 16 * 2)` - `decoded_size` must match `(width * height) + (palette_count * 16 * 2)`
### 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 `AssetId` using 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:
1. `flags: u8`
2. `glyph_asset_id: i32` (little-endian)
3. `tile_size: u8`
4. `reserved: u16`
5. `parallax_x: f32`
6. `parallax_y: f32`
7. `width: u32`
8. `height: u32`
9. `tile_count: u32`
10. `reserved: u32`
This makes the canonical per-layer header size `32` bytes.
Normative rules:
- `glyph_asset_id` MUST be interpreted as the cold identity of the glyph dependency for that layer;
- `glyph_asset_id` MUST 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_count` MUST equal `width * 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 `AssetId` to 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_scene` MUST 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 ## 5 Banks and Slots
The current runtime exposes bank types: The current runtime exposes bank types:
- `GLYPH` - `GLYPH`
- `SOUNDS` - `SOUNDS`
- `SCENE`
Assets are loaded into explicit slots identified by slot index at the public ABI boundary. Assets are loaded into explicit slots identified by slot index at the public ABI boundary.

View File

@ -229,6 +229,12 @@ For mutating composer operations:
- `set_camera` remains `void` in v1; - `set_camera` remains `void` in v1;
- no caller-provided sprite index or `active` flag is part of the canonical contract. - no caller-provided sprite index or `active` flag is part of the canonical contract.
Fatal boundary clarification for `bind_scene`:
- `bind_scene` remains status-returning for ordinary orchestration-domain rejection such as unresolved target scene slot;
- missing glyph dependencies inside a resident scene are not accepted as passive `ComposerOpStatus` outcomes;
- if scene activation or later composition detects an unresolved scene glyph dependency, runtime MUST escalate that condition as a fatal machine error with clear logging.
## 7 Syscalls as Callable Entities (Not First-Class) ## 7 Syscalls as Callable Entities (Not First-Class)
Syscalls behave like call sites, not like first-class guest values. Syscalls behave like call sites, not like first-class guest values.