Files
ReSkate/Extension/Trainer/Test/presets_tests.cpp
T
andrewnakasandClaude Opus 5.5 0bcb0ba2ac Merge ReSkate v2.1.0 into the trainer, in its new menu layout
The trainer keeps upstream's page and naming (SANDBOX; noclip, No Bail and
the boosts on Skater > Cheats; the full screen controller menu) with its own
TUNE sections, presets card and search inside. The MOVEMENT tab is gone:
`trainer open movement` opens Skater > Cheats, where the Revert Boost rows
now sit. Upstream's Hardcore preset sits beside Realistic; saving
trainer.json and reading the map file stay off the client thread.

Board Break's wear and the trainer no longer undo each other: wear writes
four tuning values itself, and every trainer write used to put them back to
the trainer's copy (and a level loaded on a worn board took them as stock).
board_break::handling_value() tells the trainer what wear wants; values the
player has not changed are handed back to it, their stock is the game's own.

Tests: Hardcore's check follows the sections (topic, not the old lists);
the session test pokes a flip speed past the trainer's own limit (100).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 00:13:49 -06:00

113 lines
6.3 KiB
C++

// The REALISTIC list's presets (trainer_presets.h): that Hardcore goes at least as far as
// Realistic on everything Realistic changes and shows on that list, and that each of their rules
// naming one of the game's data-defined classes names a field it has. A pattern that matches
// nothing is skipped without a word in game, so a misspelt one would otherwise go unnoticed.
// Only these two presets are checked: in the others a pattern such as "wipeout.x" may be meant
// for the physics tuning asset ("physicswipeout.x"), whose names need the game's data.
// And that a preset of the player's own never keeps a built-in preset's name: apply and remove
// look at the player's presets first, so it would hide the built-in.
#include "Extension/Trainer/trainer_classes.h"
#include "Extension/Trainer/trainer_presets.h"
#include <algorithm>
#include <cctype>
#include <iostream>
#include <string>
#include <string_view>
namespace {
using namespace dingosdk::trainer;
int failures = 0;
void check(bool condition, const std::string &message) {
if (!condition) { std::cerr << "FAIL: " << message << '\n'; ++failures; }
}
std::string lower(std::string_view text) {
std::string out(text);
for (auto &c : out) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
return out;
}
const BuiltinPreset *find_preset(std::string_view name) {
const auto &presets = builtin_presets();
const auto found = std::ranges::find(presets, name, &BuiltinPreset::name);
return found == presets.end() ? nullptr : &*found;
}
// The class whose key the pattern starts with ("push." for "push.maxpushspeedlight"), if any.
const ClassSpec *class_of(std::string_view pattern) {
for (const auto &spec : class_specs)
if (const auto key = lower(spec.key); pattern.size() > key.size() && pattern.starts_with(key) && pattern[key.size()] == '.')
return &spec;
return nullptr;
}
}
int main() {
const auto *realistic = find_preset("Realistic");
const auto *hardcore = find_preset("Hardcore");
check(realistic != nullptr, "Realistic is a built-in preset");
check(hardcore != nullptr, "Hardcore is a built-in preset");
if (realistic && hardcore) {
check(preset_dial("Hardcore").topic == topic_game && preset_dial("Realistic").topic == topic_game, "Hardcore shows beside Realistic (WHOLE GAME)");
// Every value Realistic turns down, Hardcore turns down at least as far, so switching
// from one to the other never makes the game easier.
for (const auto &rule : realistic->rules) {
const auto same = std::ranges::find(hardcore->rules, rule.pattern, &PresetRule::pattern);
check(same != hardcore->rules.end(), "Hardcore also sets \"" + std::string(rule.pattern) + "\"");
if (same == hardcore->rules.end()) continue;
check(same->multiply == rule.multiply && same->curves == rule.curves && same->amount <= rule.amount,
"Hardcore goes at least as far as Realistic on \"" + std::string(rule.pattern) + "\"");
}
}
for (const auto *preset : {realistic, hardcore}) {
if (!preset) continue;
for (const auto &rule : preset->rules) {
if (rule.multiply) check(rule.amount > 0, std::string(preset->name) + ": a multiplier above 0 for \"" + std::string(rule.pattern) + "\"");
// One word that starts with a class's key: it is meant for that class's fields.
if (rule.curves || rule.pattern.find(' ') != std::string_view::npos) continue;
const auto *spec = class_of(rule.pattern);
if (!spec) continue;
bool found = false;
for (std::size_t i = 0; i < spec->count && !found; ++i)
found = lower(std::string(spec->key) + "." + class_fields[spec->first + i].name).find(rule.pattern) != std::string::npos;
check(found, std::string(preset->name) + ": \"" + std::string(rule.pattern) + "\" names a field of " + spec->key);
}
}
// Names a player's preset may not have, in any case: the built-ins', Stock and Map.
for (const auto *name : {"Hardcore", "hardcore", "HARDCORE", "Realistic", "Stock", "map"})
check(reserved_preset_name(name), std::string("\"") + name + "\" is reserved");
for (const auto *name : {"Hardcore (yours)", "My Hardcore", "Hardcor", ""})
check(!reserved_preset_name(name), std::string("\"") + name + "\" is free");
// A preset saved before a built-in took its name keeps its values under a free one.
UserPresets user{{"Hardcore", {{"physicsmode.jumpmaxheight", 0.5}}},
{"hardcore (yours)", {{"push.maxpushspeedlight", 3.0}}},
{"STOCK", {{"jump.basejumpheight", 0.5}}},
{"Mine", {{"physicsmode.grindlockdist", 0.4}}}};
const auto renamed = rename_reserved_presets(user);
check(renamed.size() == 2, "two presets are renamed");
const auto renamed_to = [&](std::string_view from) {
const auto found = std::ranges::find(renamed, from, [](const auto &pair) { return std::string_view(pair.first); });
return found == renamed.end() ? std::string{} : found->second;
};
check(renamed_to("Hardcore") == "Hardcore (yours 2)", "a taken new name, in any case, gets a number");
check(renamed_to("STOCK") == "STOCK (yours)", "Stock is renamed like a built-in's name");
check(user.size() == 4 && user.contains("Mine") && user.contains("hardcore (yours)"), "the other presets stay as they were");
check(user.contains("Hardcore (yours 2)") && user.at("Hardcore (yours 2)").at("physicsmode.jumpmaxheight") == 0.5,
"a renamed preset keeps its values");
for (const auto &entry : user) check(!reserved_preset_name(entry.first), "no reserved name is left: \"" + entry.first + "\"");
check(rename_reserved_presets(user).empty(), "renaming again changes nothing");
// Every reserved name still fits once renamed, or the preset would be dropped on the next load.
for (const auto &preset : builtin_presets()) {
UserPresets one{{std::string(preset.name), {}}};
const auto result = rename_reserved_presets(one);
check(result.size() == 1 && result[0].second.size() <= max_preset_name, std::string(preset.name) + " fits once renamed");
}
if (failures) {
std::cerr << failures << " check(s) failed.\n";
return 1;
}
std::cout << "Trainer presets: all checks passed.\n";
return 0;
}