From 2bf775febda59c7da46283d387e0d6803fd91901 Mon Sep 17 00:00:00 2001 From: tertu marybig Date: Wed, 20 Dec 2023 13:56:20 -0600 Subject: [PATCH 01/18] High-quality RNG, behind a flag. HQ_RANDOM enabled by default for testing purposes. --- include/battle.h | 13 +++ include/contest.h | 3 +- include/random.h | 80 +++++++++++++++++-- include/recorded_battle.h | 7 +- src/battle_controller_link_opponent.c | 1 + src/battle_controller_link_partner.c | 1 + src/battle_main.c | 9 ++- src/contest.c | 5 +- src/contest_util.c | 6 +- src/main.c | 2 +- src/random.c | 111 ++++++++++++++++++++++++-- src/recorded_battle.c | 4 +- 12 files changed, 213 insertions(+), 29 deletions(-) diff --git a/include/battle.h b/include/battle.h index e78434578813..156b84099789 100644 --- a/include/battle.h +++ b/include/battle.h @@ -15,6 +15,7 @@ #include "pokeball.h" #include "battle_debug.h" #include "battle_dynamax.h" +#include "random.h" // for HQ_RANDOM // Used to exclude moves learned temporarily by Transform or Mimic #define MOVE_IS_PERMANENT(battler, moveSlot) \ @@ -579,6 +580,13 @@ struct LostItem u16 stolen:1; }; +#if HQ_RANDOM == 1 +struct BattleVideo { + u32 battleTypeFlags; + rng_value_t rngSeed; +}; +#endif + struct BattleStruct { u8 turnEffectsTracker; @@ -650,7 +658,12 @@ struct BattleStruct u16 lastTakenMoveFrom[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT]; // a 2-D array [target][attacker] union { struct LinkBattlerHeader linkBattlerHeader; + + #if HQ_RANDOM == 0 u32 battleVideo[2]; + #else + struct BattleVideo battleVideo; + #endif } multiBuffer; u8 wishPerishSongState; u8 wishPerishSongBattlerId; diff --git a/include/contest.h b/include/contest.h index 21e2530c2114..7a67fecd55a7 100644 --- a/include/contest.h +++ b/include/contest.h @@ -3,6 +3,7 @@ #include "palette.h" #include "constants/contest.h" +#include "random.h" // for rng_value_t enum { @@ -327,7 +328,7 @@ extern struct ContestResources *gContestResources; extern struct ContestWinner gCurContestWinner; extern u8 gCurContestWinnerIsForArtist; extern u8 gCurContestWinnerSaveIdx; -extern u32 gContestRngValue; +extern rng_value_t gContestRngValue; // contest.c void ResetLinkContestBoolean(void); diff --git a/include/random.h b/include/random.h index c3d6067efaa1..0d3cf710698f 100644 --- a/include/random.h +++ b/include/random.h @@ -1,8 +1,64 @@ #ifndef GUARD_RANDOM_H #define GUARD_RANDOM_H -extern u32 gRngValue; -extern u32 gRng2Value; +// The number 1103515245 comes from the example implementation of rand and srand +// in the ISO C standard. +#define ISO_RANDOMIZE1(val)(1103515245 * (val) + 24691) +#define ISO_RANDOMIZE2(val)(1103515245 * (val) + 12345) + +// If 1, use a high-quality PRNG. +// This may break existing projects. +#define HQ_RANDOM 1 + +/* Notes about new functions: +* If using HQ_RANDOM, you MUST call AdvanceRandom() in VBlank handlers. +* If you do not, you risk corruption of the RNG state. +* LocalRandom(*val) adapts to either choice of RNG implementation. +* Other new functions should be self-explanatory. +*/ + +#if HQ_RANDOM == 1 +struct Sfc32State { + u32 a; + u32 b; + u32 c; + u32 ctr; +}; + +typedef struct Sfc32State rng_value_t; + +// Calling this function directly is discouraged. +// Use LocalRandom() instead. +static inline u32 _SFC32_Next(struct Sfc32State *state) +{ + const u32 result = state->a + state->b + state->ctr++; + state->a = state->b ^ (state->b >> 9); + state->b = state->c * 9; + state->c = result + ((state->c << 21) | (state->c >> 11)); + return result; +} + +static inline u16 LocalRandom(rng_value_t *val) +{ + return _SFC32_Next(val) >> 16; +} + +u32 Random32(void); +u32 Random2_32(void); + +static inline u16 Random(void) +{ + return Random32() >> 16; +} + +static inline u16 Random2(void) +{ + return Random2_32() >> 16; +} + +void AdvanceRandom(void); +#else +typedef u32 rng_value_t; //Returns a 16-bit pseudorandom number u16 Random(void); @@ -10,11 +66,23 @@ u16 Random2(void); //Returns a 32-bit pseudorandom number #define Random32() (Random() | (Random() << 16)) +#define Random2_32() (Random2() | (Random2() << 16)) -// The number 1103515245 comes from the example implementation of rand and srand -// in the ISO C standard. -#define ISO_RANDOMIZE1(val)(1103515245 * (val) + 24691) -#define ISO_RANDOMIZE2(val)(1103515245 * (val) + 12345) +static inline u16 LocalRandom(rng_value_t *val) +{ + *val = ISO_RANDOMIZE1(*val); + return *val >> 16; +} + +static inline void AdvanceRandom(void) +{ + Random(); +} + +#endif + +extern rng_value_t gRngValue; +extern rng_value_t gRng2Value; //Sets the initial seed value of the pseudorandom number generator void SeedRng(u16 seed); diff --git a/include/recorded_battle.h b/include/recorded_battle.h index fcbe9495fd86..5d3f47d14014 100644 --- a/include/recorded_battle.h +++ b/include/recorded_battle.h @@ -2,6 +2,7 @@ #define GUARD_RECORDED_BATTLE_H #include "constants/battle.h" +#include "random.h" // for rng_value_t #define BATTLER_RECORD_SIZE 664 @@ -13,7 +14,7 @@ struct RecordedBattleSave u8 playersGender[MAX_BATTLERS_COUNT]; u32 playersTrainerId[MAX_BATTLERS_COUNT]; u8 playersLanguage[MAX_BATTLERS_COUNT]; - u32 rngSeed; + rng_value_t rngSeed; u32 battleFlags; u8 playersBattlers[MAX_BATTLERS_COUNT]; u16 opponentA; @@ -49,8 +50,8 @@ enum RECORDED_ITEM_MOVE, }; -extern u32 gRecordedBattleRngSeed; -extern u32 gBattlePalaceMoveSelectionRngValue; +extern rng_value_t gRecordedBattleRngSeed; +extern rng_value_t gBattlePalaceMoveSelectionRngValue; extern u8 gRecordedBattleMultiplayerId; #define B_RECORD_MODE_RECORDING 1 diff --git a/src/battle_controller_link_opponent.c b/src/battle_controller_link_opponent.c index 3a3cb7e8ce33..f01f5da1e353 100644 --- a/src/battle_controller_link_opponent.c +++ b/src/battle_controller_link_opponent.c @@ -28,6 +28,7 @@ #include "constants/songs.h" #include "constants/trainers.h" #include "recorded_battle.h" +#include "random.h" // for rng_value_t static void LinkOpponentHandleLoadMonSprite(u32 battler); static void LinkOpponentHandleSwitchInAnim(u32 battler); diff --git a/src/battle_controller_link_partner.c b/src/battle_controller_link_partner.c index 4b8daeb1e1fd..b0256874374a 100644 --- a/src/battle_controller_link_partner.c +++ b/src/battle_controller_link_partner.c @@ -28,6 +28,7 @@ #include "constants/songs.h" #include "constants/trainers.h" #include "recorded_battle.h" +#include "random.h" // for rng_value_t static void LinkPartnerHandleLoadMonSprite(u32 battler); static void LinkPartnerHandleSwitchInAnim(u32 battler); diff --git a/src/battle_main.c b/src/battle_main.c index 0f708d41ee59..f9bdb7595a6c 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -1712,9 +1712,16 @@ static void CB2_HandleStartMultiBattle(void) case 8: if (IsLinkTaskFinished()) { + #if HQ_RANDOM == 1 + struct BattleVideo *ptr = &gBattleStruct->multiBuffer.battleVideo; + ptr->battleTypeFlags = gBattleTypeFlags; + ptr->rngSeed = gRecordedBattleRngSeed; + #else u32 *ptr = gBattleStruct->multiBuffer.battleVideo; ptr[0] = gBattleTypeFlags; ptr[1] = gRecordedBattleRngSeed; // UB: overwrites berry data + #endif + SendBlock(BitmaskAllOtherLinkPlayers(), ptr, sizeof(gBattleStruct->multiBuffer.battleVideo)); gBattleCommunication[MULTIUSE_STATE]++; } @@ -2052,7 +2059,7 @@ void VBlankCB_Battle(void) { // Change gRngSeed every vblank unless the battle could be recorded. if (!(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_FRONTIER | BATTLE_TYPE_RECORDED))) - Random(); + AdvanceRandom(); SetGpuReg(REG_OFFSET_BG0HOFS, gBattle_BG0_X); SetGpuReg(REG_OFFSET_BG0VOFS, gBattle_BG0_Y); diff --git a/src/contest.c b/src/contest.c index 8f452b092168..d4cc73677cc4 100644 --- a/src/contest.c +++ b/src/contest.c @@ -358,7 +358,7 @@ EWRAM_DATA bool8 gCurContestWinnerIsForArtist = 0; EWRAM_DATA u8 gCurContestWinnerSaveIdx = 0; // IWRAM common vars. -u32 gContestRngValue; +rng_value_t gContestRngValue; extern const u8 gText_LinkStandby4[]; extern const u8 gText_BDot[]; @@ -1709,7 +1709,7 @@ static void Task_AppealSetup(u8 taskId) if (++gTasks[taskId].data[0] > 19) { eContest.turnNumber = 0; - eContest.unusedRng = gRngValue; + eContest.unusedRng = 0; if ((gLinkContestFlags & LINK_CONTEST_FLAG_IS_LINK) && IsPlayerLinkLeader()) { s32 i; @@ -6109,4 +6109,3 @@ void StripPlayerAndMonNamesForLinkContest(struct ContestPokemon *mon, s32 langua name[PLAYER_NAME_LENGTH] = EOS; } } - diff --git a/src/contest_util.c b/src/contest_util.c index 3bd1c2829357..32588a8c00dd 100644 --- a/src/contest_util.c +++ b/src/contest_util.c @@ -2655,8 +2655,7 @@ void GenerateContestRand(void) if (gLinkContestFlags & LINK_CONTEST_FLAG_IS_LINK) { - gContestRngValue = ISO_RANDOMIZE1(gContestRngValue); - random = gContestRngValue >> 16; + random = LocalRandom(&gContestRngValue); result = &gSpecialVar_Result; } else @@ -2669,8 +2668,7 @@ void GenerateContestRand(void) u16 GetContestRand(void) { - gContestRngValue = ISO_RANDOMIZE1(gContestRngValue); - return gContestRngValue >> 16; + return LocalRandom(&gContestRngValue); } bool8 LinkContestWaitForConnection(void) diff --git a/src/main.c b/src/main.c index d850bc27bc9c..537af7191b02 100644 --- a/src/main.c +++ b/src/main.c @@ -373,7 +373,7 @@ static void VBlankIntr(void) TryReceiveLinkBattleData(); if (!gMain.inBattle || !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_FRONTIER | BATTLE_TYPE_RECORDED))) - Random(); + AdvanceRandom(); UpdateWirelessStatusIndicatorSprite(); diff --git a/src/random.c b/src/random.c index 6a462004b4c3..54c61db99eee 100644 --- a/src/random.c +++ b/src/random.c @@ -4,13 +4,93 @@ #include #endif +// IWRAM common +rng_value_t gRngValue; +rng_value_t gRng2Value; + +#if HQ_RANDOM == 1 +EWRAM_DATA static volatile u8 sUnknown = 11; +#define RNG_LOCK sUnknown + + +static void SFC32_Seed(struct Sfc32State *state, u16 seed) +{ + u32 i; + state->a = state->b = 0; + state->c = seed; + state->ctr = 1; + for(i = 0; i < 16; i++) + { + _SFC32_Next(state); + } +} + +u32 NAKED Random32(void) +{ + asm(".thumb\n\ + push {r4, r5, r6, r7}\n\ + ldr r7, =sUnknown\n\ + mov r6, #11\n\ + strb r6, [r7]\n\ + ldr r5, =gRngValue\n\ + ldmia r5!, {r1, r2, r3, r4}\n\ + @ e (result) = a + b + d++\n\ + add r1, r1, r2\n\ + add r0, r1, r4\n\ + add r4, r4, #1\n\ + @ a = b ^ (b >> 9)\n\ + lsr r1, r2, #9\n\ + eor r1, r1, r2\n\ + @ b = c + (c << 3) [c * 9]\n\ + lsl r2, r3, #3\n\ + add r2, r2, r3\n\ + @ c = rol(c, 21) + e\n\ + ror r3, r3, r6\n\ + add r3, r3, r0\n\ + sub r5, r5, #16\n\ + stmia r5!, {r1, r2, r3, r4}\n\ + mov r1, #0\n\ + strb r1, [r7]\n\ + pop {r4, r5, r6, r7}\n\ + bx lr\n\ + .ltorg" + ); +} + +u32 Random2_32(void) +{ + return _SFC32_Next(&gRng2Value); +} + +void SeedRng(u16 seed) +{ + SFC32_Seed(&gRngValue, seed); + RNG_LOCK = 0; +} + +void SeedRng2(u16 seed) +{ + SFC32_Seed(&gRng2Value, seed); +} + +void AdvanceRandom(void) +{ + if (RNG_LOCK == 0) + _SFC32_Next(&gRngValue); +} + +#define LOOP_RANDOM_START \ + struct Sfc32State *const state = &gRngValue; \ + RNG_LOCK = 11 + +#define LOOP_RANDOM_END RNG_LOCK = 0 + +#define LOOP_RANDOM ((u16)(_SFC32_Next(state) >> 16)) + +#else EWRAM_DATA static u8 sUnknown = 0; EWRAM_DATA static u32 sRandCount = 0; -// IWRAM common -u32 gRngValue; -u32 gRng2Value; - u16 Random(void) { gRngValue = ISO_RANDOMIZE1(gRngValue); @@ -35,15 +115,24 @@ u16 Random2(void) return gRng2Value >> 16; } +#define LOOP_RANDOM_START +#define LOOP_RANDOM_END + +#define LOOP_RANDOM (Random()) + +#endif + #define SHUFFLE_IMPL \ u32 tmp; \ + LOOP_RANDOM_START; \ --n; \ while (n > 1) \ { \ - int j = (Random() * (n+1)) >> 16; \ + int j = (LOOP_RANDOM * (n+1)) >> 16; \ SWAP(data[n], data[j], tmp); \ --n; \ - } + } \ + LOOP_RANDOM_END void Shuffle8(void *data_, size_t n) { @@ -66,15 +155,19 @@ void Shuffle32(void *data_, size_t n) void ShuffleN(void *data, size_t n, size_t size) { void *tmp = alloca(size); + LOOP_RANDOM_START; --n; + while (n > 1) { - int j = (Random() * (n+1)) >> 16; + int j = (LOOP_RANDOM * (n+1)) >> 16; memcpy(tmp, (u8 *)data + n*size, size); // tmp = data[n]; memcpy((u8 *)data + n*size, (u8 *)data + j*size, size); // data[n] = data[j]; memcpy((u8 *)data + j*size, tmp, size); // data[j] = tmp; --n; } + + LOOP_RANDOM_END; } __attribute__((weak, alias("RandomUniformDefault"))) @@ -96,12 +189,14 @@ u32 RandomUniformDefault(enum RandomTag tag, u32 lo, u32 hi) u32 RandomUniformExceptDefault(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reject)(u32)) { + LOOP_RANDOM_START; while (TRUE) { - u32 n = RandomUniformDefault(tag, lo, hi); + u32 n = lo + (((hi - lo + 1) * LOOP_RANDOM) >> 16); if (!reject(n)) return n; } + LOOP_RANDOM_END; } u32 RandomWeightedArrayDefault(enum RandomTag tag, u32 sum, u32 n, const u8 *weights) diff --git a/src/recorded_battle.c b/src/recorded_battle.c index 4d36ce12b275..bf66e166f63f 100644 --- a/src/recorded_battle.c +++ b/src/recorded_battle.c @@ -33,8 +33,8 @@ struct PlayerInfo // Save data using TryWriteSpecialSaveSector is allowed to exceed SECTOR_DATA_SIZE (up to the counter field) STATIC_ASSERT(sizeof(struct RecordedBattleSave) <= SECTOR_COUNTER_OFFSET, RecordedBattleSaveFreeSpace); -EWRAM_DATA u32 gRecordedBattleRngSeed = 0; -EWRAM_DATA u32 gBattlePalaceMoveSelectionRngValue = 0; +EWRAM_DATA rng_value_t gRecordedBattleRngSeed; +EWRAM_DATA rng_value_t gBattlePalaceMoveSelectionRngValue; EWRAM_DATA static u8 sBattleRecords[MAX_BATTLERS_COUNT][BATTLER_RECORD_SIZE] = {0}; EWRAM_DATA static u16 sBattlerRecordSizes[MAX_BATTLERS_COUNT] = {0}; EWRAM_DATA static u16 sBattlerPrevRecordSizes[MAX_BATTLERS_COUNT] = {0}; From 99666d42e268af303de1d07dc778f6f29cb711b4 Mon Sep 17 00:00:00 2001 From: tertu marybig Date: Wed, 20 Dec 2023 15:26:33 -0600 Subject: [PATCH 02/18] Empty initialization (fixes agbcc) --- include/random.h | 4 ++++ src/recorded_battle.c | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/include/random.h b/include/random.h index 0d3cf710698f..264ff0e816e5 100644 --- a/include/random.h +++ b/include/random.h @@ -27,6 +27,8 @@ struct Sfc32State { typedef struct Sfc32State rng_value_t; +#define RNG_VALUE_EMPTY {} + // Calling this function directly is discouraged. // Use LocalRandom() instead. static inline u32 _SFC32_Next(struct Sfc32State *state) @@ -60,6 +62,8 @@ void AdvanceRandom(void); #else typedef u32 rng_value_t; +#define RNG_VALUE_EMPTY 0 + //Returns a 16-bit pseudorandom number u16 Random(void); u16 Random2(void); diff --git a/src/recorded_battle.c b/src/recorded_battle.c index bf66e166f63f..1ba62b283712 100644 --- a/src/recorded_battle.c +++ b/src/recorded_battle.c @@ -33,8 +33,8 @@ struct PlayerInfo // Save data using TryWriteSpecialSaveSector is allowed to exceed SECTOR_DATA_SIZE (up to the counter field) STATIC_ASSERT(sizeof(struct RecordedBattleSave) <= SECTOR_COUNTER_OFFSET, RecordedBattleSaveFreeSpace); -EWRAM_DATA rng_value_t gRecordedBattleRngSeed; -EWRAM_DATA rng_value_t gBattlePalaceMoveSelectionRngValue; +EWRAM_DATA rng_value_t gRecordedBattleRngSeed = RNG_VALUE_EMPTY; +EWRAM_DATA rng_value_t gBattlePalaceMoveSelectionRngValue = RNG_VALUE_EMPTY; EWRAM_DATA static u8 sBattleRecords[MAX_BATTLERS_COUNT][BATTLER_RECORD_SIZE] = {0}; EWRAM_DATA static u16 sBattlerRecordSizes[MAX_BATTLERS_COUNT] = {0}; EWRAM_DATA static u16 sBattlerPrevRecordSizes[MAX_BATTLERS_COUNT] = {0}; From f580028de7cf4c78b043ac3cbb551ff478403fa9 Mon Sep 17 00:00:00 2001 From: tertu marybig Date: Wed, 20 Dec 2023 15:26:54 -0600 Subject: [PATCH 03/18] Make things that are similar but not identical to Random32 into Random32 This is probably not all of them --- src/debug.c | 2 +- src/load_save.c | 2 +- src/lottery_corner.c | 2 +- src/time_events.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/debug.c b/src/debug.c index 1ed438598be6..c1853af4cf27 100644 --- a/src/debug.c +++ b/src/debug.c @@ -2144,7 +2144,7 @@ static void DebugAction_Util_Player_Gender(u8 taskId) static void DebugAction_Util_Player_Id(u8 taskId) { - u32 trainerId = ((Random() << 16) | Random()); + u32 trainerId = Random32(); SetTrainerId(trainerId, gSaveBlock2Ptr->playerTrainerId); Debug_DestroyMenu_Full(taskId); ScriptContext_Enable(); diff --git a/src/load_save.c b/src/load_save.c index 4f706180c4e6..d42b71a4a990 100644 --- a/src/load_save.c +++ b/src/load_save.c @@ -127,7 +127,7 @@ void MoveSaveBlocks_ResetHeap(void) gMain.vblankCallback = vblankCB; // create a new encryption key - encryptionKey = (Random() << 16) + (Random()); + encryptionKey = Random32(); ApplyNewEncryptionKeyToAllEncryptedData(encryptionKey); gSaveBlock2Ptr->encryptionKey = encryptionKey; } diff --git a/src/lottery_corner.c b/src/lottery_corner.c index f19e9f7b7b80..b2de9d123c27 100644 --- a/src/lottery_corner.c +++ b/src/lottery_corner.c @@ -25,7 +25,7 @@ void ResetLotteryCorner(void) { u16 rand = Random(); - SetLotteryNumber((Random() << 16) | rand); + SetLotteryNumber(Random32()); VarSet(VAR_POKELOT_PRIZE_ITEM, 0); } diff --git a/src/time_events.c b/src/time_events.c index cec6a44c3415..1c8a12a2375e 100644 --- a/src/time_events.c +++ b/src/time_events.c @@ -25,7 +25,7 @@ static void SetMirageRnd(u32 rnd) // unused void InitMirageRnd(void) { - SetMirageRnd((Random() << 16) | Random()); + SetMirageRnd(Random32()); } void UpdateMirageRnd(u16 days) From ca7e5ddcc6b35da42a0678cf43df8b2c9130e30b Mon Sep 17 00:00:00 2001 From: tertu marybig Date: Wed, 20 Dec 2023 19:04:20 -0600 Subject: [PATCH 04/18] Change HQ_RANDOM formatting per Lunos --- include/battle.h | 6 +++--- include/config.h | 1 + include/random.h | 6 +----- src/battle_main.c | 2 +- src/lottery_corner.c | 2 -- src/random.c | 24 ++++++++++-------------- 6 files changed, 16 insertions(+), 25 deletions(-) diff --git a/include/battle.h b/include/battle.h index 156b84099789..0d6b69f4493e 100644 --- a/include/battle.h +++ b/include/battle.h @@ -15,7 +15,7 @@ #include "pokeball.h" #include "battle_debug.h" #include "battle_dynamax.h" -#include "random.h" // for HQ_RANDOM +#include "random.h" // for rng_value_t // Used to exclude moves learned temporarily by Transform or Mimic #define MOVE_IS_PERMANENT(battler, moveSlot) \ @@ -580,7 +580,7 @@ struct LostItem u16 stolen:1; }; -#if HQ_RANDOM == 1 +#if HQ_RANDOM == TRUE struct BattleVideo { u32 battleTypeFlags; rng_value_t rngSeed; @@ -659,7 +659,7 @@ struct BattleStruct union { struct LinkBattlerHeader linkBattlerHeader; - #if HQ_RANDOM == 0 + #if HQ_RANDOM == FALSE u32 battleVideo[2]; #else struct BattleVideo battleVideo; diff --git a/include/config.h b/include/config.h index 8a6e990a5d59..f4be812643dc 100644 --- a/include/config.h +++ b/include/config.h @@ -79,5 +79,6 @@ #define EXPANSION_INTRO TRUE // If TRUE, a custom RHH intro will play after the vanilla copyright screen. #define POKEDEX_PLUS_HGSS FALSE // If TRUE, enables the custom HGSS style Pokedex. #define SUMMARY_SCREEN_NATURE_COLORS TRUE // If TRUE, nature-based stat boosts and reductions will be red and blue in the summary screen. +#define HQ_RANDOM TRUE // If TRUE, replaces the default RNG with an implementation of SFC32 RNG. May break code that relies on RNG. #endif // GUARD_CONFIG_H diff --git a/include/random.h b/include/random.h index 264ff0e816e5..97e610d4e36b 100644 --- a/include/random.h +++ b/include/random.h @@ -6,10 +6,6 @@ #define ISO_RANDOMIZE1(val)(1103515245 * (val) + 24691) #define ISO_RANDOMIZE2(val)(1103515245 * (val) + 12345) -// If 1, use a high-quality PRNG. -// This may break existing projects. -#define HQ_RANDOM 1 - /* Notes about new functions: * If using HQ_RANDOM, you MUST call AdvanceRandom() in VBlank handlers. * If you do not, you risk corruption of the RNG state. @@ -17,7 +13,7 @@ * Other new functions should be self-explanatory. */ -#if HQ_RANDOM == 1 +#if HQ_RANDOM == TRUE struct Sfc32State { u32 a; u32 b; diff --git a/src/battle_main.c b/src/battle_main.c index f9bdb7595a6c..6fdecdf97764 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -1712,7 +1712,7 @@ static void CB2_HandleStartMultiBattle(void) case 8: if (IsLinkTaskFinished()) { - #if HQ_RANDOM == 1 + #if HQ_RANDOM == TRUE struct BattleVideo *ptr = &gBattleStruct->multiBuffer.battleVideo; ptr->battleTypeFlags = gBattleTypeFlags; ptr->rngSeed = gRecordedBattleRngSeed; diff --git a/src/lottery_corner.c b/src/lottery_corner.c index b2de9d123c27..d06934ca511f 100644 --- a/src/lottery_corner.c +++ b/src/lottery_corner.c @@ -23,8 +23,6 @@ static u8 GetMatchingDigits(u16, u16); void ResetLotteryCorner(void) { - u16 rand = Random(); - SetLotteryNumber(Random32()); VarSet(VAR_POKELOT_PRIZE_ITEM, 0); } diff --git a/src/random.c b/src/random.c index 54c61db99eee..79fecdcf241b 100644 --- a/src/random.c +++ b/src/random.c @@ -8,9 +8,9 @@ rng_value_t gRngValue; rng_value_t gRng2Value; -#if HQ_RANDOM == 1 -EWRAM_DATA static volatile u8 sUnknown = 11; -#define RNG_LOCK sUnknown +#if HQ_RANDOM == TRUE +EWRAM_DATA static volatile u8 sUnknown = 1; +#define RNG_LOOP_LOCK sUnknown static void SFC32_Seed(struct Sfc32State *state, u16 seed) @@ -28,10 +28,8 @@ static void SFC32_Seed(struct Sfc32State *state, u16 seed) u32 NAKED Random32(void) { asm(".thumb\n\ - push {r4, r5, r6, r7}\n\ - ldr r7, =sUnknown\n\ + push {r4, r5, r6}\n\ mov r6, #11\n\ - strb r6, [r7]\n\ ldr r5, =gRngValue\n\ ldmia r5!, {r1, r2, r3, r4}\n\ @ e (result) = a + b + d++\n\ @@ -49,9 +47,7 @@ u32 NAKED Random32(void) add r3, r3, r0\n\ sub r5, r5, #16\n\ stmia r5!, {r1, r2, r3, r4}\n\ - mov r1, #0\n\ - strb r1, [r7]\n\ - pop {r4, r5, r6, r7}\n\ + pop {r4, r5, r6}\n\ bx lr\n\ .ltorg" ); @@ -65,7 +61,7 @@ u32 Random2_32(void) void SeedRng(u16 seed) { SFC32_Seed(&gRngValue, seed); - RNG_LOCK = 0; + RNG_LOOP_LOCK = 0; } void SeedRng2(u16 seed) @@ -75,15 +71,15 @@ void SeedRng2(u16 seed) void AdvanceRandom(void) { - if (RNG_LOCK == 0) - _SFC32_Next(&gRngValue); + if (RNG_LOOP_LOCK == 0) + Random32(); } #define LOOP_RANDOM_START \ struct Sfc32State *const state = &gRngValue; \ - RNG_LOCK = 11 + RNG_LOOP_LOCK = 1 -#define LOOP_RANDOM_END RNG_LOCK = 0 +#define LOOP_RANDOM_END RNG_LOOP_LOCK = 0 #define LOOP_RANDOM ((u16)(_SFC32_Next(state) >> 16)) From 35ae0b35682149316f58767f2a78e3a1c20e243a Mon Sep 17 00:00:00 2001 From: tertu marybig Date: Wed, 20 Dec 2023 19:46:11 -0600 Subject: [PATCH 05/18] Fix test compilation --- include/test/battle.h | 2 +- test/test_runner_battle.c | 42 +++++++++++++++++++++++++++++++++------ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/include/test/battle.h b/include/test/battle.h index c83a61891d8d..f65582220170 100644 --- a/include/test/battle.h +++ b/include/test/battle.h @@ -842,7 +842,7 @@ struct moveWithPP { void OpenPokemon(u32 sourceLine, u32 side, u32 species); void ClosePokemon(u32 sourceLine); -void RNGSeed_(u32 sourceLine, u32 seed); +void RNGSeed_(u32 sourceLine, rng_value_t seed); void AIFlags_(u32 sourceLine, u32 flags); void AILogScores(u32 sourceLine); void Gender_(u32 sourceLine, u32 gender); diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index ac689676b0fc..e6ff8efee866 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -32,8 +32,25 @@ #define STATE gBattleTestRunnerState #define DATA gBattleTestRunnerState->data -#define RNG_SEED_DEFAULT 0x00000000 +#if HQ_RANDOM == TRUE +#define RNG_SEED_DEFAULT (struct Sfc32State){.a = 0, .b = 0, .c = 0, .ctr = 1} +static inline bool32 RngSeedNotDefault(const rng_value_t *seed) +{ + const struct Sfc32State defaultSeed = RNG_SEED_DEFAULT; + return + defaultSeed.a != seed->a || + defaultSeed.b != seed->b || + defaultSeed.c != seed->c || + defaultSeed.ctr != seed->ctr; +} +#else +#define RNG_SEED_DEFAULT 0x00000000 +static inline bool32 RngSeedNotDefault(const rng_value_t *seed) +{ + return *seed != RNG_SEED_DEFAULT; +} +#endif #undef Q_4_12 #define Q_4_12(n) (s32)((n) * 4096) @@ -1347,6 +1364,19 @@ static void CB2_BattleTest_NextParameter(void) } } +static inline rng_value_t MakeRngValue(const u16 seed) +{ + #if HQ_RANDOM == TRUE + rng_value_t result = {.a = 0, .b = 0, .c = seed, .ctr = 1}; + for (int i = 0; i < 16; i++) + { + _SFC32_Next(&result); + } + return result; + #else + return ISO_RANDOMIZE1(seed); + #endif +} static void CB2_BattleTest_NextTrial(void) { TearDownBattle(); @@ -1370,7 +1400,7 @@ static void CB2_BattleTest_NextTrial(void) { PrintTestName(); gTestRunnerState.result = TEST_RESULT_PASS; - DATA.recordedBattle.rngSeed = ISO_RANDOMIZE1(STATE->runTrial); + DATA.recordedBattle.rngSeed = MakeRngValue(STATE->runTrial); DATA.queuedEvent = 0; DATA.lastActionTurn = 0; SetVariablesForRecordedBattle(&DATA.recordedBattle); @@ -1442,16 +1472,16 @@ void Randomly(u32 sourceLine, u32 passes, u32 trials, struct RandomlyContext ctx } else { - INVALID_IF(DATA.recordedBattle.rngSeed != RNG_SEED_DEFAULT, "RNG seed already set"); + INVALID_IF(RngSeedNotDefault(&DATA.recordedBattle.rngSeed), "RNG seed already set"); STATE->trials = 50; STATE->trialRatio = Q_4_12(1) / STATE->trials; - DATA.recordedBattle.rngSeed = 0; + DATA.recordedBattle.rngSeed = RNG_SEED_DEFAULT; } } -void RNGSeed_(u32 sourceLine, u32 seed) +void RNGSeed_(u32 sourceLine, rng_value_t seed) { - INVALID_IF(DATA.recordedBattle.rngSeed != RNG_SEED_DEFAULT, "RNG seed already set"); + INVALID_IF(RngSeedNotDefault(&DATA.recordedBattle.rngSeed), "RNG seed already set"); DATA.recordedBattle.rngSeed = seed; } From 25358c4d5a098f577f01414a39b0406d7ce96ffa Mon Sep 17 00:00:00 2001 From: tertu marybig Date: Wed, 20 Dec 2023 20:09:36 -0600 Subject: [PATCH 06/18] Maybe half-fix one test --- test/random.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/random.c b/test/random.c index 845f24f102a5..759bb6178411 100644 --- a/test/random.c +++ b/test/random.c @@ -227,6 +227,13 @@ TEST("RandomUniform mul-based faster than mod-based (compile-time)") TEST("RandomUniform mul-based faster than mod-based (run-time)") { + #if HQ_RANDOM == TRUE + const u32 expectedMulSum = 289; + const u32 expectedModSum = 249; + #else + const u32 expectedMulSum = 232; + const u32 expectedModSum = 249; + #endif u32 i; struct Benchmark mulBenchmark, modBenchmark; u32 mulSum = 0, modSum = 0; From 82bc6f55321e2ecda5690cb2138a09226323ed6a Mon Sep 17 00:00:00 2001 From: tertu marybig Date: Wed, 20 Dec 2023 20:18:46 -0600 Subject: [PATCH 07/18] More test junk (I develop on a Mac so I can't run tests locally.) --- test/random.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/test/random.c b/test/random.c index 759bb6178411..084efd4b8747 100644 --- a/test/random.c +++ b/test/random.c @@ -196,6 +196,13 @@ TEST("RandomElement generates a uniform distribution") TEST("RandomUniform mul-based faster than mod-based (compile-time)") { + #if HQ_RANDOM == TRUE + const u32 expectedMulSum = 6; + const u32 expectedModSum = 249; + #else + const u32 expectedMulSum = 3; + const u32 expectedModSum = 249; + #endif struct Benchmark mulBenchmark, modBenchmark; u32 mulSum = 0, modSum = 0; @@ -221,8 +228,8 @@ TEST("RandomUniform mul-based faster than mod-based (compile-time)") // These numbers are different because multiplication and modulus // have subtly different biases (so subtle that it's irrelevant for // our purposes). - EXPECT_EQ(mulSum, 3); - EXPECT_EQ(modSum, 4); + EXPECT_EQ(mulSum, expectedMulSum); + EXPECT_EQ(modSum, expectedModSum); } TEST("RandomUniform mul-based faster than mod-based (run-time)") @@ -253,6 +260,6 @@ TEST("RandomUniform mul-based faster than mod-based (run-time)") EXPECT_FASTER(mulBenchmark, modBenchmark); // Reference mulSum/modSum to prevent optimization. - EXPECT_EQ(mulSum, 232); - EXPECT_EQ(modSum, 249); + EXPECT_EQ(mulSum, expectedMulSum); + EXPECT_EQ(modSum, expectedModSum); } From 50b70f217b29bff8fb5e4afc1c40dab1e68e91fa Mon Sep 17 00:00:00 2001 From: tertu marybig Date: Wed, 20 Dec 2023 22:23:21 -0600 Subject: [PATCH 08/18] Maybe fix complaining about RNG seed not being set --- test/random.c | 6 +++--- test/test_runner_battle.c | 9 ++------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/test/random.c b/test/random.c index 084efd4b8747..48860b5ff2d1 100644 --- a/test/random.c +++ b/test/random.c @@ -198,10 +198,10 @@ TEST("RandomUniform mul-based faster than mod-based (compile-time)") { #if HQ_RANDOM == TRUE const u32 expectedMulSum = 6; - const u32 expectedModSum = 249; + const u32 expectedModSum = 4; #else const u32 expectedMulSum = 3; - const u32 expectedModSum = 249; + const u32 expectedModSum = 4; #endif struct Benchmark mulBenchmark, modBenchmark; u32 mulSum = 0, modSum = 0; @@ -239,7 +239,7 @@ TEST("RandomUniform mul-based faster than mod-based (run-time)") const u32 expectedModSum = 249; #else const u32 expectedMulSum = 232; - const u32 expectedModSum = 249; + const u32 expectedModSum = 205; #endif u32 i; struct Benchmark mulBenchmark, modBenchmark; diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index e6ff8efee866..c7c3eac5b463 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -33,15 +33,10 @@ #define DATA gBattleTestRunnerState->data #if HQ_RANDOM == TRUE -#define RNG_SEED_DEFAULT (struct Sfc32State){.a = 0, .b = 0, .c = 0, .ctr = 1} +#define RNG_SEED_DEFAULT (struct Sfc32State){.a = 0, .b = 0, .c = 0, .ctr = 0} static inline bool32 RngSeedNotDefault(const rng_value_t *seed) { - const struct Sfc32State defaultSeed = RNG_SEED_DEFAULT; - return - defaultSeed.a != seed->a || - defaultSeed.b != seed->b || - defaultSeed.c != seed->c || - defaultSeed.ctr != seed->ctr; + return (seed->a | seed->b | seed->c | seed->ctr) != 0; } #else From 9bac69d62d0d90580f2c1c2d4bb7567737eb143b Mon Sep 17 00:00:00 2001 From: tertu marybig Date: Wed, 20 Dec 2023 22:45:55 -0600 Subject: [PATCH 09/18] Hopefully make the last tests pass This makes the shuffle error a bit more tolerant --- test/random.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/random.c b/test/random.c index 48860b5ff2d1..0649ba9a7bbf 100644 --- a/test/random.c +++ b/test/random.c @@ -17,7 +17,7 @@ error = 0; \ for (i = 0; i < ARRAY_COUNT(indexSum); i++) \ error += abs(3584 - indexSum[i]); \ - EXPECT_LT(error, (int)(28672 * 0.025)); + EXPECT_LT(error, (int)(28672 * 0.03)); TEST("Shuffle randomizes the array [Shuffle8]") { @@ -236,10 +236,10 @@ TEST("RandomUniform mul-based faster than mod-based (run-time)") { #if HQ_RANDOM == TRUE const u32 expectedMulSum = 289; - const u32 expectedModSum = 249; + const u32 expectedModSum = 205; #else const u32 expectedMulSum = 232; - const u32 expectedModSum = 205; + const u32 expectedModSum = 249; #endif u32 i; struct Benchmark mulBenchmark, modBenchmark; From 9b6439a9a4a241b46db2875f8d0012bcbfc34890 Mon Sep 17 00:00:00 2001 From: tertu marybig Date: Thu, 21 Dec 2023 09:59:55 -0600 Subject: [PATCH 10/18] Review updates and separate streams for Random2 --- include/config.h | 2 +- include/random.h | 11 ++++++++--- src/random.c | 41 +++++++++++++++++++++++++++-------------- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/include/config.h b/include/config.h index f4be812643dc..a61e8de56c36 100644 --- a/include/config.h +++ b/include/config.h @@ -79,6 +79,6 @@ #define EXPANSION_INTRO TRUE // If TRUE, a custom RHH intro will play after the vanilla copyright screen. #define POKEDEX_PLUS_HGSS FALSE // If TRUE, enables the custom HGSS style Pokedex. #define SUMMARY_SCREEN_NATURE_COLORS TRUE // If TRUE, nature-based stat boosts and reductions will be red and blue in the summary screen. -#define HQ_RANDOM TRUE // If TRUE, replaces the default RNG with an implementation of SFC32 RNG. May break code that relies on RNG. +#define HQ_RANDOM FALSE // If TRUE, replaces the default RNG with an implementation of SFC32 RNG. May break code that relies on RNG. #endif // GUARD_CONFIG_H diff --git a/include/random.h b/include/random.h index 97e610d4e36b..e8f0038f3e71 100644 --- a/include/random.h +++ b/include/random.h @@ -7,9 +7,14 @@ #define ISO_RANDOMIZE2(val)(1103515245 * (val) + 12345) /* Notes about new functions: -* If using HQ_RANDOM, you MUST call AdvanceRandom() in VBlank handlers. -* If you do not, you risk corruption of the RNG state. -* LocalRandom(*val) adapts to either choice of RNG implementation. +* +* If using HQ_RANDOM, you cannot call Random() in interrupt handlers safely. +* AdvanceRandom() is provided to handle burning numbers in the VBlank handler +* if you choose to do that, and can be used regardless of HQ_RANDOM setting. +* +* LocalRandom(*val) allows you to have local random states that are the same +* type as the global states regardless of HQ_RANDOM setting. +* * Other new functions should be self-explanatory. */ diff --git a/src/random.c b/src/random.c index 79fecdcf241b..0aa6a4459f22 100644 --- a/src/random.c +++ b/src/random.c @@ -9,19 +9,33 @@ rng_value_t gRngValue; rng_value_t gRng2Value; #if HQ_RANDOM == TRUE -EWRAM_DATA static volatile u8 sUnknown = 1; -#define RNG_LOOP_LOCK sUnknown +EWRAM_DATA static volatile bool8 sRngLoopUnlocked; +// Do not change STREAM1 without changing Random32(). +#define STREAM1 1 +#define STREAM2 29 -static void SFC32_Seed(struct Sfc32State *state, u16 seed) +// A variant of SFC32 that lets you change the stream. +// stream cannot be even, but any other (8-bit) value should be OK. +static inline u32 _SFC32_Next_Stream(struct Sfc32State *state, const u8 stream) +{ + const u32 result = state->a + state->b + state->ctr; + state->ctr += stream; + state->a = state->b ^ (state->b >> 9); + state->b = state->c * 9; + state->c = result + ((state->c << 21) | (state->c >> 11)); + return result; +} + +static void SFC32_Seed(struct Sfc32State *state, u16 seed, u8 stream) { u32 i; state->a = state->b = 0; state->c = seed; - state->ctr = 1; + state->ctr = stream; for(i = 0; i < 16; i++) { - _SFC32_Next(state); + _SFC32_Next_Stream(state, stream); } } @@ -35,6 +49,7 @@ u32 NAKED Random32(void) @ e (result) = a + b + d++\n\ add r1, r1, r2\n\ add r0, r1, r4\n\ + @the argument is the same as STREAM1\n\ add r4, r4, #1\n\ @ a = b ^ (b >> 9)\n\ lsr r1, r2, #9\n\ @@ -55,36 +70,35 @@ u32 NAKED Random32(void) u32 Random2_32(void) { - return _SFC32_Next(&gRng2Value); + return _SFC32_Next_Stream(&gRng2Value, STREAM2); } void SeedRng(u16 seed) { - SFC32_Seed(&gRngValue, seed); - RNG_LOOP_LOCK = 0; + SFC32_Seed(&gRngValue, seed, STREAM1); + sRngLoopUnlocked = TRUE; } void SeedRng2(u16 seed) { - SFC32_Seed(&gRng2Value, seed); + SFC32_Seed(&gRng2Value, seed, STREAM2); } void AdvanceRandom(void) { - if (RNG_LOOP_LOCK == 0) + if (sRngLoopUnlocked == TRUE) Random32(); } #define LOOP_RANDOM_START \ struct Sfc32State *const state = &gRngValue; \ - RNG_LOOP_LOCK = 1 + sRngLoopUnlocked = FALSE; -#define LOOP_RANDOM_END RNG_LOOP_LOCK = 0 +#define LOOP_RANDOM_END sRngLoopUnlocked = TRUE; #define LOOP_RANDOM ((u16)(_SFC32_Next(state) >> 16)) #else -EWRAM_DATA static u8 sUnknown = 0; EWRAM_DATA static u32 sRandCount = 0; u16 Random(void) @@ -97,7 +111,6 @@ u16 Random(void) void SeedRng(u16 seed) { gRngValue = seed; - sUnknown = 0; } void SeedRng2(u16 seed) From f5a27654e7dc90da7b1359f7b8f77cbfb00253ea Mon Sep 17 00:00:00 2001 From: tertu marybig Date: Thu, 21 Dec 2023 10:04:05 -0600 Subject: [PATCH 11/18] Change HQ_RANDOM back to TRUE for testing --- include/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/config.h b/include/config.h index a61e8de56c36..f4be812643dc 100644 --- a/include/config.h +++ b/include/config.h @@ -79,6 +79,6 @@ #define EXPANSION_INTRO TRUE // If TRUE, a custom RHH intro will play after the vanilla copyright screen. #define POKEDEX_PLUS_HGSS FALSE // If TRUE, enables the custom HGSS style Pokedex. #define SUMMARY_SCREEN_NATURE_COLORS TRUE // If TRUE, nature-based stat boosts and reductions will be red and blue in the summary screen. -#define HQ_RANDOM FALSE // If TRUE, replaces the default RNG with an implementation of SFC32 RNG. May break code that relies on RNG. +#define HQ_RANDOM TRUE // If TRUE, replaces the default RNG with an implementation of SFC32 RNG. May break code that relies on RNG. #endif // GUARD_CONFIG_H From 16614b13843439fd2b98805f35ea525536b919ae Mon Sep 17 00:00:00 2001 From: tertu marybig Date: Thu, 21 Dec 2023 11:53:41 -0600 Subject: [PATCH 12/18] agbcc doesn't support struct literals --- test/test_runner_battle.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index c7c3eac5b463..c17cf85906bd 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -33,7 +33,7 @@ #define DATA gBattleTestRunnerState->data #if HQ_RANDOM == TRUE -#define RNG_SEED_DEFAULT (struct Sfc32State){.a = 0, .b = 0, .c = 0, .ctr = 0} +#define RNG_SEED_DEFAULT {0, 0, 0, 0} static inline bool32 RngSeedNotDefault(const rng_value_t *seed) { return (seed->a | seed->b | seed->c | seed->ctr) != 0; From ecfa3d9975c428a854c19790365f9a87e268d5d3 Mon Sep 17 00:00:00 2001 From: tertu marybig Date: Thu, 21 Dec 2023 12:45:14 -0600 Subject: [PATCH 13/18] Fix tests that I broke on agbcc (There are other broken tests on agbcc but those are out of scope for my changes) --- test/test_runner_battle.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index c17cf85906bd..dd7813d2f093 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -268,11 +268,12 @@ static void BattleTest_Run(void *data) s32 i; u32 requiredPlayerPartySize; u32 requiredOpponentPartySize; + const rng_value_t defaultSeed = RNG_SEED_DEFAULT; const struct BattleTest *test = data; memset(&DATA, 0, sizeof(DATA)); - DATA.recordedBattle.rngSeed = RNG_SEED_DEFAULT; + DATA.recordedBattle.rngSeed = defaultSeed; DATA.recordedBattle.textSpeed = OPTIONS_TEXT_SPEED_FAST; // Set battle flags and opponent ids. switch (test->type) @@ -1362,8 +1363,9 @@ static void CB2_BattleTest_NextParameter(void) static inline rng_value_t MakeRngValue(const u16 seed) { #if HQ_RANDOM == TRUE - rng_value_t result = {.a = 0, .b = 0, .c = seed, .ctr = 1}; - for (int i = 0; i < 16; i++) + int i; + rng_value_t result = {0, 0, seed, 1}; + for (i = 0; i < 16; i++) { _SFC32_Next(&result); } @@ -1467,10 +1469,11 @@ void Randomly(u32 sourceLine, u32 passes, u32 trials, struct RandomlyContext ctx } else { + const rng_value_t defaultSeed = RNG_SEED_DEFAULT; INVALID_IF(RngSeedNotDefault(&DATA.recordedBattle.rngSeed), "RNG seed already set"); STATE->trials = 50; STATE->trialRatio = Q_4_12(1) / STATE->trials; - DATA.recordedBattle.rngSeed = RNG_SEED_DEFAULT; + DATA.recordedBattle.rngSeed = defaultSeed; } } From 575e512004b5007a75ba15cdd3de41409d546935 Mon Sep 17 00:00:00 2001 From: tertu marybig Date: Thu, 21 Dec 2023 15:44:59 -0600 Subject: [PATCH 14/18] New --- test/random.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/random.c b/test/random.c index 0649ba9a7bbf..7e1e085e9b2e 100644 --- a/test/random.c +++ b/test/random.c @@ -263,3 +263,27 @@ TEST("RandomUniform mul-based faster than mod-based (run-time)") EXPECT_EQ(mulSum, expectedMulSum); EXPECT_EQ(modSum, expectedModSum); } + +#if HQ_RANDOM == TRUE +TEST("Thumb and C SFC32 implementations produce the same results") +{ + u32 thumbSum; + u32 cSum; + int i; + rng_value_t localState; + + thumbSum = 0; + cSum = 0; + + SeedRng(0); + localState = gRngValue; + + for(i = 0; i < 32; i++) + { + thumbSum += Random(); + cSum += _SFC32_Next(&localState); + } + + EXPECT_EQ(thumbSum, cSum); +} +#endif \ No newline at end of file From b68e897436d684ec207d963669afafad01bac382 Mon Sep 17 00:00:00 2001 From: tertu marybig Date: Thu, 21 Dec 2023 16:05:18 -0600 Subject: [PATCH 15/18] possibly fix thumb sfc? --- src/random.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/random.c b/src/random.c index 0aa6a4459f22..709584b5ec96 100644 --- a/src/random.c +++ b/src/random.c @@ -39,11 +39,13 @@ static void SFC32_Seed(struct Sfc32State *state, u16 seed, u8 stream) } } +// As the most frequently called RNG function, this is written in assembly +// for speed. It is functionally equivalent to _SFC32_Next(&gRngValue) u32 NAKED Random32(void) { asm(".thumb\n\ push {r4, r5, r6}\n\ - mov r6, #11\n\ + mov r6, #21\n\ ldr r5, =gRngValue\n\ ldmia r5!, {r1, r2, r3, r4}\n\ @ e (result) = a + b + d++\n\ @@ -57,7 +59,7 @@ u32 NAKED Random32(void) @ b = c + (c << 3) [c * 9]\n\ lsl r2, r3, #3\n\ add r2, r2, r3\n\ - @ c = rol(c, 21) + e\n\ + @ c = ror(c, 21) + e\n\ ror r3, r3, r6\n\ add r3, r3, r0\n\ sub r5, r5, #16\n\ From 7075780848ef65444871b0552c3efaf467505136 Mon Sep 17 00:00:00 2001 From: tertu marybig Date: Thu, 21 Dec 2023 16:11:11 -0600 Subject: [PATCH 16/18] Revert "possibly fix thumb sfc?" OOPS i did the test wrong. Sad! This reverts commit b68e897436d684ec207d963669afafad01bac382. --- src/random.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/random.c b/src/random.c index 709584b5ec96..0aa6a4459f22 100644 --- a/src/random.c +++ b/src/random.c @@ -39,13 +39,11 @@ static void SFC32_Seed(struct Sfc32State *state, u16 seed, u8 stream) } } -// As the most frequently called RNG function, this is written in assembly -// for speed. It is functionally equivalent to _SFC32_Next(&gRngValue) u32 NAKED Random32(void) { asm(".thumb\n\ push {r4, r5, r6}\n\ - mov r6, #21\n\ + mov r6, #11\n\ ldr r5, =gRngValue\n\ ldmia r5!, {r1, r2, r3, r4}\n\ @ e (result) = a + b + d++\n\ @@ -59,7 +57,7 @@ u32 NAKED Random32(void) @ b = c + (c << 3) [c * 9]\n\ lsl r2, r3, #3\n\ add r2, r2, r3\n\ - @ c = ror(c, 21) + e\n\ + @ c = rol(c, 21) + e\n\ ror r3, r3, r6\n\ add r3, r3, r0\n\ sub r5, r5, #16\n\ From 79a7e993211ab0ae00b29e6a1a4625a1bf677b9f Mon Sep 17 00:00:00 2001 From: tertu marybig Date: Thu, 21 Dec 2023 16:11:57 -0600 Subject: [PATCH 17/18] Fix test --- test/random.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/random.c b/test/random.c index 7e1e085e9b2e..0232ff154731 100644 --- a/test/random.c +++ b/test/random.c @@ -280,7 +280,7 @@ TEST("Thumb and C SFC32 implementations produce the same results") for(i = 0; i < 32; i++) { - thumbSum += Random(); + thumbSum += Random32(); cSum += _SFC32_Next(&localState); } From a539f3f7e481d3ddb01f380469f1285f78c23c3c Mon Sep 17 00:00:00 2001 From: tertu marybig Date: Fri, 22 Dec 2023 11:06:03 -0600 Subject: [PATCH 18/18] Review changes and others --- include/random.h | 11 ++++++++--- include/recorded_battle.h | 2 +- src/battle_controller_link_opponent.c | 2 +- src/battle_controller_link_partner.c | 2 +- src/random.c | 16 +++++++++++----- 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/include/random.h b/include/random.h index e8f0038f3e71..a13b28674f7a 100644 --- a/include/random.h +++ b/include/random.h @@ -6,16 +6,21 @@ #define ISO_RANDOMIZE1(val)(1103515245 * (val) + 24691) #define ISO_RANDOMIZE2(val)(1103515245 * (val) + 12345) -/* Notes about new functions: +/* Some functions have been added to support HQ_RANDOM. * * If using HQ_RANDOM, you cannot call Random() in interrupt handlers safely. * AdvanceRandom() is provided to handle burning numbers in the VBlank handler * if you choose to do that, and can be used regardless of HQ_RANDOM setting. +* If you need to use random numbers in the VBlank handler, a local state +* should be used instead. * * LocalRandom(*val) allows you to have local random states that are the same -* type as the global states regardless of HQ_RANDOM setting. +* type as the global states regardless of HQ_RANDOM setting, which is useful +* if you want to be able to set them from or assign them to gRngValue. * -* Other new functions should be self-explanatory. +* Random2_32() was added to HQ_RANDOM because the output of the generator is +* always 32 bits and Random()/Random2() are just wrappers in that mode. It is +* also available in non-HQ mode for consistency. */ #if HQ_RANDOM == TRUE diff --git a/include/recorded_battle.h b/include/recorded_battle.h index 5d3f47d14014..c64a665b74fe 100644 --- a/include/recorded_battle.h +++ b/include/recorded_battle.h @@ -2,7 +2,7 @@ #define GUARD_RECORDED_BATTLE_H #include "constants/battle.h" -#include "random.h" // for rng_value_t +#include "random.h" #define BATTLER_RECORD_SIZE 664 diff --git a/src/battle_controller_link_opponent.c b/src/battle_controller_link_opponent.c index 03080e501625..c9197bc5dfd3 100644 --- a/src/battle_controller_link_opponent.c +++ b/src/battle_controller_link_opponent.c @@ -28,7 +28,7 @@ #include "constants/songs.h" #include "constants/trainers.h" #include "recorded_battle.h" -#include "random.h" // for rng_value_t +#include "random.h" static void LinkOpponentHandleLoadMonSprite(u32 battler); static void LinkOpponentHandleSwitchInAnim(u32 battler); diff --git a/src/battle_controller_link_partner.c b/src/battle_controller_link_partner.c index 93fdfd7fef18..e5fb49bebf65 100644 --- a/src/battle_controller_link_partner.c +++ b/src/battle_controller_link_partner.c @@ -28,7 +28,7 @@ #include "constants/songs.h" #include "constants/trainers.h" #include "recorded_battle.h" -#include "random.h" // for rng_value_t +#include "random.h" static void LinkPartnerHandleLoadMonSprite(u32 battler); static void LinkPartnerHandleSwitchInAnim(u32 battler); diff --git a/src/random.c b/src/random.c index 0aa6a4459f22..db82334aa930 100644 --- a/src/random.c +++ b/src/random.c @@ -9,14 +9,15 @@ rng_value_t gRngValue; rng_value_t gRng2Value; #if HQ_RANDOM == TRUE + EWRAM_DATA static volatile bool8 sRngLoopUnlocked; -// Do not change STREAM1 without changing Random32(). +// Streams allow generators seeded the same to have separate outputs. #define STREAM1 1 #define STREAM2 29 // A variant of SFC32 that lets you change the stream. -// stream cannot be even, but any other (8-bit) value should be OK. +// stream can be any odd number. static inline u32 _SFC32_Next_Stream(struct Sfc32State *state, const u8 stream) { const u32 result = state->a + state->b + state->ctr; @@ -39,6 +40,8 @@ static void SFC32_Seed(struct Sfc32State *state, u16 seed, u8 stream) } } +/*This ASM implementation uses some shortcuts and is generally faster on the GBA. +* It's not necessarily faster if inlined, or on other platforms. */ u32 NAKED Random32(void) { asm(".thumb\n\ @@ -49,8 +52,7 @@ u32 NAKED Random32(void) @ e (result) = a + b + d++\n\ add r1, r1, r2\n\ add r0, r1, r4\n\ - @the argument is the same as STREAM1\n\ - add r4, r4, #1\n\ + add r4, r4, #" STR(STREAM1) "\n\ @ a = b ^ (b >> 9)\n\ lsr r1, r2, #9\n\ eor r1, r1, r2\n\ @@ -75,7 +77,11 @@ u32 Random2_32(void) void SeedRng(u16 seed) { - SFC32_Seed(&gRngValue, seed, STREAM1); + struct Sfc32State state; + SFC32_Seed(&state, seed, STREAM1); + + sRngLoopUnlocked = FALSE; + gRngValue = state; sRngLoopUnlocked = TRUE; }