📜 ⬆️ ⬇️

Screen transitions in Legend of Zelda use the undocumented features of NES

For the vertical scrolling effect, in the first part of “The Legend of Zelda”, the NES graphic “hardware” manipulations are used, most likely not provided for by the console developers.


I do not have access to the official documentation of the Picture Processing Unit (PPU - graphics chip) of the NES console, so my statements about "undefined behavior" are rather closer to conjectures. The graphics hardware specifications I took from the NesDev Wiki . PPU is controlled by writing to registers with memory mapping. If these registers were used in the way that was (it seems) conceived by the designers, then it would be impossible to achieve this effect:


When scrolling the screen vertically, the entire screen should scroll at once. The previous GIF shows an example of partial vertical scrolling. Part of the screen remains stationary (interface elements), and the other part (game area) scrolls vertically. Partial vertical scrolling cannot be implemented with “standard” operation with PPU.
')
In contrast, partial horizontal scrolling is fully defined and possible.


Writing to a separate PPU register at the time of frame drawing can lead to graphical artifacts. The Legend of Zelda intentionally causes an artifact that manifests as a partial vertical scrolling. In this post I will talk a little about the NES graphics hardware and explain how the vertical scrolling trick works.

Types of graphics


The NES console has two types of graphics:


To demonstrate the difference between them, I will show a scene made up of sprites and a background:


And here is the same scene, in which only sprites are visible:


And here is a scene in which only the background is visible:


Scrolling


Image Processor (NES Picture Processor) supports scrolling background images. In the graphics graphics memory, the background is stored as a two-dimensional grid of tiles covering an area twice the width and height of the screen.

A “window” is displayed on this screen in a grid the size of a screen, and the position of this window can be precisely controlled. With the gradual movement of the visible window on the grid creates the effect of smooth scrolling.

The NES output video signal is 256x240 pixels. The grid of tiles inside the memory is represented as a pixel area of ​​512x480 in size and is divided into four screen-sized rectangles, which are called “name tables”. Games can configure the Picture Processing Unit (PPU), indicating the position of the visible window by selecting the pixel coordinates in the grid of name tables.

When selecting a coordinate (0, 0), the entire upper left table of names will be displayed on the screen:


Moving to (125, 181), we will see a little from each name table:


The visible window is minimized to the far part of the tile grid in memory. Moving to (342, 290), we will place the upper left corner of the visible screen inside the lower right name table, and due to folding, parts of each name table will be visible:


Not enough memory!


Each name table has a size of 1 KB, but NES allocates only 2 KB of its video memory to these tables, so only two name tables can fit in memory at a time.

How can there be four tables of names in it?

Mirroring Name Tables


The video memory is connected to the PPU in such a way that when the PPU renders a tile of one of the four seeming name tables, one of the two real tables is actually selected, and the reading is from there. In essence, this means that the four visible name tables are actually composed of two identical pairs of tables.

This image shows a snapshot of the contents of all four tables. The upper left and upper right are the same, as are both lower.


Why not just store two tables of names?

Fortunately, the exact binding between the apparent and real tables can be configured at run time. If the game wants to perform horizontal scrolling, then it adjusts the graphics hardware so that the upper left and upper right tables are different, and they can be scrolled without noticeable duplication. In this configuration, the upper left and lower left tables will refer to one real name table; similarly for the two right tables. This configuration is called "vertical mirroring" (Vertical Mirroring).


There is also another possible configuration - “Horizontal Mirroring” (Horizontal Mirroring), which games use for vertical scrolling.


Usually games do not scroll diagonally, because it creates artifacts around the edges of the screen due to the mirroring of the name tables.

Cartridges


In the cartridge of each game there is an “iron” that allows you to configure the mirroring of the tables.


In some games, there is no need to switch the mirroring at all, so their horizontal or vertical mirroring is rigidly written in their cartridges. Other games dynamically switch between these two modes, so the mirroring in their cartridges is configured programmatically. The Legend of Zelda falls into the second category. Finally, in the cartridges of some truly complex games there is additional video memory, that is, they do not need mirroring at all: they can perform simultaneous scrolling vertically and horizontally without visible duplication artifacts.

Real example



An example of vertical scrolling that is displayed on the screen.


Shown here is a table entry of names with horizontal mirroring. The current visible window is highlighted.

Remember that in the very vertical scrolling is not unusual - the unusualness lies in the vertical scrolling with split screen .

Split screen


Each frame of the video signal created by NES is drawn from top to bottom, one row of pixels at a time. In each row, the pixels are drawn one at a time, from left to right. Halfway through the frame drawing, the game can reconfigure PPU, which affects the display of pixels that have not been drawn yet. One of the most common changes in the middle of the frame is the update of the horizontal scrolling position.



With horizontal scrolling between rooms, The Legend of Zelda always starts from the scrolling position (0, 0) and renders the interface elements at the top of the screen. After the last row of interface pixels is drawn on the screen, the horizontal scrolling changes by an amount that increases with each frame, thanks to which the camera moves smoothly.

The animation of the display of the name tables shows how the game switches from horizontal to vertical mirroring before scrolling, and then to horizontal again after the transition is completed. In addition, while scrolling continues, the upper left (and lower left) tables of names are updated, they are recorded copy of the room in which the player. After completing the scrolling, the game ceases to divide the screen and again is completely rendered from the upper left table.

Measure the degree of rendering


To split the screen in the desired position, the game needs to somehow find out which part of the current frame was drawn. Pixel rows are rendered at a known frequency, so the number of the pixel row drawn can be determined by counting the number of processor cycles that have passed since the beginning of the frame.

There is another, more accurate technique called Sprite Zero Hit.

NES can simultaneously render up to 64 sprites. The first sprite in the video memory is called Sprite Zero (zero sprite). In each frame, as soon as the opaque pixel of the zero sprite is superimposed on the opaque pixel of the background, a Sprite Zero Hit event occurs. It sets a bit in one of the PPU registers with memory mapping, which can be checked by the processor.

To use Sprite Zero Hit to split the screen, the games have a zero sprite in a vertical position near the boundary of the split, and during rendering it is constantly checked whether the Sprite Zero Hit event occurred. If so, the game switches from horizontal scrolling to implement the split.

Below is a horizontal transition between rooms with and without background.




The brown circle that appears at the beginning of the transition and disappears at the end is a zero sprite. Take a closer look at the interface with and without the background:



A zero sprite is a discolored bomb sprite, ideally matching in location with a regular bomb sprite from the game interface. The zero sprite is configured to appear under the background, but since the black pixels of the interface are considered transparent, the zero sprite bomb would have been visible if it had not been strategically hidden behind the bomb from the interface.

Note that Sprite Zero Hit occurs several lines of pixels before the bottom line of the interface. It occurs on the top pixel of the bomb, which is 16 pixels from the bottom of the interface. When Sprite Zero Hit occurs, the game starts counting processor cycles, and after completing the required number of cycles it sets horizontal scrolling.

Beam reversal suppression


Most of the time, the PPU console draws pixels to the screen. There is a short period of idle time between frames, during which no rendering is performed. This phenomenon is called reverse beam damping (Vertical Blank, or vblank). Some types of PPU configuration changes can only be performed during vblank.

Scrolling register


Games change the scrolling position by writing to the PPU register called PPUSCROLL , which is mapped to a memory address 0x2005 . The first write operation in PPUSCROLL sets the X component of the scrolling position, and the second operation sets the Y component. Similarly, the alternate recording is performed further.

Below are shown all non-zero write operations to PPUSCROLL during this playback (in slow motion) of 16 screen frames with the plot of the game. The Y component of the scrolling position is incremented every two frames. All write operations to PPUSCROLL in this example are performed during the vblank, which causes the entire background to scroll with it.




Split screen scrolling


The write operations to PPUSCROLL during vblank take effect at the beginning of the frame, drawn immediately after the vblank. If the scrolling position changes during the frame drawing (i.e., not during the vblank time), then this change takes effect when the drawing reaches the next row of pixels. Partial horizontal scrolling is implemented by writing to PPUSCROLL while the PPU device draws the last row of pixels before scrolling.




When updating the scrolling position in the middle of the frame, only the X position of the scrolling position is applied. That is, component Y of the scrolling position is discarded. Thus, if the game wants to split the screen and changes the scrolling position of the frame, it can only scroll horizontally.

And yet:



Believe it or not, but during this transition the value of the PPUSCROLL register PPUSCROLL not change.

You can see under the interface a graphical artifact with a height of one pixel. This is a bug of my emulator, caused by the lack of synchronization of processor clock cycles with pixel-by-pixel rendering.

Intervention in other registers


The second register, called PPUADDR , mapped to the memory address 0x2006 ,, is used to set the current address of the video memory. When a game, for example, wants to change one of the tiles in the name table, it first writes the video memory address of the PPUADDR to PPUADDR , and then writes the new PPUDATA value to PPUDATA — this is the third register mapped to the address 0x2007 .

Writing to PPUADDR not during vblank (i.e., when the frame is drawn) can cause graphical artifacts. This is because the PPU circuit, which is affected by the entry in PPUADDR , is also directly controlled by the PPU device in the process of obtaining tiles from the video memory to draw them. Since the drawing process on the screen runs from top to bottom, and from left to right within the line, the PPU essentially assigns PPUADDR value of the address of the current PPUADDR drawn. When the drawing moves from one tile to another, PPUADDR changes by incrementing the current value.

Thus, writing to PPUADDR in the middle of a frame can change the tiles received by the PPU from memory for the time of the current frame.

Let's write write operations to PPUADDR during a vertical transition. Since the name table is also updated during the transition, the output of all write operations to PPUADDR will be too extensive. With a horizontal transition, scrolling is set at the time of drawing a row of 63 pixels, therefore, we consider write operations to PPUADDR only during this line.




Clearly visible pattern. Every two frames, the address written in the row of 63 pixels is reduced by 32 (0x20). But how does this lead to updating the actual scrolling position?

Real scrolling register


Inside the PPU there is a 15-bit register that is not mapped to the CPU. It is used as both the current address for access to the video memory and the background scrolling configuration.

When working with this value as with the address, bit 14 is ignored, and bits 0-13 are treated as an address in the video memory.

When working with this value as with the scrolling configuration, its different parts have different meanings:


The selection of the name table is a value from 0 to 3, which determines the current name table from which the drawing is made.

Rough scrolling on X and Rough scrolling on Y determine the coordinate of the tile inside the selected name table. This is the current rendered tile.

Exact Y scrolling contains a value from 0 to 7, which determines the current vertical displacement of a row of pixels inside the current tile. Tiles are squares with a side of 8 pixels.

Exact scrolling on X in this register is missing. There is a separate register containing only the horizontal offset of the current pixel, but it is not important for explaining how vertical scrolling is performed in The Legend of Zelda.

What happens to this register when the game writes to PPUADDR ? Here are the first three write operations from the demo shown above.


By breaking the records at the address into scrolling components, you can clearly understand what is happening here. Every two frames, the coarse scrolling value of Y decreases, resulting in vertical scrolling by one tile or 8 pixels.

During each frame, the initial scrolling offset is 0.0, after which the row of pixels 63 is written to the address. This means that the first 63 rows of pixels are drawn from the top of the selected name table containing the background of the interface. However, the 64th row of pixels is further drawn with vertical scrolling applied from this address. Since vertical scrolling decreases every two frames, this gives the feeling of vertical scrolling of a part of the screen.

Scroll down to scroll up


The Legend of Zelda cannot hide this trick completely. It creates a visible artifact on the vertical transitions of the screen, which are noticeable if you look closely. When moving between rooms, the first frame of the scrolling animation will scroll down. Here is the animation in very slow motion.



In the name table mode, you can see what is actually happening. Although it may seem to players that the visible area will scroll upwards smoothly, the scrolling transition begins by moving the visible area from the upper left name table to the lower left table, which contains a copy of the room background. This is necessary because the interface at the top of the screen is also part of the name table, and if the visible area scrolled up from its original position, it would pass through the interface.

Vertical scrolling is implemented by writing to the PPUADDR register in the middle of the frame. The very first value is written 0x2800 . Two frames later, 0x23A0 written, and then the value begins to decrease by 32 every second frame.


Writing the value 0x2800 to the 0x2800 register assigns the name table PPUADDR to 2, which results in the rendering of the lower left name table. Since both scrolling values ​​are 0, it will start from the top left tile of this name table. However, the exact scrolling on Y is 2, therefore there is a two-pixel vertical offset from the top of the lower left name table. That is why in the very first frame of the transition we see at the bottom of the screen a black bar with a height of 2 pixels. The original scrolling value for the transition animation is shifted down by 2 pixels so that the transition is seamless.

Two frames later, the PPUADDR written to 0x23A0 . This brings us back to the upper left table of names, and we render from the 29th row of tiles, that is, the lowest one. Accurate Y scrolling still contains 2.

Why is it necessary to assign Precise Scrolling on Y to a value of 2? Why doesn't the game just write 0x0800 and 0x03A0 so as not to suffer from a two-pixel offset?

Four tables of names occupy an area of ​​4 KB in the PPU address space, from 0x2000 to 0x2FFF . Each tile in the table occupies one byte of video memory (in fact, they are only indices in another table), and the order of the tiles and tables of names in the video memory is such that the choice of the name table , coarse scrolling on Y and coarse scrolling on X make up the displacement of the tile inside memory areas with name tables. That is, taking the lower 12 bits of the internal register PPU and adding them to 0x2000 , you can find the address of the tile in the video memory. And this is no coincidence! This is exactly how a register should be processed: both as an address register and as a scrolling register.

But there is one flaw.

When processed as an address register, bits 12 and 13 are considered part of the address. During rendering, PPU constantly rewrites the register with the address of the currently drawn tile. Since the tiles are located in the name tables, and the tables are located in the memory area from 0x2000 to 0x2FFF , PPU assigns values ​​from this interval to the register.

When a game writes to PPUADDR in the middle of a frame, if it does not write the tile address in the name table, the PPU will try to read from somewhere else in the video memory. Any bytes that he has to count will be perceived as tiles, which is likely to lead to undesirable results. Therefore, all values ​​written in the middle of the frame in PPUADDR must be in the range from 0x2000 to 0x2FFF . Taking each number in this interval and taking into account its scrolling components, the value of Precision scrolling in Y should always be equal to 2.

This restriction means that we cannot change the exact Y scrolling in the middle of the frame, that is, using this trick to implement vertical scrolling of the screen division, we are limited to scrolling 8 pixels at a time and always two-pixel vertical offset from the tile border. The Legend of Zelda moves 4 pixels per frame for horizontal scrolling, but 8 pixels per frame for vertical scrolling, and now we know why.

The artifact is also noticeable when scrolling down between rooms, but in this case it occurs at the end of the animation.



Additional reading



Notes


Until I learned about the internal register of PPU, my emulator showed the effect of erasing on the vertical transitions of the screen of The Legend of Zelda.



The sprite of Link was transferred to the bottom of the screen, as it should be, but the background did not scroll. The erasure was caused by the fact that the game gradually updated the name table so that it contained the graphics of the new room, but did not update the scrolling to keep the updates off-screen.

Source: https://habr.com/ru/post/460383/


All Articles