Files
ReSkate/Extension/BoardBreak/Test/board_break_tests.cpp
T
Fourth Studio b999f37e7b Board Break: wear and tear, rail snaps, break and body sounds, settings
Boards wear: hard landings, hits onto rails and ledges, grinding and
slams wear the board of the deck in the game, kept per deck. From a fifth
worn it steers lazier and its trucks go soft (the game's own physics
tuning, outside multiplayer sessions only); a worn board breaks from less
and, worn out, can give way under the rider. A board that broke is a new
one once it is whole again, and the game's own board wear is cleared with
it.

A boardslide, lipslide or darkslide that comes down too hard onto a rail
can snap the board, and the rider goes down with it: No Bail can now be
asked for a wipeout, which goes in as a cause before the selector picks.

Sounds play through XAudio2: a crack by the size of the break, metal when
the board broke on metal (the material comes from the game's contact
effects; which are metal is marked in play or listed by a pack), and the
body's hits in a bail by how hard they land.

The settings are a card on the overlay's Skater page, two rows in Mod
Options and console verbs, kept in BoardBreak.settings. A deck may name
itself and set its wheels wider apart in a deck.ini; Old School and Pig
join the decks the feature knows.
2026-10-15 19:22:21 +02:00

233 lines
15 KiB
C++

// Board Break's rules, apart from the game: what a break leaves of a board, how likely a slam
// breaks it, and where the parts of a broken board lie.
#include "Extension/BoardBreak/board_break_model.h"
#include "Extension/BoardBreak/board_break_feel.h"
#include <cmath>
#include <initializer_list>
#include <iostream>
#include <stdexcept>
using namespace dingosdk::board_break;
namespace {
void check(bool value, const char* message) { if (!value) throw std::runtime_error(message); }
bool near(float a, float b, float within = 1e-4f) { return std::abs(a - b) < within; }
bool near(Vec a, Vec b, float within = 1e-4f) { return length(a - b) < within; }
// Where a point of a part ends up after the part's motion.
Vec moved(const Rigid& motion, Vec point) { return turn(motion.facing, point) + motion.at; }
constexpr Quat level{};
// The rig's mount: a quarter turn about the upright axis.
const Quat mount = about({0, 1, 0}, 1.5707963f);
void a_break_leaves_two_pieces_or_one_that_hangs_together() {
for (const auto place : {Place::nose, Place::mid, Place::tail}) {
const auto clean = broken_at(place, false);
check(clean.hinge == Hinge::none && clean.loose, "A clean break has a piece that comes off");
check((clean.kept | clean.loose) == whole_board && !(clean.kept & clean.loose), "and the two are the whole board, each part once");
const auto joined = broken_at(place, true);
check(joined.kept == whole_board && !joined.loose && joined.hinge != Hinge::none, "Held by the grip tape, nothing comes off");
}
check(broken_at(Place::nose, false).loose == nose && broken_at(Place::tail, false).loose == tail, "An end comes off by itself");
const auto halves = broken_at(Place::mid, false);
check(halves.kept == (nose | front | front_truck) && halves.loose == (back | tail | back_truck),
"In the middle the board keeps its front half, and each half its truck");
check(broken_at(Place::nose, true).hinge == Hinge::nose && broken_at(Place::mid, true).hinge == Hinge::fold &&
broken_at(Place::tail, true).hinge == Hinge::tail,
"and each place hangs together its own way");
}
void a_harder_slam_breaks_a_board_more_often() {
check(break_chance(10.0f, 10.0f, 0.25f) == 1.0f && break_chance(30.0f, 10.0f, 0.25f) == 1.0f, "A heavy slam always breaks it");
check(break_chance(0.0f, 10.0f, 0.25f) == 0.25f && break_chance(6.0f, 10.0f, 0.25f) == 0.25f, "A light one now and then");
check(near(break_chance(8.0f, 10.0f, 0.25f), 0.625f), "and in between it rises evenly");
float before{};
for (float force = 0; force <= 12.0f; force += 0.5f) {
const auto chance = break_chance(force, 10.0f, 0.25f);
check(chance >= before && chance <= 1.0f, "It never falls with more force");
before = chance;
}
}
void the_size_of_a_break_picks_its_sound() {
check(break_size(Place::mid, false, 0.0f) == Size::heavy, "A board that comes apart in the middle is the biggest break");
check(break_size(Place::mid, true, 0.9f) == Size::heavy && break_size(Place::mid, true, 0.4f) == Size::medium,
"A fold right down is as big, a shallow one in between");
check(break_size(Place::nose, false, 0.0f) == Size::medium && break_size(Place::tail, false, 0.0f) == Size::medium, "An end that comes off is in between");
check(break_size(Place::nose, true, 0.0f) == Size::light && break_size(Place::tail, true, 0.0f) == Size::light, "One that hangs on is a small one");
}
void a_whole_board_keeps_its_stretches_away() {
const auto bones = bones_of({});
for (std::size_t i = 0; i < 3; ++i) check(near(bones[i].at, away), "A stretch that does not show goes far away");
check(near(bones[3].at, Vec{}) && near(bones[3].facing.w, 1.0f), "and the tail's, which hangs on the front's, goes with that one");
}
void the_tail_is_placed_from_the_front_stretch() {
// The tail's bone hangs on the front stretch's: wherever the two lie, the front's motion
// followed by what goes into the tail's bone is the tail's own motion.
const Rigid front_motion{about({1, 0, 0}, 0.3f), {0.01f, 0.02f, -0.03f}};
const Rigid tail_motion{unit(about({0, 1, 0}, 1.1f) * about({1, 0, 0}, -0.4f)), {0.5f, -0.2f, 0.7f}};
for (const bool front_shown : {true, false}) {
std::array<Stretch, 4> stretch{};
stretch[1] = {front_shown, front_motion};
stretch[3] = {true, tail_motion};
const auto bones = bones_of(stretch);
const Rigid parent = front_shown ? front_motion : Rigid{{}, away};
for (const Vec point : {Vec{0, 0.09f, -0.3f}, Vec{0.1f, 0.1f, -0.4f}})
check(near(moved(parent, moved(bones[3], point)), moved(tail_motion, point), 2e-3f), "The tail lies where its piece does");
}
}
void a_piece_shows_only_its_own_stretches() {
const auto clean = broken_at(Place::mid, false);
const auto layout = lay_out(clean.kept, clean.hinge, 0.0f, 1.0f, mount);
check(layout.stretch[0].shown && layout.stretch[1].shown && !layout.stretch[2].shown && !layout.stretch[3].shown,
"The front half is the nose and the front stretch");
check(layout.front_truck && !layout.back_truck, "on its front truck");
const auto bones = bones_of(layout.stretch);
check(near(bones[2].at, away), "and the back stretch is nowhere to be seen");
// Half a board on one truck tips onto its broken end while it is the right way up, and not on its grip.
const auto broken_end = moved(layout.stretch[1].motion, turn(mount, Vec{0, 0.09f, mid_row}));
check(broken_end.y < 0.09f - 0.03f, "The half tips onto its broken end");
const auto upside_down = lay_out(clean.kept, clean.hinge, 0.0f, -1.0f, mount);
check(near(upside_down.stretch[1].motion.at, Vec{}), "Lying on its grip it stays as it is");
}
void a_folded_board_meets_at_the_break() {
for (const float depth : {0.2f, 0.6f, 1.0f}) {
Rigid front_half, back_half;
folded(depth, front_half, back_half);
// The grip of both halves at the break: the same height, a crack's width apart.
const Vec grip{0, 0.095f, mid_row};
const auto ahead = moved(front_half, grip), behind = moved(back_half, grip);
check(near(ahead.y, behind.y, 1e-3f), "Both halves' grip is at one height at the break");
check(near(ahead.z - behind.z, crack, 1e-3f), "with the crack between them");
check(ahead.y < grip.y, "and the break has sagged");
// Each half turns about its own axle: the wheels stay where they were, but for the slide along the board.
check(near(moved(front_half, {0, axle_height, front_axle}).y, axle_height, 1e-4f) &&
near(moved(back_half, {0, axle_height, back_axle}).y, axle_height, 1e-4f),
"The axles keep their height");
}
const auto whole = lay_out(whole_board, Hinge::fold, 0.8f, 1.0f, mount);
for (const auto& one : whole.stretch) check(one.shown, "A folded board shows all four stretches");
check(!near(whole.stretch[1].motion.at, whole.stretch[2].motion.at), "its two halves moved apart");
check(near(whole.stretch[0].motion.at, whole.stretch[1].motion.at) && near(whole.stretch[2].motion.at, whole.stretch[3].motion.at),
"and each end with its half");
}
void a_hanging_end_turns_about_the_break() {
const auto layout = lay_out(whole_board, Hinge::nose, 0.5f, 1.0f, mount);
check(near(layout.stretch[1].motion.at, Vec{}) && near(layout.stretch[2].motion.at, Vec{}) && near(layout.stretch[3].motion.at, Vec{}),
"The rest of the board stays where it is");
const auto tip = moved(layout.stretch[0].motion, turn(mount, Vec{0, 0.14f, 0.41f}));
check(tip.y < 0.14f - 0.05f, "and the nose droops");
const auto hinge = moved(hung_on_grip(nose_row, 1.0f, 0.5f), {0, grip_height - 0.004f, nose_row});
check(near(hinge, Vec{0, grip_height - 0.004f, nose_row + crack * 0.5f}), "It turns about the break line, a hair away from the board");
}
void a_truck_goes_with_its_piece() {
// The deck's bone is moved away to hide the real deck, and the truck hangs on it: what goes into
// the truck's bone takes that move back and adds the piece's motion.
const Vec mount_at{0, 0.0856f, 0};
const auto still = truck_bone({}, mount, mount_at, level, front_truck_place);
check(near(still.at + away, front_truck_place) && near(still.facing.w, 1.0f), "A piece that has not moved leaves its truck where it was");
const Rigid lifted{{}, {0, 0.25f, 0}};
const auto up = truck_bone(lifted, mount, mount_at, level, front_truck_place);
check(near(up.at + away, front_truck_place + Vec{0, 0.25f, 0}), "A piece that is lifted takes its truck up with it");
}
void a_baseplate_stays_on_the_deck_while_its_truck_steers() {
// A baseplate sits on the truck's effector, a child of the truck's bone. Whatever the truck's
// bone does, the truck's bone followed by the effector's is what it is at rest.
for (const auto& rest : {back_truck_rest, front_truck_rest}) {
const auto still = baseplate_bone({rest.facing, rest.at}, rest);
check(near(still.at, rest.effector) && near(std::abs(still.facing.w), 1.0f), "At rest the effector is where the rig has it");
const Vec bolt{0.03f, 0.08f, rest.at.z + 0.02f};
const auto on_deck = moved({rest.facing, rest.at}, moved({{}, rest.effector}, bolt));
for (const float lean : {-0.4f, 0.15f, 0.6f}) {
const Rigid steered{unit(rest.facing * about({0, 0, 1}, lean)), rest.at};
const auto effector = baseplate_bone(steered, rest);
check(near(moved(steered, moved(effector, bolt)), on_deck, 1e-4f), "Steered, the baseplate has not moved on the deck");
}
}
}
void the_settings_keep_to_their_ranges() {
Settings s;
check(set_setting_value(s, "sensitivity", 9999) && s.sensitivity == sensitivity_max, "A percent is kept within its range");
check(set_setting_value(s, "always", 5) && s.always && setting_value(s, "always") == 1, "a switch is on or off");
check(set_setting_value(s, "joined", -3) && s.joined == 0, "and nothing goes below its least");
check(!set_setting_value(s, "nonsense", 1) && setting_value(s, "nonsense") == -1, "An unknown name is no setting");
for (const auto& info : setting_info) check(setting_value(s, info.name) >= 0, "Every setting has a value");
}
void a_worn_or_touchy_board_breaks_sooner() {
check(slam_chance(heavy_slam, 100, 0.0f) == 1.0f, "A heavy slam always breaks a new board");
check(slam_chance(6.0f, 100, 0.0f) < slam_chance(6.0f, 100, 1.0f), "A worn board breaks from less");
check(slam_chance(heavy_slam * 0.5f, 200, 0.0f) == 1.0f, "At 200% half the force always breaks it");
check(slam_chance(8.0f, 50, 0.0f) < slam_chance(8.0f, 100, 0.0f), "at 50% it takes more");
check(slam_chance(0.0f, 400, 1.0f) <= 0.95f, "and a stumble never breaks it for certain");
}
void every_hit_wears_a_board() {
check(landing_wear(3.0f) == 0.0f && landing_wear(5.0f) > 0.0f && landing_wear(9.0f) > landing_wear(5.0f), "Harder landings wear more, light ones none");
check(rail_wear(3.0f, true) > rail_wear(3.0f, false), "A slide wears the deck more than a grind on the trucks");
check(grind_wear_per_second(true) > grind_wear_per_second(false), "and sliding more than grinding");
check(slam_wear(2.0f) == 0.0f && slam_wear(12.0f) > slam_wear(7.0f), "A stumble costs nothing, a hard slam a lot");
check(add_wear(0.5f, 0.1f, 0) == 0.5f && near(add_wear(0.5f, 0.1f, 200), 0.7f) && add_wear(0.95f, 1.0f, 100) == 1.0f,
"Wear goes at the player's rate, and never past worn out");
}
void a_worn_board_handles_worse() {
check(handling(0.0f) == Handling{} && handling(0.2f) == Handling{}, "A board under a fifth worn handles as new");
const auto half = handling(0.6f), done = handling(1.0f);
check(half.steering < 1.0f && done.steering < half.steering && done.spring < half.spring && done.twist > half.twist,
"The more worn, the lazier the turn and the softer the trucks");
check(done.steering > 0.5f && done.spring > 0.2f, "but it can still be ridden");
check(gives_way_per_second(1.0f) > 50 * gives_way_per_second(0.0f), "A worn-out board gives way under the rider far more often");
}
void a_board_across_its_way_slides() {
const Vec level_up{0, 1, 0}, ahead{0, 0, 1}, across{1, 0, 0}, upside_down{0, -1, 0}, going{0, -1, 5};
check(contact_of(ahead, level_up, going) == Contact::grind, "Along the rail it grinds on its trucks");
check(contact_of(across, level_up, going) == Contact::slide, "Across it, it slides on the deck");
check(contact_of(across, upside_down, going) == Contact::darkslide, "On its grip it is a darkslide");
check(contact_of(across, level_up, {0, -2, 0.1f}) == Contact::grind, "A board that hardly moves along counts as grinding");
check(rail_chance(9.0f, Contact::grind, 100, 0.0f) == 0.0f, "A grind on the trucks never snaps the board");
check(rail_chance(2.0f, Contact::slide, 100, 0.0f) == 0.0f && rail_chance(6.0f, Contact::slide, 100, 0.0f) == 1.0f,
"A hop onto a rail is safe, a drop onto it snaps the board");
check(rail_chance(3.5f, Contact::darkslide, 100, 0.0f) > rail_chance(3.5f, Contact::slide, 100, 0.0f), "a darkslide sooner");
}
void a_board_breaks_where_it_hit() {
check(place_of_break(0.8f, false, 0.1f) == Place::nose && place_of_break(-0.8f, false, 0.1f) == Place::tail, "Nose first at the nose, tail first at the tail");
check(place_of_break(0.8f, true, 0.1f) == Place::mid, "Across a rail in the middle");
check(place_of_break(0.0f, false, 0.2f) == Place::mid && place_of_break(0.0f, false, 0.9f) == Place::tail, "Flat mostly in the middle");
check(body_loudness(2.0f) == 0.0f && body_loudness(body_full) == 1.0f && body_loudness(5.0f) < body_loudness(9.0f), "Harder hits sound louder");
}
} // namespace
int main() {
try {
a_break_leaves_two_pieces_or_one_that_hangs_together();
a_harder_slam_breaks_a_board_more_often();
the_size_of_a_break_picks_its_sound();
a_whole_board_keeps_its_stretches_away();
the_tail_is_placed_from_the_front_stretch();
a_piece_shows_only_its_own_stretches();
a_folded_board_meets_at_the_break();
a_hanging_end_turns_about_the_break();
a_truck_goes_with_its_piece();
a_baseplate_stays_on_the_deck_while_its_truck_steers();
the_settings_keep_to_their_ranges();
a_worn_or_touchy_board_breaks_sooner();
every_hit_wears_a_board();
a_worn_board_handles_worse();
a_board_across_its_way_slides();
a_board_breaks_where_it_hit();
std::cout << "board break tests passed\n";
return 0;
} catch (const std::exception& error) {
std::cerr << "board break test failed: " << error.what() << '\n';
return 1;
}
}