Add solid leaf shadows in the shadow pass - #18
Conversation
Leaves sampled mip-filtered binary alpha in the shadow pass, so the canopy shadow silhouette crawled as the sun and view angle changed. The block shader now tests the built-in IN_SHADOW_PASS and writes opaque coverage for leaves (the only blocks the mesher gives a full COLOR.a wind weight), so the shadow map rasterizes a solid geometric silhouette instead of a dithered cutout. The visible pass keeps the cutout look, non-leaf cutout blocks are unaffected, and solid_leaf_shadows = 0 restores the old dappled shadows. Measured on a flat-world canopy A/B (fixed camera, 0.1 sun steps, close-up shadow-edge temporal MAD, off -> off control -> on): no TAA 0.0031 -> 0.0027 -> 0.0018, TAA 0.0017 -> 0.0018 -> 0.0012.
📋 SummaryLinked issues: The PR body references the open This PR adds a shadow-pass-only leaf proxy to I verified the wiring against the full sources: 📌 Review Metadata
The one substantive finding is in the measurement tooling, not the shipped shader. 🔴 Critical Issues (Must Fix - Blocks Merge)None identified. The shader change is correct: in the visible pass
|
| Principle | Score | Notes |
|---|---|---|
| Single Responsibility | 9 | Shader owns the visual proxy; tools are single-purpose; mesher and registry untouched |
| Open/Closed | 8 | Data-driven via existing COLOR.a marker + a uniform escape hatch; no mesher changes needed for the new behavior |
| Liskov Substitution | 8 | No inheritance introduced; contracts preserved (visible-pass output identical for non-leaf blocks) |
| Interface Segregation | 8 | Small focused scripts; measure and verify tools are cleanly split headless vs display |
| Dependency Inversion | 7 | The shader↔mesher COLOR.a convention is implicit cross-module coupling, but it is documented in AGENTS.md and pinned by shadow_proxy_verify.gd |
| Average | 8.0 |
🎯 Final Assessment
Overall Confidence Score: 82%
Rate your confidence in this PR being ready to merge (0-100%).
How to interpret:
- 0-30%: Major concerns, do not merge without significant rework
- 31-60%: Moderate concerns, several issues need addressing
- 61-80%: Minor concerns, mostly ready with some fixes
- 81-100%: High confidence, ready to merge or with trivial fixes
Confidence Breakdown:
- Code Quality: 90% (minimal, well-commented shader delta; tools match repo conventions exactly — SceneTree
_initializepattern,.uidfiles,user://output viamake_dir_recursive_absolute) - Completeness: 90% (implements precisely the roadmap's named option; docs updated on both AGENTS.md and ROADMAP.md; headless + display verification provided)
- Risk Level: 85% (no worker-thread, determinism, streaming, or water invariants touched; only residual runtime risk is that CI cannot compile the shader — locally verified per the PR)
- Verification: 70% (
shadow_proxy_verify.gdshould be run headless andweather_verify.gdre-run; neither can execute on this runner — treated as residual risk per policy. The display measurement's methodology has the Medium finding above, though its control-phase design keeps the conclusion sound)
Merge Readiness:
- All critical issues resolved
- SOLID average score >= 6.0
- Overall confidence >= 60%
- No security concerns
- No unresolved worker-thread, determinism, or data-loss risk
Verdict:
MERGE WITH FIXES
The shader change is correct, well-contracted, and properly documented; fix the measurement tool's day-cycle override (or amend the recorded methodology) so the ROADMAP numbers describe what actually rendered, and run shadow_proxy_verify.gd locally before merging.
Machine Readable Verdict
{
"reviewed_sha": "d0ad7abfd12166e0250ee8c5b83e39d90a1a3623",
"critical_issues": 0,
"high_priority_issues": 0,
"medium_priority_issues": 1,
"overall_confidence_score": 82,
"recommendation": "MERGE_WITH_FIXES"
}- Drive the measurement sun through DayNightCycle.set_time() and freeze auto_advance; its _process rewrites the sun rotation from time_hours every frame, so the manual rotation writes never rendered and the "0.1 deg sun steps" were really the game clock (~20x slower). Re-ran the A/B: no-TAA control tracks off (0.0060-0.0074) with the proxy at 0.0037-0.0042 (~45% lower); TAA already smooths the edge so it gains little. Also settle the sculpted chunks and flush TAA history before sampling, which removes the run-to-run variance. - Expose the tradeoff as Advanced Graphics -> Shadows "Solid Leaf Shadows" (default on, present in all presets, applied in Main._apply_graphics), so players can restore dappled shadows. - Derive the verifier's expected leaf set from block names so a future leaf block cannot silently miss FLAG_LEAVES, and pin the preset/UI coverage of the new graphics key.
Review feedback addressed —
|
| off | off (control) | on | |
|---|---|---|---|
| no TAA | 0.0060–0.0074 | 0.0060–0.0074 | 0.0037–0.0042 |
| TAA | 0.0038–0.0040 | 0.0037–0.0039 | 0.0015–0.0033 |
Without TAA the control tracks off every run and the proxy lands ~45% lower; with TAA the sampler already smooths the edge, so it gains little and the control is noisier. ROADMAP.md and the tool header now describe the real methodology and these numbers.
LOW — no player-facing toggle (addressed).
Added Advanced Graphics → Shadows → Solid Leaf Shadows (default on, key in all three GRAPHICS_PRESETS, applied in Main._apply_graphics() via the shared ShaderMaterial). Dappled shadows are restorable from Settings. shadow_proxy_verify.gd now asserts the key exists in every preset and has a section row.
LOW — hardcoded leaf list (addressed).
_check_leaf_blocks_independent now derives the expected set from BlockRegistry.get_block_name() (*leaves*) instead of six named constants, so a future leaf block that forgets FLAG_LEAVES fails the check. It also asserts at least six leaf blocks exist.
Local verification after the changes: parse check 0 errors; shadow_proxy_verify, weather_verify, ui_flow_verify, ui_scale_verify, frame_pacing_verify all PASS.
|
All verification complete. Here is my review: 📋 SummaryLinked issues: No GitHub issues referenced ( This commit (4b8037e, "Address shadow proxy review feedback") is a direct response to the previous automated review: it re-drives the measurement sun through 📌 Review Metadata
🔴 Critical Issues (Must Fix - Blocks Merge)None identified. ✅ All previously reported critical issues have been resolved (none were reported).
|
| Principle | Score | Notes |
|---|---|---|
| Single Responsibility | 9 | Proxy isolated in the shader; toggle flows through the established graphics preset/UI/apply pipeline; tools remain single-purpose |
| Open/Closed | 8 | Behavior keyed off the existing COLOR.a data channel + uniform; new setting is data-driven through GRAPHICS_PRESETS/SECTIONS |
| Liskov Substitution | 8 | No inheritance introduced; visible-pass output and all existing signal/material contracts preserved |
| Interface Segregation | 8 | Headless verify vs display measure cleanly split; new check functions are focused |
| Dependency Inversion | 7 | The shader↔mesher COLOR.a convention is still implicit cross-module coupling, but it is documented in AGENTS.md, and the verifier now guards both sides (marker uniqueness + name-derived leaf set) |
| Average | 8.0 |
🎯 Final Assessment
Overall Confidence Score: 88%
Rate your confidence in this PR being ready to merge (0-100%).
How to interpret:
- 0-30%: Major concerns, do not merge without significant rework
- 31-60%: Moderate concerns, several issues need addressing
- 61-80%: Minor concerns, mostly ready with some fixes
- 81-100%: High confidence, ready to merge or with trivial fixes
Confidence Breakdown:
- Code Quality: 92% (all three review findings addressed with the stronger fix option; toggle wiring matches repo conventions exactly, including the
frame_pacing_verify.gdautoload-fetch precedent) - Completeness: 95% (roadmap item implemented and ticked with provenance; docs, presets, UI, application, and regression pinning all present)
- Risk Level: 88% (main-thread-only shader parameter and settings plumbing; no worker, determinism, streaming, or water invariants touched; material guard covers the non-shader fallback)
- Verification: 75% (
shadow_proxy_verify.gdshould be re-run headless locally — its new_check_graphics_togglepostdates the PR body's "PASS" claim — andweather_verify.gdremains valid since commit 2 did not touch wind wiring; the engine binary is unavailable on this runner, which is residual risk per policy, not a blocker)
Merge Readiness:
- All critical issues resolved
- SOLID average score >= 6.0
- Overall confidence >= 60%
- No security concerns
- No unresolved worker-thread, determinism, or data-loss risk
Verdict:
MERGE
All previously reported issues are fixed and verified against the full sources; the only remaining nit is the stale PR-description table, which affects no shipped code.
Machine Readable Verdict
{
"reviewed_sha": "4b8037ebbe2b93359342da114640ff7d0fd36fc9",
"critical_issues": 0,
"high_priority_issues": 0,
"medium_priority_issues": 0,
"overall_confidence_score": 88,
"recommendation": "MERGE"
}

What
Fixes the long-standing "Residual angle-dependent edge aliasing on direct sun shadows" item in
ROADMAP.md(Shadows section).Leaves are cutout blocks, so the shadow pass rasterized their silhouette by sampling the texture-array alpha. At shadow-map texel densities that alpha is mip-filtered, so the binary cutout edge dithered frame to frame as the sun and view angle changed — the canopy shadow edge visibly crawled.
world/block.gdshadernow tests the engine built-inIN_SHADOW_PASSand, for leaves only, writes opaque coverage:COLOR.awind weight (ChunkMesher._build_light_tables), soCOLOR.adoubles as the leaf marker. Glass and cross foliage are unaffected.Main._apply_graphics()). Off restores the old dappled cutout shadows.Why not
alpha_hashThe earlier
alpha_hashattempt (reverted) only re-dithered mip-filtered texels; base-mip leaf alpha is binary, so it added per-frame noise without stabilizing the silhouette. Changing the silhouette in the shadow pass attacks the root cause.Verification
redot --editor --headless --path . --quit— clean, 0 errors.tools/shadow_proxy_verify.gd(headless) — PASS. Pins the mesher's leaf wind marker (uniqueness across the block table), the name-derived leaf/FLAG_LEAVESset, the shader'sIN_SHADOW_PASS/solid_leaf_shadows/ALPHA = max(tex.a, shadow_proxy)wiring, and that the new graphics key exists in every preset and has a UI row.tools/shadow_proxy_measure.gd(display) — flat-world canopy A/B, fixed camera, day/night clock frozen, 12 frames per phase at exact 0.1° sun steps driven throughDayNightCycle.set_time(), close-up shadow-edge temporal MAD, off → off control → on, four runs:Without TAA the control tracks
offevery run and the proxy lands ~45% lower; with TAA the sampler already smooths the edge, so it gains little and the control is noisier. Close-up captures show the ragged speckled edge replaced by a clean straight one, with the visible-pass canopy top identical off and on.weather_verify.gd,ui_flow_verify.gd,ui_scale_verify.gd,frame_pacing_verify.gd— all PASS (the graphics toggle rides the existing settings flow; the wind wiring is unchanged).Notes for reviewers
_sun.rotation_degreesdirectly. That never rendered:DayNightCycle._processcalls_apply()every frame and rewrites the rotation fromtime_hours. The tool now freezesauto_advanceand usesset_time(), and the numbers above are from the corrected tool.IN_SHADOW_PASSis a spatial-shader built-in on all renderers (Forward+, mobile, GLES3); this project is Forward+.