mirror of
https://github.com/SK8-ENGINE/skate-3-rust-engine.git
synced 2026-10-26 19:47:44 +00:00
Connect native board possession effects and hand drives to shared physics
This commit is contained in:
1 parent
890d9001ad
commit
fb78128153
24 files changed
+765
-53
No files matched your search
@@ -97,9 +97,13 @@ impl BoardRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collision_group(&self) -> u32 { self.collision_group }
|
||||
pub fn collision_group(&self) -> u32 {
|
||||
self.collision_group
|
||||
}
|
||||
///All seven native board parts share the current assembly group.
|
||||
pub fn set_collision_group(&mut self, group: u32) { self.collision_group = group }
|
||||
pub fn set_collision_group(&mut self, group: u32) {
|
||||
self.collision_group = group
|
||||
}
|
||||
|
||||
pub fn bodies(&self) -> &[BodySnapshot; BODY_COUNT] {
|
||||
&self.bodies
|
||||
@@ -142,9 +146,8 @@ impl BoardRuntime {
|
||||
body.rates.angular_velocity = Vector3::ZERO;
|
||||
body.rates.force_acceleration = gravity;
|
||||
body.rates.torque_acceleration = Vector3::ZERO;
|
||||
body.rates.world_inverse_inertia = world_inverse_inertia(
|
||||
body.rates.basis, body.inertia.inverse_tensor,
|
||||
);
|
||||
body.rates.world_inverse_inertia =
|
||||
world_inverse_inertia(body.rates.basis, body.inertia.inverse_tensor);
|
||||
}
|
||||
copy_pose(&hook, &mut self.hook.body.rates);
|
||||
self.forces.clear();
|
||||
@@ -263,6 +266,30 @@ impl BoardRuntime {
|
||||
set_part_transform(&mut part, pose_words(requested));
|
||||
copy_pose(&part, &mut self.hook.body.rates);
|
||||
}
|
||||
///82C0B2C8, used by board retrieval and hiding. Changes live part poses
|
||||
///without Reset's rate, contact-history, or force-queue clearing.
|
||||
pub fn set_transform(&mut self, requested: RetailAffineTransform) {
|
||||
let poses = self.part_transforms();
|
||||
let mut parts = core::array::from_fn(|i| PartPose {
|
||||
transform: pose_words(poses[i]),
|
||||
local_mass_frame: Some(mass_frame_words(self.mass_frames[i])),
|
||||
body: Some(body_pose_words(self.bodies[i].rates)),
|
||||
inertia: Some(inertia_words(self.bodies[i].inertia)),
|
||||
});
|
||||
let mut hook = PartPose {
|
||||
transform: pose_words(self.hook_transform()),
|
||||
local_mass_frame: None,
|
||||
body: Some(body_pose_words(self.hook.body.rates)),
|
||||
inertia: None,
|
||||
};
|
||||
set_board_transform(&mut parts, &mut hook, pose_words(requested));
|
||||
for (body, part) in self.bodies.iter_mut().zip(&parts) {
|
||||
copy_pose(part, &mut body.rates);
|
||||
body.rates.world_inverse_inertia =
|
||||
world_inverse_inertia(body.rates.basis, body.inertia.inverse_tensor);
|
||||
}
|
||||
copy_pose(&hook, &mut self.hook.body.rates);
|
||||
}
|
||||
}
|
||||
|
||||
const ZERO_INERTIA: RetailInertiaDynamics = RetailInertiaDynamics {
|
||||
|
||||
@@ -10,6 +10,20 @@ use crate::math::{Basis3, Vector3};
|
||||
const STEP: f32 = f32::from_bits(0x3c88_8889);
|
||||
const NORMAL_MINIMUM: f32 = f32::from_bits(0x3586_37bd); //82F826F8 ->830BD350.
|
||||
|
||||
///82C07328 subtracts the current angular displacement along the requested
|
||||
///axis, without07000's directional clamps, then performs the same tensor and
|
||||
///fixed-step conversion as075B8. The thrown-board controller calls this leaf.
|
||||
pub fn apply_axis_displacement(body: &mut RetailBodyRates, requested: Vector3) {
|
||||
let squared = dot(requested, requested);
|
||||
let inverse = inverse_length_squared(squared, 2);
|
||||
let length = if squared == 0.0 { 0.0 } else { squared * inverse };
|
||||
let direction = if length > NORMAL_MINIMUM {
|
||||
scale(requested, inverse)
|
||||
} else { Vector3::ZERO };
|
||||
let existing = scale(direction, dot(direction, scale(body.angular_velocity, STEP)));
|
||||
apply_angular_displacement(body, subtract(requested, existing));
|
||||
}
|
||||
|
||||
/// 82C07000 first limits the requested angular displacement against the
|
||||
/// displacement already supplied by angular velocity along that same axis.
|
||||
pub fn apply_limited_displacement(body: &mut RetailBodyRates, requested: Vector3) {
|
||||
@@ -130,6 +144,21 @@ mod tests {
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn thrown_board_correction_brakes_axis_overshoot_without_touching_other_motion() {
|
||||
let mut state = body();
|
||||
state.angular_velocity = Vector3::new(4., 3., 5.);
|
||||
apply_axis_displacement(&mut state, Vector3::new(0., 0.02, 0.));
|
||||
assert!((state.torque_acceleration.y + 108.).abs() < 0.0001);
|
||||
assert!(state.torque_acceleration.x.abs() < 0.0001);
|
||||
assert!(state.torque_acceleration.z.abs() < 0.0001);
|
||||
assert_eq!(state.angular_velocity, Vector3::new(4., 3., 5.));
|
||||
assert_eq!(state.cool_down, 0);
|
||||
let mut reverse = body();
|
||||
reverse.angular_velocity = Vector3::new(0., -3., 0.);
|
||||
apply_axis_displacement(&mut reverse, Vector3::new(0., 0.02, 0.));
|
||||
assert!((reverse.torque_acceleration.y - 252.).abs() < 0.0001);
|
||||
}
|
||||
#[test]
|
||||
fn correction_enters_accumulator_and_respects_existing_angular_motion() {
|
||||
let request = Vector3::new(0.02, 0.01, -0.03);
|
||||
let mut state = body();
|
||||
|
||||
@@ -33,7 +33,7 @@ impl SkateboardController {
|
||||
let previous = self.fields.state_448;
|
||||
self.fields.word_444 = 0;
|
||||
if previous != 0 {
|
||||
actions.let_go_of_skateboard();
|
||||
actions.let_go_of_skateboard(&mut self.fields);
|
||||
self.fields.state_448 = 0;
|
||||
}
|
||||
self.fields.system_on_452 = false;
|
||||
|
||||
@@ -59,6 +59,43 @@ fn close(actual: Vector3, expected: Vector3) {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moving_board_transform_preserves_rates_and_pending_forces() {
|
||||
let mut board = runtime(Vector3::new(0., -9.8, 0.), BoardMotion::Active);
|
||||
for (i, body) in board.bodies_mut().iter_mut().enumerate() {
|
||||
body.rates.linear_velocity = Vector3::new(i as f32, 2., 3.);
|
||||
body.rates.angular_velocity = Vector3::new(1., i as f32, 2.);
|
||||
body.rates.force_acceleration = Vector3::new(5., 6., 7.);
|
||||
body.rates.torque_acceleration = Vector3::new(8., 9., 10.);
|
||||
}
|
||||
let before = *board.bodies();
|
||||
let parts = board.part_transforms();
|
||||
let delta = Vector3::new(4., 5., 6.);
|
||||
let mut target = parts[BodyId::Deck.index()];
|
||||
target.translation = delta;
|
||||
board.set_transform(target);
|
||||
for (i, body) in board.bodies().iter().enumerate() {
|
||||
close(body.rates.linear_velocity, before[i].rates.linear_velocity);
|
||||
close(
|
||||
body.rates.angular_velocity,
|
||||
before[i].rates.angular_velocity,
|
||||
);
|
||||
close(
|
||||
body.rates.force_acceleration,
|
||||
before[i].rates.force_acceleration,
|
||||
);
|
||||
close(
|
||||
body.rates.torque_acceleration,
|
||||
before[i].rates.torque_acceleration,
|
||||
);
|
||||
let p = parts[i].translation;
|
||||
close(
|
||||
board.part_transforms()[i].translation,
|
||||
Vector3::new(p.x + delta.x, p.y + delta.y, p.z + delta.z),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_preserves_each_mass_frame_and_keeps_the_hook_separate() {
|
||||
let mut data = masses();
|
||||
|
||||
@@ -118,8 +118,14 @@ pub struct GrindOutputFields {
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct OffBoardOutputFields {
|
||||
pub scalar_32: f32,
|
||||
pub board_angle_36: f32,
|
||||
pub board_angle_40: f32,
|
||||
pub flag_304: u8,
|
||||
pub flag_311: u8,
|
||||
pub free_board_312: u8,
|
||||
pub returning_board_313: u8,
|
||||
pub hidden_board_321: u8,
|
||||
pub board_request_324: u8,
|
||||
pub flag_314: u8,
|
||||
pub flag_315: u8,
|
||||
pub flag_316: u8,
|
||||
|
||||
@@ -79,8 +79,8 @@ pub struct StateChangeData {
|
||||
/// implementations are `HoldSkateboard` `0x82D75370` and
|
||||
/// `LetGoOfSkateboard` `0x82D75440`; this lifecycle never replaces them.
|
||||
pub trait SkateboardControllerActions {
|
||||
fn hold_skateboard(&mut self);
|
||||
fn let_go_of_skateboard(&mut self);
|
||||
fn hold_skateboard(&mut self, _fields: &mut SkateboardControllerFields);
|
||||
fn let_go_of_skateboard(&mut self, _fields: &mut SkateboardControllerFields);
|
||||
}
|
||||
|
||||
/// Context supplied to an owned state's Enter or Exit implementation.
|
||||
@@ -196,11 +196,11 @@ fn prepare_skateboard_controller<C: SkateboardControllerActions>(
|
||||
controller.word_444 = 0;
|
||||
if processed_flags_2480 & (0x80 | 0x100) != 0 {
|
||||
if controller.state_448 != 2 {
|
||||
actions.let_go_of_skateboard();
|
||||
actions.let_go_of_skateboard(controller);
|
||||
controller.state_448 = 2;
|
||||
}
|
||||
} else if controller.state_448 != 1 {
|
||||
actions.hold_skateboard();
|
||||
actions.hold_skateboard(controller);
|
||||
controller.state_448 = 1;
|
||||
}
|
||||
controller.system_on_452 = true;
|
||||
@@ -218,7 +218,7 @@ fn stop_skateboard_controller<C: SkateboardControllerActions>(
|
||||
let previous_state = controller.state_448;
|
||||
controller.word_444 = 0;
|
||||
if previous_state != 0 {
|
||||
actions.let_go_of_skateboard();
|
||||
actions.let_go_of_skateboard(controller);
|
||||
controller.state_448 = 0;
|
||||
}
|
||||
controller.system_on_452 = false;
|
||||
|
||||
@@ -37,11 +37,11 @@ impl PhysicalStateCalls for SharedRecorder {
|
||||
}
|
||||
|
||||
impl SkateboardControllerActions for SharedRecorder {
|
||||
fn hold_skateboard(&mut self) {
|
||||
fn hold_skateboard(&mut self, _fields: &mut SkateboardControllerFields) {
|
||||
self.events.borrow_mut().push(Event::Hold);
|
||||
}
|
||||
|
||||
fn let_go_of_skateboard(&mut self) {
|
||||
fn let_go_of_skateboard(&mut self, _fields: &mut SkateboardControllerFields) {
|
||||
self.events.borrow_mut().push(Event::LetGo);
|
||||
}
|
||||
}
|
||||
@@ -62,11 +62,11 @@ impl PhysicalStateCalls for Recorder {
|
||||
}
|
||||
|
||||
impl SkateboardControllerActions for Recorder {
|
||||
fn hold_skateboard(&mut self) {
|
||||
fn hold_skateboard(&mut self, _fields: &mut SkateboardControllerFields) {
|
||||
self.events.push(Event::Hold);
|
||||
}
|
||||
|
||||
fn let_go_of_skateboard(&mut self) {
|
||||
fn let_go_of_skateboard(&mut self, _fields: &mut SkateboardControllerFields) {
|
||||
self.events.push(Event::LetGo);
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,41 @@ fn seeded_data() -> StateChangeData {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_callback_retains_its_throw_countdown_through_state_publication() {
|
||||
struct Throw;
|
||||
impl SkateboardControllerActions for Throw {
|
||||
fn hold_skateboard(&mut self, _: &mut SkateboardControllerFields) {
|
||||
panic!("unexpected hold");
|
||||
}
|
||||
fn let_go_of_skateboard(&mut self, fields: &mut SkateboardControllerFields) {
|
||||
assert_eq!(fields.word_444, 0);
|
||||
fields.word_444 = 8;
|
||||
}
|
||||
}
|
||||
let mut data = seeded_data();
|
||||
data.skateboard_controller = SkateboardControllerFields {
|
||||
word_444: 123,
|
||||
state_448: 1,
|
||||
system_on_452: true,
|
||||
};
|
||||
let mut states = Recorder {
|
||||
reported_type: PhysicalStateId::BipedGround,
|
||||
events: vec![],
|
||||
};
|
||||
PhysicalPlayerStateLifecycle::new(PhysicalStateId::BipedGround)
|
||||
.set_physics_state(
|
||||
PhysicalStateId::PhysicsGround as u32,
|
||||
&mut data,
|
||||
&mut states,
|
||||
&mut Throw,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(data.skateboard_controller.word_444, 8);
|
||||
assert_eq!(data.skateboard_controller.state_448, 0);
|
||||
assert!(!data.skateboard_controller.system_on_452);
|
||||
}
|
||||
|
||||
fn recorder(reported_type: PhysicalStateId) -> Recorder {
|
||||
Recorder {
|
||||
reported_type,
|
||||
|
||||
@@ -43,8 +43,8 @@ impl PhysicalStateCalls for States {
|
||||
struct Controller;
|
||||
|
||||
impl SkateboardControllerActions for Controller {
|
||||
fn hold_skateboard(&mut self) {}
|
||||
fn let_go_of_skateboard(&mut self) {}
|
||||
fn hold_skateboard(&mut self, _fields: &mut SkateboardControllerFields) {}
|
||||
fn let_go_of_skateboard(&mut self, _fields: &mut SkateboardControllerFields) {}
|
||||
}
|
||||
|
||||
fn input() -> StateSelectionInput {
|
||||
|
||||
@@ -276,6 +276,10 @@ impl GamePhysics {
|
||||
self.processed_flags_2468,
|
||||
self.settings.step.simulation.time_step,
|
||||
)?;
|
||||
offboard::board_effects::classify_alignment(
|
||||
&skater.offboard.board_policy,
|
||||
&mut self.riding.ground,
|
||||
);
|
||||
let partial = skateboard_controller::partial_request(
|
||||
&skater.skateboard_controller,
|
||||
&self.riding.ground,
|
||||
|
||||
@@ -123,6 +123,7 @@ pub(super) fn advance(
|
||||
}
|
||||
state => return Err(format!("Selected {state:?} requires its physical update")),
|
||||
}
|
||||
super::offboard::possession::update(physics, skater)?;
|
||||
// Source State82DB6120 submits the queue once, then advances state time.
|
||||
// BoardRuntime applies that same queue during its shared solve.
|
||||
skater.player_input.player.state_timer_1344 += skater.player_input.processed.timestep_2604;
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
//! Board-controller effects applied to the existing board and hook bodies.
|
||||
use skate_core::{
|
||||
math::{Basis3, Vector3},
|
||||
physics::{board::BodyId, board_runtime::BoardRuntime, drive_frames::RetailAffineTransform},
|
||||
player::offboard::board_possession::{
|
||||
Frame, Vector,
|
||||
lifecycle::{Alignment, Effects},
|
||||
},
|
||||
};
|
||||
|
||||
pub(crate) struct Policy {
|
||||
pub volumes_enabled: bool,
|
||||
pub released: bool,
|
||||
pub alignment: Option<Alignment>,
|
||||
}
|
||||
impl Default for Policy {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
volumes_enabled: true,
|
||||
released: false,
|
||||
alignment: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) struct BoardEffects<'a> {
|
||||
pub board: &'a mut BoardRuntime,
|
||||
pub animated: &'a mut u8,
|
||||
pub policy: &'a mut Policy,
|
||||
pub standard_deck_drag: f32,
|
||||
pub timestep: f32,
|
||||
}
|
||||
fn xyz(v: Vector) -> Vector3 {
|
||||
Vector3::new(v[0], v[1], v[2])
|
||||
}
|
||||
fn transform(f: Frame) -> RetailAffineTransform {
|
||||
RetailAffineTransform {
|
||||
basis: Basis3 {
|
||||
columns: std::array::from_fn(|i| [f[i][0], f[i][1], f[i][2]]),
|
||||
},
|
||||
translation: xyz(f[3]),
|
||||
}
|
||||
}
|
||||
impl Effects for BoardEffects<'_> {
|
||||
fn enable_animation_soft(&mut self) {
|
||||
self.board
|
||||
.hook_mut()
|
||||
.drive
|
||||
.enable_animation_soft(self.animated);
|
||||
}
|
||||
fn enable_animation_angular_only(&mut self) {
|
||||
self.board
|
||||
.hook_mut()
|
||||
.drive
|
||||
.enable_angular_only(self.animated);
|
||||
}
|
||||
fn disable_animation(&mut self) {
|
||||
self.board.hook_mut().drive.disable_animation(self.animated);
|
||||
}
|
||||
fn disable_linear_drive(&mut self) {
|
||||
self.board.hook_mut().drive.disable_linear();
|
||||
}
|
||||
fn standard_board(&mut self) {
|
||||
self.policy.released = false;
|
||||
self.board.set_collision_group(4);
|
||||
self.board.bodies_mut()[BodyId::Deck.index()]
|
||||
.inertia
|
||||
.angular_drag = self.standard_deck_drag;
|
||||
self.policy.volumes_enabled = true;
|
||||
}
|
||||
fn released_board(&mut self) {
|
||||
self.policy.released = true;
|
||||
self.board.bodies_mut()[BodyId::Deck.index()]
|
||||
.inertia
|
||||
.angular_drag = f32::from_bits(0x416f_ffff);
|
||||
self.board.set_collision_group(7);
|
||||
self.policy.volumes_enabled = true;
|
||||
}
|
||||
fn collision_volumes(&mut self, enabled: bool) {
|
||||
self.policy.volumes_enabled = enabled;
|
||||
}
|
||||
fn clear_alignment(&mut self) {
|
||||
self.policy.alignment = None;
|
||||
}
|
||||
fn alignment(&mut self, value: Alignment) {
|
||||
self.policy.alignment = Some(value);
|
||||
}
|
||||
fn velocity(&mut self, value: Vector) {
|
||||
for body in self.board.bodies_mut() {
|
||||
body.rates.linear_velocity = xyz(value);
|
||||
}
|
||||
}
|
||||
fn position(&mut self, value: Vector) {
|
||||
let mut target = self.board.part_transforms()[BodyId::Deck.index()];
|
||||
target.translation = xyz(value);
|
||||
self.board.set_transform(target);
|
||||
}
|
||||
fn hook_frame(&mut self, value: Frame) {
|
||||
self.board.set_hook_transform(transform(value));
|
||||
}
|
||||
fn target_position_velocity(&mut self, value: Vector) {
|
||||
//82C04270 reads Body+16 (mass-frame position), not GetPartTransform.
|
||||
let origin = self.board.bodies()[BodyId::Deck.index()].rates.position;
|
||||
let frequency = 1.0 / self.timestep;
|
||||
self.velocity([
|
||||
(value[0] - origin.x) * frequency,
|
||||
(value[1] - origin.y) * frequency,
|
||||
(value[2] - origin.z) * frequency,
|
||||
0.,
|
||||
]);
|
||||
}
|
||||
fn torque(&mut self, value: Vector) {
|
||||
//750F0 passes all three767D0 outputs to07328. They are requested
|
||||
//angular displacements, not direct torques or inverse-inertia products.
|
||||
skate_core::physics::deck_angular_correction::apply_axis_displacement(
|
||||
&mut self.board.bodies_mut()[BodyId::Deck.index()].rates,
|
||||
xyz(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///82C08370..8428 classifies retained per-part normals against the controller's
|
||||
///two directions. This is a release observation, not an extra force or joint.
|
||||
pub(crate) fn classify_alignment(
|
||||
policy: &Policy,
|
||||
ground: &mut skate_core::physics::board_ground::BoardGroundState,
|
||||
) {
|
||||
let Some(a) = policy.alignment else {
|
||||
return;
|
||||
};
|
||||
for part in &ground.parts {
|
||||
if !part.in_contact {
|
||||
continue;
|
||||
}
|
||||
let normal = [part.normal.x, part.normal.y, part.normal.z, 0.];
|
||||
if [a.first_1008, a.second_1024].into_iter().any(|direction| {
|
||||
(normal[0] * -direction[0] + normal[1] * -direction[1]) + normal[2] * -direction[2]
|
||||
> a.factor_1040
|
||||
}) {
|
||||
ground.collision_flags |= 0x0400_0000;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
//! Native SkateboardController drives82D74FD8 /82D751D8 in the shared island.
|
||||
//! Factory82AE64F8 stores argument5 at drive16 (A), argument4 at20 (B):
|
||||
//! hand bodies3/7 are A and the deck is B, despite the factory argument order.
|
||||
use skate_core::{
|
||||
math::{Basis3, Vector3},
|
||||
physics::{
|
||||
assembly::BodySnapshot,
|
||||
board::BodyId,
|
||||
board_step::ATTACHED_REACTION_BASE,
|
||||
drive_frames::{RetailDriveFrame, RetailDriveFrames, retail_quaternion_from_basis},
|
||||
drive_parameters::{RetailDriveDynamics, RetailDriveParams, RetailDriveType},
|
||||
drive_solver::{RetailDriveBodyState, RetailDriveRows, build_drive_rows},
|
||||
rigid_body::pack_world_inverse_inertia,
|
||||
skeleton_body::prepare_bone_drive_frames,
|
||||
},
|
||||
player::offboard::board_possession::{Frame, State},
|
||||
};
|
||||
|
||||
pub(crate) fn append(
|
||||
rows: &mut Vec<RetailDriveRows>,
|
||||
state: &State,
|
||||
board: &[BodySnapshot; 7],
|
||||
skeleton: &[BodySnapshot; 26],
|
||||
dt: f32,
|
||||
) {
|
||||
for (hand, part) in [3, 7].into_iter().enumerate() {
|
||||
let drive = state.hands[hand];
|
||||
let a = skeleton[part];
|
||||
let b = board[BodyId::Deck.index()];
|
||||
if (a.state_flags | b.state_flags) & 4 == 0 {
|
||||
continue;
|
||||
}
|
||||
let parameters = |words: [u32; 4]| RetailDriveParams {
|
||||
spring_or_max_velocity: f32::from_bits(words[0]),
|
||||
damping: f32::from_bits(words[1]),
|
||||
max_strength: f32::from_bits(words[2]),
|
||||
drive_type: match words[3] {
|
||||
1 => RetailDriveType::SoftDrive,
|
||||
2 => RetailDriveType::HardDrive,
|
||||
_ => RetailDriveType::NoDrive,
|
||||
},
|
||||
};
|
||||
rows.push(build_drive_rows(
|
||||
body(a, ATTACHED_REACTION_BASE + part),
|
||||
body(b, BodyId::Deck.index()),
|
||||
prepare_bone_drive_frames(RetailDriveFrames {
|
||||
body_a: frame(drive.child),
|
||||
body_b: frame(drive.parent),
|
||||
}),
|
||||
RetailDriveDynamics {
|
||||
linear: parameters(drive.dynamics[0]),
|
||||
angular: parameters(drive.dynamics[1]),
|
||||
},
|
||||
dt,
|
||||
));
|
||||
}
|
||||
}
|
||||
fn frame(f: Frame) -> RetailDriveFrame {
|
||||
RetailDriveFrame {
|
||||
orientation: retail_quaternion_from_basis(Basis3 {
|
||||
columns: std::array::from_fn(|i| [f[i][0], f[i][1], f[i][2]]),
|
||||
}),
|
||||
translation: Vector3::new(f[3][0], f[3][1], f[3][2]),
|
||||
}
|
||||
}
|
||||
fn body(b: BodySnapshot, index: usize) -> RetailDriveBodyState {
|
||||
let r = b.rates;
|
||||
RetailDriveBodyState {
|
||||
reaction_index: index,
|
||||
state: b.state_flags,
|
||||
orientation: r.orientation,
|
||||
basis: r.basis,
|
||||
center_of_mass: r.position,
|
||||
linear_velocity: r.linear_velocity,
|
||||
angular_velocity: r.angular_velocity,
|
||||
force_acceleration: r.force_acceleration,
|
||||
torque_acceleration: r.torque_acceleration,
|
||||
inverse_mass: b.inertia.inverse_mass,
|
||||
world_inverse_inertia: pack_world_inverse_inertia(r.world_inverse_inertia),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use skate_core::physics::{
|
||||
drive_solver::solve_drive_rows,
|
||||
rigid_body::{
|
||||
RetailBodyRates, RetailInertiaDynamics, RetailQuaternion, RetailReactionCorrections,
|
||||
},
|
||||
};
|
||||
fn active() -> BodySnapshot {
|
||||
let basis = Basis3 {
|
||||
columns: [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]],
|
||||
};
|
||||
BodySnapshot {
|
||||
state_flags: 4,
|
||||
rates: RetailBodyRates {
|
||||
orientation: RetailQuaternion::IDENTITY,
|
||||
basis,
|
||||
world_inverse_inertia: basis,
|
||||
position: Vector3::ZERO,
|
||||
linear_velocity: Vector3::ZERO,
|
||||
angular_velocity: Vector3::ZERO,
|
||||
force_acceleration: Vector3::ZERO,
|
||||
torque_acceleration: Vector3::ZERO,
|
||||
kinetic_energy: 0.,
|
||||
cool_down: 0,
|
||||
},
|
||||
inertia: RetailInertiaDynamics {
|
||||
inverse_tensor: Vector3::new(1., 1., 1.),
|
||||
inverse_mass: 1.,
|
||||
spherical: 0.,
|
||||
maximum_linear_velocity: 1000.,
|
||||
maximum_angular_velocity: 1000.,
|
||||
linear_drag: 0.,
|
||||
angular_drag: 0.,
|
||||
},
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn held_hand_and_deck_exchange_reactions_and_release_removes_force() {
|
||||
let board = [active(); 7];
|
||||
let mut skeleton = [active(); 26];
|
||||
skeleton[3].rates.position.x = 1.;
|
||||
let mut state = State::default();
|
||||
state.selected_hand_424 = 0;
|
||||
state.hands[0].dynamics = [[0x4395ffff, 0, 0x468c9fff, 2]; 2];
|
||||
let mut rows = Vec::new();
|
||||
append(&mut rows, &state, &board, &skeleton, 1. / 60.);
|
||||
let mut reactions = [RetailReactionCorrections::default(); 34];
|
||||
solve_drive_rows(&mut rows, &mut reactions, 4);
|
||||
let hand = reactions[ATTACHED_REACTION_BASE + 3].linear_displacement.x;
|
||||
let deck = reactions[BodyId::Deck.index()].linear_displacement.x;
|
||||
assert!(hand < 0. && deck > 0., "hand={hand}, deck={deck}");
|
||||
assert!((hand + deck).abs() < 1e-6);
|
||||
assert_eq!(
|
||||
reactions[ATTACHED_REACTION_BASE + 7].linear_displacement,
|
||||
Vector3::ZERO
|
||||
);
|
||||
state.disable_hand();
|
||||
rows.clear();
|
||||
append(&mut rows, &state, &board, &skeleton, 1. / 60.);
|
||||
reactions.fill(RetailReactionCorrections::default());
|
||||
solve_drive_rows(&mut rows, &mut reactions, 4);
|
||||
assert!(
|
||||
reactions
|
||||
.iter()
|
||||
.all(|r| r.linear_displacement == Vector3::ZERO
|
||||
&& r.angular_displacement == Vector3::ZERO)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
//! Off-board game adapters over the shared physical and scene owners.
|
||||
pub(crate) mod board_effects;
|
||||
pub(crate) mod contact_queries;
|
||||
pub(crate) mod ground_input;
|
||||
pub(crate) mod ground_query;
|
||||
pub(crate) mod hand_drives;
|
||||
pub(crate) mod pose_adjust;
|
||||
pub(crate) mod possession;
|
||||
pub(crate) mod runtime;
|
||||
pub(crate) mod settings;
|
||||
pub(crate) mod skeleton_ground;
|
||||
pub(crate) mod contact_queries;
|
||||
pub(crate) mod runtime;
|
||||
pub(crate) mod ground_input;
|
||||
@@ -0,0 +1,117 @@
|
||||
//! Native hand controller over the player's live pose and board observations.
|
||||
use crate::physics::{GamePhysics, SkaterRuntime};
|
||||
use skate_core::{
|
||||
physics::{contact_feedback::choose_surface, skeleton_animation_record::compose_affine},
|
||||
player::offboard::board_possession::{Processed, lifecycle::Observation},
|
||||
};
|
||||
|
||||
pub(crate) struct Actions<'a> {
|
||||
pub state: &'a mut skate_core::player::offboard::board_possession::State,
|
||||
pub settings: &'a skate_core::player::offboard::board_possession::lifecycle::Settings,
|
||||
pub observation: &'a Observation,
|
||||
pub effects: super::board_effects::BoardEffects<'a>,
|
||||
}
|
||||
impl skate_core::player::lifecycle::SkateboardControllerActions for Actions<'_> {
|
||||
fn hold_skateboard(
|
||||
&mut self,
|
||||
fields: &mut skate_core::player::lifecycle::SkateboardControllerFields,
|
||||
) {
|
||||
self.state.hold(fields, self.observation, &mut self.effects);
|
||||
}
|
||||
fn let_go_of_skateboard(
|
||||
&mut self,
|
||||
fields: &mut skate_core::player::lifecycle::SkateboardControllerFields,
|
||||
) {
|
||||
self.state
|
||||
.let_go(fields, self.observation, self.settings, &mut self.effects);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn observe(
|
||||
physics: &GamePhysics,
|
||||
skater: &SkaterRuntime,
|
||||
) -> Result<Observation, String> {
|
||||
let p = &skater.player_input.processed;
|
||||
let toolkit = skater
|
||||
.player_input
|
||||
.toolkit
|
||||
.as_ref()
|
||||
.ok_or("Board controller requires this update's board toolkit")?;
|
||||
let roots = &skater.animated_skeleton.roots;
|
||||
let frames = &skater.skeleton_input.drive_frames;
|
||||
let physical_parts = skater.skeleton.part_transforms();
|
||||
Ok(Observation {
|
||||
processed: Processed {
|
||||
board_frame_64: toolkit.deck,
|
||||
player_frame_192: p
|
||||
.effective_anim_transform_192
|
||||
.map(|v| v.map(f32::from_bits)),
|
||||
position_592: p.vectors_544_560_592_608[2].map(f32::from_bits),
|
||||
velocity_912: p.vectors_880_896_912_928_944[2].map(f32::from_bits),
|
||||
direction_400: p.vectors_400_416[0].map(f32::from_bits),
|
||||
hide_direction_464: p.vectors_464_480_496_512_528[0].map(f32::from_bits),
|
||||
flags_2476: p.flags_2476,
|
||||
flags_2480: p.flags_2480,
|
||||
flags_2488: p.flags_2488,
|
||||
},
|
||||
board_collision_flags_872: physics.riding.ground.collision_flags,
|
||||
//82C08818 produces840 by voting the four retained wheel surfaces.
|
||||
board_state_840: choose_surface(
|
||||
physics.riding.wheel_lines.physics_surfaces,
|
||||
std::array::from_fn(|i| physics.riding.ground.parts[i].in_contact),
|
||||
physics.riding.ground.collision_flags & 0x0200_0000 != 0,
|
||||
),
|
||||
hand_contacts: [3, 7].map(|i| skater.collision_feedback.current[i]),
|
||||
physical_hand_positions: [3, 7].map(|i| physical_parts[i][3]),
|
||||
animation_board_frame_12624: frames[0],
|
||||
animation_hand_frames: [frames[3], frames[7]],
|
||||
attachment_frame_0: compose_affine(&roots.animation_to_world, &frames[0]),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn update(physics: &mut GamePhysics, skater: &mut SkaterRuntime) -> Result<(), String> {
|
||||
if !skater.skateboard_controller.fields.system_on_452 {
|
||||
return Ok(());
|
||||
}
|
||||
let observation = observe(physics, skater)?;
|
||||
let offboard = &mut skater.offboard;
|
||||
let mut effects = super::board_effects::BoardEffects {
|
||||
board: &mut physics.board,
|
||||
animated: &mut skater.ground_lifecycle.board_animated_290,
|
||||
policy: &mut offboard.board_policy,
|
||||
standard_deck_drag: offboard.standard_deck_drag,
|
||||
timestep: skater.player_input.processed.timestep_2604,
|
||||
};
|
||||
offboard.possession.update(
|
||||
&mut skater.skateboard_controller.fields,
|
||||
&observation,
|
||||
&offboard.possession_settings,
|
||||
&mut effects,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn publish(physics: &GamePhysics, skater: &mut SkaterRuntime) -> Result<(), String> {
|
||||
let observation = observe(physics, skater)?;
|
||||
let bone = compose_affine(
|
||||
&skater.animated_skeleton.roots.animation_to_world,
|
||||
&skater.skeleton_input.drive_frames[11],
|
||||
);
|
||||
let value = skate_core::player::offboard::board_possession::fill(
|
||||
&skater.skateboard_controller.fields,
|
||||
&skater.offboard.possession,
|
||||
&observation.processed,
|
||||
bone,
|
||||
);
|
||||
let output = &mut skater.player_input.physical.off_board;
|
||||
output.board_angle_36 = value.angle_36;
|
||||
output.board_angle_40 = value.angle_40;
|
||||
output.flag_311 = u8::from(value.held_311);
|
||||
output.free_board_312 = u8::from(value.free_312);
|
||||
output.returning_board_313 = u8::from(value.returning_313);
|
||||
output.hidden_board_321 = u8::from(value.hiding_321);
|
||||
output.dropping_board_322 = u8::from(value.flag_322);
|
||||
output.retrieving_board_323 = u8::from(value.flag_323);
|
||||
output.board_request_324 = u8::from(value.flag_324);
|
||||
Ok(())
|
||||
}
|
||||
@@ -10,6 +10,10 @@ use skate_core::point_graph::PointGraph;
|
||||
use skate_data::{animation_metadata::AnimationMetadata, collections::Collections};
|
||||
|
||||
pub(crate) struct Runtime {
|
||||
pub board_policy: super::board_effects::Policy,
|
||||
pub standard_deck_drag: f32,
|
||||
pub possession: skate_core::player::offboard::board_possession::State,
|
||||
pub possession_settings: skate_core::player::offboard::board_possession::lifecycle::Settings,
|
||||
pub controller: Controller,
|
||||
pub ground: ground_entry::State,
|
||||
pub contacts: Toolkit,
|
||||
@@ -136,6 +140,11 @@ impl Runtime {
|
||||
pub(crate) fn load(data: &Collections, metadata: &AnimationMetadata) -> Result<Self, String> {
|
||||
let settings = super::settings::Settings::load(data, metadata)?;
|
||||
Ok(Self {
|
||||
board_policy: Default::default(),
|
||||
standard_deck_drag: data.float("physicsdeck", "default", "DeckAngularDrag")?
|
||||
* f32::from_bits(0x426f_ffff),
|
||||
possession: Default::default(),
|
||||
possession_settings: settings.possession,
|
||||
controller: Controller::new(settings.controller, settings.metrics),
|
||||
ground: Default::default(),
|
||||
contacts: Default::default(),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
mod board;
|
||||
mod curves;
|
||||
mod metrics;
|
||||
mod possession;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
use skate_core::{
|
||||
@@ -11,6 +12,7 @@ use skate_core::{
|
||||
use skate_data::{animation_metadata::AnimationMetadata, collections::Collections};
|
||||
|
||||
pub(crate) struct Settings {
|
||||
pub possession: skate_core::player::offboard::board_possession::lifecycle::Settings,
|
||||
pub controller: controller::Settings,
|
||||
pub board: skate_core::player::offboard::ground_sync::BoardSettings,
|
||||
pub metrics: [Option<controller::ClipMetric>; 3],
|
||||
@@ -26,6 +28,7 @@ impl Settings {
|
||||
let biped = |name| curves::load::<8>(data, "physics_biped", name);
|
||||
let (sprint_blend, bounds) = biped("Hash_6B93C51256A30FB4")?;
|
||||
Ok(Self {
|
||||
possession: possession::load(data)?,
|
||||
controller: controller::Settings {
|
||||
movement_intent: movement_intent::Settings {
|
||||
sprint_speed: curves::load::<4>(
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
//! Stock physics_skatecontroller/default data used by the board controller.
|
||||
use skate_core::player::offboard::board_possession::lifecycle::Settings;
|
||||
use skate_data::collections::Collections;
|
||||
pub(super) fn load(data: &Collections) -> Result<Settings, String> {
|
||||
let float = |name| data.float("physics_skatecontroller", "default", name);
|
||||
let curve = |name| super::curves::load::<8>(data, "physics_skatecontroller", name).map(|v| v.0);
|
||||
Ok(Settings {
|
||||
//75F00 reads893DA1B966231D76 for the hide trigger;75BB8 reads
|
||||
//6CFF6F2C43F0AD41 for the hidden position. Lookup8 verifies both names.
|
||||
hide_distance: float("MaxDistance")?,
|
||||
hide_offset: float("DistanceToHideSkateboard")?,
|
||||
return_distance: float("DistanceForBoardReturn")?,
|
||||
mounted_return_distance: float("DistanceForMountedReturn")?,
|
||||
mounting_time: float("MinTimeToRetrieveBoardWhileMounting")?,
|
||||
retrieval_time: curve("RetrievalTimeVsDist")?,
|
||||
retrieval_weight: curve("RetrievalSpeedCurve")?,
|
||||
throw_pitch: float("ThrownSkateboardLaunchPitch")?,
|
||||
throw_velocity: curve("ThrownSkateboardVelocity")?,
|
||||
throw_target_pitch: float("ThrownSkateboardPitchTarget")?,
|
||||
throw_pitch_scalar: float("ThrownSkateboardPitchScalar")?,
|
||||
throw_roll_scalar: float("ThrownSkateboardRollScalar")?,
|
||||
throw_yaw_scalar: float("ThrownSkateboardYawScalar")?,
|
||||
})
|
||||
}
|
||||
@@ -85,4 +85,7 @@ fn actual_stock_settings_and_metrics_load_together() {
|
||||
});
|
||||
assert!(speeds[0] > 0.0 && speeds[0] < speeds[1] && speeds[1] < speeds[2]);
|
||||
assert_eq!(settings.controller.movement_intent.sprint_time_cap, 4.0);
|
||||
//Independent native lookup8 keys:893DA1B966231D76 /6CFF6F2C43F0AD41.
|
||||
assert_eq!(settings.possession.hide_distance, 30.0);
|
||||
assert_eq!(settings.possession.hide_offset, 1000.0);
|
||||
}
|
||||
@@ -25,6 +25,51 @@ pub(crate) struct ReckoningUpdate {
|
||||
pub forward: [f32; 4],
|
||||
pub blend: f32,
|
||||
}
|
||||
|
||||
///Borrow the same bodies, IK, roots, collision feedback and camera reckoning as
|
||||
///the riding phases. Sync supplies the completed Biped job frame and COM.
|
||||
pub(crate) fn update(
|
||||
physics: &mut crate::physics::GamePhysics,
|
||||
skater: &mut crate::physics::SkaterRuntime,
|
||||
frame: Transform,
|
||||
centre_of_mass: [f32; 4],
|
||||
) -> Result<(), String> {
|
||||
let collision = crate::physics::input_phase::collision(skater);
|
||||
let flags = skater.player_input.processed.flags_2468;
|
||||
let spin = skater.animation_input.extra.physical_body_spin;
|
||||
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_biped_ground(
|
||||
&mut skater.skeleton_air,
|
||||
&mut physics.board,
|
||||
Input {
|
||||
world_frame: &frame,
|
||||
centre_of_mass_1056: centre_of_mass,
|
||||
},
|
||||
&mut skater.player_input.processed,
|
||||
&mut owners,
|
||||
&skater.animation.packet.hierarchy,
|
||||
&collision,
|
||||
physics.settings.step.simulation,
|
||||
|update| {
|
||||
physics.riding.update_biped_reckoning(
|
||||
&mut skater.air_reckoning.state,
|
||||
update,
|
||||
flags,
|
||||
spin,
|
||||
);
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
impl SkeletonInputRuntime {
|
||||
pub(crate) fn update_biped_ground<F>(
|
||||
&mut self,
|
||||
|
||||
@@ -124,14 +124,6 @@ pub(super) fn publish(physics: &mut GamePhysics, skater: &mut SkaterRuntime) ->
|
||||
//State Fill82D3B010 is a single byte from this retained Slide owner.
|
||||
physical.state.signed_ground_step_84 = u8::from(skater.slide_state.state.wall_riding);
|
||||
}
|
||||
//ProcessOutput82DB71C4 calls SkateboardController::FillPhysOut82D76D20
|
||||
//for every selected state. The constructor82D74DD8 seeds448=0; use the
|
||||
//same controller owner as state transitions and post-physics ragdoll.
|
||||
physical.off_board.dropping_board_322 = u8::from(p.flags_2476 & (1 << 9) != 0);
|
||||
physical.off_board.retrieving_board_323 = u8::from(
|
||||
p.flags_2476 & (1 << 10) != 0 || skater.skateboard_controller.fields.state_448 == 4,
|
||||
);
|
||||
|
||||
//ProcessOutput82DB7130..7180 copies the HandPlantManager animation
|
||||
//flags1876 into Air304+20 one bit at a time, preserving the low28 bits.
|
||||
//The same manager word is consumed by the actual input publication.
|
||||
@@ -177,5 +169,6 @@ pub(super) fn publish(physics: &mut GamePhysics, skater: &mut SkaterRuntime) ->
|
||||
if state == PhysicalStateId::Teleporting {
|
||||
skater.teleport_state.publish_output(&mut skater.player_input.physical);
|
||||
}
|
||||
super::super::offboard::possession::publish(physics, skater)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -35,15 +35,6 @@ impl PhysicalStateCalls for Calls {
|
||||
));
|
||||
}
|
||||
}
|
||||
struct InactiveController;
|
||||
impl SkateboardControllerActions for InactiveController {
|
||||
fn hold_skateboard(&mut self) {
|
||||
unreachable!("active controller rejected before transition")
|
||||
}
|
||||
fn let_go_of_skateboard(&mut self) {
|
||||
unreachable!("active controller rejected before transition")
|
||||
}
|
||||
}
|
||||
pub(super) fn set(
|
||||
physics: &mut GamePhysics,
|
||||
skater: &mut SkaterRuntime,
|
||||
@@ -81,11 +72,29 @@ pub(super) fn set(
|
||||
"Physical state transition {current:?} -> {requested:?} requires its native Enter/Exit production adapter"
|
||||
));
|
||||
}
|
||||
if skater.skateboard_controller.fields.system_on_452 {
|
||||
return Err(
|
||||
"Ground transition requires active SkateboardController::LetGoOfSkateboard".into(),
|
||||
);
|
||||
if skater.player_input.toolkit.is_none() {
|
||||
skater.player_input.toolkit = Some(BoardToolkit::from_board(
|
||||
&physics.board,
|
||||
skater.player_input.processed.flags_2468,
|
||||
skater.player_input.processed.scalar_2612,
|
||||
skater.player_input.processed.vectors_464_480_496_512_528[0].map(f32::from_bits),
|
||||
[0.0, 1.0, 0.0, 0.0],
|
||||
));
|
||||
}
|
||||
let observation = super::super::offboard::possession::observe(physics, skater)?;
|
||||
let offboard = &mut skater.offboard;
|
||||
let mut controller_actions = super::super::offboard::possession::Actions {
|
||||
state: &mut offboard.possession,
|
||||
settings: &offboard.possession_settings,
|
||||
observation: &observation,
|
||||
effects: super::super::offboard::board_effects::BoardEffects {
|
||||
board: &mut physics.board,
|
||||
animated: &mut skater.ground_lifecycle.board_animated_290,
|
||||
policy: &mut offboard.board_policy,
|
||||
standard_deck_drag: offboard.standard_deck_drag,
|
||||
timestep: skater.player_input.processed.timestep_2604,
|
||||
},
|
||||
};
|
||||
let p = &mut skater.player_input.processed;
|
||||
let player = &mut skater.player_input.player;
|
||||
let mut data = StateChangeData {
|
||||
@@ -114,7 +123,7 @@ pub(super) fn set(
|
||||
requested as u32,
|
||||
&mut data,
|
||||
&mut Calls,
|
||||
&mut InactiveController,
|
||||
&mut controller_actions,
|
||||
)
|
||||
.map_err(|e| format!("Unknown physical state {}", e.0))?;
|
||||
player.state_count_1312 = data.player.word_1312;
|
||||
@@ -129,15 +138,6 @@ pub(super) fn set(
|
||||
p.state_count_2564 = data.processed.word_2564;
|
||||
p.state_timer_2664 = data.processed.scalar_2664;
|
||||
skater.skateboard_controller.fields = data.skateboard_controller;
|
||||
if skater.player_input.toolkit.is_none() {
|
||||
skater.player_input.toolkit = Some(BoardToolkit::from_board(
|
||||
&physics.board,
|
||||
p.flags_2468,
|
||||
p.scalar_2612,
|
||||
p.vectors_464_480_496_512_528[0].map(f32::from_bits),
|
||||
[0.0, 1.0, 0.0, 0.0],
|
||||
));
|
||||
}
|
||||
//Native82DB8540 publishes Processed state/history BEFORE old Exit. The
|
||||
//same retained objects then receive Exit followed by the new Enter.
|
||||
match current {
|
||||
@@ -159,7 +159,10 @@ pub(super) fn set(
|
||||
PhysicalStateId::GroundAnimation => super::super::ground_animation::enter(physics, skater),
|
||||
PhysicalStateId::SlideGround => super::super::slide_state::enter(physics, skater),
|
||||
PhysicalStateId::WipeoutGround => super::super::wipeout_states::enter(physics, skater),
|
||||
PhysicalStateId::Teleporting => { skater.teleport_state.enter(); Ok(()) },
|
||||
PhysicalStateId::Teleporting => {
|
||||
skater.teleport_state.enter();
|
||||
Ok(())
|
||||
}
|
||||
_ => unreachable!("state support checked before publication"),
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,30 @@ pub(crate) struct RidingOutputs {
|
||||
pending_wheel_queries: Option<[Option<WheelLineHit>; 4]>,
|
||||
}
|
||||
impl RidingOutputs {
|
||||
///Skeleton82BDE060's final82D8E3E0 call, argument7=true.
|
||||
pub(crate) fn update_biped_reckoning(
|
||||
&mut self,
|
||||
air: &mut skate_core::air::reckoning::AirState,
|
||||
input: super::offboard::skeleton_ground::ReckoningUpdate,
|
||||
flags_2468: u32,
|
||||
body_spin: 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: input.up, requested_forward: input.forward, blend: input.blend,
|
||||
reverse_stance: flags_2468 & 0x0010_0000 != 0,
|
||||
enable_body_spin_input: true, physical_body_spin_2812: body_spin,
|
||||
},
|
||||
);
|
||||
}
|
||||
///Board82C0D680 resets CollisionInfo and both probes, preserving7692.
|
||||
pub fn reset_for_teleport(&mut self) {
|
||||
let elapsed = self.ground.time_without_wheel_contact;
|
||||
|
||||
@@ -14,7 +14,11 @@ pub(super) fn advance(
|
||||
skater: &mut SkaterRuntime,
|
||||
truck_targets: [f32; 2],
|
||||
) -> Result<(), String> {
|
||||
let board_volumes = colliders::world_volumes(&physics.board, &physics.settings);
|
||||
let board_volumes = if skater.offboard.board_policy.volumes_enabled {
|
||||
colliders::world_volumes(&physics.board, &physics.settings)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let skeleton_volumes =
|
||||
skeleton_colliders::world_volumes(&skater.skeleton, &skater.skeleton_collision)?;
|
||||
// Each native assembly has its own query record and retention buffer.
|
||||
@@ -55,6 +59,16 @@ pub(super) fn advance(
|
||||
ATTACHED_REACTION_BASE + PART_COUNT,
|
||||
dt,
|
||||
);
|
||||
let skeleton_drive_count = drives.rows.len();
|
||||
if skater.skateboard_controller.fields.system_on_452 {
|
||||
super::offboard::hand_drives::append(
|
||||
&mut drives.rows,
|
||||
&skater.offboard.possession,
|
||||
physics.board.bodies(),
|
||||
skater.skeleton.bodies(),
|
||||
dt,
|
||||
);
|
||||
}
|
||||
let bodies = skater
|
||||
.skeleton
|
||||
.bodies_mut()
|
||||
@@ -77,6 +91,9 @@ pub(super) fn advance(
|
||||
.publish_physical_record(deck_frame(&physics.board));
|
||||
// These solved rows are consumed by the actual collision/drive feedback
|
||||
// phase; keep their identity and impulses after the shared solve.
|
||||
//Controller drive descriptors have flags28=0: they are not Skeleton's
|
||||
//spy drives and must not be paired with SkeletonDriveIdentity feedback.
|
||||
drives.rows.truncate(skeleton_drive_count);
|
||||
skater.solved_drives = Some(drives);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -109,3 +109,40 @@ stock controller's real-floor test runs60 updates each for idle, half input and
|
||||
full input. This is controller/query coverage; production transitions still
|
||||
reject BipedGround until its complete physical adapter is available. No playable
|
||||
binary has been staged or launched from these changes.
|
||||
|
||||
## Board possession and shared solver integration
|
||||
|
||||
Native hand drives82D74FD8/82D751D8 now append to the existing board/skeleton
|
||||
island. Factory82AE64F8 stores its fifth argument as body A and fourth as B:
|
||||
hands3/7 therefore react against deck6. The skeleton's drive-spy results retain
|
||||
their original row count. Tests verify opposite hand/deck reactions and zero
|
||||
reaction after releasing the hand drive.
|
||||
|
||||
The controller now has production hold/release, update and output adapters over
|
||||
the actual animated hand frames, physical hand contacts, board toolkit and
|
||||
retained collision normals. Its standard/released drag, hook drive and collision
|
||||
volume policies feed the existing physical owners. Native82C08818's surface vote
|
||||
is the producer of the controller's observed board840; it is not a sleeping flag.
|
||||
The lifecycle callback accepts the live controller fields so LetGo can preserve
|
||||
its eight-update throw countdown across a state change. Board positioning uses
|
||||
82C0B2C8 without clearing rates or pending forces, verified by regression test.
|
||||
|
||||
Observed code comes from the generated TU3 functions and raw image
|
||||
default_82000000_011B0000.bin, SHA256
|
||||
f4aa113eb541bfba03dbc108cf5ab43f58c965b20fa3b82f9c40938a0ad841c4.
|
||||
Lookup8 proves893DA1B966231D76 is MaxDistance (hide trigger) and
|
||||
6CFF6F2C43F0AD41 is DistanceToHideSkateboard (hidden position offset), correcting
|
||||
the earlier reversed inference. The biped skeleton adapter also borrows the live roots,
|
||||
IK, collision and shared reckoning owners, including native body-spin input.
|
||||
|
||||
The thrown-board update750F0 calls82C07328 three times. These outputs are angular
|
||||
displacements:07328 removes the current per-step motion along the requested axis
|
||||
and uses inverse-tensor/step/tensor conversion to add angular acceleration. It
|
||||
does not apply the outputs as raw torque. A regression covers braking overshoot
|
||||
and correcting opposite motion while preserving perpendicular angular velocity.
|
||||
|
||||
Validation:459 core tests and83 game tests passed, including the final
|
||||
output/skeleton-owner wiring, private map and stock difficulty tests. These
|
||||
are still component and existing-state tests: BipedGround/BipedAir state
|
||||
integration and remount remain unfinished. The runout crash is not yet fixed
|
||||
in a staged playable build.
|
||||
Reference in new issue
Block a user