Contributing to Babylon Lite
June 18, 2026 · View on GitHub
Adding a New Scene to the Lab
Each scene demonstrates a specific rendering feature and serves as both a visual demo and an automated regression test. Follow these steps to add Scene N.
1. Choose an ID and Slug
Pick the next available scene number (e.g., 23) and a descriptive slug:
- ID:
23 - Slug:
scene23-my-feature
2. Create the Lite Scene
lab/lite/sceneN.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Babylon Lite — Scene N: My Feature</title>
<style>
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: #000;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<script src="/loader.js"></script>
<script type="module" src="/lite/src/lite/sceneN.ts"></script>
</body>
</html>
lab/lite/src/lite/sceneN.ts
import { createEngine, createSceneContext, createDefaultCamera, attachControl, registerScene, startEngine } from "babylon-lite";
async function main(): Promise<void> {
const canvas = document.getElementById("renderCanvas") as HTMLCanvasElement;
const engine = await createEngine(canvas);
const scene = createSceneContext(engine);
// --- Set up your scene here ---
const cam = createDefaultCamera(scene);
attachControl(cam, canvas, scene);
await registerScene(scene);
await startEngine(engine);
canvas.dataset.ready = "true";
}
main().catch(console.error);
Tip: Study existing scenes in
lab/lite/src/lite/for patterns. If a similar feature already exists (e.g., DDS skybox in scene14, animation in scene7), reuse its approach.
3. Create the Babylon.js Reference
lab/lite/babylon-ref-sceneN.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Babylon.js Reference — Scene N: My Feature</title>
<style>
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: #000;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<script type="module" src="/lite/src/bjs/sceneN.ts"></script>
</body>
</html>
lab/lite/src/bjs/sceneN.ts — Implement the same scene using @babylonjs/core APIs. This is the pixel-perfect reference. Set canvas.dataset.ready = "true" once rendered.
4. Vite Config (Auto-Detected)
HTML files in lab/lite/ are auto-detected by vite.config.ts — no manual entry needed.
5. Capture the Golden Reference
Run the BJS reference page and capture a screenshot:
# Start the dev server
pnpm dev:lab
# In another terminal, capture the golden
pnpm exec tsx scripts/capture-golden.ts --scene N
Or manually screenshot the canvas at http://localhost:5174/babylon-ref-sceneN.html and save as:
reference/lite/sceneN-my-feature/babylon-ref-golden.png
Also save a downscaled JPG thumbnail (≤720p, e.g. 1280×720):
lab/public/thumbnails/sceneN.jpg
6. Add Scene Config
Add an entry to scene-config.json:
{
"id": 23,
"slug": "scene23-my-feature",
"name": "Scene 23 — My Feature",
"maxMad": 0.01,
"maxRawKB": 60,
"description": "Brief description of what the scene demonstrates.",
"tags": ["pbr", "procedural"]
}
maxMad— Maximum Mean Absolute Difference allowed (start tight, loosen only with approval)maxRawKB— Bundle raw size ceiling (never raise without explicit approval)
7. Create the Parity Test
tests/lite/parity/scenes/sceneN-my-feature.spec.ts
import { test, expect } from "@playwright/test";
import * as path from "path";
import { captureGolden, compareImages, getSceneConfig } from "../compare-utils";
const sceneConfig = getSceneConfig(23);
const REFERENCE_DIR = path.resolve(__dirname, "../../../../reference/lite/scene23-my-feature");
const GOLDEN_REF = path.join(REFERENCE_DIR, "babylon-ref-golden.png");
test("Scene 23 — My Feature matches Babylon.js reference", async ({ page }) => {
const browser = page.context().browser()!;
await captureGolden(browser, { sceneId: 23 });
await page.goto("/scene23.html");
await page.waitForFunction(() => document.querySelector("canvas")?.dataset.ready === "true", { timeout: 20_000 });
await page.waitForTimeout(500);
const screenshotPath = path.join(REFERENCE_DIR, "test-actual.png");
await page.locator("canvas").screenshot({ path: screenshotPath });
const full = compareImages(screenshotPath, GOLDEN_REF);
console.log(`Full image (${full.totalPixels} px): MAD=${full.mad.toFixed(3)}`);
expect(full.mad, `Full image MAD should be ≤ ${sceneConfig.maxMad}`).toBeLessThanOrEqual(sceneConfig.maxMad);
});
8. Run and Verify
# Run the parity test for your scene
pnpm exec playwright test tests/lite/parity/scenes/sceneN-my-feature.spec.ts
# Run the full suite to check for regressions
pnpm test
Checklist
-
lab/lite/sceneN.htmlcreated -
lab/lite/src/lite/sceneN.tscreated -
lab/lite/babylon-ref-sceneN.htmlcreated -
lab/lite/src/bjs/sceneN.tscreated -
reference/lite/sceneN-slug/babylon-ref-golden.pngcaptured -
lab/public/thumbnails/sceneN.jpgsaved (JPG, ≤720p) -
scene-config.jsonentry added -
tests/lite/parity/scenes/sceneN-slug.spec.tscreated - Parity test passes locally
- All existing tests still pass (
pnpm test)
Adding Plumbing Tests
Plumbing tests validate engine internals (dispose, material-swap, etc.) using Playwright + WebGPU.
- Create a test page in
lab/(HTML + TS) - Create the spec in
tests/lite/plumbing/ - Run:
pnpm exec playwright test tests/lite/plumbing/my-test.spec.ts
Note: CI uses Chrome's SwiftShader Vulkan backend for WebGPU — no real GPU needed.
Adding Unit Tests
For pure logic (shader composition, math) that doesn't need a browser:
- Create
tests/lite/unit/my-feature.test.ts - Use vitest:
describe,it,expect - Run:
pnpm exec vitest run
Code Quality
- Run
pnpm lintbefore submitting — must produce 0 errors, 0 warnings - Prefix intentionally unused variables with
_(e.g.,_light,_cam) - Use
import typefor type-only imports - Never raise bundle-size ceilings or MAD thresholds without explicit approval