Skip to content

Ground shadows: drain the queue only where the world draw is finished - #1630

Merged
obiot merged 2 commits into
masterfrom
fix/ground-shadow-drain
Aug 31, 2026
Merged

obiot merged 2 commits into
masterfrom
fix/ground-shadow-drain

Conversation

@obiot

@obiot obiot commented Aug 31, 2026

Copy link
Copy Markdown
Member

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.draw installs the camera's screen projection around a floating child, 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:

site why
Container.draw, just before a floating child blobs go down over the world, under the overlay
Camera2d.draw, once the container is down inside the camera's own bracket
Application.draw, end of frame a scene that is nothing but meshes

Renderer grows a beginScreenSpace / endScreenSpace bracket so a drain raised inside one is held rather than lost, and setBatcher no 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 unbalanced end must not be able to wedge the queue shut).

Verification

  • 266 files / 6461 tests pass; lint and types clean
  • Per-material Textures and Billboard still show their shadows with their floating labels on top
  • checked on a scene combining a perspective camera, a floating HUD, particle emitters and terrain — the case that surfaced both bugs

🤖 Generated with Claude Code

https://claude.ai/code/session_012Aa37KGXZcnVrbn1yG4j1N

Copilot AI lite review requested due to automatic review settings August 31, 2026 13:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 31, 2026 13:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

obiot and others added 2 commits August 31, 2026 21:23
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
obiot force-pushed the fix/ground-shadow-drain branch from 8140079 to 0235b68 Compare August 31, 2026 13:24
Copilot AI review requested due to automatic review settings August 31, 2026 13:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@obiot
obiot merged commit 0d7650d into master Aug 31, 2026
6 checks passed
@obiot
obiot deleted the fix/ground-shadow-drain branch August 31, 2026 13:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants