Board Break: the trucks' baseplates stay on a broken board

The game's truck model has its hangers on the trucks' bones and its
baseplates on the deck's bone. A broken board hides the real deck by moving
that bone away, so its trucks lost their baseplates.

The decks inside a deck pack bring a truck model with the baseplates on the
trucks' effectors, bones under the trucks' own that no model uses. The render
pose hook writes each effector from its truck's bone, so that the baseplate
sits on the deck of a whole board however the truck steers, and goes with the
truck when a piece carries it off. With the game's own truck model nothing is
on those bones and nothing changes.
This commit is contained in:
Fourth Studio committed 2026-10-12 01:10:34 +02:00
1 parent 0c8add27a4
commit 3d8a5fc8c7
4 files changed
+93 -7

No files matched your search

@@ -133,6 +133,21 @@ void a_truck_goes_with_its_piece() {
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");
}
}
}
} // namespace
int main() {
@@ -146,6 +161,7 @@ int main() {
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();
std::cout << "board break tests passed\n";
return 0;
} catch (const std::exception& error) {
+24 -3
View File
@@ -45,6 +45,9 @@ constexpr float hand_reach = 1.3f, hand_upright = 0.8f;
// deck's mount, 3 the deck, 4 and 8 the trucks, and the spare bones that carry the four stretches
// (nose, front, back, tail; the tail's hangs on the front's).
constexpr int root_bone = 1, mount_bone = 2, deck_bone = 3, back_truck_bone = 4, front_truck_bone = 8;
// The trucks' effectors, each right after its truck's bone: a deck pack's truck model has the
// baseplates on them (board_break_model.h, baseplate_bone).
constexpr int back_plate_bone = 5, front_plate_bone = 9;
constexpr std::array<int, 4> carrier{12, 13, 15, 14};
constexpr std::uint32_t rig_bones = 16;
@@ -152,6 +155,21 @@ void carry_truck(std::uintptr_t buffer, const Rigid& motion, int index, Vec plac
(void)store_bone(bone_at(buffer, index), bone.facing, bone.at);
}
// Keeps the baseplates on the deck (baseplate_bone), from each truck's bone as the animation left
// it: a truck that a piece has carried off already, in a pose nobody has written since, is left.
void hold_baseplates(std::uintptr_t buffer) noexcept {
const auto hold = [&](int truck_index, int plate_index, const TruckRest& rest) {
PoseBone truck{};
if (!load_bone(bone_at(buffer, truck_index), truck)) return;
const Rigid now{facing_of(truck), place_of(truck)};
if (!(length(now.at - rest.at) < 0.05f)) return;
const auto bone = baseplate_bone(now, rest);
(void)store_bone(bone_at(buffer, plate_index), bone.facing, bone.at);
};
hold(back_truck_bone, back_plate_bone, back_truck_rest);
hold(front_truck_bone, front_plate_bone, front_truck_rest);
}
// ---- the piece that comes off --------------------------------------------------------------------
// The client tick starts a flight (a new id) when the board breaks; the render pose hook moves the
// piece through the world from there (a kick, gravity, the ground) and writes it relative to
@@ -276,14 +294,15 @@ std::uint32_t fly_piece(std::uintptr_t buffer, Rigid& piece) {
}
// ---- what the hook writes ------------------------------------------------------------------------
// The local board, broken: the real deck goes away, the trucks come back, each stretch goes to
// where its piece lies and one that belongs to no piece goes away too.
// The local board, broken: the real deck goes away, the trucks come back (their baseplates with
// them), each stretch goes to where its piece lies and one that belongs to no piece goes away too.
void draw_broken(std::uintptr_t base, std::uintptr_t holder, std::uint32_t kept, Hinge hinge, float angle) {
const auto buffer = board_pose(base, holder);
PoseBone root{}, mount{}, deck{};
if (!buffer || !load_bone(bone_at(buffer, root_bone), root) || !load_bone(bone_at(buffer, mount_bone), mount) ||
!load_bone(bone_at(buffer, deck_bone), deck))
return;
hold_baseplates(buffer);
// From where its animation put it this frame; a pose nobody has written since the last move stays as it is.
if (const Vec at = place_of(deck); length(at) < 100.0f) (void)store_bone(bone_at(buffer, deck_bone), facing_of(deck), at + away);
auto layout = lay_out(kept, hinge, angle, turn(facing_of(root), {0, 1, 0}).y, facing_of(mount));
@@ -298,10 +317,12 @@ void draw_broken(std::uintptr_t base, std::uintptr_t holder, std::uint32_t kept,
const auto bones = bones_of(layout.stretch);
for (std::size_t i = 0; i < bones.size(); ++i) (void)store_bone(bone_at(buffer, carrier[i]), bones[i].facing, bones[i].at);
}
// A whole board, any player's: its four stretches go far away from it.
// A whole board, any player's: its four stretches go far away from it, and its baseplates sit on
// the deck.
void park_pieces(std::uintptr_t base, std::uintptr_t holder) {
const auto buffer = board_pose(base, holder);
if (!buffer) return;
hold_baseplates(buffer);
const auto bones = bones_of({});
for (std::size_t i = 0; i < bones.size(); ++i) (void)store_bone(bone_at(buffer, carrier[i]), bones[i].facing, bones[i].at);
}
+17
View File
@@ -203,4 +203,21 @@ inline Rigid truck_bone(const Rigid& motion, Quat mount, Vec mount_at, Quat stee
const auto at = turn(conj(mount), turn(motion.facing, mount_at) + motion.at - mount_at);
return {unit(facing * steer), turn(facing, place) + at - away};
}
// The trucks' baseplates. The game's own truck model has them on the deck's bone, which goes away
// with the real deck when the board breaks, and they went with it. A deck pack's truck model has
// each baseplate on its truck's effector instead, a bone under the truck's that no model uses:
// wherever a piece carries the truck, its baseplate goes along. This is where that effector goes
// so that the baseplate sits on the deck however the truck steers: the truck's own turn and place
// taken back, its rest put in. `truck` is the truck's bone as the animation left it this frame.
struct TruckRest {
Quat facing; // the truck's bone on the deck's, at rest
Vec at;
Vec effector; // its effector on the truck's bone, at rest (not turned)
};
constexpr TruckRest back_truck_rest{{-0.3641458f, -0.3641458f, -0.6061335f, 0.6061335f}, {0.0005361f, -0.0183003f, -0.2344986f}, {0.0395021f, 0.0f, 0.0f}};
constexpr TruckRest front_truck_rest{{-0.6061674f, 0.6061674f, 0.3640894f, 0.3640894f}, {0.0005361f, -0.0182917f, 0.2346680f}, {0.0395677f, 0.0f, 0.0f}};
inline Rigid baseplate_bone(const Rigid& truck, const TruckRest& rest) noexcept {
const auto back = conj(truck.facing);
return {unit(back * rest.facing), turn(back, rest.at + turn(rest.facing, rest.effector) - truck.at)};
}
} // namespace dingosdk::board_break
+36 -4
View File
@@ -19,7 +19,9 @@ deck's place in the mod list in [mod_catalog.cpp](../Engine/Vfs/mod_catalog.cpp)
while it is ridden: the nose goes.
- `boardbreak 0|1`, `boardbreak deck [name|number|next]`, `boardbreak snap [nose|mid|tail]` to look
at a break (that one stays until `boardbreak mend` or a respawn). The switch and the deck are in
the menus beside Road Rash (Mod Options > Player, and the overlay's Skater page). The switch is
the menus beside Road Rash (Mod Options > Player, and the overlay's Skater page). The overlay has
a card BOARD BREAK DECK with a button for each deck, on the Mods page and under the switch: a
press asks for that deck, the mods are applied and the level loads again with it. The switch is
saved with the profile.
- A break plays back in the replay editor as it happened.
- Without a deck pack installed nothing breaks and nothing is changed.
@@ -66,6 +68,18 @@ deck's place in the mod list in [mod_catalog.cpp](../Engine/Vfs/mod_catalog.cpp)
`light`, `medium`, `heavy`, by the size of the break), played straight to the speakers.
- **The slam's force** comes through No Bail's existing hooks: the cause it records for the watched
skater, and the cause collector at the moment the selector chooses Wipeout. Nothing new is hooked.
- **The trucks' baseplates.** The game's truck model (one for every truck item,
`characters/skateboard/unlicensed/truck/generic/default/2022/truck_gen_default_mesh`) has its
hangers on the trucks' bones and everything bolted to the deck, the baseplates and their bolts,
on the deck's bone. With the deck's bone moved away to hide the real deck, a broken board had
hangers and wheels and no baseplates. The decks inside a deck pack bring a copy of the truck
model with those vertices on the trucks' effectors instead (`eff_truckBack`, `eff_truckFront`:
a bone under each truck's that no model uses), and the hook writes each effector from its
truck's bone as the animation left it, so that the truck's bone followed by the effector's is
what the two are at rest: on a whole board the baseplate sits on the deck however the truck
steers, and a truck that a piece carries off takes its baseplate along
(`baseplate_bone`, with a test). That is done on every board that passes the hook. With the
game's own truck model nothing is on the effectors and writing them shows nothing.
- **On foot the grip tape always holds.** A loose piece takes the ground from where the board
rolled or lies; a board in the skater's hand does not tell where the ground is, and a piece that
came off there hung in the air at hand height.
@@ -87,7 +101,10 @@ A deck is a content mod's copy of the deck model (no code), and comes in one of
what an older ReSkate loads from the pack. ReSkate 2.0.6 knows a deck by its folder's name only,
so a pack in a folder named `Board_Break_<Name>` (the package `Board_Break_Pack`, installed as
`Team-Board_Break_Pack`) with a breakable deck as its own layout breaks boards there with that
one deck, and brings all its decks from this version on.
one deck, and brings all its decks from this version on. The truck model with the baseplates on
the effectors belongs in the decks inside the pack only: a ReSkate that does not place those
bones would draw the baseplates turning with the hangers, and such a version reads the pack's
own layout, never `decks/`.
The name is what the deck goes by. The eight the feature was made with are standard, wide,
concave, powell, slim, egg, square and shaped, and the console also knows those by number. Any
@@ -104,6 +121,9 @@ other deck that follows the recipe works, with no change to ReSkate:
deck's own edge faces standing in the gap as the broken end. Push the row itself along the board
by a jagged line, the same line for both sides of a break, so the pieces fit together.
- Build the stretches at their own size, in the place they have in the whole deck.
- The truck model for a deck inside a pack: the game's own, with every vertex that is on the bone
Deck moved to `eff_truckBack` (behind the middle of the board) or `eff_truckFront` (ahead of
it). Nothing else changes; Studio takes it back as it is.
- The sounds are `sounds/light|medium|heavy/*.wav` in the folder of any mod that brings a deck.
## Seen working in game
@@ -126,6 +146,15 @@ with the game's own deck there, and with a breakable deck there in a folder name
overlay's Skater page turns it off and on. On the
older build the feature was developed on, a middle break played back in the replay editor.
On main at 8351654, with the pack as the only source of decks (its decks with the truck model
that has the baseplates on the effectors): a dozen breaks from real slams at the nose, in the
middle and at the tail, clean and held by the grip, with the baseplates on the pieces, and on the
whole board in hand and on the ground. The effectors are bones 5 and 9 of the pose, each at
0.0395 m along its truck's bone and not turned, as the rig has them. A press on Shaped in the
overlay's deck card: the mods applied in 3.8 s, the level loaded again in 15 s with that deck,
which then broke. In the game's Mod Options > Player page, on 2.0.6: one press on Board Break
deck is one deck further and one reload.
## Not tried yet
- Multiplayer: other players' boards are only handled by the code path (their stretches are moved
@@ -133,6 +162,9 @@ older build the feature was developed on, a middle break played back in the repl
- The online route: all of the above ran on the offline profile.
- The replay editor on this branch (the replay was checked on the older build the feature was
developed on).
- The rows in Mod Options > Player in the game itself (their view has a test; the overlay's switch
was used).
- The deck card with a deck that is not one of the eight, and with none installed (it draws
nothing then).
- A board that does not pass the render pose hook, with the truck model from a pack: its
baseplates would follow the hangers.
- A mod that brings a truck model of its own beside a deck pack: the pack's is the one that loads.
- A level change in the middle of a break.