diff --git a/crates/skate-core/src/player/selector/air.rs b/crates/skate-core/src/player/selector/air.rs index 5ef95114..861d3667 100644 --- a/crates/skate-core/src/player/selector/air.rs +++ b/crates/skate-core/src/player/selector/air.rs @@ -20,7 +20,8 @@ impl StateSelector { if p.has_2476(0x8000) { return PhysicalStateId::BipedGround; } - if p.has_2476(0x80) && !p.has_2484(1) { + //Native lbz2484 selects the high byte of the big-endian flag word. + if p.has_2476(0x80) && !p.has_2484(0x100_0000) { return PhysicalStateId::BipedAir; } if facts.force_known_air { @@ -72,7 +73,7 @@ impl StateSelector { { return PhysicalStateId::WipeoutGround; } - if p.has_2476(0x80) && !p.has_2484(1) { + if p.has_2476(0x80) && !p.has_2484(0x100_0000) { return PhysicalStateId::BipedAir; } if p.has_2476(0x8000) { diff --git a/crates/skate-core/src/player/selector/offboard.rs b/crates/skate-core/src/player/selector/offboard.rs index b0972921..363e4767 100644 --- a/crates/skate-core/src/player/selector/offboard.rs +++ b/crates/skate-core/src/player/selector/offboard.rs @@ -35,7 +35,10 @@ impl StateSelector { return PhysicalStateId::WipeoutGround; } if !p.has_2480(0x10) { - if p.has_2484(1) { + //82D8ADE8's BipedAir branch reads lhz2484, then tests its low bit. On + //big-endian Xenon this is full-word bit16 (Offboard328), + //not bit0 (the unrelated BipedBoardOnGround attribute). + if p.has_2484(0x1_0000) { if p.has_2476(0x8000) || p.has_2476(0x80) { current } else { diff --git a/crates/skate-game/src/physics/offboard/air_diagnostics.rs b/crates/skate-game/src/physics/offboard/air_diagnostics.rs index 42731c07..6e8d2f13 100644 --- a/crates/skate-game/src/physics/offboard/air_diagnostics.rs +++ b/crates/skate-game/src/physics/offboard/air_diagnostics.rs @@ -1,10 +1,11 @@ -//! Bounded numeric history for user-reproduced airborne bails. No per-frame IO. +//! Bounded numeric history for user-reproduced off-board 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 { + physical_state: u32, tick: i32, flags: [u32; 3], packet: skate_core::player::offboard::air_prediction::Packet, @@ -37,6 +38,7 @@ pub(crate) fn record(skater: &mut SkaterRuntime) { let p = &skater.player_input.processed; if history.samples.len() == 32 { history.samples.pop_front(); } history.samples.push_back(Sample { + physical_state: p.state_2508, tick: state.tick, flags: [p.flags_2476, p.flags_2480, p.flags_2484], packet: state.packet, @@ -51,7 +53,7 @@ pub(crate) fn record(skater: &mut SkaterRuntime) { 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); + let report = format!("SKATE_BIPED_BAIL history={:#?}", history.samples); eprintln!("{report}"); history.samples.clear(); history.reports += 1; diff --git a/crates/skate-game/src/physics/wipeout.rs b/crates/skate-game/src/physics/wipeout.rs index 0425f944..f935be96 100644 --- a/crates/skate-game/src/physics/wipeout.rs +++ b/crates/skate-game/src/physics/wipeout.rs @@ -151,6 +151,7 @@ pub(super) fn check_after_physics( ground_kind_356: skater.offboard.retained_contact.kind_164, }, ); + super::offboard::air_diagnostics::record(skater); Ok(()) } PhysicalStateId::PhysicsAir => skater.wipeout.check_air(&observations, false), diff --git a/docs/OFFBOARD_STATE_FLAGS.md b/docs/OFFBOARD_STATE_FLAGS.md new file mode 100644 index 00000000..82dbfd25 --- /dev/null +++ b/docs/OFFBOARD_STATE_FLAGS.md @@ -0,0 +1,24 @@ +# Off-board state flag correction + +The user reported jumps immediately playing a landing animation, repeated +landing animations and rising motion after walking off a ledge. The preceding +run's stderr contained no airborne bail report; this alone does not establish +whether the airborne state was entered. + +Static evidence: TU3 dump `default_82000000_011B0000.bin`, SHA-256 +`f4aa113eb541bfba03dbc108cf5ab43f58c965b20fa3b82f9c40938a0ad841c4`. +The generated instruction listing for `82D8ADE8` contains: + +- `82D8B8D4`: `lhz r11,2484(r4)`, followed by a low-bit test. +- `82D8B490` and `82D8B5F0`: `lbz r11,2484(r4)`, followed by a low-bit test. + +On this big-endian target these test full-word masks `0x00010000` and +`0x01000000`, respectively. The port incorrectly used `0x00000001` for all +three. Bit16 is published from Offboard328, the airborne continuation flag; +bit0 is a separate animation attribute. BipedAir therefore incorrectly returned +to BipedGround while its airborne flag was still set. + +Corrected all three masks. Repeated premature ground entry is a plausible +cause of the reported animation loop; gameplay confirmation remains with the +user. Build only, no tests run. Bounded bail diagnostics also cover BipedGround +so any remaining turning-related failure can be captured.