Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
59b1056
feat(ffi): define_core_ffi! — single shared source for the non-physic…
proggeramlug Jun 12, 2026
799c698
feat(ffi): migrate all platforms to define_core_ffi!; add parity vali…
proggeramlug Jun 12, 2026
0fb98fb
chore: enforce 2000-line limit on source files (CI, ratcheting baseline)
proggeramlug Jun 12, 2026
b6c0c8f
fix(ffi): harden the Perry StringHeader ABI boundary
proggeramlug Jun 12, 2026
f9184fa
feat(physics): fixed-timestep stepping with interpolation; clamp engi…
proggeramlug Jun 12, 2026
73ee333
fix(audio): control/render split with lock-free command ring — kills …
proggeramlug Jun 12, 2026
42ecfe2
fix(windows): pass the audio renderer into wasapi_audio_thread
proggeramlug Jun 12, 2026
adb26d7
fix(resources): generational handles + the three real GPU leak paths
proggeramlug Jun 12, 2026
07da894
refactor(renderer): split texture management out of mod.rs
proggeramlug Jun 12, 2026
63c5c05
feat(renderer): back-to-front depth sorting for the translucent bucket
proggeramlug Jun 12, 2026
50fa460
docs(renderer): expand the Android single-mip exclusion rationale
proggeramlug Jun 12, 2026
5549f01
feat(renderer): Hi-Z occlusion culling (max-depth grid, async readback)
proggeramlug Jun 12, 2026
8c35cf9
feat(test): headless rendering + golden-image regression harness — ca…
proggeramlug Jun 12, 2026
576c02f
feat(renderer): raise light caps 4+16 → 8+256 (the audit's top graphi…
proggeramlug Jun 12, 2026
e2e6506
feat(api)!: consistency sweep — colors 0-255 everywhere, degrees ever…
proggeramlug Jun 12, 2026
7f7c042
feat(scene): LOD system — per-node detail variants selected by screen…
proggeramlug Jun 12, 2026
5a31774
feat(web): close the renderer-settings FFI gap; validator now gates w…
proggeramlug Jun 12, 2026
4fbe61d
refactor(web): render settings into render_settings.rs; silence wasm-…
proggeramlug Jun 12, 2026
c903483
refactor(renderer): split shaders.rs (5861 lines) into per-cluster mo…
proggeramlug Jun 12, 2026
dfaffcb
feat(audio): streamed music decode — OGG/MP3 no longer fully decoded …
proggeramlug Jun 12, 2026
32395a5
feat(assets): bloom-cook texture cooking + transparent cooked-DDS loa…
proggeramlug Jun 12, 2026
f6ae082
refactor(tools): split bloom-reference path-tracing core into tracer.rs
proggeramlug Jun 12, 2026
8e7315e
feat(renderer): render-graph migration cluster 1 — Hi-Z build + occlu…
proggeramlug Jun 12, 2026
f07c31b
refactor(renderer): extract SSR march + temporal denoiser into ssr_pa…
proggeramlug Jun 12, 2026
b04ba2c
refactor(renderer): extract the SSGI probe block into ssgi_pass.rs
proggeramlug Jun 12, 2026
4502754
refactor(renderer): extract the cascaded shadow pass into shadow_pass.rs
proggeramlug Jun 12, 2026
2ad12ea
refactor(renderer): extract the bloom chain into postfx_chain.rs
proggeramlug Jun 12, 2026
5fecf2b
refactor(renderer): extract scene-compose into postfx_chain.rs
proggeramlug Jun 12, 2026
39368f4
refactor(renderer): extract the post-FX tail into postfx_chain.rs
proggeramlug Jun 12, 2026
94c1e92
refactor(renderer): extract the HDR scene pass into scene_pass.rs
proggeramlug Jun 12, 2026
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
17 changes: 17 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,23 @@ jobs:
working-directory: native/shared
run: cargo test --release

# ---------------------------------------------------------------------------
# FFI parity: every function in package.json's perry.nativeLibrary manifest
# must be exported with matching arity by every platform crate (hand-written
# or via define_core_ffi!/define_physics_ffi!). This is the check whose
# absence let Android ship 60 functions behind (#59) and Windows ship the
# whole scene-graph surface as silent no-op stubs. Cheap (pure text parse),
# runs everywhere, gates merges.
# ---------------------------------------------------------------------------
ffi-parity:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: validate-ffi
run: node tools/validate-ffi.js
- name: file line limit (2000, ratcheting baseline)
run: node tools/check-file-lines.js

# ---------------------------------------------------------------------------
# Lint: rustfmt + clippy. Advisory while the codebase is still in flux.
# Flip `continue-on-error: false` once the lint baseline is clean.
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,6 @@ tools/unreal_reference/
tools/dump_dds/target/
tools/dump_dds/Cargo.lock
examples/intel-sponza/assets/outdoor.hdr

# Golden-test failure artifacts (written next to the golden on mismatch)
*.actual.png
66 changes: 66 additions & 0 deletions docs/migration-0.5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Migrating to Bloom 0.5

0.5 makes the API consistent in three places where conventions silently
diverged. Each change is breaking on purpose — the old inconsistencies
caused invisible bugs (colors that rendered white, rotations that were
60× too fast). All engine examples are already migrated and serve as
references.

## Surface colors are 0–255 everywhere

`setSceneNodeColor`, `setOutlineColor`, and the color part of
`setSceneNodeWaterMaterial` previously took 0–1 floats — the only places
in the API that did. They now take 0–255 like every `draw*` call and the
`Colors` presets.

```ts
// before // after
setSceneNodeColor(node, 0.75, 0.75, 0.7); setSceneNodeColor(node, 191, 191, 179);
setSceneNodeColor(node, c.r/255, c.g/255, …) setSceneNodeColor(node, c.r, c.g, c.b, c.a); // Colors presets now just work
```

Symptom of unmigrated code: scene nodes render almost black (values
divided twice).

**Unchanged:** light colors (`addDirectionalLight`, `addPointLight`)
stay 0–1 floats with a separate intensity — that's the radiometric
convention (Unity and Unreal do the same), and light color × intensity
can meaningfully exceed 1.0. The `*.world.json` format also keeps 0–1
tints (serialized data is versioned separately); the loader converts.

## Angles are degrees everywhere

`drawModelRotated`'s `rotY` was radians; `Camera2D.rotation` was degrees.
Everything user-facing is now degrees (the raylib convention).

```ts
// before // after
drawModelRotated(m, p, 1.0, Math.PI / 2, t); drawModelRotated(m, p, 1.0, 90, t);
```

Symptom of unmigrated code: models spin ~57× faster than intended.

**Unchanged:** physics angular velocity stays radians/sec (SI, matches
Jolt), and quaternions are quaternions.

## `Texture.handle` (was `Texture.id`)

`Texture` was the only resource type whose handle field was named `id`;
`Sound`, `Music`, `Font`, and `Model` all use `handle`.

```ts
// before // after
myAtlas.id myAtlas.handle
```

## Also in 0.5 (non-breaking)

- `physics.step(world, dt)` is now fixed-timestep with an accumulator
and returns the interpolation alpha; `physics.stepVariable` is the
old exact-dt behavior. See docs/physics.md "Stepping".
- Stale handles (use-after-free/destroy) now fail lookups instead of
aliasing whatever object reused the slot.
- `*Raw` function variants are documented `@internal` — they exist only
as a compiler workaround and will be removed.
- Coordinate system is now documented at the top of the physics and
scene modules: right-handed, Y-up, meters, SI units.
42 changes: 40 additions & 2 deletions docs/physics.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,50 @@ const ball = physics.createBody(world, ballShape, {
// 4. Call optimizeBroadphase once after initial body setup.
physics.optimizeBroadphase(world);

// 5. In your game loop:
physics.step(world, 1 / 60);
// 5. In your game loop — pass the real frame delta; step() runs the
// simulation at a fixed rate internally (see "Stepping" below):
physics.step(world, deltaTime);
const pos = physics.getBodyPosition(ball);
// ... read positions and render sprites / meshes at those transforms
```

## Stepping

`physics.step(world, deltaTime)` is **fixed-timestep**: it accumulates the
wall-clock delta and advances the solver in whole steps of 1/60 s
(configurable via `setFixedTimestep(world, hz, maxSteps?)`). Variable-size
solver steps feed frame hitches straight into the constraint solver —
tunneling and joint explosions on any slow frame — so the accumulator is
the default, with two protections baked in:

- A single frame's contribution is clamped to 0.25 s (debugger pauses and
OS hitches produce one slowed-down frame, not minutes of catch-up).
- At most `maxSteps` (default 4) fixed steps run per frame; surplus
backlog is dropped. The simulation slows down instead of spiraling.

`step` returns the **interpolation alpha**: how far the carried remainder
sits between the last two physics states. Two ways to use it:

```typescript
// Easiest: let the engine blend body transforms for rendering.
physics.setInterpolation(world, true);
runGame((dt) => {
physics.step(world, dt);
const p = physics.getBodyPosition(ball); // already smoothed
});

// Manual: interpolate game-side state with the same alpha.
const alpha = physics.step(world, dt); // or physics.getStepAlpha(world)
```

With interpolation on, `getBodyPosition`/`getBodyRotation` return the
blended state; physics queries (raycasts, overlaps, contacts) always see
the raw simulation state.

Exact-dt stepping is still available as `physics.stepVariable(world, dt)`
for code that drives its own accumulator — it trades stability for
control.

## Character controllers (Tier 2)

For player movement, use `CharacterVirtual` — Jolt's kinematic controller with
Expand Down
2 changes: 1 addition & 1 deletion examples/pbr-spheres/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ for (let row = 0; row < GRID_N; row = row + 1) {

const node = createSceneNode();
attachModelToNode(node, sphereHandle, 0);
setSceneNodeColor(node, BASE_R, BASE_G, BASE_B);
setSceneNodeColor(node, BASE_R * 255, BASE_G * 255, BASE_B * 255);
setSceneNodePbr(node, roughness, metallic);
setSceneNodeCastShadow(node, false);
setSceneNodeReceiveShadow(node, false);
Expand Down
8 changes: 4 additions & 4 deletions examples/renderer-test/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ function placeSphere(
roughness: number, metalness: number,
): number {
const node = placeNode(sphereHandle, 0, px, py, pz, scale, scale, scale);
setSceneNodeColor(node, cr, cg, cb);
setSceneNodeColor(node, cr * 255, cg * 255, cb * 255);
setSceneNodePbr(node, roughness, metalness);
return node;
}
Expand All @@ -243,7 +243,7 @@ function placeCube(
roughness: number, metalness: number,
): number {
const node = placeNode(cubeHandle, 0, px, py, pz, sx, sy, sz);
setSceneNodeColor(node, cr, cg, cb);
setSceneNodeColor(node, cr * 255, cg * 255, cb * 255);
setSceneNodePbr(node, roughness, metalness);
// Thin horizontal slabs (floors) should receive but not cast
// shadows — otherwise they fill the shadow map with their own
Expand Down Expand Up @@ -374,7 +374,7 @@ function setupWater(): void {
updateSceneNodeGeometry(waterNode, wv, wi);
const wm = mat4Translate(mat4Identity(), { x: 0, y: 0.2, z: cz });
setSceneNodeTransform(waterNode, wm);
setSceneNodeWaterMaterial(waterNode, 0.15, 1.5, 0.1, 0.3, 0.5, 0.6);
setSceneNodeWaterMaterial(waterNode, 0.15, 1.5, 26, 77, 128, 153);
setSceneNodeReceiveShadow(waterNode, true);

// Rocks / objects sticking out of water
Expand Down Expand Up @@ -457,7 +457,7 @@ function setupThinGeometry(): void {
const x = cx - 5.5 + i * 1.0;
const node = createSceneNode();
attachModelToNode(node, cubeHandle, 0);
setSceneNodeColor(node, 0.6, 0.6, 0.62);
setSceneNodeColor(node, 153, 153, 158);
setSceneNodePbr(node, 0.3, 1.0);
setSceneNodeCastShadow(node, true);
setSceneNodeReceiveShadow(node, true);
Expand Down
6 changes: 3 additions & 3 deletions examples/scene-graph/interactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ let selectedWallId: string | null = null;
const floorHandle = createSceneNode();
const floorPolygon = [-10, -10, 10, -10, 10, 10, -10, 10];
extrudePolygon(floorHandle, floorPolygon, 0.02);
setSceneNodeColor(floorHandle, 0.85, 0.85, 0.82, 1.0);
setSceneNodeColor(floorHandle, 217, 217, 209, 255);
setSceneNodePbr(floorHandle, 0.7, 0.0);

// Handle → wall ID lookup (for picking)
Expand Down Expand Up @@ -112,9 +112,9 @@ function wallSystem(dt: number): void {

// Color based on selection
if (wall.id === selectedWallId) {
setSceneNodeColor(wall.handle, 0.3, 0.6, 1.0, 1.0);
setSceneNodeColor(wall.handle, 77, 153, 255, 255);
} else {
setSceneNodeColor(wall.handle, 0.95, 0.95, 0.92, 1.0);
setSceneNodeColor(wall.handle, 242, 242, 235, 255);
}
setSceneNodePbr(wall.handle, 0.8, 0.0);

Expand Down
8 changes: 4 additions & 4 deletions examples/scene-graph/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,10 @@ const floorIdx: number[] = [0, 1, 2, 0, 2, 3];
updateSceneNodeGeometry(floor, floorVerts, floorIdx);

// Set materials
setSceneNodeColor(wall1, 0.95, 0.95, 0.92, 1.0);
setSceneNodeColor(wall2, 0.92, 0.92, 0.88, 1.0);
setSceneNodeColor(wall3, 0.90, 0.90, 0.86, 1.0);
setSceneNodeColor(floor, 0.7, 0.7, 0.65, 1.0);
setSceneNodeColor(wall1, 242, 242, 235, 255);
setSceneNodeColor(wall2, 235, 235, 224, 255);
setSceneNodeColor(wall3, 230, 230, 219, 255);
setSceneNodeColor(floor, 179, 179, 166, 255);

// Set PBR properties
setSceneNodePbr(wall1, 0.8, 0.0);
Expand Down
4 changes: 2 additions & 2 deletions examples/scene-graph/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ function slabSystem(dt: number): void {
extrudePolygon(handle, flat, slab.elevation);

// Gray floor material
setSceneNodeColor(handle, 0.75, 0.75, 0.70, 1.0);
setSceneNodeColor(handle, 191, 191, 179, 255);
setSceneNodePbr(handle, 0.6, 0.0);

clearDirty(id);
Expand Down Expand Up @@ -203,7 +203,7 @@ function wallSystem(dt: number): void {
}

// White wall material
setSceneNodeColor(handle, 0.95, 0.95, 0.92, 1.0);
setSceneNodeColor(handle, 242, 242, 235, 255);
setSceneNodePbr(handle, 0.8, 0.0);

clearDirty(id);
Expand Down
6 changes: 3 additions & 3 deletions examples/scene-graph/shadows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ setDirectionalLight(0.5, 1.0, 0.3, 255, 240, 220, 0.7);
const floor = createSceneNode();
const floorPoly = [-5, -5, 5, -5, 5, 5, -5, 5];
extrudePolygon(floor, floorPoly, 0.05);
setSceneNodeColor(floor, 0.8, 0.78, 0.72, 1.0);
setSceneNodeColor(floor, 204, 199, 184, 255);
setSceneNodePbr(floor, 0.7, 0.0);

// Walls
Expand All @@ -69,7 +69,7 @@ function makeWall(sx: number, sz: number, ex: number, ez: number): void {
sx - nx, sz - nz,
];
extrudePolygon(node, poly, 3.0);
setSceneNodeColor(node, 0.95, 0.93, 0.88, 1.0);
setSceneNodeColor(node, 242, 237, 224, 255);
setSceneNodePbr(node, 0.85, 0.0);
}

Expand All @@ -88,7 +88,7 @@ function makeBox(cx: number, cy: number, cz: number, w: number, h: number, d: nu
// Offset Y via transform
const t = mat4Translate(mat4Identity(), 0, cy, 0);
setSceneNodeTransform(node, t);
setSceneNodeColor(node, r, g, b, 1.0);
setSceneNodeColor(node, r * 255, g * 255, b * 255, 255);
setSceneNodePbr(node, 0.6, 0.0);
}

Expand Down
Loading
Loading