Optional high-quality RNG (#3780)

High-quality RNG, behind the HQ_RANDOM flag, enabled by default

Makes the shuffle test error a bit more tolerant
This commit is contained in:
tertu 2023-12-22 11:39:15 -06:00 committed by GitHub
parent 63316daf30
commit fb28ce50ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 324 additions and 50 deletions

View file

@ -15,6 +15,7 @@
#include "pokeball.h" #include "pokeball.h"
#include "battle_debug.h" #include "battle_debug.h"
#include "battle_dynamax.h" #include "battle_dynamax.h"
#include "random.h" // for rng_value_t
// Used to exclude moves learned temporarily by Transform or Mimic // Used to exclude moves learned temporarily by Transform or Mimic
#define MOVE_IS_PERMANENT(battler, moveSlot) \ #define MOVE_IS_PERMANENT(battler, moveSlot) \
@ -580,6 +581,13 @@ struct LostItem
u16 stolen:1; u16 stolen:1;
}; };
#if HQ_RANDOM == TRUE
struct BattleVideo {
u32 battleTypeFlags;
rng_value_t rngSeed;
};
#endif
struct BattleStruct struct BattleStruct
{ {
u8 turnEffectsTracker; u8 turnEffectsTracker;
@ -651,7 +659,12 @@ struct BattleStruct
u16 lastTakenMoveFrom[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT]; // a 2-D array [target][attacker] u16 lastTakenMoveFrom[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT]; // a 2-D array [target][attacker]
union { union {
struct LinkBattlerHeader linkBattlerHeader; struct LinkBattlerHeader linkBattlerHeader;
#if HQ_RANDOM == FALSE
u32 battleVideo[2]; u32 battleVideo[2];
#else
struct BattleVideo battleVideo;
#endif
} multiBuffer; } multiBuffer;
u8 wishPerishSongState; u8 wishPerishSongState;
u8 wishPerishSongBattlerId; u8 wishPerishSongBattlerId;

View file

@ -79,5 +79,6 @@
#define EXPANSION_INTRO TRUE // If TRUE, a custom RHH intro will play after the vanilla copyright screen. #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 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 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 #endif // GUARD_CONFIG_H

View file

@ -3,6 +3,7 @@
#include "palette.h" #include "palette.h"
#include "constants/contest.h" #include "constants/contest.h"
#include "random.h" // for rng_value_t
enum enum
{ {
@ -327,7 +328,7 @@ extern struct ContestResources *gContestResources;
extern struct ContestWinner gCurContestWinner; extern struct ContestWinner gCurContestWinner;
extern u8 gCurContestWinnerIsForArtist; extern u8 gCurContestWinnerIsForArtist;
extern u8 gCurContestWinnerSaveIdx; extern u8 gCurContestWinnerSaveIdx;
extern u32 gContestRngValue; extern rng_value_t gContestRngValue;
// contest.c // contest.c
void ResetLinkContestBoolean(void); void ResetLinkContestBoolean(void);

View file

@ -1,8 +1,74 @@
#ifndef GUARD_RANDOM_H #ifndef GUARD_RANDOM_H
#define GUARD_RANDOM_H #define GUARD_RANDOM_H
extern u32 gRngValue; // The number 1103515245 comes from the example implementation of rand and srand
extern u32 gRng2Value; // in the ISO C standard.
#define ISO_RANDOMIZE1(val)(1103515245 * (val) + 24691)
#define ISO_RANDOMIZE2(val)(1103515245 * (val) + 12345)
/* 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, which is useful
* if you want to be able to set them from or assign them to gRngValue.
*
* 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
struct Sfc32State {
u32 a;
u32 b;
u32 c;
u32 ctr;
};
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)
{
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;
#define RNG_VALUE_EMPTY 0
//Returns a 16-bit pseudorandom number //Returns a 16-bit pseudorandom number
u16 Random(void); u16 Random(void);
@ -10,11 +76,23 @@ u16 Random2(void);
//Returns a 32-bit pseudorandom number //Returns a 32-bit pseudorandom number
#define Random32() (Random() | (Random() << 16)) #define Random32() (Random() | (Random() << 16))
#define Random2_32() (Random2() | (Random2() << 16))
// The number 1103515245 comes from the example implementation of rand and srand static inline u16 LocalRandom(rng_value_t *val)
// in the ISO C standard. {
#define ISO_RANDOMIZE1(val)(1103515245 * (val) + 24691) *val = ISO_RANDOMIZE1(*val);
#define ISO_RANDOMIZE2(val)(1103515245 * (val) + 12345) 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 //Sets the initial seed value of the pseudorandom number generator
void SeedRng(u16 seed); void SeedRng(u16 seed);

View file

@ -2,6 +2,7 @@
#define GUARD_RECORDED_BATTLE_H #define GUARD_RECORDED_BATTLE_H
#include "constants/battle.h" #include "constants/battle.h"
#include "random.h"
#define BATTLER_RECORD_SIZE 664 #define BATTLER_RECORD_SIZE 664
@ -13,7 +14,7 @@ struct RecordedBattleSave
u8 playersGender[MAX_BATTLERS_COUNT]; u8 playersGender[MAX_BATTLERS_COUNT];
u32 playersTrainerId[MAX_BATTLERS_COUNT]; u32 playersTrainerId[MAX_BATTLERS_COUNT];
u8 playersLanguage[MAX_BATTLERS_COUNT]; u8 playersLanguage[MAX_BATTLERS_COUNT];
u32 rngSeed; rng_value_t rngSeed;
u32 battleFlags; u32 battleFlags;
u8 playersBattlers[MAX_BATTLERS_COUNT]; u8 playersBattlers[MAX_BATTLERS_COUNT];
u16 opponentA; u16 opponentA;
@ -49,8 +50,8 @@ enum
RECORDED_ITEM_MOVE, RECORDED_ITEM_MOVE,
}; };
extern u32 gRecordedBattleRngSeed; extern rng_value_t gRecordedBattleRngSeed;
extern u32 gBattlePalaceMoveSelectionRngValue; extern rng_value_t gBattlePalaceMoveSelectionRngValue;
extern u8 gRecordedBattleMultiplayerId; extern u8 gRecordedBattleMultiplayerId;
#define B_RECORD_MODE_RECORDING 1 #define B_RECORD_MODE_RECORDING 1

View file

@ -855,7 +855,7 @@ void ClearFlagAfterTest(void);
void OpenPokemon(u32 sourceLine, u32 side, u32 species); void OpenPokemon(u32 sourceLine, u32 side, u32 species);
void ClosePokemon(u32 sourceLine); 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 AIFlags_(u32 sourceLine, u32 flags);
void AILogScores(u32 sourceLine); void AILogScores(u32 sourceLine);
void Gender_(u32 sourceLine, u32 gender); void Gender_(u32 sourceLine, u32 gender);

View file

@ -28,6 +28,7 @@
#include "constants/songs.h" #include "constants/songs.h"
#include "constants/trainers.h" #include "constants/trainers.h"
#include "recorded_battle.h" #include "recorded_battle.h"
#include "random.h"
static void LinkOpponentHandleLoadMonSprite(u32 battler); static void LinkOpponentHandleLoadMonSprite(u32 battler);
static void LinkOpponentHandleSwitchInAnim(u32 battler); static void LinkOpponentHandleSwitchInAnim(u32 battler);

View file

@ -28,6 +28,7 @@
#include "constants/songs.h" #include "constants/songs.h"
#include "constants/trainers.h" #include "constants/trainers.h"
#include "recorded_battle.h" #include "recorded_battle.h"
#include "random.h"
static void LinkPartnerHandleLoadMonSprite(u32 battler); static void LinkPartnerHandleLoadMonSprite(u32 battler);
static void LinkPartnerHandleSwitchInAnim(u32 battler); static void LinkPartnerHandleSwitchInAnim(u32 battler);

View file

@ -1713,9 +1713,16 @@ static void CB2_HandleStartMultiBattle(void)
case 8: case 8:
if (IsLinkTaskFinished()) if (IsLinkTaskFinished())
{ {
#if HQ_RANDOM == TRUE
struct BattleVideo *ptr = &gBattleStruct->multiBuffer.battleVideo;
ptr->battleTypeFlags = gBattleTypeFlags;
ptr->rngSeed = gRecordedBattleRngSeed;
#else
u32 *ptr = gBattleStruct->multiBuffer.battleVideo; u32 *ptr = gBattleStruct->multiBuffer.battleVideo;
ptr[0] = gBattleTypeFlags; ptr[0] = gBattleTypeFlags;
ptr[1] = gRecordedBattleRngSeed; // UB: overwrites berry data ptr[1] = gRecordedBattleRngSeed; // UB: overwrites berry data
#endif
SendBlock(BitmaskAllOtherLinkPlayers(), ptr, sizeof(gBattleStruct->multiBuffer.battleVideo)); SendBlock(BitmaskAllOtherLinkPlayers(), ptr, sizeof(gBattleStruct->multiBuffer.battleVideo));
gBattleCommunication[MULTIUSE_STATE]++; gBattleCommunication[MULTIUSE_STATE]++;
} }
@ -2053,7 +2060,7 @@ void VBlankCB_Battle(void)
{ {
// Change gRngSeed every vblank unless the battle could be recorded. // Change gRngSeed every vblank unless the battle could be recorded.
if (!(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_FRONTIER | BATTLE_TYPE_RECORDED))) if (!(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_FRONTIER | BATTLE_TYPE_RECORDED)))
Random(); AdvanceRandom();
SetGpuReg(REG_OFFSET_BG0HOFS, gBattle_BG0_X); SetGpuReg(REG_OFFSET_BG0HOFS, gBattle_BG0_X);
SetGpuReg(REG_OFFSET_BG0VOFS, gBattle_BG0_Y); SetGpuReg(REG_OFFSET_BG0VOFS, gBattle_BG0_Y);

View file

@ -358,7 +358,7 @@ EWRAM_DATA bool8 gCurContestWinnerIsForArtist = 0;
EWRAM_DATA u8 gCurContestWinnerSaveIdx = 0; EWRAM_DATA u8 gCurContestWinnerSaveIdx = 0;
// IWRAM common vars. // IWRAM common vars.
u32 gContestRngValue; rng_value_t gContestRngValue;
extern const u8 gText_LinkStandby4[]; extern const u8 gText_LinkStandby4[];
extern const u8 gText_BDot[]; extern const u8 gText_BDot[];
@ -1709,7 +1709,7 @@ static void Task_AppealSetup(u8 taskId)
if (++gTasks[taskId].data[0] > 19) if (++gTasks[taskId].data[0] > 19)
{ {
eContest.turnNumber = 0; eContest.turnNumber = 0;
eContest.unusedRng = gRngValue; eContest.unusedRng = 0;
if ((gLinkContestFlags & LINK_CONTEST_FLAG_IS_LINK) && IsPlayerLinkLeader()) if ((gLinkContestFlags & LINK_CONTEST_FLAG_IS_LINK) && IsPlayerLinkLeader())
{ {
s32 i; s32 i;
@ -6109,4 +6109,3 @@ void StripPlayerAndMonNamesForLinkContest(struct ContestPokemon *mon, s32 langua
name[PLAYER_NAME_LENGTH] = EOS; name[PLAYER_NAME_LENGTH] = EOS;
} }
} }

View file

@ -2655,8 +2655,7 @@ void GenerateContestRand(void)
if (gLinkContestFlags & LINK_CONTEST_FLAG_IS_LINK) if (gLinkContestFlags & LINK_CONTEST_FLAG_IS_LINK)
{ {
gContestRngValue = ISO_RANDOMIZE1(gContestRngValue); random = LocalRandom(&gContestRngValue);
random = gContestRngValue >> 16;
result = &gSpecialVar_Result; result = &gSpecialVar_Result;
} }
else else
@ -2669,8 +2668,7 @@ void GenerateContestRand(void)
u16 GetContestRand(void) u16 GetContestRand(void)
{ {
gContestRngValue = ISO_RANDOMIZE1(gContestRngValue); return LocalRandom(&gContestRngValue);
return gContestRngValue >> 16;
} }
bool8 LinkContestWaitForConnection(void) bool8 LinkContestWaitForConnection(void)

View file

@ -2144,7 +2144,7 @@ static void DebugAction_Util_Player_Gender(u8 taskId)
static void DebugAction_Util_Player_Id(u8 taskId) static void DebugAction_Util_Player_Id(u8 taskId)
{ {
u32 trainerId = ((Random() << 16) | Random()); u32 trainerId = Random32();
SetTrainerId(trainerId, gSaveBlock2Ptr->playerTrainerId); SetTrainerId(trainerId, gSaveBlock2Ptr->playerTrainerId);
Debug_DestroyMenu_Full(taskId); Debug_DestroyMenu_Full(taskId);
ScriptContext_Enable(); ScriptContext_Enable();

View file

@ -127,7 +127,7 @@ void MoveSaveBlocks_ResetHeap(void)
gMain.vblankCallback = vblankCB; gMain.vblankCallback = vblankCB;
// create a new encryption key // create a new encryption key
encryptionKey = (Random() << 16) + (Random()); encryptionKey = Random32();
ApplyNewEncryptionKeyToAllEncryptedData(encryptionKey); ApplyNewEncryptionKeyToAllEncryptedData(encryptionKey);
gSaveBlock2Ptr->encryptionKey = encryptionKey; gSaveBlock2Ptr->encryptionKey = encryptionKey;
} }

View file

@ -23,9 +23,7 @@ static u8 GetMatchingDigits(u16, u16);
void ResetLotteryCorner(void) void ResetLotteryCorner(void)
{ {
u16 rand = Random(); SetLotteryNumber(Random32());
SetLotteryNumber((Random() << 16) | rand);
VarSet(VAR_POKELOT_PRIZE_ITEM, 0); VarSet(VAR_POKELOT_PRIZE_ITEM, 0);
} }

View file

@ -373,7 +373,7 @@ static void VBlankIntr(void)
TryReceiveLinkBattleData(); TryReceiveLinkBattleData();
if (!gMain.inBattle || !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_FRONTIER | BATTLE_TYPE_RECORDED))) if (!gMain.inBattle || !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_FRONTIER | BATTLE_TYPE_RECORDED)))
Random(); AdvanceRandom();
UpdateWirelessStatusIndicatorSprite(); UpdateWirelessStatusIndicatorSprite();

View file

@ -4,12 +4,108 @@
#include <alloca.h> #include <alloca.h>
#endif #endif
EWRAM_DATA static u8 sUnknown = 0;
EWRAM_DATA static u32 sRandCount = 0;
// IWRAM common // IWRAM common
u32 gRngValue; rng_value_t gRngValue;
u32 gRng2Value; rng_value_t gRng2Value;
#if HQ_RANDOM == TRUE
EWRAM_DATA static volatile bool8 sRngLoopUnlocked;
// 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 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;
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 = stream;
for(i = 0; i < 16; i++)
{
_SFC32_Next_Stream(state, 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\
push {r4, r5, r6}\n\
mov r6, #11\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, #" STR(STREAM1) "\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\
pop {r4, r5, r6}\n\
bx lr\n\
.ltorg"
);
}
u32 Random2_32(void)
{
return _SFC32_Next_Stream(&gRng2Value, STREAM2);
}
void SeedRng(u16 seed)
{
struct Sfc32State state;
SFC32_Seed(&state, seed, STREAM1);
sRngLoopUnlocked = FALSE;
gRngValue = state;
sRngLoopUnlocked = TRUE;
}
void SeedRng2(u16 seed)
{
SFC32_Seed(&gRng2Value, seed, STREAM2);
}
void AdvanceRandom(void)
{
if (sRngLoopUnlocked == TRUE)
Random32();
}
#define LOOP_RANDOM_START \
struct Sfc32State *const state = &gRngValue; \
sRngLoopUnlocked = FALSE;
#define LOOP_RANDOM_END sRngLoopUnlocked = TRUE;
#define LOOP_RANDOM ((u16)(_SFC32_Next(state) >> 16))
#else
EWRAM_DATA static u32 sRandCount = 0;
u16 Random(void) u16 Random(void)
{ {
@ -21,7 +117,6 @@ u16 Random(void)
void SeedRng(u16 seed) void SeedRng(u16 seed)
{ {
gRngValue = seed; gRngValue = seed;
sUnknown = 0;
} }
void SeedRng2(u16 seed) void SeedRng2(u16 seed)
@ -35,15 +130,24 @@ u16 Random2(void)
return gRng2Value >> 16; return gRng2Value >> 16;
} }
#define LOOP_RANDOM_START
#define LOOP_RANDOM_END
#define LOOP_RANDOM (Random())
#endif
#define SHUFFLE_IMPL \ #define SHUFFLE_IMPL \
u32 tmp; \ u32 tmp; \
LOOP_RANDOM_START; \
--n; \ --n; \
while (n > 1) \ while (n > 1) \
{ \ { \
int j = (Random() * (n+1)) >> 16; \ int j = (LOOP_RANDOM * (n+1)) >> 16; \
SWAP(data[n], data[j], tmp); \ SWAP(data[n], data[j], tmp); \
--n; \ --n; \
} } \
LOOP_RANDOM_END
void Shuffle8(void *data_, size_t n) void Shuffle8(void *data_, size_t n)
{ {
@ -66,15 +170,19 @@ void Shuffle32(void *data_, size_t n)
void ShuffleN(void *data, size_t n, size_t size) void ShuffleN(void *data, size_t n, size_t size)
{ {
void *tmp = alloca(size); void *tmp = alloca(size);
LOOP_RANDOM_START;
--n; --n;
while (n > 1) 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(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 + n*size, (u8 *)data + j*size, size); // data[n] = data[j];
memcpy((u8 *)data + j*size, tmp, size); // data[j] = tmp; memcpy((u8 *)data + j*size, tmp, size); // data[j] = tmp;
--n; --n;
} }
LOOP_RANDOM_END;
} }
__attribute__((weak, alias("RandomUniformDefault"))) __attribute__((weak, alias("RandomUniformDefault")))
@ -96,12 +204,14 @@ u32 RandomUniformDefault(enum RandomTag tag, u32 lo, u32 hi)
u32 RandomUniformExceptDefault(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reject)(u32)) u32 RandomUniformExceptDefault(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reject)(u32))
{ {
LOOP_RANDOM_START;
while (TRUE) while (TRUE)
{ {
u32 n = RandomUniformDefault(tag, lo, hi); u32 n = lo + (((hi - lo + 1) * LOOP_RANDOM) >> 16);
if (!reject(n)) if (!reject(n))
return n; return n;
} }
LOOP_RANDOM_END;
} }
u32 RandomWeightedArrayDefault(enum RandomTag tag, u32 sum, u32 n, const u8 *weights) u32 RandomWeightedArrayDefault(enum RandomTag tag, u32 sum, u32 n, const u8 *weights)

View file

@ -33,8 +33,8 @@ struct PlayerInfo
// Save data using TryWriteSpecialSaveSector is allowed to exceed SECTOR_DATA_SIZE (up to the counter field) // 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); STATIC_ASSERT(sizeof(struct RecordedBattleSave) <= SECTOR_COUNTER_OFFSET, RecordedBattleSaveFreeSpace);
EWRAM_DATA u32 gRecordedBattleRngSeed = 0; EWRAM_DATA rng_value_t gRecordedBattleRngSeed = RNG_VALUE_EMPTY;
EWRAM_DATA u32 gBattlePalaceMoveSelectionRngValue = 0; EWRAM_DATA rng_value_t gBattlePalaceMoveSelectionRngValue = RNG_VALUE_EMPTY;
EWRAM_DATA static u8 sBattleRecords[MAX_BATTLERS_COUNT][BATTLER_RECORD_SIZE] = {0}; 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 sBattlerRecordSizes[MAX_BATTLERS_COUNT] = {0};
EWRAM_DATA static u16 sBattlerPrevRecordSizes[MAX_BATTLERS_COUNT] = {0}; EWRAM_DATA static u16 sBattlerPrevRecordSizes[MAX_BATTLERS_COUNT] = {0};

View file

@ -25,7 +25,7 @@ static void SetMirageRnd(u32 rnd)
// unused // unused
void InitMirageRnd(void) void InitMirageRnd(void)
{ {
SetMirageRnd((Random() << 16) | Random()); SetMirageRnd(Random32());
} }
void UpdateMirageRnd(u16 days) void UpdateMirageRnd(u16 days)

View file

@ -17,7 +17,7 @@
error = 0; \ error = 0; \
for (i = 0; i < ARRAY_COUNT(indexSum); i++) \ for (i = 0; i < ARRAY_COUNT(indexSum); i++) \
error += abs(3584 - 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]") TEST("Shuffle randomizes the array [Shuffle8]")
{ {
@ -196,6 +196,13 @@ TEST("RandomElement generates a uniform distribution")
TEST("RandomUniform mul-based faster than mod-based (compile-time)") TEST("RandomUniform mul-based faster than mod-based (compile-time)")
{ {
#if HQ_RANDOM == TRUE
const u32 expectedMulSum = 6;
const u32 expectedModSum = 4;
#else
const u32 expectedMulSum = 3;
const u32 expectedModSum = 4;
#endif
struct Benchmark mulBenchmark, modBenchmark; struct Benchmark mulBenchmark, modBenchmark;
u32 mulSum = 0, modSum = 0; u32 mulSum = 0, modSum = 0;
@ -221,12 +228,19 @@ TEST("RandomUniform mul-based faster than mod-based (compile-time)")
// These numbers are different because multiplication and modulus // These numbers are different because multiplication and modulus
// have subtly different biases (so subtle that it's irrelevant for // have subtly different biases (so subtle that it's irrelevant for
// our purposes). // our purposes).
EXPECT_EQ(mulSum, 3); EXPECT_EQ(mulSum, expectedMulSum);
EXPECT_EQ(modSum, 4); EXPECT_EQ(modSum, expectedModSum);
} }
TEST("RandomUniform mul-based faster than mod-based (run-time)") TEST("RandomUniform mul-based faster than mod-based (run-time)")
{ {
#if HQ_RANDOM == TRUE
const u32 expectedMulSum = 289;
const u32 expectedModSum = 205;
#else
const u32 expectedMulSum = 232;
const u32 expectedModSum = 249;
#endif
u32 i; u32 i;
struct Benchmark mulBenchmark, modBenchmark; struct Benchmark mulBenchmark, modBenchmark;
u32 mulSum = 0, modSum = 0; u32 mulSum = 0, modSum = 0;
@ -246,6 +260,30 @@ TEST("RandomUniform mul-based faster than mod-based (run-time)")
EXPECT_FASTER(mulBenchmark, modBenchmark); EXPECT_FASTER(mulBenchmark, modBenchmark);
// Reference mulSum/modSum to prevent optimization. // Reference mulSum/modSum to prevent optimization.
EXPECT_EQ(mulSum, 232); EXPECT_EQ(mulSum, expectedMulSum);
EXPECT_EQ(modSum, 249); 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 += Random32();
cSum += _SFC32_Next(&localState);
}
EXPECT_EQ(thumbSum, cSum);
}
#endif

View file

@ -33,8 +33,20 @@
#define STATE gBattleTestRunnerState #define STATE gBattleTestRunnerState
#define DATA gBattleTestRunnerState->data #define DATA gBattleTestRunnerState->data
#define RNG_SEED_DEFAULT 0x00000000 #if HQ_RANDOM == TRUE
#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;
}
#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 #undef Q_4_12
#define Q_4_12(n) (s32)((n) * 4096) #define Q_4_12(n) (s32)((n) * 4096)
@ -257,11 +269,12 @@ static void BattleTest_Run(void *data)
s32 i; s32 i;
u32 requiredPlayerPartySize; u32 requiredPlayerPartySize;
u32 requiredOpponentPartySize; u32 requiredOpponentPartySize;
const rng_value_t defaultSeed = RNG_SEED_DEFAULT;
const struct BattleTest *test = data; const struct BattleTest *test = data;
memset(&DATA, 0, sizeof(DATA)); memset(&DATA, 0, sizeof(DATA));
DATA.recordedBattle.rngSeed = RNG_SEED_DEFAULT; DATA.recordedBattle.rngSeed = defaultSeed;
DATA.recordedBattle.textSpeed = OPTIONS_TEXT_SPEED_FAST; DATA.recordedBattle.textSpeed = OPTIONS_TEXT_SPEED_FAST;
// Set battle flags and opponent ids. // Set battle flags and opponent ids.
switch (test->type) switch (test->type)
@ -1349,6 +1362,20 @@ static void CB2_BattleTest_NextParameter(void)
} }
} }
static inline rng_value_t MakeRngValue(const u16 seed)
{
#if HQ_RANDOM == TRUE
int i;
rng_value_t result = {0, 0, seed, 1};
for (i = 0; i < 16; i++)
{
_SFC32_Next(&result);
}
return result;
#else
return ISO_RANDOMIZE1(seed);
#endif
}
static void CB2_BattleTest_NextTrial(void) static void CB2_BattleTest_NextTrial(void)
{ {
ClearFlagAfterTest(); ClearFlagAfterTest();
@ -1373,7 +1400,7 @@ static void CB2_BattleTest_NextTrial(void)
{ {
PrintTestName(); PrintTestName();
gTestRunnerState.result = TEST_RESULT_PASS; gTestRunnerState.result = TEST_RESULT_PASS;
DATA.recordedBattle.rngSeed = ISO_RANDOMIZE1(STATE->runTrial); DATA.recordedBattle.rngSeed = MakeRngValue(STATE->runTrial);
DATA.queuedEvent = 0; DATA.queuedEvent = 0;
DATA.lastActionTurn = 0; DATA.lastActionTurn = 0;
SetVariablesForRecordedBattle(&DATA.recordedBattle); SetVariablesForRecordedBattle(&DATA.recordedBattle);
@ -1446,16 +1473,17 @@ void Randomly(u32 sourceLine, u32 passes, u32 trials, struct RandomlyContext ctx
} }
else else
{ {
INVALID_IF(DATA.recordedBattle.rngSeed != RNG_SEED_DEFAULT, "RNG seed already set"); const rng_value_t defaultSeed = RNG_SEED_DEFAULT;
INVALID_IF(RngSeedNotDefault(&DATA.recordedBattle.rngSeed), "RNG seed already set");
STATE->trials = 50; STATE->trials = 50;
STATE->trialRatio = Q_4_12(1) / STATE->trials; STATE->trialRatio = Q_4_12(1) / STATE->trials;
DATA.recordedBattle.rngSeed = 0; DATA.recordedBattle.rngSeed = defaultSeed;
} }
} }
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; DATA.recordedBattle.rngSeed = seed;
} }