Well, it's been just over a month now since Asie surprised all of us with The Reconstruction of ZZT, a recreation of ZZT's lost source code. ZZT and Zeta are now MIT licensed and the sky is the limit. Let's take a look at the new discoveries, solved mysteries, cracking bugs, new features, requested features, investigations into other versions of ZZT, and of course the current forks in development to get an idea of just what's been going on lately.
Honestly, I did it in no small part because I wanted to give people a readable reference for ZZT behavior — Asie
One thing having source code available means is that no matter how esoteric the behavior, you can find an answer for why. The reconstructed source provides details on previously ambiguous aspects of ZZT and has even revealed a few new ones.
Command line Switches
Until the release of the reconstruction nobody knew that ZZT has some options that can be passed at the command line.
ZZT.EXE /R forces ZZT to run its configuration. In ZZT v3.2 this always happens anyway, but the switch is still parsed. In ZZT v3.0 this is a much easier option than deleting the ZZT.CFG file entirely.
ZZT.EXE /T adjusts the method ZZT uses for timing! By default ZZT uses the system time interrupt 21h for its timing. If this argument is passed it will instead use the timer interrupt 1Ch. If you're running ZZT on a system with a malfunctioning real-time clock, this could be of use?
Unusual Stat Limits
ZZT has a MAX_STAT value of 150. Everything in the engine respects that limit but one element wants to be a rebel: Duplicators. Thanks to some better bounds checking when spawning something with a stat there isn't actually a bug here, but duplicators make their own (duplicate) check using a stat limit of 174.
Why the discrepancy? Who knows! Stranger still, ongoing attempts at reconstructing Super ZZT have revealed that its duplicators still have the same check. This time the comparison assumes a stat limit of 152. Super ZZT also has a reduced stat limit of 128 which rather oddly means that both ZZT and Super ZZT have a check that's off by 24.
ZZT is a rather efficient little program that was capable of running on some very low-end machines quite well even in its earliest days. One thing you notice after years of working with it are the obvious instances where generic code gets to handle multiple scenarios.
Most obvious is that if you name a board
and bring up the board list in the editor, the board titles are parsed with the same text formatting they'd get
as if they were lines of ZZT-OOP! The fact that hyperlinks and labels work in board titles and the board list is
the basis of the notorious Super Lock.
The reconstruction has let us confirm that the code handling drawing torchlight and handling bomb explosions is also shared. If your modification wants to increase the radius of torchlight, it's going to increase the blast radius of bombs as well unless the two are decoupled.
Mysteries Solved And New Mysteries
The Length of Silence
Sometimes ZZTers have wanted to mask sound effects. Some earlier fan-hacks remove various sounds entirely. Probably the most common example is to
mute duplicators on boards that duplicate them onto a player. These boards will have duplicators chugging along until it's time to let them duplicate onto
a player and force a board transition. They get pretty noisy though! Lucky for us ZZT's
#play command overrides default sounds.
A solution is to have an object play a bunch of silence with
#play xxxxxxxxxxx to play a bunch of rests. This gets put in an
audio buffer which carries on to the next board.
To my knowledge nobody ever bothered This one's on me, such information is unsurprisingly contained in a release of #play calculating documenting-in-a still-readily-available-manner the length of one cycle's worth of silence, enough to quiet the built-ins
but allow sound to resume immediately.
Asie helpfully explained that it's as simple as a single 32nd note.
#play tx /i#restart
This code will provide the minimum amount of silence needed!
Well... almost. If your silence lasts for a single cycle you need to make sure the object producing it is before anything that makes noise in the stat order, and the player will always be ahead of it, so player shots will still make noise with this solution.
An early question for Asie after the project was revealed was if there was any reason for the stat limit to be 150. Obviously it's a nice round number, but people were curious why not 200? Or 255? Or 1500 to fill a board entirely?
Turbo Pascal (at the time) had a hard limit of 64K for statically allocated data, meaning it would be possible to run out of space when creating a larger stat array.
The Skipped Element
Anybody who's taken a look at ZZT's file format (or even manually changed a tile's element in KevEdit) has come across the list of elements and noticed "Element 46". There's this one element that goes unused. Cut content? The editor cursor? Yet another text color?
Nah, it's just undefined. Asie's speculation is that it was originally white-on-black text that got moved to the end of the list. Since the "white text" ZZT produces is white-on-black rather than white-on-dark-white, it makes sense for Element 46 to be "black text", which would fit the white-on-dark-background-color format all the other text characters use.
At the end of the element list in the file format are the various colors of text. In the earlier days of hex-editing ZZT worlds, there was an obvious interest in seeing what would happen if you changed element IDs to a larger number than any of the known elements. Sure enough, you get more text colors!
(Err... don't ask why the colors are all shifted I took this from an existing example since otherwise I'd have to hex-edit some fresh text.)
A pretty sensible theory (that turned out to be correct) is that after passing a certain threshold for element IDs, ZZT treats everything afterwards as text. The colors to use are calculated based on the element ID, and so going beyond the defined seven colors just continues this trend. What makes this text "Kryptonite" is the fact that touching it will usually crash ZZT.
Now we know why, and it's exactly as you'd guess. ZZT defines a default set of properties for an element and then creates information for all 53 used elements populating unique data for each element when it's there like a name or what key accesses it in the editor. If you go beyond this range, the element is drawn as text, but when something interacts with it, it doesn't even have those default values to point to and typically explodes.
ZZT's Audio System
Always impressive and always a mess, replicating ZZT's sound accurately has been a tremendous pain for ages. With the sound system exposed it's very much possible to
port it elsewhere and get fully accurate notes for ZZT's own sound effects. Internally (in some cases at least), ZZT uses an entirely different format for defining sounds
than the system used with
#play. If you're looking for a project, getting a 100% accurate ZZM player would be a treat.
Why Can't You Push Torches?
You can push ammo. You can push gems. You can push keys. You can push scrolls, bombs, boulders, sliders, and creatures. You can't push torches.
I don't know, it always bugged me. My only guess was that torches are unique in that they are visible in dark rooms and don't have stats. I figured perhaps if you pushed a torch in the darkness the board might not update its display properly? Well, thanks to being able to easily modify properties I made ZZT with pushable torches and got to see what happened if you moved them around in the dark. The answer: nothing, they pushed just fine and displayed just fine. So now it bugs me even more.
Years ago I said "to hell with it", and mass extracted every ZZT world in z2's /zgames/ directory into a single folder.
ZZT does not like this. This isn't all that surprising, but we know what the actual limit is now. Text windows are limited to 1024 entries. More than that is a bad time.
One of the last variables in the deconstruction to be given a proper name was an argument sent when calling functions related to touching elements. This has turned out to be the stat ID of the element doing the touching. In every single instance this is 0 as the player is the only element that can touch things. This raises the possibility that there was consideration for objects or other elements to be able to send touch messages as well, something that would have dramatically altered ZZT. It's speculation, of course but definitely fun to consider.
Objects running ZZT-OOP are only allowed to execute 32 instructions per cycle. Some like movement commands immediately end the cycle after their execution, while others let code keep being processed on the same cycle. One interesting edge case is the discovery that following a hyperlink in text resets the count. Presumably this is so a series of links that travel back and forth between each other aren't suddenly cut off if the player spends too many actions navigating choices.
Press ENTER to select this
Nagohkluh asked about a minor ZZT bug and was curious what its origins were. In some instances when displaying messages in a scroll window when the cursor is on a blank line of text the top of the scroll will display "Press ENTER to select this" as if the line was a hyperlink. After finding a reproducible instance of the bug Asie dug into it and found the cause
Essentially when one scroll closes and a new one opens, the new one is written right over the old one in memory. A check for what to display in the top of the window uses the first character of the currently selected line, and if the line in the new scroll is blank (meaning length = 0), it will still try to read the first character, which will remain whatever the first character was of the old scroll. So if your third line starts with a "!" in the old scroll and the third line is blank in the new one, ZZT will see the "!" still and display the "Press ENTER" line accordingly!
While I know I've seen this bug in the wild a few times, I don't know how aware of it people were. I have seen several ZZT worlds use "$" for otherwise blank lines of text which would defeat the bug and result in a blank line as "$" activates the centered + white text mode.
While plenty of odd emergent behavior has been used to enhance ZZT, some of its bugs have never been all that beneficial. Numerous fixes have already been implemented.
Some, such as fixing the broken message when picking up a black key or opening a black door is as simple as adding "Black" to the array of color names and increasing its size by one. Others, like... making them not give 256 gems are a little more involved.
Other fixes include
#put not working in the bottom row. ZZT crashing when a scroll moves of uses
Asie has a patch available that allows to either unpause the game by pressing "P" again or by merely attempting to move (rather than successfully moving). This fixes a common softlock when the player pauses while surrounded, or more likely, restores a saved game where they're surrounded and the game starts paused.
(Because it's possible to touch an object while paused and then move out of the way, the latter unpausing method can break using this as an exploit as well as breaking a puzzle in 2019's Yuki and the Space Show. I am very much on team "Press P again".)
One limitation of ZZT that offers no benefit and has been the bane of many ZZTers is its memory allocation. ZZT runs entirely in conventional memory which restricts world sizes based on available resources. One of the first major overhauls Asie pulled off was adding support for EMS memory,
Now massive worlds like the 400+ KB For Elise have some breathing room. The debug information in the above screenshot shows the game running with more than 400KB of conventional memory and 134 pages of expanded memory available. This memory change applies to the currently loaded board as well allowing for a board size limit closer to 65,000 bytes. Worlds using this system meanwhile, can be measured in megabytes.
A very approachable way to make changes to ZZT without a lot of knowledge of its code and/or Pascal is adding new cheats. While the extremely open nature of ZZT worlds means it's easy to adjust a game to your liking via editing, it can be a bit of a pain to drop what you're doing and break out the editor, possibly having to mess around with renaming save files to load them in KevEdit. Cheats are a fast way to make broad changes to a game experience. A few new cheats that others have coded in include:
?PASSAGEto change boards arbitrarily and
?@followed by a number to warp to that board
?SPEEDto adjust game speed without having to quit first
?-KEYSto remove all the keys a player is carrying
?FLAGSto list all set flags
Asie comes up with an alternative board transition style:
My first critical change was to fix the water to match the water found in DEMO.ZZT. (Fun fact, this actually breaks the ability to specify colored water in ZZT-OOP!)
Other Versions of ZZT
The obvious question after reconstructing ZZT v3.2 was if there'd be a reconstruction of Super ZZT or earlier releases of ZZT. Since the initial release of RoZ, Asie has been chipping away at Super ZZT and Mr. Alert, a former developer of z2 has taken up the challenge of cracking ZZT's earlier releases. But first, a helpful rough ordering of releases:
- ZZT v2.0
- ZZT v3.0
- Worlds of ZZT v3.56
- ZZT v3.1
- Super ZZT v1.X
- ZZT v3.2
- Super ZZT v2.0
For the longest time, older versions of ZZT were considered novelties at best, but now they can be better understood. The purchase of a 5.25" floppy labeled "ZZT's Labyrinth" in 2018 caused a small uproar when it got us to finally notice ZZT 3.0's ability to skip the game configuration screen. Attempts were made at figuring out the specifics of its extended configuration file, but it's only now that we have a complete understanding as Mr. Alert has been reconstructing ZZT v3.0 along with discovering numerous other changes differentiating it from ZZT.
- Reads and writes a configuration file with the following format:
- Line 1: Flags
- 1: Configuration invalid
- 2: Joystick supported
- 4: Mouse supported
- 8: Joystick enabled
- 16: Mouse enabled
- 32: Monochrome video
- Lines 2/3: Initial joystick position X/Y
- Line 4: Video mode before ZZT started
- Line 5: Initial world file and editor flag ("*" prefix disables editor, blank defaults to TOWN)
- Line 6: Registration flag (any non-blank line means registered)
- Line 7: Serial number (optional, defaults to NONE, printed at the end when printing ORDER.HLP)
- Enters the configuration screen if one of the following conditions are met:
- Configuration invalid flag is set (set by default if the configuration file is missing)
- "/R" command line option is entered
- Joystick is supported and the initial X/Y position differs by more than a cumulative 50%
- Joystick or mouse support differs from what is recorded in the configuration file
- Mouse was enabled in the configuration file
- Last video mode differs from what is recorded in the configuration file
- Copyrighted to Potomac Computer Systems
- File I/O errors are displayed on the sidebar with a sound effect rather than in a text window
- Editor will not open locked world files with DEBUG flag set
- World is reloaded on replay even with DEBUG flag set
- Text window size and position is set at compile-time
- File viewing instructions are moved over by 4 spaces
- Input code does not record last non-zero delta values
- VGA text mode not set to 350 lines
- Monochrome attributes are derived from color values differently
- Video code does not include support for 40-column mode
This list is taken verbatim from Mr. Alert
Worth noting is the lack of 40-column mode support. This makes sense for ZZT v3.0 as it appears to be the version before Super ZZT's initial release. As the two engines share a decent chunk of code, ZZT v3.2 contains some references to Super ZZT features that go unused in the original.
"ZZT 3.0 also preserves the blinking attribute, while 3.1 does not."
Mr. Alert has also documented the changes for ZZT v3.1 as well!
ZZT v3.1 (Shareware)
- Doesn't read any configuration file, and therefore cannot be registered
- Title in configuration screen is "ZZT: The Object-Oriented Game"
- Configuration screen background is brown and blue
- Does not reset video mode on exit
- Registered version exit message only says "Thank you for playing ZZT."
- I/O error message does not attempt to show error code
- Saving world does not close or delete file on error
- "DEBUG" flag must be set for non-flag commands to work
- Non-flag commands must be entered in lowercase
- "-dark" is implemented, but does not work because command is uppercased when "+" or "-" is the first character
- Editor does not clear board neighbor info when importing board
- Editor does not redraw board after editing board info
This list is taken verbatim from Mr. Alert
There's nothing too cool here other than the discovery of a bugged
Imported boards not clearing their exits was known, and actually gives 3.0 (and 2.0 which shares the bug) some useful functionality today. Asie used it
when stitching together Outskirts of Zweldronia!, an incomplete world
with a corrupt board known only to exist in exported board form. By importing the boards in the proper order with this version, the board connections
were preserved instead of needing to be recreated.
Mr. Alert also pointed out that the version of ZZT v3.2 included with ZZT's Revenge actually has a single tiny difference from the normal v3.2 implying that the copy in Revenge predates it.
- The ONLY difference is that you can't shoot by pressing the space bar.
And of course, you aren't suddenly given complete control of the code-base without daydreaming a few feature requests. These are just a few of the ideas people have expressed interest in being added to ZZT:
- Restoring saves without having to quit to the title screen
- Listing the name of the first board of each file in the world menu to allow for custom named worlds
- Implementing damage sources with different amounts of damage
- Pressing "E" to launch KevEdit instead of ZZT's own editor
- Testing integration to go from editor to the edited board quickly
- Handling situations where the player is destroyed to prevent soft-locking
- A method to immediately clear the audio buffer
Current Patches/Forks and Features
OpenZoo is a project of Asie intended to collect various patches to ZZT's code-base, allow users to choose which patches to apply, and generate a version of ZZT with them. Currently it is just a collection of various patches with no interface for applying them.Features:
- Fix for ZZT always saying "Dos Error #0" rather than the correct error number
- Fix for black key/door message
- Fix for duplicators using the wrong MAX_STAT value
- Fix #put on the bottom row
- ?PASSAGE cheat to change boards during gameplay
- ?SPEED cheat to adjust speed during gameplay
- Support for EMS memory, allowing larger worlds to be loaded
- Various memory optimizations to free more memory and allow ZZT to run on more limited hardware
- Removing unused variables
- Various ZZT-OOP optimizations to allow ZZT to run on more limited hardware
- Ability to remove the editor entirely, again freeing memory and reducing ZZT's requirements
- Allowing for unpausing by attempting to move even if the player is blocked, or by pressing "P" again, preventing soft-locks
Asie has in particular shown interest in creating a "ZZTRUN", a stripped down ZZT v3.2 that removes its editor and reduces memory and file size as a drop-in ZZT replacement for the modern ZZT use case of ZZT + Zeta + KevEdit that would also have the benefit of freeing up memory for playing ZZT worlds on DOS machines where the editor isn't of interest
Libzoo is Asie's re-implementation of the ZZT engine that plans to port it to a portable C library. The intent of which is that by porting in such a manner that it becomes possible to create front-end implementations of the ZZT engine and aid in its ability to run it on all sorts of hardware versus a more modern programming language focused on Windows/Mac/Linux implementations.
I want libzoo so I can port ZZT to more platforms and I want libzoo because some platforms can't just handle an identical ZZT interface: think phones, or TVs, or consoles — Asie
Asie has set ZZT free of MS-DOS and now has it running on... the Game Boy Advance...
Okay, so it might not seem like the most relevant thing to port libzoo to, but it's a good example of what libzoo allows to be accomplished. The GBA hardware has a 240x160 resolution, 256KB of memory and a fairly limited number of buttons. It's not hardware that should directly be able to run ZZT, but Asie has been making good progress on it as low-end target for what ZZT can manage to run on. It's so tiny!
While Asie takes the long term approach of getting ZZT on everything under the sun, GreaseMonkey has gone with the alternative (and just as valid) goal of "ZZT on modern machines". His own project, Zoo64, takes the approach of compiling ZZT with a modern version of FreePascal.
The Linux Reconstruction of ZZTGreaseMonkey isn't the only one interested in getting ZZT working with FreePascal. Over on z2's forums, a user known as The Mysterious KM has shared The Linux Reconstruction of ZZT. This is definitely the attempt that's farthest along currently. A few features (sound, mouse, and joystick support) have been temporarily cut to focus on getting ZZT running in a terminal window first and foremost. However, depsite those omissions (of which only audio is missed), the Linux Reconstruction has made excellent progress, with success in running complex ZZT programs such as the Mandelbrot renderer in Preposterous Machines!
Mr. Alert's Demo Branch
While not a full-fledged port, Mr. Alert has been working on adding demo recording/playback functionality to ZZT. This would be a boon to potential speedrunners and those wishing to create a Tool-Assisted speedrun.
This has of course lead to realizations like how adjusting game speed adjusts how many cycles messages are displayed to ensure there's time to read them. This can desync demos thanks to how messages use stats!
Dos's Branch and Patches and It's Just a Big Mess SorryFeatures:
- Keys the player doesn't have are displayed on the HUD as black on dark blue silhouettes
- The HUD now displays board exits
Water matches DEMO.ZZT's- This actually breaks ZZT-OOP letting you produce colored water. Not worth the goof. Messages are solid green instead of flashing- This was to ensure easily readable text in screenshots but it looks so lifeless during play
- Current length of the audio buffer is now displayed on the HUD
I couldn't not get in on this of course. I mean, you already saw my amazingly improved water. I've yet to commit anything serious and have been waffling between focusing on making patches for OpenZoo or committing to my own branch aimed towards my own personal goals of making ZZT friendlier for those who have never used it as well as making it something that can benefit me when doing streams of ZZT worlds or capturing screenshots for articles.
But nonetheless, I've done some experiments here and there with it.
The (Marginal) Silliness of ZZT
RT-55J has a fork that exists just to be more of a playground for experimental features just to see what can be done to ZZT's gameplay called The (Marginal) Silliness of ZZT. It's not intended for serious use, but it is definitely showing off a lot of new functionality that a game could base itself around.Features:
- When a player is energized they now shoot stars that can attack enemies like bullets!
- Customizable player color
#BRIGHTENto toggle darkness on a board
#IF ISDARKcondition to check if a board is dark
#GIVE/TAKE WICKto adjust remaining torch cycles
#GIVE/TAKE ENERGYto adjust remaining energizer cycles
- Pressing Z executes the code
- Pressing X executes the code
Zzo38, has also been working on a fork that has a bit of a variety in new features and fixes and is the first fork to play with the idea of extending ZZT enough to change its world format, focusing on creating new possibilities while maintaining support for classic worlds as well. FreeZZT is the most ambitious fork yet, and as its develops it will be interesting to see what comes of it! Zzo plans to focus solely on DOS support with Zeta and minimize ZZT's memory footprint as much as possible resulting in a lot of rarely used features being stripped away.Features:
- Error number properly displayed
- Game can be quit while paused by pushing "Q"
- (Untested) fix for passages being damaged when the player is hurt and "re-enter when zapped" is on
- Unpause by pushing enter
- Trying to delete a nonexistent stat no longer crashes the game
- Objects trying to push themselves no longer crashes the game
- Games can be restored while playing
- ?-KEYS, ?FLAGS, ?SPEED, and ?@ cheats (detailed earlier)
- Removes mouse, joystick, and monochrome support and the config prompt.
- "About ZZT" no longer automatically opens on startup
- Help files are removed
- ZZT's editor is removed
#GIVE/TAKE LIGHTallows adjusting remaining torch cycles
#GIVE/TAKE USERas a generic custom counter somewhat like Super ZZT's "Z"
- Gives the name HBEAM/VBEAM/UNKNOWN for blink wall rays and the unused element 46 so they can be accessed from ZZT-OOP
- Sound toggling can be done on the title screen
- World descriptions ("The Dungeons of ZZT") in the world menu are removed, instead file modification times are displayed in the world/restore menus
- Pressing X will clear flashing messages
- Home/End will jump to the top and bottom of popup windows. Tab will jump to the next hyperlink and wrap around to the top.
- Board size limit increased to 21000 bytes
- H sends all objects to a :HINT label (like Super ZZT)
Honestly I absolutely love having timestamps for saves. That is such a good idea. Zzo also has a list of some in-development features available as well.
All in all, it's been an extremely productive month! There's a lot of knowledge being shared in the Worlds of ZZT Discord which has a channel dedicated to the reconstruction and things built from it. For those interested in making their own modifications, ZZT's code-base is surprisingly straightforward, and those of us with experience in programming in non-Pascal languages have been able to jump right in. Experimentation is highly encouraged as we all figure out what the future of ZZT as engine might be like where limits are gracefully lifted and what will or won't catch on and be used when making ZZT worlds.
The Worlds of ZZT project is committed to the preservation of ZZT and its history.
This article was produced thanks to supporters on Patreon.Support Worlds of ZZT on Patreon!