diff --git a/docs/specs/compiler-languages/pbs/5. Manifest, Stdlib, and SDK Resolution Specification.md b/docs/specs/compiler-languages/pbs/5. Manifest, Stdlib, and SDK Resolution Specification.md index 21a23fcb..1d1f2a5e 100644 --- a/docs/specs/compiler-languages/pbs/5. Manifest, Stdlib, and SDK Resolution Specification.md +++ b/docs/specs/compiler-languages/pbs/5. Manifest, Stdlib, and SDK Resolution Specification.md @@ -18,7 +18,7 @@ This document defines the normative compile-time model for: This document is the authority for how the compiler discovers and loads: - ordinary project modules, -- reserved stdlib modules such as `@sdk:gfx`, +- reserved stdlib modules such as `@sdk:gfx` and `@sdk:composer`, - compile-time reserved metadata attached to those reserved modules, - interface-only service facades that wrap host surfaces for source ergonomics, - and backend-provided compile surfaces consumed by the frontend for symbolic authoring. @@ -211,6 +211,20 @@ Rules: - `Gfx` becomes visible only through `import`. - The compiler accepts that import because the selected stdlib environment exposes `@sdk:gfx`. +For sprite composition in stdlib line `1`, the canonical reserved SDK surface is: + +```pbs +import { Composer } from @sdk:composer; + +let status: int = Composer.emit_sprite(7, 0, 12, 18, 0, 2, false, false, 1); +``` + +Rules: + +- `@sdk:composer` is the canonical sprite-composition module in this wave. +- `@sdk:gfx` remains valid for primitive and overlay-oriented graphics operations. +- `Gfx.set_sprite` is not part of the active stdlib line `1` contract. + ## 6. Effective Stdlib of the Build The root project owns the effective stdlib line of the build. @@ -359,6 +373,26 @@ Rules: - It contributes compile-time symbols and metadata. - It does not produce executable bytecode by itself. +Another valid reserved stdlib interface module in stdlib line `1` is the sprite-composition surface: + +```pbs +declare host LowComposer { + [Host(module = "composer", name = "emit_sprite", version = 1)] + [Capability(name = "gfx")] + fn emit_sprite( + glyph_id: int, + palette_id: int, + x: int, + y: int, + layer: int, + bank_id: int, + flip_x: bool, + flip_y: bool, + priority: int + ) -> int; +} +``` + ## 10. Compiler Loading of the Stdlib Environment The compiler loads the stdlib environment from the stdlib line selected by the root project. @@ -433,7 +467,7 @@ Rules: - `declare host` remains reserved to SDK/toolchain-controlled modules. - Ordinary user-authored project modules do not declare canonical host bindings directly. - User code consumes SDK exports through normal imports. -- SDK-facing surfaces such as `Gfx.draw_pixel(...)` are resolved by the compiler against the selected stdlib environment. +- SDK-facing surfaces such as `Gfx.draw_pixel(...)` and `Composer.emit_sprite(...)` are resolved by the compiler against the selected stdlib environment. - Canonical host identity comes from reserved host-binding metadata such as `[Host(...)]`, not from the source spelling of the imported owner. ## 13. Project Publication and Distribution diff --git a/docs/specs/compiler-languages/pbs/6.2. Host ABI Binding and Loader Resolution Specification.md b/docs/specs/compiler-languages/pbs/6.2. Host ABI Binding and Loader Resolution Specification.md index 0a5c99e7..f23173bf 100644 --- a/docs/specs/compiler-languages/pbs/6.2. Host ABI Binding and Loader Resolution Specification.md +++ b/docs/specs/compiler-languages/pbs/6.2. Host ABI Binding and Loader Resolution Specification.md @@ -61,7 +61,7 @@ as the normative runtime-facing identity. The host-binding pipeline is: -1. User code imports SDK surfaces such as `Gfx` from `@sdk:gfx`. +1. User code imports SDK surfaces such as `Gfx` from `@sdk:gfx` and `Composer` from `@sdk:composer`. 2. The compiler resolves those surfaces against the selected stdlib line. 3. The compiler maps each host-backed SDK member to a canonical identity `(module, name, version)`. 4. The compiler emits those required canonical identities into the PBX `SYSC` table. @@ -136,6 +136,33 @@ but the PBX-facing declaration is canonical, for example: ("gfx", "draw_pixel", 1) ``` +The same rule applies to sprite composition through the composer domain. +For example, the PBS-facing declaration: + +```pbs +declare host LowComposer { + [Host(module = "composer", name = "emit_sprite", version = 1)] + [Capability(name = "gfx")] + fn emit_sprite( + glyph_id: int, + palette_id: int, + x: int, + y: int, + layer: int, + bank_id: int, + flip_x: bool, + flip_y: bool, + priority: int + ) -> int; +} +``` + +maps to the canonical runtime identity: + +```text +("composer", "emit_sprite", 1) +``` + The same rule applies to the low-level asset surface. For example, the PBS-facing declaration: @@ -396,7 +423,7 @@ Diagnostics should identify the failing canonical identity whenever available. Rules: -- the loader does not resolve source imports such as `@sdk:gfx`, +- the loader does not resolve source imports such as `@sdk:gfx` or `@sdk:composer`, - the loader only consumes canonical host-binding metadata emitted into the PBX, - `stdlib` affects the loader indirectly through what the compiler emitted, - the loader must not attempt to reconstruct SDK/module semantics from the PBX. diff --git a/docs/specs/compiler-languages/pbs/8. Stdlib Environment Packaging and Loading Specification.md b/docs/specs/compiler-languages/pbs/8. Stdlib Environment Packaging and Loading Specification.md index fa848dd4..af06c17b 100644 --- a/docs/specs/compiler-languages/pbs/8. Stdlib Environment Packaging and Loading Specification.md +++ b/docs/specs/compiler-languages/pbs/8. Stdlib Environment Packaging and Loading Specification.md @@ -56,6 +56,9 @@ prometeu-compiler/frontends/prometeu-frontend-pbs/ stdlib/ 1/ sdk/ + composer/ + main.pbs + mod.barrel gfx/ main.pbs mod.barrel @@ -75,7 +78,7 @@ Interpretation: - `stdlib/1` selects stdlib major line `1`, - `sdk` and `core` are reserved project spaces, -- `gfx`, `audio`, `math` are module paths within those reserved spaces, +- `composer`, `gfx`, `audio`, `math` are module paths within those reserved spaces, - `main.pbs` and `mod.barrel` form the interface module contents. ## 5. Logical Mapping @@ -85,6 +88,7 @@ The compiler must treat the physical layout as a logical stdlib environment. Required mapping: - `stdlib//sdk/gfx` -> `@sdk:gfx` +- `stdlib//sdk/composer` -> `@sdk:composer` - `stdlib//sdk/asset` -> `@sdk:asset` - `stdlib//sdk/audio` -> `@sdk:audio` - `stdlib//core/math` -> `@core:math` @@ -94,7 +98,7 @@ Rules: - physical storage is an implementation detail, - logical module identity is authoritative, -- callers of the resolver should work with logical addresses such as `@sdk:gfx`, not resource paths. +- callers of the resolver should work with logical addresses such as `@sdk:gfx` and `@sdk:composer`, not resource paths. ## 6. Module File Convention @@ -145,6 +149,7 @@ Responsibility: Minimum operations: - `resolve(@sdk:gfx)` +- `resolve(@sdk:composer)` - `resolve(@core:math/vector)` ### 7.3 `StdlibModuleSource` @@ -219,6 +224,26 @@ declare host LowAssets { } ``` +or: + +```pbs +declare host LowComposer { + [Host(module = "composer", name = "emit_sprite", version = 1)] + [Capability(name = "gfx")] + fn emit_sprite( + glyph_id: int, + palette_id: int, + x: int, + y: int, + layer: int, + bank_id: int, + flip_x: bool, + flip_y: bool, + priority: int + ) -> int; +} +``` + Rules: - the parser reads the attribute as part of the interface module source, @@ -226,6 +251,11 @@ Rules: - the compiler stores the extracted metadata in its interface graph, - the raw attribute surface is not treated as a runtime object, - later lowering stages may consume the extracted metadata to produce PBX host-binding declarations. +- stdlib line `1` MUST expose the sprite-composition interface module at `@sdk:composer`, +- that module MUST declare `LowComposer` and public service `Composer`, +- that module MUST use runtime module `composer` and capability `gfx`, +- that module MUST expose `emit_sprite(glyph_id: int, palette_id: int, x: int, y: int, layer: int, bank_id: int, flip_x: bool, flip_y: bool, priority: int) -> int`, +- stdlib line `1` MUST NOT expose `Gfx.set_sprite`, - stdlib line `1` MUST expose the low-level asset interface module at `@sdk:asset`, - that module MUST declare `LowAssets`, - that module MUST use runtime module `asset` and capability `asset`, diff --git a/docs/specs/compiler/18. Standard Library Surface Specification.md b/docs/specs/compiler/18. Standard Library Surface Specification.md index 677955ac..2358cd28 100644 --- a/docs/specs/compiler/18. Standard Library Surface Specification.md +++ b/docs/specs/compiler/18. Standard Library Surface Specification.md @@ -164,6 +164,20 @@ Rules: - PBS symbolic asset references (`Addressable`) are not themselves required to be stdlib-imported modules; - backend-owned frontend surface contracts MAY supply symbolic values that later lower into stdlib host-backed APIs. +### 12.1 Current line `1` sprite-composition rule + +For stdlib line `1`, sprite composition is owned by `@sdk:composer`, not by `@sdk:gfx`. + +Rules: + +- `@sdk:composer` MUST expose a low-level host owner `LowComposer`. +- `@sdk:composer` MUST expose a public service facade `Composer`. +- `Composer.emit_sprite(...)` MUST lower through canonical host identity `("composer", "emit_sprite", 1)`. +- `Composer.emit_sprite(...)` MUST return a raw `int` in this wave. +- `@sdk:gfx` MAY continue to expose primitive and overlay-oriented operations. +- `@sdk:gfx` MUST NOT expose `set_sprite`. +- `bind_scene`, `unbind_scene`, and `set_camera` are deferred and are not required members of `@sdk:composer` in this wave. + ## 13. Stdlib Contract Expectations for Builtin MVP For the current builtin MVP, stdlib should be able to expose at least: diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/composer/main.pbs b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/composer/main.pbs new file mode 100644 index 00000000..5fde843a --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/composer/main.pbs @@ -0,0 +1,33 @@ +declare host LowComposer { + [Host(module = "composer", name = "emit_sprite", version = 1)] + [Capability(name = "gfx")] + fn emit_sprite( + glyph_id: int, + palette_id: int, + x: int, + y: int, + layer: int, + bank_id: int, + flip_x: bool, + flip_y: bool, + priority: int + ) -> int; +} + +declare service Composer +{ + fn emit_sprite( + glyph_id: int, + palette_id: int, + x: int, + y: int, + layer: int, + bank_id: int, + flip_x: bool, + flip_y: bool, + priority: int + ) -> int + { + return LowComposer.emit_sprite(glyph_id, palette_id, x, y, layer, bank_id, flip_x, flip_y, priority); + } +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/composer/mod.barrel b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/composer/mod.barrel new file mode 100644 index 00000000..19c2fd17 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/composer/mod.barrel @@ -0,0 +1,2 @@ +mod host LowComposer; +pub service Composer; diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/gfx/main.pbs b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/gfx/main.pbs index 8fb80fea..c13a1b30 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/gfx/main.pbs +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/resources/stdlib/1/sdk/gfx/main.pbs @@ -25,21 +25,6 @@ declare host LowGfx { [Capability(name = "gfx")] fn draw_square(x: int, y: int, w: int, h: int, border_color: Color, fill_color: Color) -> void; - [Host(module = "gfx", name = "set_sprite", version = 1)] - [Capability(name = "gfx")] - fn set_sprite( - bank_id: int, - index: int, - x: int, - y: int, - tile_id: int, - palette_id: int, - active: bool, - flip_x: bool, - flip_y: bool, - priority: int - ) -> int; - [Host(module = "gfx", name = "draw_text", version = 1)] [Capability(name = "gfx")] fn draw_text(x: int, y: int, message: str, color: Color) -> void; @@ -81,22 +66,6 @@ declare service Gfx LowGfx.draw_square(x, y, w, h, border_color, fill_color); } - fn set_sprite( - bank_id: int, - index: int, - x: int, - y: int, - tile_id: int, - palette_id: int, - active: bool, - flip_x: bool, - flip_y: bool, - priority: int - ) -> int - { - return LowGfx.set_sprite(bank_id, index, x, y, tile_id, palette_id, active, flip_x, flip_y, priority); - } - fn draw_text(x: int, y: int, message: str, color: Color) -> void { LowGfx.draw_text(x, y, message, color); diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/stdlib/InterfaceModuleLoaderTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/stdlib/InterfaceModuleLoaderTest.java index 8e4afe1f..b856e477 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/stdlib/InterfaceModuleLoaderTest.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/stdlib/InterfaceModuleLoaderTest.java @@ -84,4 +84,71 @@ class InterfaceModuleLoaderTest { "pub host LowAssets;", fileTable.get(module.barrelFiles().getFirst().fileId()).readUtf8().orElseThrow()); } + + @Test + void shouldLoadComposerStdlibModuleWithPublicServiceBarrel() { + final var projectTable = new ProjectTable(); + final var projectId = projectTable.register(ProjectDescriptor.builder() + .name("app") + .version("1.0.0") + .rootPath(Path.of("/tmp/app")) + .sourceRoots(ReadOnlyList.wrap(java.util.List.of(Path.of("/tmp/app/src")))) + .build()); + final var fileTable = new FileTable(1); + final var diagnostics = DiagnosticSink.empty(); + final var moduleSource = new StdlibModuleSource( + "sdk", + ReadOnlyList.wrap(java.util.List.of("composer")), + ReadOnlyList.wrap(java.util.List.of(new StdlibModuleSource.SourceFile( + "main.pbs", + """ + declare host LowComposer { + [Host(module = "composer", name = "emit_sprite", version = 1)] + [Capability(name = "gfx")] + fn emit_sprite( + glyph_id: int, + palette_id: int, + x: int, + y: int, + layer: int, + bank_id: int, + flip_x: bool, + flip_y: bool, + priority: int + ) -> int; + } + + declare service Composer { + fn emit_sprite( + glyph_id: int, + palette_id: int, + x: int, + y: int, + layer: int, + bank_id: int, + flip_x: bool, + flip_y: bool, + priority: int + ) -> int { + return LowComposer.emit_sprite(glyph_id, palette_id, x, y, layer, bank_id, flip_x, flip_y, priority); + } + } + """))), + """ + mod host LowComposer; + pub service Composer; + """); + + final var module = new InterfaceModuleLoader().load(moduleSource, projectId, fileTable, diagnostics); + + assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString()); + assertEquals(1, module.sourceFiles().size()); + assertEquals(1, module.barrelFiles().size()); + assertEquals( + """ + mod host LowComposer; + pub service Composer; + """.strip(), + fileTable.get(module.barrelFiles().getFirst().fileId()).readUtf8().orElseThrow().strip()); + } }