Voxel lighting + day/night cycle (sun, moon, two-channel shading)#1
Conversation
Implements a full voxel-aware lighting model independent of Babylon's real-time lights, plus a directional shadow layer on top. Voxel light engine (src/game/lighting/): - Two per-voxel channels (sun + block light), 0..15, stored per chunk in typed arrays (LightMap), modelled on Luanti's param1 day/night nibbles. - SunLightPropagator: top-down sky exposure + BFS flood fill. Sunlight streams straight down through air/sunlight-passing blocks with no decay and attenuates sideways/up; water and leaves break the column so depth and canopies dim naturally. Cross-chunk boundary inflow keeps chunk seams correct. - BlockLightPropagator: emitter BFS that decays 1/step through light-passing cells (opaque blocks are walls). Glowstone (id 28) emits 15 to demonstrate it in caves. - VoxelLightEngine owns the light maps, implements read-only boundary access, and recomputes a chunk from scratch (with a border-change diff that queues neighbours). Reusable scratch buffers = zero alloc. Block registry: - New BlockLightDefinition (lightPassesThrough, sunlightPassesThrough, lightEmission, lightAbsorption, casts/receivesShadows) with a cached resolveLight() so behaviour is data-driven, never hardcoded. - Leaves/water/jungle-leaves carry light; glowstone emits; new glowstone tile 33 + hotbar slot. Mesh integration: - ChunkMesher takes a BrightnessSampler callback and bakes face-shade * lightToBrightness(combined) into vertex colours per face (light read from the neighbour cell the face looks into). Caves go dark, outdoors stay bright, faces get directional shading. - Sun baked at full day; day/night dimming is global (no per-frame remesh). Day/night + shadows: - DayNightCycle drives the Babylon sun/ambient/hemi intensities and sky/fog colours from a time-of-day value (pause/scrub/snap controls). - ShadowManager wraps a directional ShadowGenerator limited to nearby opaque chunk meshes. Fixed frustum centred on the player with an explicit depth range, frustumEdgeFalloff fade, and a caster margin so the frustum boundary never shows as a hard rectangle. Performance: lighting only runs on change via a budgeted dirty-queue (3 chunks/frame); neighbours re-light only when a border value changes and converge in a few frames. Debug: overlay panel (L), raw sun/block/combined visual modes (K), shadow render-list + frustum diagnostics via __voxl.lighting(), and an H toggle to disable Babylon shadows while keeping voxel light.
Game clock + celestial visuals on top of the voxel light engine: - DayNightCycle: timeOfDay (0=midnight, 0.25=sunrise, 0.5=noon, 0.75= sunset), configurable day length + timeScale, pause/resume, and preset helpers (setSunrise/setNoon/setSunset/setMidnight). Sun orbits east→west with a slight tilt; moon is the exact antipode. dayFactor/moonFactor and a multi-stop sky-colour gradient (night→dusk purple/orange→day blue) are recomputed each frame and pushed to lights + sky/fog. - CelestialSystem: camera-anchored sun disc + additive halo + cratered moon billboards that fade in/out at the horizon, warm at golden hour. Stay in rendering group 0 (with terrain) so blocks occlude them; never cast/receive shadows; ignore fog. - VoxelTerrainMaterial: custom two-channel ShaderMaterial. The mesher bakes sun and block light into vertex-colour .r/.g (raw levels into .b/.a for the debug overlay); the shader combines max(sun*dayFactor, sun*moonFloor, block). Day/night therefore dims outdoor terrain at night WITHOUT touching torch/glowstone (block) light and WITHOUT rebuilding any chunk mesh — only the uDayFactor/uMoonFloor uniforms update each frame. Linear fog is done manually. Fixes the prior shader compile error (uTexture now sampler-only; sources passed inline) and the sun-through-blocks bug (depth group). - Shadows: ShadowManager retained but disabled by default (terrain uses voxel sunlight), permanently retiring the rectangular-frustum artifact. - Debug: L overlay, K cycle sun/block/combined view (now a uniform, no remesh), T pause, H toggle Babylon shadows, [ / ] time presets, O / I speed, ; / ' midnight/noon. Overlay shows time, day/moon factors, sun direction/visibility, intensities.
📋 SummaryNo linked issues are referenced in the PR ( PR #1 adds a Minetest/Luanti-style two-channel voxel light engine (sun + block light), a day/night cycle with celestial billboards, a custom terrain shader for cheap time-of-day dimming, and debug tooling. The game package typechecks and builds cleanly, and the website package builds successfully. However, the website 📌 Review Metadata
🔴 Critical Issues (Must Fix - Blocks Merge)None identified.
|
| Principle | Score | Notes |
|---|---|---|
| Single Responsibility | 7 | LightingSystem is a clean facade, but DayNightCycle mixes clock, sky colors, and light intensities. |
| Open/Closed | 8 | New blocks append IDs; lighting behavior is data-driven via BlockLightDefinition. |
| Liskov Substitution | 8 | No inheritance hierarchy to violate. |
| Interface Segregation | 8 | LightAccess, BrightnessSampler, and ShadowConfig are small and focused. |
| Dependency Inversion | 8 | Propagators depend on LightAccess, not World; engine uses a block-lookup callback. |
| Average | 7.8 |
🎯 Final Assessment
Overall Confidence Score: 72%
Confidence Breakdown:
- Code Quality: 85%
- Completeness: 70%
- Risk Level: 75%
- Test Coverage: 30%
Merge Readiness:
- All critical issues resolved
- SOLID average score >= 6.0
- Overall confidence >= 60%
- No security concerns
- Tests present and passing (if applicable)
Verdict:
MERGE WITH FIXES
Game logic is well-structured and the build/typecheck pass, but fix the world-top skylight seed, commit the promised propagation tests, and tidy the encapsulation/dead-code nits before merging.
- [HIGH] SunLightPropagator Phase A: seed the topmost light-passing-but-not- sun-passing cell (water/leaves at the world ceiling) with one step of decay before closing the sky column, so it is no longer pitch-black and skylight bleeds into it. Also drop the misleading top-edge inflow comment + dead sunPass variable in pullInflow. - [MEDIUM] LightingSystem no longer peeks World.lightDirty via an as-unknown cast; World exposes a public lightDirtyCount getter. - [MEDIUM] Commit the propagation tests as scripts/lighttest.ts (+ bun run test:light) so the verification is reproducible. Adds a world-top water boundary case alongside open shaft / enclosed pocket / glowstone decay / cross-chunk bleed / canopy / water depth. - [MEDIUM] VoxelLightEngine.relightChunk now breaks the diff scan once both changed+borderChanged are true instead of scanning the rest of the chunk. - [MEDIUM] CelestialSystem sets renderingGroupId once in makeDisc (was set every frame in update) — the value is constant. - [LOW] Remove unused LightMap.markValid()/dirty; add VoxelLightEngine.dispose and call it from World.dispose. - [LOW] DayNightCycle.apply reuses scratch Color3/Color4/Vector3 instead of allocating per frame. Pre-existing website screenshot.ts/playwright typecheck left untouched (out of scope; playwright is an optional dep per AGENTS.md).
📋 SummaryNo linked issues are referenced in the PR body. PR #1 adds a Minetest/Luanti-style two-channel voxel light engine (sun + block light), a day/night cycle with camera-anchored sun/moon billboards, a custom two-channel terrain shader for time-of-day dimming without remeshing, and debug tooling. The author has addressed almost every issue raised in the previous automated review: world-top skylight seeding is fixed, 📌 Review Metadata
🔴 Critical Issues (Must Fix - Blocks Merge)None identified. ✅ All previously reported critical issues have been resolved.
|
| Previous Issue | Status |
|---|---|
SunLightPropagator world-top sunlight lost for non-sun-passing light-passing blocks |
✅ [FIXED] Seeded at LIGHT_MAX - 1 - absorption in Phase A; world-top water test passes. |
LightingSystem peeked World.lightDirty via as unknown |
✅ [FIXED] World now exposes public lightDirtyCount getter. |
| No propagation tests committed | ✅ [FIXED] scripts/lighttest.ts added with bun run test:light; all pass. |
VoxelLightEngine wasted chunk scan with empty early-exit |
✅ [FIXED] Now breaks the y-loop once both flags are set. |
SunLightPropagator unused sunPass / misleading comment |
✅ [FIXED] sunPass is used in spread; comment clarified. |
CelestialSystem renderingGroupId set every frame |
✅ [FIXED] Set once in makeDisc. |
LightMap.markValid() unused / dirty never reset |
✅ [FIXED] Removed. |
VoxelLightEngine no explicit disposal |
✅ [FIXED] dispose() added and called from World.dispose(). |
DayNightCycle per-frame Color3/Color4 allocations |
✅ [FIXED] Scratch instances reused. |
📊 SOLID Principles Score
| Principle | Score | Notes |
|---|---|---|
| Single Responsibility | 8 | LightingSystem is a clean facade; DayNightCycle still mixes clock, colors, and light intensities but is better isolated. |
| Open/Closed | 8 | New blocks append IDs; lighting behavior is data-driven via BlockLightDefinition. |
| Liskov Substitution | 9 | No inheritance hierarchy to violate. |
| Interface Segregation | 8 | LightAccess, BrightnessSampler, and ShadowConfig are small and focused. |
| Dependency Inversion | 8 | Propagators depend on LightAccess, not World; engine uses a block-lookup callback. |
| Average | 8.2 |
🎯 Final Assessment
Overall Confidence Score: 82%
Confidence Breakdown:
- Code Quality: 88% (well-structured, previous review nits mostly addressed)
- Completeness: 85% (feature-complete per PR scope, debug overlay + tests present)
- Risk Level: 75% (large new subsystem touching meshing, shader, and world streaming)
- Test Coverage: 65% (standalone propagation tests exist; no integration/visual tests)
Merge Readiness:
- All critical issues resolved
- SOLID average score >= 6.0
- Overall confidence >= 60%
- No security concerns
- Tests present and passing
Verdict:
MERGE WITH FIXES
The lighting system is in good shape and the previous review feedback has been thoroughly addressed. Before merging, fix the DayNightCycle private-field access in LightingSystem and update the PR description/test count claim; the website typecheck failure is pre-existing and can be handled separately.
- [MEDIUM] DayNightCycle exposes public sunIntensity/ambientIntensity getters;
LightingSystem.buildDebugInfo uses them instead of bracket-accessing the
private light fields (same brittle-coupling pattern as the prior lightDirty fix).
- [MEDIUM] Exclude website/scripts from astro check — screenshot.ts is optional
tooling whose dynamic import("playwright") breaks typecheck when the optional
dep is absent. Restores `bun run typecheck` (astro check) in website/.
- [LOW] Sky.setDomeColours reuses two scratch Vector3 instead of allocating per frame.
- [LOW] ShadowManager.sameSet compares with an allocation-free O(n²) membership
check instead of building a Set every frame.
📋 SummaryNo linked issues are referenced in the PR body ( PR #1 adds a Minetest/Luanti-style two-channel voxel light engine (sun + block light), a day/night cycle with camera-anchored sun/moon billboards, a custom two-channel terrain shader for time-of-day dimming without remeshing, and debug tooling. The author has addressed every substantive issue raised in the previous automated reviews: world-top skylight seeding is fixed, encapsulation violations are resolved via public getters, propagation tests are committed and passing, per-frame allocations are removed, dead code is cleaned up, and the website 📌 Review Metadata
🔴 Critical Issues (Must Fix - Blocks Merge)None identified. ✅ All previously reported critical issues have been resolved.
|
| Previous Issue | Status |
|---|---|
SunLightPropagator world-top sunlight lost for non-sun-passing light-passing blocks |
✅ [FIXED] Seeded at LIGHT_MAX - 1 - absorption in Phase A; world-top water test passes. |
LightingSystem peeked World.lightDirty via as unknown |
✅ [FIXED] World now exposes public lightDirtyCount getter. |
LightingSystem bypassed DayNightCycle private light fields |
✅ [FIXED] DayNightCycle now exposes public sunIntensity/ambientIntensity getters. |
Website typecheck failed because playwright was not installed |
✅ [FIXED] website/tsconfig.json now excludes scripts/. |
| No propagation tests committed | ✅ [FIXED] scripts/lighttest.ts added with bun run test:light; all pass. |
VoxelLightEngine wasted chunk scan with empty early-exit |
✅ [FIXED] Now breaks the y-loop once both flags are set. |
SunLightPropagator unused sunPass / misleading comment |
✅ [FIXED] sunPass is used in spread; comment clarified. |
CelestialSystem renderingGroupId set every frame |
✅ [FIXED] Set once in makeDisc. |
LightMap.markValid() unused / dirty never reset |
✅ [FIXED] Removed. |
VoxelLightEngine no explicit disposal |
✅ [FIXED] dispose() added and called from World.dispose(). |
DayNightCycle per-frame Color3/Color4 allocations |
✅ [FIXED] Scratch instances reused. |
Sky.setDomeColours allocated Vector3 every frame |
✅ [FIXED] Reuses two scratch Vector3s. |
ShadowManager.sameSet allocated a Set every frame |
✅ [FIXED] Allocation-free O(n²) membership check. |
📊 SOLID Principles Score
| Principle | Score | Notes |
|---|---|---|
| Single Responsibility | 8 | LightingSystem is a clean facade; DayNightCycle still mixes clock, colors, and light intensities but is better isolated. |
| Open/Closed | 8 | New blocks append IDs; lighting behavior is data-driven via BlockLightDefinition. |
| Liskov Substitution | 9 | No inheritance hierarchy to violate. |
| Interface Segregation | 8 | LightAccess, BrightnessSampler, and ShadowConfig are small and focused. |
| Dependency Inversion | 8 | Propagators depend on LightAccess, not World; engine uses a block-lookup callback. |
| Average | 8.2 |
🎯 Final Assessment
Overall Confidence Score: 85%
Confidence Breakdown:
- Code Quality: 88% (well-structured, previous review nits mostly addressed)
- Completeness: 85% (feature-complete per PR scope, debug overlay + tests present)
- Risk Level: 75% (large new subsystem touching meshing, shader, and world streaming)
- Test Coverage: 65% (standalone propagation tests exist; no integration/visual tests)
Merge Readiness:
- All critical issues resolved
- SOLID average score >= 6.0
- Overall confidence >= 60%
- No security concerns
- Tests present and passing
Verdict:
MERGE WITH FIXES
The lighting system is in good shape and all substantive review feedback has been addressed. Before merging, update the PR description/test count claim; the remaining low-priority nits are optional cleanups.
# Conflicts: # src/game/Blocks.ts # src/game/Game.ts # src/main.ts
📋 SummaryNo linked issues are referenced in the PR body ( PR #1 adds a Minetest/Luanti-style two-channel voxel light engine (sun + block light), a day/night cycle with camera-anchored sun/moon billboards, a custom two-channel terrain shader for time-of-day dimming without remeshing, and debug tooling. The author has addressed every substantive issue raised in the previous automated reviews: world-top skylight seeding is fixed, encapsulation violations are resolved via public getters, propagation tests are committed and passing, per-frame allocations are removed, dead code is cleaned up, and the website 📌 Review Metadata
🔴 Critical Issues (Must Fix - Blocks Merge)None identified. ✅ All previously reported critical issues have been resolved.
|
| Previous Issue | Status |
|---|---|
SunLightPropagator world-top sunlight lost for non-sun-passing light-passing blocks |
✅ [FIXED] Seeded at LIGHT_MAX - 1 - absorption in Phase A; world-top water test passes. |
LightingSystem peeked World.lightDirty via as unknown |
✅ [FIXED] World now exposes public lightDirtyCount getter. |
LightingSystem bypassed DayNightCycle private light fields |
✅ [FIXED] DayNightCycle now exposes public sunIntensity/ambientIntensity getters. |
Website typecheck failed because playwright was not installed |
✅ [FIXED] website/tsconfig.json now excludes scripts/. |
| PR description test count mismatch | ✅ [FIXED] PR body now reads "7 propagation scenarios / 15 assertions pass". |
| No propagation tests committed | ✅ [FIXED] scripts/lighttest.ts added with bun run test:light; all pass. |
VoxelLightEngine wasted chunk scan with empty early-exit |
✅ [FIXED] Now breaks the y-loop once both flags are set. |
SunLightPropagator unused sunPass / misleading comment |
✅ [FIXED] sunPass is used in spread; comment clarified. |
CelestialSystem renderingGroupId set every frame |
✅ [FIXED] Set once in makeDisc. |
LightMap.markValid() unused / dirty never reset |
✅ [FIXED] Removed. |
VoxelLightEngine no explicit disposal |
✅ [FIXED] dispose() added and called from World.dispose(). |
DayNightCycle per-frame Color3/Color4 allocations |
✅ [FIXED] Scratch instances reused. |
Sky.setDomeColours allocated Vector3 every frame |
✅ [FIXED] Reuses two scratch Vector3s. |
ShadowManager.sameSet allocated a Set every frame |
✅ [FIXED] Allocation-free O(n²) membership check. |
📊 SOLID Principles Score
| Principle | Score | Notes |
|---|---|---|
| Single Responsibility | 8 | LightingSystem is a clean facade; DayNightCycle still mixes clock, colors, and light intensities but is better isolated. |
| Open/Closed | 8 | New blocks append IDs; lighting behavior is data-driven via BlockLightDefinition. |
| Liskov Substitution | 9 | No inheritance hierarchy to violate. |
| Interface Segregation | 8 | LightAccess, BrightnessSampler, and ShadowConfig are small and focused. |
| Dependency Inversion | 8 | Propagators depend on LightAccess, not World; engine uses a block-lookup callback. |
| Average | 8.2 |
🎯 Final Assessment
Overall Confidence Score: 85%
Confidence Breakdown:
- Code Quality: 88% (well-structured, previous review nits mostly addressed)
- Completeness: 90% (feature-complete per PR scope, debug overlay + tests present, PR description now accurate)
- Risk Level: 75% (large new subsystem touching meshing, shader, and world streaming)
- Test Coverage: 65% (standalone propagation tests exist; no integration/visual tests)
Merge Readiness:
- All critical issues resolved
- SOLID average score >= 6.0
- Overall confidence >= 60%
- No security concerns
- Tests present and passing
Verdict:
MERGE WITH FIXES
The lighting system is in good shape and all substantive review feedback has been thoroughly addressed. The remaining four low-priority cleanups are optional but worth tidying before merge.




Adds a full Minetest/Luanti-style voxel lighting system and a day/night cycle with a visual sun and moon. Built on the Babylon.js version of the game; does not touch inventory/survival/creative.
What's included
Voxel light engine (
src/game/lighting/)SunLightPropagator: top-down sky exposure + BFS flood fill. Sunlight streams straight down through air/glass with no decay and attenuates sideways/up; water and leaves break the column so depth and canopies dim naturally. Cross-chunk boundary inflow keeps chunk seams correct. The topmost light-passing-but-not-sun-passing cell (water/leaves at the world ceiling) is seeded with one decay step so it isn't pitch-black.BlockLightPropagator: emitter BFS decaying 1/step through light-passing cells (opaque = wall). Glowstone (id 28) emits 15.VoxelLightEnginerecomputes a chunk from scratch with a border-change diff that queues neighbours; reusable scratch buffers (zero alloc). Relighting is budgeted (3 chunks/frame) and only runs when something changes.Block registry lighting fields —
BlockLightDefinition(lightPassesThrough,sunlightPassesThrough,lightEmission,lightAbsorption,casts/receivesShadows) with a cachedresolveLight(). Data-driven, never hardcoded in the mesher.Day/night cycle
DayNightCycle:timeOfDay(0=midnight, 0.25=sunrise, 0.5=noon, 0.75=sunset), configurable day length +timeScale, pause/resume, preset helpers. Sun orbits east→west with a slight tilt; moon is the antipode.dayFactor/moonFactorand a multi-stop sky gradient are recomputed each frame and pushed to the Babylon lights, sky dome, and fog (using reused scratch vectors — no per-frame allocation).CelestialSystem: camera-anchored sun disc + additive halo + cratered moon billboards that fade at the horizon and warm at golden hour. Stay in rendering group 0 (set once) so terrain occludes them; never cast/receive shadows.VoxelTerrainMaterial: custom two-channel ShaderMaterial. Sun and block light are baked into vertex-colour.r/.g; the shader combinesmax(sun*dayFactor, sun*moonFloor, block). So night dims outdoor terrain without touching torch/glowstone (block) light and without rebuilding any chunk mesh — only uniforms update per frame. Linear fog is manual.Shadows —
ShadowManagerretained but disabled by default (terrain uses voxel sunlight), which permanently retires the earlier rectangular-frustum artifact. Its render-list diff is allocation-free.Debug tooling — overlay (
L), raw sun/block/combined view (K, now a uniform — no remesh), pause (T), shadow toggle (H), time presets ([/]), speed (O/I), midnight/midday (;/').__voxl.lighting()dumps live state + the shadow render list.Fixes along the way
uTexturewas listed as both uniform and sampler; switched to inlinevertexSource/fragmentSource).Verification
bun run typecheckandbun run build(game) andbun run typecheck(website,astro check) all pass.bun run test:light— 7 propagation scenarios / 15 assertions pass (open shaft, enclosed pocket, glowstone decay, cross-chunk bleed, canopy attenuation, water depth, world-top water boundary).Known limitations / TODOs
Manual test checklist