Port native sequence publication ordering and line settlement

This commit is contained in:
chazmm committed 2026-09-08 16:38:04 +01:00
1 parent 3c2860e376
commit e66e81c8ae
5 files changed
+141 -4

No files matched your search

+9 -1
View File
@@ -8,6 +8,7 @@ pub const SCORABLE_COUNT: usize = 332;
pub const SCORE_TYPE_COUNT: usize = 14;
pub mod carrier;
pub mod catalog;
pub mod session;
pub mod timer;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -133,9 +134,16 @@ impl ScoreHolder {
}
/// Module82DA3B38 banks the line when its timer expires or reset is requested.
pub fn finish_line(&mut self) {
self.bank_line(true);
}
/// A zero line timer banks the current line even while a collector is
/// active, but 82DA3B38 retains repetition in that case.
pub fn bank_line(&mut self, clear_repetition: bool) {
self.snapshot.completed_lines += self.snapshot.line;
self.snapshot.line = 0.0;
self.repetitions.fill(0);
if clear_repetition {
self.repetitions.fill(0);
}
}
/// Native Reset82DA5C78 retains lifetime completed-lines (+24).
pub fn reset(&mut self) {
+6
View File
@@ -2,6 +2,12 @@
//! Collectors supply the native clock and already-conditioned point factor.
use super::Scorable;
/// 82DA45C8 multiplies the sum of authored and collector delay by the
/// binary32 constant at 8303745C (0x42700000), then truncates to integer.
pub fn delay_ticks(authored_seconds: f32, extra_seconds: f32) -> u32 {
(((authored_seconds + extra_seconds) * f32::from_bits(0x42700000)) as i64) as u32
}
#[derive(Clone, Debug)]
pub struct Carrier {
pub scorable: Scorable,
+109
View File
@@ -0,0 +1,109 @@
//! Module publication and line settlement, TU3 82DA37B0/82DA3B38.
use super::{
ScoreHolder,
timer::{ComboTimer, PointTimer},
};
#[derive(Clone, Copy, Debug)]
pub struct Rules {
pub combo_capacity: f32,
pub combo_levels: [(f32, f32); 3],
pub combo_refresh_threshold: f32,
pub line_capacity: f32,
pub bail_factor: f32,
}
#[derive(Clone, Debug, Default)]
pub struct Session {
pub holder: ScoreHolder,
pub combo: ComboTimer,
pub line: PointTimer,
}
impl Session {
/// The collector owns the decision to publish. Capture the multiplier
/// before crediting timers: a threshold reached by this reward affects
/// the next publication, not this publication's score (f29 at 82DA37B0).
pub fn publish_sequence(
&mut self,
rules: &Rules,
landing_factor: f32,
penalized: bool,
multiplier_enabled: bool,
) -> f32 {
self.holder.reward_sequence(landing_factor);
let snapshot = &self.holder.snapshot;
let mut raw =
(snapshot.fingerflip_pending + snapshot.general_pending) + snapshot.accumulated;
if penalized && rules.bail_factor < 1.0 {
raw *= rules.bail_factor;
}
let multiplier = if multiplier_enabled {
self.combo.multiplier
} else {
1.0
};
if multiplier_enabled {
self.combo.credit(
raw,
rules.combo_capacity,
rules.combo_levels,
rules.combo_refresh_threshold,
);
if self.combo.multiplier > f32::from_bits(0x3f8147ae) {
self.line.credit(raw, rules.line_capacity);
}
}
let reward = multiplier * raw;
self.holder
.publish(reward, multiplier_enabled && self.line.points > 0.0);
reward
}
/// Reset/bail/output requests or the expiry edge reset line time and the
/// multiplier. A merely empty line timer does not reset the multiplier.
pub fn settle_line(&mut self, reset_requested: bool, collector_active: bool) {
if reset_requested || self.line.expired {
self.line = PointTimer::default();
self.combo.multiplier = 1.0;
self.holder.finish_line();
} else if self.line.points <= 0.0 {
self.holder.bank_line(!collector_active);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scoring::Scorable;
#[test]
fn newly_crossed_threshold_applies_to_the_following_publication() {
let rules = Rules {
combo_capacity: 801.0,
combo_levels: [(50.0, 1.5), (450.0, 2.0), (800.0, 3.0)],
combo_refresh_threshold: 799.0,
line_capacity: 400.0,
bail_factor: 0.0,
};
let mut session = Session::default();
let trick = Scorable {
id: 96,
class: 3,
score_type: 2,
};
session.holder.end_trick(trick, 50.0);
session.holder.finish_collector();
assert_eq!(session.publish_sequence(&rules, 1.0, false, true), 50.0);
assert_eq!(session.combo.multiplier, 1.5);
session.holder.end_trick(trick, 10.0);
session.holder.finish_collector();
assert_eq!(session.publish_sequence(&rules, 1.0, false, true), 15.0);
assert_eq!(session.holder.snapshot.line, 65.0);
session.line.points = 0.0;
session.settle_line(false, true);
assert_eq!(session.holder.repetition_count(trick), Some(2));
session.settle_line(false, false);
assert_eq!(session.holder.repetition_count(trick), Some(0));
}
}
+9
View File
@@ -40,6 +40,15 @@ pub struct ScoringData {
}
impl ScoringData {
pub fn session_rules(&self) -> skate_core::scoring::session::Rules {
skate_core::scoring::session::Rules {
combo_capacity: self.combo_capacity,
combo_levels: self.combo_levels,
combo_refresh_threshold: self.combo_refresh_threshold,
line_capacity: self.line_capacity,
bail_factor: self.bail_factor,
}
}
pub fn load(data: &Collections) -> Result<Self, String> {
let mut definitions = Vec::new();
for (id, &(identifier, class, score_type)) in IDENTIFIERS.iter().enumerate() {
+8 -3
View File
@@ -18,6 +18,9 @@ Implemented source components:
* `skate-core::scoring`: ScoreHolder accounting at `82DA6198`, `82DA6260`,
`82DA6408`, `82DA6468`, `82DA6538`; point timers at `82DA4C28`; carrier
announcement/completion at `82DA45C8`, `82DA46D0`, `82DA5DE0`, `82DA5F98`.
Session publication at `82DA37B0` captures the multiplier before crediting
this reward to the combo timer. Settlement at `82DA3B38` preserves repetition
on an empty line timer while a collector is active.
* `skate-data::scoring`: resolves authored VLT points, labels, delay, repetition,
announcement curves, combo thresholds and timer settings. Missing data is
an error. The executable's 332-entry identifier metadata includes unused
@@ -61,8 +64,9 @@ with a new overlay would not meet this port's requirements.
Collectors are not wired into the production tick yet. Recognition still
requires conditioner gates, descriptor conversion, air/ground/grind collector
transitions, spin/flip bonuses, gaps, landing and sequence publication ordering.
The native clock frequency must be recovered before converting authored
announcement delays to ticks. A physics-frame count is not a safe substitute.
Announcement delay conversion uses the verified binary32 60 at `8303745C`.
The carrier's start/current tick source still needs to be connected at the
matching production phase.
The HUD still needs a complete movie object hierarchy, native bindings,
timeline execution and rendering with multiplicative and additive colors.
@@ -84,7 +88,8 @@ release build/launcher remain to be integrated and checked. No game/recomp,
controller harness, gameplay automation, `--check-assets`, or screenshot
capture has been run for this work.
Validation so far: eight scoring unit tests pass (including unsigned clock
Validation so far: nine scoring unit tests pass (including unsigned clock
rollover, early completion, conversion, timer thresholds, one-time banking and
cancelled rewards); the owned-data loader audit resolves 300 definitions; the
APT data-only audit passes placement validation and constructor checks.