From 88982bba08d6e2172bfafb90f0da7402ef575c95 Mon Sep 17 00:00:00 2001 From: Entroper Date: Sun, 6 Sep 2020 04:28:08 -0400 Subject: [PATCH 1/6] wip rng --- FF1Lib/FF1Lib.csproj | 2 +- FF1Lib/FF1Rom.cs | 1 + FF1Lib/Hacks.cs | 19 +++++++++++++++++++ FF1Lib/asm/LCG.asm | 7 +++++++ FF1R/FF1R.csproj | 2 +- FFR.Common/FFR.Common.csproj | 2 +- 6 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 FF1Lib/asm/LCG.asm diff --git a/FF1Lib/FF1Lib.csproj b/FF1Lib/FF1Lib.csproj index 4eee7f15e..3aafeedca 100644 --- a/FF1Lib/FF1Lib.csproj +++ b/FF1Lib/FF1Lib.csproj @@ -55,7 +55,7 @@ - + diff --git a/FF1Lib/FF1Rom.cs b/FF1Lib/FF1Rom.cs index 8771b0f1c..b4b7f6b78 100644 --- a/FF1Lib/FF1Rom.cs +++ b/FF1Lib/FF1Rom.cs @@ -106,6 +106,7 @@ public void Randomize(Blob seed, Flags flags, Preferences preferences) FixWarpBug(); // The warp bug must be fixed for magic level shuffle and spellcrafter SeparateUnrunnables(); UpdateDialogs(); + MoveSmokeSpriteVariables(); flags = Flags.ConvertAllTriState(flags, rng); diff --git a/FF1Lib/Hacks.cs b/FF1Lib/Hacks.cs index 025feee68..95ef8b12a 100644 --- a/FF1Lib/Hacks.cs +++ b/FF1Lib/Hacks.cs @@ -42,6 +42,25 @@ public partial class FF1Rom : NesRom public const string BattleBoxUndrawFrames = "04"; // 2/3 normal (Must divide 12) public const string BattleBoxUndrawRows = "03"; + public const int SmokeSpriteReplaceStart = 0x317E9; + public const int SmokeSpriteReplaceEnd = 0x31A2C; + + public void MoveSmokeSpriteVariables() + { + // This moves some temporary memory locations used to draw the smoke effect sprites + // in battle to the same locations used to store attacker stats. These can overwrite + // each other without issue, and it frees up some space for a few bytes of RNG state. + var smokeSpriteCode = Get(SmokeSpriteReplaceStart, SmokeSpriteReplaceEnd - SmokeSpriteReplaceStart); + + smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68AF }), Blob.FromUShorts(new ushort[] { 0x686C })); + smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B1 }), Blob.FromUShorts(new ushort[] { 0x686E })); + smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B3 }), Blob.FromUShorts(new ushort[] { 0x6870 })); + smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B4 }), Blob.FromUShorts(new ushort[] { 0x6871 })); + smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B5 }), Blob.FromUShorts(new ushort[] { 0x6872 })); + + Put(SmokeSpriteReplaceStart, smokeSpriteCode); + } + // Required for npc quest item randomizing public void PermanentCaravan() { diff --git a/FF1Lib/asm/LCG.asm b/FF1Lib/asm/LCG.asm new file mode 100644 index 000000000..910d77c29 --- /dev/null +++ b/FF1Lib/asm/LCG.asm @@ -0,0 +1,7 @@ +; Linear Congruential Generator (better random number generator) +; https://en.wikipedia.org/wiki/Linear_congruential_generator +; This is a simple but effective RNG with a much longer period than FF1's. + +; A research paper on selecting good parameters for the multiplier: +; https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.34.1024&rep=rep1&type=pdf + diff --git a/FF1R/FF1R.csproj b/FF1R/FF1R.csproj index 252252f62..9e779d9ec 100644 --- a/FF1R/FF1R.csproj +++ b/FF1R/FF1R.csproj @@ -13,7 +13,7 @@ - + diff --git a/FFR.Common/FFR.Common.csproj b/FFR.Common/FFR.Common.csproj index c25273d28..ea9394a95 100644 --- a/FFR.Common/FFR.Common.csproj +++ b/FFR.Common/FFR.Common.csproj @@ -2,7 +2,7 @@ - + From 9afbfb72893eaf90aedfff0c7f89804219482a79 Mon Sep 17 00:00:00 2001 From: Entroper Date: Wed, 9 Sep 2020 21:01:41 -0400 Subject: [PATCH 2/6] just 4 bytes, and move all of them --- FF1Lib/Hacks.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/FF1Lib/Hacks.cs b/FF1Lib/Hacks.cs index 95ef8b12a..5be4304c2 100644 --- a/FF1Lib/Hacks.cs +++ b/FF1Lib/Hacks.cs @@ -53,10 +53,12 @@ public void MoveSmokeSpriteVariables() var smokeSpriteCode = Get(SmokeSpriteReplaceStart, SmokeSpriteReplaceEnd - SmokeSpriteReplaceStart); smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68AF }), Blob.FromUShorts(new ushort[] { 0x686C })); + smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B0 }), Blob.FromUShorts(new ushort[] { 0x686D })); smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B1 }), Blob.FromUShorts(new ushort[] { 0x686E })); - smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B3 }), Blob.FromUShorts(new ushort[] { 0x6870 })); - smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B4 }), Blob.FromUShorts(new ushort[] { 0x6871 })); - smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B5 }), Blob.FromUShorts(new ushort[] { 0x6872 })); + smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B2 }), Blob.FromUShorts(new ushort[] { 0x686F })); + //smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B3 }), Blob.FromUShorts(new ushort[] { 0x6870 })); + //smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B4 }), Blob.FromUShorts(new ushort[] { 0x6871 })); + //smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B5 }), Blob.FromUShorts(new ushort[] { 0x6872 })); Put(SmokeSpriteReplaceStart, smokeSpriteCode); } From fe4a66364cae455b59b82a2fd7f8b72fc14248ad Mon Sep 17 00:00:00 2001 From: Entroper Date: Sun, 20 Sep 2020 01:26:09 -0400 Subject: [PATCH 3/6] new RNG code --- FF1Lib/FF1Rom.cs | 2 +- FF1Lib/Hacks.cs | 13 +++++- FF1Lib/asm/LCG.asm | 112 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 2 deletions(-) diff --git a/FF1Lib/FF1Rom.cs b/FF1Lib/FF1Rom.cs index b4b7f6b78..cd5c4d570 100644 --- a/FF1Lib/FF1Rom.cs +++ b/FF1Lib/FF1Rom.cs @@ -106,7 +106,7 @@ public void Randomize(Blob seed, Flags flags, Preferences preferences) FixWarpBug(); // The warp bug must be fixed for magic level shuffle and spellcrafter SeparateUnrunnables(); UpdateDialogs(); - MoveSmokeSpriteVariables(); + ReplaceBattleRNG(rng); flags = Flags.ConvertAllTriState(flags, rng); diff --git a/FF1Lib/Hacks.cs b/FF1Lib/Hacks.cs index 5be4304c2..0647ac6c8 100644 --- a/FF1Lib/Hacks.cs +++ b/FF1Lib/Hacks.cs @@ -45,13 +45,16 @@ public partial class FF1Rom : NesRom public const int SmokeSpriteReplaceStart = 0x317E9; public const int SmokeSpriteReplaceEnd = 0x31A2C; - public void MoveSmokeSpriteVariables() + public const int BattleRNGOffset = 0x7FCE7; + + public void ReplaceBattleRNG(MT19337 rng) { // This moves some temporary memory locations used to draw the smoke effect sprites // in battle to the same locations used to store attacker stats. These can overwrite // each other without issue, and it frees up some space for a few bytes of RNG state. var smokeSpriteCode = Get(SmokeSpriteReplaceStart, SmokeSpriteReplaceEnd - SmokeSpriteReplaceStart); + // We only need 4 bytes, and moving the others seems to mess some stuff up. smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68AF }), Blob.FromUShorts(new ushort[] { 0x686C })); smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B0 }), Blob.FromUShorts(new ushort[] { 0x686D })); smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B1 }), Blob.FromUShorts(new ushort[] { 0x686E })); @@ -61,6 +64,14 @@ public void MoveSmokeSpriteVariables() //smokeSpriteCode.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B5 }), Blob.FromUShorts(new ushort[] { 0x6872 })); Put(SmokeSpriteReplaceStart, smokeSpriteCode); + + // LCG.asm + Put(BattleRngOffset, Blob.FromHex("ADAF68AAAD57FD205FFD186D5BFD9020E8188DAF688A8DB368ADB068AAAD58FD205FFD186D5CFD9002E8186DB3689002E8188DB0688A8DB368ADB168AAAD59FD205FFD186D5DFD9002E8186DB3689002E8188DB1688A8DB368ADB268AAAD5AFD205FFD186D5EFD186DB368188DB26860054B56AC000000008DB3688EB468A208A9008DB5684EB3689004186DB4686A6EB568CAD0F0AAADB56860")); + + // Choose a random odd number for c in the LCG. + uint c = rng.Next(); + c |= 0x00000001; + Put(BattleRngOffset + 4, Blob.FromUInts(new[] { c })); } // Required for npc quest item randomizing diff --git a/FF1Lib/asm/LCG.asm b/FF1Lib/asm/LCG.asm index 910d77c29..85a14ddb2 100644 --- a/FF1Lib/asm/LCG.asm +++ b/FF1Lib/asm/LCG.asm @@ -4,4 +4,116 @@ ; A research paper on selecting good parameters for the multiplier: ; https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.34.1024&rep=rep1&type=pdf +; We'll use m = 2891336453, or 0xAC564B05 from Table 4. +; Any odd integer will do for c, so we'll take a random value. + + +* = $FCE7 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Bank 0F, $FCE7 (BattleRNG) ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +battle_rng_state = $68AF + +BattleRNG: + LDA battle_rng_state ; Get the first byte of state + TAX + LDA battle_rng_m ; Get the first byte of m + JSR MultiplyXA ; Multiply state by m + CLC + ADC battle_rng_c ; Add c + BCC :+ + INX ; Increment high bits if necessary + CLC + : + STA battle_rng_state ; Store the low bits back to state + TXA + STA btltmp_multA ; Save the high bits for the next step + + LDA battle_rng_state + 1 ; Now do it again for the next byte of state + TAX + LDA battle_rng_m + 1 + JSR MultiplyXA + CLC + ADC battle_rng_c + 1 + BCC :+ + INX + CLC + : + ADC btltmp_multA ; Add the high bits from the previous step + BCC :+ + INX + CLC + : + STA battle_rng_state + 1 + TXA + STA btltmp_multA + + LDA battle_rng_state + 2 + TAX + LDA battle_rng_m + 2 + JSR MultiplyXA + CLC + ADC battle_rng_c + 2 + BCC :+ + INX + CLC + : + ADC btltmp_multA + BCC :+ + INX + CLC + : + STA battle_rng_state + 2 + TXA + STA btltmp_multA + + LDA battle_rng_state + 3 ; Last byte + TAX + LDA battle_rng_m + 3 + JSR MultiplyXA + CLC + ADC battle_rng_c + 3 + CLC ; No need to save the high bits, so just CLC + ADC btltmp_multA + CLC ; Just in case + STA battle_rng_state + 3 + + ; And we're done. A already has the highest bits of state, and that's what we want to return. + RTS + +battle_rng_m: + .BYTE $05, $4B, $56, $AC ; m +battle_rng_c: + .BYTE $00, $00, $00, $00 ; c (this will be replaced by the randomizer) + + + +; MultiplyXA copied from bank 0B +btltmp_multA = $68B3 +btltmp_multB = $68B4 +btltmp_multC = $68B5 + +MultiplyXA: + STA btltmp_multA ; store the values we'll be multiplying + STX btltmp_multB + LDX #$08 ; Use x as a loop counter. X=8 for 8 bits + + LDA #$00 ; A will be the high byte of the product + STA btltmp_multC ; multC will be the low byte + + ; For each bit in multA + @Loop: + LSR btltmp_multA ; shift out the low bit + BCC :+ + CLC ; if it was set, add multB to our product + ADC btltmp_multB + : ROR A ; then rotate down our product + ROR btltmp_multC + DEX + BNE @Loop + + TAX ; put high bits of product in X + LDA btltmp_multC ; put low bits in A + RTS From 1a8665fe0f46f060cc0fa4c0fea5e8d670f6dd62 Mon Sep 17 00:00:00 2001 From: Entroper Date: Sun, 20 Sep 2020 02:07:11 -0400 Subject: [PATCH 4/6] rename m -> a to match whitepaper, fix offset for c --- FF1Lib/Hacks.cs | 2 +- FF1Lib/asm/LCG.asm | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/FF1Lib/Hacks.cs b/FF1Lib/Hacks.cs index 0647ac6c8..80bfbb306 100644 --- a/FF1Lib/Hacks.cs +++ b/FF1Lib/Hacks.cs @@ -71,7 +71,7 @@ public void ReplaceBattleRNG(MT19337 rng) // Choose a random odd number for c in the LCG. uint c = rng.Next(); c |= 0x00000001; - Put(BattleRngOffset + 4, Blob.FromUInts(new[] { c })); + Put(BattleRngOffset + 0x74, Blob.FromUInts(new[] { c })); } // Required for npc quest item randomizing diff --git a/FF1Lib/asm/LCG.asm b/FF1Lib/asm/LCG.asm index 85a14ddb2..49ca77d5f 100644 --- a/FF1Lib/asm/LCG.asm +++ b/FF1Lib/asm/LCG.asm @@ -4,7 +4,7 @@ ; A research paper on selecting good parameters for the multiplier: ; https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.34.1024&rep=rep1&type=pdf -; We'll use m = 2891336453, or 0xAC564B05 from Table 4. +; We'll use a = 2891336453, or 0xAC564B05 from Table 4. ; Any odd integer will do for c, so we'll take a random value. @@ -19,7 +19,7 @@ battle_rng_state = $68AF BattleRNG: LDA battle_rng_state ; Get the first byte of state TAX - LDA battle_rng_m ; Get the first byte of m + LDA battle_rng_a ; Get the first byte of m JSR MultiplyXA ; Multiply state by m CLC ADC battle_rng_c ; Add c @@ -33,7 +33,7 @@ BattleRNG: LDA battle_rng_state + 1 ; Now do it again for the next byte of state TAX - LDA battle_rng_m + 1 + LDA battle_rng_a + 1 JSR MultiplyXA CLC ADC battle_rng_c + 1 @@ -52,7 +52,7 @@ BattleRNG: LDA battle_rng_state + 2 TAX - LDA battle_rng_m + 2 + LDA battle_rng_a + 2 JSR MultiplyXA CLC ADC battle_rng_c + 2 @@ -71,7 +71,7 @@ BattleRNG: LDA battle_rng_state + 3 ; Last byte TAX - LDA battle_rng_m + 3 + LDA battle_rng_a + 3 JSR MultiplyXA CLC ADC battle_rng_c + 3 @@ -83,10 +83,10 @@ BattleRNG: ; And we're done. A already has the highest bits of state, and that's what we want to return. RTS -battle_rng_m: - .BYTE $05, $4B, $56, $AC ; m +battle_rng_a: + .BYTE $05, $4B, $56, $AC battle_rng_c: - .BYTE $00, $00, $00, $00 ; c (this will be replaced by the randomizer) + .BYTE $00, $00, $00, $00 ; (this will be replaced by the randomizer) From 3fa376bb5601cf1cba29798889f5db4442d77f5f Mon Sep 17 00:00:00 2001 From: Entroper Date: Sun, 20 Sep 2020 17:55:17 -0400 Subject: [PATCH 5/6] bug fixes --- FF1Lib/FF1Rom.cs | 10 +++++----- FF1Lib/Hacks.cs | 31 +++++++++++++++++++++++++++---- FF1Lib/asm/LCG.asm | 42 +++++++++++++++++++++--------------------- 3 files changed, 53 insertions(+), 30 deletions(-) diff --git a/FF1Lib/FF1Rom.cs b/FF1Lib/FF1Rom.cs index cd5c4d570..7b1f9f4bf 100644 --- a/FF1Lib/FF1Rom.cs +++ b/FF1Lib/FF1Rom.cs @@ -15,7 +15,7 @@ namespace FF1Lib public partial class FF1Rom : NesRom { public const int RngOffset = 0x7F100; - public const int BattleRngOffset = 0x7FCF1; + public const int BattleRngLutOffset = 0x7FCF1; public const int RngSize = 256; public const int LevelRequirementsOffset = 0x6CC81; @@ -1075,10 +1075,10 @@ public void FixMissingBattleRngEntry() { // of the 256 entries in the battle RNG table, the 98th entry (index 97) is a duplicate '00' where '95' hex / 149 int is absent. // you could arbitrarily choose the other '00', the 111th entry (index 110), to replace instead - var battleRng = Get(BattleRngOffset, RngSize).Chunk(1).ToList(); + var battleRng = Get(BattleRngLutOffset, RngSize).Chunk(1).ToList(); battleRng[97] = Blob.FromHex("95"); - Put(BattleRngOffset, battleRng.SelectMany(blob => blob.ToBytes()).ToArray()); + Put(BattleRngLutOffset, battleRng.SelectMany(blob => blob.ToBytes()).ToArray()); } public void ShuffleRng(MT19337 rng) @@ -1088,10 +1088,10 @@ public void ShuffleRng(MT19337 rng) Put(RngOffset, rngTable.SelectMany(blob => blob.ToBytes()).ToArray()); - var battleRng = Get(BattleRngOffset, RngSize).Chunk(1).ToList(); + var battleRng = Get(BattleRngLutOffset, RngSize).Chunk(1).ToList(); battleRng.Shuffle(rng); - Put(BattleRngOffset, battleRng.SelectMany(blob => blob.ToBytes()).ToArray()); + Put(BattleRngLutOffset, battleRng.SelectMany(blob => blob.ToBytes()).ToArray()); } } diff --git a/FF1Lib/Hacks.cs b/FF1Lib/Hacks.cs index 80bfbb306..d2e7a6685 100644 --- a/FF1Lib/Hacks.cs +++ b/FF1Lib/Hacks.cs @@ -44,8 +44,14 @@ public partial class FF1Rom : NesRom public const int SmokeSpriteReplaceStart = 0x317E9; public const int SmokeSpriteReplaceEnd = 0x31A2C; + public const int Bank0BRandAXReplaceStart = 0x2DF1D; + public const int Bank0BRandAXReplaceEnd = 0x2DF3B; + public const int Bank0CRandAXReplaceStart = 0x32E5D; + public const int Bank0CRandAXReplaceEnd = 0x32E7B; + public const int CombatBoxReplaceStart = 0x320A2; + public const int CombatBoxReplaceEnd = 0x320CD; - public const int BattleRNGOffset = 0x7FCE7; + public const int BattleRngCodeOffset = 0x7FCE7; public void ReplaceBattleRNG(MT19337 rng) { @@ -65,13 +71,30 @@ public void ReplaceBattleRNG(MT19337 rng) Put(SmokeSpriteReplaceStart, smokeSpriteCode); - // LCG.asm - Put(BattleRngOffset, Blob.FromHex("ADAF68AAAD57FD205FFD186D5BFD9020E8188DAF688A8DB368ADB068AAAD58FD205FFD186D5CFD9002E8186DB3689002E8188DB0688A8DB368ADB168AAAD59FD205FFD186D5DFD9002E8186DB3689002E8188DB1688A8DB368ADB268AAAD5AFD205FFD186D5EFD186DB368188DB26860054B56AC000000008DB3688EB468A208A9008DB5684EB3689004186DB4686A6EB568CAD0F0AAADB56860")); + // RandAX uses these locations, too. + var randAX = Get(Bank0BRandAXReplaceStart, Bank0BRandAXReplaceEnd - Bank0BRandAXReplaceStart); + randAX.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68AF }), Blob.FromUShorts(new ushort[] { 0x686C })); + randAX.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B0 }), Blob.FromUShorts(new ushort[] { 0x686D })); + Put(Bank0BRandAXReplaceStart, randAX); + + // There are two copies of RandAX in different banks, so we have to do this again. + randAX = Get(Bank0CRandAXReplaceStart, Bank0CRandAXReplaceEnd - Bank0CRandAXReplaceStart); + randAX.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68AF }), Blob.FromUShorts(new ushort[] { 0x686C })); + randAX.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B0 }), Blob.FromUShorts(new ushort[] { 0x686D })); + Put(Bank0CRandAXReplaceStart, randAX); + + // One more usage of this space. + var combatBox = Get(CombatBoxReplaceStart, CombatBoxReplaceEnd - CombatBoxReplaceStart); + combatBox.ReplaceInPlace(Blob.FromUShorts(new ushort[] { 0x68B1 }), Blob.FromUShorts(new ushort[] { 0x686E })); + Put(CombatBoxReplaceStart, combatBox); + + // Now the good stuff. Write LCG.asm in place of BattleRNG. + Put(BattleRngCodeOffset, Blob.FromHex("8A48ADAF68AE51FD2059FD186D55FD9002E8188DAF688610ADB068AE52FD2059FD186D56FD9002E81865109002E8188DB0688610ADB168AE53FD2059FD186D57FD9002E81865109002E8188DB1688610ADB268AE54FD2059FD186D58FD186510188DB26868AAADB26860054B56AC0000000085118612A208A9008513461190031865126A6613CAD0F3AAA51360")); // Choose a random odd number for c in the LCG. uint c = rng.Next(); c |= 0x00000001; - Put(BattleRngOffset + 0x74, Blob.FromUInts(new[] { c })); + Put(BattleRngCodeOffset + 0x6E, Blob.FromUInts(new[] { c })); } // Required for npc quest item randomizing diff --git a/FF1Lib/asm/LCG.asm b/FF1Lib/asm/LCG.asm index 49ca77d5f..ff353416d 100644 --- a/FF1Lib/asm/LCG.asm +++ b/FF1Lib/asm/LCG.asm @@ -15,11 +15,14 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; battle_rng_state = $68AF +tmp = $10 BattleRNG: + TXA + PHA ; Push X onto the stack, because we'll clobber it + LDA battle_rng_state ; Get the first byte of state - TAX - LDA battle_rng_a ; Get the first byte of m + LDX battle_rng_a ; Get the first byte of m JSR MultiplyXA ; Multiply state by m CLC ADC battle_rng_c ; Add c @@ -28,12 +31,10 @@ BattleRNG: CLC : STA battle_rng_state ; Store the low bits back to state - TXA - STA btltmp_multA ; Save the high bits for the next step + STX tmp ; Save the high bits for the next step LDA battle_rng_state + 1 ; Now do it again for the next byte of state - TAX - LDA battle_rng_a + 1 + LDX battle_rng_a + 1 JSR MultiplyXA CLC ADC battle_rng_c + 1 @@ -41,18 +42,16 @@ BattleRNG: INX CLC : - ADC btltmp_multA ; Add the high bits from the previous step + ADC tmp ; Add the high bits from the previous step BCC :+ INX CLC : STA battle_rng_state + 1 - TXA - STA btltmp_multA + STX tmp LDA battle_rng_state + 2 - TAX - LDA battle_rng_a + 2 + LDX battle_rng_a + 2 JSR MultiplyXA CLC ADC battle_rng_c + 2 @@ -60,27 +59,28 @@ BattleRNG: INX CLC : - ADC btltmp_multA + ADC tmp BCC :+ INX CLC : STA battle_rng_state + 2 - TXA - STA btltmp_multA + STX tmp LDA battle_rng_state + 3 ; Last byte - TAX - LDA battle_rng_a + 3 + LDX battle_rng_a + 3 JSR MultiplyXA CLC ADC battle_rng_c + 3 CLC ; No need to save the high bits, so just CLC - ADC btltmp_multA + ADC tmp CLC ; Just in case STA battle_rng_state + 3 - ; And we're done. A already has the highest bits of state, and that's what we want to return. + PLA + TAX ; Restore X from the stack + + LDA battle_rng_state + 3 ; We want to return the highest byte of state. RTS battle_rng_a: @@ -91,9 +91,9 @@ battle_rng_c: ; MultiplyXA copied from bank 0B -btltmp_multA = $68B3 -btltmp_multB = $68B4 -btltmp_multC = $68B5 +btltmp_multA = $11 +btltmp_multB = $12 +btltmp_multC = $13 MultiplyXA: STA btltmp_multA ; store the values we'll be multiplying From 0c7fd991e4625e54d1dee958a6f04559e8522c39 Mon Sep 17 00:00:00 2001 From: Entroper Date: Sat, 30 Mar 2024 21:38:07 -0400 Subject: [PATCH 6/6] update citation --- FF1Lib/asm/LCG.asm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/FF1Lib/asm/LCG.asm b/FF1Lib/asm/LCG.asm index ff353416d..8aa6485d3 100644 --- a/FF1Lib/asm/LCG.asm +++ b/FF1Lib/asm/LCG.asm @@ -3,7 +3,10 @@ ; This is a simple but effective RNG with a much longer period than FF1's. ; A research paper on selecting good parameters for the multiplier: -; https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.34.1024&rep=rep1&type=pdf +; MATHEMATICS OF COMPUTATION +; Volume 68, Number 225, January 1999, Pages 249–260 +; S 0025-5718(99)00996-5 +; https://www.ams.org/journals/mcom/1999-68-225/S0025-5718-99-00996-5/S0025-5718-99-00996-5.pdf ; We'll use a = 2891336453, or 0xAC564B05 from Table 4. ; Any odd integer will do for c, so we'll take a random value.