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>