From 6fcd427bf6d1f140f8d9ee69db90c2a45e8fcbdf Mon Sep 17 00:00:00 2001 From: andrewnakas Date: Tue, 6 Oct 2026 19:50:09 -0600 Subject: [PATCH] Trainer 0.4.2: flip trick speed scales only the stick-speed curve (the pop keeps its height); the slow-flip switch says when a session keeps it off --- Extension/Trainer/CHANGELOG.md | 9 +++++ Extension/Trainer/trainer.cpp | 2 + Extension/Trainer/trainer.h | 1 + Extension/Trainer/trainer_classes.cpp | 53 +++++++++++++++++++-------- Extension/Trainer/trainer_classes.h | 3 ++ Extension/Trainer/trainer_page.cpp | 5 ++- 6 files changed, 55 insertions(+), 18 deletions(-) diff --git a/Extension/Trainer/CHANGELOG.md b/Extension/Trainer/CHANGELOG.md index e02de555..7755f0b8 100644 --- a/Extension/Trainer/CHANGELOG.md +++ b/Extension/Trainer/CHANGELOG.md @@ -10,6 +10,15 @@ AutoRevertBoost. Settings saved by 0.4.1 with the old 12 are moved to off. Turn the rule on yourself and a landing that counts by it alone gets the auto revert boost at most. Found and measured by AutoRevertBoost's authors (Sivaes, jaq and OVM). +- **Flip trick speed no longer lowers your ollie.** The slider used to scale seven of the game's + flip curves and shrink the range each one is read over; three of those are what a flick adds + to the height of the pop, so a slow flip came with a low ollie. It now scales only the one + curve that turns the stick's speed into the flip's speed. Measured on flat ground at x 0.3: + kickflips at about half the game's turn rate, plain ollies the same height and air time as + with the slider untouched. +- **Let slow flips stay slow says when it is off.** In a session whose host enforces physics or + has turned trick settings off, the switch cannot act (the likely reason it seemed to work only on + the main map); a line under it now says so. ## v0.4.1 - 2026-10-06 diff --git a/Extension/Trainer/trainer.cpp b/Extension/Trainer/trainer.cpp index d20e1a93..86ceb958 100644 --- a/Extension/Trainer/trainer.cpp +++ b/Extension/Trainer/trainer.cpp @@ -1541,6 +1541,7 @@ void build_view() { next->flip_speed = s.flip_speed; next->flip_gate = s.flip_gate; next->flip_gate_found = flip_gates() != 0; + next->flip_gate_blocked = !s.flip_gate && (s.enforced || (multiplayer_session_active() && !session_boosts_allowed())); next->boneless_height = s.boneless_height; next->revert_boost = s.revert_boost; next->revert = s.revert; @@ -1758,6 +1759,7 @@ void tick(std::uintptr_t base, std::uintptr_t client, bool playing, const std::s find_classes(); } } + flip_speed_in_use(s.boosts.flip != 1.0f); want_flip_speed(s.boosts.flip); // The player's own choice, and only where their own trick settings count: not under a // host's enforced physics or in a session that has turned boosts off. diff --git a/Extension/Trainer/trainer.h b/Extension/Trainer/trainer.h index 60b7a9a5..7207e5d4 100644 --- a/Extension/Trainer/trainer.h +++ b/Extension/Trainer/trainer.h @@ -92,6 +92,7 @@ struct View { float flip_speed{1}; // board flip tricks: x of the game's own speed bool flip_gate{true}; // the game brings a flip round before the landing; false: slow flips stay slow bool flip_gate_found{}; // the rule's number was found in memory (the switch can act) + bool flip_gate_blocked{}; // ticked, but this session's rules keep the game's own flips // HUD bool hud{}, hud_jump{}, logging{}; // The loaded map and what its author ships for the trainer (Mods//trainer.json). diff --git a/Extension/Trainer/trainer_classes.cpp b/Extension/Trainer/trainer_classes.cpp index 96cacd45..bf6d129b 100644 --- a/Extension/Trainer/trainer_classes.cpp +++ b/Extension/Trainer/trainer_classes.cpp @@ -65,7 +65,7 @@ int mirror_field(const MirrorSlot &slot) noexcept { struct FlipCurve { std::uintptr_t curve{}, points{}; std::vector outputs; - float low{}, high{}; // the bounds the curve clamps its output to (+0x20, +0x24) + float low{}, high{}; // the input range the curve is read over (MinX, MaxX at +0x20, +0x24): never written }; constexpr std::size_t curve_point = 0x1c, curve_x = 0xc, curve_y = 0x14, curve_points_at = 0x18; // The point that tells what a curve holds: its largest output (some curves start at 0). @@ -81,6 +81,7 @@ struct Found { std::mutex mutex; std::vector flip; float flip_wanted{1}, flip_written{1}; + bool flip_used{}; // a flip speed other than the game's is set, though not written at this moment std::vector gates; // where the flick warp limit's number lives bool gate_kept{true}; // false: the trainer holds it off Copies copies; @@ -232,6 +233,14 @@ void collect_eights(std::uintptr_t start, std::size_t size, const std::vector flip_curve_mask{0x10}; // One curve's points: false unless it has `count` points starting at (x0, y0). bool read_curve(std::uintptr_t curve, std::uint32_t count, float x0, float y0, FlipCurve &out) noexcept { std::uintptr_t points{}; @@ -265,18 +274,24 @@ std::vector find_flip_curves(std::uintptr_t curve_type) { for (const auto run : runs) { std::array curves{}; if (!peek(run, curves)) continue; - // In memory order: 0 the flip speed against the flick, 1 the same for the legacy controls, - // 2 a multiplier for a high flick (1 to 2: left alone), 3, 6 and 7 the speed a flick's height - // and the stick sensitivity add (0 to 2.75), 4 flick speed to flip speed, 5 ollie speed to - // flip speed. Scaling only 0, 1, 4 and 5 slowed a flip by a third at x0.4: the rest is 3, 6, 7. + // In memory order: 0 and 1 a speed against the flick (new controls and legacy), 2 a + // multiplier for a high flick (1 to 2), 3, 6 and 7 what a flick adds to the height of the + // pop (0 to 2.75 over the stick's speed), 4 the stick's speed to the flip speed, 5 the + // ollie's speed to the flip speed (flat 1). All seven are checked to know the run is the + // flip trick tuning; flip_curve_mask says which are scaled. std::array speed; if (!read_curve(curves[0], 3, 0.0f, 0.4f, speed[0]) || !read_curve(curves[1], 3, 0.35f, 0.65f, speed[1]) || !read_curve(curves[4], 3, 0.0f, 0.05f, speed[2]) || !read_curve(curves[5], 2, 0.0f, 1.0f, speed[3]) || !read_curve(curves[3], 4, 0.0f, 0.0f, speed[4]) || !read_curve(curves[6], 4, 0.0f, 0.0f, speed[5]) || !read_curve(curves[7], 4, 0.0f, 0.0f, speed[6])) continue; - for (auto &curve : speed) - if (std::ranges::none_of(result, [&](const FlipCurve &known) { return known.points == curve.points; })) result.push_back(std::move(curve)); + // Which of them the flip speed scales: a bit per curve, in memory order (see flip_curve_mask). + static constexpr std::size_t memory_index[]{0, 1, 4, 5, 3, 6, 7}; + const auto mask = flip_curve_mask.load(std::memory_order_relaxed); + for (std::size_t i = 0; i < speed.size(); ++i) + if (auto &curve = speed[i]; ((mask >> memory_index[i]) & 1u) && + std::ranges::none_of(result, [&](const FlipCurve &known) { return known.points == curve.points; })) + result.push_back(std::move(curve)); } return result; } @@ -414,8 +429,6 @@ DWORD WINAPI search(void *) noexcept { if (std::ranges::any_of(f.flip, [&](const FlipCurve &known) { return known.points == curve.points; })) continue; if (f.flip_written != 1) { for (std::size_t i = 0; i < curve.outputs.size(); ++i) (void)put(curve.points + i * curve_point + curve_y, curve.outputs[i] * f.flip_written); - (void)put(curve.curve + 0x20, curve.low > 0 ? curve.low * f.flip_written : curve.low); - (void)put(curve.curve + 0x24, curve.high > 0 ? curve.high * f.flip_written : curve.high); } f.flip.push_back(std::move(curve)); } @@ -458,9 +471,6 @@ std::size_t write_gates(Found &f) noexcept { } void write_flip_curve(const FlipCurve &curve, float factor) noexcept { for (std::size_t i = 0; i < curve.outputs.size(); ++i) (void)put(curve.points + i * curve_point + curve_y, curve.outputs[i] * factor); - // The bounds move with the outputs, or the curve clamps the scaled values back. - (void)put(curve.curve + 0x20, curve.low > 0 ? curve.low * factor : curve.low); - (void)put(curve.curve + 0x24, curve.high > 0 ? curve.high * factor : curve.high); } } // namespace @@ -475,6 +485,15 @@ bool refresh_classes(std::uint64_t now) noexcept { std::size_t repaired{}; repaired += write_gates(f); lost = lost || (!f.gate_kept && f.gates.empty()); + // On the ground the curves hold the game's own (the flip speed is written in the air only): + // one that holds something else is someone else's memory now. + if (f.flip_used && f.flip_written == 1) { + std::erase_if(f.flip, [&](const FlipCurve &curve) { + float y{}; + return curve.outputs.empty() || !peek(probe_address(curve), y) || !same(y, probe_output(curve)); + }); + lost = lost || f.flip.empty(); + } if (f.flip_written != 1) { std::erase_if(f.flip, [&](const FlipCurve &curve) { float y{}; @@ -567,7 +586,7 @@ void want_class_value(std::size_t field, float value) noexcept { bool classes_wanted() noexcept { auto &f = found(); std::lock_guard lock(f.mutex); - if (f.flip_wanted != 1 || !f.gate_kept) return true; + if (f.flip_wanted != 1 || f.flip_used || !f.gate_kept) return true; for (std::size_t i = 0; i < class_field_count; ++i) if (f.wanted[i] != class_fields[i].stock) return true; return false; @@ -587,9 +606,6 @@ std::size_t apply_classes() noexcept { for (const auto &curve : f.flip) { for (std::size_t i = 0; i < curve.outputs.size(); ++i) if (put(curve.points + i * curve_point + curve_y, curve.outputs[i] * f.flip_wanted)) ++count; - // The bounds move with the outputs, or the curve clamps the scaled values back. - (void)put(curve.curve + 0x20, curve.low > 0 ? curve.low * f.flip_wanted : curve.low); - (void)put(curve.curve + 0x24, curve.high > 0 ? curve.high * f.flip_wanted : curve.high); } f.flip_written = f.flip_wanted; } @@ -631,6 +647,11 @@ void want_flip_speed(float factor) noexcept { f.dirty = true; } } +void flip_speed_in_use(bool used) noexcept { + auto &f = found(); + std::lock_guard lock(f.mutex); + f.flip_used = used; +} void want_flip_gate(bool kept) noexcept { auto &f = found(); std::lock_guard lock(f.mutex); diff --git a/Extension/Trainer/trainer_classes.h b/Extension/Trainer/trainer_classes.h index 3cfbd76a..21e2388a 100644 --- a/Extension/Trainer/trainer_classes.h +++ b/Extension/Trainer/trainer_classes.h @@ -51,6 +51,9 @@ std::string classes_summary(); // (speed against the flick, against the ollie...); the search finds it as eight curve references // in a row and apply_classes multiplies the outputs of its four speed curves by this. void want_flip_speed(float factor) noexcept; +// The player has a flip speed of their own set, whether or not it is written right now (it is +// written only while the board is in the air): the curves must be found either way. +void flip_speed_in_use(bool used) noexcept; // How many flip trick speed curves were found (0: the multiplier does nothing yet). std::size_t flip_curves() noexcept; // The game hurries a flip trick so that it is round 1/6 s before the landing it predicts, however diff --git a/Extension/Trainer/trainer_page.cpp b/Extension/Trainer/trainer_page.cpp index 61b32eb5..ff026133 100644 --- a/Extension/Trainer/trainer_page.cpp +++ b/Extension/Trainer/trainer_page.cpp @@ -474,7 +474,7 @@ void trick_heights(SkateMenu &menu, const CallbacksV3 &callbacks, Page &p, const return true; }; slider("Flip trick speed", "flip_speed", view.flip_speed, p.flip_edit, p.flip_editing, - "How fast the board turns in a flip trick. Below 1 a flip is slower and gets that much longer to come round. On its own the game still " + "How fast the board turns in a flip trick. It does not change how high you pop. Below 1 a flip is slower. On its own the game still " "hurries a slow flip so it is finished before you land: tick the switch below to stop that. Above 1 the game's own limit on how fast a " "board turns takes over.", 3.0f); @@ -485,7 +485,8 @@ void trick_heights(SkateMenu &menu, const CallbacksV3 &callbacks, Page &p, const wrapped_tooltip("%s", "The game speeds a flip up so the board is round 1/6 of a second before the landing it predicts, however slow the flip speed " "is set. That is why a slowed flip still comes round on a small pop. Ticked: that rule is off. The board turns as " "slowly as you set it and lands however far round it got, so a flip you start too late or too low is a bail."); - if (slow && !view.flip_gate_found) ImGui::TextDisabled("Not found in the game yet: it takes effect a few seconds after a level loads."); + if (view.flip_gate_blocked) ImGui::TextDisabled("Off in this session: the host's physics rules apply here, so flips finish before landing as usual."); + else if (slow && !view.flip_gate_found) ImGui::TextDisabled("Not found in the game yet: it takes effect a few seconds after a level loads."); } slider("No comply height", "nocomply_height", view.nocomply_height, p.nocomply_edit, p.nocomply_editing, "How high a no comply pops. 1 is the game's own."); slider("Boneless height", "boneless_height", view.boneless_height, p.boneless_edit, p.boneless_editing, "How high a boneless pops. 1 is the game's own.");