Zelda Screen Transitions Are Undefined Behaviour(gridbugs.org) |
Zelda Screen Transitions Are Undefined Behaviour(gridbugs.org) |
There are many, many classical effects on raster hardware which are accomplished by changing registers within the horizontal blanking period... copper bars, mode 7, certain paralax scrolling. When you're on a resource limited system it becomes an art to get the most out of the platform. Look at the difference between Mario 64 and Conker's Bad Fur Day... or Genji: Days of the Blade (PS3) vs Persona 5 (PS3) Even with modern consoles, there is a marked improvement in the apparent visual quality over the lifetime of the device, as developers learn how to squeeze more and more out of the platform.
https://en.wikipedia.org/wiki/Undefined_behavior
This case appears to be "undocumented scenario" or "unsupported use-case", though.
If you want an example of something that’s really an unsupported use case, consider mid-frame palette changes—in order to do this, you actually have to disable and re-enable rasterization during the horizontal blanking interval. It works, but it is difficult to get right and the PPU is clearly not designed with this in mind.
Short of having an actual spec in hand, it would appear that mid-frame scroll register updates were entirely normal and an intended way to use the device, based on the evidence (no other reason for sprite zero check, lots of first-party titles using this feature, other mid-frame updates are much more difficult).
It could be "defined by e-mail".
That's a part of it, to be sure, but another dynamic is that developers have to squeeze more out of the system. Dropping old techniques on new hardware will probably give you a game that looks "better" than what's already out. Once everyone has done that, to look "better" you need something new other than hardware.
- implementation-defined: Not defined by the standard, but implementations must choose a consistent behavior and document it (e.g. what's shifted in when right-shifting signed integer types)
- unspecified: Not defined by the standard; implementations must choose some way of addressing the situation, but need not document it (e.g. the order in which function arguments are evaluated)
- undefined: Entirely outside the scope of the standard; implementations may assume that such situations never occur, and need not have any sensible or consistent behavior (e.g. dereferencing NULL)
In that taxonomy, I think this is much closer to "unspecified" than "undefined". The latter is usually used in scarier contexts like random memory corruption or crashes, not consistent behaviors that rely on deliberate implementation choices as we see here.
Lots of games used funky scrolling mechanics, typically to create status bars, but of all the different games I tested with, TLOZ was by-far the weirdest, requiring an entire special case to get working!
I don't have any screenshots of my own, but some japanese website recently covered wideNES, posting screenshots of it working with the original Legent of Zelda.[2]
[1] http://prilik.com/blog/2018/08/24/wideNES.html
[2] https://emulog.net/fc-nes-emulator-anese-how-to-use-widenes/
You could probably run an async loop which slices up painted frames and compares hashes of the slices to find identical slices to anchor and stitch similar frames together, still maintaining separate layers in case a better match is found later on. Something like that should solve for games like SMB.
Reading articles like this, or about the Atari and how the code would double as a sprite in pac-man, or how 3D was rendered in Wolfenstein, makes me think one had to be much more clever back then.
And that's just the NES devices! The Famicoms were different again, there were at least two licensed clones, and dozens of unlicensed clones (if you, eg, wanted your game to sell in Russia, you'd care about being compatible with the Dendy as well as the genuine NES).
https://www.youtube.com/channel/UCwRqWnW5ZkVaP_lZF7caZ-g/vid...
See http://bootgod.dyndns.org:7777/ for a database of the hardware inside each cartridge.
Paperboy comes to mind. It's smooth and looks good without any noticable glitching, and without using custom chips, too. Not sure how they did it.
Edit: as sibling comment noted, Paperboy does have custom logic on the cartridge, a 74HC161 4-bit counter. I think this is just used to switch between CHR banks.
Nope. Paperboy uses a CNROM, which is basically an MMC3. Maybe they're doing some clever edge-replacement stuff on the mirrored table, like SMB3 does with the same hardware? But you can see glitching when SMB3 does it.
(edit: my sibling post answers it. That's slick.)
Wow, this is chock-full of content. I wish there was one for the Sega Master System too
Off-topic, but thought I'd link to this very fun comparison of various games across the two platforms: https://huguesjohnson.com/features/nes-vs-sms/
SMB1 actually also does not use the sprite zero split technique because it never scrolls vertically. Its status bar is just a bunch of fixed background tiles.
Regarding SMB1, I'm quite sure it uses the sprite 0 thing to keep the status bar stationary while the level scrolls smoothly beneath it by setting the scroll register only after when the status bar is done drawing. See more thorough description here: https://retrocomputing.stackexchange.com/questions/1898/how-...
Also, Y-scrolling wasn't completely figured out until late in the NES's life. The register writes needed to do so are very strange, and Zelda certainly doesn't do it correctly!
Mode X or Q reminds me of the amazing Mode 7 SNES graphics used to great smooth effect in F-Zero and many other titles.
"The system was designed to accommodate the ongoing development of a variety of enhancement chips integrated in game cartridges to be competitive into the next generation."
Also isn't vertical split quite common? I would assume this is something the hardware designera thought of, not a game company figuring it out. They even put stuff like sprite 0 hit bit for this kind of tricks
Just doing it now, converting all the animations to WebP makes it 1.6MB. and it works in all current browsers.
The 6502 has a few "undocumented" instructions due the fact that if you have certain on/off input pin set, you're actually crossing gates used for multiple instructions. These may crash the chip or do non-useful things, but a few do something useful in vary specific circumstances. The trick to developing on these old systems is to experiment with the chips to understand what the system does in various circumstances.
What I liked about that time was that it was possible to truly understand everything about a system because nothing is hidden from you by software drivers. The were good times.
Want to understand a tiny chunk of that complexity yourself? Check out how the addition operation overflow flag is implemented in silicon on the 6502: http://www.righto.com/2013/01/a-small-part-of-6502-chip-expl...
See
Which was also true back then. Not all programmers back then had to derive arcanely clever tricks to get their job done.
There's also a part of it that is underappreciated, which is that sometimes the cool effect wasn't necessarily an original design goal, but instead was sometimes something that was stumbled upon one way or another, and then it was having that little trick in one's pocket which informed how the design would be.
I love reading about how programmers pulled off these tricks on such limited hardware, hopefully there are some notes about it somewhere on the internet.
Try programming microcontrollers and you can enjoy that feeling today! Get comfy with the relatively powerful ARM Cortex-M variants, which typically sports 10s of kB of RAM. Then if you feel frisky, try your hand at some 8-bit uC's with less than 64 bytes of RAM (spend your bits wisely).
Maybe it's finding creative solutions, but today there is a lot to know.
https://www.vice.com/amp/en_us/article/9a3b8y/why-zelda-was-...
The counter is based on the following trick: whenever rendering is turned on in the PPU, it fetches nametable and BG pattern tiles from dots 0-255 and 320-340 of a scanline and fetches sprite patterns from dots 256-319, even if no sprites are visible. Because of this, if BG uses the left pattern table ($0000), and if sprites always use the right pattern table ($1000), A12 will remain low during all nametable and BG pattern fetches, and high during all sprite pattern fetches, causing it to oscillate exactly one time per scanline and 241 times per frame.
Then there are situations where response content length is not known, such as streaming over HTTP.
Last, if a certain "Accept-Content-Length" became standard, like 8MB, developers (such as those for the ad industry) would just create javascript libraries that would download large files in 8MB chunks and sidestep it.
You still need to write the logic that each layer needs to be useful.
However, I suppose somewhat subjectively in reviewing or writing any of this glue or logic layer code using modern web frameworks I've never had the sort of WOW response that I do when I see what the programmers of yore were coming up with.
This wasn't a big problem, because TVs of the time often had big overscan areas (areas that were rendered, but covered by the TV's physical bezel). However, it was visible on many TVs, and today it is very visible under emulation. Activision's programmers found this unacceptable. Rather than perform the very difficult, or even impossible, task of making their code run faster than the HBLANK period, they instead chose to render the first cm or so of each scanline intentionally black! Notice the width of the screen in this screenshot compared to other nearby games: https://videogamecritic.com/2600ff.htm#rev203
More crazy tricks like this are described in the excellent "Racing the Beam" book by Nick Montfort and Ian Bogost.
For the NES, depending on the registers in question, it can be easy or hard to update them during horizontal blank. Changing the scroll registers is easy, the PPU was designed to make that possible, and gave you a sprite zero hit test so you could get the timing correct--which is how Super Mario Bros. draws the HUD at the top of the screen.
With some extra hardware you can make this easier and get some cool effects like parallax. The NES exposes a NMI line to the 6502 on the cartridge connector, and you can wire up some logic on the cartridge to signal NMI for every scanline. Battletoads uses this effectively. Atari 2600 lacks NMI because the 6507 doesn't have a pin for it.
Certain registers are much more difficult to change during horizontal blank, but not impossible. For example, palette entries. This requires very precise timing so it was very rare to see it. Indiana Jones and the Last Crusade changes the palette entries mid-frame, but only for the title screen.
This was a workaround to avoid Atari patents. On the 2600 you could write to a register to halt the CPU until horizontal blanking (commonly written as sta WSYNC), to get perfectly synchronized to the next scanline. Sprite zero is more flexible, but the timing isn't as precise.
Some of this may have been undefined behavior, but the MMC3 chipset involved was produced and manufactured by Nintendo. Whatever the original design was, Nintendo appears to have stuck with that same set of capabilities throughout the console's lifetime, and encouraged their developers to take advantage of all the tricks.
I think there are games out there that do use sprites to do the same on the other side.
Of course, if you don't have cloners immediately nipping at your heels, you might end up releasing a smash hit that takes advantage of UB in a way that forces you to define the behavior as "it works however it needs to work to keep that game working", because eventually you yourself (the original console manufacturer) will tape out later revisions of the CPU, and you'll want to make sure they can run that game, given that it's "officially licensed" by you.
If you had the right static analysis tools in play, though, you might have wanted to run them over the game, notice the use of UB, and thus fail the game at the QA stage, before things need to escalate to that point.
I hadn't put thought into how important that scroll effect is to the game, but if there was a clean wipe between scenes it would have been tremendously distracting. This technique really is essential to the feeling of immersion.
The one playing semantics is you, since your apparent concern is the definition of the term "undefined behavior" and whether something falls under that definition.
My point is about how well an engineering decision is justified, not what term applies to it according to some document.
You are correct thought, often guarantees about behavior can be made after the fact. Generally what happens these days is someone determines they need or want to use a certain undefined behavior. The hardware people then go, look at the RTL for the chip and confirm it behaves in a certain way, and then they UPDATE the documentation to explicitly document the newly guaranteed behavior.
Conversely, often something that is documented to work just doesn't. Especially in the case of console developers who tend to be using early steppings of custom silicon. In that case sometimes when you go to the HW team they fix it in the next stepping, sometimes they document it as errata and move on.
This doesn't preclude compromising readability for hacks when you need to squeeze performance out of some logic (just look at Tensorflow code), but you'd expect a maturing computational environment and engineering culture to reduce the number of clever hacks needed or present.
What NES cartridge from 1986 can you plug into current Nintendo boxes?
Even though the 1CHIP SNES is a complete ground-up re-layout (has to be, turning a bunch of individual chips into one SoC), every (officially licensed) cartridge made for "the SNES" works on a 1CHIP SNES.
A hardware revision like this, requires retaining bug-for-bug UB compatibility with officially-sanctioned released game titles, because to do otherwise is to doom the console's support line to endless complaints from people whose games don't work. But hardware revisions are not concerned with keeping UB working the same where no officially-sanctioned released title took advantage of said UB. For everything not constrained by bug-for-bug compat with an existing title, all that's required of the new revision is that it keeps to the spec.
If you were a third-party in the middle of developing a new title, and were relying on some new UB you found, when suddenly your console mfgr released a new revision, it could turn out that your clever UB hack won't work the same on the new revision. (And this happened; it was why unlicensed titles—your Game Genies, your Aladdin Deck Enhancers, etc.—frequently wouldn't work with later console revisions.)