Basically, they pre-computed the waves as a list of heights (where the input is the incoming velocity or whatever), and only update half the waves each frame. So it takes 2 frames to fully update. Pre-computation is basically what most heavy tricks on old systems use.
The other half of the trick (besides what @LenFalken concisely and accurately described [here])(https://lobste.rs/s/uhu2w7/water_surface_simulation_on_nes#c_ri8jig)), which I think is less familiar to a modern audience, is CHR bank switching. It’s a very beautiful and surprisingly straightforward technique.
Platforms like the NES don’t give you access to a framebuffer that you can manipulate pixel-by-pixel. Instead, you can set a background comprised of 8x8 tiles, like a mosaic, over which you can paint sprites at arbitrary positions. The programmer’s “view” of the “video memory” or “screen”* consists of a 30x32-tile buffer called a nametable (well, it’s actually 2, but that’s not relevant here). Each of those tiles has an ID between 0 and 255, which corresponds to one of the 8x8 tiles in the current tile ROM. You can see that in this figure from this page: the background on the left is painted with tiles from the leftmost set on the right. There’s 256 tiny tiles in there.
You can animate that with practically zero CPU effort by changing the tile bank that’s currently mapped in memory. If you look at that GIF, you’ll notice that the tiles that depict the palm leaves are slightly offset on the two frames. Those are actually two different copies of those tiles, stored at different addresses in the ROM. At any given time, only one of those sets is mapped into the system’s memory. Because the background contains the indexes of the tiles, if you change the tile set into which those indexes point, i.e. if you just map another tileset into memory, you can paint different tiles on each frame without changing the tile IDs in video memory.
It’s basically indirect addressing for video memory: you leave the indirect indexes unchanged, but you change what’s mapped at the target address.
Why is this useful? You can actually change the background contents, too, i.e. you can change the tile IDs in memory. That does require moving some bytes back and forth, though, and the NES’ CPU is very cycle-starved (it’s basically a 1 MHz 6502-ish CPU). ROM memory, on the other hand, is fairly cheap – nowadays even more so, but it was already a good trade-off to make back when Megaman came out.
* I’m deliberately hand-wavy here about “video memory” and “screen” because the NES doesn’t use these terms exactly, but it’s a good enough approximation.
It’s cool that they discussed it, but note that Böbl isn’t using bank switching for animation, as best I can tell.
ROM memory, on the other hand, is fairly cheap – nowadays even more so, but it was already a good trade-off to make back when Megaman came out.
Mega Man 5 (pictured) also has the benefit of being able to swap only part of each pattern table, MMC3 supports banks as small as 1 KB of the total 8 KB address space, so since all the animated tiles are packed together there’s relatively little waste.
The “Animating the water surface” section suggests it does, at least for that waterfalls, but I can definitely see swapped tiles on the right, too, and that looks very much like the PPU tileset view. Either I’m misunderstanding the diagram (obviously very likely) or it’s also using bank switching.
I knew I recognized the studio’s name. Here is a link to a similar video from them which you have seen before: https://youtu.be/ZWQ0591PAxM. Always enjoy this kind of golfy content. Warms the cockles of my heart.
The trick of using only 1 bit per pixel is handy, and if you can afford to waste a background palette you can even use the same pattern table tile for two independent screen tiles, using two palettes that each only show one bitplane (one is black,black,white,white, the other black,white,black,white).
That’s one of the tricks I used in a little NES vector demo Nestify (video). I wanted to allow partial updates so I could reproduce the erasure artifacts of the “Mystify” screensaver, so I also have a lot of pregenerated code for setting or clearing parts of a tile. The update commands are then addresses and args pushed onto the stack so they can be cycled through with no logic during the narrow window of vblank, Bobl uses something similar but a bit less complex (in this respect).
That was an amazing read. And I understood only about half of it!
Basically, they pre-computed the waves as a list of heights (where the input is the incoming velocity or whatever), and only update half the waves each frame. So it takes 2 frames to fully update. Pre-computation is basically what most heavy tricks on old systems use.
The other half of the trick (besides what @LenFalken concisely and accurately described [here])(https://lobste.rs/s/uhu2w7/water_surface_simulation_on_nes#c_ri8jig)), which I think is less familiar to a modern audience, is CHR bank switching. It’s a very beautiful and surprisingly straightforward technique.
Platforms like the NES don’t give you access to a framebuffer that you can manipulate pixel-by-pixel. Instead, you can set a background comprised of 8x8 tiles, like a mosaic, over which you can paint sprites at arbitrary positions. The programmer’s “view” of the “video memory” or “screen”* consists of a 30x32-tile buffer called a nametable (well, it’s actually 2, but that’s not relevant here). Each of those tiles has an ID between 0 and 255, which corresponds to one of the 8x8 tiles in the current tile ROM. You can see that in this figure from this page: the background on the left is painted with tiles from the leftmost set on the right. There’s 256 tiny tiles in there.
You can animate that with practically zero CPU effort by changing the tile bank that’s currently mapped in memory. If you look at that GIF, you’ll notice that the tiles that depict the palm leaves are slightly offset on the two frames. Those are actually two different copies of those tiles, stored at different addresses in the ROM. At any given time, only one of those sets is mapped into the system’s memory. Because the background contains the indexes of the tiles, if you change the tile set into which those indexes point, i.e. if you just map another tileset into memory, you can paint different tiles on each frame without changing the tile IDs in video memory.
It’s basically indirect addressing for video memory: you leave the indirect indexes unchanged, but you change what’s mapped at the target address.
Why is this useful? You can actually change the background contents, too, i.e. you can change the tile IDs in memory. That does require moving some bytes back and forth, though, and the NES’ CPU is very cycle-starved (it’s basically a 1 MHz 6502-ish CPU). ROM memory, on the other hand, is fairly cheap – nowadays even more so, but it was already a good trade-off to make back when Megaman came out.
* I’m deliberately hand-wavy here about “video memory” and “screen” because the NES doesn’t use these terms exactly, but it’s a good enough approximation.
It’s cool that they discussed it, but note that Böbl isn’t using bank switching for animation, as best I can tell.
Mega Man 5 (pictured) also has the benefit of being able to swap only part of each pattern table, MMC3 supports banks as small as 1 KB of the total 8 KB address space, so since all the animated tiles are packed together there’s relatively little waste.
The “Animating the water surface” section suggests it does, at least for that waterfalls, but I can definitely see swapped tiles on the right, too, and that looks very much like the PPU tileset view. Either I’m misunderstanding the diagram (obviously very likely) or it’s also using bank switching.
I knew I recognized the studio’s name. Here is a link to a similar video from them which you have seen before: https://youtu.be/ZWQ0591PAxM. Always enjoy this kind of golfy content. Warms the cockles of my heart.
Same! That video is SO GOOD!
The trick of using only 1 bit per pixel is handy, and if you can afford to waste a background palette you can even use the same pattern table tile for two independent screen tiles, using two palettes that each only show one bitplane (one is black,black,white,white, the other black,white,black,white).
That’s one of the tricks I used in a little NES vector demo Nestify (video). I wanted to allow partial updates so I could reproduce the erasure artifacts of the “Mystify” screensaver, so I also have a lot of pregenerated code for setting or clearing parts of a tile. The update commands are then addresses and args pushed onto the stack so they can be cycled through with no logic during the narrow window of vblank, Bobl uses something similar but a bit less complex (in this respect).