From ba0ecc59aa857c24f72aac066968879c4be7556e Mon Sep 17 00:00:00 2001 From: Christopher Leggett Date: Fri, 9 Jan 2026 14:10:12 +0000 Subject: [PATCH] Fix RNG used by cosmetics editor to use same RNG method as rando (#5979) Refactor ShipUtils to optionally take a state pointer. Also changed random.h/cpp to be a wrapper around ShipUtils RNG providing a pointer to the rando state variable. Use ShipUtils RNG for UIWidgets GetRandomValue Fix AudioEditor to use ShipUtils RNG and its own state. It seems like shuffling Audio at the right time could have potentially messed with rando seed generation before this, but that bug, if it existed, should also be fixed with this. --- soh/soh/Enhancements/audio/AudioEditor.cpp | 6 +- .../cosmetics/CosmeticsEditor.cpp | 4 +- .../randomizer/3drando/random.cpp | 36 ++---------- .../randomizer/3drando/random.hpp | 25 +++----- soh/soh/ShipUtils.cpp | 58 +++++++++++++++++++ soh/soh/ShipUtils.h | 47 +++++++++++++++ soh/soh/SohGui/UIWidgets.cpp | 31 +++------- soh/soh/SohGui/UIWidgets.hpp | 2 +- 8 files changed, 133 insertions(+), 76 deletions(-) diff --git a/soh/soh/Enhancements/audio/AudioEditor.cpp b/soh/soh/Enhancements/audio/AudioEditor.cpp index 51bc7f32c..1baf38abf 100644 --- a/soh/soh/Enhancements/audio/AudioEditor.cpp +++ b/soh/soh/Enhancements/audio/AudioEditor.cpp @@ -109,6 +109,8 @@ void UpdateCurrentBGM(u16 seqKey, SeqType seqType) { } } +static uint64_t seeded_audio_state = 0; + void RandomizeGroup(SeqType type, bool manual = true) { std::vector values; @@ -118,7 +120,7 @@ void RandomizeGroup(SeqType type, bool manual = true) { uint32_t finalSeed = type + (IS_RANDO ? Rando::Context::GetInstance()->GetSeed() : static_cast(gSaveContext.ship.stats.fileCreatedAt)); - Random_Init(finalSeed); + ShipUtils::RandInit(finalSeed, &seeded_audio_state); } } @@ -139,7 +141,7 @@ void RandomizeGroup(SeqType type, bool manual = true) { if (!values.size()) return; } - Shuffle(values); + ShipUtils::Shuffle(values, &seeded_audio_state); for (const auto& [seqId, seqData] : AudioCollection::Instance->GetAllSequences()) { const std::string cvarKey = AudioCollection::Instance->GetCvarKey(seqData.sfxKey); const std::string cvarLockKey = AudioCollection::Instance->GetCvarLockKey(seqData.sfxKey); diff --git a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp index b8cb0c35d..49e74f69c 100644 --- a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp +++ b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp @@ -2104,6 +2104,8 @@ void ApplySideEffects(CosmeticOption& cosmeticOption) { } } +static uint64_t seeded_cosmetics_state = 0; + void RandomizeColor(CosmeticOption& cosmeticOption, bool manual = true) { ImVec4 randomColor; @@ -2115,7 +2117,7 @@ void RandomizeColor(CosmeticOption& cosmeticOption, bool manual = true) { (IS_RANDO ? Rando::Context::GetInstance()->GetSeed() : static_cast(gSaveContext.ship.stats.fileCreatedAt)); - randomColor = GetRandomValue(finalSeed); + randomColor = GetRandomValue(finalSeed, &seeded_cosmetics_state); } else { randomColor = GetRandomValue(); } diff --git a/soh/soh/Enhancements/randomizer/3drando/random.cpp b/soh/soh/Enhancements/randomizer/3drando/random.cpp index 4cac19818..257fa0c12 100644 --- a/soh/soh/Enhancements/randomizer/3drando/random.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/random.cpp @@ -5,51 +5,25 @@ #include static bool init = false; -static uint64_t state = 0; +uint64_t rando_state = 0; const uint64_t multiplier = 6364136223846793005ULL; const uint64_t increment = 11634580027462260723ULL; // Initialize with seed specified void Random_Init(uint64_t seed) { - init = true; - state = seed; + ShipUtils::RandInit(seed, &rando_state); } uint32_t next32() { - if (!init) { - // No seed given, get a random number from device to seed -#if !defined(__SWITCH__) && !defined(__WIIU__) - uint64_t seed = static_cast(std::random_device{}()); -#else - uint64_t seed = static_cast(std::hash{}(std::to_string(rand()))); -#endif - Random_Init(seed); - } - - state = state * multiplier + increment; - uint32_t xorshifted = static_cast(((state >> 18) ^ state) >> 27); - uint32_t rot = static_cast(state >> 59); - return std::rotr(xorshifted, rot); + return ShipUtils::next32(&rando_state); } // Returns a random integer in range [min, max-1] uint32_t Random(uint32_t min, uint32_t max) { - if (min == max) { - return min; - } - assert(max > min); - - uint32_t n = max - min; - uint32_t cutoff = UINT32_MAX - UINT32_MAX % static_cast(n); - for (;;) { - uint32_t r = next32(); - if (r <= cutoff) { - return min + r % n; - } - } + return ShipUtils::Random(min, max, &rando_state); } // Returns a random floating point number in [0.0, 1.0) double RandomDouble() { - return ldexp(next32(), -32); + return ShipUtils::RandomDouble(&rando_state); } diff --git a/soh/soh/Enhancements/randomizer/3drando/random.hpp b/soh/soh/Enhancements/randomizer/3drando/random.hpp index 514389471..b08e7807f 100644 --- a/soh/soh/Enhancements/randomizer/3drando/random.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/random.hpp @@ -1,5 +1,6 @@ #pragma once +#include "soh/ShipUtils.h" #include #include #include @@ -7,37 +8,25 @@ #include #include +extern uint64_t rando_state; + void Random_Init(uint64_t seed); uint32_t Random(uint32_t min, uint32_t max); double RandomDouble(); // Get a random element from a vector or array template T RandomElement(std::vector& vector, bool erase) { - const auto idx = Random(0, static_cast(vector.size())); - const T selected = vector[idx]; - if (erase) { - vector.erase(vector.begin() + idx); - } - return selected; + return ShipUtils::RandomElement(vector, erase, &rando_state); } template auto& RandomElement(Container& container) { - return container[Random(0, static_cast(std::size(container)))]; + return ShipUtils::RandomElement(container, &rando_state); } template const auto& RandomElement(const Container& container) { - return container[Random(0, static_cast(std::size(container)))]; + return ShipUtils::RandomElement(container, &rando_state); } template const T RandomElementFromSet(const std::set& set) { - if (set.size() == 1) { - return *set.begin(); - } - uint32_t rand = Random(0, static_cast(set.size())); - auto it = set.begin(); - for (uint32_t i = 0; i < rand; i++) { - it++; - } - auto test = *it; - return *it; + return ShipUtils::RandomElementFromSet(set, &rando_state); } // Shuffle items within a vector or array diff --git a/soh/soh/ShipUtils.cpp b/soh/soh/ShipUtils.cpp index 1e914d03c..9fd4d1be6 100644 --- a/soh/soh/ShipUtils.cpp +++ b/soh/soh/ShipUtils.cpp @@ -1,5 +1,6 @@ #include "ShipUtils.h" #include +#include #include "soh_assets.h" extern "C" { @@ -96,3 +97,60 @@ extern "C" void* Ship_GetCharFontTexture(u8 character) { return (void*)fontTbl[adjustedChar]; } + +static bool rand_init = false; +uint64_t default_state = 0; +const uint64_t multiplier = 6364136223846793005ULL; +const uint64_t increment = 11634580027462260723ULL; + +// Initialize with seed specified +void ShipUtils::RandInit(uint64_t seed, uint64_t* state) { + rand_init = true; + if (state == nullptr) { + state = &default_state; + } + *state = seed; +} + +uint32_t ShipUtils::next32(uint64_t* state) { + if (state == nullptr) { + state = &default_state; + } + + if (!rand_init) { + // No seed given, get a random number from device to seed +#if !defined(__SWITCH__) && !defined(__WIIU__) + uint64_t seed = static_cast(std::random_device{}()); +#else + uint64_t seed = static_cast(rand()); +#endif + ShipUtils::RandInit(seed, state); + } + + *state = *state * multiplier + increment; + uint32_t xorshifted = static_cast(((*state >> 18) ^ *state) >> 27); + uint32_t rot = static_cast(*state >> 59); + return std::rotr(xorshifted, rot); +} + +// Returns a random integer in range [min, max-1] +uint32_t ShipUtils::Random(uint32_t min, uint32_t max, uint64_t* state) { + if (min == max) { + return min; + } + assert(max > min); + + uint32_t n = max - min; + uint32_t cutoff = UINT32_MAX - UINT32_MAX % static_cast(n); + for (;;) { + uint32_t r = next32(state); + if (r <= cutoff) { + return min + r % n; + } + } +} + +// Returns a random floating point number in [0.0, 1.0) +double ShipUtils::RandomDouble(uint64_t* state) { + return ldexp(next32(state), -32); +} diff --git a/soh/soh/ShipUtils.h b/soh/soh/ShipUtils.h index 85fc89cc5..87e72e3cd 100644 --- a/soh/soh/ShipUtils.h +++ b/soh/soh/ShipUtils.h @@ -26,6 +26,53 @@ void* Ship_GetCharFontTexture(u8 character); #ifdef __cplusplus } + +namespace ShipUtils { +void RandInit(uint64_t seed, uint64_t* state = nullptr); +uint32_t next32(uint64_t* state = nullptr); +uint32_t Random(uint32_t min, uint32_t max, uint64_t* state = nullptr); +double RandomDouble(uint64_t* state = nullptr); + +// Get a random element from a vector or array +template T RandomElement(std::vector& vector, bool erase, uint64_t* state = nullptr) { + const auto idx = Random(0, static_cast(vector.size()), state); + const T selected = vector[idx]; + if (erase) { + vector.erase(vector.begin() + idx); + } + return selected; +} +template auto& RandomElement(Container& container, uint64_t* state = nullptr) { + return container[Random(0, static_cast(std::size(container)), state)]; +} +template const auto& RandomElement(const Container& container, uint64_t* state = nullptr) { + return container[Random(0, static_cast(std::size(container)), state)]; +} + +template const T RandomElementFromSet(const std::set& set, uint64_t* state = nullptr) { + if (set.size() == 1) { + return *set.begin(); + } + uint32_t rand = Random(0, static_cast(set.size()), state); + auto it = set.begin(); + for (uint32_t i = 0; i < rand; i++) { + it++; + } + auto test = *it; + return *it; +} + +template void Shuffle(std::vector& vector, uint64_t* state = nullptr) { + for (size_t i = 0; i + 1 < vector.size(); i++) { + std::swap(vector[i], vector[Random(static_cast(i), static_cast(vector.size()), state)]); + } +} +template void Shuffle(std::array& arr, uint64_t* state = nullptr) { + for (size_t i = 0; i + 1 < arr.size(); i++) { + std::swap(arr[i], arr[Random(static_cast(i), static_cast(arr.size()), state)]); + } +} +} // namespace ShipUtils #endif #endif // SHIP_UTILS_H diff --git a/soh/soh/SohGui/UIWidgets.cpp b/soh/soh/SohGui/UIWidgets.cpp index 9a0547352..3fedea85d 100644 --- a/soh/soh/SohGui/UIWidgets.cpp +++ b/soh/soh/SohGui/UIWidgets.cpp @@ -1154,34 +1154,19 @@ void DrawFlagArray8Mask(const std::string& name, uint8_t& flags, Colors color) { } // namespace UIWidgets ImVec4 GetRandomValue() { -#if !defined(__SWITCH__) && !defined(__WIIU__) - std::random_device rd; - std::mt19937 rng(rd()); -#else - size_t seed = std::hash{}(std::to_string(rand())); - std::mt19937_64 rng(seed); -#endif - std::uniform_int_distribution dist(0, 255 - 1); - ImVec4 NewColor; - NewColor.x = (float)(dist(rng)) / 255.0f; - NewColor.y = (float)(dist(rng)) / 255.0f; - NewColor.z = (float)(dist(rng)) / 255.0f; + NewColor.x = (float)ShipUtils::RandomDouble(); + NewColor.y = (float)ShipUtils::RandomDouble(); + NewColor.z = (float)ShipUtils::RandomDouble(); return NewColor; } -ImVec4 GetRandomValue(uint32_t seed) { -#if !defined(__SWITCH__) && !defined(__WIIU__) - std::mt19937 rng(seed); -#else - std::mt19937_64 rng(seed); -#endif - std::uniform_int_distribution dist(0, 255 - 1); - +ImVec4 GetRandomValue(uint32_t seed, uint64_t* state) { + ShipUtils::RandInit(seed, state); ImVec4 NewColor; - NewColor.x = (float)(dist(rng)) / 255.0f; - NewColor.y = (float)(dist(rng)) / 255.0f; - NewColor.z = (float)(dist(rng)) / 255.0f; + NewColor.x = (float)ShipUtils::RandomDouble(state); + NewColor.y = (float)ShipUtils::RandomDouble(state); + NewColor.z = (float)ShipUtils::RandomDouble(state); return NewColor; } diff --git a/soh/soh/SohGui/UIWidgets.hpp b/soh/soh/SohGui/UIWidgets.hpp index 8ff022870..dc116b95b 100644 --- a/soh/soh/SohGui/UIWidgets.hpp +++ b/soh/soh/SohGui/UIWidgets.hpp @@ -1052,7 +1052,7 @@ void InsertHelpHoverText(const char* text); } // namespace UIWidgets ImVec4 GetRandomValue(); -ImVec4 GetRandomValue(uint32_t seed); +ImVec4 GetRandomValue(uint32_t seed, uint64_t* state = nullptr); Color_RGBA8 RGBA8FromVec(ImVec4 vec); ImVec4 VecFromRGBA8(Color_RGBA8 color);