feat(ecs): Light2D/Occluder/Ambient as auto-consumed components (#47)
The ECS-native shape the 2D lighting follow-up asked for, built on the engine-owned-system hook from #43. A torch is just an entity carrying Light2D, a wall an entity carrying Occluder, and one Ambient entity sets the night tint — the engine runs the whole deterministic light pass at the end of the Render phase (ambient modulate -> carve occluder shadows -> accumulate additive radial lights) and presents. No Light.* calls wired. - runtime/native/systems_light.ludic: esys_light2d, consuming the components through the by-name reflection ABI and reusing the #4 accumulation core (light_ambient / light_occlude / light_point). Reads position from a Position component when present, else the light's own x/y. - Split from systems.ludic so a SpriteAnim/Motion-only game never links the light pass; spliced (with light.ludic) only when Light2D/Occluder declared. - emit_main now boots rt_init like the auto-loop/test runner, so an entry-driven game that renders has its framebuffer allocated (headless: allocate only, no window, byte-identical stdout for non-rendering games). Worked example + regression: examples/library/light_ecs.ludic (32 1 32 1). Full suite 74 passed, self-host C-free fixpoint intact, no golden drift. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b0143337d9
commit
3679ce1797
8 changed files with 14660 additions and 14404 deletions
13
LANGUAGE.md
13
LANGUAGE.md
|
|
@ -270,6 +270,9 @@ enough, and a game that declares none is byte-for-byte unchanged.
|
|||
|---|---|---|
|
||||
| `SpriteAnim { ticks, fps, frames, mode, frame }` | `Update` | advances `frame` — spritesheet frame animation (`mode` 0 loop, 1 once, 2 ping-pong) |
|
||||
| `Motion { ticks, dur, from, to, ease, value, done }` | `Update` | advances `value` — value tween (`ease` 0 linear, 1 in, 2 out, 3 in-out), latches `done` |
|
||||
| `Light2D { x, y, radius, color, intensity }` | `Render` | additive radial glow; the engine runs the whole 2D light pass and presents |
|
||||
| `Occluder { x, y, w, h }` | `Render` | a rectangular shadow caster the light pass carves out |
|
||||
| `Ambient { color }` | `Render` | one entity tints the whole scene (night/cave) before lights accumulate |
|
||||
|
||||
```ludic
|
||||
# doc-check: skip — illustrative engine-owned system
|
||||
|
|
@ -279,9 +282,15 @@ model Hero { Pos, SpriteAnim }
|
|||
spawn Hero { Pos { x: 0, y: 0 } SpriteAnim { fps: 10, frames: 6, mode: 0 } }
|
||||
```
|
||||
|
||||
A `Light2D` / `Occluder` reads its position from a `Position { x, y }` component
|
||||
on the same entity when the entity carries one, else from its own `x` / `y`
|
||||
fields — so "Position + Light2D" and a self-positioned light both work. With
|
||||
`Light2D` present the engine owns the frame flip: a draw handler renders the
|
||||
scene and does **not** call `Screen.show`.
|
||||
|
||||
Everything is integer and deterministic (the frame clock ticks at a fixed 60/s),
|
||||
so animation and motion reproduce exactly under replay and lockstep netcode. See
|
||||
`examples/library/anim_ecs.ludic`.
|
||||
so animation, motion and lighting reproduce exactly under replay and lockstep
|
||||
netcode. See `examples/library/anim_ecs.ludic` and `examples/library/light_ecs.ludic`.
|
||||
|
||||
## Annotations
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue