Wii VC Round-to-Zero: Difference between revisions

From Ukikipedia
Jump to navigation Jump to search
mNo edit summary
mNo edit summary
 
(4 intermediate revisions by 3 users not shown)
Line 1: Line 1:
[[File:WiiVC BitFS.png|thumb|300px|right|Stream by Switchpalacecorner, after 71.5 hours of raising in Wii Virtual Console]]  
[[File:WiiVC BitFS.png|thumb|300px|right|Stream by Switchpalacecorner, after 71.5 hours of raising in Wii Virtual Console]]  
[[File:WiiVC BitFS 146hr.png|thumb|300px|right|After 146.5 hours (6 days 2.5 hrs)]]
[[File:WiiVC BitFS 146hr.png|thumb|300px|right|After 146.5 hours (6d 2h 30m)]]  
[[File:Original-bitfs-raising.jpg|thumb|The original BitFS platform raising discovery by Andru! on May 10th 2018.]]
'''Wii VC Round-to-Zero''' is an emulation inaccuracy in the [[Virtual Console|Wii Virtual Console]] (Wii VC) version of ''[[Super Mario 64]]''.
'''Wii VC Round-to-Zero''' refers to an emulation inaccuracy in the [[Virtual Console|Wii Virtual Console]] (Wii VC) version of SM64. When converting a double precision floating point number (a "double") to a single precision floating point number (a "float"), the N64 typically uses the round-to-nearest rounding mode. This means that if the double is not exactly equal to a float, it will round up or down to the float that is closest in value to the original number. While this behavior is accurately emulated by most emulators and the Wii U VC, the SM64 Wii VC instead uses the round-to-zero mode. This mode will always round the double up if it is negative, and round it down if it is positive.


While the programmers did not intentionally use doubles very often, favoring single precision floats instead, they occasionally used them accidentally.
When converting a [[wikipedia:Double-precision floating-point format|double-precision floating point number]] (a ''double'') to a [[wikipedia:Single-precision floating-point format|single-precision floating point number]] (a ''float''), some doubles will have to be [[wikipedia:Rounding|rounded]] to a nearby float. There is a discrepancy in the rounding mode used:
*The Nintendo 64 typically uses the [[wikipedia:Rounding#Rounding to the nearest integer|''round-to-nearest'']] mode. This means that if the double is not exactly equal to a float, it will round up or down to the float that is closest in value to the original number. This behavior is accurately emulated by most emulators and the Wii U VC.
*The Wii VC instead effectively uses a [[wikipedia:Rounding#Rounding toward zero|''round-toward-zero'']] mode. This mode rounds all positive doubles down, and all negative doubles up, to the nearest float.


<syntaxhighlight lang="c">
While the programmers did not intentionally use doubles very often (favoring single-precision floats instead), they occasionally used them accidentally, causing extra double-to-single conversions in many places in the game's source code. The rounding involved creates discrepancies in the Wii VC version of the game, such as slightly different [[wikipedia:Normal (geometry)|surface normals]], which build up over time throughout gameplay. For this reason, [[TAS]]es produced for either N64 or Wii VC cannot generally be played back on the other platform without desync.
 
Technically, the Wii VC version of ''Super Mario 64'' does set the Wii's CPU to round-to-nearest. However, storing a double to the Wii's floating point registers and reading it back as a single is [[wikipedia:Undefined behavior|undefined behavior]]. In practice, doing so ends up discarding the lower bits of the [[wikipedia:Significand|mantissa]]&mdash;a [[wikipedia:Truncation|truncation]], which rounds toward zero. (The opposite situation, storing a single and reading it back as a double, is supported and converted correctly.)<ref name="dolphin">[https://dolphin-emu.org/blog/2018/07/06/dolphin-progress-report-june-2018 "Dolphin Progress Report: June 2018"]</ref>
 
==Platform drift==
In [[Bowser in the Fire Sea]], round-to-zero causes a bug known as '''Wii VC platform drift''', where the oscillating platforms that sink into lava will slowly rise up to the height of the [[wikipedia:Origin (mathematics)|origin]].
 
This code controls the oscillation of these platforms. The variable <code>y</code> represents the platform's height.
 
<syntaxhighlight lang="C" line='line'>
y -= sins(t) * 0.58;
y -= sins(t) * 0.58;
t += 0x100;
t += 0x100;
</syntaxhighlight>
</syntaxhighlight>


In the above code sample, <code>y</code> and <code>sins(t)</code> are both single precision floats. However, in C, the numeric literal <code>0.58</code> is a double since it lacks a trailing <code>f</code>, as in <code>0.58f</code>. This causes the expression to evaluate to a double, which is then converted to a float before being stored in <code>y</code>. Examples such as this one arise in many places in the game's source code, and over time they result in large discrepancies between the N64 and Wii VC versions of the game. For this reason, TASes produced for one of these platforms cannot generally be played back on the other without desync.
Here, <code>y</code> and <code>sins(t)</code> are both single-precision floats. However, in C, the [[wikipedia:Literal (computer programming)|numeric literal]] <code>0.58</code> is a double since it lacks a trailing <code>f</code>, as in <code>0.58f</code>. This causes the expression <code>sins(t) * 0.58</code> to evaluate to a double, which is then converted back to a float for the subtraction-assignment to <code>y</code>.
 
Using the N64's rounding mode, <code>y</code> will repeatedly return to its original value and continue oscillating with fixed maximum height. Even though each double-to-single conversion produces a small amount of error, this error balances out over the course of the platform's cycle. On the Wii VC, however, the error always accumulates in the same direction (upward if the platform is below <var>y</var> = 0, and downward if the platform is above <var>y</var> = 0), and so the platform slowly drifts in the vertical direction.
 
The platforms in BitFS are below <var>y</var> = 0, and so they gradually rise upward. As a result, the bug is of interest to the [[A Button Challenge]], since it lets Mario ride the platforms up to skip a pole that requires an A press to dismount.
 
===Drift speed===
From its starting position of height <var>y</var> = &minus;3065 until it reaches <var>y</var> = &minus;2048, this platform rises 254 times by <math display="inline">\frac{1}{4096}</math>th of a unit for each 256-frame cycle: 127 times as it goes up, 127 times as it goes down (at the peak and bottom of the oscillation cycle, the platform stays still so no rounding occurs). So the platform initially drifts by about 13.08 units per hour:


The example given above is taken from the code controlling the oscillation of the platforms at the beginning of [[Bowser in the Fire Sea]]. Using the N64's rounding mode, the <code>y</code> variable, which represents the platform's height, will repeatedly return to its original value and continue oscillating with fixed maximum height. Even though each double to single conversion produces a small amount of error, this error balances out over the course of the platform's cycle. On the Wii VC, however, the error always accumulates in the same direction (upward if the platform is below ''y'' = 0, and downward if the platform is above ''y'' = 0), and so the platform slowly drifts in the vertical direction. This is known as '''Wii VC platform drift'''.
:<math>\frac{127}{256} \times \frac{1}{4096}\ \frac{\mathrm{units}}{\mathrm{frames}} \times \left(30\ \frac{\mathrm{frames}}{\mathrm{s}} \times 60\ \frac{\mathrm{s}}{\mathrm{min}} \times 60\ \frac{\mathrm{min}}{\mathrm{h}}\right) \approx 13.0806\ \frac{\mathrm{units}}{\mathrm{h}}</math>


From its starting position of y=-3065 til it reaches y=-2048, this platform rises 254 times by 1/4096th of a unit for each 256 frame cycle, 127 times as it goes up, 127 times as it goes down (at the peak and bottom of the oscillation cycle, the platform stays still so no rounding occurs). So doing the math, 127/128*1/4096units*30frames*60seconds*60minutes, we get that the platform drifts by ~13.0806 units/h.
The floating-point numbers are distributed non-uniformly; the gap between floats doubles after each [[wikipedia:Power of two|power of two]], so their number line is denser closer to zero. Because the platform drifts toward zero, the error factor (initially <math display="inline">\frac{1}{4096}</math>) is halved after certain thresholds, and so the platform's net speed is also halved. First, above <var>y</var> = &minus;2048, it drops to about 6.54 units per hour. It continues to halve in net speed as it passes through <var>y</var> = &minus;1024, <var>y</var> = &minus;512, and all other <math display="inline">y = 2^n</math> boundaries, until it stops around <var>y</var> = 0.


Once it passes y=-2048, the platform halves in rising speed: ~6.5403 units/h. The platform continues to halve in speed as it passes through y=-1024, y=-512, y=-256, and all other 2^n boundaries til it comes to a halt near y=0.
Each time the platform oscillates through one of these boundaries, its behavior becomes complex. Throughout its cycle, the platform is sometimes above <math display="inline">y = 2^n</math> and sometimes below. The overall speed decrease as the platform's average height passes through the boundary is represented by the [[wikipedia:Differential equation|differential equation]]
:<math>v_y = \frac{dy}{dt} = \arccos\left(y\right)</math>
The platform suddenly slows down quickly, then at a slower rate before it decelerates faster again, and finally, suddenly the platform's acceleration completely stops, the speed settling at a constant, half of what it started with.


As it passes through a 2^n boundary, its behavior becomes complex. Throughout its up-down oscillation cycle, the platform is sometimes past the 2^n boundary and sometimes not. The overall speed decrease as the platform's average height passes through the 2^n boundary is represented by a differential equation of <code>[velocity of y]=dy/dt=arccos(y)</code>: the platform suddenly slows down quickly, then at a slower rate before it decelerates faster again, and finally, suddenly the platform's acceleration completely stops, the speed settling at a constant, half of what it started with.
Over the course of several days, the platforms rise high enough for Mario to reach the elevator past the pole. It takes eight days for the platform to reach high enough for a dive recover to land in the elevator shaft, but using [[Vertical Speed Conservation|VSC]] with a lava boost requires only three days.


The platforms at the beginning of the level are below ''y'' = 0, and so they gradually rise upward. Over the course of several days, they are high enough they can be used to reach the elevator past the pole, which previously required one A press to get past. It takes eight days for the platform to reach high enough for a dive recover to land in the elevator shaft, but using [[Vertical Speed Conservation|VSC]] with a lava boost requires only three days.
==History==
[[File:Original-bitfs-raising.jpg|thumb|The original BitFS platform raising discovery by Andru! on May 10, 2018.]]
The platform-drift bug was discovered by [https://www.youtube.com/channel/UCz8K5WhRhrjFRNGvkWupUlQ xAndru!] on May 10, 2018. He had left his Wii powered on overnight while in Bowser in the Fire Sea, and noticed that the oscillating platforms had risen above their original positions.<ref>[https://www.youtube.com/watch?v=MFxJuq3FRgI "Bowser in the Fire Sea in 0x A Presses? (New Glitch Explanation)" by bad_boot]</ref>


The glitch was discovered by '''[https://www.youtube.com/channel/UCz8K5WhRhrjFRNGvkWupUlQ xAndru!]''' when he left his Wii powered on overnight while in Bowser in the Fire Sea and noticed that the oscillating platforms had risen above their original positions.<ref>[https://www.youtube.com/watch?v=MFxJuq3FRgI "Bowser in the Fire Sea in 0x A Presses? (New Glitch Explanation)" by bad_boot]</ref>
After several failed attempts by various players, the A press save in [[Bowser in the Fire Sea]] was console verified RTA on June 20, 2018 by Emilia Blue.<ref>[https://youtu.be/B1m-5LWOxW0 "BitFS 0x A Presses" by Emilia Blue]</ref> Six days later, pannenkoek2012 TASed the red coin star in zero A presses using a modified Nintendo 64 emulator that replicated the rounding error.<ref>[https://youtu.be/Aa_CciaM4aM "Bowser in the Fire Sea with Red Coins 0x A Presses (Wii VC Only)" by UncommentatedPannen]</ref>


The bug does not occur in any later Wii VC releases, nor in the Wii U VC or in any mainstream N64 emulators. At the time of its discovery, the bug did not occur on the Dolphin emulator, but has since been corrected.<ref>[https://dolphin-emu.org/blog/2018/07/06/dolphin-progress-report-june-2018 "Dolphin Progress Report: June 2018"]</ref>
==Scope==
The bug does not occur in any later Wii VC releases, nor in the Wii U VC or in any mainstream Nintendo 64 emulators. At the time of its discovery, the bug did not occur on the [[wikipedia:Dolphin (emulator)|Dolphin]] emulator, but was corrected in 2018 to match the behavior of the Wii hardware.<ref name="dolphin" />


No objects other than the sinking platforms are known to have similarly exploitable behavior, though the rounding bug does cause many discrepancies between the original and Wii VC versions of the game, including slightly different surface normals.
No objects other than the sinking platforms are known to have similarly exploitable behavior. (However, as said, the rounding inaccuracy does cause other discrepancies between the versions of the game.)


==Applications==
This bug does not affect the sinking platforms in [[Lethal Lava Land]] because they are already located at <var>y</var> = 0.
After several failed attempts by various players, the A press save in Bowser in the Fire Sea was console verified RTA on 20 Jun 2018 by ethanwhitesm64.<ref>[https://youtu.be/B1m-5LWOxW0 "BitFS 0x A Presses" by Ethan White]</ref> Six days later, pannenkoek2012 TASed the red coin star in zero A presses using a modified N64 emulator that replicated the rounding error.<ref>[https://youtu.be/Aa_CciaM4aM "Bowser in the Fire Sea with Red Coins 0x A Presses (Wii VC Only)" by UncommentatedPannen]</ref>


==References==
==References==
<references/>
<references />


<!-- Category -->
<!-- Category -->

Latest revision as of 22:50, 10 May 2025

Stream by Switchpalacecorner, after 71.5 hours of raising in Wii Virtual Console
After 146.5 hours (6d 2h 30m)

Wii VC Round-to-Zero is an emulation inaccuracy in the Wii Virtual Console (Wii VC) version of Super Mario 64.

When converting a double-precision floating point number (a double) to a single-precision floating point number (a float), some doubles will have to be rounded to a nearby float. There is a discrepancy in the rounding mode used:

  • The Nintendo 64 typically uses the round-to-nearest mode. This means that if the double is not exactly equal to a float, it will round up or down to the float that is closest in value to the original number. This behavior is accurately emulated by most emulators and the Wii U VC.
  • The Wii VC instead effectively uses a round-toward-zero mode. This mode rounds all positive doubles down, and all negative doubles up, to the nearest float.

While the programmers did not intentionally use doubles very often (favoring single-precision floats instead), they occasionally used them accidentally, causing extra double-to-single conversions in many places in the game's source code. The rounding involved creates discrepancies in the Wii VC version of the game, such as slightly different surface normals, which build up over time throughout gameplay. For this reason, TASes produced for either N64 or Wii VC cannot generally be played back on the other platform without desync.

Technically, the Wii VC version of Super Mario 64 does set the Wii's CPU to round-to-nearest. However, storing a double to the Wii's floating point registers and reading it back as a single is undefined behavior. In practice, doing so ends up discarding the lower bits of the mantissa—a truncation, which rounds toward zero. (The opposite situation, storing a single and reading it back as a double, is supported and converted correctly.)[1]

Platform drift

In Bowser in the Fire Sea, round-to-zero causes a bug known as Wii VC platform drift, where the oscillating platforms that sink into lava will slowly rise up to the height of the origin.

This code controls the oscillation of these platforms. The variable y represents the platform's height.

y -= sins(t) * 0.58;
t += 0x100;

Here, y and sins(t) are both single-precision floats. However, in C, the numeric literal 0.58 is a double since it lacks a trailing f, as in 0.58f. This causes the expression sins(t) * 0.58 to evaluate to a double, which is then converted back to a float for the subtraction-assignment to y.

Using the N64's rounding mode, y will repeatedly return to its original value and continue oscillating with fixed maximum height. Even though each double-to-single conversion produces a small amount of error, this error balances out over the course of the platform's cycle. On the Wii VC, however, the error always accumulates in the same direction (upward if the platform is below y = 0, and downward if the platform is above y = 0), and so the platform slowly drifts in the vertical direction.

The platforms in BitFS are below y = 0, and so they gradually rise upward. As a result, the bug is of interest to the A Button Challenge, since it lets Mario ride the platforms up to skip a pole that requires an A press to dismount.

Drift speed

From its starting position of height y = −3065 until it reaches y = −2048, this platform rises 254 times by th of a unit for each 256-frame cycle: 127 times as it goes up, 127 times as it goes down (at the peak and bottom of the oscillation cycle, the platform stays still so no rounding occurs). So the platform initially drifts by about 13.08 units per hour:

The floating-point numbers are distributed non-uniformly; the gap between floats doubles after each power of two, so their number line is denser closer to zero. Because the platform drifts toward zero, the error factor (initially ) is halved after certain thresholds, and so the platform's net speed is also halved. First, above y = −2048, it drops to about 6.54 units per hour. It continues to halve in net speed as it passes through y = −1024, y = −512, and all other boundaries, until it stops around y = 0.

Each time the platform oscillates through one of these boundaries, its behavior becomes complex. Throughout its cycle, the platform is sometimes above and sometimes below. The overall speed decrease as the platform's average height passes through the boundary is represented by the differential equation

The platform suddenly slows down quickly, then at a slower rate before it decelerates faster again, and finally, suddenly the platform's acceleration completely stops, the speed settling at a constant, half of what it started with.

Over the course of several days, the platforms rise high enough for Mario to reach the elevator past the pole. It takes eight days for the platform to reach high enough for a dive recover to land in the elevator shaft, but using VSC with a lava boost requires only three days.

History

The original BitFS platform raising discovery by Andru! on May 10, 2018.

The platform-drift bug was discovered by xAndru! on May 10, 2018. He had left his Wii powered on overnight while in Bowser in the Fire Sea, and noticed that the oscillating platforms had risen above their original positions.[2]

After several failed attempts by various players, the A press save in Bowser in the Fire Sea was console verified RTA on June 20, 2018 by Emilia Blue.[3] Six days later, pannenkoek2012 TASed the red coin star in zero A presses using a modified Nintendo 64 emulator that replicated the rounding error.[4]

Scope

The bug does not occur in any later Wii VC releases, nor in the Wii U VC or in any mainstream Nintendo 64 emulators. At the time of its discovery, the bug did not occur on the Dolphin emulator, but was corrected in 2018 to match the behavior of the Wii hardware.[1]

No objects other than the sinking platforms are known to have similarly exploitable behavior. (However, as said, the rounding inaccuracy does cause other discrepancies between the versions of the game.)

This bug does not affect the sinking platforms in Lethal Lava Land because they are already located at y = 0.

References