From db15cd7e245325c95dbb6efc05b6f768597c96b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 8 Jun 2026 15:40:19 +0200 Subject: [PATCH 1/2] fix(watchos): generate the full FFI stub set so games link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gen_stubs.js threw on `string` params (rustType only knew f64/i64/void), so it crashed before writing src/ffi_stubs.rs and the checked-in file went stale — a game linking against the watchOS lib failed with undefined `_bloom_*` (post-pass, ssao, wind, …). Map `string` -> i64 (Perry passes string pointers in an integer register) for both params and returns, and add bloom_get_language / bloom_set_direct_2d_mode to OVERRIDES (real impls in lib.rs that regen would otherwise duplicate). Regenerate. Four functions (bloom_set_material_params, bloom_splat_impulse, bloom_profiler_frame_history, bloom_profiler_overlay_text) are declared in the engine's core/index.ts but absent from package.json's nativeLibrary.functions, so gen_stubs can't see them — add hand-written no-op stubs in src/ffi_stubs_manual.rs (perry validates the manifest cross-platform, so the manifest gap is left for a separate change). --- native/watchos/gen_stubs.js | 10 +- native/watchos/src/ffi_stubs.rs | 349 ++++++++++++++++++++----- native/watchos/src/ffi_stubs_manual.rs | 35 +++ 3 files changed, 332 insertions(+), 62 deletions(-) create mode 100644 native/watchos/src/ffi_stubs_manual.rs diff --git a/native/watchos/gen_stubs.js b/native/watchos/gen_stubs.js index aab953f..48f9d3d 100644 --- a/native/watchos/gen_stubs.js +++ b/native/watchos/gen_stubs.js @@ -11,7 +11,8 @@ const fns = pkg.perry.nativeLibrary.functions; const OVERRIDES = new Set([ // Platform + input - 'bloom_get_platform', 'bloom_get_crown_rotation', + 'bloom_get_platform', 'bloom_get_crown_rotation', 'bloom_get_language', + 'bloom_set_direct_2d_mode', 'bloom_get_screen_width', 'bloom_get_screen_height', 'bloom_get_touch_x', 'bloom_get_touch_y', 'bloom_get_touch_count', 'bloom_get_delta_time', 'bloom_get_time', 'bloom_get_fps', @@ -25,7 +26,8 @@ const OVERRIDES = new Set([ 'bloom_is_gamepad_available', 'bloom_get_gamepad_axis', 'bloom_is_gamepad_button_pressed', 'bloom_is_gamepad_button_down', 'bloom_is_gamepad_button_released', 'bloom_get_gamepad_axis_count', - // 2D shapes + // 2D camera + shapes + 'bloom_begin_mode_2d', 'bloom_end_mode_2d', 'bloom_draw_rect', 'bloom_draw_rect_lines', 'bloom_draw_circle', 'bloom_draw_circle_lines', 'bloom_draw_line', 'bloom_draw_triangle', 'bloom_draw_poly', @@ -69,6 +71,9 @@ const OVERRIDES = new Set([ const rustType = (p) => { if (p === 'f64') return 'f64'; if (p === 'i64') return 'i64'; + // Perry passes/returns heap strings as a pointer carried in an integer + // register; the stub ignores it, so i64 is the correct ABI-compatible type. + if (p === 'string') return 'i64'; if (p === 'void') return '()'; throw new Error(`Unknown param type: ${p}`); }; @@ -76,6 +81,7 @@ const rustType = (p) => { const defaultForReturn = (r) => { if (r === 'f64') return '0.0'; if (r === 'i64') return '0'; + if (r === 'string') return '0'; if (r === 'void') return '()'; throw new Error(`Unknown return type: ${r}`); }; diff --git a/native/watchos/src/ffi_stubs.rs b/native/watchos/src/ffi_stubs.rs index 598db52..9244484 100644 --- a/native/watchos/src/ffi_stubs.rs +++ b/native/watchos/src/ffi_stubs.rs @@ -43,14 +43,12 @@ } #[no_mangle] pub extern "C" fn bloom_gen_texture_mipmaps(_p0: f64) { } -#[no_mangle] pub extern "C" fn bloom_begin_mode_2d(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64) { -} -#[no_mangle] pub extern "C" fn bloom_end_mode_2d() { -} #[no_mangle] pub extern "C" fn bloom_draw_ray(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64, _p8: f64, _p9: f64) { } #[no_mangle] pub extern "C" fn bloom_draw_model(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64, _p8: f64) { } +#[no_mangle] pub extern "C" fn bloom_draw_model_rotated(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64) { +} #[no_mangle] pub extern "C" fn bloom_unload_model(_p0: f64) { } #[no_mangle] pub extern "C" fn bloom_gen_mesh_heightmap(_p0: f64, _p1: f64, _p2: f64, _p3: f64) -> f64 { @@ -59,10 +57,64 @@ #[no_mangle] pub extern "C" fn bloom_load_shader(_p0: i64) -> f64 { 0.0 } +#[no_mangle] pub extern "C" fn bloom_compile_material(_p0: i64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_compile_material_refractive(_p0: i64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_compile_material_transparent(_p0: i64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_compile_material_additive(_p0: i64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_compile_material_cutout(_p0: i64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_compile_material_instanced(_p0: i64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_create_instance_buffer(_p0: i64, _p1: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_submit_material_draw_instanced(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64) { +} +#[no_mangle] pub extern "C" fn bloom_destroy_instance_buffer(_p0: f64) { +} +#[no_mangle] pub extern "C" fn bloom_create_planar_reflection(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_set_material_reflection_probe(_p0: f64, _p1: f64) { +} +#[no_mangle] pub extern "C" fn bloom_create_texture_array(_p0: i64, _p1: f64, _p2: f64, _p3: f64, _p4: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_create_texture_array_ex(_p0: i64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_set_material_texture_array(_p0: f64, _p1: f64, _p2: f64) { +} +#[no_mangle] pub extern "C" fn bloom_set_material_shading_model(_p0: f64, _p1: f64) { +} +#[no_mangle] pub extern "C" fn bloom_set_material_foliage(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64) { +} +#[no_mangle] pub extern "C" fn bloom_set_post_pass(_p0: i64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_clear_post_pass() { +} +#[no_mangle] pub extern "C" fn bloom_add_post_pass(_p0: i64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_clear_all_post_passes() { +} +#[no_mangle] pub extern "C" fn bloom_draw_material(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64, _p8: f64, _p9: f64, _p10: f64) { +} #[no_mangle] pub extern "C" fn bloom_load_model_animation(_p0: i64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_update_model_animation(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64) { +#[no_mangle] pub extern "C" fn bloom_update_model_animation(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64) { } #[no_mangle] pub extern "C" fn bloom_create_mesh(_p0: i64, _p1: f64, _p2: i64, _p3: f64) -> f64 { 0.0 @@ -83,14 +135,20 @@ } #[no_mangle] pub extern "C" fn bloom_set_render_scale(_p0: f64) { } -#[no_mangle] pub extern "C" fn bloom_get_render_scale() -> f64 { 1.0 } +#[no_mangle] pub extern "C" fn bloom_get_render_scale() -> f64 { + 0.0 +} #[no_mangle] pub extern "C" fn bloom_set_upscale_mode(_p0: f64) { } #[no_mangle] pub extern "C" fn bloom_set_cas_strength(_p0: f64) { } -#[no_mangle] pub extern "C" fn bloom_get_physical_width() -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_get_physical_height() -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_set_auto_resolution(_target_hz: f64, _enabled: f64) { +#[no_mangle] pub extern "C" fn bloom_get_physical_width() -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_get_physical_height() -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_set_auto_resolution(_p0: f64, _p1: f64) { } #[no_mangle] pub extern "C" fn bloom_set_env_intensity(_p0: f64) { } @@ -112,6 +170,12 @@ } #[no_mangle] pub extern "C" fn bloom_set_ssao_enabled(_p0: f64) { } +#[no_mangle] pub extern "C" fn bloom_set_ssao_intensity(_p0: f64) { +} +#[no_mangle] pub extern "C" fn bloom_set_ssao_radius(_p0: f64) { +} +#[no_mangle] pub extern "C" fn bloom_set_wind(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { +} #[no_mangle] pub extern "C" fn bloom_set_ssr_enabled(_p0: f64) { } #[no_mangle] pub extern "C" fn bloom_set_motion_blur_enabled(_p0: f64) { @@ -319,136 +383,301 @@ #[no_mangle] pub extern "C" fn bloom_commit_music(_p0: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_create_world(_p0: f64, _p1: f64, _p2: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_create_world(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_destroy_world(_p0: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_set_gravity(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_get_gravity(_p0: f64, _p1: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_optimize_broadphase(_p0: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_step(_p0: f64, _p1: f64, _p2: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_set_layer_collides(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_get_layer_collides(_p0: f64, _p1: f64, _p2: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_body_count(_p0: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_active_body_count(_p0: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_shape_box(_p0: f64, _p1: f64, _p2: f64, _p3: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_shape_sphere(_p0: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_shape_capsule(_p0: f64, _p1: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_shape_cylinder(_p0: f64, _p1: f64, _p2: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_shape_scaled(_p0: f64, _p1: f64, _p2: f64, _p3: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_shape_offset_com(_p0: f64, _p1: f64, _p2: f64, _p3: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_shape_release(_p0: f64) { } -#[no_mangle] pub extern "C" fn bloom_physics_set_gravity(_p0: f64, _p1: f64, _p2: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_scratch_reset() { } -#[no_mangle] pub extern "C" fn bloom_physics_set_timestep(_p0: f64, _p1: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_scratch_push_f32(_p0: f64) { } -#[no_mangle] pub extern "C" fn bloom_physics_create_body(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_scratch_push_u32(_p0: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_shape_convex_hull(_p0: f64, _p1: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_shape_mesh(_p0: f64, _p1: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_shape_heightfield(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_compound_begin() { +} +#[no_mangle] pub extern "C" fn bloom_physics_compound_add_child(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_compound_end() -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_shape_bounds(_p0: f64, _p1: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_shape_volume(_p0: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_body_create(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64, _p8: f64, _p9: f64, _p10: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_body_destroy(_p0: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_activate(_p0: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_deactivate(_p0: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_is_active(_p0: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_body_is_valid(_p0: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_body_get_position(_p0: f64, _p1: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_body_get_rotation(_p0: f64, _p1: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_body_set_position(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_set_rotation(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_set_transform(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64, _p8: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_move_kinematic(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64, _p8: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_get_linear_velocity(_p0: f64, _p1: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_body_get_angular_velocity(_p0: f64, _p1: f64) -> f64 { + 0.0 +} +#[no_mangle] pub extern "C" fn bloom_physics_body_get_point_velocity(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_destroy_body(_p0: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_body_set_linear_velocity(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { } -#[no_mangle] pub extern "C" fn bloom_physics_set_body_enabled(_p0: f64, _p1: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_body_set_angular_velocity(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { } -#[no_mangle] pub extern "C" fn bloom_physics_set_body_ccd(_p0: f64, _p1: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_body_add_force(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { } -#[no_mangle] pub extern "C" fn bloom_physics_set_body_gravity_scale(_p0: f64, _p1: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_body_add_impulse(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { } -#[no_mangle] pub extern "C" fn bloom_physics_set_kinematic_target(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_body_add_torque(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { } -#[no_mangle] pub extern "C" fn bloom_physics_lock_rotations(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_body_add_angular_impulse(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { } -#[no_mangle] pub extern "C" fn bloom_physics_add_box_collider(_p0: f64, _p1: f64, _p2: f64, _p3: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_body_add_force_at(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_add_impulse_at(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_set_friction(_p0: f64, _p1: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_set_restitution(_p0: f64, _p1: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_set_linear_damping(_p0: f64, _p1: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_set_angular_damping(_p0: f64, _p1: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_set_gravity_factor(_p0: f64, _p1: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_set_ccd(_p0: f64, _p1: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_set_motion_type(_p0: f64, _p1: f64, _p2: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_set_object_layer(_p0: f64, _p1: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_set_is_sensor(_p0: f64, _p1: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_set_allow_sleeping(_p0: f64, _p1: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_set_shape(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_lock_rotation_axes(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_lock_translation_axes(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_body_get_mass(_p0: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_add_sphere_collider(_p0: f64, _p1: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_body_get_friction(_p0: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_add_capsule_collider(_p0: f64, _p1: f64, _p2: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_body_get_restitution(_p0: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_add_cylinder_collider(_p0: f64, _p1: f64, _p2: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_body_get_object_layer(_p0: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_set_collider_properties(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_body_set_user_data(_p0: f64, _p1: f64, _p2: f64) { } -#[no_mangle] pub extern "C" fn bloom_physics_apply_force(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_body_get_user_data(_p0: f64, _p1: f64) -> f64 { + 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_apply_impulse(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_raycast(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64, _p8: f64) -> f64 { + 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_apply_torque(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_raycast_all(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64, _p8: f64, _p9: f64) -> f64 { + 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_apply_torque_impulse(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_ray_hit_count() -> f64 { + 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_set_linear_velocity(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_ray_hit_body(_p0: f64) -> f64 { + 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_set_angular_velocity(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_ray_hit_axis(_p0: f64, _p1: f64) -> f64 { + 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_step(_p0: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_ray_hit_fraction(_p0: f64) -> f64 { + 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_sync_transforms() { +#[no_mangle] pub extern "C" fn bloom_physics_ray_hit_sub_shape(_p0: f64) -> f64 { + 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_get_body_position_x(_p0: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_overlap_sphere(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_get_body_position_y(_p0: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_overlap_point(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_get_body_position_z(_p0: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_overlap_box(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64, _p8: f64, _p9: f64, _p10: f64, _p11: f64, _p12: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_get_body_rotation_x(_p0: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_overlap_body(_p0: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_get_body_rotation_y(_p0: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_constraint_fixed(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64, _p8: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_get_body_rotation_z(_p0: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_constraint_point(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64, _p8: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_get_body_rotation_w(_p0: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_constraint_hinge(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64, _p8: f64, _p9: f64, _p10: f64, _p11: f64, _p12: f64, _p13: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_get_linear_velocity_x(_p0: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_constraint_slider(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64, _p8: f64, _p9: f64, _p10: f64, _p11: f64, _p12: f64, _p13: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_get_linear_velocity_y(_p0: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_constraint_distance(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64, _p8: f64, _p9: f64, _p10: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_get_linear_velocity_z(_p0: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_constraint_destroy(_p0: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_constraint_set_enabled(_p0: f64, _p1: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_contact_count() -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_get_angular_velocity_x(_p0: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_contact_field(_p0: f64, _p1: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_get_angular_velocity_y(_p0: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_clear_contacts(_p0: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_character_create(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64, _p8: f64, _p9: f64, _p10: f64, _p11: f64, _p12: f64, _p13: f64, _p14: f64, _p15: f64, _p16: f64, _p17: f64, _p18: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_get_angular_velocity_z(_p0: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_character_destroy(_p0: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_character_update(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_character_get_position(_p0: f64, _p1: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_raycast(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_character_get_rotation(_p0: f64, _p1: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_ray_hit_body() -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_character_set_position(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_character_set_rotation(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_character_get_linear_velocity(_p0: f64, _p1: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_ray_hit_distance() -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_character_set_linear_velocity(_p0: f64, _p1: f64, _p2: f64, _p3: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_character_get_ground_state(_p0: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_ray_hit_x() -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_character_get_ground_normal(_p0: f64, _p1: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_ray_hit_y() -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_character_get_ground_position(_p0: f64, _p1: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_ray_hit_z() -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_character_get_ground_body(_p0: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_get_collision_count() -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_character_set_shape(_p0: f64, _p1: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_soft_body_create(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64, _p8: f64, _p9: f64, _p10: f64, _p11: f64, _p12: f64, _p13: f64, _p14: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_get_collision_event(_p0: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_soft_body_vertex_count(_p0: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_get_collision_body_b() -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_soft_body_get_vertex(_p0: f64, _p1: f64, _p2: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_get_collision_started() -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_soft_body_set_vertex(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_soft_body_set_vertex_inv_mass(_p0: f64, _p1: f64, _p2: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_vehicle_create(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64, _p8: f64, _p9: f64, _p10: f64, _p11: f64, _p12: f64, _p13: f64, _p14: f64, _p15: f64, _p16: f64, _p17: f64, _p18: f64, _p19: f64, _p20: f64, _p21: f64, _p22: f64, _p23: f64, _p24: f64, _p25: f64, _p26: f64, _p27: f64, _p28: f64, _p29: f64, _p30: f64, _p31: f64, _p32: f64, _p33: f64, _p34: f64, _p35: f64, _p36: f64, _p37: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_attach_scene_node(_p0: f64, _p1: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_vehicle_destroy(_p0: f64) { } -#[no_mangle] pub extern "C" fn bloom_physics_create_fixed_joint(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_vehicle_get_chassis(_p0: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_create_revolute_joint(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_vehicle_set_input(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64) { +} +#[no_mangle] pub extern "C" fn bloom_physics_vehicle_get_wheel_transform(_p0: f64, _p1: f64, _p2: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_create_prismatic_joint(_p0: f64, _p1: f64, _p2: f64, _p3: f64, _p4: f64, _p5: f64, _p6: f64, _p7: f64) -> f64 { +#[no_mangle] pub extern "C" fn bloom_physics_vehicle_get_engine_rpm(_p0: f64) -> f64 { 0.0 } -#[no_mangle] pub extern "C" fn bloom_physics_destroy_joint(_p0: f64) { +#[no_mangle] pub extern "C" fn bloom_physics_vehicle_get_wheel_angular_velocity(_p0: f64, _p1: f64) -> f64 { + 0.0 } diff --git a/native/watchos/src/ffi_stubs_manual.rs b/native/watchos/src/ffi_stubs_manual.rs new file mode 100644 index 0000000..3966d84 --- /dev/null +++ b/native/watchos/src/ffi_stubs_manual.rs @@ -0,0 +1,35 @@ +// Hand-written no-op stubs for bloom_* FFI functions that the engine's +// TypeScript (src/core/index.ts) `declare function`s but that are ABSENT from +// the package.json `perry.nativeLibrary.functions` manifest — so gen_stubs.js +// (which reads the manifest) cannot generate them. +// +// The watchOS proof-of-life crate does not implement these advanced +// 3D / post-fx / profiler features (SCNTechnique/SCNRenderer is absent from the +// watchOS SDK). Bloom Jump (2D) never calls them, but Perry still emits their +// wrapper symbols from the TS declarations, so the native symbols must resolve +// at link time. +// +// Signatures mirror the `declare function` lines in core/index.ts: +// - `number` params -> f64 (Perry passes these in float registers) +// - the `any` pointer -> i64 (a pointer carried in an integer register) +// - `string` returns -> i64 (a pointer carried in an integer register) +// +// If these four are ever added to the manifest, delete this file and let +// gen_stubs.js generate them instead. +#![allow(unused_variables, non_snake_case)] + +#[no_mangle] +pub extern "C" fn bloom_set_material_params(_handle: f64, _params_ptr: i64, _count: f64) {} + +#[no_mangle] +pub extern "C" fn bloom_splat_impulse(_x: f64, _z: f64, _radius: f64, _strength: f64) {} + +#[no_mangle] +pub extern "C" fn bloom_profiler_frame_history() -> i64 { + 0 +} + +#[no_mangle] +pub extern "C" fn bloom_profiler_overlay_text() -> i64 { + 0 +} From 6b8ca675aee2fff8694df0780849bdc6fc438b7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 8 Jun 2026 15:40:19 +0200 Subject: [PATCH 2/2] fix(watchos): correct string FFI, implement file reading + 2D camera MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three runtime gaps that kept games from actually rendering on watchOS: - StringHeader was 16 bytes (4×u32), but Perry 0.5.x's canonical header is 20 bytes (5×u32 incl `flags`, data at +20). `perry_str` read every incoming string 4 bytes early — text rendered with a 4-null prefix + truncated tail ("BLOOM JUMP" -> "BLOOM") and file paths came back corrupted so levels never loaded. Add the missing `flags` field. - bloom_read_file was a stub returning 0 (null); the game's inline `.length` then dereferenced null and crashed. Implement it: resolve the bundle-relative path, read the file, and return a real StringHeader via a new alloc_perry_string (empty string on a miss, never null). - bloom_begin_mode_2d / bloom_end_mode_2d were no-ops, so world-space draws (tiles, sprites) rendered at raw coordinates off-screen while the screen-space HUD showed. Emit BEGIN_2D/END_2D marker commands carrying the camera; the SwiftUI Canvas applies the matching affine transform (world -> screen) to every command in between. Also drop the watchOS `metal_sources` post-fx shader: SCNTechnique/SCNRenderer is absent from the watchOS SDK so it can't run, and compiling it needs the separately-downloadable Metal Toolchain. The app renders fine without the metallib. --- native/watchos/src/BloomWatchApp.swift | 23 ++++++- native/watchos/src/draw_list.rs | 6 ++ native/watchos/src/lib.rs | 85 ++++++++++++++++++++++++-- package.json | 3 - 4 files changed, 108 insertions(+), 9 deletions(-) diff --git a/native/watchos/src/BloomWatchApp.swift b/native/watchos/src/BloomWatchApp.swift index 692ac28..8d29239 100644 --- a/native/watchos/src/BloomWatchApp.swift +++ b/native/watchos/src/BloomWatchApp.swift @@ -176,6 +176,8 @@ let K_TEXTURE: Int32 = 7 let K_TEXTURE_REC: Int32 = 8 let K_TEXTURE_PRO: Int32 = 9 let K_TEXT: Int32 = 10 +let K_BEGIN_2D: Int32 = 11 +let K_END_2D: Int32 = 12 // 3D primitives let K_CUBE: Int32 = 20 @@ -307,11 +309,30 @@ struct BloomRootView: View { // Pull this frame's commands. 2D draws render here; 3D // commands are filtered by BloomSceneView. let n = Int(bloom_watchos_copy_draw_list(drawBuf.baseAddress!, 4096)) + // `active` is the context world-space draws use: between a + // BEGIN_2D / END_2D pair it carries the camera's affine + // transform (world → screen); otherwise it's the plain + // screen-space context. + var active = ctx for i in 0..= 20 && k <= 29 { continue } // 3D — handled by SceneView - drawOne(ctx: ctx, cmdPtr: ptr) + if k == K_BEGIN_2D { + let zoom = ptr.pointee.size + var t = CGAffineTransform(translationX: ptr.pointee.x, y: ptr.pointee.y) + t = t.scaledBy(x: zoom, y: zoom) + t = t.translatedBy(x: -ptr.pointee.w, y: -ptr.pointee.h) + var w = ctx + w.transform = t + active = w + continue + } + if k == K_END_2D { + active = ctx + continue + } + drawOne(ctx: active, cmdPtr: ptr) } } } diff --git a/native/watchos/src/draw_list.rs b/native/watchos/src/draw_list.rs index f2887dd..34f05f1 100644 --- a/native/watchos/src/draw_list.rs +++ b/native/watchos/src/draw_list.rs @@ -30,6 +30,12 @@ pub mod kind { pub const TEXTURE_PRO: i32 = 9; // full: source, dest, origin, rotation pub const TEXT: i32 = 10; + // 2D camera mode markers. BEGIN carries the camera in reused fields: + // x,y = screen offset; w,h = world target; size = zoom. The Swift Canvas + // applies the matching affine transform to every command until END. + pub const BEGIN_2D: i32 = 11; + pub const END_2D: i32 = 12; + // 3D immediate-mode primitives (pos = x,y,z; w,h = scale; src_x,y,z = secondary). pub const CUBE: i32 = 20; // pos (x,y,z), size (w,h,size=depth) pub const CUBE_WIRES: i32 = 21; diff --git a/native/watchos/src/lib.rs b/native/watchos/src/lib.rs index bc09837..04d2993 100644 --- a/native/watchos/src/lib.rs +++ b/native/watchos/src/lib.rs @@ -10,6 +10,7 @@ #![allow(non_upper_case_globals)] mod ffi_stubs; +mod ffi_stubs_manual; mod draw_list; mod textures; mod audio; @@ -26,6 +27,12 @@ struct StringHeader { byte_len: u32, capacity: u32, refcount: u32, + // Perry 0.5.x's canonical StringHeader is 5×u32 = 20 bytes (data at +20). + // Omitting `flags` made this 16 bytes, so `perry_str` read every incoming + // string 4 bytes early — text rendered with a 4-null prefix + truncated + // tail ("BLOOM JUMP" → "BLOOM"), and file paths came back corrupted so + // levels never loaded. + flags: u32, } /// Decode a Perry-side string pointer (i64 on this ABI) into a borrowed &str. @@ -42,6 +49,43 @@ fn perry_str<'a>(ptr: i64) -> &'a str { } } +/// Allocate a Perry-side heap string (StringHeader + UTF-8 payload) and return +/// its pointer as the i64 the FFI boundary expects. Mirrors `perry_str`'s +/// layout so a string we hand back is read identically to one Perry passed us. +/// Returning this (never 0) for a `returns:"string"` function is mandatory: +/// Perry's inline `.length` dereferences the pointer, so a null segfaults the +/// caller. +fn alloc_perry_string(s: &str) -> i64 { + let bytes = s.as_bytes(); + let byte_len = bytes.len(); + let utf16_len = if bytes.iter().all(|&b| b < 0x80) { + byte_len + } else { + s.encode_utf16().count() + }; + // A value RETURNED across the FFI boundary is consumed by Perry's internal + // string machinery, whose canonical StringHeader is 5×u32 = 20 bytes + // (utf16_len, byte_len, capacity, refcount, flags) with data at +20. + // (The local 16-byte `StringHeader`/`perry_str` above describe the *incoming* + // arg representation, which omits `flags`; don't conflate the two.) + const HEADER_SIZE: usize = 20; + let total = HEADER_SIZE + byte_len; + unsafe { + let layout = std::alloc::Layout::from_size_align(total, 4).unwrap(); + let ptr = std::alloc::alloc(layout); + if ptr.is_null() { + return 0; + } + *(ptr.add(0) as *mut u32) = utf16_len as u32; + *(ptr.add(4) as *mut u32) = byte_len as u32; + *(ptr.add(8) as *mut u32) = byte_len as u32; // capacity + *(ptr.add(12) as *mut u32) = 1; // refcount = unique + *(ptr.add(16) as *mut u32) = 0; // flags + std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.add(HEADER_SIZE), byte_len); + ptr as i64 + } +} + use std::ffi::{c_char, c_void}; use std::sync::atomic::{AtomicI64, AtomicU64, AtomicUsize, Ordering}; use std::sync::OnceLock; @@ -374,6 +418,27 @@ pub extern "C" fn bloom_draw_circle(cx: f64, cy: f64, rad: f64, r: f64, g: f64, draw_list::push(c); } +// 2D camera: emit marker commands carrying the camera so the Swift Canvas can +// apply the matching affine transform to every world-space draw until end. +// world→screen is (world - target) * zoom + offset. Without this, gameplay +// tiles/sprites (drawn between begin/end) render at raw world coords off-screen. +#[no_mangle] +pub extern "C" fn bloom_begin_mode_2d(ox: f64, oy: f64, tx: f64, ty: f64, _rot: f64, zoom: f64) { + let mut c = DrawCmd::zero(); + c.kind = kind::BEGIN_2D; + c.x = ox; c.y = oy; // screen offset + c.w = tx; c.h = ty; // world target + c.size = zoom; // zoom + draw_list::push(c); +} + +#[no_mangle] +pub extern "C" fn bloom_end_mode_2d() { + let mut c = DrawCmd::zero(); + c.kind = kind::END_2D; + draw_list::push(c); +} + #[no_mangle] pub extern "C" fn bloom_draw_circle_lines(cx: f64, cy: f64, rad: f64, thickness: f64, r: f64, g: f64, b: f64, a: f64) { let mut c = DrawCmd::zero(); @@ -560,11 +625,21 @@ pub extern "C" fn bloom_file_exists(path: i64) -> f64 { #[no_mangle] pub extern "C" fn bloom_read_file(path: i64) -> i64 { - // Perry's native FFI wraps string returns — returning 0 signals null - // from the watchOS side. Jump reads level files via this path; a later - // pass will resolve bundle-relative paths and hand back a StringHeader. - let _ = path; - 0 + let p = perry_str(path); + if p.is_empty() { + return alloc_perry_string(""); + } + // Resolve bundle-relative paths the same way bloom_file_exists / textures do. + let full = if p.starts_with('/') { + p.to_string() + } else { + textures::resolve_bundle_path(p) + }; + // Always hand back a valid StringHeader — empty on a miss, never null. + match std::fs::read_to_string(&full) { + Ok(contents) => alloc_perry_string(&contents), + Err(_) => alloc_perry_string(""), + } } #[no_mangle] diff --git a/package.json b/package.json index 4d30f1f..ab77a80 100644 --- a/package.json +++ b/package.json @@ -3390,9 +3390,6 @@ "swift_sources": [ "native/watchos/src/BloomWatchApp.swift", "native/watchos/src/BloomWatchAudio.swift" - ], - "metal_sources": [ - "native/watchos/shaders/bloom_postfx.metal" ] }, "windows": {