Container: order floating children by depth alone, not by screen position - #1629
Merged
Conversation
…tion
`Container.draw` gives a `floating` child `resetTransform()` and the
camera's screen projection, so its `pos.x/y` are canvas pixels rather
than a place in the world. `_sortDepth` fed those to a world-space
distance and subtracted the camera position on top of that, with two
consequences under a `Camera3d`:
- a HUD's layering depended on where it sat on the SCREEN. A score in
a corner scored 20² + 16² and floated above the scene; the same text
centred scored 512² + 200² and sank behind it.
- it drifted as the camera travelled, via the (z - camZ)² term, so a
HUD that was correct at the start of a level was buried by the end.
A floating child is now ordered by |pos.z| alone. Magnitude is what the
screen-space idioms already encode — the flight demo's HUD sits at -150,
and the glTF, Billboard, Night City and Instanced Forest examples park a
floating sky at -10000 or 100000 — and the old key squared the distance,
so the sign never carried meaning. Reading it as meaningful would put
those four skies in front of their own scenes; the new key ignores it.
World children are untouched.
Tests cover both idioms at extreme camera positions, and are checked
against four mutations: the pre-fix key (10 fail), floating forced to
the front (11), a signed key (5), and keeping the screen coordinates (6).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Aa37KGXZcnVrbn1yG4j1N
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.
The bug
Container.drawgives afloatingchildresetTransform()and the camera's screen projection, so itspos.x/yare canvas pixels — not a place in the world._sortDepthfed those to a world-space distance and subtracted the camera position on top of that.Two consequences, both under a
Camera3d:A HUD's layering depended on where it sat on the screen.
656302144Same
floating, same depth. Only the screen position differed.And it drifted as the camera travelled, via the
(z - camZ)²term — a HUD correct at the start of a level was buried by the end of it, which is the kind of thing that gets papered over with a per-framehud.depth = camera.pos.z + nworkaround.The fix
A floating child is ordered by
|pos.z|alone. World children are untouched.Magnitude is what the screen-space idioms in this repo already encode, and the old key squared the distance, so the sign never carried meaning:
That is the flight demo's HUD at
-150, and the glTF Scene, glTF Animated Model, Billboard and Night City examples parking a floating sky at-10000, and Instanced Forest at100000. Treating a negative depth as "nearest" pulls those four skies in front of their own scenes and leaves nothing but a gradient — so the sign is ignored.Tests
tests/floating-depth-sort.spec.js, 21 tests: both idioms at extreme camera positions, a backdrop with the camera sitting on its own depth, an overlay after 8000 units of travel, sign symmetry, and the world path asserted unchanged.Checked against four mutations, each of which is a fix someone could plausibly write:
Verification
267 files / 6480 testspass; lint, types and biome clean"z", not"depth"Supersedes #1628, which forced floating children to the front and blanked three examples.
🤖 Generated with Claude Code
https://claude.ai/code/session_012Aa37KGXZcnVrbn1yG4j1N