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.
This commit is contained in:
Christopher Leggett 2026-01-09 14:10:12 +00:00 committed by GitHub
parent 9ca5ce0b53
commit ba0ecc59aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 133 additions and 76 deletions

View file

@ -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<u16> values;
@ -118,7 +120,7 @@ void RandomizeGroup(SeqType type, bool manual = true) {
uint32_t finalSeed = type + (IS_RANDO ? Rando::Context::GetInstance()->GetSeed()
: static_cast<uint32_t>(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);

View file

@ -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<uint32_t>(gSaveContext.ship.stats.fileCreatedAt));
randomColor = GetRandomValue(finalSeed);
randomColor = GetRandomValue(finalSeed, &seeded_cosmetics_state);
} else {
randomColor = GetRandomValue();
}

View file

@ -5,51 +5,25 @@
#include <cassert>
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<uint64_t>(std::random_device{}());
#else
uint64_t seed = static_cast<uint64_t>(std::hash<std::string>{}(std::to_string(rand())));
#endif
Random_Init(seed);
}
state = state * multiplier + increment;
uint32_t xorshifted = static_cast<uint32_t>(((state >> 18) ^ state) >> 27);
uint32_t rot = static_cast<int>(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<uint32_t>(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);
}

View file

@ -1,5 +1,6 @@
#pragma once
#include "soh/ShipUtils.h"
#include <array>
#include <cstddef>
#include <cstdint>
@ -7,37 +8,25 @@
#include <vector>
#include <set>
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 <typename T> T RandomElement(std::vector<T>& vector, bool erase) {
const auto idx = Random(0, static_cast<uint32_t>(vector.size()));
const T selected = vector[idx];
if (erase) {
vector.erase(vector.begin() + idx);
}
return selected;
return ShipUtils::RandomElement(vector, erase, &rando_state);
}
template <typename Container> auto& RandomElement(Container& container) {
return container[Random(0, static_cast<uint32_t>(std::size(container)))];
return ShipUtils::RandomElement(container, &rando_state);
}
template <typename Container> const auto& RandomElement(const Container& container) {
return container[Random(0, static_cast<uint32_t>(std::size(container)))];
return ShipUtils::RandomElement(container, &rando_state);
}
template <typename T> const T RandomElementFromSet(const std::set<T>& set) {
if (set.size() == 1) {
return *set.begin();
}
uint32_t rand = Random(0, static_cast<uint32_t>(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

View file

@ -1,5 +1,6 @@
#include "ShipUtils.h"
#include <libultraship/libultraship.h>
#include <random>
#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<uint64_t>(std::random_device{}());
#else
uint64_t seed = static_cast<uint64_t>(rand());
#endif
ShipUtils::RandInit(seed, state);
}
*state = *state * multiplier + increment;
uint32_t xorshifted = static_cast<uint32_t>(((*state >> 18) ^ *state) >> 27);
uint32_t rot = static_cast<int>(*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<uint32_t>(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);
}

View file

@ -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 <typename T> T RandomElement(std::vector<T>& vector, bool erase, uint64_t* state = nullptr) {
const auto idx = Random(0, static_cast<uint32_t>(vector.size()), state);
const T selected = vector[idx];
if (erase) {
vector.erase(vector.begin() + idx);
}
return selected;
}
template <typename Container> auto& RandomElement(Container& container, uint64_t* state = nullptr) {
return container[Random(0, static_cast<uint32_t>(std::size(container)), state)];
}
template <typename Container> const auto& RandomElement(const Container& container, uint64_t* state = nullptr) {
return container[Random(0, static_cast<uint32_t>(std::size(container)), state)];
}
template <typename T> const T RandomElementFromSet(const std::set<T>& set, uint64_t* state = nullptr) {
if (set.size() == 1) {
return *set.begin();
}
uint32_t rand = Random(0, static_cast<uint32_t>(set.size()), state);
auto it = set.begin();
for (uint32_t i = 0; i < rand; i++) {
it++;
}
auto test = *it;
return *it;
}
template <typename T> void Shuffle(std::vector<T>& vector, uint64_t* state = nullptr) {
for (size_t i = 0; i + 1 < vector.size(); i++) {
std::swap(vector[i], vector[Random(static_cast<uint32_t>(i), static_cast<uint32_t>(vector.size()), state)]);
}
}
template <typename T, size_t size> void Shuffle(std::array<T, size>& arr, uint64_t* state = nullptr) {
for (size_t i = 0; i + 1 < arr.size(); i++) {
std::swap(arr[i], arr[Random(static_cast<uint32_t>(i), static_cast<uint32_t>(arr.size()), state)]);
}
}
} // namespace ShipUtils
#endif
#endif // SHIP_UTILS_H

View file

@ -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::string>{}(std::to_string(rand()));
std::mt19937_64 rng(seed);
#endif
std::uniform_int_distribution<int> 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<int> 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;
}

View file

@ -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);