Skip to content

fix(web): EN-063 - bloom-shared compiles without models3d again#119

Merged
proggeramlug merged 1 commit into
mainfrom
fix/en-063-web-models3d
Jul 17, 2026
Merged

fix(web): EN-063 - bloom-shared compiles without models3d again#119
proggeramlug merged 1 commit into
mainfrom
fix/en-063-web-models3d

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Fixes EN-063: build-web has been red on every main run since PR #107 (EN-055) — models.rs grew ungated references to anim_mixer, gltf, image_dds, and StagedModel, and the wasm web-feature check is the one build that runs without models3d. PRs #108#117 all merged over the inherited red.

The fix, two parts:

  1. Un-gate anim_mixer in lib.rs — the mixer is pure per-instance state embedded in the always-compiled ModelAnimation struct, no heavy deps. models3d exists to drop gltf/image_dds from pure-2D binaries (EN-014), not the mixer math.
  2. Gate what actually touches the optional deps: the four ModelManager loader methods, the five load_gltf* functions, and their ten private helpers get #[cfg(feature = "models3d")]. Every FFI call site already had feature-off stubs in ffi_core/models.rs, so no surface changes in any configuration.

Verified locally: wasm --no-default-features --features web (the exact failing CI command) compiles clean; wasm web,models3d (the native/web shape) and native default-features both still compile. Remaining warnings on the web config are pre-existing renderer ones, untouched here.

This PR's own build-web check doubles as the proof — it should be the first green one since #104.

Also marks EN-063 shipped in tickets.md (the 'make the red gate loud' follow-up stays open — five PRs merged over this without anyone noticing).

Summary by CodeRabbit

  • New Features

    • Added synchronous model staging for environments without worker-thread support.
    • Added web support for ragdolls, model workflows, profiler data, HDR environment data, and expanded input handling.
    • Improved browser asset preloading and WebGPU initialization.
  • Bug Fixes

    • Fixed web gamepad, touch, keyboard, pointer-lock, and mouse input behavior.
    • Fixed cached skinned-model rendering and texture/material assignments.
    • Improved window-close handling across desktop and web builds.
    • Improved browser rendering compatibility, depth behavior, color output, and shader sampling.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@proggeramlug, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 51071cdc-98dc-40d8-97a9-dc766a0c568b

📥 Commits

Reviewing files that changed from the base of the PR and between 050a69f and fa8e9d4.

📒 Files selected for processing (2)
  • native/web/jolt_bridge.js
  • tools/validate-ffi.js
📝 Walkthrough

Walkthrough

This change enables wasm feature-gated model and ragdoll paths, adapts renderer bindings and formats for WebGPU, expands browser FFI and input support, adds web model and physics parity APIs, and implements clean shutdown behavior across native platforms.

Changes

Platform and renderer compatibility

Layer / File(s) Summary
Feature gates and shutdown behavior
native/shared/*, native/{linux,macos,windows}/src/lib.rs, docs/tickets.md
Models3d loading code is feature-gated, wasm ragdoll support is enabled, browser error logging is added, and native window-close functions set the engine shutdown flag.
Wasm renderer adaptations
native/shared/src/renderer/*, native/shared/src/textures.rs
Scene inputs are folded into wasm per-frame bindings, output formats are normalized, wasm depth behavior is adjusted, shader sampling uses explicit LODs, and RGBA8 decoding is added.

Web integration

Layer / File(s) Summary
Bootstrap and input ABI
native/web/bloom_glue.js, native/web/src/{lib.rs,input_ffi.rs,physics_ffi.rs}, package.json, tools/validate-ffi.js
Browser startup now prefetches assets and waits for WebGPU initialization; FFI wrappers, input injection, pointer lock, audio, shutdown, and ABI validation are updated.
Model and parity APIs
native/web/src/{lib.rs,material_ffi.rs,parity_ffi.rs}, src/{index.ts,models/index.ts,math/index.ts}
Web exports add staged model bytes, mesh and texture-array scratch paths, profiler accessors, HDR and scene operations, model animation updates, cache fixes, and synchronous model staging.
Jolt and ragdoll integration
native/web/jolt_bridge.js, native/web/src/{physics_ffi.rs,ragdoll_ffi.rs}
Jolt query filters are cached, runtime API variants are supported, six-degree-of-freedom constraints are exposed, and wasm ragdoll creation, update, push, and release flows are implemented.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the web compile fix for EN-063 and matches the main change.
Description check ✅ Passed It includes the fix summary, local verification, and follow-up notes, though it doesn't use the exact template headings.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/en-063-web-models3d

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (1)
native/shared/src/textures.rs (1)

67-76: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse this helper in the existing loaders to prevent decode-path drift.

load_texture and load_image duplicate the same load_from_memory(...).to_rgba8() logic. Routing those methods through decode_rgba8 would keep the native and web decoding behavior aligned as supported formats or conversion handling evolve.

Proposed refactor
 pub fn decode_rgba8(file_data: &[u8]) -> Option<(Vec<u8>, u32, u32)> {
     let img = image::load_from_memory(file_data).ok()?.to_rgba8();
     let (w, h) = img.dimensions();
     Some((img.into_raw(), w, h))
 }

Then replace the duplicated decode blocks in load_texture and load_image with calls to this helper.

🤖 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 `@native/shared/src/textures.rs` around lines 67 - 76, Update load_texture and
load_image to use the existing decode_rgba8 helper instead of directly calling
image::load_from_memory(...).to_rgba8(). Preserve each loader’s current handling
of decoded pixels and dimensions while removing the duplicated decode logic.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@native/web/bloom_glue.js`:
- Around line 316-327: Protect the localStorage.getItem calls in
imports.bloom_file_exists and imports.bloom_read_file with exception handling so
storage access failures do not escape the FFI wrapper. On failure, continue
through the existing manifest/syncFetchBytes fallback for bloom_file_exists and
syncFetchText fallback for bloom_read_file.
- Around line 267-277: Build texture arrays atomically in
native/web/bloom_glue.js within bloom_create_texture_array_from_files: if any
layer file is missing or decoding fails, reset the pending array and return 0
immediately instead of continuing. In native/web/src/parity_ffi.rs within the
corresponding texture-array construction logic, reject the entire array on any
layer dimension mismatch rather than skipping that layer; apply the required
change at both listed sites.
- Around line 105-106: Move the setupDomBridge() call from the initial DOM
wiring section to after the renderer initialization wait completes, ensuring
ENGINE is initialized before ResizeObserver can invoke bloom_resize. Keep the
existing DOM bridge setup behavior unchanged once initialization has finished.
- Around line 121-126: Require WebGPU support for at least 19 sampled textures
across all device setup paths: in native/web/bloom_glue.js lines 121-126,
request the adapter with the required 19-texture capability and fail fast when
unsupported; in native/web/src/lib.rs lines 143-151 and
native/shared/src/attach.rs lines 147-191, clamp
max_sampled_textures_per_shader_stage to a minimum of 19 before device creation.

In `@native/web/jolt_bridge.js`:
- Around line 927-946: Release the temporary Jolt settings wrapper immediately
after Create() in the six-DOF constraint flow. In native/web/jolt_bridge.js
lines 1039-1055, destroy the heightfield settings wrapper after its data has
been copied. In lines 1088-1108, update world teardown to evict that world's
entries from queryFilterCache so cached filters do not outlive the world.

In `@native/web/src/ragdoll_ffi.rs`:
- Around line 41-61: Make ragdoll activation transactional in
native/web/src/ragdoll_ffi.rs:41-61, native/web/src/ragdoll_ffi.rs:65-69, and
native/web/src/ragdoll_ffi.rs:99-146 by rejecting invalid or already-active
slots before calling the planning/build flow, then rolling back every body and
constraint created so far whenever capsule/body creation or a required SixDOF
constraint fails. Return 0.0 on any failure, and only mark the slot successful
after all planned attachments complete.
- Around line 65-94: Release each temporary capsule shape handle returned by
jb_shape_capsule after jb_body_create completes, including when body creation
fails, while preserving the existing zero-shape handling and body setup flow in
the builds loop.

---

Nitpick comments:
In `@native/shared/src/textures.rs`:
- Around line 67-76: Update load_texture and load_image to use the existing
decode_rgba8 helper instead of directly calling
image::load_from_memory(...).to_rgba8(). Preserve each loader’s current handling
of decoded pixels and dimensions while removing the duplicated decode logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7cb1b3f5-40e9-48c6-80b0-9fbaad066ed7

📥 Commits

Reviewing files that changed from the base of the PR and between 57de189 and 050a69f.

⛔ Files ignored due to path filters (3)
  • native/shared/Cargo.lock is excluded by !**/*.lock
  • native/web/Cargo.lock is excluded by !**/*.lock
  • native/windows/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (31)
  • docs/tickets.md
  • native/linux/src/lib.rs
  • native/macos/src/lib.rs
  • native/shared/Cargo.toml
  • native/shared/src/engine.rs
  • native/shared/src/ffi.rs
  • native/shared/src/lib.rs
  • native/shared/src/models.rs
  • native/shared/src/renderer/material_pipeline.rs
  • native/shared/src/renderer/material_system.rs
  • native/shared/src/renderer/mod.rs
  • native/shared/src/renderer/scene_pass.rs
  • native/shared/src/renderer/shaders/ao.rs
  • native/shared/src/renderer/shaders/core.rs
  • native/shared/src/renderer/shaders/post.rs
  • native/shared/src/renderer/shaders/ssgi.rs
  • native/shared/src/textures.rs
  • native/web/bloom_glue.js
  • native/web/jolt_bridge.js
  • native/web/src/input_ffi.rs
  • native/web/src/lib.rs
  • native/web/src/material_ffi.rs
  • native/web/src/parity_ffi.rs
  • native/web/src/physics_ffi.rs
  • native/web/src/ragdoll_ffi.rs
  • native/windows/src/lib.rs
  • package.json
  • src/index.ts
  • src/math/index.ts
  • src/models/index.ts
  • tools/validate-ffi.js

Comment thread native/web/bloom_glue.js Outdated
Comment on lines 105 to 106
// 5. DOM wiring (input + HiDPI canvas sizing + audio unlock).
setupDomBridge();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Install the DOM bridge only after renderer initialization.

ResizeObserver can fire while requestAdapter() is awaited, calling bloom_resize before ENGINE is initialized. Move setupDomBridge() below the initialization wait.

Proposed fix
-  setupDomBridge();

   bloom.bloom_init_window(w, h, 0, 0);
   const deadline = performance.now() + 15000;
   while (bloom.bloom_is_initialized() < 0.5) {
     ...
   }
+  setupDomBridge();

Also applies to: 131-139

🤖 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 `@native/web/bloom_glue.js` around lines 105 - 106, Move the setupDomBridge()
call from the initial DOM wiring section to after the renderer initialization
wait completes, ensuring ENGINE is initialized before ResizeObserver can invoke
bloom_resize. Keep the existing DOM bridge setup behavior unchanged once
initialization has finished.

Comment thread native/web/bloom_glue.js Outdated
Comment on lines +121 to +126
const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' })
.catch(() => null);
if (!adapter) {
throw new Error('No WebGPU adapter available. Check chrome://gpu — '
+ 'hardware acceleration must be enabled and the GPU not blocklisted.');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C4 '19 textures|max_sampled_textures_per_shader_stage|requestAdapter' native/web native/shared

Repository: Bloom-Engine/engine

Length of output: 3542


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '110,170p' native/web/bloom_glue.js
printf '\n--- native/web/src/lib.rs ---\n'
sed -n '120,170p' native/web/src/lib.rs
printf '\n--- native/shared/src/attach.rs ---\n'
sed -n '150,180p' native/shared/src/attach.rs

Repository: Bloom-Engine/engine

Length of output: 7031


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- native/web/src/lib.rs call sites ---'
rg -n -C3 'request_device|required_limits|max_sampled_textures_per_shader_stage|bloom_init_window|attach::|attach\(' native/web native/shared

printf '\n%s\n' '--- native/shared/src/attach.rs outline ---'
ast-grep outline native/shared/src/attach.rs --view expanded

printf '\n%s\n' '--- native/web/src/lib.rs outline ---'
ast-grep outline native/web/src/lib.rs --view expanded

Repository: Bloom-Engine/engine

Length of output: 37605


Require a 19-texture minimum for the WebGPU device request.

  • native/web/bloom_glue.js:121-126 should fail fast when the adapter cannot support the renderer’s 19 sampled textures.
  • native/web/src/lib.rs:143-151 and native/shared/src/attach.rs:147-191 should clamp max_sampled_textures_per_shader_stage to at least 19 instead of inheriting the adapter/default value and letting pipeline creation fail later.
📍 Affects 2 files
  • native/web/bloom_glue.js#L121-L126 (this comment)
  • native/web/src/lib.rs#L143-L151
🤖 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 `@native/web/bloom_glue.js` around lines 121 - 126, Require WebGPU support for
at least 19 sampled textures across all device setup paths: in
native/web/bloom_glue.js lines 121-126, request the adapter with the required
19-texture capability and fail fast when unsupported; in native/web/src/lib.rs
lines 143-151 and native/shared/src/attach.rs lines 147-191, clamp
max_sampled_textures_per_shader_stage to a minimum of 19 before device creation.

Comment thread native/web/bloom_glue.js Outdated
Comment on lines +267 to +277
imports.bloom_create_texture_array_from_files = (paths, format, mipLevels) => {
if (!bloom.bloom_texture_array_files_push) return 0;
bloom.bloom_texture_array_files_reset();
for (const raw of String(paths).split(',')) {
const p = raw.trim();
if (!p) continue;
const data = syncFetchBytes(p);
if (data) bloom.bloom_texture_array_files_push(data);
else console.warn('[texarray] missing layer file:', p);
}
return bloom.bloom_texture_array_files_commit(format, mipLevels);

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 | ⚡ Quick win

Build texture arrays atomically to preserve layer indices.

Both paths silently omit invalid layers, compacting subsequent layers into different indices and causing materials to sample the wrong textures.

  • native/web/bloom_glue.js#L267-L277: abort, reset, and return 0 when a file is missing or decoding fails.
  • native/web/src/parity_ffi.rs#L176-L199: reject the complete array on any dimension mismatch instead of skipping that layer.
📍 Affects 2 files
  • native/web/bloom_glue.js#L267-L277 (this comment)
  • native/web/src/parity_ffi.rs#L176-L199
🤖 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 `@native/web/bloom_glue.js` around lines 267 - 277, Build texture arrays
atomically in native/web/bloom_glue.js within
bloom_create_texture_array_from_files: if any layer file is missing or decoding
fails, reset the pending array and return 0 immediately instead of continuing.
In native/web/src/parity_ffi.rs within the corresponding texture-array
construction logic, reject the entire array on any layer dimension mismatch
rather than skipping that layer; apply the required change at both listed sites.

Comment thread native/web/bloom_glue.js Outdated
Comment on lines +316 to +327
imports.bloom_file_exists = (path) => {
const p = String(path);
if (localStorage.getItem(LS_PREFIX + p) !== null) return 1;
if (manifestPaths) return manifestPaths.has(p) ? 1 : 0;
return syncFetchBytes(p) !== null ? 1 : 0;
};
imports.bloom_read_file = (path) => {
const v = localStorage.getItem(LS_PREFIX + String(path));
return v === null ? '' : v; // plain string; Perry re-encodes via wrapFfiForI64
const p = String(path);
const v = localStorage.getItem(LS_PREFIX + p);
if (v !== null) return v;
const text = syncFetchText(p);
return text === null ? '' : text; // plain string; Perry re-encodes via wrapFfiForI64

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard localStorage reads against access failures.

Unlike writes, getItem() is unprotected. Restricted storage access will throw through the FFI wrapper and break fileExists/readFile; catch it and continue with the asset fallback.

🤖 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 `@native/web/bloom_glue.js` around lines 316 - 327, Protect the
localStorage.getItem calls in imports.bloom_file_exists and
imports.bloom_read_file with exception handling so storage access failures do
not escape the FFI wrapper. On failure, continue through the existing
manifest/syncFetchBytes fallback for bloom_file_exists and syncFetchText
fallback for bloom_read_file.

Comment thread native/web/jolt_bridge.js Outdated
Comment thread native/web/src/ragdoll_ffi.rs Outdated
Comment on lines +41 to +61
let eng = engine();

let builds = {
let Some(a) = eng.models.get_animation(anim) else {
return 0.0;
};
// 12 bodies / 0.38 radius fraction — same sweet spot as native (spine
// + limbs; past that you buy fingers, which cost solver time and buy
// jitter).
bloom_shared::ragdoll::plan(
a,
scale as f32,
[px as f32, py as f32, pz as f32],
rot_y as f32,
12,
0.38,
)
};
if builds.is_empty() {
return 0.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.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Make ragdoll activation transactional. Validate the slot before allocation, and if any required body or joint fails, destroy all constraints and bodies created so far before returning 0.0.

  • native/web/src/ragdoll_ffi.rs#L41-L61: reject invalid or active slots before planning/building.
  • native/web/src/ragdoll_ffi.rs#L65-L69: abort and roll back when a capsule or body cannot be created.
  • native/web/src/ragdoll_ffi.rs#L99-L146: abort and roll back when a required SixDOF constraint returns zero; return success only after a complete attachment.
📍 Affects 1 file
  • native/web/src/ragdoll_ffi.rs#L41-L61 (this comment)
  • native/web/src/ragdoll_ffi.rs#L65-L69
  • native/web/src/ragdoll_ffi.rs#L99-L146
🤖 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 `@native/web/src/ragdoll_ffi.rs` around lines 41 - 61, Make ragdoll activation
transactional in native/web/src/ragdoll_ffi.rs:41-61,
native/web/src/ragdoll_ffi.rs:65-69, and native/web/src/ragdoll_ffi.rs:99-146 by
rejecting invalid or already-active slots before calling the planning/build
flow, then rolling back every body and constraint created so far whenever
capsule/body creation or a required SixDOF constraint fails. Return 0.0 on any
failure, and only mark the slot successful after all planned attachments
complete.

Comment thread native/web/src/ragdoll_ffi.rs Outdated
Comment on lines +65 to +94
for b in builds.iter() {
let shape = jb_shape_capsule(b.half_height as f64, b.radius as f64);
if shape == 0.0 {
bodies.push(0.0);
continue;
}
let q = bloom_shared::ragdoll::quat_from_mat(&b.world);
let body = jb_body_create(
world,
shape,
2.0,
b.world[3][0] as f64,
b.world[3][1] as f64,
b.world[3][2] as f64,
q[0] as f64,
q[1] as f64,
q[2] as f64,
q[3] as f64,
1.0,
);
if body != 0.0 {
jb_body_set_friction(body, 0.8); // corpses do not skate
jb_body_set_restitution(body, 0.02); // they do not bounce
jb_body_set_linear_damping(body, 0.20);
// Angular damping does most of the work of making this read as a
// body rather than a rag.
jb_body_set_angular_damping(body, 0.75);
}
bodies.push(body);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
ast-grep outline native/web/src/physics_ffi.rs \
  --match 'jb_shape_release|bloom_physics_shape_release'
rg -n -C8 'registerShape|shapeRelease|bodyCreate' native/web/jolt_bridge.js

Repository: Bloom-Engine/engine

Length of output: 6918


Release the temporary capsule shape handle after jb_body_create.
These bodies keep their own shape reference; dropping the capsule handle here prevents the JS shape map from growing forever.

🤖 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 `@native/web/src/ragdoll_ffi.rs` around lines 65 - 94, Release each temporary
capsule shape handle returned by jb_shape_capsule after jb_body_create
completes, including when body creation fails, while preserving the existing
zero-shape handling and body setup flow in the builds loop.

build-web has been red on every main run since PR #107 (EN-055): models.rs
grew ungated references to anim_mixer, gltf, image_dds and StagedModel,
all of which only exist when the models3d feature is on - and the wasm
web-feature check is the one build that runs without it.

Two-part fix:
- UN-gate anim_mixer in lib.rs. The mixer is pure per-instance state
  embedded in the always-compiled ModelAnimation struct - no heavy deps.
  Gating it on models3d was wrong from the start; models3d exists to drop
  gltf/gltf_json/image_dds from pure-2D binaries, not the mixer math.
- Move everything that actually touches the optional deps - the
  load_gltf* family and its private helpers, ~1200 lines - into a new
  models_gltf.rs, included from models.rs as a single models3d-gated
  module. The four ModelManager loader methods carry the same cfg. Every
  FFI call site already had feature-off stubs in ffi_core/models.rs, so
  no surface changes in any configuration.

The split also takes models.rs from 2346 to ~1090 lines, retiring its
EN-052 grandfather entry in tools/file-lines-baseline.json (the ratchet
script's own instruction once a file drops under the 2000 limit).

Verified locally: wasm --no-default-features --features web (the failing
CI command) compiles clean; wasm web,models3d (the native/web shape) and
native default-features both still compile. tickets.md EN-063 marked
shipped; its loud-gate follow-up stays open.
@proggeramlug
proggeramlug force-pushed the fix/en-063-web-models3d branch from fa8e9d4 to 7b8c907 Compare July 17, 2026 10:59
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Rebuilt from origin/main — the previous head of this branch was accidentally based on the unmerged feat/en-063-web-3d-shooter branch (shared working copy; HEAD had moved overnight), so this PR was dragging the whole web-3D feature along with the build-web fix. Now it contains only the EN-063 compile fix.

The stray commit fa8e9d4 (CodeRabbit fixes for the Jolt bridge + FFI validator — PR #118 content) was force-pushed away from this branch but is preserved verbatim on rescue/fa8e9d4-coderabbit-jolt-fix for cherry-picking onto #118.

Also changed in the rebuild: instead of 19 scattered #[cfg] attributes, the gltf loader block (~1,200 lines) moved to a new models_gltf.rs behind a single models3d gate — which also drops models.rs under the 2,000-line limit and retires its ratchet grandfather entry.

@proggeramlug
proggeramlug merged commit 64cfec3 into main Jul 17, 2026
9 checks passed
@proggeramlug
proggeramlug deleted the fix/en-063-web-models3d branch July 17, 2026 11:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant