Skip to content
Merged
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
28 changes: 21 additions & 7 deletions build-web.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,44 @@
# assets/ game sprites, sounds, levels (copied from ./assets)
#
# Flags:
# --skip-bloom Reuse existing ../engine/native/web/pkg/ (skip wasm-pack)
# --skip-bloom Reuse the existing pkg/ (skip wasm-pack)
# --engine-src Build bloom_web from the ../engine checkout instead of node_modules
# --serve After build, launch `python3 -m http.server 8080` in dist/web

set -euo pipefail

JUMP_DIR="$(cd "$(dirname "$0")" && pwd)"
# Build bloom_web from the SAME engine the game compiles against (node_modules),
# so the runtime glue and the game WASM's FFI ABI never skew. Falls back to a
# sibling engine source checkout (../engine) for local engine development.
BLOOM_WEB="$JUMP_DIR/node_modules/@bloomengine/engine/native/web"
[ -d "$BLOOM_WEB" ] || BLOOM_WEB="$JUMP_DIR/../engine/native/web"
OUT="$JUMP_DIR/dist/web"

skip_bloom=false
engine_src=false
serve=false
for arg in "$@"; do
case "$arg" in
--skip-bloom) skip_bloom=true ;;
--engine-src) engine_src=true ;;
--serve) serve=true ;;
-h|--help) sed -n '2,15p' "$0"; exit 0 ;;
-h|--help) sed -n '2,16p' "$0"; exit 0 ;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

--help prints shell code, not just the header.

The comment header ends at line 13 (--serve); line 14 is blank and line 15 is set -euo pipefail. sed -n '2,16p' therefore dumps set -euo pipefail (and trailing blank lines) into the help output. Narrow the range to the comment block.

🩹 Proposed fix
-    -h|--help) sed -n '2,16p' "$0"; exit 0 ;;
+    -h|--help) sed -n '2,13p' "$0"; exit 0 ;;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
-h|--help) sed -n '2,16p' "$0"; exit 0 ;;
-h|--help) sed -n '2,13p' "$0"; exit 0 ;;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@build-web.sh` at line 28, Update the --help branch in the build script’s
argument handling to print only the comment header, changing the sed range from
lines 2–16 to the range ending at line 13 so shell commands are excluded.

*) echo "unknown flag: $arg" >&2; exit 2 ;;
esac
done

# Default: build bloom_web from the SAME engine the game compiles against
# (node_modules), so the runtime glue and the game WASM's FFI ABI never skew.
#
# --engine-src builds from the sibling ../engine checkout instead. Needed for any
# engine-side change that isn't in a published release yet: the npm tarball ships a
# prebuilt pkg/ that works, but its bundled Rust *source* does not currently compile
# (its web crate calls 3D model APIs the shared crate it ships with doesn't expose),
# so a from-source rebuild of node_modules fails. Verify the result end-to-end
# (tools/headless-check.js) — nothing else guards ABI skew on this path.
if $engine_src; then
BLOOM_WEB="$JUMP_DIR/../engine/native/web"
else
BLOOM_WEB="$JUMP_DIR/node_modules/@bloomengine/engine/native/web"
[ -d "$BLOOM_WEB" ] || BLOOM_WEB="$JUMP_DIR/../engine/native/web"
fi
Comment on lines +42 to +47

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the patch target and how perry/cp consume the engine on the --engine-src path.
fd -a 'patch_engine_web.js' --exec sed -n '1,40p' {}
fd -a 'build-web.sh' --exec sed -n '60,80p' {}

Repository: Bloom-Engine/jump

Length of output: 2586


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- build-web.sh outline ---'
ast-grep outline build-web.sh --view expanded || true

echo '--- build-web.sh relevant slice ---'
nl -ba build-web.sh | sed -n '1,120p'

echo '--- search for engine-src and patch_engine_web usage ---'
rg -n --no-heading --line-number --context 2 'engine-src|patch_engine_web|perry compile|BLOOM_WEB' .

Repository: Bloom-Engine/jump

Length of output: 287


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- engine package.json web target ---'
fd -a 'package.json' node_modules/@bloomengine/engine 2>/dev/null | head -n 5 | xargs -r -I{} sh -c 'echo "FILE: {}"; nl -ba "{}" | sed -n "1,220p"'

echo '--- sibling engine checkout hint ---'
fd -a 'package.json' ../engine 2>/dev/null | head -n 5 | xargs -r -I{} sh -c 'echo "FILE: {}"; nl -ba "{}" | sed -n "1,220p"'

Repository: Bloom-Engine/jump

Length of output: 194


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- locate build-web.sh in repo ---'
git ls-files | rg '^build-web\.sh$|/build-web\.sh$'

echo '--- inspect node_modules engine manifest if present in repo ---'
fd -a 'package.json' 'node_modules/@bloomengine/engine' 2>/dev/null | head -n 1 | xargs -r -I{} sh -c 'echo "FILE: {}"; nl -ba "{}" | sed -n "1,160p"'

Repository: Bloom-Engine/jump

Length of output: 268


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- build-web.sh lines 1-140 ---'
sed -n '1,140p' build-web.sh | cat -n

printf '%s\n' '--- patch_engine_web.js ---'
sed -n '1,120p' store/tools/patch_engine_web.js | cat -n

Repository: Bloom-Engine/jump

Length of output: 6665


--engine-src should keep the engine build on one checkout. This path rebuilds pkg/ from ../engine, but patch_engine_web.js and perry compile still read node_modules/@bloomengine/engine. That leaves dist/web/pkg and the game WASM built against different engine ABIs. Point the patch/compile steps at the sibling checkout too, or keep all three steps on node_modules.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@build-web.sh` around lines 42 - 47, Ensure the --engine-src path uses the
sibling engine checkout consistently: update the patch_engine_web.js and perry
compile steps to read from the same $BLOOM_WEB/../engine source used to rebuild
pkg/, rather than node_modules/@bloomengine/engine. Alternatively, use
node_modules for all three steps, but do not mix engine checkouts.


if [ ! -d "$BLOOM_WEB" ]; then
echo "error: bloom web crate not found at $BLOOM_WEB" >&2
exit 1
Expand Down
79 changes: 77 additions & 2 deletions web/bloom_ffi.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,12 +279,81 @@ function buildFfiImports() {
return ffi;
}

// ----- GPU backend selection -----
// The engine needs a WebGPU adapter. Two things make its absence far nastier than it
// should be, and this function handles both.
//
// 1. Having the WebGPU *API* is not the same as having an *adapter*. Chrome ships
// `navigator.gpu` on by default (since 113), but `requestAdapter()` still returns
// null when graphics acceleration is off, the GPU is blocklisted, or the session is
// a VM / remote desktop. Left alone, the engine hits that inside wgpu and panics
// ("No WebGPU/WebGL adapter found"), boot stalls, and the user sees a wall of Rust
// stack traces and a bogus "init timed out". So probe for a real adapter here and
// fail with something a human can act on.
//
// 2. wgpu picks its backend once, in `Instance::new`, on a single question: is
// `navigator.gpu` defined? (wgpu-29 src/api/instance.rs:74). If it is, wgpu commits
// to WebGPU and never looks at GL again — so the WebGL2 backend we compile in (the
// `webgl` feature) can never engage on its own. Hiding `navigator.gpu` from the wasm
// (an own `undefined` property shadows the Navigator.prototype getter; wgpu reads
// that as absent) is what routes wgpu to wgpu-core → WebGL2.
//
// That WebGL2 route is plumbed and reachable, but it cannot render yet: Bloom's
// Renderer::new eagerly builds 14 compute pipelines, storage buffers and a 64KB joint
// uniform, and WebGL2 (GLES 3.0) has no compute shaders or storage buffers at all — it
// dies during renderer init. Flipping this to true is only meaningful once the engine
// gains a downlevel/2D-only init path. Until then an automatic fallback would trade one
// crash for another, so we send the user a clear message instead. `?renderer=gl` still
// forces the attempt for engine development.
const WEBGL2_FALLBACK_SUPPORTED = false;

async function selectGpuBackend() {
const forceGl = new URLSearchParams(location.search).get("renderer") === "gl";

if (!forceGl && navigator.gpu) {
let adapter = null;
try {
adapter = await navigator.gpu.requestAdapter({ powerPreference: "high-performance" });
} catch (e) {
console.warn("navigator.gpu.requestAdapter() threw:", e);
}
if (adapter) return "webgpu";
}

if (!forceGl && !WEBGL2_FALLBACK_SUPPORTED) return "none";

// Route wgpu away from WebGPU and onto wgpu-core → WebGL2.
if ("gpu" in navigator) {
try {
Object.defineProperty(navigator, "gpu", { value: undefined, configurable: true });
} catch (e) {
console.warn("could not hide navigator.gpu; wgpu will stay on WebGPU:", e);
return "none";
}
}
return document.createElement("canvas").getContext("webgl2") ? "webgl2" : "none";
}

// ----- Boot sequence -----
async function boot() {
const loading = document.getElementById("loading");
if (loading) loading.textContent = "Initializing Bloom engine...";

await init(); // wasm-bindgen init

const backend = await selectGpuBackend();
if (backend === "none") {
throw new Error(
"This browser can't provide a WebGPU adapter, which Bloom Jump needs to render.\n\n" +
"WebGPU itself is enabled by default in Chrome 113+ — an adapter usually goes missing " +
"because graphics acceleration is switched off (chrome://settings/system), the GPU is " +
"blocklisted, or the browser is running in a VM or remote session. " +
"Open chrome://gpu and look at the WebGPU row for the specific reason."
);
}
console.log(`[bloom] renderer: ${backend}`);
if (loading) loading.textContent = `Initializing Bloom engine (${backend})...`;

installInputListeners();
installAudioBridge();

Expand All @@ -299,7 +368,7 @@ async function boot() {
bloom.bloom_init_window(w, h, 0, 0);
const readyDeadline = Date.now() + 10_000;
while (bloom.bloom_is_initialized() < 0.5) {
if (Date.now() > readyDeadline) throw new Error("bloom engine init timed out");
if (Date.now() > readyDeadline) throw new Error(`bloom engine init timed out (${backend})`);
await new Promise((r) => setTimeout(r, 16));
}

Expand All @@ -319,5 +388,11 @@ async function boot() {
boot().catch((err) => {
console.error("Boot failed:", err, "\nstack:", err?.stack);
const root = document.getElementById("loading") || document.body;
root.textContent = "Boot error: " + (err?.message || err);
root.textContent = err?.message || String(err);
// The no-GPU message is multi-line prose meant to be read, not a one-line status.
root.style.whiteSpace = "pre-wrap";
root.style.maxWidth = "36rem";
root.style.margin = "2rem auto";
root.style.lineHeight = "1.5";
root.style.textAlign = "left";
});