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.html created
  • lab/lite/src/lite/sceneN.ts created
  • lab/lite/babylon-ref-sceneN.html created
  • lab/lite/src/bjs/sceneN.ts created
  • reference/lite/sceneN-slug/babylon-ref-golden.png captured
  • lab/public/thumbnails/sceneN.jpg saved (JPG, ≤720p)
  • scene-config.json entry added
  • tests/lite/parity/scenes/sceneN-slug.spec.ts created
  • 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.

  1. Create a test page in lab/ (HTML + TS)
  2. Create the spec in tests/lite/plumbing/
  3. 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:

  1. Create tests/lite/unit/my-feature.test.ts
  2. Use vitest: describe, it, expect
  3. Run: pnpm exec vitest run

Code Quality

  • Run pnpm lint before submitting — must produce 0 errors, 0 warnings
  • Prefix intentionally unused variables with _ (e.g., _light, _cam)
  • Use import type for type-only imports
  • Never raise bundle-size ceilings or MAD thresholds without explicit approval