feat(sprite): add renderer-native Sprite2D Y-sort - #595
Conversation
API ChangesAPI Extractor detected public API changes for No removed public API lines were detected; this appears to be additive. API Extractor diffdiff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index 319e8155..4b340401 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -2117,6 +2117,9 @@ export interface DirectionalLight extends LightBase {
// @public
export function disableOrthographicCamera(camera: Camera): void;
+// @public
+export function disableSprite2DYSort(layer: Sprite2DLayer): boolean;
+
// @public
export interface DiscOptions {
arc?: number;
@@ -2466,6 +2469,9 @@ export function enableSkeletonShadows(generator: ShadowGenerator): void;
// @public
export function enableSpatial(host: AudioGraphHost, options?: SpatialSoundOptions): void;
+// @public
+export function enableSprite2DYSort(layer: Sprite2DLayer, options?: Sprite2DYSortOptions): Sprite2DYSortState;
+
// @public
export function enableStandardSkeleton(): void;
@@ -5861,6 +5867,12 @@ export function setSprite2DShaderParams(layer: Sprite2DLayer, params: readonly [
// @public
export function setSprite2DUvOffset(layer: Sprite2DLayer, index: number, uvOffset: readonly [number, number]): void;
+// @public
+export function setSprite2DYSortBias(layer: Sprite2DLayer, index: number, bias: number): void;
+
+// @public
+export function setSprite2DYSortHandleBias(handle: Sprite2DHandle, bias: number): void;
+
// @public
export function setSpriteRendererTarget(sr: SpriteRenderer, target: Texture2D | null): void;
@@ -6383,6 +6395,18 @@ export interface Sprite2DView {
zoom: number;
}
+// @public
+export interface Sprite2DYSortOptions {
+ defaultBias?: number;
+}
+
+// @public
+export interface Sprite2DYSortState {
+ readonly defaultBias: number;
+ readonly enabled: boolean;
+ readonly layer: Sprite2DLayer;
+}
+
// @public
export interface SpriteAnimationBinding {
// (undocumented) |
Lite Playground - Static SiteBuild 20260821.7 - merge @ f5cce63 |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Self code review completed by the code-review skill. Review fixes: 9cdfe11d
The full Y-sort implementation and correction round were independently approved with no remaining substantive findings. |
API ChangesAPI Extractor detected public API changes for No removed public API lines were detected; this appears to be additive. API Extractor diffdiff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index 319e8155..4b340401 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -2117,6 +2117,9 @@ export interface DirectionalLight extends LightBase {
// @public
export function disableOrthographicCamera(camera: Camera): void;
+// @public
+export function disableSprite2DYSort(layer: Sprite2DLayer): boolean;
+
// @public
export interface DiscOptions {
arc?: number;
@@ -2466,6 +2469,9 @@ export function enableSkeletonShadows(generator: ShadowGenerator): void;
// @public
export function enableSpatial(host: AudioGraphHost, options?: SpatialSoundOptions): void;
+// @public
+export function enableSprite2DYSort(layer: Sprite2DLayer, options?: Sprite2DYSortOptions): Sprite2DYSortState;
+
// @public
export function enableStandardSkeleton(): void;
@@ -5861,6 +5867,12 @@ export function setSprite2DShaderParams(layer: Sprite2DLayer, params: readonly [
// @public
export function setSprite2DUvOffset(layer: Sprite2DLayer, index: number, uvOffset: readonly [number, number]): void;
+// @public
+export function setSprite2DYSortBias(layer: Sprite2DLayer, index: number, bias: number): void;
+
+// @public
+export function setSprite2DYSortHandleBias(handle: Sprite2DHandle, bias: number): void;
+
// @public
export function setSpriteRendererTarget(sr: SpriteRenderer, target: Texture2D | null): void;
@@ -6383,6 +6395,18 @@ export interface Sprite2DView {
zoom: number;
}
+// @public
+export interface Sprite2DYSortOptions {
+ defaultBias?: number;
+}
+
+// @public
+export interface Sprite2DYSortState {
+ readonly defaultBias: number;
+ readonly enabled: boolean;
+ readonly layer: Sprite2DLayer;
+}
+
// @public
export interface SpriteAnimationBinding {
// (undocumented) |
Lite Playground - Static SiteBuild 20260821.9 - merge @ 67e2c8f |
There was a problem hiding this comment.
Pull request overview
Adds an opt-in, renderer-native stable Y-sort path for pure Sprite2DLayers so top-down/isometric sprite overlap (and pickSprite2D) matches GPU draw order without mutating canonical CPU instance storage or breaking stable handle/index identity.
Changes:
- Introduces optional Y-sort state, sorting/permutation logic, and packed GPU-order upload path behind a null-by-default hook.
- Integrates the hook into Sprite2D mutation tracking, instance uploads, and CPU picking to keep visual and pick order consistent.
- Adds focused unit + Playwright parity coverage plus bundle-size isolation assertions and a new Scene 303 demo/config.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/lite/unit/sprite-2d-y-sort.test.ts | New unit tests covering ordering, stability, bias, dirty behavior, and enable/disable semantics. |
| tests/lite/parity/scenes/scene303-sprite2d-y-sort.spec.ts | New Playwright spec validating visible overlap pixels and picking for Scene 303. |
| tests/lite/parity/bundle-size.spec.ts | Adds bundle-size / runtime-module assertions to ensure Y-sort remains opt-in and Scene 303 stays on the intended sprite path. |
| scene-config.json | Registers Scene 303 with ceiling/tags and skipParity rationale. |
| packages/babylon-lite/src/sprite/sprite-pipeline.ts | Routes instance uploads through the optional Y-sort hook when present. |
| packages/babylon-lite/src/sprite/sprite-2d.ts | Adds _ySortState field and forwards add/remove/clear/dirty events to the Y-sort hook. |
| packages/babylon-lite/src/sprite/sprite-2d-y-sort.ts | New optional Y-sort implementation: stable sort, permutation/inverse, packed staging, bias API, enable/disable. |
| packages/babylon-lite/src/sprite/sprite-2d-y-sort-hook.ts | New null-by-default hook contract + global registration getter/setter. |
| packages/babylon-lite/src/sprite/sprite-2d-handle-y-sort.ts | New optional handle-based bias setter that resolves current index. |
| packages/babylon-lite/src/sprite/picking/pick-sprite-2d.ts | Updates picking to respect Y-sort draw permutation when enabled. |
| packages/babylon-lite/src/index.ts | Re-exports the new Y-sort APIs/types from the root entry point. |
| lab/public/bundle/manifest/scene303.json | Adds committed bundle manifest for Scene 303 size tracking. |
| lab/lite/src/lite/scene303.ts | New demo scene exercising live Y changes, equal-Y ties, and bias behavior (plus dataset for tests). |
| lab/lite/scene303.html | New dev HTML entry for Scene 303. |
| lab/lite/bundle-scene303.html | New bundled HTML entry for Scene 303. |
| docs/lite/architecture/32-sprites.md | Documents the Y-sort feature, constraints, semantics, and its interaction with picking/uploads/bundle ceilings. |
Suppressed comments (1)
packages/babylon-lite/src/sprite/sprite-2d-y-sort.ts:418
- Same
key !== state._keys[index]issue as inobserveDirty: ifpositionPx.yever becomesNaN, this comparison will always evaluate true and keep forcing_sortDirty/_fullUpload, leading to repeated full uploads even when the effective key didn’t change. ANaN-stable equality check (e.g.Object.is) would avoid this churn.
const key = keyAt(layer, state, index);
if (key !== state._keys[index]) {
state._keys[index] = key;
state._sortDirty = true;
state._fullUpload = true;
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
API ChangesAPI Extractor detected public API changes for No removed public API lines were detected; this appears to be additive. API Extractor diffdiff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index 319e8155..4b340401 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -2117,6 +2117,9 @@ export interface DirectionalLight extends LightBase {
// @public
export function disableOrthographicCamera(camera: Camera): void;
+// @public
+export function disableSprite2DYSort(layer: Sprite2DLayer): boolean;
+
// @public
export interface DiscOptions {
arc?: number;
@@ -2466,6 +2469,9 @@ export function enableSkeletonShadows(generator: ShadowGenerator): void;
// @public
export function enableSpatial(host: AudioGraphHost, options?: SpatialSoundOptions): void;
+// @public
+export function enableSprite2DYSort(layer: Sprite2DLayer, options?: Sprite2DYSortOptions): Sprite2DYSortState;
+
// @public
export function enableStandardSkeleton(): void;
@@ -5861,6 +5867,12 @@ export function setSprite2DShaderParams(layer: Sprite2DLayer, params: readonly [
// @public
export function setSprite2DUvOffset(layer: Sprite2DLayer, index: number, uvOffset: readonly [number, number]): void;
+// @public
+export function setSprite2DYSortBias(layer: Sprite2DLayer, index: number, bias: number): void;
+
+// @public
+export function setSprite2DYSortHandleBias(handle: Sprite2DHandle, bias: number): void;
+
// @public
export function setSpriteRendererTarget(sr: SpriteRenderer, target: Texture2D | null): void;
@@ -6383,6 +6395,18 @@ export interface Sprite2DView {
zoom: number;
}
+// @public
+export interface Sprite2DYSortOptions {
+ defaultBias?: number;
+}
+
+// @public
+export interface Sprite2DYSortState {
+ readonly defaultBias: number;
+ readonly enabled: boolean;
+ readonly layer: Sprite2DLayer;
+}
+
// @public
export interface SpriteAnimationBinding {
// (undocumented) |
Lite Playground - Static SiteBuild 20260821.11 - merge @ 19e2ccb |
|
Bundle-size CI diagnosis and fix:
|
API ChangesAPI Extractor detected public API changes for No removed public API lines were detected; this appears to be additive. API Extractor diffdiff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index 319e8155..4b340401 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -2117,6 +2117,9 @@ export interface DirectionalLight extends LightBase {
// @public
export function disableOrthographicCamera(camera: Camera): void;
+// @public
+export function disableSprite2DYSort(layer: Sprite2DLayer): boolean;
+
// @public
export interface DiscOptions {
arc?: number;
@@ -2466,6 +2469,9 @@ export function enableSkeletonShadows(generator: ShadowGenerator): void;
// @public
export function enableSpatial(host: AudioGraphHost, options?: SpatialSoundOptions): void;
+// @public
+export function enableSprite2DYSort(layer: Sprite2DLayer, options?: Sprite2DYSortOptions): Sprite2DYSortState;
+
// @public
export function enableStandardSkeleton(): void;
@@ -5861,6 +5867,12 @@ export function setSprite2DShaderParams(layer: Sprite2DLayer, params: readonly [
// @public
export function setSprite2DUvOffset(layer: Sprite2DLayer, index: number, uvOffset: readonly [number, number]): void;
+// @public
+export function setSprite2DYSortBias(layer: Sprite2DLayer, index: number, bias: number): void;
+
+// @public
+export function setSprite2DYSortHandleBias(handle: Sprite2DHandle, bias: number): void;
+
// @public
export function setSpriteRendererTarget(sr: SpriteRenderer, target: Texture2D | null): void;
@@ -6383,6 +6395,18 @@ export interface Sprite2DView {
zoom: number;
}
+// @public
+export interface Sprite2DYSortOptions {
+ defaultBias?: number;
+}
+
+// @public
+export interface Sprite2DYSortState {
+ readonly defaultBias: number;
+ readonly enabled: boolean;
+ readonly layer: Sprite2DLayer;
+}
+
// @public
export interface SpriteAnimationBinding {
// (undocumented) |
Lite Playground - Static SiteBuild 20260821.13 - merge @ fe2c5fb |
Lab - Static SiteBuild 20260821.13 - merge @ fe2c5fb |
|
Replacement CI build 58038 is fully green on final head The Bundle Size job now passes with Scene 303 at 20,249 bytes raw (19.8 KB / 8.5 KB gzip), below its 25 KB ceiling. API, compat, lint/typecheck, snapshot, cloud parity, performance, release markers, unit tests, and GitGuardian also pass. Both Copilot review threads remain resolved. |
Purpose
Top-down, isometric, and 2.5D sprite games need characters and scenery to overlap according to their world Y position. Doing this outside Lite would require rearranging private packed instance buffers, which would break public numeric indices and stable sprite handles.
This PR adds an optional renderer-native Y-sort for pure
Sprite2DLayers. It preserves canonical CPU storage and identity while uploading a separately packed GPU draw order.Behavior
positionPx.y + bias, so larger effective Y draws later/on topSprite2DHandleidentitypickSprite2Dresolve the same topmost sprite the GPU displaysdepth: "none"layers and rejects depth-hosted layers explicitlyPay-for-use
Y-sort is installed through an explicit optional module and a null-by-default hook. Ordinary Sprite2D scenes retain zero Y-sort bytes. Scene 50 remains byte-identical at 16,168 bytes and excludes all Y-sort modules.
Validation
Scene 303 is intentionally
skipParity: Babylon.js has no equivalent pure-2D GPU-order staging API to use as a pixel oracle.