mirror of
https://github.com/SK8-ENGINE/skate-3-rust-engine.git
synced 2026-10-26 19:47:44 +00:00
Recover native fifty-fifty forces and grind animation controls
This commit is contained in:
1 parent
6bb16a6827
commit
88b51467e9
6 files changed
+234
No files matched your search
@@ -0,0 +1,69 @@
|
||||
//! TU3 GrindControlFade82BB0D40 and ControlGrindCrouch82BB10F8.
|
||||
//! Animation attributes, physical observations and collection values remain
|
||||
//! explicit inputs; these routines do not choose the grind or animation.
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FadeSettings {
|
||||
///Collection owner312: offsets1640,1644,1648,1652.
|
||||
pub response: f32,
|
||||
pub input_scale: f32,
|
||||
pub acceleration: f32,
|
||||
pub maximum_step: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Fade {
|
||||
pub just_began: bool,
|
||||
pub elapsed: f32,
|
||||
pub value: f32,
|
||||
pub target: f32,
|
||||
pub step: f32,
|
||||
pub minimum: f32,
|
||||
pub maximum: f32,
|
||||
}
|
||||
|
||||
impl Fade {
|
||||
///Begin82BB0A30 obtains bounds by evaluating the animation attribute at
|
||||
///both endpoints. The caller supplies those evaluations and mirrored twist.
|
||||
pub fn begin(&mut self, minimum: f32, maximum: f32, twist: f32) -> f32 {
|
||||
*self = Self {
|
||||
just_began: true,
|
||||
minimum,
|
||||
maximum,
|
||||
value: bound(twist, minimum, maximum),
|
||||
target: bound(twist, minimum, maximum),
|
||||
..Self::default()
|
||||
};
|
||||
self.value
|
||||
}
|
||||
|
||||
///The first Update after Begin only clears byte8; it emits no attribute.
|
||||
pub fn update(&mut self, dt: f32, intent: f32, settings: FadeSettings) -> Option<f32> {
|
||||
if self.just_began {
|
||||
self.just_began = false;
|
||||
return None;
|
||||
}
|
||||
self.elapsed += dt;
|
||||
self.target = bound(settings.input_scale.mul_add(intent, self.target), self.minimum, self.maximum);
|
||||
let desired = (1.0 - settings.response).mul_add(self.value, self.target * settings.response);
|
||||
let step = bound(desired - self.value, self.step - settings.acceleration, self.step + settings.acceleration);
|
||||
self.step = bound(step, -settings.maximum_step, settings.maximum_step);
|
||||
self.value = bound(self.value + self.step, self.minimum, self.maximum);
|
||||
Some(self.value)
|
||||
}
|
||||
}
|
||||
|
||||
///Collection owner340: min1760,max1792,rate1816. Begin82BB1078 seeds
|
||||
///previous with physical crouching output56+72; it does not seed from intent.
|
||||
pub fn crouch(previous: f32, intent: f32, grind_crouch: f32, minimum: f32, maximum: f32, rate: f32, dt: f32) -> f32 {
|
||||
let desired = bound(1.0 - intent, minimum, maximum)
|
||||
.min(bound(1.0 - grind_crouch, minimum, maximum));
|
||||
let step = dt * rate;
|
||||
bound(desired, previous - step, previous + step)
|
||||
}
|
||||
|
||||
fn bound(value: f32, minimum: f32, maximum: f32) -> f32 {
|
||||
//The native lower comparison precedes its upper comparison.
|
||||
let value = if minimum - value >= 0.0 { minimum } else { value };
|
||||
if maximum - value >= 0.0 { value } else { maximum }
|
||||
}
|
||||
@@ -13,6 +13,7 @@ pub mod phase_blend;
|
||||
pub mod pose_blend;
|
||||
pub mod playback_transition;
|
||||
pub mod crouching;
|
||||
pub mod grind_control;
|
||||
pub mod pose_sample;
|
||||
pub mod pose_trajectory;
|
||||
pub mod pose_add;
|
||||
|
||||
@@ -17,6 +17,68 @@ pub struct TruckContact {
|
||||
pub primitive: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FiftyFiftyCandidate {
|
||||
pub direction: V,
|
||||
pub centre: V,
|
||||
pub front: V,
|
||||
pub rear: V,
|
||||
pub primitive: usize,
|
||||
}
|
||||
|
||||
///82D89150's same-primitive contact branch, before shared admission82D886B8.
|
||||
/// Linked/adjacent primitive arbitration belongs to the caller; this boundary
|
||||
/// explicitly returns none for that case instead of guessing an owner.
|
||||
pub fn fifty_fifty_candidate(
|
||||
board: [V; 4],
|
||||
balance: [f32; 2],
|
||||
contacts: [Option<TruckContact>; 2],
|
||||
) -> Option<FiftyFiftyCandidate> {
|
||||
let [Some(front), Some(rear)] = contacts else { return None };
|
||||
if balance[0].abs().max(balance[1].abs()) >= 0.9
|
||||
|| front.primitive != rear.primitive
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let front_depth = dot3(sub(board[3], front.position), board[1]);
|
||||
let rear_depth = dot3(sub(board[3], rear.position), board[1]);
|
||||
if front_depth.max(rear_depth) >= 0.13 {
|
||||
return None;
|
||||
}
|
||||
let difference = sub(front.position, rear.position);
|
||||
let length = dot3(difference, difference).sqrt();
|
||||
// A degenerate pair cannot define a finite native grind frame.
|
||||
if !length.is_finite() || length == 0.0 { return None; }
|
||||
Some(FiftyFiftyCandidate {
|
||||
direction: scale(difference, length.recip()),
|
||||
centre: scale(add(front.position, rear.position), 0.5),
|
||||
front: front.position,
|
||||
rear: rear.position,
|
||||
primitive: front.primitive,
|
||||
})
|
||||
}
|
||||
|
||||
///82D370F8: upward-facing normal perpendicular to the spline direction.
|
||||
pub fn upright_normal(direction: V) -> V {
|
||||
let cross_up = cross([0.0, 1.0, 0.0, 0.0], direction);
|
||||
let mut normal = cross(cross_up, direction);
|
||||
let length = dot3(normal, normal).sqrt();
|
||||
if length <= 0.0 { return [1.0, 0.0, 0.0, 0.0]; }
|
||||
if normal[1] < 0.0 { normal = scale(normal, -1.0); }
|
||||
scale(normal, length.recip())
|
||||
}
|
||||
|
||||
///82D88518, common active-grind angular admission. Plane projection occurs
|
||||
/// before the direction comparison; vertical speed is not an approach angle.
|
||||
pub fn within_approach_angle(direction: V, velocity: V, degrees: f32) -> bool {
|
||||
let normal = upright_normal(direction);
|
||||
let projected = sub(velocity, scale(normal, dot3(velocity, normal)));
|
||||
let length = dot3(projected, projected).sqrt();
|
||||
if length <= 0.001 { return true; }
|
||||
let alignment = dot3(scale(projected, length.recip()), direction).abs();
|
||||
alignment > (degrees * f32::from_bits(0x3c8e_fa35)).cos()
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
//! TU3 grind force leaves. Their inputs belong to the retained grind manager;
|
||||
//! contact admission and lifecycle are separate from these force calculations.
|
||||
use super::native_arithmetic::dot3;
|
||||
type V = [f32; 4];
|
||||
|
||||
///82D3FA18, called by the 50-50 update82D41D70 with800,0,.07.
|
||||
/// Returns a force at the deck origin; never moves the body to a spline.
|
||||
pub fn lateral_pin(
|
||||
board: [V; 4],
|
||||
point: V,
|
||||
across: V,
|
||||
velocity: V,
|
||||
strength: f32,
|
||||
forward_offset: f32,
|
||||
up_offset: f32,
|
||||
forward_selected: bool,
|
||||
slope_multiplier: f32,
|
||||
) -> V {
|
||||
let direction = if forward_selected { board[2] } else { scale(board[2], -1.0) };
|
||||
let reference = core::array::from_fn(|i| {
|
||||
direction[i].mul_add(forward_offset, board[3][i]) - board[1][i] * up_offset
|
||||
});
|
||||
let force = scale(across, dot3(sub(point, reference), across) * strength);
|
||||
let length = dot3(force, force).sqrt();
|
||||
if length <= 0.0 {
|
||||
return [0.0; 4];
|
||||
}
|
||||
let axis = scale(force, length.recip());
|
||||
let damping = scale(axis, strength * f32::from_bits(0x3e08_3127) * dot3(velocity, axis));
|
||||
scale(sub(force, damping), slope_multiplier)
|
||||
}
|
||||
|
||||
///82D3FD88 after its native surface/material selection. It damps velocity in
|
||||
/// the plane perpendicular to the grind normal, including the along-rail lane.
|
||||
pub fn friction(
|
||||
velocity: V,
|
||||
grind_normal: V,
|
||||
support: V,
|
||||
time_multiplier: f32,
|
||||
flagged_surface: bool,
|
||||
surface_multiplier: f32,
|
||||
engagement: u32,
|
||||
strengths: [f32; 3],
|
||||
) -> V {
|
||||
let tangent_velocity = sub(velocity, scale(grind_normal, dot3(velocity, grind_normal)));
|
||||
let speed = dot3(tangent_velocity, tangent_velocity).sqrt();
|
||||
if speed <= 0.001 {
|
||||
return [0.0; 4];
|
||||
}
|
||||
let strength = strengths[match engagement { 0 => 0, 1 => 1, _ => 2 }];
|
||||
let load = support[1].max(0.0);
|
||||
let surface_flag_multiplier = if flagged_surface { 1.9 } else { 1.0 };
|
||||
let multiplier = load / speed * time_multiplier * surface_flag_multiplier
|
||||
* surface_multiplier * strength * f32::from_bits(0xbef5_c28f);
|
||||
scale(tangent_velocity, multiplier)
|
||||
}
|
||||
|
||||
fn sub(a: V, b: V) -> V { core::array::from_fn(|i| a[i] - b[i]) }
|
||||
fn scale(a: V, scale: f32) -> V { a.map(|v| v * scale) }
|
||||
@@ -41,6 +41,7 @@ pub mod startup;
|
||||
mod triangle_closest;
|
||||
pub mod triangle_query;
|
||||
pub mod grind_contact;
|
||||
pub mod grind_forces;
|
||||
mod triangle_sweep;
|
||||
mod triangle_sweep_roots;
|
||||
mod triangle_sweep_walk;
|
||||
|
||||
@@ -84,3 +84,45 @@ 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.
|
||||
|
||||
## Additional static recovery
|
||||
|
||||
The reference process was closed at the user's request. Continue using static
|
||||
analysis; do not relaunch it. IDA database
|
||||
`research/reverse-engineering/XexDump/Skate3TU3_dump.i64` was copied into local
|
||||
ignored `logs/grind-research-copy.i64`. The original was not modified. The
|
||||
database has sparse function boundaries; bounded definitions are needed even
|
||||
where the community symbol map supplies a name. VMX128 decompilation remains
|
||||
unreliable; cross-check vector operations against generated instruction text.
|
||||
|
||||
New core routines (not yet called by the game's active state):
|
||||
|
||||
- `fifty_fifty_candidate`:82D89150 same-primitive two-truck branch. Rejects
|
||||
balance magnitude >=.9 and local-up contact depth >=.13. Adjacent-segment
|
||||
arbitration is deliberately outside this routine.
|
||||
- `upright_normal`:82D370F8; the vertical-line fallback is the actual constant
|
||||
X axis at82139A10. `within_approach_angle`:82D88518's plane projection and
|
||||
strict cosine comparison.
|
||||
- `grind_forces::lateral_pin`:82D3FA18. Native50-50 caller82D41D70 supplies
|
||||
strength800, forward offset0, up offset.07, damping scalar.133.
|
||||
- `grind_forces::friction`:82D3FD88 after material selection. Receives actual
|
||||
support, material and engagement inputs; does not invent them. Native50-50
|
||||
caller supplies strength50 for engagement0,40 otherwise.
|
||||
- `animation::grind_control::Fade`:82BB0D40; Begin82BB0A30 evaluates attribute
|
||||
bounds. First Update only clears the Begin latch. Later updates apply the
|
||||
native target, response, acceleration and step bounds.
|
||||
- `grind_control::crouch`:82BB10F8. Begin82BB1078 seeds from physical crouching
|
||||
output. Runtime collection names for owner312 offsets1640..1652 and owner340
|
||||
offsets1760/1792/1816 still need resolving before host integration.
|
||||
|
||||
Vtable823283D0 identifies50-50: Enter82D3F318, Exit82D3F430,
|
||||
PreUpdate82D3F4E8, Update82D41D70, Fill82D41FF8. Its angular target path
|
||||
82D41E38 ->82D40890 uses existing matrix interpolation82BD3150 with weight.1,
|
||||
then hook target82BD4318 and angular-only hook drive82C05658. It does not
|
||||
teleport the deck to a spline. This should use the existing core interpolation
|
||||
and hook implementation when connected.
|
||||
|
||||
The playable-state integration is still incomplete. In particular no selector
|
||||
candidate is published, the active state adapter is absent, and MotionHost
|
||||
still rejects the unbound stock grind behaviors. These ports alone are not a
|
||||
grind-ready game build.
|
||||
Reference in new issue
Block a user