Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions packages/babylon-lite-compat/COMPAT-STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ updated by the `update-compat-layer` skill.
<!-- The two markers below are machine-read by the update-compat-layer skill.
Do not rename them. Update the SHA after re-syncing against BJS master. -->

- **Last synced BJS commit:** `b1d90d67bc09f56bf2882671f4484997630c4482`
- **Last sync date:** 2026-07-09
- **Last synced BJS commit:** `958a8dbed4e263d412ee9255ae747299043de19f`
- **Last sync date:** 2026-07-10
- **Lite compat package version:** 0.0.1

> The "Last synced BJS commit" is the `BabylonJS/Babylon.js` `master` HEAD that the
Expand Down Expand Up @@ -89,6 +89,7 @@ date` markers above record the `BabylonJS/Babylon.js` `master` HEAD the surface
| `engine.runRenderLoop` / `stopRenderLoop` | ⚡ Partial | engine (async startup; N callbacks) |
| `engine.resize` / `setSize` / `dispose` / `getRenderingCanvas` | ✅ Full | engine |
| `engine.getCaps()` (compressed-format flags `astc` / `s3tc` / `etc2`) | ✅ Full | engine (derived from the Lite WebGPU device's enabled features) |
| `AbstractEngine.Version` / `AbstractEngine.NpmPackage` (static) | ✅ Full | engine (constant version string tracking the last-synced `@babylonjs/core` release — currently `9.16.1`) |
| `engine.beginFrame` / `endFrame` | ❌ Not supported | — |
| `NullEngine` | ✅ Full | [engine/engine.ts](src/engine/engine.ts) (backed by Lite's real device-less `createNullEngine`; scene built with `defaultRenderTask: false` — no swapchain/GPU resource — and advanced via Lite `stepScene`, which fires the same before-render hook as the GPU path) |
| `AbstractScene` / `Scene` | ⚡ Partial | [scene/scene.ts](src/scene/scene.ts) over [scene/abstract-scene.ts](src/scene/abstract-scene.ts) (entity collections on `AbstractScene`, as in BJS) |
Expand Down Expand Up @@ -141,7 +142,7 @@ date` markers above record the `BabylonJS/Babylon.js` `master` HEAD the surface
| `PointLight` | ✅ Full | lights |
| `SpotLight` | ✅ Full | lights |
| `light.diffuse/specular/intensity/position/direction` | ✅ Full | lights |
| `light.setEnabled(false)` | 🔧 Needs Lite core | per-light visibility toggle |
| `light.setEnabled(false)` | ✅ Full | lights (no per-light flag in Lite; a disabled light zeroes its intensity in the shared lights UBO and restores the logical value on re-enable) |
| `RectAreaLight` | ❌ Not supported | not in Lite |
| `ClusteredLightContainer` | ❌ Not supported | throwing stub; not in Lite's public API |

Expand Down
14 changes: 14 additions & 0 deletions packages/babylon-lite-compat/src/engine/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ import { Observable } from "../misc/observable.js";
import type { Scene } from "../scene/scene.js";

export abstract class AbstractEngine {
/**
* Babylon.js `AbstractEngine.Version` — the `@babylonjs/core` version this
* compat layer targets. Kept in sync with the last-reconciled BJS release
* (see `COMPAT-STATUS.md` › "Last synced BJS commit").
*/
public static get Version(): string {
return "9.16.1";
}

/** Babylon.js `AbstractEngine.NpmPackage` — the npm package + version string. */
public static get NpmPackage(): string {
return `babylonjs@${AbstractEngine.Version}`;
}

/** @internal The underlying Lite engine context. Populated by `initAsync()`. */
public _lite!: EngineContext;

Expand Down
79 changes: 49 additions & 30 deletions packages/babylon-lite-compat/src/lights/lights.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,55 @@ export abstract class Light extends Node {
return "Light";
}

public abstract get intensity(): number;
public abstract set intensity(value: number);
/**
* @internal When the light is disabled via {@link setEnabled}, its Lite
* intensity is forced to `0` (zero contribution) and the caller-visible
* intensity is parked here so {@link intensity} keeps reporting the real
* value and re-enabling restores it. `null` means the light is enabled.
*/
private _disabledIntensity: number | null = null;

/** @internal Typed accessor for the Lite light's `intensity` scalar. */
private get _liteIntensity(): number {
return (this._lite as unknown as { intensity: number }).intensity;
}
private set _liteIntensity(value: number) {
(this._lite as unknown as { intensity: number }).intensity = value;
}

/**
* Babylon.js `light.intensity`. While the light is disabled the Lite light
* carries `0`, but the getter still returns the logical value the caller set.
*/
public get intensity(): number {
return this._disabledIntensity ?? this._liteIntensity;
}
public set intensity(value: number) {
if (this._disabledIntensity !== null) {
this._disabledIntensity = value;
} else {
this._liteIntensity = value;
}
}

/**
* Babylon.js `light.setEnabled(false)` — toggle the light's contribution.
* Babylon Lite has no per-light enable flag, so a disabled light is expressed
* by zeroing its intensity in the shared lights UBO (and restoring it on
* re-enable). The logical intensity value is preserved across the toggle.
*/
public override setEnabled(value: boolean): void {
if (value !== this.isEnabled()) {
if (!value) {
this._disabledIntensity = this._liteIntensity;
this._liteIntensity = 0;
} else if (this._disabledIntensity !== null) {
this._liteIntensity = this._disabledIntensity;
this._disabledIntensity = null;
}
}
super.setEnabled(value);
}

/** Detach this light's shadow generator (compat for `light.shadowEnabled = false`). */
public set shadowEnabled(enabled: boolean) {
Expand Down Expand Up @@ -92,13 +139,6 @@ export class HemisphericLight extends Light {
return "HemisphericLight";
}

public get intensity(): number {
return this._lite.intensity;
}
public set intensity(value: number) {
this._lite.intensity = value;
}

public get direction(): Vector3 {
return readVector(this._lite.direction);
}
Expand Down Expand Up @@ -144,13 +184,6 @@ export class DirectionalLight extends Light {
return "DirectionalLight";
}

public get intensity(): number {
return this._lite.intensity;
}
public set intensity(value: number) {
this._lite.intensity = value;
}

public get direction(): Vector3 {
return readVector(this._lite.direction);
}
Expand Down Expand Up @@ -196,13 +229,6 @@ export class PointLight extends Light {
return "PointLight";
}

public get intensity(): number {
return this._lite.intensity;
}
public set intensity(value: number) {
this._lite.intensity = value;
}

public get range(): number {
return this._lite.range;
}
Expand Down Expand Up @@ -248,13 +274,6 @@ export class SpotLight extends Light {
return "SpotLight";
}

public get intensity(): number {
return this._lite.intensity;
}
public set intensity(value: number) {
this._lite.intensity = value;
}

public get angle(): number {
return this._lite.angle;
}
Expand Down
16 changes: 15 additions & 1 deletion packages/babylon-lite-compat/tests/engine-scene-api.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";

import { WebGPUEngine } from "../src/engine/engine";
import { AbstractEngine, Engine, ThinEngine, WebGPUEngine } from "../src/engine/engine";
import { Scene } from "../src/scene/scene";

/**
Expand Down Expand Up @@ -167,3 +167,17 @@ describe("Scene entity registries", () => {
expect(b.getUniqueId()).toBe(2);
});
});

describe("AbstractEngine version statics", () => {
it("reports the targeted @babylonjs/core version", () => {
expect(AbstractEngine.Version).toBe("9.16.1");
expect(AbstractEngine.NpmPackage).toBe("babylonjs@9.16.1");
});

it("inherits the version statics on every engine subclass", () => {
for (const Ctor of [ThinEngine, Engine, WebGPUEngine]) {
expect(Ctor.Version).toBe("9.16.1");
expect(Ctor.NpmPackage).toBe("babylonjs@9.16.1");
}
});
});
67 changes: 67 additions & 0 deletions packages/babylon-lite-compat/tests/lights.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { describe, expect, it } from "vitest";

import { DirectionalLight, HemisphericLight, PointLight, SpotLight } from "../src/lights/lights";
import { Vector3 } from "../src/math/vector";

/**
* The light wrappers forward to Babylon Lite's device-free light factories, so
* their scalar/color proxying and the `setEnabled` visibility toggle can be
* exercised under Node without a GPU. `setEnabled(false)` has no per-light flag
* in Lite, so the wrapper zeroes the underlying intensity while preserving the
* caller-visible value — this test pins that behaviour.
*/

describe("Light.setEnabled visibility toggle", () => {
it("zeroes the Lite intensity when disabled and restores it when enabled", () => {
const light = new DirectionalLight("d", new Vector3(0, -1, 0));
light.intensity = 0.8;
expect(light.intensity).toBeCloseTo(0.8);
expect(light._lite.intensity).toBeCloseTo(0.8);

light.setEnabled(false);
expect(light.isEnabled()).toBe(false);
// Caller-visible intensity is preserved; the Lite light contributes nothing.
expect(light.intensity).toBeCloseTo(0.8);
expect(light._lite.intensity).toBe(0);

light.setEnabled(true);
expect(light.isEnabled()).toBe(true);
expect(light.intensity).toBeCloseTo(0.8);
expect(light._lite.intensity).toBeCloseTo(0.8);
});

it("remembers intensity changes made while disabled", () => {
const light = new PointLight("p", new Vector3(0, 1, 0));
light.intensity = 1;
light.setEnabled(false);
expect(light._lite.intensity).toBe(0);

light.intensity = 2.5;
// Still contributes nothing while disabled, but the value is remembered.
expect(light._lite.intensity).toBe(0);
expect(light.intensity).toBeCloseTo(2.5);

light.setEnabled(true);
expect(light._lite.intensity).toBeCloseTo(2.5);
});

it("is idempotent for repeated toggles of the same state", () => {
const light = new SpotLight("s", new Vector3(0, 5, 0), new Vector3(0, -1, 0), Math.PI / 4, 2);
light.intensity = 0.5;
light.setEnabled(false);
light.setEnabled(false);
expect(light._lite.intensity).toBe(0);
expect(light.intensity).toBeCloseTo(0.5);
light.setEnabled(true);
expect(light._lite.intensity).toBeCloseTo(0.5);
});

it("supports the hemispheric light too", () => {
const light = new HemisphericLight("h", new Vector3(0, 1, 0));
light.intensity = 0.7;
light.setEnabled(false);
expect(light._lite.intensity).toBe(0);
light.setEnabled(true);
expect(light._lite.intensity).toBeCloseTo(0.7);
});
});
Loading