mirror of
https://github.com/SK8-ENGINE/skate-3-rust-engine.git
synced 2026-10-26 19:47:44 +00:00
Connect native stalled grind release phase and outward exit forces
This commit is contained in:
1 parent
c8a9a7caa2
commit
e0b119d729
3 files changed
+111
No files matched your search
@@ -164,3 +164,41 @@ fn cross(a: V, b: V) -> V {
|
||||
0.0,
|
||||
]
|
||||
}
|
||||
|
||||
/// Grind PreUpdate82D3F4E8 / exit decision82D40DA0. Counts are native
|
||||
/// simulation updates, not render frames. Once leaving, the phase is latched.
|
||||
#[derive(Default)]
|
||||
pub struct Release {
|
||||
updates: u32,
|
||||
leaving_updates: u32,
|
||||
pub leaving: bool,
|
||||
}
|
||||
impl Release {
|
||||
pub fn begin_update(&mut self, speed: f32, explicit_exit: bool) {
|
||||
self.leaving |= explicit_exit || (self.updates > 60 && speed < 0.15);
|
||||
}
|
||||
pub fn finish_update(&mut self) -> bool {
|
||||
self.leaving_updates = if self.leaving { self.leaving_updates.wrapping_add(1) } else { 0 };
|
||||
self.updates = self.updates.wrapping_add(1);
|
||||
self.leaving_updates > 35
|
||||
}
|
||||
}
|
||||
|
||||
///82D3F850: push away from the contacted edge until the native lateral
|
||||
///speed limit is reached. Ledge engagement may supply its outward direction.
|
||||
pub fn release_force(
|
||||
position: V, point: V, direction: V, normal: V, velocity: V,
|
||||
surface_kind: u32, surface_side: V, force_across: bool,
|
||||
strength: f32, speed_limit: f32, lift: f32,
|
||||
) -> V {
|
||||
let outward = if !force_across && surface_kind != 0 {
|
||||
scale(surface_side, -1.0)
|
||||
} else {
|
||||
let across = cross(direction, normal);
|
||||
scale(across, if dot3(across, sub(position, point)) > 0.0 { 1.0 } else { -1.0 })
|
||||
};
|
||||
if dot3(velocity, outward) >= speed_limit { return [0.0; 4]; }
|
||||
let lateral = scale(outward, strength);
|
||||
let upward = normal[1].max(0.0) * lift;
|
||||
core::array::from_fn(|i| normal[i].mul_add(upward, lateral[i]))
|
||||
}
|
||||
@@ -37,6 +37,7 @@ pub(crate) struct Runtime {
|
||||
test_depth_epsilon: f32,
|
||||
test_depth: f32,
|
||||
exiting: bool,
|
||||
release: grind_forces::Release,
|
||||
surface_kind: u32,
|
||||
surface_normal: V,
|
||||
surface_side: V,
|
||||
@@ -80,6 +81,7 @@ impl Runtime {
|
||||
test_depth_epsilon: data.float("physics_grinds", "default", "TestDepthEpsilon")?,
|
||||
test_depth: data.float("physics_grinds", "default", "TestDepth")?,
|
||||
exiting: false,
|
||||
release: grind_forces::Release::default(),
|
||||
surface_kind: 0,
|
||||
surface_normal: [0.0, 1.0, 0.0, 0.0],
|
||||
surface_side: [0.0; 4],
|
||||
@@ -492,6 +494,7 @@ pub(super) fn enter(physics: &mut GamePhysics, skater: &mut SkaterRuntime) -> Re
|
||||
| PhysicalStateId::GrindDarkslide
|
||||
);
|
||||
physics.grind.exiting = false;
|
||||
physics.grind.release = grind_forces::Release::default();
|
||||
physics.grind.distance = 0.0;
|
||||
physics.grind.previous_position = super::solve::deck_frame(&physics.board)[3];
|
||||
skater.ground_lifecycle.skeleton_elapsed_16505 = true;
|
||||
@@ -530,6 +533,20 @@ pub(super) fn update(physics: &mut GamePhysics, skater: &mut SkaterRuntime) -> R
|
||||
physics.grind.launched = false;
|
||||
let board = super::solve::deck_frame(&physics.board);
|
||||
let p = &skater.player_input.processed;
|
||||
if physics.grind.active {
|
||||
//82D40D90; Boardslide82D41788 only exposes this exit on kind2.
|
||||
let explicit = p.flags_2476 & 0x2000_0000 != 0
|
||||
&& (!matches!(physics.grind.kind, 1 | 5) || physics.grind.surface_kind == 2);
|
||||
let was_leaving = physics.grind.release.leaving;
|
||||
physics.grind.release.begin_update(p.scalar_2652, explicit);
|
||||
if !was_leaving && physics.grind.release.leaving {
|
||||
bevy::log::info!("GRIND_RELEASE kind={} speed={} explicit={}", physics.grind.kind, p.scalar_2652, explicit);
|
||||
}
|
||||
if physics.grind.release.finish_update() {
|
||||
//82D3F638: byte37/value124, using the retained request owner.
|
||||
skater.wipeout.state.request(17, 0.0);
|
||||
}
|
||||
}
|
||||
if physics.grind.active && p.flags_2468 & 0x00400000 != 0 {
|
||||
let c = physics
|
||||
.grind
|
||||
@@ -604,6 +621,31 @@ pub(super) fn update(physics: &mut GamePhysics, skater: &mut SkaterRuntime) -> R
|
||||
grind_contact::upright_normal(c.direction)
|
||||
};
|
||||
physics.grind.crouch = dot3(board[2], normal).abs() * 0.4;
|
||||
if physics.grind.release.leaving {
|
||||
let surface = physics.grind.surface_kind;
|
||||
let (strength, limit, lift, force_across) = match physics.grind.kind {
|
||||
//50-50 exit82D41DF0, 5-O exit82D428C0.
|
||||
0 => if surface < 2 { (100.0, 1.0, 125.0, false) } else { (10.0, 1.0, 0.0, false) },
|
||||
3 => if surface < 2 { (120.0, 0.9, 125.0, false) } else { (10.0, 0.9, 0.0, false) },
|
||||
//Static rail Tipslide82D420A0 and Backslash82D42A80.
|
||||
2 => (100.0, 1.5, 0.0, true),
|
||||
4 => (100.0, 0.75, if surface == 1 { 125.0 } else { 0.0 }, surface == 1),
|
||||
//Boardslide82D41250 enters the existing runout/wipeout path
|
||||
//on rail/ledge engagement; kind2 uses a physical side force.
|
||||
_ => (90.0, 0.75, 0.0, false),
|
||||
};
|
||||
if matches!(physics.grind.kind, 1 | 5) && surface != 2 {
|
||||
skater.wipeout.state.request(10, 0.0);
|
||||
} else {
|
||||
append(physics, grind_forces::release_force(
|
||||
board[3], c.centre, c.direction, normal, velocity, surface,
|
||||
physics.grind.surface_side, force_across, strength, limit, lift,
|
||||
))?;
|
||||
}
|
||||
physics.riding.update_grind_reckoning(
|
||||
&mut skater.air_reckoning.state, normal, board[2], p.flags_2468, 0.9);
|
||||
return super::input_phase::update_ground(physics, skater);
|
||||
}
|
||||
let across = cross(c.direction, normal);
|
||||
physics.grind.control.update(
|
||||
physics.grind.kind,
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# Grind release at low speed and rail ends
|
||||
|
||||
TU3 reference: default_82000000_011B0000.bin, base82000000,
|
||||
SHA256 f4aa113eb541bfba03dbc108cf5ab43f58c965b20fa3b82f9c40938a0ad841c4.
|
||||
Static generated instruction extracts are under logs/biped-<address>.txt.
|
||||
|
||||
Observed: PreUpdate82D3F4E8 owns a latched leaving phase. Decision82D40DA0
|
||||
enters it after more than60 state updates when Processed2652 speed is below
|
||||
0.15m/s, or on the family exit condition. It dispatches exit forces instead
|
||||
of the ordinary holding-force update. More than35 leaving updates requests
|
||||
native reason17. The previous host never ran this phase and continued pinning
|
||||
and orienting the board while stationary.
|
||||
|
||||
Connected Release state and the existing physical body/force/request owners.
|
||||
50-50 exit82D41DF0 uses100 side/125 lift below investigation kind2, otherwise
|
||||
10 side/0 lift, capped by outward speed1.0. Helper82D3F850 uses the actual
|
||||
contact side and support normal. Other static-rail families use their native
|
||||
exit calls82D428C0/82D420A0/82D42A80/82D41250. Ordinary friction/pinning and
|
||||
orientation updates do not run during release. GRIND_RELEASE records the
|
||||
transition during user gameplay.
|
||||
|
||||
Endpoint queries already clip segment intersections to[0,1]; losing the last
|
||||
contact makes the existing selector enter Nonspecific701 and disable the
|
||||
hook, then choose airborne/ground from actual contacts. No infinite extension
|
||||
or endpoint teleport was added. The release phase addresses a board that
|
||||
stalls while its last truck/tip remains supported at an endpoint.
|
||||
|
||||
Scope: common slow-speed/explicit release and static-rail exit forces.
|
||||
Specialized tipslide drop-in and investigation-driven early-exit branches
|
||||
remain separate existing gaps; this does not claim full retail parity.
|
||||
Build only, no tests or automated gameplay. Visual checking is left to user.
|
||||
Reference in new issue
Block a user