Thirty years on, ZZT is still giving us mysteries to solve. After all these years not only are new glitches still being discovered, but ways in which their unexpected behavior might be used to do things previously not possible with ZZT. This article is going to cover one glitch discovered in October 2021 which has at least one pretty significant use-case. I'll cover how to setup the glitch, its discovery, what ZZT is doing behind the scenes to make the glitch act the way it does, and a bit about what else can potentially be done using the glitch.
For those reading that aren't as familiar with ZZT-OOP I want to explicitly define a few terms that will come up.
Element - An element is what occupies a given tile in ZZT. The board is completely covered with them. Even the border is made up of "board edge" elements! Solid walls, the player, a tiger, bullets, objects, text, keys, it's all elements.
The Koopo Method - A piece of engineering which forces a board change. Eventually. A duplicator is arranged so that it tries to duplicate a passage onto an object. Eventually when the board change is needed, the object turns into a player clone. The next time the duplicator activates, ZZT processes this as the player touching a passage and changes boards appropriately. This has a few drawbacks in that there's an inconsistent wait before the board actually changes, the constant sound of the duplicator being blocked (requiring an object to either play silence and suppress all noise, or just dealing with the sound), and some more complex issues with ZZT finishing processing the duplicator cycle on the new board which is an obstacle beyond the scope of this article.
Stat - In order for an element to do something, it requires a stat to be involved. Under normal circumstances, anything that needs a stat will be created with one. In the previous list, the player, a tiger, bullets, and objects all have stats. Walls, text, and keys do not. When you place a tiger in the editor, ZZT sets the tile to the tiger element, and then creates a stat for it. During gameplay, ZZT iterates over every stat in the list. Each stat contains a set of coordinates that point to a tile, and based on what is found in that tile, different functions are called to make tigers move, objects run code, and so forth. When a tiger moves north, its stat updates it coordinates to continue pointing to a tiger.
Some glitches, most notably a bug with conveyors, can cause a stat to disassociate from its intended element. Additionally, external editors allow you to create statless elements as well as give a stat to elements that normally don't have a stat. For the most part these aren't too useful for gameplay, mostly being used in areas inaccessible to the player and making things like crowds with statless players or unique backgrounds with now inert creatures that can be infinite in number as there is a limit to how many stats can be on a board. In some extreme cases, interacting with something ZZT expects to have a stat that suddenly doesn't may even crash the program!
The stat is the heart and soul of an element that allows it to act. Without one they just sit there, relying on other interactions like a player touching one in order to be able to do anything. On the other side of things, giving a stat to something that normally wouldn't have one, generally does nothing, though there are at least some clever uses to the technique.
All stats have the same structure, and based on the element found at the stats coordinates, the data may be parsed differently. Stats have three parameters, often referred to as param1 through param3. They have an X-Step and Y-Step which tracks what direction an object walks in or what direction a transporter is facing. Stats hold ZZT-OOP code as well. Many elements ignore much of this data, such as ZZT-OOP which is only used by scrolls and objects. If you were to put code in a passage, it would do nothing. At least until the discovery of this new glitch.
I've dubbed this technique "The Lion, The Switch, And The Wardrobe" (I'll get into why on earth it would be called that later.) The name makes it easy to remember the order the components are to be placed. This exploit in ZZT's stat handling is capable of doing more than the core example which will allow the programmer to force a board change at any time they like. While the on-demand board change is perhaps the most useful thing you can do with the glitch, you can definitely do some more esoteric things as well. (I'd love to be proven wrong about there being limited useful applications of the glitch.) A more generic name for the glitch might be "Elementary Code Execution" as the technique can allow any element with a stat to execute a limited amount of ZZT-OOP.
The Setup
Let's go through the most practical example first, and create an LSW powered board change. This requires three elements to be placed in a specific order: the Lion, the Switch, and the Wardrobe.
The Lion
First is our lion. This element is a sacrificial stat that must come first in the stat order before the Switch and the Wardrobe. It does not have to be a lion. In practice any element which has a stat and can be referenced by ZZT-OOP via #change
will do. This element can be set to black-on-black or whatever color will allow it to be camouflaged if you don't want the player to expect that such a technique may be used on the board.
The technique only works when the event that causes the stat's destruction is #change
or #put
. Shooting, bombing, #becoming
, etc. will not execute the glitch.
Depending on the board's design it may be useful to set this sacrificial stat to cycle zero in order to ensure that it doesn't get an opportunity to do anything. This is not the same as a statless element. A statless element will never get an opportunity to be processed. A cycle zero element will be iterated over like any other stat, but never get to act.
The Switch
Second, you'll need an object to actually execute the #change
command and begin the exploit. This doesn't need to be initiated by the player, but it's easy to do so for a manual demonstration. Its code is simply the following:
#end
:touch
#change lion empty
That's it! The element the Lion is changed into doesn't matter either. It can be transformed into something with a stat or without so long as the original stat is destroyed. You will likely only need one element to match the #change
command.
The Wardrobe
Lastly, is the element which will run ZZT-OOP regardless of whether or not it's an object. This one requires the most nuance to use effectively. In our case, this will be a duplicator which will be manually activated in order to trigger a board transition by duplicating a passage onto a player clone, aka the traditional "Koopo method".
Normally, the setup for such a transporter would be to have a duplicator with maxed duplication rate, and ideally something to mask the constant duplication blocked noise while waiting for it to be time to change boards. In this case, the duplicator's initial stats actually don't matter at all. It's still easiest if you set the source direction for duplication however, though even this can be handled in code later. One added benefit of the initial stat being irrelevant is that you can even set the duplicator to cycle zero in your external editor of choice and not have to worry about an independent silencer object.
Now for the real magic which lets us just decide to send the player to Narnia or something. (Listen, the Lion and the Switch were easier to justify the names of.) This code needs to be added to the duplicator. In KevEdit, this can be done with control+T to edit the duplicator. Then hit enter on the "ZZT-OOP Kind" field and turn the duplicator into a scroll or object temporarily. Edit the code there in these OOP-supporting elements, before using the same functionality to turn your scroll/object back into a duplicator. Modifying an element's kind in this manner in KevEdit will preserve its stat so you'll end up with a duplicator with code. (You can also just start with an object/scroll and then turn it into a duplicator and adjust the duplicator's settings afterwards.)
The code should look like this:
@Sacrificial Line of Code!
#cycle 1
#char 5
Line by line the following happens:
@Sacrificial Line of Code! - The first line of code is skipped with this exploit as the OOP parser that starts executing code like this is trying to tell the Switch object to advance to the next line. This can be a blank line, an object name, a comment, even #end
! The parser will advance to the next line and begin execution from there.
#cycle 1 - This is to make sure that the duplicator wakes up. If your duplicator is set to cycle zero, changing the cycle is required. If it's non-zero, you don't actually need to do this, though you're kind of defeating the purpose of having a silent duplicator and are once again at the mercy of the game tick in determining when the duplicator acts rendering this technique pretty pointless.
#char 5 - Because all stats have the same format, but different meaning, you can use the #char
command to arbitrarily set parameter one in the element executing code. For duplicators, this value counts how long until duplication is to actually occur. Setting it to five or greater means that the duplicator will duplicate on its next tick.
From here everything else works like the Koopo method as normal. You can either duplicate a passage onto a player clone to transition the player to the destination board, or if you want a naturally silent transition to an arbitrary destination tile you can use a board edge. Depending on the specifics of your boards this may require surrounding the board's borders with statless player clones.
Additionally, you can silence the passage noise by adding #play sx
into the Switch object's code before the #change
command.
And if you really want to be lazy about setting your duplicator parameters, you can set the duplicator's source direction in ZZT-OOP by having the duplicator execute a #walk
command where the direction points to the passage or board edge.
How Was This Found?
As is often the case, through sheer accident. While doing some conceptual work for my 2021 Oktrollberfest entry Bear With Me I managed to unintentionally set up the proper scenario. Originally, board exits would be blocked off until you led a bear onto a fake wall where it would then let you leave the board, and temporarily change the bear into a blinkwall. When the player re-entered the board they'd have a few moments before the blinkwall would be turned back into a bear.
Because this was for Oktrollberfest I thought it might be funny to have the game's explanation be in a scroll directly next to the bear's starting position, a near guaranteed game over for any player who dared read it without getting the bear out of the way at first. You'd get your popup about how the bear must not come to harm, close the window, and probably immediately get attacked.
Everything happened to be in place. The bear was the Lion sacrificial stat. The object was a pressure-plate based Switch. The third stat miraculously happened to be a scroll where code would be entered freely. It could have been an object whose first line of code was a name and second line was #end
which would have led to the glitch executing and doing nothing, going entirely unnoticed.
My scroll was just a two-line placeholder. Having the bear cover the fake wall and be transformed resulted in a flashing single line message of text at the bottom of the screen. Something was amiss. This kind of derailed my afternoon, as suddenly I was trying to figure out what was going on here and how exactly to cause it to happen. Deliberately poor game design paid off for once.
Why Does This Work?
Moving over to the Reconstruction of ZZT this obviously begins in the OOP parser when #change
is called. The code validates the parameters for colors and elements that were provided before iterating over each tile on the board with OopPlaceTile()
. This function ("procedure") does the comparison and decides if the tile should be overwritten. If it should, the function BoardDamageTile()
is called. If it finds a stat, DamageStat()
is called next! This function handles the player's health being reduced when harmed, or plays the relevant sound effects when you destroy a creature or bullet. Then it calls RemoveStat()
where something finally relevant happens.
if statId < CurrentStatTicked then
CurrentStatTicked := CurrentStatTicked - 1;
Since the Lion comes before the Switch, the index as to which stat is currently being processed needs to be rolled back by one for each stat destroyed by the #change
command. This is still all correct. In our duplicator example the stats were as follows before and after the #change
:
Before | After |
---|---|
|
|
The stat index is properly updated from two to one, so why is the new stat two, a non-object/scroll, suddenly running code?
Because ZZT never stopped processing OOP after it read the #change
command, and that code block is working with its own variable that still says to use stat two! So now, after having completed a line of code it advances to the next line, which means skipping the first line of our Wardrobe and executing code from there! This explains why the duplicator can run code and why the first line is ignored.
Hold on though. If the position in code was advanced to the next line for the duplicator that means that it wasn't changed for the Switch object. You might expect this to mean that the command is ran a second time, but that doesn't happen. The instruction pointer's position ends after the last argument to the #change
command. This results in a weird quirk where you can put a second instruction on the same line of code. Something like #change lion empty/s
on a single line will result in the object moving to the south if the lion comes before the object in the stat ordering. Otherwise, the movement is discarded. There you have it, the most contrived instance of saving a single byte of memory imaginable.
What Else Can You Do With It?
At a glance, this trick seems like it can be incredibly powerful. Giving anything the ability to execute code should be a game changer. Unfortunately, it's not that simple, and the limitations of the glitch actually heavily restrict the possibilities.
The biggest hurdle is in the fact that activating this glitch is inherently limited to how often you can use it. In the duplicator example previously, a lion was destroyed to begin the process. Repeating the process requires additional stats to be destroyed, and they have to be located before the Switch object that destroys them in the stat order. It is possible to repeat this technique, destroying a different sacrificial stat each time, but they have to be allocated in advance as stats can only be added to the end of the stat list, not the beginning. This means you can only sacrifice stats a finite amount of times before you've run out which makes many other more gimmicky-applications of the glitch fall flat.
Manipulating duplicators is by far the best thing that's been come up with for the trick. Most notably it throws out a whole bunch of synchronization efforts documented in asie's Full motion video in ZZT: State of the art article. If you're doing a one-time board change sequence, this technique is basically the dream.
The other hurdle to deal with is exactly what code can be executed. The code that runs can only run on the cycle where the Switch is destroyed. This gives you a narrow window, and while it's possible to run any individual command, several commands instantly end the cycle for code parsing. This is why objects can only move once per cycle, and why you can #put
multiple elements simultaneously. The former ends the cycle, while the latter does not. There's still an upper limit of thirty-two commands as ZZT eventually worries that the code is taking too long and thus ends the cycle regardless of what commands were executed, so you can only do so much even when restricting yourself to a few commands.
Cycle-Ending | Non-Cycle-Ending | |
---|---|---|
|
|
|
* Internally these commands do not stop code from being processed, however the effects of the commands prevent additional code from processing. This in the source of the "diemove" glitch (which works just as well as "becomemove") where the stat after the self-destructing object will move based on its X/Y-Step with the values being interpreted as a vector for ** As discussed previously, the object that executes |
Another thing to consider is that ZZT-OOP abides by the rules of objects, and not the element that's actually running the code. Can you make a player clone move with /n
? Yes. Can you make a player clone pick up a key by doing this? No. Just like an object, it will push the key out of the way, or if it's blocked from doing so, it won't move at all.
So you can do something gimmicky like have a ruffian shoot a bullet sure, but practical efforts are a bit limited. Most goofy things I can come up with can be just as easily faked with an object pretending to be a different element. You don't need this trick to make a door change its color or have a pusher change direction, those can be done repeatedly and with much less preparation by just using objects instead. Where creative applications can come into play mostly relies not by having an element act like an object for a flickering moment, but to take advantage of how many of an element's normally immutable properties can then be modified when said element is running ZZT-OOP.
The following commands allow manipulation of an element's properties that otherwise cannot be changed during gameplay:
#cycle
- Sets the element's cycle to a number from 1-255. The ZZT-OOP parser prevents setting a cycle to zero so you cannot use this to freeze an element.#char
- Adjusts the element's param1 value. This is again from 1-255. This value can adjust a blink walls' starting time, bears' sensitivity, various other creatures' intelligence, bombs' timers, etc.#lock/#unlock
- Sets the element's param2 value to 1 or 0 respectively. This value can adjust duplicators' duplication rate, blink walls' periods, ruffians' resting time, slimes' movement speed, tigers'/spinning guns' firing rates, and centipedes' deviance. However as only zero and one can be set this way the options are rather limited.#walk
- Sets the element's X-Step and Y-Step. This is usually one of five possible values depending on which direction is specified (plus idle), however if you manually set the element's X-Step and Y-Step in advance, you can rotate those values with the flow direction and modifiers to it.
Of these, there's some potential to be found beyond activating duplicators. You could engineer an explosion sequence with bombs and numerous sacrificial stats without having a dozen of the things ticking away the entire time. (You can even set up the activation of the next explosion by having an object in the previous explosion jump to a :bombed
label.) #walk
offers the most exploitable potential here as step values are used by several objects to track directions of things. A blink wall with code can alter the direction of its rays. A one-way transporter into a boss arena can let the player exit by changing directions after a foe is defeated. A pusher can reverse. It's not much, and one stat needed to be destroyed per element that needs to run some code means that big changes are a considerable challenge to set up. Still, some clever tricks can most certainly be played.
Perhaps the biggest disappointment though is that ZZT-OOP has no way to access a stat's third parameter, a parameter used in ZZT by a single element: Passages. So as wonderful as it would be, you cannot cause a passage to change its destination. Drat. The best alternative I can come up with is having a second passage with a modified X/Y-step #put
an empty relative to its flow direction to erase the old passage, and then adjusting its X/Y-step with #walk cw|ccw|opp flow
and have the passage with the new destination move into place.
Hopefully this information is of some use! If you're dealing with one-way board transitions from a menu or cut-scene, this is an invaluable tool to be aware of. If you're just playing around, or perhaps gathering horrible ideas for Oktrollberfest 2022, I fear seeing what you come up with! By the time this article goes public, my end of year compilation world 2021 Dot ZZT should have a few demonstrations included.