---
id: LSN-0041
ticket: studio-frame-composer-syscall-and-sprite-alignment
title: Composer Must Own the Public Sprite-Composition Surface
created: 2026-04-18
tags: [studio, compiler, pbs, stdlib, runtime-alignment, abi, syscall, frame-composer, sprites]
---

## Context

The runtime had already moved canonical frame orchestration to `FrameComposer` and published the corresponding public ABI under `composer.*`, but Studio still exposed sprite composition through `@sdk:gfx` and `Gfx.set_sprite`.

That left the repository teaching the wrong owner even though runtime dispatch, architectural lessons, and the public syscall domain had already changed. The migration closed that drift for the sprite path without pulling scene binding, camera control, or scene-bank work into the same ticket.

## Key Decisions

### Public Source Ownership Must Match the Runtime ABI

**What:**
Studio now exposes sprite composition through `@sdk:composer`, with `LowComposer` as the low-level host owner and `Composer` as the public service facade. The old `Gfx.set_sprite` path was removed instead of being kept as compatibility sugar.

**Why:**
Once runtime ownership moved to `FrameComposer`, keeping sprite submission under `gfx.*` would preserve a false mental model and create a dual contract between what the runtime actually owns and what PBS/stdlib teaches.

**Trade-offs:**
The migration required coordinated changes across stdlib resources, specs, frontend conformance, examples, and LSP fixtures. That was more work up front, but it avoided a longer-lived compatibility layer that would have encoded the wrong architecture.

### A Partial Domain Rollout Is Valid If the Boundary Is Explicit

**What:**
This wave shipped only `composer.emit_sprite(...)` in Studio. `bind_scene`, `unbind_scene`, and `set_camera` were explicitly deferred.

**Why:**
The current pipeline only needed sprite composition. Pulling scene/camera rollout into the same thread would have mixed ABI convergence with unfinished scene-work concerns and made the migration harder to execute cleanly.

**Trade-offs:**
The docs had to state clearly that `@sdk:composer` exists now for sprite emission while other composer members remain future work. That is slightly less symmetrical in the short term, but it keeps the implemented contract honest.

### Raw Status Returns Can Be Preserved During Ownership Migration

**What:**
`Composer.emit_sprite(...)` kept the raw `int` status return for this wave instead of introducing a new editorial status shell.

**Why:**
The architectural problem in this discussion was ownership and ABI drift, not status typing. Keeping the return shape stable allowed the migration to focus on the service boundary and public surface without coupling it to a second API redesign.

**Trade-offs:**
The API remains less expressive than a nominal status shell, but the migration stayed narrow and easier to validate end to end.

## Patterns and Algorithms

- Treat runtime service ownership changes as public-source changes, not merely internal lowering rewrites.
- When the runtime removes a legacy public syscall path, prefer removing the matching source-level facade instead of silently retargeting it.
- Split migrations by boundary: first stdlib/spec surface, then compiler/conformance, then examples and fixture cleanup.
- Keep primitive-oriented `@sdk:gfx` coverage intact while moving only frame-composition behavior to `@sdk:composer`.
- Use a negative regression test for the removed API so compatibility leakage is caught explicitly.

## Pitfalls

- Retargeting `Gfx.set_sprite` internally to `composer.emit_sprite` would have looked convenient, but it would have preserved the wrong owner in the public teaching surface.
- A repository sweep that ignores examples and fixtures leaves stale guidance behind even when compiler tests are green.
- Replacing every `@sdk:gfx` example wholesale would be overcorrection; primitive and overlay operations still belong there.
- Updating docs to mention deferred composer members as if they already shipped would create a second kind of contract drift.

## Takeaways

- Public SDK ownership should mirror canonical runtime ownership, not historical implementation leftovers.
- Removing the old API is safer than keeping a compatibility facade that encodes the wrong subsystem boundary.
- A sprite-only `@sdk:composer` wave is acceptable when the deferred scene/camera scope is stated explicitly.
- Ownership migration and status-typing redesign do not need to happen in the same change.
