feat(ludic.lab): the visual lab as a package - a scene on a plate, never a world

A scene is an entry in the open registry LabScenes, shown on a plate: a
flat lit disc, the package's own small sky with the sun at one hour, and
a frame clock. Headless each camera (lab_shot, lab_shot_at) settles and
is written as build/lab/<scene>/<shot>.png (a stored PNG, written here);
in a window N and P step through them. tools/lab/run.sh and sheet.py
make the contact sheet; tools/lab/plate_build.py makes plate/.
ludic.render3d gains r3d_plate_mode (no terrain, grass or water is made)
and r3d_sky_path. quit() in a program with no frame loop links. The
example takes ~120 MB resident, 417 MiB peak footprint. Reseed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 07:48:43 +03:00
parent cc930a114f
commit b401f7acc1
35 changed files with 4184 additions and 2986 deletions

View file

@ -582,7 +582,7 @@ function emit_call(e: Node) -> Val {
emit(" call void @L_world_load(ptr "); emit(b.code); emit(", i32 "); emit(l.code); emit(")\n")
return val("0", "void")
}
if (name == "quit") { emit(" store i32 0, ptr @L_running\n"); return val("0", "void") }
if (name == "quit") { g_uses_quit = true; emit(" store i32 0, ptr @L_running\n"); return val("0", "void") }
# NETWORKING (NETWORKING-DESIGN §5) — the low-level freedom layer, callable from
# Ludic. serialize/apply/sync_size lower to the @Sync by-kind dispatchers (N2);
# owner/set_owner/is_owner to the @Owned storage (N3); is_server/local_id read

View file

@ -46,6 +46,7 @@ var g_fprintf_declared: bool = false # @fprintf is declared once, by whoever ne
var g_bounds: bool = true # check every slice index (--no-bounds turns it off)
var g_uses_result: bool = false # ok()/err()/try was emitted -> define the %Result value type (issue #46)
var g_uses_option: bool = false # some()/none() was emitted -> define the %Option value type (issue #53)
var g_uses_quit: bool = false # quit() was lowered: a program with no frame loop still needs @L_running
var g_tests: []Node # test "name" { ... } blocks collected by the parser
var g_src_name: pointer = "?" # base name of the source file, for panic/expect file:line messages
var g_uses_longstr: bool = false # string(long) / interpolating a long was emitted -> emit fn_long_str

View file

@ -273,6 +273,7 @@ function emit_program() -> void {
g_fprintf_declared = false
g_uses_result = false
g_uses_option = false
g_uses_quit = false
g_uses_world_despawn = false # #84: set when a world_despawn call is emitted (below)
g_cov_lines = new []int
g_cov_active = true
@ -312,6 +313,8 @@ function emit_program() -> void {
i = 0
while i < len(prog) { if prog[i].kind == N_MAIN { emit_main(prog[i]) }; i += 1 }
} } }
# quit() in a program with no frame loop (a package's code in a test program): the flag it sets
if g_uses_quit and not has_ecs() { emith("@L_running = internal global i32 1\n") }
if g_uses_world_despawn { emit_world_despawn_fn() } # #84: @fn_world_despawn, after all World.despawn / esys_bounds-kill uses are seen
if g_uses_loopback { emit_loopback() } # built-in transport, after all net_send/net_poll uses are seen
if g_uses_str { emit_str_prelude() } # @lp_str_eq / @lp_str_concat, after all uses are seen

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff