@@ -13031,6 +13031,51 @@ static void test_post_ccd_hull_tet_not_inside_beam_5c801e62()
1303113031 destroy_world(w);
1303213032}
1303313033
13034+ // Soak repro: spawn_rng=0x2f59c53c, HULL_TET scale=0.8853,
13035+ // vel=(110.143,2.834,4.490), omega=(-3.230,-4.335,-4.709). Body tunnels
13036+ // fully past wall (center ends on far side). Post-solve velocity is flipped
13037+ // (-0.93 in x) so velocity-based TOI sweep misses — body ends at (5.125)
13038+ // past wall back face (5.05). Fix: segment-AABB clip tunnel detection
13039+ // samples the pre_pos -> post_pos center trajectory.
13040+ static void test_post_ccd_hull_tet_no_tunnel_2f59c53c()
13041+ {
13042+ WorldParams wp = { .gravity = V3(0, -9.81f, 0), .broadphase = BROADPHASE_BVH };
13043+ World w = create_world(wp);
13044+ WorldInternal* wi = (WorldInternal*)w.id;
13045+ wi->sleep_enabled = 0;
13046+
13047+ Body floor_b = create_body(w, (BodyParams){ .position = V3(0, -1, 0), .rotation = quat_identity(), .mass = 0, .friction = 0.5f });
13048+ body_add_shape(w, floor_b, (ShapeParams){ .type = SHAPE_BOX, .box.half_extents = V3(20, 1, 20) });
13049+ Body wall = create_body(w, (BodyParams){ .position = V3(5.0f, 2.0f, 0), .rotation = quat_identity(), .mass = 0 });
13050+ body_add_shape(w, wall, (ShapeParams){ .type = SHAPE_BOX, .box.half_extents = V3(0.05f, 3.0f, 3.0f) });
13051+ Body beam = create_body(w, (BodyParams){ .position = V3(5.0f, 2.0f, 0), .rotation = quat_identity(), .mass = 0 });
13052+ body_add_shape(w, beam, (ShapeParams){ .type = SHAPE_BOX, .box.half_extents = V3(0.35f, 0.15f, 3.0f) });
13053+
13054+ v3 tet[] = { {0, 0.6f, 0}, {0.5f, -0.3f, 0.3f}, {-0.5f, -0.3f, 0.3f}, {0, -0.3f, -0.5f} };
13055+ Hull* h_tet = quickhull(tet, 4);
13056+ float s = 0.8853f;
13057+ Body b = create_body(w, (BodyParams){
13058+ .position = V3(-8.5f, 1.5f, 0), .rotation = quat_identity(),
13059+ .mass = 0.15f, .restitution = 0.25f, .friction = 0.4f,
13060+ });
13061+ body_add_shape(w, b, (ShapeParams){ .type = SHAPE_HULL, .hull = { .hull = h_tet, .scale = V3(0.18f*s, 0.18f*s, 0.18f*s) } });
13062+ body_set_velocity(w, b, V3(110.143f, 2.834f, 4.490f));
13063+ body_set_angular_velocity(w, b, V3(-3.230f, -4.335f, -4.709f));
13064+
13065+ float dt = 1.0f / 60.0f;
13066+ int tunneled = 0;
13067+ for (int frame = 0; frame < 30; frame++) {
13068+ world_step(w, dt);
13069+ v3 pos = body_get_position(w, b);
13070+ int in_wall_yz = fabsf(pos.y - 2.0f) < 3.0f && fabsf(pos.z) < 3.0f;
13071+ if (pos.x > 5.10f && in_wall_yz) { tunneled = 1; break; }
13072+ }
13073+ TEST_BEGIN("post-CCD hull_tet (soak 0x2f59c53c): no tunnel past wall");
13074+ TEST_ASSERT(!tunneled);
13075+ hull_free(h_tet);
13076+ destroy_world(w);
13077+ }
13078+
1303413079static void run_post_ccd_tests()
1303513080{
1303613081 printf("--- nudge post-CCD motion tests ---\n");
@@ -13041,6 +13086,7 @@ static void run_post_ccd_tests()
1304113086 test_post_ccd_capsule_not_wedged_814d37c7();
1304213087 test_post_ccd_capsule_not_frozen_4c9aeda6();
1304313088 test_post_ccd_hull_tet_not_inside_beam_5c801e62();
13089+ test_post_ccd_hull_tet_no_tunnel_2f59c53c();
1304413090 test_post_ccd_tet_not_stuck_in_beam();
1304513091 test_post_ccd_box_bullet_no_freeze_after_rebound();
1304613092 test_post_ccd_box_bullet_no_tunnel_soak_seed();
0 commit comments