Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d9976b9
fix(profiler): honest readouts + crash-triage kit + test repairs (rou…
Jul 4, 2026
61c8f92
fix(render): round-2 visual correctness — cutout transmission, 2D gam…
Jul 4, 2026
039a0e1
fix(render): release the depth-snapshot transient — 8 MB/frame textur…
Jul 4, 2026
05c5916
feat(render): EN-021 — SSR + IBL specular exclusive ownership
Jul 4, 2026
944445c
feat(gi): EN-023 groundwork — SDF card lookup fixed, WSRC ground bounce
Jul 4, 2026
4ec1698
feat(render): setPresentMode FFI — Fifo/Mailbox/Immediate at runtime
Jul 4, 2026
60ba704
feat(text): rasterize glyphs at physical resolution (crisp 4K HUD)
Jul 4, 2026
73ef591
feat(render): EN-022 engine half — real prev-frame transforms for mat…
Jul 4, 2026
c6542ce
Merge branch 'fix/transient-depth-leak' into round2/integration
Jul 4, 2026
56626c7
Merge branch 'feat/en021-ssr-ibl-ownership' into round2/integration
Jul 4, 2026
076c1e2
Merge branch 'feat/en023-gi-sw-cards' into round2/integration
Jul 4, 2026
18a06fa
Merge branch 'feat/present-mode-ffi' into round2/integration
Jul 4, 2026
86788f3
Merge branch 'feat/en022-material-velocity' into round2/integration
Jul 4, 2026
0f2fe8b
fix(EN-020): tail-pad Perry FFI strings; add in-engine crash reporting
Jul 4, 2026
54385c2
fix(EN-020): numeric profiler ABI - Perry split/parseFloat overread t…
Jul 4, 2026
93abb23
docs: consolidate specs, sync all docs to post-round-2 + EN-020 reality
Jul 6, 2026
713e2a1
fix(render): fullscreen lag + flicker root causes (2026-07-06 investi…
Jul 6, 2026
85de283
fix(taa): bound history chroma at 3x the luma variance band
Jul 6, 2026
43d944c
feat(windows): native F12 screenshot hotkey
Jul 6, 2026
7c5f20d
fix(render): tame unsharp-mask halo/shimmer on silhouettes
Jul 6, 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
94 changes: 88 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,17 @@ Bloom is a native TypeScript game engine compiled by [Perry](../../perry/perry)
## Build Commands

```bash
# Native (macOS example)
# Native (macOS)
cd native/macos && cargo build --release

# Native (Windows — set INTERPROCEDURAL_OPTIMIZATION=OFF so the Jolt
# cmake build doesn't trip over LTO with the MSVC toolchain)
INTERPROCEDURAL_OPTIMIZATION=OFF cargo build --release --manifest-path native/windows/Cargo.toml

# Tests: unit + golden-image suite (run before any renderer PR)
cd native/shared && cargo test --release
BLOOM_UPDATE_GOLDEN=1 cargo test --release # regenerate goldens — commit ONLY the ones your change intentionally moved (mean tol 2, outliers 1%)

# Web/WASM
cd native/web && cargo check --target wasm32-unknown-unknown
./native/web/build.sh [game.ts] # Full web build pipeline
Expand All @@ -21,6 +29,13 @@ cd native/shared && cargo check # nativ
cd native/shared && cargo check --target wasm32-unknown-unknown --no-default-features --features web # WASM
```

After an engine-only rebuild, a consuming game does NOT relink by
itself: `perry compile` skips the link if the game's `main.ts` is
untouched — touch it first. After ANY change to `package.json`'s
native-function manifest, also delete the game's `.perry-cache/`.
Games should link with `perry compile … --debug-symbols` so `main.pdb`
lands next to the exe (see `docs/crash-triage-windows.md`).

## Architecture

```
Expand All @@ -36,10 +51,26 @@ src/ TypeScript API (compiled by Perry)
physics/ Jolt-backed rigid + soft bodies, character, vehicles

native/ Rust implementations (one crate per platform)
shared/ Cross-platform core (~7000 lines)
- renderer.rs: wgpu + WGSL shaders (2D/3D, shadows, post-FX)
- audio.rs: platform-agnostic mixer
- text_renderer.rs: fontdue-based text
shared/ Cross-platform core
- renderer/: wgpu 29 renderer — deferred MRT
(hdr/material/velocity/albedo), TSR upscaling,
cascaded shadows, SSR, Lumen-class GI (screen
probes; HW ray-query / SDF-clipmap / Hi-Z tiers,
mesh cards, WSRC), material system with a
5-bind-group ABI (shaders/material_abi.wgsl),
transient texture pool, 2D draw layer.
WGSL lives as strings in renderer/shaders/*.rs
- profiler.rs: CPU+GPU pass profiler (enabling it
inserts a blocking per-frame GPU sync — never
benchmark with it on)
- audio/: control/render split over a lock-free
SPSC ring (mod/render/stream/decode)
- text_renderer.rs: fontdue text, rasterized at
physical resolution
- string_header.rs: Perry string ABI (read its
header comment before touching FFI strings)
- ffi_core/: define_core_ffi! macro — the shared
FFI surface each platform crate instantiates
- physics_jolt.rs: JoltPhysics wrapper (native only)
- jolt_sys.rs: C ABI bindings to bloom_jolt shim
- textures.rs, models.rs, scene.rs, etc.
Expand All @@ -63,6 +94,41 @@ Each platform implements ~230 `bloom_*` FFI functions declared in `package.json`

String parameters are `i64` on native (Perry StringHeader pointers) and NaN-boxed string IDs on web (converted by JS glue layer).

### Hard-won FFI rules (violating these produced real shipped bugs)

- **Every new native MUST be declared in `package.json`'s manifest.**
Perry silently no-ops undeclared functions — no error, the call just
does nothing. Consumers must clear `.perry-cache/` after manifest
changes.
- **Never return packed text for per-frame parsing (EN-020).** Perry
0.5.x `split()`/`parseFloat()` read past their own slice allocations;
parsing a delimited FFI string every frame is a crash lottery the
engine cannot pad away. Cross numbers as `f64` FFIs; strings cross
whole and get drawn, not parsed (see the `bloom_profiler_row_*`
numeric ABI and `docs/tickets.md` § EN-020).
- Engine→Perry strings go through `string_header::alloc_perry_string`
(tail-padded); Perry→engine through `str_from_header` (validated,
never UB). Don't hand-roll headers.
- **Perry 0.5.1171 i64-array regression:** passing a TS `number[]` to
an `i64` param is broken — use the all-f64 scratch pattern
(`bloom_mesh_scratch_*`) like createMesh does.
- Engine TS in `src/` is compiled by Perry too, so Perry codegen quirks
apply here as well (no reachable `throw`, explicit object keys in
returns — the shooter's `docs/perry-quirks.md` is the reference list).

### Runtime/debug behavior worth knowing

- `panic = "abort"` on all native targets: a Rust panic prints to
stderr then fast-fails (0xC0000409). An AV instead triggers the
Windows crate's crash filter, which prints `main.exe+RVA` and writes
a minidump to the game's `tools/.testout/dumps/` —
`docs/crash-triage-windows.md` is the triage runbook.
- **WGSL compiles at runtime** (`create_shader_module`), so
`cargo build` does NOT validate shaders — boot a game (or the golden
suite) after any WGSL edit.
- `begin_frame` resets per-frame lighting state; renderer FFI calls
before `initWindow` panic with "Engine not initialized".

Physics FFI (~110 of the ~230) is generated by the `define_physics_ffi!` macro in `native/shared/src/physics_jolt.rs`; each platform crate invokes it once to re-export the full surface. On web the same surface is wasm_bindgen wrappers forwarding to `native/web/jolt_bridge.js`, which drives JoltPhysics.js.

## Web/WASM Target
Expand All @@ -86,7 +152,11 @@ The web crate exposes `_str` variants (accepting `&str`) and `_bytes` variants (
| `package.json` | FFI function manifest + per-platform build config |
| `src/core/index.ts` | Core API: window, input, drawing, `runGame()` |
| `src/physics/index.ts` | Physics API (see `docs/physics.md` for architecture) |
| `native/shared/src/renderer.rs` | wgpu renderer (2D/3D, ~2600 lines) |
| `native/shared/src/renderer/mod.rs` | Renderer root: frame orchestration, lighting, GI plumbing |
| `native/shared/src/renderer/material_system.rs` | Material draws, per-slot UBOs, prev-frame model history (EN-022) |
| `native/shared/src/renderer/shaders/` | All WGSL (core scene, ssgi/SSR, gi, post) as Rust strings |
| `native/shared/shaders/material_abi.wgsl` | The 5-bind-group material ABI games compile against |
| `native/shared/src/string_header.rs` | Perry string ABI both directions (padded alloc + validated read) |
| `native/shared/src/engine.rs` | EngineState with timing, frame callbacks |
| `native/shared/src/physics_jolt.rs` | Jolt handle registries + `define_physics_ffi!` macro |
| `native/third_party/bloom_jolt/` | C++ shim wrapping JoltPhysics behind a C ABI |
Expand All @@ -96,3 +166,15 @@ The web crate exposes `_str` variants (accepting `&str`) and `_bytes` variants (
| `native/web/index.html` | Engine-only standalone page (no game); creates `__bloomReady` + loads bloom_glue.js |
| `native/web/splice_game.py` | Splices the Bloom bootstrap into Perry's self-contained WASM HTML, gating `bootPerryWasm()` on `__bloomReady` |
| `native/web/build.sh` | Build script: wasm-pack + wasm-opt + Perry compile + splice/assembly |

## Where the truth lives

- `bloom-renderer-spec-v2.md` — the (only) renderer plan; its header
carries the as-built status vs. plan.
- `docs/tickets.md` — EN-xxx work items with current status.
- `docs/perf/` — per-feature design docs + `lumen-roadmap.md` for GI.
- `docs/crash-triage-windows.md` — native-fault runbook (the engine
self-reports crashes since 2026-07).
- The Bloom Shooter (`../shooter`) is the flagship consumer; its
`CLAUDE.md` + `docs/perry-quirks.md` document the Perry-side rules
games (and engine `src/` TS) must follow.
Loading
Loading