Ground shadows: drain the queue only where the world draw is finished - #1630
Merged
Merged
Conversation
Blob shadows are queued and replayed once the opaque meshes are down. The replay was triggered by any batcher switch, which fired in two places it should not have. Inside the screen-projection window `Container.draw` opens around a `floating` child: the blobs are WORLD-space geometry, so replaying them there fed every one screen-space clip coordinates and put it off-screen. A single HUD anywhere in a scene silently deleted every ground shadow in it. And in the middle of a scene, whenever anything non-mesh sorted there. A particle emitter or a sprite raises the same transition, and every mesh still to come then painted straight over the blobs just put down — the ground plane above all, which routinely sorts after the props standing on it. That is the case the deferral exists to prevent, and it was reintroducing it. The queue now drains at three sites that really are the end of the world draw: `Container.draw` just before a floating child (so an overlay still covers them), `Camera2d.draw` once the whole container is down, and `Application.draw` at end of frame. `Renderer` grows a screen-space bracket so a drain raised inside one is held rather than lost. Behaviour change: a NON-floating 2D renderable drawn part-way through a 3D scene now draws under the blobs instead of over them. The spec that pinned the old drain site is rewritten rather than dropped, and two cover the failures above. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Aa37KGXZcnVrbn1yG4j1N
Two things that cost real time to work out on a live scene, neither of them written down. A blob is an ellipse sized to the caster's own footprint and placed at the caster's x/z — never offset by light direction. A tall or narrow object shows its shadow; a wide flat-bottomed one resting on the floor covers its own completely from a camera looking down at it. That is correct behaviour, and reads as a missing feature. And the fix that suggests itself is the wrong one: raising `shadowGroundY` does not slide the blob out from under the object, it floats the blob up, and past a few units it projects over the top of the caster as a dark ring around it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Aa37KGXZcnVrbn1yG4j1N
obiot
force-pushed
the
fix/ground-shadow-drain
branch
from
August 31, 2026 13:24
8140079 to
0235b68
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two ways a scene lost its ground shadows
Blob shadows are queued and replayed once the opaque meshes are down (#1515). The replay was triggered by any batcher switch, which fired in two places it should not have.
A single HUD deleted every shadow in the scene
Container.drawinstalls the camera's screen projection around afloatingchild, and the first thing that child does is switch batcher. A queued blob is world-space geometry, so replaying it there fed every one screen-space clip coordinates and put it off-screen.Isolated by removing the HUD from a scene and watching the shadows come back; confirmed with a probe showing the blobs being replayed with sane matrices, tint and alpha, and simply not landing anywhere visible.
Anything non-mesh mid-scene put them down too early
A particle emitter or a sprite that sorts into the middle of a scene raises the same transition, and every mesh still to come then paints straight over the blobs just put down — the ground plane above all, which routinely sorts after the props standing on it. That is precisely the case the deferral exists to prevent, and the drain site was reintroducing it.
Isolated by disabling the emitters in the same scene: the surviving shadows reappeared.
The fix
The queue drains at three sites that really are the end of the world draw:
Container.draw, just before a floating childCamera2d.draw, once the container is downApplication.draw, end of frameRenderergrows abeginScreenSpace/endScreenSpacebracket so a drain raised inside one is held rather than lost, andsetBatcherno longer drains in either backend.Behaviour change
A non-floating 2D renderable drawn part-way through a 3D scene now draws under the blobs rather than over them. I think that is the more defensible order — the blob belongs to the ground — but it is a change, and it is why the spec that pinned the old drain site is rewritten rather than deleted.
Tests
tests/ground_shadow.spec.js: the rewritten contract test, plus one per failure above and one for bracket balance (an unbalancedendmust not be able to wedge the queue shut).Verification
266 files / 6461 testspass; lint and types clean🤖 Generated with Claude Code
https://claude.ai/code/session_012Aa37KGXZcnVrbn1yG4j1N