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

@ -0,0 +1,56 @@
# boot.ludic - the window, the renderer on a plate (no terrain, no grass, no water), the plate and
# its sky, then the scene named by R3D_SCENE (else the first one added)
function lab_draw() -> void {
actor_draw()
scatter_draw()
if lb_draw != null { lb_draw() }
}
function lab_casters(light_vp: floats) -> void {
actor_draw_casters(light_vp)
scatter_draw_casters(light_vp)
}
function lab_pick_scene() -> int {
if LAB_COUNT == 0 { return -1 }
lb_scene = LabScenes[0].key
if Os.has_env("R3D_SCENE") { lb_scene = Os.env("R3D_SCENE") }
return labscenes_find(lb_scene)
}
# once, from the program's Start handler
export function lab_boot() -> void {
lb_windowed = is_windowed()
let k = lab_pick_scene()
if k < 0 {
print(`lab: no scene called '{lb_scene}' - {lab_scene_names()}`)
quit()
return
}
var ww = 1920
var wh = 1080
if lb_windowed {
ww = 960
wh = 540
}
r3d_plate_mode(true)
r3d_sky_path = `{lab_plate_dir()}/sky.hdr`
if not r3d_init(ww, wh, lb_title) {
quit()
return
}
if lb_font != "" { overlay_init(lb_font) }
r3d_on_draw(fn lab_draw)
r3d_on_casters(fn lab_casters)
lab_plate_load()
lab_shots_reset()
lb_out = `build/lab/{lb_scene}`
let sc = LabScenes[k]
lb_draw = sc.draw
if sc.setup != null { sc.setup() }
if lab_shot_count() == 0 { lab_shot_at("plate", 0.0, 1.0, 0.0, 8.0, 0.0, 15.0) }
lab_shot_apply(0)
if not lb_windowed {
Fs.mkdir("build")
Fs.mkdir("build/lab")
Fs.mkdir(lb_out)
}
print(`lab: scene {lb_scene}, {lab_shot_count()} shots`)
}