From 6bb16a6827f0d4d0fa9cdffda842db737a9bb010 Mon Sep 17 00:00:00 2001 From: chazmm Date: Mon, 7 Sep 2026 17:39:10 +0100 Subject: [PATCH] Preserve convex rail contacts and port native truck probes --- .../skate-core/src/physics/grind_contact.rs | 63 +++++++++++++++++++ crates/skate-game/src/physics/ground.rs | 14 ++++- docs/GRIND_INTEGRATION.md | 24 +++++++ 3 files changed, 99 insertions(+), 2 deletions(-) diff --git a/crates/skate-core/src/physics/grind_contact.rs b/crates/skate-core/src/physics/grind_contact.rs index 854d0f1b..9f2442da 100644 --- a/crates/skate-core/src/physics/grind_contact.rs +++ b/crates/skate-core/src/physics/grind_contact.rs @@ -3,6 +3,67 @@ use super::native_arithmetic::dot3; type V = [f32; 4]; +/// Native query entry: endpoints plus a spline owner, not three vectors. +#[derive(Clone, Copy, Debug)] +pub struct Primitive { + pub start: V, + pub end: V, + pub owner: u64, +} + +#[derive(Clone, Copy, Debug)] +pub struct TruckContact { + pub position: V, + pub primitive: usize, +} + +/// Truck rectangles from82C1FDC0. The geometry dimensions come from the +/// physics_grinds collection (TruckToWheel584, DeckCenterToTruck636). +/// Output order is positive board-forward truck, negative board-forward truck. +/// These are geometric contacts;82D89150 still decides whether to engage. +pub fn truck_contacts( + board: [V; 4], + flags_2484: u32, + truck_to_wheel: f32, + deck_center_to_truck: f32, + primitives: &[Primitive], +) -> [Option; 2] { + if flags_2484 & 0x0020_0000 != 0 { + return [None; 2]; + } + let [right, up, forward, position] = board; + //821659F8 /8208EA7C: the probe starts .02 below the board and extends + //another .20 down its local up axis. Do not substitute world vertical. + let centre = core::array::from_fn(|i| up[i].mul_add(-0.02, position[i])); + let side = scale(right, truck_to_wheel); + let along = scale(forward, deck_center_to_truck); + let down = scale(up, -0.2); + let centres = [add(centre, along), sub(centre, along)]; + let mut result = [None; 2]; + let mut distance = [1_000_000.0; 2]; //822F88D4, squared-distance ceiling. + for (primitive, edge) in primitives.iter().enumerate() { + for truck in 0..2 { + let a = add(centres[truck], side); + let b = sub(centres[truck], side); + let c = add(b, down); + let d = add(a, down); + //Native calls both triangles and retains the first triangle's + //intersection if both succeed, including a shared diagonal hit. + let first = segment_triangle(edge.start, edge.end, [a, b, c]); + let second = segment_triangle(edge.start, edge.end, [a, d, c]); + if let Some(position) = first.or(second) { + let delta = sub(position, centres[truck]); + let squared = dot3(delta, delta); + if distance[truck] > squared { + distance[truck] = squared; + result[truck] = Some(TruckContact { position, primitive }); + } + } + } + } + result +} + /// Intersect a grind primitive segment with a board/truck probe triangle. /// Returns the native hit position; acquisition/scoring belongs to the caller. /// Both windings are accepted. Failed branches do not synthesize a contact. @@ -26,6 +87,8 @@ pub fn segment_triangle(start: V, end: V, triangle: [V; 3]) -> Option { } fn sub(a: V, b: V) -> V { core::array::from_fn(|i| a[i] - b[i]) } +fn add(a: V, b: V) -> V { core::array::from_fn(|i| a[i] + b[i]) } +fn scale(a: V, s: f32) -> V { a.map(|v| v * s) } fn cross(a: V, b: V) -> V { [(-a[2]).mul_add(b[1], a[1] * b[2]), (-a[0]).mul_add(b[2], a[2] * b[0]), diff --git a/crates/skate-game/src/physics/ground.rs b/crates/skate-game/src/physics/ground.rs index 098b03f9..85048257 100644 --- a/crates/skate-game/src/physics/ground.rs +++ b/crates/skate-game/src/physics/ground.rs @@ -109,15 +109,25 @@ pub(crate) fn surfaces() -> [Vec<[Vector3; 4]>; 3] { pub(super) fn world(material: RetailContactMaterial) -> BoardWorld { let mut triangles = Vec::new(); + let rail_faces = crate::grind_world::surfaces(); for (tag, quads) in surfaces().into_iter().enumerate() { for vertices in quads { - for indices in [[0, 2, 1], [0, 3, 2]] { + let rail_face = rail_faces.contains(&vertices); + for (half, indices) in [[0, 2, 1], [0, 3, 2]].into_iter().enumerate() { + // Closed rectangular rails have convex perimeter edges. Keep + // the coplanar triangulation diagonal non-convex. Without + // these flags,82AD3130 rejects narrow-rail edge contacts. + let convex = if rail_face { + if half == 0 { 0x60 } else { 0xc0 } + } else { + 0 + }; triangles.push(WorldTriangle { triangle: triangle_from_volume( indices.map(|i| vertices[i]), 0.0, [1.0; 3], - TriangleFeature::ONE_SIDED, + TriangleFeature::ONE_SIDED | convex, ), material, tag: tag as u32, diff --git a/docs/GRIND_INTEGRATION.md b/docs/GRIND_INTEGRATION.md index 5bbb01bf..f30c835f 100644 --- a/docs/GRIND_INTEGRATION.md +++ b/docs/GRIND_INTEGRATION.md @@ -60,3 +60,27 @@ record/scorer, then lifecycle/constraint/output integration as one coherent vertical slice. Do not enable selector candidates before those owners exist. Validation: build only; no gameplay or automated tests, per user instruction. + +## 2026-09-07 collision and contact follow-up + +The rail boxes were emitted with only ONE_SIDED. Native contact fixup82AD3130 +rejects an edge-region contact when its convex bit is absent. Authored rail +faces now set the two perimeter-edge bits on each triangle and leave the +coplanar diagonal non-convex (0x60 /0xC0 for the existing triangle winding). +This change applies to rail/coping/support boxes only. Gameplay confirmation +is outstanding; a build does not establish that this explains every missed hit. + +Core truck_contacts ports82C1FDC0's two local-board probe rectangles, using +TruckToWheel and DeckCenterToTruck, the -.02 start offset and -.20 depth, +first-triangle priority, strict squared-distance comparison and first-entry +tie retention. It remains a contact query, not an engagement decision. + +Checked the task Research readable game recreation and its referenced +Skate3CustomEngineLayer/src/skate3_native_grind.cpp. That integration registers +splines in the original guest GrindData owner; it does not contain replacement +grind forces. Expand Skate 3 replay toolkit likewise supplies research and +capture infrastructure rather than a standalone grind controller. + +The remaining native state/force and graph-attribute contracts are unresolved. +Do not advertise this build as grind-ready. Build log: +logs/grind-collision-build.log (successful,35 warnings); no tests executed.