mirror of
https://github.com/SK8-ENGINE/skate-3-rust-engine.git
synced 2026-10-26 19:47:44 +00:00
Connect live fifty-fifty grinds, stock animation and rail exits
This commit is contained in:
1 parent
88b51467e9
commit
4ddc99959b
26 files changed
+688
-46
No files matched your search
@@ -16,7 +16,11 @@ pub fn lateral_pin(
|
||||
forward_selected: bool,
|
||||
slope_multiplier: f32,
|
||||
) -> V {
|
||||
let direction = if forward_selected { board[2] } else { scale(board[2], -1.0) };
|
||||
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
|
||||
});
|
||||
@@ -26,7 +30,10 @@ pub fn lateral_pin(
|
||||
return [0.0; 4];
|
||||
}
|
||||
let axis = scale(force, length.recip());
|
||||
let damping = scale(axis, strength * f32::from_bits(0x3e08_3127) * dot3(velocity, axis));
|
||||
let damping = scale(
|
||||
axis,
|
||||
strength * f32::from_bits(0x3e08_3127) * dot3(velocity, axis),
|
||||
);
|
||||
scale(sub(force, damping), slope_multiplier)
|
||||
}
|
||||
|
||||
@@ -47,13 +54,70 @@ pub fn friction(
|
||||
if speed <= 0.001 {
|
||||
return [0.0; 4];
|
||||
}
|
||||
let strength = strengths[match engagement { 0 => 0, 1 => 1, _ => 2 }];
|
||||
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);
|
||||
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) }
|
||||
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)
|
||||
}
|
||||
|
||||
///82D73AB0 straight, static 50-50 branch: both support normals coincide,
|
||||
///primitive motion336 is zero. The manager age controls the native exit nudge.
|
||||
pub fn fifty_fifty_pop(
|
||||
velocity: V,
|
||||
normal: V,
|
||||
direction: V,
|
||||
board_position: V,
|
||||
point: V,
|
||||
height: f32,
|
||||
nudge: f32,
|
||||
manager_age: f32,
|
||||
) -> V {
|
||||
let across = cross(direction, normal);
|
||||
let balance = if manager_age < 0.31 {
|
||||
if nudge < 0.0 {
|
||||
nudge.min(-0.42)
|
||||
} else if nudge > 0.0 {
|
||||
nudge.max(0.42)
|
||||
} else if dot3(sub(board_position, point), across) > 0.0 {
|
||||
0.42
|
||||
} else {
|
||||
-0.42
|
||||
}
|
||||
} else {
|
||||
nudge
|
||||
};
|
||||
let lateral = scale(across, balance * 1.8);
|
||||
let length = dot3(lateral, lateral).sqrt();
|
||||
let lateral = if length > 1.8 {
|
||||
scale(lateral, 1.8 / length)
|
||||
} else {
|
||||
lateral
|
||||
};
|
||||
let planar = sub(velocity, scale(normal, dot3(velocity, normal)));
|
||||
core::array::from_fn(|i| normal[i].mul_add(height * 1.03, planar[i]) + lateral[i])
|
||||
}
|
||||
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]),
|
||||
(-a[1]).mul_add(b[0], a[0] * b[1]),
|
||||
0.0,
|
||||
]
|
||||
}
|
||||
@@ -310,6 +310,8 @@ impl Default for PlayerInputState {
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct ProcessedPhysicsInput {
|
||||
pub grind_position_1120: RawVector,
|
||||
pub grind_direction_1136: RawVector,
|
||||
pub effective_anim_transform_192: RawMatrix,
|
||||
pub vectors_400_416: [RawVector; 2],
|
||||
pub vectors_464_480_496_512_528: [RawVector; 5],
|
||||
@@ -395,6 +397,8 @@ pub struct ProcessedPhysicsInput {
|
||||
impl Default for ProcessedPhysicsInput {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
grind_position_1120: [0;4],
|
||||
grind_direction_1136: [0;4],
|
||||
effective_anim_transform_192: [[0; 4]; 4],
|
||||
vectors_400_416: [[0; 4]; 2],
|
||||
vectors_464_480_496_512_528: [[0; 4]; 5],
|
||||
|
||||
@@ -6,6 +6,7 @@ mod condition_nodes;
|
||||
pub(crate) use condition_nodes::numeric as parse_numeric_condition;
|
||||
mod crouching_settings;
|
||||
pub mod motion;
|
||||
pub(crate) mod motion_grind;
|
||||
mod motion_air_leg;
|
||||
mod motion_animation;
|
||||
#[path = "motion_offboard/cadence.rs"]
|
||||
|
||||
@@ -44,6 +44,7 @@ pub struct MotionPhysical {
|
||||
pub foot_frame: Option<PushFootFrame>,
|
||||
}
|
||||
enum Instance {
|
||||
Grind(super::motion_grind::State),
|
||||
OffboardAir(super::motion_offboard_air::State),
|
||||
ToggleBoard(super::motion_toggle_board::ToggleBoard),
|
||||
Cadence(super::motion_cadence::MatchCadence),
|
||||
@@ -74,6 +75,7 @@ enum Instance {
|
||||
impl Instance {
|
||||
fn new(operation: &MotionOperation) -> Self {
|
||||
match operation {
|
||||
MotionOperation::Grind(_) => Self::Grind(Default::default()),
|
||||
MotionOperation::OffboardAir(_) => Self::OffboardAir(Default::default()),
|
||||
MotionOperation::Cadence(_) => Self::Cadence(Default::default()),
|
||||
MotionOperation::AddRunoutAttribs => Self::Runout(None),
|
||||
@@ -127,6 +129,8 @@ impl Instance {
|
||||
}
|
||||
}
|
||||
pub struct MotionHost {
|
||||
pub grind_physical: super::motion_grind::Physical,
|
||||
grind_settings: super::motion_grind::Settings,
|
||||
pub offboard_output: skate_core::player::input_phase::OffBoardOutputFields,
|
||||
pub toggle_board_physical: super::motion_toggle_board::Physical,
|
||||
pub disable_dismount: bool,
|
||||
@@ -229,6 +233,8 @@ impl MotionHost {
|
||||
});
|
||||
let pushing = PushingSettings::load(data, &mut metadata)?;
|
||||
Ok(Self {
|
||||
grind_physical: Default::default(),
|
||||
grind_settings: super::motion_grind::Settings::load(data)?,
|
||||
toggle_board_physical: Default::default(),
|
||||
disable_dismount: false,
|
||||
offboard_output: Default::default(),
|
||||
|
||||
@@ -104,6 +104,23 @@ impl MotionAnimation {
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
///82BB0A30 samples the actual selected blend tree at normalized endpoints.
|
||||
///Work on a clone so endpoint probing cannot leak into the current pose.
|
||||
pub fn attribute_endpoints(&self, name: AttributeName) -> Result<[f32;2],String> {
|
||||
let source=self.current.as_ref().ok_or("Grind has no selected animation")?;
|
||||
let mut values=[0.;2];
|
||||
for i in 0..2 {
|
||||
let mut tree=source.clone();
|
||||
let mut attributes=self.settable.entries().to_vec();
|
||||
attributes.push(SettableAttribute {name,value:i as f32,normalized:true,sequence_id:-1});
|
||||
tree.set_attributes(&attributes)?;
|
||||
// During entry the destination tree owns bounds, not the fading-out pose.
|
||||
let attribute=tree.attributes(15)?.into_iter().find(|a|a.name==name)
|
||||
.ok_or("Grind animation is missing its twist attribute")?;
|
||||
values[i]=f32::from_bits(attribute.payload.0[0].ok_or("Grind twist is uninitialized")?);
|
||||
}
|
||||
Ok(values)
|
||||
}
|
||||
pub fn tree_attributes(&self) -> &[AnimationAttribute] {
|
||||
&self.tree_attributes
|
||||
}
|
||||
|
||||
@@ -72,6 +72,8 @@ pub enum MotionCondition {
|
||||
Riding(super::motion_riding_conditions::MotionRidingCondition),
|
||||
/// Native Air condition: PhysOutAnimation collision/trajectory time.
|
||||
TimeToLand(NumericCondition),
|
||||
///DistToEdge82BA8238: completed OffBoard116, not a fresh nearest-rail query.
|
||||
DistToEdge(NumericCondition),
|
||||
/// Native off-board trajectory time (PhysOutOffBoard+32).
|
||||
ObTimeToLand(NumericCondition),
|
||||
/// Native off-board trajectory time used by dismount/runout branches.
|
||||
@@ -177,6 +179,7 @@ impl MotionCondition {
|
||||
name if super::motion_riding_conditions::MotionRidingCondition::recognizes(name) => {
|
||||
Self::Riding(super::motion_riding_conditions::MotionRidingCondition::parse(a)?)
|
||||
}
|
||||
"DistToEdge" => Self::DistToEdge(numeric()),
|
||||
"TimeToLand" => Self::TimeToLand(numeric()),
|
||||
"OBTimeToLand" => Self::ObTimeToLand(numeric()),
|
||||
"OBTrajTime" => Self::ObTrajTime(numeric()),
|
||||
@@ -243,6 +246,7 @@ impl MotionCondition {
|
||||
Self::ManualOutTimerIsActive => host.riding.manual_out_timer > 0.0,
|
||||
Self::Gameplay(condition) => condition.evaluate(host)?,
|
||||
Self::Riding(condition) => condition.evaluate(host)?,
|
||||
Self::DistToEdge(n) => n.matches(host.offboard_output.distance_116),
|
||||
Self::TimeToLand(n) => n.matches(
|
||||
host.gameplay_conditions
|
||||
.as_ref()
|
||||
|
||||
@@ -15,6 +15,12 @@ impl MotionHost {
|
||||
.get(behavior)
|
||||
.ok_or("Unbound MotionGraph behavior")?;
|
||||
let operation = self.operations[id].clone();
|
||||
if let MotionOperation::Grind(operation) = operation {
|
||||
let Instance::Grind(state)=&mut self.instances[behavior] else {return Err("Grind instance mismatch".into())};
|
||||
let height=self.crouching_physical.map(|p|p.animation_height_72).ok_or("Grind requires physical height")?;
|
||||
return super::super::motion_grind::execute(state,&operation,phase,frame.dt,
|
||||
&self.grind_settings,&self.grind_physical,height,&mut self.animation);
|
||||
}
|
||||
if let MotionOperation::OffboardAir(operation) = operation {
|
||||
let Instance::OffboardAir(state) = &mut self.instances[behavior] else {
|
||||
return Err("OffboardAir instance mismatch".into());
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
//! Stock grind graph behaviors82BAF208/82BB0A30/82BB0D40/82BB10F8.
|
||||
use skate_core::animation::{
|
||||
grind_control::{Fade, FadeSettings, crouch},
|
||||
playback_parameters::{AttributeSink, SettableAttribute},
|
||||
skeleton_input::name::encode,
|
||||
};
|
||||
use skate_data::{collections::Collections, state_graph::attributes::Attributes};
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Operation {
|
||||
Attributes,
|
||||
Crouch,
|
||||
Fade {
|
||||
height: String,
|
||||
twist: String,
|
||||
intent: String,
|
||||
},
|
||||
}
|
||||
impl Operation {
|
||||
pub fn parse(a: &Attributes<'_>) -> Option<Self> {
|
||||
Some(match a.text("name")? {
|
||||
"CreateGrindAttributes" => Self::Attributes,
|
||||
"ControlGrindCrouch" => Self::Crouch,
|
||||
"GrindControlFade" => Self::Fade {
|
||||
height: a
|
||||
.text("distBoardToCogAnimAttribute")
|
||||
.unwrap_or("DistToCog")
|
||||
.into(),
|
||||
twist: a.text("twistAnimAttribute").unwrap_or("twist").into(),
|
||||
intent: a.text("twistMGIntent").unwrap_or("GrindBalanceX").into(),
|
||||
},
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
pub struct Settings {
|
||||
pub fade: FadeSettings,
|
||||
pub height: [f32; 3],
|
||||
}
|
||||
impl Settings {
|
||||
pub fn load(d: &Collections) -> Result<Self, String> {
|
||||
let f = |n| d.float("anim_motion", "grind_twist", n);
|
||||
Ok(Self {
|
||||
fade: FadeSettings {
|
||||
response: f("twist_smoothing")?,
|
||||
input_scale: f("twist_sensitivity")?,
|
||||
acceleration: f("twist_max_delta_delta")?,
|
||||
maximum_step: f("twist_max_delta")?,
|
||||
},
|
||||
height: [
|
||||
d.float("anim_motion", "grind_height", "min_grind_disttocog")?,
|
||||
d.float("anim_motion", "grind_height", "max_grind_disttocog")?,
|
||||
d.float("anim_motion", "grind_height", "grind_disttocog_speed")?,
|
||||
],
|
||||
})
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct State {
|
||||
pub fade: Fade,
|
||||
pub height: f32,
|
||||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Physical {
|
||||
pub name: String,
|
||||
pub grinding: bool,
|
||||
pub crouch: f32,
|
||||
pub twist: f32,
|
||||
pub facing_backwards: bool,
|
||||
}
|
||||
pub fn execute(
|
||||
state: &mut State,
|
||||
op: &Operation,
|
||||
phase: u8,
|
||||
dt: f32,
|
||||
settings: &Settings,
|
||||
physical: &Physical,
|
||||
height: f32,
|
||||
animation: &mut super::motion_animation::MotionAnimation,
|
||||
) -> Result<(), String> {
|
||||
let set = |animation: &mut super::motion_animation::MotionAnimation, name: &str, value: f32| {
|
||||
animation.set_attribute(SettableAttribute {
|
||||
name: encode(name.as_bytes()),
|
||||
value,
|
||||
normalized: false,
|
||||
sequence_id: -1,
|
||||
});
|
||||
};
|
||||
match op {
|
||||
Operation::Attributes if phase == 1 && physical.grinding => {
|
||||
animation.motion_intents.insert(&physical.name, 1.0);
|
||||
animation.motion_intents.insert(
|
||||
if physical.facing_backwards {
|
||||
"GrindFacingBackwards"
|
||||
} else {
|
||||
"GrindFacingForwards"
|
||||
},
|
||||
1.0,
|
||||
);
|
||||
}
|
||||
Operation::Crouch if phase == 0 => state.height = height,
|
||||
Operation::Crouch if phase == 1 => {
|
||||
let intent = animation
|
||||
.motion_intents
|
||||
.get("Crouch")
|
||||
.copied()
|
||||
.unwrap_or(0.0);
|
||||
let [min, max, rate] = settings.height;
|
||||
state.height = crouch(state.height, intent, physical.crouch, min, max, rate, dt);
|
||||
set(animation, "DistToCog", state.height);
|
||||
}
|
||||
Operation::Fade {
|
||||
height: name,
|
||||
twist,
|
||||
intent,
|
||||
} => {
|
||||
if phase == 0 {
|
||||
set(animation, name, height);
|
||||
let bounds = animation.attribute_endpoints(encode(twist.as_bytes()))?;
|
||||
let value = state.fade.begin(bounds[0], bounds[1], physical.twist);
|
||||
set(animation, twist, value);
|
||||
} else if phase == 1 {
|
||||
let input = animation.motion_intents.get(intent).copied().unwrap_or(0.0);
|
||||
if let Some(value) = state.fade.update(dt, input, settings.fade) {
|
||||
set(animation, twist, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -14,6 +14,7 @@ use skate_data::state_graph::{
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum MotionOperation {
|
||||
Grind(super::motion_grind::Operation),
|
||||
OffboardAir(super::motion_offboard_air::Operation),
|
||||
ToggleBoard,
|
||||
Cadence(super::motion_cadence::Operation),
|
||||
@@ -81,7 +82,9 @@ impl OperationFactory for MotionFactory {
|
||||
let operation = if kind == OperationKind::Condition {
|
||||
MotionCondition::parse(a)?.map(MotionOperation::Condition)
|
||||
} else if kind == OperationKind::Behavior {
|
||||
if let Some(operation) = super::motion_offboard_air::Operation::parse(a) {
|
||||
if let Some(operation) = super::motion_grind::Operation::parse(a) {
|
||||
Some(MotionOperation::Grind(operation))
|
||||
} else if let Some(operation) = super::motion_offboard_air::Operation::parse(a) {
|
||||
Some(MotionOperation::OffboardAir(operation))
|
||||
} else if name == "ToggleBoard" {
|
||||
Some(MotionOperation::ToggleBoard)
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
use skate_core::math::Vector3;
|
||||
use bevy::prelude::*;
|
||||
|
||||
/// Retained geometry for the forthcoming native contact/scorer adapter. This
|
||||
/// deliberately does not publish selector candidate1488 or enter state400.
|
||||
/// Shared authored geometry. The physical grind owner runs native truck queries
|
||||
/// against these same spline endpoints before state selection.
|
||||
#[derive(Resource)]
|
||||
pub(crate) struct GrindGeometry {
|
||||
pub rails: Vec<Rail>,
|
||||
@@ -21,7 +21,7 @@ impl Plugin for GrindGeometryPlugin {
|
||||
GrindGeometry { rails: Vec::new(), native_blob: Vec::new() }
|
||||
};
|
||||
if test_world {
|
||||
info!("GRIND_GEOMETRY rails={} bytes={} native_acquisition=not_connected",
|
||||
info!("GRIND_GEOMETRY rails={} bytes={} native_acquisition=paired_trucks_50_50",
|
||||
geometry.rails.len(), geometry.native_blob.len());
|
||||
for rail in &geometry.rails {
|
||||
info!("GRIND_RAIL id={} name={} start={:?} end={:?}",
|
||||
|
||||
@@ -31,6 +31,7 @@ mod animation_input;
|
||||
mod animation_phase;
|
||||
mod landing_quality;
|
||||
mod frame;
|
||||
mod grind;
|
||||
mod ground_phase;
|
||||
mod ground_animation;
|
||||
mod slide_state;
|
||||
@@ -70,6 +71,7 @@ pub(crate) struct GamePhysics {
|
||||
clock: clock::SimulationClock,
|
||||
pub board: BoardRuntime,
|
||||
pub riding: RidingOutputs,
|
||||
grind: grind::Runtime,
|
||||
world: BoardWorld,
|
||||
settings: PhysicsSettings,
|
||||
animation_profile: animation_phase::AnimationProfile,
|
||||
@@ -144,6 +146,7 @@ impl GamePhysics {
|
||||
let (query, retention) = ground::query_settings();
|
||||
Ok(Self {
|
||||
clock: clock::SimulationClock::default(),
|
||||
grind: grind::Runtime::load(&data, map.is_none())?,
|
||||
board,
|
||||
riding,
|
||||
world,
|
||||
|
||||
@@ -181,17 +181,31 @@ pub(crate) fn advance(
|
||||
board_on_ground: physical.off_board.flag_311 != 0,
|
||||
animation_height: feedback.crouching.animation_height_72,
|
||||
});
|
||||
// FilteredState82DE5BA0 clears grind metadata on every nongrind output.
|
||||
// A future grind phase must supply its actual cached name, not an AG intent.
|
||||
if physical.filtered_state_0 == 3 {
|
||||
return Err("Animation phase requires the selected grind's filtered output name".into());
|
||||
}
|
||||
let grinding=skater.player_state.filtered_output.as_ref().is_some_and(|f|f.grinding);
|
||||
let grind_name=if grinding {physics.grind.name.clone()} else {String::new()};
|
||||
//FillSkeleton82BE1AE8 uses the vector between physical parts10/6,
|
||||
//Processed352 and Reckoning1152, then wraps the signed angle to +/-pi.
|
||||
let a=skater.skeleton.record.pose[10][3];
|
||||
let b=skater.skeleton.record.pose[6][3];
|
||||
let lateral=Vector3::new(a[0]-b[0],a[1]-b[1],a[2]-b[2]);
|
||||
let forward=skater.player_input.toolkit.as_ref().map(|t| t.forward)
|
||||
.unwrap_or([0.,0.,1.,0.]);
|
||||
let mut twist=skate_core::riding::collision_response::signed_angle(
|
||||
Vector3::new(forward[0],forward[1],forward[2]),lateral,physics.riding.reckoning.up);
|
||||
let turns=twist* f32::from_bits(0x3e22f983);
|
||||
let fraction=turns-turns.floor();
|
||||
twist=(fraction-if fraction>0.5 {1.0} else {0.0})*f32::from_bits(0x40c90fdb);
|
||||
if mirrored {twist=if twist>=0.0 {std::f32::consts::PI-twist} else {-std::f32::consts::PI-twist};}
|
||||
skater.animation.motion.grind_physical=crate::graph_host::motion_grind::Physical {
|
||||
name:grind_name.clone(),grinding,crouch:physics.grind.output().crouch,twist,
|
||||
facing_backwards: physics.grind.name.starts_with("BF_"),
|
||||
};
|
||||
let conditions = ConditionInputs {
|
||||
speeds: Some(physics.riding.graph_speeds()),
|
||||
physical_state: Some(PhysicalStateInputs {
|
||||
category: physical.filtered_state_0,
|
||||
grinding: false,
|
||||
grind_name: String::new(),
|
||||
grinding,
|
||||
grind_name,
|
||||
}),
|
||||
time_since_last_input: Some(p.time_since_last_input_2748),
|
||||
mirrored: Some(mirrored),
|
||||
|
||||
@@ -85,6 +85,8 @@ pub(super) fn advance(
|
||||
player_state::post_input_and_select(physics, skater)?;
|
||||
player_state::pre_state(physics, skater)?;
|
||||
match skater.player_state.current() {
|
||||
skate_core::player::state::PhysicalStateId::GrindFiftyFifty
|
||||
| skate_core::player::state::PhysicalStateId::Nonspecific => super::grind::update(physics, skater)?,
|
||||
skate_core::player::state::PhysicalStateId::BipedAir => {
|
||||
super::offboard::air_state::update(physics, skater)?;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
//! Live straight-spline grind owner. TU3 truck query82C1FDC0, paired scorer
|
||||
//!82D89150, 50-50 update82D41D70 and exit82D3F430.
|
||||
use super::{GamePhysics, SkaterRuntime};
|
||||
use skate_core::riding::ground_correction_math::dot_product as dot3;
|
||||
use skate_core::{
|
||||
animation::skeleton_input::name::encode,
|
||||
math::{Basis3, Vector3},
|
||||
physics::{
|
||||
drive_frames::RetailAffineTransform,
|
||||
filtered_state::GrindState,
|
||||
force_queue::QueuedPointForce,
|
||||
grind_contact::{self, FiftyFiftyCandidate, Primitive},
|
||||
grind_forces,
|
||||
},
|
||||
player::state::PhysicalStateId,
|
||||
point_graph::PointGraph,
|
||||
};
|
||||
use skate_data::collections::Collections;
|
||||
type V = [f32; 4];
|
||||
pub(crate) struct Runtime {
|
||||
primitives: Vec<Primitive>,
|
||||
pub candidate: Option<FiftyFiftyCandidate>,
|
||||
pub name: String,
|
||||
pub active: bool,
|
||||
truck_to_wheel: f32,
|
||||
deck_to_truck: f32,
|
||||
pin_vs_slope: PointGraph<4>,
|
||||
pub distance: f32,
|
||||
pop_heights: [[f32; 2]; 5],
|
||||
manager_age: f32,
|
||||
pub launched: bool,
|
||||
pub launch_velocity: V,
|
||||
cooldown: u32,
|
||||
previous_position: V,
|
||||
}
|
||||
impl Runtime {
|
||||
pub fn load(data: &Collections, test_world: bool) -> Result<Self, String> {
|
||||
let graph = data
|
||||
.words::<8>("physics_grinds", "default", "PinVsSlope")?
|
||||
.map(f32::from_bits);
|
||||
Ok(Self {
|
||||
primitives: if test_world {
|
||||
crate::grind_world::rails()
|
||||
.iter()
|
||||
.map(|r| Primitive {
|
||||
start: lanes(r.start),
|
||||
end: lanes(r.end),
|
||||
owner: r.id,
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
},
|
||||
candidate: None,
|
||||
name: String::new(),
|
||||
active: false,
|
||||
truck_to_wheel: data.float("physics_grinds", "default", "TruckToWheel")?,
|
||||
deck_to_truck: data.float("physics_grinds", "default", "DeckCenterToTruck")?,
|
||||
pin_vs_slope: PointGraph {
|
||||
x: graph[..4].try_into().unwrap(),
|
||||
y: graph[4..8].try_into().unwrap(),
|
||||
},
|
||||
pop_heights: ["easy", "normal", "hardcore", "motorized", "test"]
|
||||
.map(|key| {
|
||||
Ok::<_, String>([
|
||||
data.float("physics_mode", key, "Hash_3097A69281990652")?,
|
||||
data.float("physics_mode", key, "Hash_FA4CDBAE0DFD1FAD")?,
|
||||
])
|
||||
})
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
manager_age: 0.0,
|
||||
launched: false,
|
||||
launch_velocity: [0.; 4],
|
||||
cooldown: 0,
|
||||
distance: 0.0,
|
||||
previous_position: [0.; 4],
|
||||
})
|
||||
}
|
||||
pub fn output(&self) -> GrindState {
|
||||
if !self.active {
|
||||
return GrindState::default();
|
||||
}
|
||||
let owner = self
|
||||
.candidate
|
||||
.map(|c| self.primitives[c.primitive].owner)
|
||||
.unwrap_or(u64::MAX);
|
||||
GrindState {
|
||||
kind: 0,
|
||||
name: encode(self.name.as_bytes()),
|
||||
scoring_name: encode(self.name.as_bytes()),
|
||||
pathed_guid: owner,
|
||||
local_guid: owner,
|
||||
..GrindState::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
///Run after ProcessInput, before the native state selector. Geometry is queried
|
||||
///against both current trucks; merely being near a rail cannot acquire it.
|
||||
pub(super) fn query(physics: &mut GamePhysics, skater: &mut SkaterRuntime) {
|
||||
let p = &mut skater.player_input.processed;
|
||||
let runtime = &mut physics.grind;
|
||||
let board = super::solve::deck_frame(&physics.board);
|
||||
runtime.cooldown = runtime.cooldown.saturating_sub(1);
|
||||
runtime.manager_age = (runtime.manager_age + 0.0035).min(1.0);
|
||||
let old = runtime.candidate;
|
||||
runtime.candidate = None;
|
||||
if runtime.cooldown > 0 || skater.player_input.grind.disabled || p.flags_2476 & 0x01000000 != 0
|
||||
{
|
||||
return;
|
||||
}
|
||||
let hits = grind_contact::truck_contacts(
|
||||
board,
|
||||
p.flags_2484,
|
||||
runtime.truck_to_wheel,
|
||||
runtime.deck_to_truck,
|
||||
&runtime.primitives,
|
||||
);
|
||||
let extra = &skater.animation_input.extra;
|
||||
let Some(candidate) = grind_contact::fifty_fifty_candidate(
|
||||
board,
|
||||
[extra.grind_translation, extra.grind_stability_nudge],
|
||||
hits,
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
let velocity = p.vectors_400_416[0].map(f32::from_bits);
|
||||
//82D886B8: air accepts an actual intersection; same active grind uses90deg.
|
||||
//The ordinary level-ground admission uses17deg.
|
||||
let angle = if p.category_2512 == 400 || p.state_2508 == 701 {
|
||||
90.0
|
||||
} else {
|
||||
17.0
|
||||
};
|
||||
if p.category_2512 != 200
|
||||
&& !grind_contact::within_approach_angle(candidate.direction, velocity, angle)
|
||||
{
|
||||
return;
|
||||
}
|
||||
runtime.candidate = Some(candidate);
|
||||
p.grind_position_1120 = candidate.centre.map(f32::to_bits);
|
||||
p.grind_direction_1136 = candidate.direction.map(f32::to_bits);
|
||||
if !runtime.active || old.is_none() {
|
||||
let backwards = dot3(board[2], velocity) < 0.0;
|
||||
let side = dot3(sub(candidate.centre, board[3]), board[0]) >= 0.0;
|
||||
runtime.name = format!(
|
||||
"{}{}_50_50",
|
||||
if backwards { "BF_" } else { "" },
|
||||
if side { "FS" } else { "BS" }
|
||||
);
|
||||
}
|
||||
}
|
||||
pub(super) fn enter(physics: &mut GamePhysics, skater: &mut SkaterRuntime) -> Result<(), String> {
|
||||
physics.grind.active = skater.player_state.current() == PhysicalStateId::GrindFiftyFifty;
|
||||
physics.grind.distance = 0.0;
|
||||
physics.grind.previous_position = super::solve::deck_frame(&physics.board)[3];
|
||||
skater.ground_lifecycle.skeleton_elapsed_16505 = true;
|
||||
physics
|
||||
.board
|
||||
.hook_mut()
|
||||
.drive
|
||||
.disable_animation(&mut skater.ground_lifecycle.board_animated_290);
|
||||
skater.air_reckoning.state.reset_spin();
|
||||
if physics.grind.active {
|
||||
bevy::log::info!(
|
||||
"Grind entered: {} rail={:?}",
|
||||
physics.grind.name,
|
||||
physics
|
||||
.grind
|
||||
.candidate
|
||||
.map(|c| physics.grind.primitives[c.primitive].owner)
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub(super) fn exit(physics: &mut GamePhysics, skater: &mut SkaterRuntime) -> Result<(), String> {
|
||||
physics.grind.active = false;
|
||||
physics
|
||||
.board
|
||||
.hook_mut()
|
||||
.drive
|
||||
.disable_animation(&mut skater.ground_lifecycle.board_animated_290);
|
||||
//82D3F7D0 restores all four wheel drags to zero.
|
||||
for body in &mut physics.board.bodies_mut()[..4] {
|
||||
body.inertia.linear_drag = 0.0;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub(super) fn update(physics: &mut GamePhysics, skater: &mut SkaterRuntime) -> Result<(), String> {
|
||||
physics.grind.launched = false;
|
||||
let board = super::solve::deck_frame(&physics.board);
|
||||
let p = &skater.player_input.processed;
|
||||
if physics.grind.active && p.flags_2468 & 0x00400000 != 0 {
|
||||
let c = physics
|
||||
.grind
|
||||
.candidate
|
||||
.ok_or("Grind pop has no primitive")?;
|
||||
let [min, max] = physics.grind.pop_heights[p.state_variant_index_2528 as usize];
|
||||
let strength = skater.animation_input.extra.jump_strength;
|
||||
let velocity = grind_forces::fifty_fifty_pop(
|
||||
p.vectors_400_416[0].map(f32::from_bits),
|
||||
grind_contact::upright_normal(c.direction),
|
||||
c.direction,
|
||||
board[3],
|
||||
c.centre,
|
||||
(1.0 - strength).mul_add(min, max * strength),
|
||||
skater.animation_input.extra.grind_stability_nudge,
|
||||
physics.grind.manager_age,
|
||||
);
|
||||
physics.grind.launched = true;
|
||||
physics.grind.launch_velocity = velocity;
|
||||
physics.grind.cooldown = 10;
|
||||
physics.grind.manager_age = (physics.grind.manager_age - 0.2).max(0.0);
|
||||
skater
|
||||
.ground_runtime
|
||||
.set_animated_velocity(&mut physics.board, velocity);
|
||||
physics
|
||||
.board
|
||||
.hook_mut()
|
||||
.drive
|
||||
.disable_animation(&mut skater.ground_lifecycle.board_animated_290);
|
||||
return super::input_phase::update_grind_jump(physics, skater);
|
||||
}
|
||||
let velocity = p.vectors_400_416[0].map(f32::from_bits);
|
||||
//Both state vtables neutralize the trucks before the shared board solve.
|
||||
skater.ground.steering.targets = [0.; 2];
|
||||
let normal = if physics.grind.active {
|
||||
let c = physics
|
||||
.grind
|
||||
.candidate
|
||||
.ok_or("Active grind lost its candidate before selection")?;
|
||||
let normal = grind_contact::upright_normal(c.direction);
|
||||
let across = cross(c.direction, normal);
|
||||
let force = grind_forces::lateral_pin(
|
||||
board,
|
||||
c.centre,
|
||||
across,
|
||||
velocity,
|
||||
800.,
|
||||
0.,
|
||||
0.07,
|
||||
true,
|
||||
physics.grind.pin_vs_slope.evaluate(dot3(normal, normal)),
|
||||
);
|
||||
append(physics, force)?;
|
||||
//The primitive support frame is static and level for the authored rails.
|
||||
//82D86318 publishes this unit support normal in candidate304.
|
||||
let friction = grind_forces::friction(
|
||||
velocity,
|
||||
normal,
|
||||
normal,
|
||||
skater.player_input.grind.friction_vs_time,
|
||||
false,
|
||||
1.3,
|
||||
0,
|
||||
[50., 40., 40.],
|
||||
);
|
||||
append(physics, friction)?;
|
||||
let forward = if dot3(board[2], c.direction) > 0.0 {
|
||||
c.direction
|
||||
} else {
|
||||
c.direction.map(|x| -x)
|
||||
};
|
||||
let target = [cross(normal, forward), normal, forward, board[3]];
|
||||
let (frame, _) = skate_core::animation::foot_ik::interpolate_native(&board, &target, 0.1);
|
||||
physics.board.set_hook_transform(RetailAffineTransform {
|
||||
basis: Basis3 {
|
||||
columns: std::array::from_fn(|i| [frame[i][0], frame[i][1], frame[i][2]]),
|
||||
},
|
||||
translation: xyz(frame[3]),
|
||||
});
|
||||
physics
|
||||
.board
|
||||
.hook_mut()
|
||||
.drive
|
||||
.enable_angular_only(&mut skater.ground_lifecycle.board_animated_290);
|
||||
physics.grind.distance +=
|
||||
dot3(sub(board[3], physics.grind.previous_position), c.direction).abs();
|
||||
physics.grind.previous_position = board[3];
|
||||
normal
|
||||
} else {
|
||||
//Nonspecific82D42F50 applies the native -40 support force while the
|
||||
//selector waits for actual wheel/collision observations after rail exit.
|
||||
append(
|
||||
physics,
|
||||
p.vectors_544_560_592_608[0]
|
||||
.map(f32::from_bits)
|
||||
.map(|v| v * -40.0),
|
||||
)?;
|
||||
board[1]
|
||||
};
|
||||
physics.riding.update_grind_reckoning(
|
||||
&mut skater.air_reckoning.state,
|
||||
normal,
|
||||
board[2],
|
||||
skater.player_input.processed.flags_2468,
|
||||
if physics.grind.active { 0.9 } else { 0.98 },
|
||||
);
|
||||
//82D40D40 and the ordinary nonspecific branch both call Skeleton82BDF530.
|
||||
super::input_phase::update_ground(physics, skater)
|
||||
}
|
||||
fn append(physics: &mut GamePhysics, force: V) -> Result<(), String> {
|
||||
if !physics.board.forces_mut().append(QueuedPointForce {
|
||||
tag: 0,
|
||||
force_world: xyz(force),
|
||||
point_body: Vector3::ZERO,
|
||||
}) {
|
||||
return Err("Grind force queue exhausted".into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn lanes(v: Vector3) -> V {
|
||||
[v.x, v.y, v.z, 0.]
|
||||
}
|
||||
fn xyz(v: V) -> Vector3 {
|
||||
Vector3::new(v[0], v[1], v[2])
|
||||
}
|
||||
fn sub(a: V, b: V) -> V {
|
||||
std::array::from_fn(|i| a[i] - b[i])
|
||||
}
|
||||
fn cross(a: V, b: V) -> V {
|
||||
[
|
||||
a[1] * b[2] - a[2] * b[1],
|
||||
a[2] * b[0] - a[0] * b[2],
|
||||
a[0] * b[1] - a[1] * b[0],
|
||||
0.,
|
||||
]
|
||||
}
|
||||
@@ -213,3 +213,15 @@ impl PlayerInputCallbacks for Callbacks<'_, '_> {
|
||||
self.reset_player(board, runtime, target, player, physical, processed)
|
||||
}
|
||||
}
|
||||
|
||||
///Grind pop82D40D30 calls the animated skeleton update with fast blend=false.
|
||||
pub(super) fn update_grind_jump(physics:&mut GamePhysics,skater:&mut SkaterRuntime)->Result<(),String> {
|
||||
let collision=collision(skater);
|
||||
let mut owners=SkeletonOwners {animated:&mut skater.animated_skeleton,body:&mut skater.skeleton,
|
||||
drives:&mut skater.skeleton_drives,ik:&mut skater.foot_ik,animation_input:&mut skater.animation_input,
|
||||
correction:&mut skater.skeleton_output.correction,pose_errors:&mut skater.pose_errors};
|
||||
skater.skeleton_input.update_animated(&mut skater.skeleton_air,&mut physics.board,
|
||||
&physics.riding.reckoning_frames.system,&mut skater.player_input.processed,&mut owners,
|
||||
&skater.animation.packet.hierarchy,&collision,physics.settings.step.simulation,false)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -6,14 +6,6 @@ use skate_core::player::offboard::{
|
||||
};
|
||||
pub(crate) fn prepare(skater: &SkaterRuntime, current: bool) -> Result<air_launch::Launch, String> {
|
||||
let p = &skater.player_input.processed;
|
||||
if (if current {
|
||||
p.category_2512
|
||||
} else {
|
||||
p.category_2516
|
||||
}) == 400
|
||||
{
|
||||
return Err("Biped launch from a grind requires the grind primitive owner".into());
|
||||
}
|
||||
let toolkit = skater
|
||||
.player_input
|
||||
.toolkit
|
||||
@@ -34,9 +26,9 @@ pub(crate) fn prepare(skater: &SkaterRuntime, current: bool) -> Result<air_launc
|
||||
position_592: p.vectors_544_560_592_608[2].map(f32::from_bits),
|
||||
target_112: toolkit.effective[3],
|
||||
velocity_912: p.vectors_880_896_912_928_944[2].map(f32::from_bits),
|
||||
//The current authored world has no grind primitives; category400 is rejected above.
|
||||
grind_position_1120: [0.; 4],
|
||||
grind_axis_1136: [0.; 4],
|
||||
//Retained live grind primitive, published before native state entry.
|
||||
grind_position_1120: p.grind_position_1120.map(f32::from_bits),
|
||||
grind_axis_1136: p.grind_direction_1136.map(f32::from_bits),
|
||||
stick: [
|
||||
skater.animation_input.extra.biped_world_x,
|
||||
skater.animation_input.extra.biped_world_z,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//! Original82D8A828/82D8ABD8/82D875A8 ordinary empty-edge path.
|
||||
//! The authored host floor explicitly contains no grind edges. This is an
|
||||
//! asset declaration, not a missing-loader fallback or an inferred no-hit.
|
||||
//! Input timing/gates from82D8A828/82D8ABD8. The geometry query/scorer runs
|
||||
//! in physics::grind after input completion, before the state selector.
|
||||
use skate_core::{player::input_phase::ProcessedPhysicsInput, point_graph::PointGraph};
|
||||
use skate_data::collections::Collections;
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -88,11 +87,6 @@ impl GrindInputState {
|
||||
} else {
|
||||
0.
|
||||
};
|
||||
//Empty real edge query selects standard board collision in82D89DC0
|
||||
//for ordinary ground. An active grind state needs its geometry owner.
|
||||
if p.category_2512 == 400 || state == 701 {
|
||||
return Err("Active grind state requires authored grind geometry".into());
|
||||
}
|
||||
self.low_wheel_frames = if self.previous_candidate
|
||||
&& state == 100
|
||||
&& p.wheel_count_2556 < 2
|
||||
|
||||
@@ -12,6 +12,8 @@ pub(crate) fn reset_processed(input: &mut ProcessedPhysicsInput) {
|
||||
[0, 0, 1.0f32.to_bits(), 0],
|
||||
[0; 4],
|
||||
],
|
||||
grind_position_1120: input.grind_position_1120,
|
||||
grind_direction_1136: input.grind_direction_1136,
|
||||
vector_1520: input.vector_1520,
|
||||
matrix_1536: input.matrix_1536,
|
||||
byte_1600: input.byte_1600,
|
||||
|
||||
@@ -108,6 +108,7 @@ pub(crate) fn post_input_and_select(
|
||||
physics: &mut GamePhysics,
|
||||
skater: &mut SkaterRuntime,
|
||||
) -> Result<(), String> {
|
||||
super::grind::query(physics, skater);
|
||||
post_input::advance(physics, skater)?;
|
||||
selection::advance(physics, skater)
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ struct Services<'a> {
|
||||
}
|
||||
impl PostInputServices for Services<'_> {
|
||||
fn update_grind_manager_82d8ab08(&mut self) {
|
||||
self.grind.advance(self.processed);
|
||||
if self.processed.category_2512 != 400 { self.grind.advance(self.processed); }
|
||||
}
|
||||
fn update_trajectory_selector_82d68800(&mut self) -> u8 {
|
||||
//82DB56B0 runs the real selector after GrindManager. That manager
|
||||
@@ -142,7 +142,7 @@ pub(super) fn advance(physics: &mut GamePhysics, skater: &mut SkaterRuntime) ->
|
||||
let trajectory_input = super::super::air_phase::selector_input(physics, skater)?;
|
||||
let post = &mut skater.player_state.post;
|
||||
let p = &mut skater.player_input.processed;
|
||||
if skater.ground_lifecycle.edge.is_some() || p.category_2512 == 400 || p.state_2508 == 701 {
|
||||
if skater.ground_lifecycle.edge.is_some() {
|
||||
return Err("PostInput active grind requires its actual candidate/scorer output".into());
|
||||
}
|
||||
let requests = &skater.ground_lifecycle.trajectory;
|
||||
|
||||
@@ -12,6 +12,8 @@ pub(super) fn advance(physics: &mut GamePhysics, skater: &mut SkaterRuntime) ->
|
||||
skater.player_state.current(),
|
||||
PhysicalStateId::PhysicsGround
|
||||
| PhysicalStateId::PhysicsAir
|
||||
| PhysicalStateId::GrindFiftyFifty
|
||||
| PhysicalStateId::Nonspecific
|
||||
| PhysicalStateId::KnownAir
|
||||
| PhysicalStateId::GroundAnimation
|
||||
| PhysicalStateId::SlideGround
|
||||
|
||||
@@ -3,7 +3,7 @@ use super::*;
|
||||
use skate_core::{
|
||||
physics::{
|
||||
contact_feedback::choose_surface,
|
||||
filtered_state::{FilteredStateInput, GrindState},
|
||||
filtered_state::{FilteredStateInput},
|
||||
},
|
||||
player::input_phase::CurrentStateFields,
|
||||
};
|
||||
@@ -13,6 +13,8 @@ pub(super) fn publish(physics: &mut GamePhysics, skater: &mut SkaterRuntime) ->
|
||||
state,
|
||||
PhysicalStateId::PhysicsGround
|
||||
| PhysicalStateId::PhysicsAir
|
||||
| PhysicalStateId::GrindFiftyFifty
|
||||
| PhysicalStateId::Nonspecific
|
||||
| PhysicalStateId::KnownAir
|
||||
| PhysicalStateId::GroundAnimation
|
||||
| PhysicalStateId::SlideGround
|
||||
@@ -85,6 +87,13 @@ pub(super) fn publish(physics: &mut GamePhysics, skater: &mut SkaterRuntime) ->
|
||||
set(84, skater.slide_state.state.wall_riding);
|
||||
}
|
||||
let physical = &mut skater.player_input.physical;
|
||||
if physics.grind.active {
|
||||
physical.grinds.words_136_140 = [0, 1];
|
||||
if physics.grind.launched {
|
||||
physical.air.launched_442=1;
|
||||
physical.air.launch_velocity_128=physics.grind.launch_velocity.map(f32::to_bits);
|
||||
}
|
||||
}
|
||||
//Common Biped Fill82DB7218 precedes the selected physical state's Fill.
|
||||
physical.off_board.vector_64 = skater
|
||||
.offboard
|
||||
@@ -170,8 +179,8 @@ pub(super) fn publish(physics: &mut GamePhysics, skater: &mut SkaterRuntime) ->
|
||||
offboard_has_landed: state == PhysicalStateId::BipedAir
|
||||
&& skater.offboard.air_state.remaining <= f32::from_bits(0x3c888889),
|
||||
offboard_on_deck: physical.off_board.flag_315 != 0,
|
||||
grind: GrindState::default(),
|
||||
last_grind_distance: 0.0,
|
||||
grind: physics.grind.output(),
|
||||
last_grind_distance: physics.grind.distance,
|
||||
});
|
||||
physical.filtered_state_0 = filtered.category as u32;
|
||||
skater.player_state.filtered_output = Some(filtered);
|
||||
|
||||
@@ -8,10 +8,9 @@ pub(super) fn advance(physics: &mut GamePhysics, skater: &mut SkaterRuntime) ->
|
||||
let p = &skater.player_input.processed;
|
||||
let input = StateSelectionInput {
|
||||
processed: ProcessedStateInput {
|
||||
//The authored world has no grind edges. GrindInput's real empty
|
||||
//edge path clears candidate384; zero flags are its reset record.
|
||||
//The live paired-truck scorer publishes native kind0 and candidate384.
|
||||
grind_type_1248: 0,
|
||||
grind_candidate_1488: false,
|
||||
grind_candidate_1488: physics.grind.candidate.is_some(),
|
||||
grind_investigation_flags_1516: 0,
|
||||
field_1776: p.external_physics_1616.flags as i32,
|
||||
flags_2468: p.flags_2468,
|
||||
|
||||
@@ -15,6 +15,8 @@ impl PhysicalStateCalls for Calls {
|
||||
PhysicalStateId::Sleeping
|
||||
| PhysicalStateId::PhysicsGround
|
||||
| PhysicalStateId::PhysicsAir
|
||||
| PhysicalStateId::GrindFiftyFifty
|
||||
| PhysicalStateId::Nonspecific
|
||||
| PhysicalStateId::KnownAir
|
||||
| PhysicalStateId::GroundAnimation
|
||||
| PhysicalStateId::SlideGround
|
||||
@@ -29,6 +31,8 @@ impl PhysicalStateCalls for Calls {
|
||||
call.state.state,
|
||||
PhysicalStateId::PhysicsGround
|
||||
| PhysicalStateId::PhysicsAir
|
||||
| PhysicalStateId::GrindFiftyFifty
|
||||
| PhysicalStateId::Nonspecific
|
||||
| PhysicalStateId::KnownAir
|
||||
| PhysicalStateId::GroundAnimation
|
||||
| PhysicalStateId::SlideGround
|
||||
@@ -55,7 +59,9 @@ pub(super) fn set(
|
||||
PhysicalStateId::Sleeping => requested == PhysicalStateId::PhysicsGround,
|
||||
PhysicalStateId::PhysicsGround
|
||||
| PhysicalStateId::PhysicsAir
|
||||
| PhysicalStateId::KnownAir
|
||||
| PhysicalStateId::GrindFiftyFifty
|
||||
| PhysicalStateId::Nonspecific
|
||||
| PhysicalStateId::KnownAir
|
||||
| PhysicalStateId::GroundAnimation
|
||||
| PhysicalStateId::SlideGround
|
||||
| PhysicalStateId::WipeoutGround
|
||||
@@ -65,6 +71,8 @@ pub(super) fn set(
|
||||
requested,
|
||||
PhysicalStateId::PhysicsGround
|
||||
| PhysicalStateId::PhysicsAir
|
||||
| PhysicalStateId::GrindFiftyFifty
|
||||
| PhysicalStateId::Nonspecific
|
||||
| PhysicalStateId::KnownAir
|
||||
| PhysicalStateId::GroundAnimation
|
||||
| PhysicalStateId::SlideGround
|
||||
@@ -149,6 +157,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::GrindFiftyFifty | PhysicalStateId::Nonspecific => super::super::grind::exit(physics, skater)?,
|
||||
PhysicalStateId::BipedAir => super::super::offboard::air_state::exit(physics, skater),
|
||||
PhysicalStateId::BipedGround => super::super::offboard::ground_state::exit(physics, skater),
|
||||
PhysicalStateId::PhysicsGround => super::super::ground_exit::exit(physics, skater),
|
||||
@@ -163,6 +172,7 @@ pub(super) fn set(
|
||||
_ => unreachable!("state support checked before publication"),
|
||||
}
|
||||
match requested {
|
||||
PhysicalStateId::GrindFiftyFifty | PhysicalStateId::Nonspecific => super::super::grind::enter(physics, skater),
|
||||
PhysicalStateId::BipedAir => super::super::offboard::air_state::enter(physics, skater),
|
||||
PhysicalStateId::BipedGround => {
|
||||
super::super::offboard::ground_state::enter(physics, skater)
|
||||
|
||||
@@ -76,6 +76,20 @@ impl RidingOutputs {
|
||||
},
|
||||
);
|
||||
}
|
||||
///Shared Reckoning82D8E3E0, argument7=false for grind/nonspecific.
|
||||
pub fn update_grind_reckoning(&mut self, air: &mut skate_core::air::reckoning::AirState,
|
||||
normal: [f32;4], forward: [f32;4], flags: u32, blend: f32) {
|
||||
let previous_up=lanes(self.reckoning.up);
|
||||
skate_core::player::offboard::ground_reckoning::update(
|
||||
&mut self.reckoning,&mut self.reckoning_frames,&mut self.body_spin,air,
|
||||
skate_core::player::offboard::ground_reckoning::Settings {
|
||||
ground_normal_smoothing:self.orientation_settings.ground_normal_smoothing,
|
||||
tilt_vs_rotation:&self.tilt_vs_rotation,tilt_vs_slope:&self.tilt_vs_slope,
|
||||
},skate_core::player::offboard::ground_reckoning::Input {
|
||||
previous_up,requested_up:normal,requested_forward:forward,blend,
|
||||
reverse_stance:flags&0x100000!=0,enable_body_spin_input:false,physical_body_spin_2812:0.,
|
||||
});
|
||||
}
|
||||
///Board82C0D680 resets CollisionInfo and both probes, preserving7692.
|
||||
pub fn reset_for_teleport(&mut self) {
|
||||
let elapsed = self.ground.time_without_wheel_contact;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Live grind integration
|
||||
|
||||
The default world now supplies seven straight grind paths shared with rail/coping collision geometry. This pass connects the paired-truck 50-50 acquisition path to the production state selector, board forces, skeleton update, stock grind animations and physical output. Imported map grind descriptors are not wired into this owner yet.
|
||||
|
||||
## Source and implementation
|
||||
|
||||
TU3 image: default_82000000_011B0000.bin, SHA256 f4aa113eb541bfba03dbc108cf5ab43f58c965b20fa3b82f9c40938a0ad841c4. Analysis used the local generated PPC and the copied IDA database; the reference game was not launched for this implementation.
|
||||
|
||||
- 82C1FDC0 and 82D89150: actual truck probe rectangles, paired same-primitive contacts, depth and balance rejection. No nearest-distance snap or positional teleport.
|
||||
- 82D886B8: air and ordinary level-ground admission. The host currently covers straight level rails; sloped approach classification, adjacency arbitration, tipslides, boardslides and darkslides still need their own live adapters.
|
||||
- 82D41D70 / 82D3FA18 / 82D3FD88: 50-50 lateral pin and friction, using the stock PinVsSlope and FrictionVsTime curves. Authored static rail support uses the upward normal and default surface multiplier.
|
||||
- 82D41E38 / 82D40890: rail-aligned target blended through the existing native matrix interpolation and angular-only board drive. User-driven grind twist forces and the remaining grind-manager transition scoring are not complete.
|
||||
- 82D73AB0: straight static 50-50 pop branch, stock physics-mode launch speeds, lateral nudge and launch velocity publication.
|
||||
- 82D3F430: disables the grind hook and restores wheel drag on exit. The original selector retains Nonspecific701 briefly on contact loss before choosing air/ground from actual observations; the host now supports that ordinary continuation.
|
||||
- 82BAF208, 82BB0A30, 82BB0D40 and 82BB10F8: selected grind intent, stock twist endpoints and retained fade, and grind crouch. Physical twist uses the native parts10/6 direction relative to Processed352 and Reckoning up. Canonical 50-50 names select the existing B_GRIND5050/B_BF_GRIND5050 animation trees; full chromosome naming/scorable identifiers are not ported.
|
||||
- 82BA8238: DistToEdge reads the existing OffBoard116 output. This resolves the mounting graph condition encountered in the launched build.
|
||||
|
||||
## Visual check
|
||||
|
||||
Build with Build.ps1; launch bin/skate-game.exe --assets assets (no map argument). The low straight rail is at x=-7, z=7..15, 0.45m above the lower floor; the higher rail is at x=4.5, z=8..16, 0.70m above it. Approach along the rail and land both trucks on its top. The halfpipe coping and three starting-platform edges also have authored paths.
|
||||
|
||||
Build completed. No automated tests or controller-driven gameplay tests were run. Visual behavior, catch consistency, pop timing, naming and solver stability while grinding require the user's playtest; this is not a claim of complete Skate 3 grind parity.
|
||||
Reference in new issue
Block a user