Publish native grind direction and conditioned camera contact

This commit is contained in:
chazmm committed 2026-09-07 20:30:09 +01:00
1 parent 685d55601e
commit d39703e48c
4 files changed
+121 -8

No files matched your search

+10 -8
View File
@@ -2,7 +2,7 @@
//! Reset82DE53F0 runs before the actual producers82DB6EC0/82D3A388.
use super::{GamePhysics, SkaterRuntime};
use crate::camera::{
CameraAirOutput, CameraAnimationOutput, CameraEventsOutput, CameraGrindOutput,
CameraAirOutput, CameraAnimationOutput, CameraEventsOutput,
CameraOffboardOutput, CameraPreferences, CameraPublicationInputs, CameraStateOutput,
};
use skate_core::{
@@ -15,11 +15,16 @@ use skate_core::{
/// The user's selected normal High camera is graph type1. This custom world
/// has no road/ledge/camera-volume annotations or other moving actors.
pub(crate) fn advance(
physics: &GamePhysics,
physics: &mut GamePhysics,
skater: &SkaterRuntime,
feedback: &PhysicalFeedback,
camera: &mut crate::camera::CameraRuntime,
) -> Result<(), String> {
//Once per physical publication, preserving conditioner history across
//direct grind-type changes and invalidating it on the first non-grind tick.
physics.grind.advance_camera(
skater.player_input.processed.vectors_400_416[0].map(f32::from_bits),
)?;
let inputs = publish(
physics,
skater,
@@ -137,12 +142,9 @@ pub(crate) fn publish(
use_trajectory_331: 0,
dropping_in_334: 0,
},
grinds: CameraGrindOutput {
//Reset82DE3518 loads original world-X literal82139A10.
direction_0: [1.0, 0.0, 0.0, 0.0],
camera_target_96: [0.0; 4],
grinding_316: u8::from(matches!(physical.grinds.words_136_140[1], 1 | 2)),
},
//Shared grind Fill82D40EA0 publishes the directed rail tangent;
//conditioner82DF0640 supplies the continuous contact anchor.
grinds: physics.grind.camera_output(),
events: CameraEventsOutput {
intent_51: bit(intents, 28),
preparing_52: bit(intents, 27),
+21
View File
@@ -17,7 +17,10 @@ use skate_core::{
};
use skate_data::collections::Collections;
type V = [f32; 4];
#[path = "grind_camera.rs"]
mod camera;
pub(crate) struct Runtime {
camera: camera::GrindCamera,
primitives: Vec<Primitive>,
pub candidate: Option<FiftyFiftyCandidate>,
pub name: String,
@@ -50,6 +53,7 @@ impl Runtime {
.words::<8>("physics_grinds", "default", "PinVsSlope")?
.map(f32::from_bits);
Ok(Self {
camera: camera::GrindCamera::default(),
primitives: crate::grind_world::primitives(map)?,
candidate: None,
name: String::new(),
@@ -98,6 +102,23 @@ impl Runtime {
diagnostic_tick: 0,
})
}
pub fn advance_camera(&mut self, velocity: V) -> Result<(), String> {
if self.active {
let contact = self.candidate.ok_or("Grind camera needs active contact")?;
self.camera.update(contact, self.primitives[contact.primitive], self.kind, velocity);
} else {
self.camera.exit();
}
Ok(())
}
pub fn camera_output(&self) -> crate::camera::CameraGrindOutput {
crate::camera::CameraGrindOutput {
//Reset82DE3518 defaults still apply outside the active state.
direction_0: if self.active { self.camera.direction } else { [1.0, 0.0, 0.0, 0.0] },
camera_target_96: if self.active { self.camera.target } else { [0.0; 4] },
grinding_316: u8::from(self.active),
}
}
pub fn output(&self) -> GrindState {
if !self.active {
return GrindState::default();
@@ -0,0 +1,58 @@
//! TU3 grind direction82D87460 and contact-point conditioner82DF0640.
use skate_core::physics::grind_contact::{FiftyFiftyCandidate, Primitive};
use skate_core::riding::ground_correction_math::dot_product;
type V = [f32; 4];
#[derive(Default)]
pub(super) struct GrindCamera {
pub direction: V,
pub target: V,
previous_target: V,
offset: V,
midpoint: V,
kind: u32,
active: bool,
}
impl GrindCamera {
pub fn exit(&mut self) {
//82DF0610/82DF07EC invalidate history without erasing the rail tangent.
self.active = false;
}
pub fn update(&mut self, contact: FiftyFiftyCandidate, edge: Primitive, kind: u32, velocity: V) {
//82D37048 supplies the normalized primitive tangent, independently of
//the deck's facing direction (including backwards50-50s/boardslides).
let delta: V = core::array::from_fn(|i| edge.end[i] - edge.start[i]);
let length = dot_product(delta, delta).sqrt();
let mut direction = if length > 0.0 { delta.map(|v| v / length) } else { self.direction };
let along = dot_product(direction, velocity);
if along < 0.0 {
direction = direction.map(|v| -v);
}
//82D874F0: native literals820641A8=.1 and822F8E94=-.9.
if along.abs() < 0.1 && dot_product(self.direction, direction) < -0.9 {
direction = direction.map(|v| -v);
}
self.direction = direction;
let midpoint: V = core::array::from_fn(|i| (edge.start[i] + edge.end[i]) * 0.5);
if !self.active {
self.offset = [0.0; 4];
self.target = contact.centre;
self.previous_target = contact.centre;
} else if kind != self.kind || midpoint[..3] != self.midpoint[..3] {
//Preserve extrapolated motion across a change of contact owner.
self.offset = core::array::from_fn(|i| {
self.target[i] + (self.target[i] - self.previous_target[i]) - contact.centre[i]
});
}
//Constructor82DF24CC loads literal82072818, .95 per physical tick.
//This decays a contact discontinuity, not ordinary camera movement.
self.offset = self.offset.map(|v| v * 0.95);
self.previous_target = self.target;
self.target = core::array::from_fn(|i| contact.centre[i] + self.offset[i]);
self.midpoint = midpoint;
self.kind = kind;
self.active = true;
}
}
+32
View File
@@ -0,0 +1,32 @@
# Grind camera publication
The camera adapter previously published Grinds0 as world X and Grinds96 as
world origin even during active grinds. The native movement compass82DF3AC0
uses Grinds0 directly while grinding, and GrindAnchor82DF2C78 consumes Grinds96.
This explains a heading snap followed by an unchanging direction, with an
incorrect positional anchor as well.
The live adapter now supplies:
- Directed primitive tangent:82D87460 calls82D37048, flips against velocity,
and preserves the previous direction when absolute along-speed is below
.1 and the direction dot is below -.9. These literals are820641A8/822F8E94.
- Contact target:82DF0640 initializes on entry, extrapolates the previous two
targets on changes to grind type or primitive midpoint, then decays the
resulting offset by .95 each physical tick. Constructor82DF24CC loads this
coefficient from82072818. Inactive ticks invalidate target history.
This is contact discontinuity conditioning, not a general camera latency
filter. Existing camera compass, anchors and authored camera graph remain the
consumers. Straight authored/imported primitives supply their actual endpoints.
Evidence: local TU3 native image
`Skate3Research/research/reverse-engineering/XexDump/default_82000000_011B0000.bin`,
base82000000, SHA256
`f4aa113eb541bfba03dbc108cf5ab43f58c965b20fa3b82f9c40938a0ad841c4`.
Instruction listings were extracted from the generated PPC source into
`logs/biped-82D87460.txt`, `logs/biped-82D37048.txt`,
`logs/biped-82DF0640.txt`, and `logs/biped-82DF2130.txt`; coefficients were
cross-checked directly against image bytes. Behavior above is observed static
code; the reported visual symptom is consistent with the missing publications.
No reference gameplay trace or automated gameplay testing was performed.