From 928120b2a22fff0966a028fe6b17ac582ffab48e Mon Sep 17 00:00:00 2001 From: chazmm Date: Sun, 6 Sep 2026 05:39:55 +0100 Subject: [PATCH] Connect stock trick stair gate to physical output --- .../src/player/input_phase/pose_output.rs | 6 ++- .../graph_host/motion_gameplay_conditions.rs | 5 +++ .../src/graph_host/tests/motion_tricks.rs | 39 +++++++++++++++++++ .../skate-game/src/physics/animation_phase.rs | 1 + docs/360-FLIP.md | 13 ++++++- 5 files changed, 61 insertions(+), 3 deletions(-) diff --git a/crates/skate-core/src/player/input_phase/pose_output.rs b/crates/skate-core/src/player/input_phase/pose_output.rs index a6e98097..be805241 100644 --- a/crates/skate-core/src/player/input_phase/pose_output.rs +++ b/crates/skate-core/src/player/input_phase/pose_output.rs @@ -25,11 +25,15 @@ pub struct SkeletonOutputFields { pub struct AnimationOutputFields { pub collision_time_144: f32, pub profile_148: u32, + /// PhysOutAnimation+166, tested by OkToDoTrickOnStairs82BA6930. + pub tricks_blocked_on_stairs_166: u8, } impl Default for AnimationOutputFields { fn default() -> Self { // Animation82DE3F38 stores FLT_MAX at144 and zero at148. - Self { collision_time_144: f32::MAX, profile_148: 0 } + // Reset82DE3F38 clears byte166. Ordinary Ground Fill82D3A388 + // and common ProcessOutput82DB6EC0 do not overwrite this byte. + Self { collision_time_144: f32::MAX, profile_148: 0, tricks_blocked_on_stairs_166: 0 } } } diff --git a/crates/skate-game/src/graph_host/motion_gameplay_conditions.rs b/crates/skate-game/src/graph_host/motion_gameplay_conditions.rs index 7ded9a97..62ab2ba0 100644 --- a/crates/skate-game/src/graph_host/motion_gameplay_conditions.rs +++ b/crates/skate-game/src/graph_host/motion_gameplay_conditions.rs @@ -24,6 +24,7 @@ pub struct GameplayConditions { pub time_to_land: f32, /// PhysOutOffBoard+32, shared by OBTimeToLand and OBTrajTime. pub offboard_time_to_land: f32, + pub tricks_blocked_on_stairs: bool, } #[derive(Clone, Debug, PartialEq)] @@ -42,6 +43,7 @@ pub enum GameplayCondition { Dark, UnderflipRequested, DarkCatchRequested, + OkToDoTrickOnStairs, HandPlanting { state: u8, direction: u8 }, EnteringSkitch, Skitching, @@ -64,6 +66,7 @@ impl GameplayCondition { | "IsDark" | "IsUnderflipRequested" | "IsDarkCatchRequested" + | "OkToDoTrickOnStairs" | "IsHandPlanting" | "IsEnteringSkitch" | "IsSkitching" @@ -87,6 +90,7 @@ impl GameplayCondition { "IsDark" => Self::Dark, "IsUnderflipRequested" => Self::UnderflipRequested, "IsDarkCatchRequested" => Self::DarkCatchRequested, + "OkToDoTrickOnStairs" => Self::OkToDoTrickOnStairs, "IsEnteringSkitch" => Self::EnteringSkitch, "IsSkitching" => Self::Skitching, "IsHandPlanting" => Self::HandPlanting { @@ -109,6 +113,7 @@ impl GameplayCondition { /// conditions that also require the MotionGraph/channel owner. pub fn evaluate_physical(&self, p: &GameplayConditions) -> Option { Some(match self { + Self::OkToDoTrickOnStairs => !p.tricks_blocked_on_stairs, //82BA6930:Animation166 Self::RetrievingBoard => p.retrieving_board, //82BA5EF0:Offboard323 Self::InBipedAir => p.in_biped_air, //82BA8030:Offboard328 Self::HippyHurdling => p.hippy_hurdling, //82BA5DA0:Offboard317 diff --git a/crates/skate-game/src/graph_host/tests/motion_tricks.rs b/crates/skate-game/src/graph_host/tests/motion_tricks.rs index 4f6bb33a..2453dee3 100644 --- a/crates/skate-game/src/graph_host/tests/motion_tricks.rs +++ b/crates/skate-game/src/graph_host/tests/motion_tricks.rs @@ -23,6 +23,45 @@ fn stock_360_crouch_takeoff_and_air_animation_pipeline() { binding, runtime, }; + // Include the parent entry conditions: invoking leaf animation handlers + // alone does not catch missing stock gates such as OkToDoTrickOnStairs. + let implementations = graph.binding.instantiate_operations(&graph.source, &mut MotionFactory).unwrap(); + fn check_expression(binding: &skate_data::state_graph::binding::Binding, instances: &[MotionOperation], id: usize) { + use skate_data::state_graph::binding::Node; + for node in &binding.expressions[id].children { + match *node { + Node::Expression(child) => check_expression(binding, instances, child), + Node::Operation(op) => assert!(!matches!(instances[op], MotionOperation::Unsupported { .. }), + "Unsupported stock entry/transition condition: {:?}", instances[op]), + _ => panic!("Unexpected expression child"), + } + } + } + for leaf in ["FromAntic", "LeftGround"] { + let state = graph.binding.states.iter().enumerate().find_map(|(id, state)| { + if state.name != leaf { return None; } + let mut cursor = Some(id); + let mut is_360 = false; + while let Some(i) = cursor { + is_360 |= graph.binding.states[i].name == "360Flip"; + cursor = graph.binding.states[i].parent; + } + is_360.then_some(id) + }).expect("Authored 360-flip state"); + let mut cursor = Some(state); + while let Some(i) = cursor { + let state = &graph.binding.states[i]; + if let Some(expression) = state.expression { + check_expression(&graph.binding, &implementations.operations, expression); + } + for &transition in &state.transitions { + if let Some(expression) = graph.binding.transitions[transition].expression { + check_expression(&graph.binding, &implementations.operations, expression); + } + } + cursor = state.parent; + } + } let collections = Collections::load(&root).unwrap(); let mut host = MotionHost::from_graph( &graph, diff --git a/crates/skate-game/src/physics/animation_phase.rs b/crates/skate-game/src/physics/animation_phase.rs index 3c8ff683..65d6ea94 100644 --- a/crates/skate-game/src/physics/animation_phase.rs +++ b/crates/skate-game/src/physics/animation_phase.rs @@ -102,6 +102,7 @@ pub(crate) fn advance( skitch_transition_time: profile.skitch_transition_time, time_to_land: physical.animation.collision_time_144, offboard_time_to_land: physical.off_board.scalar_32, + tricks_blocked_on_stairs: physical.animation.tricks_blocked_on_stairs_166 != 0, }, ); skater.animation.action.gameplay_conditions = skater.animation.motion.gameplay_conditions; diff --git a/docs/360-FLIP.md b/docs/360-FLIP.md index 42349a78..ca740aa2 100644 --- a/docs/360-FLIP.md +++ b/docs/360-FLIP.md @@ -49,6 +49,7 @@ the recompiled PPC instruction listings. Relevant boundaries: | Trick construction identity | 82BB5C20, 82BB5CA0 | | Scoring metadata | 82BBFA88, 8258FA20 | | Underflip requests | 82BBE098, 82BBE0A8, 82BBE358 | +| Trick stair gate and physical reset | 82BA6930, 82DE3F38 | | Nose-weight anticipation | 82BB2670, 82BB26D0, 82B97110 | Factory names were verified from registration strings. In particular, @@ -63,7 +64,9 @@ Nonvisual checks pass for 36 core input tests, the PAT parser, the gesture mapping/ActionHost lifecycle, and two private-stock fixtures. Those fixtures recognize the authored scoop through all seven files and exercise the stock crouch, scoop, takeoff, and air-animation handlers in both stances, -including finite decoded poses and required attributes. These are data/code +including finite decoded poses and required attributes. The fixture also +checks the ordinary route and ancestor entry/transition conditions for +unsupported factories. These are data/code checks, not a claim of visually verified gameplay parity. No gameplay or visual test was automated. The existing game instance was closed to stage the new build; restarting it for the user is separate from these checks. @@ -74,7 +77,13 @@ Please check: 2. Perform a 360 flip; check preparation, pop, board flip/spin, and foot motion. 3. Land and push away, then repeat in the opposite stance. -This work validates the ordinary ground route, not every trick in the newly +The first user-input run exposed the missing `OkToDoTrickOnStairs` gate. +It now reads the retained PhysOutAnimation byte166. Native reset82DE3F38 +clears this byte; ordinary Ground Fill82D3A388 and common +ProcessOutput82DB6EC0 do not override it. This is the original packet +lifecycle, not a constant-success condition. + +These nonvisual checks cover the ordinary ground route, not every trick in the newly connected catalog. Advanced manual/grind-out branches still contain the known unsupported `JumpInto` behavior, and the existing separate `AddRunoutAttribs` gap remains. Those handlers were not bypassed. The current