feat(rendering): add Light.* — deterministic 2D light accumulation with hard shadows (#4)
A software light pass over the framebuffer, run in a render phase after drawing the scene: Light.ambient multiplies the scene toward a tint (night/cave mood), Light.point additively accumulates a radial glow with linear falloff clamped per channel, and Light.occlude / Light.clear_occluders cast hard shadows by blocking a light's rays against rectangular occluders. Integer + Q16.16 fixed throughout, so a scene lights identically every run and in a headless render (diffable). Engine in runtime/native/light.ludic, spliced on demand (g_uses_light) like the regex/query runtimes; namespace wired in emit_call.ludic. Ships issue #4 tiers 1 (ambient + additive radial lights) and 2 (hard shadows). Normal-mapped sprites, soft shadows, a day/night directional light, and auto-consuming Light2D/Occluder components are follow-ups (the auto-system hook is tracked by #43). - runtime/native/light.ludic: the light-accumulation engine (isqrt falloff, segment/occluder shadow test, ambient modulate) - examples/library/lighting.ludic: 14 pixel-readback assertions - docs/language/light/: Light.ambient/point/occlude/clear_occluders - tools/x/test.ludic: lighting.ludic in the regression suite Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
31cfbc2465
commit
a71279a7b9
12 changed files with 15747 additions and 14968 deletions
30
docs/language/light/light-clear_occluders.md
Normal file
30
docs/language/light/light-clear_occluders.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
id: light-clear_occluders
|
||||
name: Light.clear_occluders
|
||||
category: light
|
||||
kind: namespace-method
|
||||
tokens: Light.clear_occluders
|
||||
sig: Light.clear_occluders()
|
||||
tip: Forget every occluder — call once per frame before re-registering.
|
||||
order: 4
|
||||
ns: Light
|
||||
member: clear_occluders
|
||||
---
|
||||
|
||||
Forgets every rectangle previously passed to <a href="light-occlude"><code>Light.occlude</code></a>. Occluders accumulate across calls and persist between frames, so a game that casts shadows calls this once at the top of its render phase and then re-registers the walls that should block light this frame — the same clear-then-rebuild rhythm as an immediate-mode draw list. With no occluders registered, <a href="light-point"><code>Light.point</code></a> lights its whole radius unobstructed.
|
||||
|
||||
Parameters: none.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
property Tag { v: int = 0 }
|
||||
model Marker { Tag }
|
||||
handler Render phase Update {
|
||||
Screen.clear(0)
|
||||
Light.clear_occluders() # start the frame with no shadows
|
||||
Light.occlude(90, 60, 6, 40)
|
||||
Light.point(40, 80, 100, Color.rgb(255, 255, 255), 1.0)
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue