Difference between revisions of "Random Number Generator"

From ALttP Speedrunning Wiki
Jump to: navigation, search
 
Line 1: Line 1:
 
{{Technical}}
 
{{Technical}}
 +
 
The '''Random Number Generator''' or '''RNG''' in ''A Link to the Past'' is a weak algorithm used to generate numbers for events.
 
The '''Random Number Generator''' or '''RNG''' in ''A Link to the Past'' is a weak algorithm used to generate numbers for events.
  
== Source ==
+
== Source and explanation ==
The RNG function is found in Bank0D of the game's code. This is how it appears in the MoN disassembly without comments:
+
 
 
<pre>
 
<pre>
; *$6BA71-$6BA7F LONG
+
0DBA71    LDA.w $2137   ; This is a software latch that causes address $213C to update with the horizontal dot position of the scanning beam
GetRandomInt:
+
0DBA74    LDA.w $213C   ; This loads the horizontal dot position of the scanning beam into the A register
LDA $2137
+
0DBA77    ADC.b $1A     ; Add in the current frame counter
LDA $213C : ADC $1A : ADC $0FA1 : STA $0FA1
+
0DBA79    ADC.w $0FA1   ; Add in the previously generated RNG value
RTL
+
0DBA7C    STA.w $0FA1   ; Save the generated value to the RNG cache
 +
0DBA7F    RTL           ; exit
 
</pre>
 
</pre>
  
=== Explanation ===
+
The carry flag is never cleared before or during the routine, so an additional +1 may occur if the sum exceeds 255 at any point.
Looking at each instruction separately, here is an explanation of the code:
 
 
 
<code>LDA $2137</code> is simply reading a hardware register. LDA means "Load into Accumulator" for the address that follows. Address {{HEXA|2137}} is a software latch. When this register is read, it also stores the value of the horizontal pixel counter into address {{HEXA|213C}}.
 
  
<code>LDA $213C</code> is reading the value latched by the previous instruction.
+
After a reset, the RNG cache ({{HEXA|0FA1}}) is always {{HEX|00}}. The RNG cache is also reset to {{HEX|00}} after a save and quit, because it is near the sprite arrays, which are all cleared.
  
<code>ADC $1A</code> [[Wikipedia:Carry flag|adds with carry]] the value in the accumulator and the value in address {{HEXA|1A}}, which is a 1 byte counter that increments every frame the game does not lag, wrapping around to 0 after 255.
+
== Analysis ==
 
 
<code>ADC $0FA1</code> takes that value and adds with carry the current value of {{HEXA|0FA1}}, which is a 1 byte storage for RNG values.
 
 
 
<code>STA $0FA1</code> takes that final value and stores it in the RNG address. As such, this means all RNG values are based on the previous value generated.
 
 
 
=== Analysis ===
 
 
From a mathematical standpoint, this function is not a strong function, even if the fact that it can only generate 256 values is ignored. It is, however, unpredictable enough in real-time that it will seem random enough. Even in a TAS environment, the nature of the horizontal pixel counter makes generating a specific value difficult; however, an understanding of how the number is generated makes creating a different value or generation time trivial without losing even a single frame.
 
From a mathematical standpoint, this function is not a strong function, even if the fact that it can only generate 256 values is ignored. It is, however, unpredictable enough in real-time that it will seem random enough. Even in a TAS environment, the nature of the horizontal pixel counter makes generating a specific value difficult; however, an understanding of how the number is generated makes creating a different value or generation time trivial without losing even a single frame.
  
 
In essence, the use of the horizontal pixel counter means that, in general, more work for the CPU means the function is called later, and the RNG values generated are different. The use of the frame counter means that waiting 1 or more frames may generate a different value, even without intermediate RNG calls.
 
In essence, the use of the horizontal pixel counter means that, in general, more work for the CPU means the function is called later, and the RNG values generated are different. The use of the frame counter means that waiting 1 or more frames may generate a different value, even without intermediate RNG calls.
  
All these conditions present a problem with multiple values per frame. As a simple example, imagine there is an enemy that moves in a random direction when it's spawned, either up or down. If the sprite generates an even number, it will move up; for odd, it will move down. Let's say that Link enters the room walking by holding {{B|<}}, and the RNG generates the values {{HEX2|13}}, {{HEX2|F7}}, and {{HEX2|5A}}. In this instance, enemies A and B move down, while enemy C moves up. Now imagine that Link enters the room walking by holding {{B|<v}}. This provides the CPU slightly more work on that frame. Let's imagine it doesn't add a lag frame, but it does delay the first RNG call. Let's say it took 3 extra horizontal pixels for the first call. Nothing else changed this frame, so the work between each call is the same. Now the values are {{HEX2|16}}, {{HEX2|FA}}, and {{HEX2|5D}}. Now enemies A and B move up, while enemy C moves down. If we continue this example but imagine a change of only one variable (e.g. the RNG value is different, but the movement entering the room was the same), we'll notice that enemies A and B ''always'' move in the same direction, and enemy C ''always'' moves in the opposite direction of those 2. For a more random generation, the expected result is that every value is independent. From our understanding of the function, though, such a set of results is impossible.
+
All these conditions present a problem with multiple values per frame. As a simple example, imagine there is an enemy that moves in a random direction when it's spawned, either up or down. If the sprite generates an even number, it will move up; for odd, it will move down. Let's say that Link enters an area walking by holding {{B|<}}, and the RNG generates the values {{HEX2|13}}, {{HEX2|F7}}, and {{HEX2|5A}}. In this instance, enemies A and B move down, while enemy C moves up. Now imagine that Link enters the same area walking by holding {{B|<v}}. This provides the CPU slightly more work on that frame. Let's imagine it doesn't add a lag frame, but it does delay the first RNG call. Let's say it took 3 extra horizontal pixels for the first call. Nothing else changed this frame, so the work between each call is the same. Now the values are {{HEX2|16}}, {{HEX2|FA}}, and {{HEX2|5D}}. Now enemies A and B move up, while enemy C moves down. If we continue this example but imagine a change of only one variable (e.g. the RNG value is different, but the movement was the same), we'll notice that enemies A and B ''always'' move in the same direction, and enemy C ''always'' moves in the opposite direction of those 2. For a more random generation, the expected result is that every value is independent. From our understanding of the function, though, such a set of results is impossible.
 +
 
 +
=== tl;dr ===
 +
The RNG routine is hard for a human to predict, but it's still not very good. The values are absolutely impossible to manipulate in real time.
  
 
[[Category:Tech]]
 
[[Category:Tech]]

Latest revision as of 16:23, 23 July 2021

The Random Number Generator or RNG in A Link to the Past is a weak algorithm used to generate numbers for events.

Source and explanation

0DBA71    LDA.w $2137    ; This is a software latch that causes address $213C to update with the horizontal dot position of the scanning beam
0DBA74    LDA.w $213C    ; This loads the horizontal dot position of the scanning beam into the A register
0DBA77    ADC.b $1A      ; Add in the current frame counter
0DBA79    ADC.w $0FA1    ; Add in the previously generated RNG value
0DBA7C    STA.w $0FA1    ; Save the generated value to the RNG cache
0DBA7F    RTL            ; exit

The carry flag is never cleared before or during the routine, so an additional +1 may occur if the sum exceeds 255 at any point.

After a reset, the RNG cache ($0FA1) is always 0x00. The RNG cache is also reset to 0x00 after a save and quit, because it is near the sprite arrays, which are all cleared.

Analysis

From a mathematical standpoint, this function is not a strong function, even if the fact that it can only generate 256 values is ignored. It is, however, unpredictable enough in real-time that it will seem random enough. Even in a TAS environment, the nature of the horizontal pixel counter makes generating a specific value difficult; however, an understanding of how the number is generated makes creating a different value or generation time trivial without losing even a single frame.

In essence, the use of the horizontal pixel counter means that, in general, more work for the CPU means the function is called later, and the RNG values generated are different. The use of the frame counter means that waiting 1 or more frames may generate a different value, even without intermediate RNG calls.

All these conditions present a problem with multiple values per frame. As a simple example, imagine there is an enemy that moves in a random direction when it's spawned, either up or down. If the sprite generates an even number, it will move up; for odd, it will move down. Let's say that Link enters an area walking by holding <, and the RNG generates the values $13, $F7, and $5A. In this instance, enemies A and B move down, while enemy C moves up. Now imagine that Link enters the same area walking by holding <v. This provides the CPU slightly more work on that frame. Let's imagine it doesn't add a lag frame, but it does delay the first RNG call. Let's say it took 3 extra horizontal pixels for the first call. Nothing else changed this frame, so the work between each call is the same. Now the values are $16, $FA, and $5D. Now enemies A and B move up, while enemy C moves down. If we continue this example but imagine a change of only one variable (e.g. the RNG value is different, but the movement was the same), we'll notice that enemies A and B always move in the same direction, and enemy C always moves in the opposite direction of those 2. For a more random generation, the expected result is that every value is independent. From our understanding of the function, though, such a set of results is impossible.

tl;dr

The RNG routine is hard for a human to predict, but it's still not very good. The values are absolutely impossible to manipulate in real time.