mirror of
https://github.com/SK8-ENGINE/skate-3-rust-engine.git
synced 2026-10-26 19:47:44 +00:00
Correct handplant bail dispatch and implement RevertGround physics
This commit is contained in:
1 parent
ab8f889603
commit
0ddc174a51
15 files changed
+386
-5
No files matched your search
@@ -293,7 +293,7 @@ impl BoardGroundState {
|
||||
|
||||
/// Complete8296EBB0, including its small-vector gate and ONE reciprocal-square-
|
||||
/// root refinement per vector. Native82C07D20 compares this angle with pi/4.
|
||||
pub(crate) fn angle_between(a: Vector3, b: Vector3) -> f32 {
|
||||
pub fn angle_between(a: Vector3, b: Vector3) -> f32 {
|
||||
let a_squared = dot(a, a);
|
||||
let b_squared = dot(b, b);
|
||||
let epsilon = f32::from_bits(0x38D1_B717);
|
||||
|
||||
@@ -127,7 +127,7 @@ pub fn check_animation(state: &mut Requests, s: &Settings, mode: &Mode, f: &Fram
|
||||
}
|
||||
}
|
||||
|
||||
///82D8FDC0, shared FootPlant/HandPlant postphysics collision checks.
|
||||
///82D8FDC0, FootPlant postphysics checks. HandPlant calls check_air(false).
|
||||
pub fn check_plant(state: &mut Requests, s: &Settings, f: &Frame) {
|
||||
state.mode = 3;
|
||||
if f.maximum_pose_error > s.ground.max_squash {
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
use super::*;
|
||||
use crate::physics::skeleton_animation_record::IDENTITY;
|
||||
use crate::point_graph::PointGraph;
|
||||
#[test]
|
||||
fn handplant_air_check_preserves_contact_threshold_and_real_impact_bails() {
|
||||
let mut f=input();
|
||||
f.category=600;
|
||||
f.closing_velocity=[0.2,0.0,0.0,0.0];
|
||||
let mut s=settings();
|
||||
s.air.xz_trick=0.1;
|
||||
s.air.y_trick=0.6;
|
||||
let mut foot=Requests::new();
|
||||
check_plant(&mut foot,&s,&f);
|
||||
assert!(foot.reasons[2]);
|
||||
//HandPlant82D4C530 calls Air(false); a coping contact is not by itself
|
||||
//the sideways-board danger branch that enables the tiny trick limits.
|
||||
let mut hand=Requests::new();
|
||||
check_air(&mut hand,&s,&mode(),&f,false);
|
||||
assert_eq!(hand.count,0);
|
||||
f.closing_velocity=[11.0,0.0,0.0,0.0];
|
||||
check_air(&mut hand,&s,&mode(),&f,false);
|
||||
assert!(hand.reasons[2]);
|
||||
}
|
||||
fn input() -> Frame {
|
||||
Frame {
|
||||
flags_2468: 0,
|
||||
|
||||
@@ -461,6 +461,7 @@ mod tests {
|
||||
.insert_resource(images)
|
||||
.insert_resource(Menu {
|
||||
open: false, selected: 0, settings: GraphicsSettings::default(),
|
||||
maps: Vec::new(), selected_map: 0,
|
||||
difficulty: Difficulty::Easy, path: PathBuf::new(), supported_msaa: vec![1, 2, 4, 8], status: String::new(),
|
||||
})
|
||||
.add_systems(Update, apply);
|
||||
|
||||
@@ -39,6 +39,7 @@ mod grind;
|
||||
mod ground_phase;
|
||||
mod ground_animation;
|
||||
mod slide_state;
|
||||
mod revert_state;
|
||||
mod ground_exit;
|
||||
mod ground_runtime;
|
||||
mod input_phase;
|
||||
|
||||
@@ -112,6 +112,7 @@ pub(super) fn advance(
|
||||
}
|
||||
player_state::pre_state(physics, skater)?;
|
||||
match skater.player_state.current() {
|
||||
skate_core::player::state::PhysicalStateId::RevertGround => super::revert_state::update(physics,skater)?,
|
||||
skate_core::player::state::PhysicalStateId::HandPlant => super::handplant::update(physics,skater)?,
|
||||
skate_core::player::state::PhysicalStateId::FootPlant => super::footplant::ground::update(physics, skater)?,
|
||||
skate_core::player::state::PhysicalStateId::Boneless => super::boneless::update(physics, skater)?,
|
||||
|
||||
@@ -27,6 +27,46 @@ pub(crate) struct GroundPumpingMode {
|
||||
pub unintentional_scalar: f32,
|
||||
}
|
||||
impl GroundPumping {
|
||||
///Revert82D435FC..36A0 uses the processed timestep and flag2472 bit16.
|
||||
pub fn update_revert(
|
||||
&self,
|
||||
state: &mut PumpingState,
|
||||
toolkit: &BoardToolkit,
|
||||
riding: &RidingOutputs,
|
||||
skeleton: &AnimatedSkeleton,
|
||||
p: &skate_core::player::input_phase::ProcessedPhysicsInput,
|
||||
balance: f32,
|
||||
) -> Result<(), String> {
|
||||
let angle = if balance != 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
(skate_core::physics::board_ground::angle_between(
|
||||
xyz(toolkit.deck[2]),
|
||||
riding.reckoning.up,
|
||||
)
|
||||
.abs()
|
||||
- std::f32::consts::FRAC_PI_2)
|
||||
.abs()
|
||||
};
|
||||
let sample = PumpingSample {
|
||||
position: toolkit.deck[3],
|
||||
normal: v(riding.reckoning.ground_normal),
|
||||
com_to_deck_world: skeleton.record.com_to_deck_world,
|
||||
deck_angle: angle,
|
||||
intentional_pumping: ((p.flags_2472 >> 16) & 1) as u8,
|
||||
};
|
||||
match controller::update(
|
||||
state,
|
||||
&self.settings,
|
||||
self.mode(p.state_variant_index_2528)?.controller,
|
||||
sample,
|
||||
p.timestep_2604,
|
||||
&mut NativePumpingGeometry,
|
||||
) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(never) => match never {},
|
||||
}
|
||||
}
|
||||
pub fn load(data: &Collections) -> Result<Self, String> {
|
||||
let f = |field| data.float("physics_pumping", "default", field);
|
||||
let c = |field| super::settings::curve8(data, "physics_pumping", "default", field);
|
||||
|
||||
@@ -18,6 +18,7 @@ pub(super) fn advance(physics: &mut GamePhysics, skater: &mut SkaterRuntime) ->
|
||||
| PhysicalStateId::FootPlant | PhysicalStateId::Boneless | PhysicalStateId::HandPlant
|
||||
| PhysicalStateId::GroundAnimation
|
||||
| PhysicalStateId::SlideGround
|
||||
| PhysicalStateId::RevertGround
|
||||
| PhysicalStateId::WipeoutGround
|
||||
| PhysicalStateId::Teleporting
|
||||
| PhysicalStateId::BipedAir
|
||||
|
||||
@@ -19,6 +19,7 @@ pub(super) fn publish(physics: &mut GamePhysics, skater: &mut SkaterRuntime) ->
|
||||
| PhysicalStateId::FootPlant | PhysicalStateId::Boneless | PhysicalStateId::HandPlant
|
||||
| PhysicalStateId::GroundAnimation
|
||||
| PhysicalStateId::SlideGround
|
||||
| PhysicalStateId::RevertGround
|
||||
| PhysicalStateId::WipeoutGround
|
||||
| PhysicalStateId::Teleporting
|
||||
| PhysicalStateId::BipedAir
|
||||
@@ -58,6 +59,7 @@ pub(super) fn publish(physics: &mut GamePhysics, skater: &mut SkaterRuntime) ->
|
||||
// State63 is set only by LandingOnDeckManager::Fill82D4E0E8..E0F4
|
||||
// when byte246 && time240<.02. Ordinary Ground never invokes that Fill,
|
||||
// so it retains the template zero here; State65 carries Ground requests.
|
||||
set(66, state == PhysicalStateId::RevertGround && skater.revert_state.active);
|
||||
set(65, skater.wipeout.requests_wipeout(p));
|
||||
set(78, skater.wipeout.requests_runout(p));
|
||||
set(71, player.flags_1296 & (1 << 24) != 0);
|
||||
@@ -128,6 +130,7 @@ pub(super) fn publish(physics: &mut GamePhysics, skater: &mut SkaterRuntime) ->
|
||||
physical.state = CurrentStateFields {
|
||||
category_12: state.category(),
|
||||
state_16: state as u32,
|
||||
flag_66: u8::from(state == PhysicalStateId::RevertGround && skater.revert_state.active),
|
||||
flag_74: u8::from(state==PhysicalStateId::HandPlant && skater.handplant.continuation),
|
||||
//ProcessOutput82DB7104..7128: selector57 requests State69 here.
|
||||
//The next input/selection enters702; never reset inside selection.
|
||||
|
||||
@@ -21,6 +21,7 @@ impl PhysicalStateCalls for Calls {
|
||||
| PhysicalStateId::FootPlant | PhysicalStateId::Boneless | PhysicalStateId::HandPlant
|
||||
| PhysicalStateId::GroundAnimation
|
||||
| PhysicalStateId::SlideGround
|
||||
| PhysicalStateId::RevertGround
|
||||
| PhysicalStateId::WipeoutGround
|
||||
| PhysicalStateId::Teleporting
|
||||
| PhysicalStateId::BipedAir
|
||||
@@ -38,6 +39,7 @@ impl PhysicalStateCalls for Calls {
|
||||
| PhysicalStateId::FootPlant | PhysicalStateId::Boneless | PhysicalStateId::HandPlant
|
||||
| PhysicalStateId::GroundAnimation
|
||||
| PhysicalStateId::SlideGround
|
||||
| PhysicalStateId::RevertGround
|
||||
| PhysicalStateId::WipeoutGround
|
||||
| PhysicalStateId::Teleporting
|
||||
| PhysicalStateId::BipedAir
|
||||
@@ -67,6 +69,7 @@ pub(super) fn set(
|
||||
| PhysicalStateId::FootPlant | PhysicalStateId::Boneless | PhysicalStateId::HandPlant
|
||||
| PhysicalStateId::GroundAnimation
|
||||
| PhysicalStateId::SlideGround
|
||||
| PhysicalStateId::RevertGround
|
||||
| PhysicalStateId::WipeoutGround
|
||||
| PhysicalStateId::Teleporting
|
||||
| PhysicalStateId::BipedAir
|
||||
@@ -80,6 +83,7 @@ pub(super) fn set(
|
||||
| PhysicalStateId::FootPlant | PhysicalStateId::Boneless | PhysicalStateId::HandPlant
|
||||
| PhysicalStateId::GroundAnimation
|
||||
| PhysicalStateId::SlideGround
|
||||
| PhysicalStateId::RevertGround
|
||||
| PhysicalStateId::WipeoutGround
|
||||
| PhysicalStateId::Teleporting
|
||||
| PhysicalStateId::BipedAir
|
||||
@@ -161,6 +165,7 @@ pub(super) fn set(
|
||||
//Native82DB8540 publishes Processed state/history BEFORE old Exit. The
|
||||
//same retained objects then receive Exit followed by the new Enter.
|
||||
match current {
|
||||
PhysicalStateId::RevertGround => {}, //Exit82B61BB8
|
||||
PhysicalStateId::HandPlant => skater.handplant.reset(),
|
||||
PhysicalStateId::FootPlant => skater.footplant.reset(), //Exit82D4C5A8
|
||||
PhysicalStateId::Boneless => {}, //empty82D4C9B4
|
||||
@@ -179,6 +184,7 @@ pub(super) fn set(
|
||||
_ => unreachable!("state support checked before publication"),
|
||||
}
|
||||
match requested {
|
||||
PhysicalStateId::RevertGround => super::super::revert_state::enter(physics, skater),
|
||||
PhysicalStateId::HandPlant => super::super::handplant::enter(physics, skater),
|
||||
PhysicalStateId::FootPlant => super::super::footplant::ground::enter(physics, skater),
|
||||
PhysicalStateId::Boneless => super::super::boneless::enter(physics, skater),
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
//! RevertGround102, TU3 vtable82327330: Enter82D43488, Update82D43518,
|
||||
//! empty Exit, Ground Post82D4C070, Fill82D43B10. Stock layout018E8A2E5028AB3F.
|
||||
use super::{GamePhysics, SkaterRuntime, ground_runtime::GroundInputObservations};
|
||||
use skate_core::{
|
||||
math::Vector3,
|
||||
physics::{force_queue::QueuedPointForce, manual::controller},
|
||||
point_graph::PointGraph,
|
||||
riding::{anti_flip, collision_response::signed_angle, pumping},
|
||||
};
|
||||
use skate_data::collections::Collections;
|
||||
type V = [f32; 4];
|
||||
pub(crate) struct RevertState {
|
||||
speed: PointGraph<8>,
|
||||
tolerance: PointGraph<4>,
|
||||
gain: f32,
|
||||
delay: f32,
|
||||
duration: f32,
|
||||
direction: f32,
|
||||
travel_sign: f32,
|
||||
velocity: V,
|
||||
elapsed: f32,
|
||||
captured: bool,
|
||||
pub active: bool,
|
||||
}
|
||||
impl RevertState {
|
||||
pub fn load(d: &Collections) -> Result<Self, String> {
|
||||
let class = "Hash_018E8A2E5028AB3F";
|
||||
let a = d
|
||||
.words::<20>(class, "default", "Hash_DAA990E803A782A1")?
|
||||
.map(f32::from_bits);
|
||||
let b = d
|
||||
.words::<12>(class, "default", "Hash_3387562E9BEA0AF6")?
|
||||
.map(f32::from_bits);
|
||||
Ok(Self {
|
||||
speed: PointGraph {
|
||||
x: a[4..12].try_into().unwrap(),
|
||||
y: a[12..20].try_into().unwrap(),
|
||||
},
|
||||
tolerance: PointGraph {
|
||||
x: b[4..8].try_into().unwrap(),
|
||||
y: b[8..12].try_into().unwrap(),
|
||||
},
|
||||
gain: d.float(class, "default", "Hash_0E5B939FD4ECDBAA")?,
|
||||
delay: d.float(class, "default", "Hash_6BF6544475D2A5AD")?,
|
||||
duration: d.float(class, "default", "Hash_035D9805EB9E2EE2")?,
|
||||
direction: 0.0,
|
||||
travel_sign: 1.0,
|
||||
velocity: [0.0; 4],
|
||||
elapsed: 0.0,
|
||||
captured: false,
|
||||
active: false,
|
||||
})
|
||||
}
|
||||
///82D43800: directed remaining angle, stock speed curve and rate damping.
|
||||
fn correction(&mut self, forward: V, normal: V, angular: V) -> V {
|
||||
if !self.captured {
|
||||
return [0.0; 4];
|
||||
}
|
||||
let desired = forward.map(|v| v * self.travel_sign);
|
||||
//Preserve the two native self-scaled projections82D438B4..D0.
|
||||
let project = |v: V| {
|
||||
let d = dot(v, normal);
|
||||
v.map(|x| x - x * d)
|
||||
};
|
||||
let from = project(self.velocity);
|
||||
let to = project(desired);
|
||||
if dot(from, from) * dot(to, to) <= f32::from_bits(0x37800000) {
|
||||
return [0.0; 4];
|
||||
}
|
||||
let normalize = |v: V| {
|
||||
let length = dot(v, v).sqrt();
|
||||
v.map(|x| x / length)
|
||||
};
|
||||
let mut angle = signed_angle(xyz(normalize(from)), xyz(normalize(to)), xyz(normal));
|
||||
//8258DB98 folds the helper's[0,2pi] output before the direction test.
|
||||
if angle >= std::f32::consts::PI {
|
||||
angle -= std::f32::consts::TAU;
|
||||
}
|
||||
if angle * self.direction > 0.0 {
|
||||
angle -= self.direction * std::f32::consts::TAU;
|
||||
}
|
||||
let target = self.speed.evaluate(angle.abs()) * self.direction;
|
||||
if angle.abs() < self.tolerance.evaluate(normal[1]) * f32::from_bits(0x3c8efa35)
|
||||
|| angle.abs() > 4.5
|
||||
{
|
||||
self.active = false;
|
||||
}
|
||||
let current = dot(angular, normal);
|
||||
let correction = if current * target > 0.0 && current.abs() > target.abs() {
|
||||
0.0
|
||||
} else {
|
||||
(target - current) * self.gain
|
||||
};
|
||||
normal.map(|v| v * correction)
|
||||
}
|
||||
}
|
||||
pub(super) fn enter(physics: &mut GamePhysics, skater: &mut SkaterRuntime) -> Result<(), String> {
|
||||
skater.ground_lifecycle.skeleton_elapsed_16505 = true;
|
||||
physics
|
||||
.board
|
||||
.hook_mut()
|
||||
.drive
|
||||
.disable_animation(&mut skater.ground_lifecycle.board_animated_290);
|
||||
let r = &mut skater.revert_state;
|
||||
r.direction = skater.animation_input.extra.revert_direction;
|
||||
r.velocity = [0.0; 4];
|
||||
r.elapsed = 0.0;
|
||||
r.captured = false;
|
||||
r.active = true;
|
||||
skater.wipeout.state.enter_ground();
|
||||
Ok(())
|
||||
}
|
||||
pub(super) fn update(physics: &mut GamePhysics, skater: &mut SkaterRuntime) -> Result<(), String> {
|
||||
let p = &skater.player_input.processed;
|
||||
let t = skater
|
||||
.player_input
|
||||
.toolkit
|
||||
.as_ref()
|
||||
.ok_or("Revert requires current BoardToolkit")?;
|
||||
let r = &mut skater.revert_state;
|
||||
if !r.captured && r.elapsed >= r.delay {
|
||||
r.captured = true;
|
||||
r.velocity = p.vectors_400_416[0].map(f32::from_bits);
|
||||
r.travel_sign = if dot(r.velocity, t.deck[2]) > 0.0 {
|
||||
-1.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
}
|
||||
physics.riding.update_slide_reckoning(
|
||||
p,
|
||||
t,
|
||||
skater.animation_input.extra.physical_body_spin,
|
||||
skater.animation_input.fields.balance,
|
||||
);
|
||||
super::input_phase::update_ground(physics, skater)?;
|
||||
let p = &skater.player_input.processed;
|
||||
let t = skater
|
||||
.player_input
|
||||
.toolkit
|
||||
.as_ref()
|
||||
.ok_or("Revert requires current BoardToolkit")?;
|
||||
skater.ground.pumping_settings.update_revert(
|
||||
&mut skater.ground.pumping,
|
||||
t,
|
||||
&physics.riding,
|
||||
&skater.animated_skeleton,
|
||||
p,
|
||||
skater.animation_input.fields.balance,
|
||||
)?;
|
||||
let input = skater.ground_settings.input(
|
||||
t,
|
||||
p,
|
||||
&skater.animation_input,
|
||||
&skater.ground.pumping,
|
||||
skater
|
||||
.ground
|
||||
.pumping_settings
|
||||
.mode(p.state_variant_index_2528)?
|
||||
.unintentional_scalar,
|
||||
&physics.riding,
|
||||
&skater.animated_skeleton,
|
||||
physics.settings.step.base_truck_transforms,
|
||||
GroundInputObservations {
|
||||
manual_drag_2724: skater.ground_lifecycle.manual_drag_2724,
|
||||
trajectory_state_bits: p.external_physics_1616.flags,
|
||||
edge_flags: 0,
|
||||
edge_point: [0.0; 4],
|
||||
},
|
||||
);
|
||||
let settings = skater.ground_settings.board();
|
||||
let correction = skater.revert_state.correction(
|
||||
t.deck[2],
|
||||
p.vectors_464_480_496_512_528[0].map(f32::from_bits),
|
||||
p.vectors_720_784_800_816_832_864[0].map(f32::from_bits),
|
||||
);
|
||||
//82C040F0(0): zero linear drag on every board body.
|
||||
for body in physics.board.bodies_mut() {
|
||||
body.inertia.linear_drag = 0.0;
|
||||
}
|
||||
let anti_flip = anti_flip::calculate(settings.anti_flip, &input.anti_flip);
|
||||
let pump = pumping::calculate(&input.pump_force);
|
||||
let manual = controller::calculate(
|
||||
&mut skater.ground.manual,
|
||||
settings.manual,
|
||||
settings.manual_mode,
|
||||
&input.manual,
|
||||
&mut skater.ground_runtime,
|
||||
)
|
||||
.map_err(|e| format!("Revert manual: {e:?}"))?;
|
||||
for value in [correction, manual.angular_displacement, anti_flip] {
|
||||
skater
|
||||
.ground_runtime
|
||||
.apply_angular_displacement(&mut physics.board, value);
|
||||
}
|
||||
physics.board.forces_mut().append(QueuedPointForce {
|
||||
tag: 8,
|
||||
force_world: Vector3::new(pump[0], pump[1], pump[2]),
|
||||
point_body: Vector3::new(pump[4], pump[5], pump[6]),
|
||||
});
|
||||
skater.revert_state.elapsed += p.timestep_2604;
|
||||
if skater.revert_state.elapsed > skater.revert_state.duration {
|
||||
skater.revert_state.active = false;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn dot(a: V, b: V) -> f32 {
|
||||
a[2].mul_add(b[2], a[1].mul_add(b[1], a[0] * b[0]))
|
||||
}
|
||||
fn xyz(v: V) -> Vector3 {
|
||||
Vector3::new(v[0], v[1], v[2])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
fn state(direction: f32) -> RevertState {
|
||||
RevertState {
|
||||
speed: PointGraph {
|
||||
x: [0.0, 0.1, 0.2, 0.3, 1.0, 2.0, 3.0, 7.0],
|
||||
y: [5.0; 8],
|
||||
},
|
||||
tolerance: PointGraph {
|
||||
x: [0.0, 0.2, 0.6, 1.0],
|
||||
y: [8.0; 4],
|
||||
},
|
||||
gain: 0.06,
|
||||
delay: 0.0,
|
||||
duration: 1.0,
|
||||
direction,
|
||||
travel_sign: -1.0,
|
||||
velocity: [0.0, 0.0, 6.0, 0.0],
|
||||
elapsed: 0.0,
|
||||
captured: true,
|
||||
active: true,
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn revert_steers_both_directions_and_does_not_brake_faster_matching_spin() {
|
||||
for direction in [-1.0, 1.0] {
|
||||
let mut r = state(direction);
|
||||
let correction = r.correction([0.0, 0.0, 1.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0; 4]);
|
||||
assert!((correction[1] - 0.3 * direction).abs() < 0.00001);
|
||||
assert!(r.active);
|
||||
assert_eq!(
|
||||
r.correction(
|
||||
[0.0, 0.0, 1.0, 0.0],
|
||||
[0.0, 1.0, 0.0, 0.0],
|
||||
[0.0, 6.0 * direction, 0.0, 0.0]
|
||||
),
|
||||
[0.0; 4]
|
||||
);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn revert_completes_when_board_faces_captured_reverse_heading() {
|
||||
let mut r = state(1.0);
|
||||
r.correction([0.0, 0.0, -1.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0; 4]);
|
||||
assert!(!r.active);
|
||||
r.velocity = [0.0; 4];
|
||||
assert_eq!(
|
||||
r.correction([0.0, 0.0, 1.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0; 4]),
|
||||
[0.0; 4]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ pub(crate) struct SkaterRuntime {
|
||||
pub known_air: super::known_air::KnownAir,
|
||||
pub ground_animation: super::ground_animation::GroundAnimationRuntime,
|
||||
pub ground_animation_settings: super::ground_animation::GroundAnimationSettings,
|
||||
pub revert_state: super::revert_state::RevertState,
|
||||
pub slide_state: super::slide_state::SlideState,
|
||||
pub trajectory: super::air_trajectory::AirTrajectoryRuntime,
|
||||
pub footplant: super::footplant::Footplant,
|
||||
@@ -205,6 +206,7 @@ impl SkaterRuntime {
|
||||
known_air: super::known_air::KnownAir::load(&data)?,
|
||||
ground_animation: Default::default(),
|
||||
ground_animation_settings: super::ground_animation::GroundAnimationSettings::load(&data)?,
|
||||
revert_state: super::revert_state::RevertState::load(&data)?,
|
||||
slide_state: super::slide_state::SlideState::load(&data)?,
|
||||
trajectory: super::air_trajectory::AirTrajectoryRuntime::load(&data)?,
|
||||
footplant: super::footplant::Footplant::load(&data)?,
|
||||
|
||||
@@ -95,7 +95,23 @@ pub(super) fn check_after_physics(
|
||||
};
|
||||
match skater.player_state.current() {
|
||||
PhysicalStateId::FootPlant | PhysicalStateId::HandPlant => {
|
||||
wipeout::check_plant(&mut skater.wipeout.state, &skater.wipeout.settings, &observations.frame()?);
|
||||
let frame = observations.frame()?;
|
||||
if skater.player_state.current() == PhysicalStateId::HandPlant {
|
||||
//HandPlant vtable823273CC slot40 ->82D4C530 ->82D90358(false).
|
||||
//FootPlant's82D4C6D8 alone uses82D8FDC0. Its low trick-impact
|
||||
//thresholds must not replace HandPlant's normal air checks.
|
||||
skater.wipeout.check_air(&observations, false)?;
|
||||
} else {
|
||||
wipeout::check_plant(&mut skater.wipeout.state, &skater.wipeout.settings, &frame);
|
||||
}
|
||||
if skater.wipeout.state.count != 0 {
|
||||
let reasons: Vec<_> = skater.wipeout.state.reasons.iter().enumerate()
|
||||
.filter_map(|(i, &active)| active.then_some(i)).collect();
|
||||
bevy::log::info!("PLANT_BAIL tick={} state={:?} reasons={reasons:?} pose_error={:?} max_pose_error={} regions={:?} closing={:?} board_contact={} world_to_animation={:?}",
|
||||
physics.ticks, skater.player_state.current(), frame.pose_error,
|
||||
frame.maximum_pose_error, frame.regions_force, frame.closing_velocity,
|
||||
frame.board_contact, frame.world_to_animation);
|
||||
}
|
||||
if skater.player_state.current() == PhysicalStateId::FootPlant {
|
||||
super::footplant::ground::post_physics(skater);
|
||||
}
|
||||
@@ -127,7 +143,7 @@ pub(super) fn check_after_physics(
|
||||
super::offboard::air_diagnostics::record(skater);
|
||||
Ok(())
|
||||
}
|
||||
PhysicalStateId::PhysicsGround | PhysicalStateId::SlideGround => {
|
||||
PhysicalStateId::PhysicsGround | PhysicalStateId::SlideGround | PhysicalStateId::RevertGround => {
|
||||
skater.wipeout.check_ground(&observations)
|
||||
}
|
||||
PhysicalStateId::GroundAnimation => {
|
||||
|
||||
@@ -853,12 +853,14 @@ mod tests {
|
||||
let mut queue = bevy::ecs::world::CommandQueue::default();
|
||||
let mut meshes = Assets::<Mesh>::default();
|
||||
let mut materials = Assets::<StandardMaterial>::default();
|
||||
let mut retail_materials = Assets::<crate::retail_render::RetailWorldMaterial>::default();
|
||||
let mut images = Assets::<Image>::default();
|
||||
spawn(
|
||||
&map,
|
||||
&mut Commands::new(&mut queue, &world),
|
||||
&mut meshes,
|
||||
&mut materials,
|
||||
&mut retail_materials,
|
||||
&mut images,
|
||||
);
|
||||
queue.apply(&mut world);
|
||||
|
||||
+23
-1
@@ -117,7 +117,7 @@ operands take precedence over unreliable decompiler output and community names.
|
||||
| Handplant skeleton | State600 vtable `823273CC`; entry/update `82D4C350/82D4C3D8`; reckoning `82D8E1C0`; hand IK `82D633B0`, target clamp `82BD9728/82BD97D0`, upper-body volume suppression `82D91298`. |
|
||||
| Shared skeleton | Anchored-foot path `82BDEC48`, COM path `82BDF090`, root positioning `82BDEEB8/82BE0E80`. Uses the existing completed animation, physical skeleton, board and four-limb IK owners. |
|
||||
| Graph timing | `IsFootPlanting` `82BBD888`; preparation `82BBD7F0`; FootPlantAbsorb Update `82BBE4E0`; handplant phase/new-position `82BBDA98/82BBDB98`; antic Begin `82BBE5D0`; handplant scoring Update `82BBF758`. |
|
||||
| Lifecycle | Footplant release `82D8B970` reads big-endian byte2480 bit0, hence full-word bit24. Common handplant publication `82DB7130..82DB7190`; shared plant wipeout check `82D8FDC0`. Teleport/reset paths clear active plant state. |
|
||||
| Lifecycle | Footplant release `82D8B970` reads big-endian byte2480 bit0, hence full-word bit24. Common handplant publication `82DB7130..82DB7190`; FootPlant wipeout check `82D8FDC0`; HandPlant post `82D4C530` calls Air `82D90358(false)`. Teleport/reset paths clear active plant state. |
|
||||
|
||||
Settings are loaded from the installation, using the schema's cached layout:
|
||||
|
||||
@@ -155,3 +155,25 @@ reuse existing engine math; bit-exact Xenon/VMX execution is not claimed.
|
||||
Handplant scoring updates the existing internal score packet, whose presentation
|
||||
remains outside this change. Stock assets and private research/build files are
|
||||
excluded from the commit.
|
||||
|
||||
## Handplant bail and delayed revert crash (8 September)
|
||||
|
||||
The supplied run entered HandPlant at tick745 and WipeoutGround at746, then
|
||||
stopped at tick1318 on the missing PhysicsGround -> RevertGround adapter.
|
||||
HandPlant incorrectly used FootPlant collision checks with unconditional trick
|
||||
impact limits (stock XZ0.1/Y0.6). It now dispatches the native Air(false) checks,
|
||||
including their contact, difficulty, cooldown and actual danger-state gates.
|
||||
The original bail reason was not recorded, so the causal gameplay result still
|
||||
requires a user retest. PLANT_BAIL now records reason indices and collision/pose
|
||||
observations when either plant requests a bail.
|
||||
|
||||
RevertGround102 now owns Enter82D43488, Update82D43518, empty Exit, Ground
|
||||
Post82D4C070 and Fill82D43B10. It loads the five fields from stock class
|
||||
018E8A2E5028AB3F, captures approach velocity after the authored delay, applies
|
||||
the directed rotation-speed curve, manual/anti-flip corrections and pumping,
|
||||
and publishes active State66 for the existing selector's normal exit.
|
||||
No gameplay execution was performed during this repair.
|
||||
|
||||
Validation for this repair: six core wipeout tests and two revert numerical
|
||||
tests passed. Two pre-existing game test fixtures were updated for their current
|
||||
map-menu and render-material API signatures so these tests could compile.
|
||||
Reference in new issue
Block a user