implements PLN-0053

This commit is contained in:
bQUARKz 2026-04-18 17:01:31 +01:00
parent ef582b6687
commit 46728a542f
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
8 changed files with 213 additions and 37 deletions

View File

@ -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: This document is the authority for how the compiler discovers and loads:
- ordinary project modules, - 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, - compile-time reserved metadata attached to those reserved modules,
- interface-only service facades that wrap host surfaces for source ergonomics, - interface-only service facades that wrap host surfaces for source ergonomics,
- and backend-provided compile surfaces consumed by the frontend for symbolic authoring. - and backend-provided compile surfaces consumed by the frontend for symbolic authoring.
@ -211,6 +211,20 @@ Rules:
- `Gfx` becomes visible only through `import`. - `Gfx` becomes visible only through `import`.
- The compiler accepts that import because the selected stdlib environment exposes `@sdk:gfx`. - 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 ## 6. Effective Stdlib of the Build
The root project owns the effective stdlib line 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 contributes compile-time symbols and metadata.
- It does not produce executable bytecode by itself. - 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 ## 10. Compiler Loading of the Stdlib Environment
The compiler loads the stdlib environment from the stdlib line selected by the root project. 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. - `declare host` remains reserved to SDK/toolchain-controlled modules.
- Ordinary user-authored project modules do not declare canonical host bindings directly. - Ordinary user-authored project modules do not declare canonical host bindings directly.
- User code consumes SDK exports through normal imports. - 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. - 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 ## 13. Project Publication and Distribution

View File

@ -61,7 +61,7 @@ as the normative runtime-facing identity.
The host-binding pipeline is: 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. 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)`. 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. 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) ("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. The same rule applies to the low-level asset surface.
For example, the PBS-facing declaration: For example, the PBS-facing declaration:
@ -396,7 +423,7 @@ Diagnostics should identify the failing canonical identity whenever available.
Rules: 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, - the loader only consumes canonical host-binding metadata emitted into the PBX,
- `stdlib` affects the loader indirectly through what the compiler emitted, - `stdlib` affects the loader indirectly through what the compiler emitted,
- the loader must not attempt to reconstruct SDK/module semantics from the PBX. - the loader must not attempt to reconstruct SDK/module semantics from the PBX.

View File

@ -56,6 +56,9 @@ prometeu-compiler/frontends/prometeu-frontend-pbs/
stdlib/ stdlib/
1/ 1/
sdk/ sdk/
composer/
main.pbs
mod.barrel
gfx/ gfx/
main.pbs main.pbs
mod.barrel mod.barrel
@ -75,7 +78,7 @@ Interpretation:
- `stdlib/1` selects stdlib major line `1`, - `stdlib/1` selects stdlib major line `1`,
- `sdk` and `core` are reserved project spaces, - `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. - `main.pbs` and `mod.barrel` form the interface module contents.
## 5. Logical Mapping ## 5. Logical Mapping
@ -85,6 +88,7 @@ The compiler must treat the physical layout as a logical stdlib environment.
Required mapping: Required mapping:
- `stdlib/<N>/sdk/gfx` -> `@sdk:gfx` - `stdlib/<N>/sdk/gfx` -> `@sdk:gfx`
- `stdlib/<N>/sdk/composer` -> `@sdk:composer`
- `stdlib/<N>/sdk/asset` -> `@sdk:asset` - `stdlib/<N>/sdk/asset` -> `@sdk:asset`
- `stdlib/<N>/sdk/audio` -> `@sdk:audio` - `stdlib/<N>/sdk/audio` -> `@sdk:audio`
- `stdlib/<N>/core/math` -> `@core:math` - `stdlib/<N>/core/math` -> `@core:math`
@ -94,7 +98,7 @@ Rules:
- physical storage is an implementation detail, - physical storage is an implementation detail,
- logical module identity is authoritative, - 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 ## 6. Module File Convention
@ -145,6 +149,7 @@ Responsibility:
Minimum operations: Minimum operations:
- `resolve(@sdk:gfx)` - `resolve(@sdk:gfx)`
- `resolve(@sdk:composer)`
- `resolve(@core:math/vector)` - `resolve(@core:math/vector)`
### 7.3 `StdlibModuleSource` ### 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: Rules:
- the parser reads the attribute as part of the interface module source, - 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 compiler stores the extracted metadata in its interface graph,
- the raw attribute surface is not treated as a runtime object, - 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. - 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`, - stdlib line `1` MUST expose the low-level asset interface module at `@sdk:asset`,
- that module MUST declare `LowAssets`, - that module MUST declare `LowAssets`,
- that module MUST use runtime module `asset` and capability `asset`, - that module MUST use runtime module `asset` and capability `asset`,

View File

@ -164,6 +164,20 @@ Rules:
- PBS symbolic asset references (`Addressable`) are not themselves required to be stdlib-imported modules; - 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. - 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 ## 13. Stdlib Contract Expectations for Builtin MVP
For the current builtin MVP, stdlib should be able to expose at least: For the current builtin MVP, stdlib should be able to expose at least:

View File

@ -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);
}
}

View File

@ -0,0 +1,2 @@
mod host LowComposer;
pub service Composer;

View File

@ -25,21 +25,6 @@ declare host LowGfx {
[Capability(name = "gfx")] [Capability(name = "gfx")]
fn draw_square(x: int, y: int, w: int, h: int, border_color: Color, fill_color: Color) -> void; 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)] [Host(module = "gfx", name = "draw_text", version = 1)]
[Capability(name = "gfx")] [Capability(name = "gfx")]
fn draw_text(x: int, y: int, message: str, color: Color) -> void; 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); 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 fn draw_text(x: int, y: int, message: str, color: Color) -> void
{ {
LowGfx.draw_text(x, y, message, color); LowGfx.draw_text(x, y, message, color);

View File

@ -84,4 +84,71 @@ class InterfaceModuleLoaderTest {
"pub host LowAssets;", "pub host LowAssets;",
fileTable.get(module.barrelFiles().getFirst().fileId()).readUtf8().orElseThrow()); 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());
}
} }