@bornengine/engine 0.4.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +231 -0
  3. package/native/android/Cargo.lock +1848 -0
  4. package/native/android/Cargo.toml +24 -0
  5. package/native/android/src/lib.rs +702 -0
  6. package/native/ios/Cargo.lock +1690 -0
  7. package/native/ios/Cargo.toml +32 -0
  8. package/native/ios/src/lib.rs +1267 -0
  9. package/native/linux/Cargo.lock +3279 -0
  10. package/native/linux/Cargo.toml +29 -0
  11. package/native/linux/src/lib.rs +1331 -0
  12. package/native/macos/Cargo.lock +3310 -0
  13. package/native/macos/Cargo.toml +46 -0
  14. package/native/macos/src/lib.rs +1302 -0
  15. package/native/shared/Cargo.lock +1899 -0
  16. package/native/shared/Cargo.toml +62 -0
  17. package/native/shared/assets/default_font.ttf +0 -0
  18. package/native/shared/build.rs +270 -0
  19. package/native/shared/shaders/common/clouds.wgsl +122 -0
  20. package/native/shared/shaders/common/fog.wgsl +16 -0
  21. package/native/shared/shaders/common/foliage_wind.wgsl +98 -0
  22. package/native/shared/shaders/common/imposter.wgsl +112 -0
  23. package/native/shared/shaders/common/pbr.wgsl +186 -0
  24. package/native/shared/shaders/common/shadows.wgsl +186 -0
  25. package/native/shared/shaders/common/sky.wgsl +8 -0
  26. package/native/shared/shaders/common/tonemap.wgsl +25 -0
  27. package/native/shared/shaders/impulse_field.wgsl +57 -0
  28. package/native/shared/shaders/material_abi.wgsl +383 -0
  29. package/native/shared/shaders/materials/test_minimal.wgsl +42 -0
  30. package/native/shared/src/anim_mixer.rs +61 -0
  31. package/native/shared/src/attach.rs +263 -0
  32. package/native/shared/src/audio/decode.rs +123 -0
  33. package/native/shared/src/audio/mod.rs +863 -0
  34. package/native/shared/src/audio/render.rs +892 -0
  35. package/native/shared/src/audio/spsc.rs +156 -0
  36. package/native/shared/src/audio/stream.rs +226 -0
  37. package/native/shared/src/custom_shaders.rs +104 -0
  38. package/native/shared/src/decals.rs +245 -0
  39. package/native/shared/src/drs.rs +211 -0
  40. package/native/shared/src/engine.rs +261 -0
  41. package/native/shared/src/ffi.rs +116 -0
  42. package/native/shared/src/ffi_core/assets.rs +388 -0
  43. package/native/shared/src/ffi_core/audio_ffi.rs +184 -0
  44. package/native/shared/src/ffi_core/draw.rs +334 -0
  45. package/native/shared/src/ffi_core/game_loop.rs +577 -0
  46. package/native/shared/src/ffi_core/input.rs +234 -0
  47. package/native/shared/src/ffi_core/mod.rs +127 -0
  48. package/native/shared/src/ffi_core/models.rs +1154 -0
  49. package/native/shared/src/ffi_core/ragdoll_ffi.rs +261 -0
  50. package/native/shared/src/ffi_core/scene.rs +626 -0
  51. package/native/shared/src/ffi_core/vfx.rs +212 -0
  52. package/native/shared/src/ffi_core/visual.rs +691 -0
  53. package/native/shared/src/frame_callbacks.rs +122 -0
  54. package/native/shared/src/geometry.rs +236 -0
  55. package/native/shared/src/handles.rs +182 -0
  56. package/native/shared/src/input.rs +448 -0
  57. package/native/shared/src/jolt_sys.rs +822 -0
  58. package/native/shared/src/lib.rs +55 -0
  59. package/native/shared/src/models.rs +1093 -0
  60. package/native/shared/src/models_gltf.rs +1280 -0
  61. package/native/shared/src/particles.rs +391 -0
  62. package/native/shared/src/physics_jolt.rs +1908 -0
  63. package/native/shared/src/picking.rs +298 -0
  64. package/native/shared/src/postfx.rs +345 -0
  65. package/native/shared/src/profiler.rs +492 -0
  66. package/native/shared/src/ragdoll.rs +474 -0
  67. package/native/shared/src/renderer/atmosphere_lut.rs +573 -0
  68. package/native/shared/src/renderer/brdf_lut.rs +154 -0
  69. package/native/shared/src/renderer/draw2d.rs +143 -0
  70. package/native/shared/src/renderer/formats.rs +822 -0
  71. package/native/shared/src/renderer/froxel.rs +421 -0
  72. package/native/shared/src/renderer/gi_bake.rs +653 -0
  73. package/native/shared/src/renderer/graph.rs +462 -0
  74. package/native/shared/src/renderer/hiz.rs +269 -0
  75. package/native/shared/src/renderer/hot_reload.rs +390 -0
  76. package/native/shared/src/renderer/impulse_field.rs +456 -0
  77. package/native/shared/src/renderer/lighting.rs +154 -0
  78. package/native/shared/src/renderer/material_instancing.rs +171 -0
  79. package/native/shared/src/renderer/material_pipeline.rs +700 -0
  80. package/native/shared/src/renderer/material_system.rs +1996 -0
  81. package/native/shared/src/renderer/material_system_tests.rs +601 -0
  82. package/native/shared/src/renderer/material_system_wasm.rs +41 -0
  83. package/native/shared/src/renderer/mod.rs +12556 -0
  84. package/native/shared/src/renderer/model_draw.rs +641 -0
  85. package/native/shared/src/renderer/occlusion.rs +429 -0
  86. package/native/shared/src/renderer/planar_pass.rs +593 -0
  87. package/native/shared/src/renderer/planar_reflection.rs +499 -0
  88. package/native/shared/src/renderer/post_pass.rs +249 -0
  89. package/native/shared/src/renderer/postfx_chain.rs +728 -0
  90. package/native/shared/src/renderer/pt_pass.rs +577 -0
  91. package/native/shared/src/renderer/scene_pass.rs +607 -0
  92. package/native/shared/src/renderer/shader_include.rs +205 -0
  93. package/native/shared/src/renderer/shader_library.rs +135 -0
  94. package/native/shared/src/renderer/shaders/ao.rs +570 -0
  95. package/native/shared/src/renderer/shaders/core.rs +1243 -0
  96. package/native/shared/src/renderer/shaders/env.rs +907 -0
  97. package/native/shared/src/renderer/shaders/gi.rs +810 -0
  98. package/native/shared/src/renderer/shaders/mod.rs +19 -0
  99. package/native/shared/src/renderer/shaders/post.rs +1558 -0
  100. package/native/shared/src/renderer/shaders/pt.rs +1859 -0
  101. package/native/shared/src/renderer/shaders/ssgi.rs +1586 -0
  102. package/native/shared/src/renderer/shadow_pass.rs +731 -0
  103. package/native/shared/src/renderer/ssgi_pass.rs +392 -0
  104. package/native/shared/src/renderer/ssr_pass.rs +188 -0
  105. package/native/shared/src/renderer/texture_store.rs +473 -0
  106. package/native/shared/src/renderer/transient.rs +591 -0
  107. package/native/shared/src/renderer/types.rs +941 -0
  108. package/native/shared/src/renderer/util.rs +152 -0
  109. package/native/shared/src/scene.rs +1362 -0
  110. package/native/shared/src/sdf_cache.rs +274 -0
  111. package/native/shared/src/shadows.rs +1036 -0
  112. package/native/shared/src/staging.rs +102 -0
  113. package/native/shared/src/string_header.rs +266 -0
  114. package/native/shared/src/text_renderer.rs +502 -0
  115. package/native/shared/src/textures.rs +197 -0
  116. package/native/tvos/Cargo.lock +1693 -0
  117. package/native/tvos/Cargo.toml +36 -0
  118. package/native/tvos/metal-patched/Cargo.toml +178 -0
  119. package/native/tvos/metal-patched/LICENSE-APACHE +201 -0
  120. package/native/tvos/metal-patched/LICENSE-MIT +25 -0
  121. package/native/tvos/metal-patched/src/acceleration_structure.rs +667 -0
  122. package/native/tvos/metal-patched/src/acceleration_structure_pass.rs +108 -0
  123. package/native/tvos/metal-patched/src/argument.rs +366 -0
  124. package/native/tvos/metal-patched/src/blitpass.rs +102 -0
  125. package/native/tvos/metal-patched/src/buffer.rs +71 -0
  126. package/native/tvos/metal-patched/src/capturedescriptor.rs +76 -0
  127. package/native/tvos/metal-patched/src/capturemanager.rs +113 -0
  128. package/native/tvos/metal-patched/src/commandbuffer.rs +192 -0
  129. package/native/tvos/metal-patched/src/commandqueue.rs +44 -0
  130. package/native/tvos/metal-patched/src/computepass.rs +107 -0
  131. package/native/tvos/metal-patched/src/constants.rs +152 -0
  132. package/native/tvos/metal-patched/src/counters.rs +119 -0
  133. package/native/tvos/metal-patched/src/depthstencil.rs +190 -0
  134. package/native/tvos/metal-patched/src/device.rs +2134 -0
  135. package/native/tvos/metal-patched/src/drawable.rs +39 -0
  136. package/native/tvos/metal-patched/src/encoder.rs +2041 -0
  137. package/native/tvos/metal-patched/src/heap.rs +281 -0
  138. package/native/tvos/metal-patched/src/indirect_encoder.rs +344 -0
  139. package/native/tvos/metal-patched/src/lib.rs +657 -0
  140. package/native/tvos/metal-patched/src/library.rs +902 -0
  141. package/native/tvos/metal-patched/src/mps.rs +575 -0
  142. package/native/tvos/metal-patched/src/pipeline/compute.rs +475 -0
  143. package/native/tvos/metal-patched/src/pipeline/mod.rs +71 -0
  144. package/native/tvos/metal-patched/src/pipeline/render.rs +762 -0
  145. package/native/tvos/metal-patched/src/renderpass.rs +443 -0
  146. package/native/tvos/metal-patched/src/resource.rs +182 -0
  147. package/native/tvos/metal-patched/src/sampler.rs +165 -0
  148. package/native/tvos/metal-patched/src/sync.rs +178 -0
  149. package/native/tvos/metal-patched/src/texture.rs +352 -0
  150. package/native/tvos/metal-patched/src/types.rs +90 -0
  151. package/native/tvos/metal-patched/src/vertexdescriptor.rs +250 -0
  152. package/native/tvos/src/audio_backend.rs +197 -0
  153. package/native/tvos/src/lib.rs +1891 -0
  154. package/native/visionos/Cargo.lock +1693 -0
  155. package/native/visionos/Cargo.toml +40 -0
  156. package/native/visionos/src/audio_backend.rs +197 -0
  157. package/native/visionos/src/lib.rs +1887 -0
  158. package/native/watchos/Cargo.lock +16 -0
  159. package/native/watchos/Cargo.toml +19 -0
  160. package/native/watchos/shaders/bloom_postfx.metal +99 -0
  161. package/native/watchos/src/BloomWatchApp.swift +1267 -0
  162. package/native/watchos/src/BloomWatchAudio.swift +179 -0
  163. package/native/watchos/src/audio.rs +55 -0
  164. package/native/watchos/src/draw_list.rs +229 -0
  165. package/native/watchos/src/ffi_stubs.rs +915 -0
  166. package/native/watchos/src/ffi_stubs_manual.rs +35 -0
  167. package/native/watchos/src/lib.rs +1124 -0
  168. package/native/watchos/src/models.rs +746 -0
  169. package/native/watchos/src/postfx.rs +95 -0
  170. package/native/watchos/src/scene.rs +534 -0
  171. package/native/watchos/src/textures.rs +184 -0
  172. package/native/web/Cargo.lock +1657 -0
  173. package/native/web/Cargo.toml +43 -0
  174. package/native/web/bloom_glue.js +695 -0
  175. package/native/web/build.sh +131 -0
  176. package/native/web/index.html +35 -0
  177. package/native/web/jolt_bridge.js +1519 -0
  178. package/native/web/src/input_ffi.rs +286 -0
  179. package/native/web/src/lib.rs +1796 -0
  180. package/native/web/src/material_ffi.rs +710 -0
  181. package/native/web/src/parity_ffi.rs +343 -0
  182. package/native/web/src/physics_ffi.rs +643 -0
  183. package/native/web/src/ragdoll_ffi.rs +250 -0
  184. package/native/web/src/render_settings.rs +98 -0
  185. package/native/windows/Cargo.lock +1815 -0
  186. package/native/windows/Cargo.toml +68 -0
  187. package/native/windows/src/lib.rs +1486 -0
  188. package/package.json +4279 -0
  189. package/src/audio/index.ts +315 -0
  190. package/src/core/colors.ts +63 -0
  191. package/src/core/index.ts +1206 -0
  192. package/src/core/keys.ts +63 -0
  193. package/src/core/types.ts +104 -0
  194. package/src/index.ts +171 -0
  195. package/src/math/index.ts +516 -0
  196. package/src/mobile/index.ts +294 -0
  197. package/src/models/index.ts +1258 -0
  198. package/src/physics/index.ts +1134 -0
  199. package/src/scene/index.ts +698 -0
  200. package/src/shapes/index.ts +120 -0
  201. package/src/text/index.ts +48 -0
  202. package/src/textures/index.ts +187 -0
  203. package/src/vfx/index.ts +191 -0
  204. package/src/world/index.ts +24 -0
  205. package/src/world/loader.ts +423 -0
  206. package/src/world/prefab.ts +217 -0
  207. package/src/world/render.ts +172 -0
  208. package/src/world/saver.ts +108 -0
  209. package/src/world/serialize.ts +301 -0
  210. package/src/world/terrain.ts +355 -0
  211. package/src/world/types.ts +160 -0
  212. package/src/world/validate.ts +319 -0
  213. package/src/world/version.ts +114 -0
@@ -0,0 +1,261 @@
1
+ //! EN-025 — ragdoll FFI.
2
+ //!
3
+ //! Section of [`define_core_ffi!`](crate::define_core_ffi).
4
+ //!
5
+ //! The shape of this API is deliberate. A ragdoll is not something a game
6
+ //! *configures*; it is something a game *triggers*, once, at the moment of
7
+ //! death, and then forgets about. So: one call to build it from a skeleton the
8
+ //! engine already has, one call to fire it with the killing impulse, one call
9
+ //! per frame to pull the pose back out, one to put it away.
10
+ //!
11
+ //! Everything else — which bones get bodies, how the capsules are sized, how the
12
+ //! joints are limited, how the simulated bodies map back onto skinning matrices
13
+ //! — is the engine's problem, because getting any of it wrong produces a corpse
14
+ //! that looks *nearly* right, which is worse than none.
15
+
16
+ #[doc(hidden)]
17
+ #[macro_export]
18
+ macro_rules! __bloom_ffi_ragdoll {
19
+ () => {
20
+
21
+ // bloom_ragdoll_create — allocate a slot. Returns a 1-based handle.
22
+ #[cfg(all(feature = "models3d", feature = "jolt", not(target_arch = "wasm32")))]
23
+ #[no_mangle]
24
+ pub extern "C" fn bloom_ragdoll_create() -> f64 {
25
+ $crate::ffi::guard("bloom_ragdoll_create", move || {
26
+ engine().ragdolls.create() as f64
27
+ })
28
+ }
29
+ #[cfg(not(all(feature = "models3d", feature = "jolt", not(target_arch = "wasm32"))))]
30
+ #[no_mangle]
31
+ pub extern "C" fn bloom_ragdoll_create() -> f64 { 0.0 }
32
+
33
+ // bloom_ragdoll_activate [EN-025]
34
+ //
35
+ // Build the bodies from the model's CURRENT pose and let go. Call it on
36
+ // the frame the thing dies, with the same (scale, position, yaw) you
37
+ // last passed to bloom_anim_update — that transform is what bridges
38
+ // model space and world space, and it is frozen here: the corpse's
39
+ // motion belongs to the bodies now, not to the dead enemy's position.
40
+ //
41
+ // 8 args (the ARM64 ceiling): the impulse direction and magnitude are
42
+ // set separately by bloom_ragdoll_push, so the killing blow's shove is
43
+ // a distinct decision from the corpse's construction.
44
+ #[cfg(all(feature = "models3d", feature = "jolt", not(target_arch = "wasm32")))]
45
+ #[no_mangle]
46
+ pub extern "C" fn bloom_ragdoll_activate(
47
+ rag: f64, anim: f64, world: f64,
48
+ scale: f64, px: f64, py: f64, pz: f64, rot_y: f64,
49
+ ) -> f64 {
50
+ $crate::ffi::guard("bloom_ragdoll_activate", move || {
51
+ let mut eng = engine();
52
+ let $crate::engine::EngineState {
53
+ models,
54
+ jolt,
55
+ ragdolls,
56
+ ..
57
+ } = &mut *eng;
58
+
59
+ let (builds, layer) = {
60
+ let Some(a) = models.get_animation(anim) else { return 0.0 };
61
+ // 12 bodies is the sweet spot for these skeletons: spine +
62
+ // limbs. Past that you start buying fingers, which cost
63
+ // solver time and buy jitter.
64
+ let builds = $crate::ragdoll::plan(
65
+ a, scale as f32,
66
+ [px as f32, py as f32, pz as f32],
67
+ rot_y as f32,
68
+ 12, // max bodies
69
+ // Chunkier capsules. Thin ones interpenetrate and let
70
+ // the corpse fold flat through itself; a limb should
71
+ // have some volume to rest ON.
72
+ 0.38, // capsule radius as a fraction of bone length
73
+ );
74
+ (builds, 1u32) // MOVING layer
75
+ };
76
+ if builds.is_empty() { return 0.0; }
77
+
78
+ // --- bodies
79
+ let mut bodies: Vec<f64> = Vec::with_capacity(builds.len());
80
+ for b in builds.iter() {
81
+ let shape = jolt.create_capsule_shape(b.half_height, b.radius);
82
+ if shape == 0.0 { bodies.push(0.0); continue; }
83
+ // Quaternion from the capsule's world basis.
84
+ let q = $crate::ragdoll::quat_from_mat(&b.world);
85
+ let body = jolt.create_body(
86
+ world, shape,
87
+ 2, // DYNAMIC
88
+ b.world[3][0], b.world[3][1], b.world[3][2],
89
+ q[0], q[1], q[2], q[3],
90
+ 0.0, 0.0, 0.0, // linear velocity
91
+ 0.0, 0.0, 0.0, // angular velocity
92
+ layer,
93
+ false, // sensor
94
+ true, // allow sleeping — corpses settle
95
+ false, // ccd: not worth it here
96
+ true, // start awake
97
+ 0.8, // friction: corpses do not skate
98
+ 0.02, // restitution: they do not bounce
99
+ // Angular damping does most of the work of making this
100
+ // read as a body rather than a rag: without it the limbs
101
+ // keep windmilling long after the thing has landed.
102
+ 0.20, 0.75, // lin / ang damping
103
+ 1.0, // gravity factor
104
+ 0.0, 0.0, 0.0, 0.0, // mass override + inertia
105
+ 0,
106
+ );
107
+ bodies.push(body);
108
+ }
109
+
110
+ // --- joints
111
+ //
112
+ // Tight-ish, and deliberately so. Generous limits let the limbs
113
+ // splay and the corpse settles into a puddle rather than a body;
114
+ // the first pass used ±1.2 rad and looked exactly like that.
115
+ // Twist (y) is held hardest, because an over-twisted limb is the
116
+ // single most obviously WRONG thing a ragdoll can do.
117
+ let rot_limits: [f32; 6] = [
118
+ -0.8, 0.8, // x — bend
119
+ -0.25, 0.25, // y — twist
120
+ -0.8, 0.8, // z — bend
121
+ ];
122
+ let mut constraints: Vec<f64> = Vec::new();
123
+ for (i, b) in builds.iter().enumerate() {
124
+ if b.parent_bone == usize::MAX { continue; }
125
+ let pa = bodies.get(b.parent_bone).copied().unwrap_or(0.0);
126
+ let pb = bodies.get(i).copied().unwrap_or(0.0);
127
+ if pa == 0.0 || pb == 0.0 { continue; }
128
+ let c = jolt.constraint_six_dof_locked_translation(
129
+ pa, pb,
130
+ b.anchor[0], b.anchor[1], b.anchor[2],
131
+ b.anchor[0], b.anchor[1], b.anchor[2],
132
+ rot_limits,
133
+ true, // anchors given in world space
134
+ );
135
+ if c != 0.0 { constraints.push(c); }
136
+ }
137
+
138
+ let Some(a) = models.get_animation(anim) else { return 0.0 };
139
+ let Some(r) = ragdolls.get_mut(rag as u32) else { return 0.0 };
140
+ r.attach(&builds, &bodies, constraints, a,
141
+ scale as f32, [px as f32, py as f32, pz as f32], rot_y as f32);
142
+ 1.0
143
+ })
144
+ }
145
+ #[cfg(not(all(feature = "models3d", feature = "jolt", not(target_arch = "wasm32"))))]
146
+ #[no_mangle]
147
+ pub extern "C" fn bloom_ragdoll_activate(_r: f64, _a: f64, _w: f64, _s: f64, _x: f64, _y: f64, _z: f64, _ry: f64) -> f64 { 0.0 }
148
+
149
+ // bloom_ragdoll_push — the killing blow. Applied to every body, so the
150
+ // whole corpse is thrown rather than one limb being yanked off.
151
+ #[cfg(all(feature = "models3d", feature = "jolt", not(target_arch = "wasm32")))]
152
+ #[no_mangle]
153
+ pub extern "C" fn bloom_ragdoll_push(rag: f64, dx: f64, dy: f64, dz: f64, impulse: f64) {
154
+ $crate::ffi::guard("bloom_ragdoll_push", move || {
155
+ let mut eng = engine();
156
+ let $crate::engine::EngineState { ragdolls, jolt, .. } = &mut *eng;
157
+ let Some(r) = ragdolls.get(rag as u32) else { return };
158
+ if !r.active { return }
159
+ let bodies = r.bodies();
160
+ if bodies.is_empty() { return }
161
+ // Spread the impulse over the bodies so a 12-bone corpse and a
162
+ // 4-bone one take off at the same speed.
163
+ let per = (impulse as f32) / (bodies.len() as f32);
164
+ for b in bodies {
165
+ jolt.body_add_impulse(
166
+ b, dx as f32 * per, dy as f32 * per, dz as f32 * per);
167
+ }
168
+ })
169
+ }
170
+ #[cfg(not(all(feature = "models3d", feature = "jolt", not(target_arch = "wasm32"))))]
171
+ #[no_mangle]
172
+ pub extern "C" fn bloom_ragdoll_push(_r: f64, _x: f64, _y: f64, _z: f64, _i: f64) {}
173
+
174
+ // bloom_ragdoll_update — read the simulated bodies back into the model's
175
+ // joint matrices and upload them. Call once per frame per active
176
+ // ragdoll, then drawModel() as usual. `dt` only ages the ragdoll (the
177
+ // physics world is stepped by the game).
178
+ #[cfg(all(feature = "models3d", feature = "jolt", not(target_arch = "wasm32")))]
179
+ #[no_mangle]
180
+ pub extern "C" fn bloom_ragdoll_update(rag: f64, anim: f64, dt: f64) -> f64 {
181
+ $crate::ffi::guard("bloom_ragdoll_update", move || {
182
+ let mut eng = engine();
183
+ let $crate::engine::EngineState {
184
+ ragdolls,
185
+ jolt,
186
+ models,
187
+ renderer,
188
+ ..
189
+ } = &mut *eng;
190
+
191
+ let (bodies, scale, pos, rot) = {
192
+ let Some(r) = ragdolls.get(rag as u32) else { return 0.0 };
193
+ if !r.active { return 0.0 }
194
+ let (s, p, ry) = r.upload_params();
195
+ (r.bodies(), s, p, ry)
196
+ };
197
+
198
+ let mut world: Vec<[[f32; 4]; 4]> = Vec::with_capacity(bodies.len());
199
+ for b in bodies.iter() {
200
+ match jolt.body_transform(*b) {
201
+ Some((p, q)) => world.push($crate::ragdoll::from_pos_quat(p, q)),
202
+ None => world.push([[1.0,0.0,0.0,0.0],[0.0,1.0,0.0,0.0],[0.0,0.0,1.0,0.0],[0.0,0.0,0.0,1.0]]),
203
+ }
204
+ }
205
+
206
+ {
207
+ let Some(r) = ragdolls.get_mut(rag as u32) else { return 0.0 };
208
+ r.age += dt as f32;
209
+ }
210
+ let age = ragdolls.get(rag as u32).map(|r| r.age).unwrap_or(0.0);
211
+
212
+ // Split borrow: apply() needs &mut ModelAnimation and &Ragdoll.
213
+ let ragdoll_ptr: *const $crate::ragdoll::Ragdoll =
214
+ match ragdolls.get(rag as u32) { Some(r) => r, None => return 0.0 };
215
+ if let Some(a) = models.get_animation_mut(anim) {
216
+ unsafe { (*ragdoll_ptr).apply(a, &world) };
217
+ }
218
+
219
+ if let Some(a) = models.get_animation(anim) {
220
+ if !a.joint_matrices.is_empty() {
221
+ let (s, c) = (rot.sin(), rot.cos());
222
+ // PT-7: anim handle = prev-palette pairing key.
223
+ renderer.set_joint_matrices_scaled(
224
+ anim.to_bits(), &a.joint_matrices, scale, pos, s, c);
225
+ }
226
+ }
227
+ age as f64
228
+ })
229
+ }
230
+ #[cfg(not(all(feature = "models3d", feature = "jolt", not(target_arch = "wasm32"))))]
231
+ #[no_mangle]
232
+ pub extern "C" fn bloom_ragdoll_update(_r: f64, _a: f64, _d: f64) -> f64 { 0.0 }
233
+
234
+ // bloom_ragdoll_release — destroy the bodies and constraints, free the
235
+ // slot for reuse. A pooled ragdoll that is never released leaks bodies
236
+ // into the physics world, which is a slow, invisible death.
237
+ #[cfg(all(feature = "models3d", feature = "jolt", not(target_arch = "wasm32")))]
238
+ #[no_mangle]
239
+ pub extern "C" fn bloom_ragdoll_release(rag: f64) {
240
+ $crate::ffi::guard("bloom_ragdoll_release", move || {
241
+ let mut eng = engine();
242
+ let $crate::engine::EngineState { ragdolls, jolt, .. } = &mut *eng;
243
+ let (bodies, cons) = {
244
+ let Some(r) = ragdolls.get(rag as u32) else { return };
245
+ (r.bodies(), r.constraint_handles().to_vec())
246
+ };
247
+ // Constraints first: destroying a body out from under a live
248
+ // constraint is how you get a use-after-free in the solver.
249
+ for c in cons { jolt.constraint_destroy(c); }
250
+ for b in bodies { jolt.destroy_body(b); }
251
+ if let Some(r) = ragdolls.get_mut(rag as u32) {
252
+ *r = $crate::ragdoll::Ragdoll::new();
253
+ }
254
+ })
255
+ }
256
+ #[cfg(not(all(feature = "models3d", feature = "jolt", not(target_arch = "wasm32"))))]
257
+ #[no_mangle]
258
+ pub extern "C" fn bloom_ragdoll_release(_r: f64) {}
259
+
260
+ };
261
+ }