mirror of
https://github.com/SK8-ENGINE/skate-3-rust-engine.git
synced 2026-10-26 19:47:44 +00:00
Connect difficulty-dependent native spline acquisition and airborne grind adjustment
This commit is contained in:
1 parent
40eb5b5005
commit
9575567a1f
19 files changed
+846
-110
No files matched your search
@@ -1,6 +1,6 @@
|
||||
//! TU3 rail-trajectory candidate leaves82D60C80/82D60B98,
|
||||
//! 82D6A398 and82D6A168. Admission and physical adjustment are separate owners.
|
||||
use super::{Prediction, Trajectory, math::*};
|
||||
use super::{math::*, Prediction, Trajectory};
|
||||
use crate::{physics::grind_contact::Primitive, point_graph::PointGraph};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -326,3 +326,18 @@ pub fn take_best_grind(
|
||||
}
|
||||
chosen.map(|i| candidates.remove(i))
|
||||
}
|
||||
|
||||
/// A real world query and its accepted target, retained through selector scoring.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct GrindTarget {
|
||||
pub air_limits: [Vector; 3],
|
||||
pub edge: Primitive,
|
||||
pub point: Vector,
|
||||
pub normal: Vector,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct GrindEvaluation {
|
||||
pub target: Option<GrindTarget>,
|
||||
pub score: f32,
|
||||
pub distance: f32,
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use super::{grind::GrindSurfaceEvidence, math::*};
|
||||
use crate::math::Vector3;
|
||||
use crate::physics::{
|
||||
board_world::BoardWorld,
|
||||
ground_hang_geometry::{HangGeometryInput, hang_lines},
|
||||
ground_hang_geometry::{hang_lines, HangGeometryInput},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -113,3 +113,56 @@ fn rotate(axis: Vector, value: Vector, angle: f32) -> Vector {
|
||||
fn xyz(v: Vector) -> Vector3 {
|
||||
Vector3::new(v[0], v[1], v[2])
|
||||
}
|
||||
|
||||
impl GrindSurface {
|
||||
///82C21930's three angular reference directions for GrindAirAdjust.
|
||||
pub fn air_limits(
|
||||
&self,
|
||||
start: Vector,
|
||||
end: Vector,
|
||||
point: Vector,
|
||||
board_position: Vector,
|
||||
) -> [Vector; 3] {
|
||||
let rail = normalize(sub(end, start));
|
||||
let up = normalize(cross(rail, cross(UP, rail)));
|
||||
let side = cross(up, rail);
|
||||
if self.evidence.kind == 0 {
|
||||
let sign = if dot(side, sub(board_position, point)) > 0.0 {
|
||||
1.0
|
||||
} else {
|
||||
-1.0
|
||||
};
|
||||
let oriented = scale(side, sign);
|
||||
let axis = cross(oriented, up);
|
||||
[
|
||||
side,
|
||||
rotate(axis, oriented, f32::from_bits(0x3eb2b8c3)),
|
||||
rotate(axis, oriented, f32::from_bits(0x401a25c2)),
|
||||
]
|
||||
} else {
|
||||
let sign = if dot(side, self.evidence.side) > 0.0 {
|
||||
1.0
|
||||
} else {
|
||||
-1.0
|
||||
};
|
||||
let axis = cross(side, up);
|
||||
[
|
||||
if self.evidence.kind == 1 {
|
||||
side
|
||||
} else {
|
||||
cross(self.normal, rail)
|
||||
},
|
||||
rotate(
|
||||
scale(axis, -sign),
|
||||
scale(side, -sign),
|
||||
f32::from_bits(0x3eb2b8c3),
|
||||
),
|
||||
rotate(
|
||||
scale(axis, sign),
|
||||
scale(side, sign),
|
||||
f32::from_bits(0x3f3ba866),
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
//! TU3 scoring82D68FE8/82D68AF0/82D69790 for a world with no grind edges.
|
||||
//! That topology is supplied by the authored BoardWorld, not inferred from a
|
||||
//! failed collision. Every wall probe still queries real world geometry.
|
||||
//! TU3 scoring82D68FE8/82D68AF0/82D69790 with world-supplied grind admission.
|
||||
use super::{
|
||||
Prediction, SelectorInput, SelectorSettings, SurfaceHit, launch::adjust_trajectory, math::*,
|
||||
launch::adjust_trajectory, math::*, Prediction, SelectorInput, SelectorSettings, SurfaceHit,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -15,6 +13,7 @@ pub(super) struct Candidate {
|
||||
pub score: f32,
|
||||
pub wall_score: f32,
|
||||
pub wall_ride: bool,
|
||||
pub grind: Option<super::grind::GrindTarget>,
|
||||
}
|
||||
pub(super) fn score(
|
||||
candidates: &mut [Candidate],
|
||||
@@ -22,6 +21,7 @@ pub(super) fn score(
|
||||
adjusted_on_vert: bool,
|
||||
input: SelectorInput,
|
||||
s: &SelectorSettings,
|
||||
mut grind: impl FnMut(&mut Prediction, bool) -> Result<super::grind::GrindEvaluation, String>,
|
||||
mut line: impl FnMut(Vector, Vector, f32) -> Result<Option<SurfaceHit>, String>,
|
||||
) -> Result<bool, String> {
|
||||
let mut all_miss = true; //selector9662, consumed by PhysicsAir COM latch
|
||||
@@ -30,6 +30,7 @@ pub(super) fn score(
|
||||
c.score = 0.0;
|
||||
c.wall_score = 0.0;
|
||||
c.wall_ride = false;
|
||||
c.grind = None;
|
||||
if !c.prediction.result.valid() {
|
||||
continue;
|
||||
}
|
||||
@@ -53,13 +54,25 @@ pub(super) fn score(
|
||||
let force_score = (force_dot * s.score_landing_force) * scalar;
|
||||
let direction_score = (sideways * s.score_landing_direction) * scalar;
|
||||
|
||||
//82D69C00 returns0 immediately for the actual empty edge query. Its
|
||||
//9552 flag remains clear.82D69D68 then evaluates the stock graph at
|
||||
//its native invalid-query distance1000; do not substitute a score.
|
||||
let grind_score = if input.flags_2476 & 0x0200_0000 == 0
|
||||
&& (input.grind_lock_distance > 0.5 || !middle)
|
||||
{
|
||||
s.grind_penalty_vs_distance.evaluate(1000.0)
|
||||
//82D69C00 attempts acquisition only on the first-pass centre trajectory.
|
||||
let mut evaluation = grind(
|
||||
&mut c.prediction,
|
||||
middle
|
||||
&& !(input.flags_2472 & 0x2000_0000 != 0
|
||||
&& input.offboard_flags_1776 & 0x0400_0000 != 0
|
||||
&& input.offboard_flags_1776 & 0x0800_0000 == 0),
|
||||
)?;
|
||||
if adjusted_on_vert && input.directional_input < 0.5 && evaluation.score > 0.0 {
|
||||
evaluation.target = None;
|
||||
evaluation.score = s.grind_penalty_vs_distance.evaluate(0.0);
|
||||
}
|
||||
c.grind = evaluation.target;
|
||||
let grind_score = if input.flags_2476 & 0x0200_0000 != 0 {
|
||||
0.0
|
||||
} else if evaluation.score != 0.0 {
|
||||
evaluation.score
|
||||
} else if input.grind_lock_distance > 0.5 || !middle {
|
||||
s.grind_penalty_vs_distance.evaluate(evaluation.distance)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
//! Original selector Launch82D67848 and Update82D68800 with typed batch ownership.
|
||||
use super::{
|
||||
LaunchInfo, Prediction, QueryRequest, QueryResult, SelectorInput, SelectorSettings, SurfaceHit,
|
||||
Trajectory, launch,
|
||||
launch,
|
||||
math::*,
|
||||
scoring::{self, Candidate},
|
||||
LaunchInfo, Prediction, QueryRequest, QueryResult, SelectorInput, SelectorSettings, SurfaceHit,
|
||||
Trajectory,
|
||||
};
|
||||
|
||||
///The current authored world contains collision triangles and no grind edges.
|
||||
@@ -22,7 +23,8 @@ pub struct Selection {
|
||||
pub collision_position: Vector, //2688
|
||||
pub com_trajectory: Trajectory, //2704
|
||||
pub surface_category: u32, //9640
|
||||
pub wall_ride: bool, //9661
|
||||
pub wall_ride: bool,
|
||||
pub grind: Option<super::grind::GrindTarget>, //9661
|
||||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct TrajectorySelector {
|
||||
@@ -172,6 +174,7 @@ impl TrajectorySelector {
|
||||
score: 0.0,
|
||||
wall_score: 0.0,
|
||||
wall_ride: false,
|
||||
grind: None,
|
||||
}
|
||||
});
|
||||
for (index, c) in self
|
||||
@@ -199,6 +202,28 @@ impl TrajectorySelector {
|
||||
s: &SelectorSettings,
|
||||
_topology: WorldWithoutGrindEdges,
|
||||
line: impl FnMut(Vector, Vector, f32) -> Result<Option<SurfaceHit>, String>,
|
||||
) -> Result<bool, String> {
|
||||
self.complete_batch_with_grinds(
|
||||
results,
|
||||
input,
|
||||
s,
|
||||
|_, _| {
|
||||
Ok(super::grind::GrindEvaluation {
|
||||
target: None,
|
||||
score: 0.0,
|
||||
distance: 1000.0,
|
||||
})
|
||||
},
|
||||
line,
|
||||
)
|
||||
}
|
||||
pub fn complete_batch_with_grinds(
|
||||
&mut self,
|
||||
results: &[QueryResult],
|
||||
input: SelectorInput,
|
||||
s: &SelectorSettings,
|
||||
grind: impl FnMut(&mut Prediction, bool) -> Result<super::grind::GrindEvaluation, String>,
|
||||
line: impl FnMut(Vector, Vector, f32) -> Result<Option<SurfaceHit>, String>,
|
||||
) -> Result<bool, String> {
|
||||
self.just_changed = false;
|
||||
if !self.pending {
|
||||
@@ -220,6 +245,7 @@ impl TrajectorySelector {
|
||||
self.adjusted_on_vert,
|
||||
input,
|
||||
s,
|
||||
grind,
|
||||
line,
|
||||
)?;
|
||||
let mut index = 0;
|
||||
@@ -250,6 +276,7 @@ impl TrajectorySelector {
|
||||
}),
|
||||
surface_category: self.selection.map_or(0, |v| v.surface_category),
|
||||
wall_ride: c.wall_ride,
|
||||
grind: c.grind,
|
||||
};
|
||||
if c.prediction.result.contact_time < s.minimum_valid_time {
|
||||
self.suggested_normal = Some(c.prediction.result.contact_normal);
|
||||
@@ -260,10 +287,17 @@ impl TrajectorySelector {
|
||||
}
|
||||
selection.surface_category = (c.prediction.result.surface >> 7) & 31;
|
||||
selection.com_trajectory = self.com_trajectory(selection, input, s);
|
||||
//No grind edges means native9652/9653 are false. AIPath is absent from
|
||||
//this player-owned runtime, so original9659 is false as well.
|
||||
self.grind_locked_to_middle = false;
|
||||
let second_pass = self.pass == 1
|
||||
//82D68C80: an accepted middle trajectory owns9653 and skips the second pass.
|
||||
self.grind_locked_to_middle = index == 0 && c.grind.is_some();
|
||||
if let Some(target) = self.candidates.iter().take(count).find_map(|c| c.grind) {
|
||||
//82D6AF30/82E0A120 publish the rail-derived upright plane normal.
|
||||
self.grind_normal = Some(crate::physics::grind_contact::upright_normal(sub(
|
||||
target.edge.end,
|
||||
target.edge.start,
|
||||
)));
|
||||
}
|
||||
let second_pass = !self.grind_locked_to_middle
|
||||
&& self.pass == 1
|
||||
&& count >= 2
|
||||
&& !(angle_between(selection.landing_normal, input.ground_normal)
|
||||
< s.minimum_normal_delta_second_pass);
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
//! TU3 GrindAirAdjust82D712E0: predicted board/contact selection and bounded pose correction.
|
||||
use super::grind_contact::{control::rotate, Primitive};
|
||||
use super::skeleton_animation_record::{AnimationPartTransform as Frame, IDENTITY};
|
||||
use crate::riding::ground_correction_math::dot_product as dot;
|
||||
type V = [f32; 4];
|
||||
const UP: V = [0., 1., 0., 0.];
|
||||
const DEG: f32 = core::f32::consts::PI / 180.0;
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Settings {
|
||||
pub frames: usize,
|
||||
pub stomp: f32,
|
||||
pub points: [V; 5],
|
||||
pub ranges: [f32; 7],
|
||||
pub distances: [f32; 7],
|
||||
pub yaw_assist: [f32; 7],
|
||||
pub max_offset: f32,
|
||||
pub max_delta: f32,
|
||||
pub max_angle: f32,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Target {
|
||||
pub edge: Primitive,
|
||||
pub limits: [V; 3],
|
||||
}
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Adjustment {
|
||||
pub axis: V,
|
||||
pub offset: V,
|
||||
pub angles: V,
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct GrindAir {
|
||||
pub target: Option<Target>,
|
||||
offset_delta: V,
|
||||
offset: V,
|
||||
angle_delta: V,
|
||||
angles: V,
|
||||
previous_kind: Option<usize>,
|
||||
}
|
||||
impl GrindAir {
|
||||
///Start82D34FE8 clears retained displacement and angle integrators.
|
||||
pub fn start(&mut self, target: Target) {
|
||||
*self = Self {
|
||||
target: Some(target),
|
||||
..Self::default()
|
||||
};
|
||||
}
|
||||
pub fn update(
|
||||
&mut self,
|
||||
board: Frame,
|
||||
velocity: V,
|
||||
angular_velocity: V,
|
||||
up: V,
|
||||
dt: f32,
|
||||
flags: [u32; 3],
|
||||
inverted: bool,
|
||||
s: &Settings,
|
||||
) -> Option<Adjustment> {
|
||||
let target = self.target?;
|
||||
//82D712E0 suppression: Processed2480 bit26,2472 bit15,2468 bit3.
|
||||
if flags[0] & 0x0400_0000 != 0 || flags[1] & 0x8000 != 0 || flags[2] & 8 != 0 {
|
||||
self.offset = [0.; 4];
|
||||
self.angles = [0.; 4];
|
||||
return None;
|
||||
}
|
||||
let rail = unit(sub(target.edge.end, target.edge.start));
|
||||
let normal = unit(cross(rail, cross(up, rail)));
|
||||
let count = s.frames;
|
||||
if count < 2 {
|
||||
return None;
|
||||
}
|
||||
//82D71430 integrates physical angular velocity and native gravity/stomp.
|
||||
|
||||
let mut samples = Vec::with_capacity(count);
|
||||
let mut headings = Vec::with_capacity(count);
|
||||
let mut current = board;
|
||||
let mut vel = velocity;
|
||||
let speed = length(angular_velocity);
|
||||
let axis = unit(angular_velocity);
|
||||
let acceleration = add([0., -9.8, 0., 0.], scale(up, s.stomp));
|
||||
for _ in 0..count {
|
||||
let points = s.points.map(|p| point(current, p));
|
||||
let forward = sub(points[1], points[4]);
|
||||
let flat = unit(sub(forward, scale(normal, dot(forward, normal))));
|
||||
let aligned = if dot(flat, rail) < 0.0 {
|
||||
scale(flat, -1.0)
|
||||
} else {
|
||||
flat
|
||||
};
|
||||
let mut angle = crate::trigonometry::acos(dot(aligned, rail).clamp(-1.0, 1.0));
|
||||
if angle.abs() > core::f32::consts::FRAC_PI_2 {
|
||||
angle = core::f32::consts::PI - angle;
|
||||
}
|
||||
let sign = if dot(normal, cross(aligned, rail)) < 0.0 {
|
||||
-1.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
headings.push(-sign * angle / DEG);
|
||||
samples.push(points);
|
||||
if speed >= f32::from_bits(0x37800000) {
|
||||
for column in &mut current[..3] {
|
||||
*column = rotate(axis, *column, speed * dt);
|
||||
}
|
||||
}
|
||||
vel = add(vel, scale(acceleration, dt));
|
||||
current[3] = add(current[3], scale(vel, dt));
|
||||
}
|
||||
//82D721A0: first strict plane crossing for each of the five deck probes.
|
||||
let mut times = [-1.0; 5];
|
||||
let mut distances = [0.0; 5];
|
||||
let mut corrections = [[0.; 4]; 5];
|
||||
let mut earliest = count as f32;
|
||||
for probe in 0..5 {
|
||||
for i in 1..count {
|
||||
let a = dot(normal, sub(samples[i - 1][probe], target.edge.start));
|
||||
let b = dot(normal, sub(samples[i][probe], target.edge.start));
|
||||
if a * b < 0.0 {
|
||||
let fraction = (a / (a - b)).abs();
|
||||
let time = (i - 1) as f32 + fraction;
|
||||
let at = add(
|
||||
scale(samples[i - 1][probe], 1.0 - fraction),
|
||||
scale(samples[i][probe], fraction),
|
||||
);
|
||||
let on = add(
|
||||
target.edge.start,
|
||||
scale(rail, dot(sub(at, target.edge.start), rail)),
|
||||
);
|
||||
let correction = sub(on, at);
|
||||
times[probe] = time;
|
||||
distances[probe] = length(correction);
|
||||
corrections[probe] = correction;
|
||||
earliest = earliest.min(time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//82D723D0/82D724E8. Entries0..6: inverted, tips, deck, both trucks, each truck.
|
||||
let probes = [0, 1, 4, 0, 2, 2, 3];
|
||||
let desired = [90., 90., 90., 90., 0., 0., 0.];
|
||||
let valid = |kind: usize| {
|
||||
let p = probes[kind];
|
||||
let t = times[p];
|
||||
if kind == 4 {
|
||||
if times[2].min(times[3]) < 4.0 && (times[2] - times[3]).abs() > 2.0 {
|
||||
return false;
|
||||
}
|
||||
} else if earliest < 5.0 && (earliest - t).abs() > 3.0 {
|
||||
return false;
|
||||
}
|
||||
t > 0.0
|
||||
&& distances[p] < s.distances[kind]
|
||||
&& (desired[kind] - headings[(t + 0.5) as usize].abs()).abs() < s.ranges[kind]
|
||||
};
|
||||
let mut selected = None;
|
||||
for mut kind in 0..7 {
|
||||
if inverted != (kind == 0) || !valid(kind) {
|
||||
continue;
|
||||
}
|
||||
if kind == 3 {
|
||||
if let Some(previous @ (1 | 2)) = self.previous_kind {
|
||||
if valid(previous) {
|
||||
kind = previous;
|
||||
}
|
||||
}
|
||||
}
|
||||
if selected.is_none_or(|old: usize| distances[probes[kind]] < distances[probes[old]]) {
|
||||
selected = Some(kind);
|
||||
}
|
||||
if selected.is_some_and(|k| k >= 5) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let kind = selected?;
|
||||
self.previous_kind = Some(kind);
|
||||
let p = probes[kind];
|
||||
let time = times[p];
|
||||
let sample = (time + 0.5) as usize;
|
||||
//82D72610: integrate correction/time; bound each step and total offset.
|
||||
self.offset_delta = limited(
|
||||
add(self.offset_delta, scale(corrections[p], 1.0 / time)),
|
||||
s.max_delta,
|
||||
);
|
||||
self.offset = limited(add(self.offset, self.offset_delta), s.max_offset);
|
||||
//82D72810: native angle integrators use degrees for the yaw error only.
|
||||
let heading = headings[sample];
|
||||
let desired = if heading > 0.0 {
|
||||
desired[kind]
|
||||
} else {
|
||||
-desired[kind]
|
||||
};
|
||||
let speed =
|
||||
((desired - heading) / time * s.yaw_assist[kind]).clamp(-s.max_angle, s.max_angle);
|
||||
self.angle_delta[1] =
|
||||
(self.angle_delta[1] / DEG + speed).clamp(-s.max_angle, s.max_angle) * DEG;
|
||||
let forward = sub(samples[sample][1], samples[sample][4]);
|
||||
let across_board = unit(sub(forward, scale(rail, dot(forward, rail))));
|
||||
let roll_error = match kind {
|
||||
0 => {
|
||||
let v = signed_angle(across_board, target.limits[0], rail);
|
||||
if v > core::f32::consts::FRAC_PI_2 {
|
||||
v - core::f32::consts::PI
|
||||
} else if v < -core::f32::consts::FRAC_PI_2 {
|
||||
v + core::f32::consts::PI
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
1 | 2 | 3 => {
|
||||
let from = if kind == 1 {
|
||||
scale(across_board, -1.0)
|
||||
} else {
|
||||
across_board
|
||||
};
|
||||
let a = signed_angle(from, target.limits[1], rail);
|
||||
let b = signed_angle(from, target.limits[2], rail);
|
||||
if a.abs() < b.abs() {
|
||||
a
|
||||
} else {
|
||||
b
|
||||
}
|
||||
}
|
||||
_ => 0.0,
|
||||
};
|
||||
self.angle_delta[0] =
|
||||
(self.angle_delta[0] + roll_error / time).clamp(-s.max_angle * DEG, s.max_angle * DEG);
|
||||
//82D71BB0: projection of effective deck-right relative to support/up.
|
||||
let projected = unit(board[0].map(|v| v - dot(board[0], normal)));
|
||||
let pitch_error = if dot(projected, projected) > 0.9 {
|
||||
signed_angle(projected, normal, board[2]) * 0.9
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
self.angle_delta[2] = if matches!(kind, 0 | 1 | 2 | 3) {
|
||||
pitch_error / time
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
self.angles = add(self.angles, self.angle_delta);
|
||||
Some(Adjustment {
|
||||
axis: rail,
|
||||
offset: self.offset,
|
||||
angles: self.angles,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Adjustment {
|
||||
///82BDCFB0 writes the existing15-frame board/IK offset owner15696.
|
||||
pub fn local_transform(
|
||||
self,
|
||||
animation_to_world: Frame,
|
||||
board_position: V,
|
||||
physical_forward: V,
|
||||
) -> Frame {
|
||||
let across = unit(cross(UP, self.axis));
|
||||
let up = unit(cross(self.axis, across));
|
||||
let rotation = |v| {
|
||||
rotate(
|
||||
up,
|
||||
rotate(
|
||||
self.axis,
|
||||
rotate(physical_forward, v, self.angles[2]),
|
||||
self.angles[0],
|
||||
),
|
||||
self.angles[1],
|
||||
)
|
||||
};
|
||||
let mut world = IDENTITY;
|
||||
for i in 0..3 {
|
||||
world[i] = rotation(IDENTITY[i]);
|
||||
}
|
||||
world[3] = sub(add(board_position, self.offset), rotation(board_position));
|
||||
let inverse = inverse(animation_to_world);
|
||||
super::skeleton_animation_record::compose_affine(
|
||||
&inverse,
|
||||
&super::skeleton_animation_record::compose_affine(&world, &animation_to_world),
|
||||
)
|
||||
}
|
||||
}
|
||||
fn inverse(f: Frame) -> Frame {
|
||||
let mut r = IDENTITY;
|
||||
for i in 0..3 {
|
||||
for j in 0..3 {
|
||||
r[i][j] = f[j][i];
|
||||
}
|
||||
}
|
||||
r[3] = [-dot(f[0], f[3]), -dot(f[1], f[3]), -dot(f[2], f[3]), 0.];
|
||||
r
|
||||
}
|
||||
fn point(f: Frame, v: V) -> V {
|
||||
super::skeleton_animation_record::transform_point(&f, v)
|
||||
}
|
||||
fn signed_angle(a: V, b: V, axis: V) -> f32 {
|
||||
let a = unit(a);
|
||||
let b = unit(b);
|
||||
let angle = crate::trigonometry::acos(dot(a, b).clamp(-1.0, 1.0));
|
||||
if dot(cross(a, b), axis) < 0.0 {
|
||||
-angle
|
||||
} else {
|
||||
angle
|
||||
}
|
||||
}
|
||||
fn sub(a: V, b: V) -> V {
|
||||
core::array::from_fn(|i| a[i] - b[i])
|
||||
}
|
||||
fn add(a: V, b: V) -> V {
|
||||
core::array::from_fn(|i| a[i] + b[i])
|
||||
}
|
||||
fn scale(v: V, s: f32) -> V {
|
||||
v.map(|v| v * s)
|
||||
}
|
||||
fn length(v: V) -> f32 {
|
||||
dot(v, v).sqrt()
|
||||
}
|
||||
fn unit(v: V) -> V {
|
||||
let l = length(v);
|
||||
if l > 0.000001 {
|
||||
scale(v, 1.0 / l)
|
||||
} else {
|
||||
[0.; 4]
|
||||
}
|
||||
}
|
||||
fn limited(v: V, max: f32) -> V {
|
||||
let l = length(v);
|
||||
if l > max {
|
||||
scale(v, max / l)
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
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.,
|
||||
]
|
||||
}
|
||||
@@ -71,3 +71,5 @@ pub mod skeleton_landing;
|
||||
pub mod skeleton_motion;
|
||||
pub mod skeleton_output;
|
||||
pub mod skeleton_root;
|
||||
|
||||
pub mod grind_air;
|
||||
@@ -0,0 +1,165 @@
|
||||
//! Live TU3 trajectory-to-spline query82D69C00 and admission82D6A840.
|
||||
use skate_core::{
|
||||
air::trajectory::{grind::*, Prediction},
|
||||
physics::{board_world::BoardWorld, grind_contact::Primitive},
|
||||
point_graph::PointGraph,
|
||||
};
|
||||
use skate_data::collections::Collections;
|
||||
type V = [f32; 4];
|
||||
pub(super) struct Settings {
|
||||
pub limits: GrindAssistLimits,
|
||||
height: PointGraph<8>,
|
||||
padding: f32,
|
||||
maximum_adjust: f32,
|
||||
velocity_scalar: f32,
|
||||
max_angle: f32,
|
||||
score: f32,
|
||||
truck_distance: f32,
|
||||
}
|
||||
impl Settings {
|
||||
pub fn load(data: &Collections) -> Result<Self, String> {
|
||||
let t = |key| data.float("physics_trajectory", "default", key);
|
||||
Ok(Self {
|
||||
limits: GrindAssistLimits {
|
||||
lock_distance: 0.0,
|
||||
max_speed_squared_ledge: t("GrindMaxSpeedSqrIntoLedge")?,
|
||||
max_speed_squared_rail: t("GrindMaxSpeedSqrIntoGrind")?,
|
||||
max_downward_speed: t("GrindMaxSpeedDownOntoGrind")?,
|
||||
ledge_scalars: [
|
||||
t("GrindLockLedgeScalar2")?,
|
||||
t("GrindLockLedgeScalar")?,
|
||||
t("GrindLockLedgeLowSideScalar2")?,
|
||||
t("GrindLockLedgeLowSideScalar")?,
|
||||
],
|
||||
tip_scalar: t("GrindTipScalar")?,
|
||||
maximum_adjust_angle: t("GrindAdjustMaxAngle")?,
|
||||
deck_dimensions: [
|
||||
data.float("physicsdeck", "default", "DeckMidLength")?,
|
||||
data.float("physicsdeck", "default", "DeckFrontEndSize")?,
|
||||
],
|
||||
},
|
||||
height: super::settings::graph8(
|
||||
data,
|
||||
"physics_trajectory",
|
||||
"RequiredAngleVsHeight",
|
||||
true,
|
||||
)?,
|
||||
padding: t("GrindOffset")?,
|
||||
maximum_adjust: t("MaxTrajectoryAdjust")?,
|
||||
velocity_scalar: t("GrindLandingVelScalar")?,
|
||||
max_angle: t("GrindLandingMaxAngle")?,
|
||||
score: t("ScoreGrind")?,
|
||||
truck_distance: data.float("physics_grinds", "default", "DeckCenterToTruck")?,
|
||||
})
|
||||
}
|
||||
pub fn evaluate(
|
||||
&self,
|
||||
prediction: &mut Prediction,
|
||||
acquire: bool,
|
||||
world: &BoardWorld,
|
||||
edges: &[Primitive],
|
||||
nearby: &mut Vec<Primitive>,
|
||||
lock_distance: f32,
|
||||
com: V,
|
||||
board_position: V,
|
||||
) -> Result<GrindEvaluation, String> {
|
||||
let velocity = prediction.collision_velocity();
|
||||
let landing = prediction
|
||||
.request
|
||||
.trajectory
|
||||
.position_at(prediction.result.contact_time);
|
||||
if acquire {
|
||||
*nearby = edges
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|e| {
|
||||
(0..3).all(|i| {
|
||||
e.start[i].min(e.end[i]) <= landing[i] + if i == 1 { 4.0 } else { 2.0 }
|
||||
&& e.start[i].max(e.end[i])
|
||||
>= landing[i] - if i == 1 { 0.5 } else { 2.0 }
|
||||
})
|
||||
})
|
||||
.take(40)
|
||||
.collect();
|
||||
}
|
||||
let landing = prediction.result.contact_position;
|
||||
let mut distance: f32 = 1000.0;
|
||||
for e in nearby.iter() {
|
||||
let delta = sub(e.end, e.start);
|
||||
let len = dot(delta, delta);
|
||||
let from = sub(landing, e.start);
|
||||
let projected = sub(from, delta.map(|v| v * dot(from, delta) / len));
|
||||
distance = distance.min(
|
||||
dot(projected, projected)
|
||||
.sqrt()
|
||||
.min(dot(from, from).sqrt())
|
||||
.min(dot(sub(landing, e.end), sub(landing, e.end)).sqrt()),
|
||||
);
|
||||
}
|
||||
if acquire {
|
||||
let mut candidates = nearby
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, e)| consider_grind_primitive(*prediction, *e, i, self.padding))
|
||||
.collect();
|
||||
let mut limits = self.limits.clone();
|
||||
limits.lock_distance = lock_distance;
|
||||
while let Some(c) = take_best_grind(&mut candidates, lock_distance, &self.height) {
|
||||
let e = nearby[c.primitive];
|
||||
let Some(surface) = skate_core::air::trajectory::grind_surface::investigate(
|
||||
world,
|
||||
e.start,
|
||||
e.end,
|
||||
c.point,
|
||||
self.truck_distance,
|
||||
)?
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(delta) = admitted_displacement(
|
||||
*prediction,
|
||||
c,
|
||||
e,
|
||||
velocity,
|
||||
com,
|
||||
surface.evidence,
|
||||
&limits,
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
apply_admitted_target(
|
||||
prediction,
|
||||
c,
|
||||
delta,
|
||||
surface.normal,
|
||||
velocity,
|
||||
self.maximum_adjust,
|
||||
self.velocity_scalar,
|
||||
self.max_angle,
|
||||
);
|
||||
bevy::log::info!("GRIND_ASSIST lock_distance={} miss_distance={} time={} rail={} correction={:?}",lock_distance,c.distance,c.time,e.owner,delta);
|
||||
return Ok(GrindEvaluation {
|
||||
target: Some(GrindTarget {
|
||||
edge: e,
|
||||
point: c.point,
|
||||
normal: surface.normal,
|
||||
air_limits: surface.air_limits(e.start, e.end, c.point, board_position),
|
||||
}),
|
||||
score: self.score,
|
||||
distance,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(GrindEvaluation {
|
||||
target: None,
|
||||
score: 0.0,
|
||||
distance,
|
||||
})
|
||||
}
|
||||
}
|
||||
fn sub(a: V, b: V) -> V {
|
||||
core::array::from_fn(|i| a[i] - b[i])
|
||||
}
|
||||
fn dot(a: V, b: V) -> f32 {
|
||||
skate_core::riding::ground_correction_math::dot_product(a, b)
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
//! Real stock-settings and BoardWorld adapter for the player trajectory selector.
|
||||
mod grind;
|
||||
mod settings;
|
||||
mod world;
|
||||
use skate_core::{
|
||||
air::trajectory::{
|
||||
LaunchInfo, QueryRequest, QueryResult, SelectorInput, SelectorSettings, TrajectorySelector,
|
||||
query_trajectory,
|
||||
query_trajectory, LaunchInfo, QueryRequest, QueryResult, SelectorInput, SelectorSettings,
|
||||
TrajectorySelector,
|
||||
},
|
||||
physics::board_world::BoardWorld,
|
||||
};
|
||||
@@ -13,6 +14,11 @@ use skate_data::collections::Collections;
|
||||
pub struct AirTrajectoryRuntime {
|
||||
pub selector: TrajectorySelector,
|
||||
pub settings: SelectorSettings,
|
||||
pub grind_board_position: [f32; 4],
|
||||
pub grind_com_position: [f32; 4],
|
||||
pub edges: Vec<skate_core::physics::grind_contact::Primitive>,
|
||||
grind_candidates: Vec<skate_core::physics::grind_contact::Primitive>,
|
||||
grind_settings: grind::Settings,
|
||||
pending_results: Option<Vec<QueryResult>>,
|
||||
}
|
||||
impl AirTrajectoryRuntime {
|
||||
@@ -29,6 +35,11 @@ impl AirTrajectoryRuntime {
|
||||
selector: TrajectorySelector::new(),
|
||||
settings: settings::load(collections)?,
|
||||
pending_results: None,
|
||||
edges: Vec::new(),
|
||||
grind_board_position: [0.; 4],
|
||||
grind_com_position: [0.; 4],
|
||||
grind_settings: grind::Settings::load(collections)?,
|
||||
grind_candidates: Vec::new(),
|
||||
})
|
||||
}
|
||||
pub fn launch(
|
||||
@@ -47,11 +58,22 @@ impl AirTrajectoryRuntime {
|
||||
let Some(results) = self.pending_results.take() else {
|
||||
return Ok(self.selector.update_without_completion());
|
||||
};
|
||||
let valid = self.selector.complete_batch(
|
||||
let valid = self.selector.complete_batch_with_grinds(
|
||||
&results,
|
||||
input,
|
||||
&self.settings,
|
||||
world::topology(world),
|
||||
|prediction, acquire| {
|
||||
self.grind_settings.evaluate(
|
||||
prediction,
|
||||
acquire,
|
||||
world,
|
||||
&self.edges,
|
||||
&mut self.grind_candidates,
|
||||
input.grind_lock_distance,
|
||||
self.grind_com_position,
|
||||
self.grind_board_position,
|
||||
)
|
||||
},
|
||||
|start, end, radius| world::line(world, start, end, radius),
|
||||
)?;
|
||||
if self.selector.pending() {
|
||||
|
||||
@@ -80,7 +80,7 @@ pub(super) fn load(data: &Collections) -> Result<SelectorSettings, String> {
|
||||
vert_jump_align_max_angle: r("VertJumpAlignMaxAngle")?, //723FF102068622CF
|
||||
})
|
||||
}
|
||||
fn graph8(
|
||||
pub(super) fn graph8(
|
||||
data: &Collections,
|
||||
class: &str,
|
||||
name: &str,
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
//!82772028, across the host's world-format boundary. BoardWorld stores static
|
||||
//!world-space triangles, so their source geometry transform is identity.
|
||||
use skate_core::{
|
||||
air::trajectory::{SurfaceHit, WorldWithoutGrindEdges},
|
||||
air::trajectory::SurfaceHit,
|
||||
math::Vector3,
|
||||
physics::{
|
||||
board_world::BoardWorld,
|
||||
triangle_query::{TriangleLineHit, triangle_segment},
|
||||
triangle_query::{triangle_segment, TriangleLineHit},
|
||||
},
|
||||
};
|
||||
type Vector = [f32; 4];
|
||||
@@ -17,13 +17,6 @@ const IDENTITY: [[f32; 4]; 4] = [
|
||||
[0.0; 4],
|
||||
];
|
||||
|
||||
///BoardWorld's authored format contains triangles only; no grind primitive
|
||||
///collection is present. Update this adapter together with any world format
|
||||
///extension that introduces actual grind edges.
|
||||
pub(super) fn topology(_world: &BoardWorld) -> WorldWithoutGrindEdges {
|
||||
WorldWithoutGrindEdges
|
||||
}
|
||||
|
||||
pub(super) fn line(
|
||||
world: &BoardWorld,
|
||||
start: Vector,
|
||||
|
||||
@@ -28,6 +28,7 @@ pub(super) fn advance(
|
||||
//Publishing Ground here runs its ForcePhysics Begin before the initial
|
||||
//input reset82DB8998, which would immediately clear that mode again.
|
||||
}
|
||||
if skater.trajectory.edges.is_empty() { skater.trajectory.edges.clone_from(&physics.grind.primitives); }
|
||||
physics.board.clear_forces();
|
||||
let query_timer = crate::performance::Scope::new("wheel_and_foot_queries");
|
||||
//World8275EA20 starts both batches before actor SetUpPhysics. Foot
|
||||
@@ -63,6 +64,8 @@ pub(super) fn advance(
|
||||
actions,
|
||||
input_available,
|
||||
)?;
|
||||
skater.trajectory.grind_board_position = solve::deck_frame(&physics.board)[3];
|
||||
skater.trajectory.grind_com_position = skater.player_input.processed.vectors_544_560_592_608[2].map(f32::from_bits);
|
||||
skater.ground_settings = skater.ground_profiles.select(
|
||||
skater.player_input.processed.state_variant_index_2528,
|
||||
skater.player_input.processed.surface_mode_2540,
|
||||
|
||||
@@ -23,7 +23,7 @@ mod camera;
|
||||
mod chromosome;
|
||||
pub(crate) struct Runtime {
|
||||
camera: camera::GrindCamera,
|
||||
primitives: Vec<Primitive>,
|
||||
pub(super) primitives: Vec<Primitive>,
|
||||
pub candidate: Option<FiftyFiftyCandidate>,
|
||||
pub name: String,
|
||||
pub active: bool,
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
//! Stock GrindAirAdjust settings82D72B40; lengths from the actual deck/truck collections.
|
||||
use skate_core::physics::grind_air::Settings;
|
||||
use skate_data::collections::Collections;
|
||||
pub(super) fn load(data: &Collections) -> Result<Settings, String> {
|
||||
let air = |key| data.float("physics_grinds_air", "default", key);
|
||||
let deck = |key| data.float("physicsdeck", "default", key);
|
||||
let half = deck("DeckMidLength")? * 0.5;
|
||||
let tip = deck("DeckFrontEndSize")? * air("TipOffsetFraction")?;
|
||||
let angle = deck("DeckFrontEndAngle")? * core::f32::consts::PI / 180.0;
|
||||
let z = half + data.float("physicstrucks", "default", "TruckZPosFront")?;
|
||||
let y = data.float("physicstrucks", "default", "TruckYPos")?;
|
||||
let frames = data.integer("physics_grinds_air", "default", "NumFramesToTest")? as usize;
|
||||
if !(2..=12).contains(&frames) {
|
||||
return Err("GrindAir NumFramesToTest exceeds native12-frame storage".into());
|
||||
}
|
||||
Ok(Settings {
|
||||
frames,
|
||||
stomp: air("StompAccelerationAdjust")?,
|
||||
points: [
|
||||
[0.; 4],
|
||||
[
|
||||
0.,
|
||||
tip * skate_core::trigonometry::sin_cos(angle).0,
|
||||
half + tip,
|
||||
0.,
|
||||
],
|
||||
[0., y, z, 0.],
|
||||
[0., y, -z, 0.],
|
||||
[
|
||||
0.,
|
||||
tip * skate_core::trigonometry::sin_cos(angle).0,
|
||||
-half - tip,
|
||||
0.,
|
||||
],
|
||||
],
|
||||
ranges: [
|
||||
90.,
|
||||
air("AngleRangeTipSlide")?,
|
||||
air("AngleRangeTipSlide")?,
|
||||
air("AngleRangeBoardSlide")?,
|
||||
air("AngleRange50_50")?,
|
||||
air("AngleRange5_0")?,
|
||||
air("AngleRange5_0")?,
|
||||
],
|
||||
distances: [
|
||||
z,
|
||||
z * air("MaxDistTipSlide")?,
|
||||
z * air("MaxDistTipSlide")?,
|
||||
z * air("MaxDistBoardSlide")?,
|
||||
z * air("MaxDist50_50")?,
|
||||
z * air("MaxDist5_0")?,
|
||||
z * air("MaxDist5_0")?,
|
||||
],
|
||||
yaw_assist: [
|
||||
air("AngleYAssistBoardSlide")?,
|
||||
air("AngleYAssistTipSlide")?,
|
||||
air("AngleYAssistTipSlide")?,
|
||||
air("AngleYAssistBoardSlide")?,
|
||||
air("AngleYAssist50_50")?,
|
||||
air("AngleYAssist5_0")?,
|
||||
air("AngleYAssist5_0")?,
|
||||
],
|
||||
max_offset: air("MaxOffsetDist")?,
|
||||
max_delta: air("MaxOffsetDeltaPerFrame")?,
|
||||
max_angle: air("MaxAngleDeltaPerFrame")?,
|
||||
})
|
||||
}
|
||||
@@ -127,11 +127,14 @@ impl KnownAirRuntime for Runtime<'_> {
|
||||
self.skater.trajectory.selector.grind_locked_to_middle()
|
||||
}
|
||||
fn start_grind_air_adjust_from_selector(&mut self) {
|
||||
//The host BoardWorld has an explicitly empty edge collection. Its selector
|
||||
//cannot produce9653; do not manufacture the required2944/2960/edge9568 data.
|
||||
self.record(Err(
|
||||
"KnownAir received a grind selection from an edge-free world".into(),
|
||||
));
|
||||
if let Some(target)=self.selection.grind {
|
||||
self.skater.skeleton_input.grind_air.start(skate_core::physics::grind_air::Target {
|
||||
edge:target.edge,limits:target.air_limits,
|
||||
});
|
||||
self.skater.skeleton_input.grind_air_started=true;
|
||||
} else {
|
||||
self.record(Err("KnownAir grind target lost its native spline record".into()));
|
||||
}
|
||||
}
|
||||
fn selector_closest_trajectory_point(&mut self, com: V, zero: V) -> (i32, V) {
|
||||
let t = self.selection.com_trajectory;
|
||||
|
||||
@@ -231,6 +231,7 @@ impl SkaterRuntime {
|
||||
player_input: PlayerInputRuntime::load(&data, NoGrindEdges)?,
|
||||
player_state: super::player_state::PlayerState::load(&data, mode)?,
|
||||
skeleton_input: SkeletonInputRuntime {
|
||||
grind_air_settings: Some(SkeletonInputRuntime::load_grind_settings(&data)?),
|
||||
drive_frames: initial_parts,
|
||||
extra_target_positions: [
|
||||
initial_targets.com,
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
//! GeneralUpdate belongs to the later movement-state phase, not PlayerInput.
|
||||
#[path = "skeleton_input_general.rs"]
|
||||
mod general;
|
||||
#[path = "grind_air_settings.rs"]
|
||||
mod grind_settings;
|
||||
#[path = "skeleton_input_teleport.rs"]
|
||||
mod teleport;
|
||||
|
||||
@@ -10,7 +12,7 @@ use super::{
|
||||
animated_skeleton::AnimatedSkeleton, animation_input::AnimationInput, foot_ik::FootIk,
|
||||
};
|
||||
use skate_core::{
|
||||
animation::output::{NativeMatrix, attributes::AnimationAttribute},
|
||||
animation::output::{attributes::AnimationAttribute, NativeMatrix},
|
||||
input::controller::ActionMap,
|
||||
physics::{
|
||||
board::BodyId,
|
||||
@@ -77,6 +79,8 @@ pub(crate) struct SkeletonInputRuntime {
|
||||
///GrindAirAdjust ctor82D7116C initializes activated289=false.
|
||||
///Only KnownAir Update82D35D30/Exit82D3597C subsequently write this flag.
|
||||
///288, ctor82D71160; activated289 is a separate retained flag.
|
||||
pub grind_air: skate_core::physics::grind_air::GrindAir,
|
||||
pub grind_air_settings: Option<skate_core::physics::grind_air::Settings>,
|
||||
pub grind_air_started: bool,
|
||||
pub grind_air_active: bool,
|
||||
pub grind_air_adjusting: bool,
|
||||
@@ -100,6 +104,8 @@ impl Default for SkeletonInputRuntime {
|
||||
force_mode: 0,
|
||||
head_tracking_history: [[0.0; 4]; 8],
|
||||
head_tracking_active: false,
|
||||
grind_air: Default::default(),
|
||||
grind_air_settings: None,
|
||||
grind_air_started: false,
|
||||
grind_air_active: false,
|
||||
grind_air_adjusting: false,
|
||||
@@ -108,6 +114,11 @@ impl Default for SkeletonInputRuntime {
|
||||
}
|
||||
|
||||
impl SkeletonInputRuntime {
|
||||
pub fn load_grind_settings(
|
||||
data: &skate_data::collections::Collections,
|
||||
) -> Result<skate_core::physics::grind_air::Settings, String> {
|
||||
grind_settings::load(data)
|
||||
}
|
||||
///Called by PlayerInputCallbacks::process_skeleton, after the actual
|
||||
///BoardToolkit and line tests have been published. It does not advance
|
||||
///Ground, rebuild a toolkit or submit a second physical simulation.
|
||||
@@ -154,9 +165,7 @@ impl SkeletonInputRuntime {
|
||||
//These authored offset producers are separate from Ground riding.
|
||||
//Do not silently reuse a riding pose if a future state enables them.
|
||||
let reparented_hands = owners.animated.reparented_hand_indices();
|
||||
super::offboard::pose_adjust::update(
|
||||
owners.animated, pose.globals, reparented_hands, p,
|
||||
)?;
|
||||
super::offboard::pose_adjust::update(owners.animated, pose.globals, reparented_hands, p)?;
|
||||
if p.flags_2476 & 0x100 != 0 {
|
||||
if p.flags_2476 & 0x10000 == 0 {
|
||||
return Err(
|
||||
@@ -168,7 +177,46 @@ impl SkeletonInputRuntime {
|
||||
//Inactive Ground leaves retained total offset/angle untouched.
|
||||
self.grind_air_adjusting = false;
|
||||
if self.grind_air_active {
|
||||
return Err("Active air grind adjustment requires82D712E0 geometric stages".into());
|
||||
let settings = self
|
||||
.grind_air_settings
|
||||
.as_ref()
|
||||
.ok_or("Missing stock GrindAir settings")?;
|
||||
let mut frame = super::solve::deck_frame(board);
|
||||
if p.flags_2468 & 0x0010_0000 != 0 {
|
||||
frame[0] = frame[0].map(|v| -v);
|
||||
frame[2] = frame[2].map(|v| -v);
|
||||
}
|
||||
let mapped = skate_core::physics::skeleton_animation_record::map_animation_parts(
|
||||
pose.globals,
|
||||
&owners.animated.bone_indices,
|
||||
&owners.animated.physics_frames,
|
||||
)?;
|
||||
skate_core::physics::skeleton_motion::SkeletonMotion::publish_unadjusted_board(
|
||||
&mapped[0],
|
||||
&mut p.flags_2472,
|
||||
);
|
||||
if let Some(adjustment) = self.grind_air.update(
|
||||
frame,
|
||||
p.vectors_400_416[0].map(f32::from_bits),
|
||||
p.vectors_720_784_800_816_832_864[0].map(f32::from_bits),
|
||||
p.vectors_544_560_592_608[0].map(f32::from_bits),
|
||||
p.timestep_2604,
|
||||
[p.flags_2480, p.flags_2472, p.flags_2468],
|
||||
p.flags_2484 & 0x0020_0000 != 0,
|
||||
settings,
|
||||
) {
|
||||
let pivot = skate_core::physics::skeleton_animation_record::transform_point(
|
||||
&owners.animated.roots.animation_to_world,
|
||||
mapped[0][3],
|
||||
);
|
||||
let offset = adjustment.local_transform(
|
||||
owners.animated.roots.animation_to_world,
|
||||
pivot,
|
||||
owners.animated.board_frames.physical_board[2],
|
||||
);
|
||||
owners.animated.board_offset.refresh_transform(offset);
|
||||
self.grind_air_adjusting = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
let up = p.vectors_544_560_592_608[0].map(f32::from_bits);
|
||||
|
||||
@@ -45,11 +45,13 @@ extracts are kept locally under logs/biped-*.txt.
|
||||
|
||||
All six contact families and their named variations are connected for a manual
|
||||
playability pass. This is not verified retail parity. Existing host limitations
|
||||
remain: trajectory-driven magnetic acquisition, specialized tipslide drop-in
|
||||
exit handling, full native material/engagement feedback, and the air439 history
|
||||
remain: specialized tipslide drop-in exit handling, full native
|
||||
material/engagement feedback, and the air439 history
|
||||
snapshot are not completely connected. Common pop/exit behavior still supplies
|
||||
those families in this adapter. The ground history supplies approach naming;
|
||||
short approaches and rapid re-grinds particularly need visual comparison.
|
||||
|
||||
Only compilation and source review were performed, as requested. Gameplay and
|
||||
animation behavior are for the user's visual test on the default map.
|
||||
|
||||
Trajectory-driven magnetic acquisition is now connected; see [magnetism](grind-magnetism-difficulty.md).
|
||||
@@ -9,9 +9,9 @@ Observed in assets/private/stock/skater-collections.json, physics_mode:
|
||||
| hardcore | 3E19999A | 0.15 |
|
||||
|
||||
The field is native physics_mode+80. The live SelectorInput already receives
|
||||
this field when the active difficulty changes. It does not currently result in
|
||||
magnetic acquisition because AirTrajectoryRuntime passes WorldWithoutGrindEdges,
|
||||
and KnownAir's StartGrindAirAdjust callback reports an unsupported owner.
|
||||
this field when the active difficulty changes. AirTrajectoryRuntime now queries
|
||||
the authored spline primitives and the selected result retains the accepted
|
||||
primitive and angular reference directions through KnownAir entry.
|
||||
|
||||
Observed TU3 path: ScoreTrajectories82D68FE8 attempts82D69C00 on the first-pass
|
||||
middle trajectory. Its actual spline query uses bounds around the natural landing
|
||||
@@ -32,9 +32,30 @@ from the difficulty-dependent initial capture distance.
|
||||
|
||||
Native source identity: TU3 default_82000000_011B0000.bin SHA256
|
||||
f4aa113eb541bfba03dbc108cf5ab43f58c965b20fa3b82f9c40938a0ad841c4.
|
||||
Instruction extracts are in local logs/biped-*.txt. No gameplay tests or reference
|
||||
process were run. Experimental selector plumbing is saved in
|
||||
logs/magnetism-in-progress, outside the production source: the incomplete pose
|
||||
owner must be connected before exposing9653 in the playable game.
|
||||
Instruction extracts are in local logs/biped-*.txt.
|
||||
|
||||
Status: findings confirmed statically; magnetism is NOT fixed in the staged game.
|
||||
## Live integration
|
||||
|
||||
The centre-trajectory query supplies ranking, admission, trajectory displacement
|
||||
and the landing normal. Alternative candidates reuse its actual edge collection
|
||||
for the distance penalty; they do not issue independent replacement queries.
|
||||
The selector retains the accepted middle target and skips its normal-delta
|
||||
second pass exactly at that handoff. The current processed COM and board position,
|
||||
predicted collision velocity and active difficulty feed the admission checks.
|
||||
|
||||
KnownAir starts the retained GrindAir owner and activates it at the original
|
||||
apex/remaining-frame boundary. It predicts the physical deck's rotation and
|
||||
translation, intersects five deck/truck points with the rail plane, chooses from
|
||||
seven native contact choices, and integrates bounded translation and angular
|
||||
corrections. ProcessData publishes the unadjusted board orientation flag before
|
||||
this decision. The result refreshes the existing board/IK offset owner; ordinary
|
||||
exit deactivates the owner and preserves the native offset decay.
|
||||
|
||||
`GRIND_ASSIST` log entries record accepted capture distance, miss distance,
|
||||
predicted contact time, primitive owner and correction. These are passive output
|
||||
from the user's play session; no scripted inputs are sent.
|
||||
|
||||
Status: implemented and compiled, ready for the user's gameplay test. No tests
|
||||
or reference process were run, as requested. The equations and constants were
|
||||
translated from static TU3 source; matched retail replay/bitwise parity has not
|
||||
been established. The current world adapter supplies static spline primitives.
|
||||
+11
-55
@@ -1,59 +1,15 @@
|
||||
# Native grind assist port status
|
||||
# Native grind assist
|
||||
|
||||
The live game now supports paired-truck50-50 and centre-deck boardslide contacts
|
||||
(see boardslide-port.md). The new
|
||||
`skate-core/src/air/trajectory/grind.rs` leaves are not connected to gameplay.
|
||||
They must not be presented as working magnetic assistance or all-grind support.
|
||||
The live acquisition and airborne board-adjustment path is now connected.
|
||||
See [difficulty and integration details](grind-magnetism-difficulty.md) and
|
||||
[grind families](grind-families.md).
|
||||
|
||||
Core trajectory leaves82D6A398/82D6A168/82D6A840 are supplied with the real spline
|
||||
collection and indexed collision investigation. Accepted targets pass through
|
||||
selector scoring and KnownAir into GrindAirAdjust82D712E0. Difficulty uses the
|
||||
stock physics_mode80 field, not a new snapping radius.
|
||||
|
||||
Reference: TU3 image SHA256
|
||||
f4aa113eb541bfba03dbc108cf5ab43f58c965b20fa3b82f9c40938a0ad841c4,
|
||||
generated PPC in the local Skate3Research/Skate3CustomEngineLayer checkout.
|
||||
No reference process or gameplay tests were run for this port.
|
||||
|
||||
## Ported leaves
|
||||
|
||||
- 82D60C80/82D60B98: quadratic trajectory-plane intersection and later-root selection.
|
||||
- 82D6A398: projected rail trajectory candidate, signed endpoint interval,
|
||||
radius plus GrindOffset plane, miss distance, crossing time, fixed60Hz frame,
|
||||
rail direction and approach direction.
|
||||
- 8296EC98/82E09C80: oriented approach angle and fractional-turn fold.
|
||||
- 82D6A168: height/angle ranking inside the difficulty lock distance and
|
||||
distance fallback outside it; removing one winner preserves retry order.
|
||||
- Admission/displacement portion of82D6A840: relative height, vertical rail,
|
||||
perpendicular speed, ledge-side lock scalars, deck-tip allowance and maximum
|
||||
trajectory angle. It requires a supplied native surface result; no dummy
|
||||
classification or unconditional snap is provided.
|
||||
|
||||
These are static source translations, not a claim of verified bitwise parity.
|
||||
The core crate builds. The user has reserved gameplay testing for themselves.
|
||||
|
||||
## Missing live dependencies
|
||||
|
||||
1. Preserve the complete indexed world-query result set for82C20728/82C20C08,
|
||||
then implement the rail/edge/ledge classification82C21930. The existing
|
||||
ground hang-up adapter only produces the fields that its ground caller reads.
|
||||
2. Complete82D6A840's accepted-target publication and82D6AF58 landing-normal
|
||||
construction. Feed the real result through trajectory scoring/second-pass
|
||||
selection instead of the current WorldWithoutGrindEdges marker.
|
||||
3. Connect GrindAirAdjust82D712E0 and its projection, target, and correction
|
||||
owners to the physical skeleton/board. The displacement leaf by itself is
|
||||
not the complete airborne assist.
|
||||
4. Port the other contact probes/scorers and state owners. Native manager
|
||||
82D875A8 uses separate centre-deck, inverted-deck, truck and tip probes.
|
||||
State400 boardslide,40150-50,402 tipslide,4035-0,404 backslash and405
|
||||
darkslide are distinct physics states. Naming variants requires the actual
|
||||
chromosome producer as well as those state IDs.
|
||||
|
||||
## Proposed read-only reference trace
|
||||
|
||||
A reference run was authorized and launched, but its controller backend did
|
||||
not expose the physical controller. The user requested continuation from code.
|
||||
No successful reference grind capture was obtained and no synthetic gameplay
|
||||
input was sent.
|
||||
|
||||
Capture query inputs and indexed hit validity/fraction/position/normal/surface
|
||||
at82C20728 and82C20C08, the classification at82C21930, ranked candidates and
|
||||
accepted/rejected attempts at82D6A168/82D6A840, and GrindAirAdjust output at
|
||||
82D712E0. Include state/category, mode, simulation tick, spline ID, board frame
|
||||
and controller inputs so these observations can be matched to their callers.
|
||||
Keep the trace local and do not modify the reference binaries or database.
|
||||
matching local Skate3CustomEngineLayer generated PPC. Static source translation
|
||||
and compilation only; gameplay validation is reserved for the user.
|
||||
Reference in new issue
Block a user