Use camera-relative offboard input and capture airborne bail diagnostics

This commit is contained in:
chazmm committed 2026-09-06 22:27:47 +01:00
1 parent 375192e795
commit 0a3904cd00
8 files changed
+94 -3

No files matched your search

@@ -87,6 +87,14 @@ pub fn produce_analog(
) -> [OffboardIntent; 4] {
let words = controller.words();
let stick = [f32::from_bits(words[7]), 0.0, f32::from_bits(words[8]), 0.0];
produce_analog_world(stick, observation)
}
/// Host camera conversion precedes the native world-space Biped attributes.
pub fn produce_analog_world(
stick: [f32; 4],
observation: AnalogObservation,
) -> [OffboardIntent; 4] {
let mut horizontal = observation.effective_skeleton_z;
horizontal[1] = 0.0;
let forward = safe_unit(horizontal);
@@ -268,7 +268,7 @@ pub(crate) fn advance(
//Native Listener::Fill825999F0, after derived controller sampling and before AG.
let mut action_intents = controls.action_intents.clone();
use skate_core::input::offboard_intentions::{
AnalogObservation, produce_analog, produce_discrete,
AnalogObservation, produce_analog_world, produce_discrete,
};
let biped = &skater.offboard.controller.state.contact;
for intent in produce_discrete(
@@ -277,8 +277,11 @@ pub(crate) fn advance(
physical.air.use_air_reckoning_452 != 0,
)
.into_iter()
.chain(produce_analog(
&controls.controller,
.chain(produce_analog_world(
controls.offboard_direction.unwrap_or_else(|| {
let words = controls.controller.words();
[f32::from_bits(words[7]), 0., f32::from_bits(words[8]), 0.]
}),
AnalogObservation {
effective_skeleton_z: p.effective_anim_transform_192[2].map(f32::from_bits),
biped_correction: biped.active.then_some(biped.direction),
+16
View File
@@ -12,6 +12,7 @@ use skate_core::input::{
#[derive(Resource)]
pub(crate) struct PlayerControls {
pub controller: DerivedControllerInput,
pub offboard_direction: Option<[f32; 4]>,
pub intents: Vec<RidingIntent>,
pub action_intents: IntentMap,
pub ticks: u64,
@@ -27,6 +28,7 @@ impl Default for PlayerControls {
controller.initialize();
Self {
controller,
offboard_direction: None,
intents: Vec::new(),
action_intents: IntentMap::new(),
ticks: 0,
@@ -43,6 +45,7 @@ pub(super) fn sample(
input: Res<ControllerInput>,
physics: Res<GamePhysics>,
skater: Res<SkaterRuntime>,
camera: Res<crate::camera::CameraRuntime>,
mut player: ResMut<PlayerControls>,
) {
let mut map = input.player_actions();
@@ -52,6 +55,18 @@ pub(super) fn sample(
physics.settings.input_magnitude_threshold,
skater.player_input.physical.scoring.capabilities_204,
);
if let Some(frame) = camera.frame {
let at = Vec3::from_array(frame.basis.columns[2]);
let forward = Vec3::new(at.x, 0., at.z).normalize_or_zero();
if forward != Vec3::ZERO {
// Match camera::present: native camera Right becomes Bevy -Right.
let right = forward.cross(Vec3::Y);
let words = player.controller.words();
let direction = right * f32::from_bits(words[7])
+ forward * f32::from_bits(words[8]);
player.offboard_direction = Some([direction.x, 0., direction.z, 0.]);
}
}
player.publish_gestures(physics.animation_profile.physics_mode, skater.player_input.physical.state.state_16);
}
@@ -69,6 +84,7 @@ impl PlayerControls {
}
pub fn update(&mut self, map: &mut impl ActionMap, dt: f32, magnitude_threshold: f32, physical_capabilities: u32) {
self.offboard_direction = None;
self.controller.update(
map,
dt,
@@ -0,0 +1,59 @@
//! Bounded numeric history for user-reproduced airborne bails. No per-frame IO.
use crate::physics::SkaterRuntime;
use std::collections::VecDeque;
#[derive(Debug)]
#[allow(dead_code)] // Fields are serialized together only when a bail is requested.
struct Sample {
tick: i32,
flags: [u32; 3],
packet: skate_core::player::offboard::air_prediction::Packet,
root: [[f32; 4]; 4],
root_velocity: [f32; 4],
body: [f32; 4],
lift: f32,
extra: [[f32; 4]; 2],
maximum_error: Option<f32>,
reasons: [bool; 34],
}
pub(crate) struct History {
samples: VecDeque<Sample>,
reports: usize,
}
impl Default for History {
fn default() -> Self {
Self { samples: VecDeque::with_capacity(32), reports: 0 }
}
}
impl History {
pub(crate) fn clear(&mut self) { self.samples.clear(); }
}
pub(crate) fn record(skater: &mut SkaterRuntime) {
let history = &mut skater.offboard.air_diagnostics;
if history.reports >= 3 { return; }
let state = &skater.offboard.air_state;
let p = &skater.player_input.processed;
if history.samples.len() == 32 { history.samples.pop_front(); }
history.samples.push_back(Sample {
tick: state.tick,
flags: [p.flags_2476, p.flags_2480, p.flags_2484],
packet: state.packet,
root: skater.animated_skeleton.roots.animation_to_world,
root_velocity: skater.skeleton_input.root_velocity,
body: state.body_position,
lift: state.lift,
extra: skater.collision_extra_displacements,
maximum_error: skater.collision_maximum_error,
reasons: skater.wipeout.state.reasons,
});
if skater.wipeout.state.reasons.iter().any(|&requested| requested) {
// Format in memory, then one write; never send Debug's individual
// formatting fragments to the Windows redirected stderr handle.
let report = format!("SKATE_BIPED_AIR_BAIL history={:#?}", history.samples);
eprintln!("{report}");
history.samples.clear();
history.reports += 1;
}
}
@@ -16,6 +16,7 @@ fn effective(skater: &SkaterRuntime) -> Frame {
frame
}
pub(crate) fn enter(physics: &mut GamePhysics, skater: &mut SkaterRuntime) -> Result<(), String> {
skater.offboard.air_diagnostics.clear();
let frame = effective(skater);
let p = &skater.player_input.processed;
let body = skater.animated_skeleton.board_frames.com_frame[3];
@@ -1,5 +1,6 @@
//! Off-board game adapters over the shared physical and scene owners.
pub(crate) mod air_state;
pub(crate) mod air_diagnostics;
pub(crate) mod air_trajectory;
pub(crate) mod board_effects;
pub(crate) mod contact_queries;
@@ -10,6 +10,7 @@ use skate_core::point_graph::PointGraph;
use skate_data::{animation_metadata::AnimationMetadata, collections::Collections};
pub(crate) struct Runtime {
pub air_diagnostics: super::air_diagnostics::History,
pub air_state: skate_core::player::offboard::air_state::State,
pub air_prediction: Option<skate_core::player::offboard::air_prediction::Prediction>,
pub air_blend_curve: PointGraph<8>,
@@ -148,6 +149,7 @@ impl Runtime {
pub(crate) fn load(data: &Collections, metadata: &AnimationMetadata) -> Result<Self, String> {
let settings = super::settings::Settings::load(data, metadata)?;
Ok(Self {
air_diagnostics: Default::default(),
air_state: Default::default(),
air_prediction: None,
air_blend_curve: settings.air_blend_curve,
+1
View File
@@ -117,6 +117,7 @@ pub(super) fn check_after_physics(
],
},
);
super::offboard::air_diagnostics::record(skater);
Ok(())
}
PhysicalStateId::PhysicsGround | PhysicalStateId::SlideGround => {