feat(lang): functions are values (L2), and render3d takes its scene as callbacks

fn(int, float) -> bool is a type, fn name is any top-level function's value, and a call through
a local, a global, a record field, a slice element, a parameter or a result of a function type
is an indirect call; two function types mix only when equal, a call checks its argument count,
and a value may be null (examples/functions/values.ludic). Job.parallel_for keeps its worker
check.

render3d's scene is registered rather than required by name: r3d_on_draw, r3d_on_casters and
r3d_on_stream_fill (hooks.ludic). The two rendering examples register theirs - and had defined
scene_draw_casters with no parameter while the renderer passed one, which nothing checked.
render3d declares numbers float itself; smooth.ludic is converted to floats and returns when
r3d_init fails instead of running on into a segfault. Noise.* check their argument count (a call
one short crashed the compiler). selfhost-build says why it failed. The migration tool reads a
declared float as evidence. Seed regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-24 00:27:26 +03:00
parent dc75a5a5ab
commit 0c73287e35
23 changed files with 71430 additions and 68575 deletions

View file

@ -0,0 +1,17 @@
# hooks.ludic - the scene a frame draws, as callbacks the program registers. They used to be
# functions every program had to define by name (scene_draw, scene_draw_casters, stream_fill),
# a contract only the linker enforced and that allowed one scene per program.
var r3d_draw_cb: fn() = null
var r3d_casters_cb: fn(floats) = null
var r3d_fill_cb: fn(Stream, int, int, int) = null
# what a frame draws, for every pass that draws the scene (the view, the reflection)
function r3d_on_draw(f: fn()) -> void { r3d_draw_cb = f }
# what casts a shadow, drawn into the light's view `light_vp`
function r3d_on_casters(f: fn(floats)) -> void { r3d_casters_cb = f }
# a streamed layer's chunk (cx, cz) at detail `band`, filled with its instances
function r3d_on_stream_fill(f: fn(Stream, int, int, int)) -> void { r3d_fill_cb = f }
function r3d_scene_draw() -> void { if r3d_draw_cb != null { r3d_draw_cb() } }
function r3d_scene_casters(light_vp: floats) -> void { if r3d_casters_cb != null { r3d_casters_cb(light_vp) } }
function r3d_stream_fill(s: Stream, cx: int, cz: int, band: int) -> void { if r3d_fill_cb != null { r3d_fill_cb(s, cx, cz, band) } }

View file

@ -1,8 +1,11 @@
# ============================================================================
# ludic.render3d — a physically based 3D renderer on Gl.* (OpenGL 4.1 core).
# Import this one file; the game supplies scene_draw() / scene_draw_casters().
# Import this one file, and register what a frame draws: r3d_on_draw, r3d_on_casters and, for a
# streamed layer, r3d_on_stream_fill (hooks.ludic).
# ============================================================================
numbers float # the renderer's numbers are floats whoever imports it
import "env.ludic"
import "hooks.ludic"
import "fmath.ludic"
import "gpu.ludic"
import "gpu_manifest.ludic"

View file

@ -257,7 +257,7 @@ function r3d_frame(time: float) -> void {
prof_end()
prof_cpu_mark("terrain")
prof_begin("scene (vegetation)")
scene_draw()
r3d_scene_draw()
prof_end()
sc_prepass = false
prof_cpu_mark("vegetation")

View file

@ -165,7 +165,7 @@ function shadow_pass() -> void {
# shadows off (a video setting): the cascades stay cleared, so everything reads lit
if sh_enabled {
if not sh_skip_terrain { terrain_draw_shadow(vp) }
scene_draw_casters(vp)
r3d_scene_casters(vp)
}
}
gpu_depth_bias(0.0, 0.0)

View file

@ -233,7 +233,7 @@ function stream_update(s: Stream, cam_x: float, cam_z: float) -> void {
c.key = key
s.cur = c
let t0 = gl_now_us()
stream_fill(s, cx, cz, band)
r3d_stream_fill(s, cx, cz, band)
let dt = gl_now_us() - t0
stream_us_gen = stream_us_gen + dt
prof_chunk(s.kind, band, c.count, dt)

View file

@ -86,7 +86,7 @@ function water_reflection_pass() -> void {
prof_cpu_mark("reflection setup")
terrain_draw()
prof_cpu_mark("reflection terrain")
scene_draw()
r3d_scene_draw()
prof_cpu_mark("reflection scene")
r3d_draw_sky()
prof_cpu_mark("reflection sky")