implements PLN-0094

This commit is contained in:
bQUARKz 2026-06-05 09:49:59 +01:00
parent c88c927fee
commit f01822bd53
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
3 changed files with 162 additions and 32 deletions

View File

@ -1,6 +1,6 @@
{"type":"meta","next_id":{"DSC":42,"AGD":42,"DEC":32,"PLN":98,"LSN":48,"CLSN":1}} {"type":"meta","next_id":{"DSC":42,"AGD":42,"DEC":32,"PLN":98,"LSN":48,"CLSN":1}}
{"type":"discussion","id":"DSC-0039","status":"abandoned","ticket":"render-pipeline-family-and-future-3d","title":"Render Pipeline Family and Future 3D","created_at":"2026-06-04","updated_at":"2026-06-04","tags":["gfx","renderer","runtime","architecture","pipeline"],"agendas":[{"id":"AGD-0039","file":"AGD-0039-render-pipeline-family-and-future-3d.md","status":"abandoned","created_at":"2026-06-04","updated_at":"2026-06-04","_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."}],"decisions":[],"plans":[],"lessons":[],"_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."} {"type":"discussion","id":"DSC-0039","status":"abandoned","ticket":"render-pipeline-family-and-future-3d","title":"Render Pipeline Family and Future 3D","created_at":"2026-06-04","updated_at":"2026-06-04","tags":["gfx","renderer","runtime","architecture","pipeline"],"agendas":[{"id":"AGD-0039","file":"AGD-0039-render-pipeline-family-and-future-3d.md","status":"abandoned","created_at":"2026-06-04","updated_at":"2026-06-04","_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."}],"decisions":[],"plans":[],"lessons":[],"_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."}
{"type":"discussion","id":"DSC-0040","status":"in_progress","ticket":"vm-render-parallel-execution-boundary","title":"VM and Render Parallel Execution Boundary","created_at":"2026-06-04","updated_at":"2026-06-05","tags":["runtime","renderer","vm","concurrency","architecture","perf"],"agendas":[{"id":"AGD-0040","file":"AGD-0040-vm-and-render-parallel-execution-boundary.md","status":"accepted","created_at":"2026-06-04","updated_at":"2026-06-05"}],"decisions":[{"id":"DEC-0031","file":"DEC-0031-vm-and-render-parallel-execution-boundary.md","status":"accepted","created_at":"2026-06-05","updated_at":"2026-06-05","ref_agenda":"AGD-0040"}],"plans":[{"id":"PLN-0087","file":"PLN-0087-fix-game2d-packet-overlay-composition.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0088","file":"PLN-0088-define-read-only-render-resource-boundary.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0089","file":"PLN-0089-introduce-render-handoff-abstraction.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0090","file":"PLN-0090-add-render-frame-pacing-contract.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0091","file":"PLN-0091-implement-appmode-render-policy-matrix.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0092","file":"PLN-0092-add-render-epoch-ownership-transitions.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0093","file":"PLN-0093-add-render-telemetry-counters-and-events.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0094","file":"PLN-0094-specify-async-render-boundary.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0095","file":"PLN-0095-prototype-local-render-worker-capability.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0096","file":"PLN-0096-define-render-shutdown-and-failure-handling.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0097","file":"PLN-0097-add-async-render-integration-test-matrix.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]}],"lessons":[]} {"type":"discussion","id":"DSC-0040","status":"in_progress","ticket":"vm-render-parallel-execution-boundary","title":"VM and Render Parallel Execution Boundary","created_at":"2026-06-04","updated_at":"2026-06-05","tags":["runtime","renderer","vm","concurrency","architecture","perf"],"agendas":[{"id":"AGD-0040","file":"AGD-0040-vm-and-render-parallel-execution-boundary.md","status":"accepted","created_at":"2026-06-04","updated_at":"2026-06-05"}],"decisions":[{"id":"DEC-0031","file":"DEC-0031-vm-and-render-parallel-execution-boundary.md","status":"accepted","created_at":"2026-06-05","updated_at":"2026-06-05","ref_agenda":"AGD-0040"}],"plans":[{"id":"PLN-0087","file":"PLN-0087-fix-game2d-packet-overlay-composition.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0088","file":"PLN-0088-define-read-only-render-resource-boundary.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0089","file":"PLN-0089-introduce-render-handoff-abstraction.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0090","file":"PLN-0090-add-render-frame-pacing-contract.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0091","file":"PLN-0091-implement-appmode-render-policy-matrix.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0092","file":"PLN-0092-add-render-epoch-ownership-transitions.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0093","file":"PLN-0093-add-render-telemetry-counters-and-events.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0094","file":"PLN-0094-specify-async-render-boundary.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0095","file":"PLN-0095-prototype-local-render-worker-capability.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0096","file":"PLN-0096-define-render-shutdown-and-failure-handling.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0097","file":"PLN-0097-add-async-render-integration-test-matrix.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0041","status":"open","ticket":"foreground-stack-game-pause-shell-vm-backed","title":"Foreground Stack, Game Pause, and VM-Backed Shell Coexistence","created_at":"2026-06-05","updated_at":"2026-06-05","tags":["runtime","os","lifecycle","shell","game","vm","foreground","architecture"],"agendas":[{"id":"AGD-0041","file":"AGD-0041-foreground-stack-game-pause-shell-vm-backed.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0041","status":"open","ticket":"foreground-stack-game-pause-shell-vm-backed","title":"Foreground Stack, Game Pause, and VM-Backed Shell Coexistence","created_at":"2026-06-05","updated_at":"2026-06-05","tags":["runtime","os","lifecycle","shell","game","vm","foreground","architecture"],"agendas":[{"id":"AGD-0041","file":"AGD-0041-foreground-stack-game-pause-shell-vm-backed.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0038","status":"done","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-06-04","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0047","file":"discussion/lessons/DSC-0038-render-frame-packet-boundary/LSN-0047-typed-render-submissions-preserve-domain-boundaries.md","status":"done","created_at":"2026-06-04","updated_at":"2026-06-04"}]} {"type":"discussion","id":"DSC-0038","status":"done","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-06-04","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0047","file":"discussion/lessons/DSC-0038-render-frame-packet-boundary/LSN-0047-typed-render-submissions-preserve-domain-boundaries.md","status":"done","created_at":"2026-06-04","updated_at":"2026-06-04"}]}
{"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]}

View File

@ -2,7 +2,7 @@
id: PLN-0094 id: PLN-0094
ticket: vm-render-parallel-execution-boundary ticket: vm-render-parallel-execution-boundary
title: Specify Async Render Boundary title: Specify Async Render Boundary
status: open status: done
created: 2026-06-05 created: 2026-06-05
ref_decisions: [DEC-0031] ref_decisions: [DEC-0031]
tags: [spec, runtime, renderer, architecture] tags: [spec, runtime, renderer, architecture]

View File

@ -92,7 +92,137 @@ This guarantees:
--- ---
## 4. PROMETEU Graphical Structure ## 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.
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.
### 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`.
### 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 outside this contract and are tracked by the
foreground/lifecycle discussion.
### 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.
---
## 5. PROMETEU Graphical Structure
The graphical world is composed of: The graphical world is composed of:
@ -101,7 +231,7 @@ The graphical world is composed of:
- **1 HUD Layer** (fixed, always on top) - **1 HUD Layer** (fixed, always on top)
- Sprites with priority between layers - Sprites with priority between layers
### 4.1 Tile Banks ### 5.1 Tile Banks
- There are up to **16 banks** - There are up to **16 banks**
- Each bank has a fixed tile size: - Each bank has a fixed tile size:
@ -115,7 +245,7 @@ The graphical world is composed of:
- serialized pixels are `4bpp` packed in payload order - serialized pixels are `4bpp` packed in payload order
- runtime memory may expand pixels to one `u8` palette index per pixel after decode - runtime memory may expand pixels to one `u8` palette index per pixel after decode
### 4.2 Layers ### 5.2 Layers
- There are: - There are:
- 4 Tile Layers - 4 Tile Layers
@ -129,7 +259,7 @@ The graphical world is composed of:
--- ---
## 5. Internal Model of a Tile Layer ## 6. Internal Model of a Tile Layer
A Tile Layer **is not a bitmap of pixels**. A Tile Layer **is not a bitmap of pixels**.
It is composed of: It is composed of:
@ -149,7 +279,7 @@ It is composed of:
--- ---
## 6. Logical Tilemap ## 7. Logical Tilemap
The tilemap represents the world: The tilemap represents the world:
@ -165,7 +295,7 @@ The tilemap can be much larger than the screen.
--- ---
## 7. Border Cache (Tile Cache) ## 8. Border Cache (Tile Cache)
The cache is a window of tiles around the camera. The cache is a window of tiles around the camera.
@ -179,7 +309,7 @@ It stores tiles already resolved from the tilemap.
--- ---
## 8. Cache Update ## 9. Cache Update
Every frame: Every frame:
@ -201,7 +331,7 @@ Only **one row and/or column** is updated per frame.
--- ---
## 9. Cache as Ring Buffer ## 10. Cache as Ring Buffer
The cache is circular: The cache is circular:
@ -214,7 +344,7 @@ Access:
--- ---
## 10. Canonical Game Projection ## 11. Canonical Game Projection
Game mode uses a typed Game 2D submission. `composer.*` owns high-level Game 2D Game mode uses a typed Game 2D submission. `composer.*` owns high-level Game 2D
frame composition: scene binding, camera, sprites, HUD, and frame orchestration. frame composition: scene binding, camera, sprites, HUD, and frame orchestration.
@ -243,7 +373,7 @@ uses `gfxui.*` and `ShellUiFramePacket`; it is never part of Game HUD or
--- ---
## 11. Drawing Order and Priority ## 12. Drawing Order and Priority
- There is no Z-buffer - There is no Z-buffer
- There is no automatic sorting - There is no automatic sorting
@ -269,7 +399,7 @@ Normative boundary:
--- ---
## 12. Transparency ## 13. Transparency
Transparency is represented by the alpha channel of the resolved RGBA8888 Transparency is represented by the alpha channel of the resolved RGBA8888
color. color.
@ -289,7 +419,7 @@ else:
--- ---
## 13. Color Math (Discrete Blending) ## 14. Color Math (Discrete Blending)
Inspired by the SNES. Inspired by the SNES.
@ -313,7 +443,7 @@ Everything is:
--- ---
## 14. Where Blend is Applied ## 15. Where Blend is Applied
- Blending occurs during drawing - Blending occurs during drawing
- For canonical game composition, the result goes to the back buffer during composition - For canonical game composition, the result goes to the back buffer during composition
@ -323,7 +453,7 @@ Everything is:
--- ---
## 15. What the GFX DOES NOT support ## 16. What the GFX DOES NOT support
By design: By design:
@ -338,7 +468,7 @@ By design:
--- ---
## 16. Performance Rule ## 17. Performance Rule
- Layers: - Layers:
- only update the border when crossing a tile - only update the border when crossing a tile
@ -350,7 +480,7 @@ By design:
--- ---
## 17. Transitions and Removed Fade Contract ## 18. Transitions and Removed Fade Contract
The previous special fade model is not part of the canonical render 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, Current render packets, syscalls, and ABI domains MUST NOT expose scene fade,
@ -371,9 +501,9 @@ Rules:
--- ---
## 18. Palette System ## 19. Palette System
### 18.1. Overview ### 19.1. Overview
PROMETEU uses **exclusively** palette-indexed graphics. PROMETEU uses **exclusively** palette-indexed graphics.
@ -382,7 +512,7 @@ Every graphical pixel is an **index** pointing to a real color in a palette.
--- ---
### 18.2. Pixel Format ### 19.2. Pixel Format
Each pixel of a tile or sprite is: Each pixel of a tile or sprite is:
@ -396,7 +526,7 @@ Fixed rule:
--- ---
### 18.3. Palette Structure ### 19.3. Palette Structure
Each **Tile Bank** contains: Each **Tile Bank** contains:
@ -413,7 +543,7 @@ Size:
--- ---
### 18.4. Palette Association ### 19.4. Palette Association
#### Fundamental Rule #### Fundamental Rule
@ -425,7 +555,7 @@ There is no palette swap within the same tile or sprite.
--- ---
### 18.5. Where the Palette is Defined ### 19.5. Where the Palette is Defined
#### Tilemap #### Tilemap
@ -457,7 +587,7 @@ Runtime-facing validity rule for v1:
--- ---
### 18.6. Color Resolution ### 19.6. Color Resolution
The pipeline works like this: The pipeline works like this:
@ -482,7 +612,7 @@ else:
--- ---
### 18.7. Organization of Tile Banks ### 19.7. Organization of Tile Banks
Tile Banks are "strong assets": Tile Banks are "strong assets":
@ -507,7 +637,7 @@ Runtime-facing v1 baseline:
--- ---
### 18.8. Metrics for Certification (CAP) ### 19.8. Metrics for Certification (CAP)
The system can measure: The system can measure:
@ -518,7 +648,7 @@ The system can measure:
--- ---
## 19. Syscall Return and Fault Policy ## 20. Syscall Return and Fault Policy
Graphics-related public ABI in v1 is split between: Graphics-related public ABI in v1 is split between:
@ -538,7 +668,7 @@ Fault boundary:
- `status`: operational failure; - `status`: operational failure;
- `Panic`: internal runtime invariant break only. - `Panic`: internal runtime invariant break only.
### 19.1 Return-shape matrix in v1 ### 20.1 Return-shape matrix in v1
| Syscall | Return | Policy basis | | Syscall | Return | Policy basis |
| ----------------------- | ------------- | --------------------------------------------------- | | ----------------------- | ------------- | --------------------------------------------------- |
@ -555,7 +685,7 @@ Fault boundary:
| `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 |
### 19.1.a Primitive domain semantics ### 20.1.a Primitive domain semantics
The primitive domains have stable operational meaning: The primitive domains have stable operational meaning:
@ -569,7 +699,7 @@ 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 the public contract for primitive drawing. Primitive calls mutate domain command
buffers that close into the active typed submission. buffers that close into the active typed submission.
### 19.1.b Scene dependency fatal boundary ### 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. `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.
@ -581,7 +711,7 @@ Rules:
- if scene composition later discovers that a layer dependency can no longer be resolved, 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. - runtime MUST NOT continue canonical scene composition after such a dependency failure.
### 19.2 `composer.emit_sprite` ### 20.2 `composer.emit_sprite`
`composer.emit_sprite` returns `status:int`. `composer.emit_sprite` returns `status:int`.