From fd4415317d47dd937679cb218f7061cbaaca08c5 Mon Sep 17 00:00:00 2001 From: Misa Date: Sat, 21 Aug 2021 19:51:19 -0700 Subject: [PATCH] Replace Gravitron RNG with seeded Xoshiro This is to make it so RNG is deterministic when played back with the same inputs in a libTAS movie even if screen effects or backgrounds are disabled. That way, Gravitron RNG is on its own system (seeded in hardreset()), separate from the constant fRandom() calls that go to visual systems and don't do anything of actual consequence. The seed is based off of SDL_GetTicks(), so RTA runners don't get the same Gravitron RNG every time. This also paves the way for a future in-built input-based recording system, which now only has to save the seed for a given recording in order for it to play back deterministically. --- desktop_version/CMakeLists.txt | 1 + desktop_version/src/Entity.cpp | 27 ++++++++------- desktop_version/src/Script.cpp | 4 +++ desktop_version/src/Xoshiro.c | 62 ++++++++++++++++++++++++++++++++++ desktop_version/src/Xoshiro.h | 21 ++++++++++++ 5 files changed, 102 insertions(+), 13 deletions(-) create mode 100644 desktop_version/src/Xoshiro.c create mode 100644 desktop_version/src/Xoshiro.h diff --git a/desktop_version/CMakeLists.txt b/desktop_version/CMakeLists.txt index c68f9715..4f56179d 100644 --- a/desktop_version/CMakeLists.txt +++ b/desktop_version/CMakeLists.txt @@ -112,6 +112,7 @@ set(VVV_SRC src/GlitchrunnerMode.c src/Network.c src/ThirdPartyDeps.c + src/Xoshiro.c ) if(NOT CUSTOM_LEVEL_SUPPORT STREQUAL "DISABLED") list(APPEND VVV_SRC src/editor.cpp) diff --git a/desktop_version/src/Entity.cpp b/desktop_version/src/Entity.cpp index efa6c3cc..e334fec9 100644 --- a/desktop_version/src/Entity.cpp +++ b/desktop_version/src/Entity.cpp @@ -11,6 +11,7 @@ #include "Music.h" #include "Script.h" #include "UtilityClass.h" +#include "Xoshiro.h" bool entityclass::checktowerspikes(int t) { @@ -210,7 +211,7 @@ void entityclass::generateswnwave( int t ) if (game.deathcounts - game.swndeaths > 25) game.swndelay += 4; break; case 1: - createentity(-150, 58 + (int(fRandom() * 6) * 20), 23, 0, 0); + createentity(-150, 58 + (int(xoshiro_rand() * 6) * 20), 23, 0, 0); game.swnstate = 0; game.swndelay = 0; //return to decision state break; @@ -238,13 +239,13 @@ void entityclass::generateswnwave( int t ) game.swndelay = 0; //return to decision state break; case 3: - createentity(320+150, 58 + (int(fRandom() * 6) * 20), 23, 1, 0); + createentity(320+150, 58 + (int(xoshiro_rand() * 6) * 20), 23, 1, 0); game.swnstate = 0; game.swndelay = 0; //return to decision state break; case 4: //left and right compliments - game.swnstate2 = int(fRandom() * 6); + game.swnstate2 = int(xoshiro_rand() * 6); createentity(-150, 58 + (game.swnstate2 * 20), 23, 0, 0); createentity(320+150, 58 + ((5-game.swnstate2) * 20), 23, 1, 0); game.swnstate = 0; @@ -319,7 +320,7 @@ void entityclass::generateswnwave( int t ) game.swnstate3 = 0; game.swnstate4 = 0; - game.swnstate2 = int(fRandom() * 100); + game.swnstate2 = int(xoshiro_rand() * 100); if (game.swnstate2 < 25) { //simple @@ -336,7 +337,7 @@ void entityclass::generateswnwave( int t ) break; case 1: //complex chain - game.swnstate2 = int(fRandom() * 8); + game.swnstate2 = int(xoshiro_rand() * 8); if (game.swnstate2 == 0) { game.swnstate = 10; @@ -382,7 +383,7 @@ void entityclass::generateswnwave( int t ) break; case 2: //simple chain - game.swnstate2 = int(fRandom() * 6); + game.swnstate2 = int(xoshiro_rand() * 6); if (game.swnstate2 == 0) { game.swnstate = 23; @@ -418,7 +419,7 @@ void entityclass::generateswnwave( int t ) break; case 3: //Choose a major action - game.swnstate2 = int(fRandom() * 100); + game.swnstate2 = int(xoshiro_rand() * 100); game.swnstate4 = 0; if (game.swnstate2 < 25) { @@ -435,7 +436,7 @@ void entityclass::generateswnwave( int t ) break; case 4: //filler chain - game.swnstate2 = int(fRandom() * 6); + game.swnstate2 = int(xoshiro_rand() * 6); if (game.swnstate2 == 0) { game.swnstate = 28; @@ -624,7 +625,7 @@ void entityclass::generateswnwave( int t ) case 22: game.swnstate4++; //left and right compliments - game.swnstate2 = int(fRandom() * 6); + game.swnstate2 = int(xoshiro_rand() * 6); createentity(-150, 58 + (game.swnstate2 * 20), 23, 0, 0); createentity(320 + 150, 58 + ((5 - game.swnstate2) * 20), 23, 1, 0); if(game.swnstate4<=12) @@ -685,7 +686,7 @@ void entityclass::generateswnwave( int t ) break; case 28: game.swnstate4++; - game.swnstate2 = int(fRandom() * 6); + game.swnstate2 = int(xoshiro_rand() * 6); createentity(-150, 58 + (game.swnstate2 * 20), 23, 0, 0); if(game.swnstate4<=6) { @@ -701,7 +702,7 @@ void entityclass::generateswnwave( int t ) break; case 29: game.swnstate4++; - game.swnstate2 = int(fRandom() * 6); + game.swnstate2 = int(xoshiro_rand() * 6); gravcreate(game.swnstate2, 1); if(game.swnstate4<=6) { @@ -717,7 +718,7 @@ void entityclass::generateswnwave( int t ) break; case 30: game.swnstate4++; - game.swnstate2 = int(fRandom() * 3); + game.swnstate2 = int(xoshiro_rand() * 3); gravcreate(game.swnstate2, 0); gravcreate(5-game.swnstate2, 0); if(game.swnstate4<=2) @@ -734,7 +735,7 @@ void entityclass::generateswnwave( int t ) break; case 31: game.swnstate4++; - game.swnstate2 = int(fRandom() * 3); + game.swnstate2 = int(xoshiro_rand() * 3); gravcreate(game.swnstate2, 1); gravcreate(5-game.swnstate2, 1); if(game.swnstate4<=2) diff --git a/desktop_version/src/Script.cpp b/desktop_version/src/Script.cpp index 48ce1f16..88634e34 100644 --- a/desktop_version/src/Script.cpp +++ b/desktop_version/src/Script.cpp @@ -2,6 +2,7 @@ #include "Script.h" #include +#include #include "editor.h" #include "Entity.h" @@ -13,6 +14,7 @@ #include "Map.h" #include "Music.h" #include "UtilityClass.h" +#include "Xoshiro.h" scriptclass::scriptclass(void) { @@ -3377,6 +3379,8 @@ void scriptclass::hardreset(void) { const bool version2_2 = GlitchrunnerMode_less_than_or_equal(Glitchrunner2_2); + xoshiro_seed(SDL_GetTicks()); + //Game: game.hascontrol = true; game.gravitycontrol = 0; diff --git a/desktop_version/src/Xoshiro.c b/desktop_version/src/Xoshiro.c new file mode 100644 index 00000000..29335006 --- /dev/null +++ b/desktop_version/src/Xoshiro.c @@ -0,0 +1,62 @@ +#include + +/* Implements the xoshiro128+ PRNG. */ + +static uint32_t rotl(const uint32_t x, const int k) +{ + return (x << k) | (x >> (32 - k)); +} + +static uint32_t s[4]; + +static uint32_t splitmix32(uint32_t* x) +{ + uint32_t z = (*x += 0x9e3779b9UL); + z = (z ^ (z >> 15)) * 0xbf58476dUL; + z = (z ^ (z >> 13)) * 0x94d049bbUL; + return z ^ (z >> 16); +} + +static void seed( + const uint32_t s0, + const uint32_t s1, + const uint32_t s2, + const uint32_t s3 +) { + s[0] = s0; + s[1] = s1; + s[2] = s2; + s[3] = s3; +} + +uint32_t xoshiro_next(void) +{ + const uint32_t result = s[0] + s[3]; + + const uint32_t t = s[1] << 9; + + s[2] ^= s[0]; + s[3] ^= s[1]; + s[1] ^= s[2]; + s[0] ^= s[3]; + + s[2] ^= t; + + s[3] = rotl(s[3], 11); + + return result; +} + +void xoshiro_seed(uint32_t s) +{ + const uint32_t s0 = splitmix32(&s); + const uint32_t s1 = splitmix32(&s); + const uint32_t s2 = splitmix32(&s); + const uint32_t s3 = splitmix32(&s); + seed(s0, s1, s2, s3); +} + +float xoshiro_rand(void) +{ + return ((float) xoshiro_next()) / ((float) UINT32_MAX); +} diff --git a/desktop_version/src/Xoshiro.h b/desktop_version/src/Xoshiro.h new file mode 100644 index 00000000..235643b1 --- /dev/null +++ b/desktop_version/src/Xoshiro.h @@ -0,0 +1,21 @@ +#ifndef XOSHIRO_H +#define XOSHIRO_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +void xoshiro_seed(uint32_t s); + +uint32_t xoshiro_next(void); + +float xoshiro_rand(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* XOSHIRO_H */