If you appreciate the work done within the wiki, please consider supporting The Cutting Room Floor on Patreon. Thanks for all your support!

Bugs:ZZT

From The Cutting Room Floor
Jump to navigation Jump to search

This page details bugs of ZZT.

This page answers the question of "Just how many bugs can you fit into under 90 kilobytes of code?"

Gameplay and UI Bugs

Involving gameplay and the user interface.

Pause Bug

If an input method other than Keyboard is used (such as Joystick or Mouse), one can input a key press and a movement in the same engine tick. In particular, this allows moving and pausing the game in the same engine tick. As the player is always processed first on a given game board, while pausing and unpausing skips an engine tick for all subsequent stats on the board, this lets the player move an arbitrary number of times while the rest of the game board (enemies, objects, etc.) remains frozen.

This bug is of particular use to speedrunners, often providing a way to skip major chunks of a game world, such as in the video at right.

Pause Softlock Bug

It is not possible to un-pause the game without the Player making a movement, while being paused freezes the game. This means that some forms of pausing - for example, when the only means of movement require an Object, such as a scripted door, to execute code - lead to a softlock.

Text Window Bugs

  • Trying to create text windows above 1,024 lines in length causes memory corruption. This can be most easily seen by trying to open ZZT in a directory with more than 1,024 files in it.
  • Text windows with lines longer than the maximum length will lead to visual artifacts.

Other

When using the Spacebar to shoot in v3.2, it is supposed to use the last direction the Player moved in. However, this direction is not updated when the movement is part of unpausing the game.

Editor Bugs

Involving the editor.

Super Lock Bug

The board selection window operates like an Object's text window; in particular, labels are actually parsed. As such, if you name two boards like so:

  • !c;Jump to label
  • :c

...trying to select the first board with ENTER will not open the board, but move the cursor to point to the second board. This, combined with a deliberately corrupted board :c, was the basis for Alexis Janson's Super Lock.[1] While considered strong and irreversible at the time, it had two fatal flaws:

  • The board names were irreversibly discarded, and
  • One could still load the "protected" boards by pressing ESC instead of ENTER.

Other

  • Copying elements into the pattern bar using ENTER doesn't always lead to a visual refresh of the pattern bar.
  • Copying object code between boards is unstable due to dangling pointers.
  • Editing board information causes a (small) memory leak.
  • In v3.1 and below, importing a board from an external file would not clear the neighbor boards' IDs, potentially creating a board which points the Player to a board that does not exist.
  • In v2.0, saving the world while exiting the editor would incorrectly create a .SAV file instead of a .ZZT file.

Engine Bugs

Involving behavior of the game engine. Note that many of these cannot be patched without breaking fan-created worlds.

Elements

  • Blink Wall
    • Vertical Blink Wall Bug: If the player is hit by a horizontal blink wall, they will be damaged once, then pushed either up or down depending on which direction has an empty space. However, if a player is hit by a vertical blink wall, they will always be pushed right; if this is not possible, the player will take infinite damage until death.
  • Conveyor
    • Stat Swapping Bug: In certain cases[2], the Conveyor can end up swapping two adjacent stats' information. Most notably, this can cause the first element on the board to be something other than the Player.
    • Statless Empties Bug: If an element has no stat attached to it, but it is expected to typically have one (for example, a Lion), the Conveyor will destroy the element instead of moving it.
    • During a Conveyor rotation, the final element to be swapped (West for clockwise, South-West for counterclockwise) may not be properly updated visually. This is only a visual bug and has no effect on gameplay.
  • Duplicator
    • Koopo Bug: If a Duplicator causes a board change (such as by duplicating a Player onto a Board Edge), it will overwrite some values of the stat in its location in the stat array on the next board - Cycle and P1. The name relates to the community member Koopo.
  • Passage
    • If the player is standing on a Passage, paused, and touches a player-damaging element (such as a creature), the Passage's color is replaced with white on light gray.
    • If the player is standing on a Passage, paused, and touches another Passage to move to another board, the Passage the Player was standing on will be destroyed.
    • If the player is standing on a Passage, paused, and unpauses by walking onto a walkable element (such as a Fake), the walkable element is destroyed.
    • If the player moves from one board to another using a Passage, the walkable element in the last spot they were standing on on the former board is destroyed.
  • Transporter
    • Division by Zero Bug: Using an external tool to create a Transporter with a Cycle value of 0 will cause the game engine to crash.

Scripting

  • #BECOME self-destruction: Unlike #PUT or #CHANGE, #BECOME always creates a new stat in the place of the old one. As such, a command like #BECOME YELLOW OBJECT will erase the calling Object's programming.
  • #BIND double-free: Using #BIND on an object which has already been bound to another before causes a memory double-free, potentially leading to corruption.
  • #CHANGE stat limit bug: Using #CHANGE to create many statted empties (for instance, #CHANGE NORMAL SHARK - Normal is statless, while Shark is statted) will silently fail when the board's stat limit (150 in ZZT) is reached, turning the remaining elements into Empties.
  • #CHANGE uninitialized memory bug: Unlike other commands, providing an invalid #CHANGE command does not stop its execution. This can lead to uninitialized or otherwise unexpected memory values to be used to execute this command.
    • In classic ZZT fashion, there is at least one fan-created game[3] which makes use of even this bug to deliberately store variables on the stack.
  • #CHAR 0: Despite being a valid value, #CHAR 0 is ineffective. As characters 32 and 255 are typically visually equivalent, this is a non-issue in practice.
  • #GO IDLE: This command puts the Object in a loop where it constantly tries to execute the command on every tick, unlike the /i command.
    • This can be misused as a feature: #IF x GO IDLE is an efficient way to stall the Object's execution until condition x becomes false.
  • #PUT bottom-row: #PUT has no effect if the target placement location is on the bottom-most row of the board.
  • #SEND newline bug: #SEND-ing an Object to a label actually leads execution to start at the newline prior to the label. If a text window is in the process of being built, this will add an extra newline to the window's output.
  • #TAKE: When sidebar numbers are being drawn, ZZT suffixes the value with spaces as a way to clear digits when going from a larger to a smaller value. However, it only uses two spaces for ammo, and one for other values. As such, a ##TAKE which reduces a value by a very large amount will cause a visual glitch. For example, #TAKE HEALTH 98, given Health: 100, will display Health: 2 0 instead.
  • Scrolls have scripting bugs exclusive to them:
    • /[direction] can cause the game to crash.
    • #BECOME can cause the game to crash.
    • #GO IDLE causes the game to crash.

#ZAP/#RESTORE

The bugs with these two commands are so complex and numerous, they deserve a separate section.

  • #restore does not work for labels located on the first line, as it relies on a newline preceding the label to locate it. For example, the following will not work correctly because the touch label is on the first line of the program:
 'touch
 This will never be called!
 #end
 :shot
 #restore touch

This can be worked around by using any non-label for the first line (a command like #end, a program name, or even a blank line).

  • #restore fails to restore labels after the first zapped-matching occurrence if the line succeeding them starts on an alphabetic character or an underscore. For example:
 #end
 'touch
 /iFirst line.
 #end
 'touch
 Second line.
 #end
 :shot
 #restore touch

This code will only restore the first touch label upon being shot; the second label will only be restored upon being shot a second time. This is because the second succeeding line starts with an alphabetic character S. Conversely, the following code:

 #end
 'touch
 /iFirst line.
 #end
 'touch
 /iSecond line.
 #end
 :shot
 #restore touch

...will restore both touch labels upon being shot just once. This is because the succeeding lines start with a non-alphabetic character (/).

  • #zap restart and #restore restart will always edit the second character of the targetted Object's code into ' or : respectively.
  • #zap and #restore edit the code of an Object - this means that, if the code is being used by other bound Objects, labels will be changed on all of them.
  • Due to a code quirk, #zap allows remotely zapping labels on other targets - for example, #zap all:label will zap one occurrence of label on every object on the board.
    • #restore also allows doing this, but only for the first occurrence of a given zapped label on every object.
      • One can, therefore, use #restore self:label to only restore the first zapped label in an object.

Engine Limits

The engine has many undocumented limits, which can cause bugs or even crashes if surpassed.

  • Stats: a maximum of 150 per board. The engine actually keeps good track of this, but creating a file with more stats than that using a custom tool may cause memory corruption.
  • Board: a maximum of 20,000 bytes per board. While the format theoretically supports a maximum of 65,535 bytes, going over this limit will cause an overflow in a temporary buffer used to load and save boards.
  • Board #: a maximum of 100 boards (not including the title screen) per world.
  • World Size: as much as you can fit in the 640K of the conventional memory of the system. This depends on the execution method used; 300-350 kilobytes is anecdotally considered "safe", with the largest fan-created worlds of the 1990s reaching around 400 kilobytes. As the original worlds bundled with ZZT never surpass 90 kilobytes, with the shareware bonus worlds reaching 250 kilobytes at their peak (Mystery Manor), this was probably not deemed a concern during the engine's development.
  • Flags: a maximum of 10 set flags. The security flag SECRET and the debugging flag DEBUG count towards this limit.

World Bugs

Involving the scripting and interactions of the official worlds.

Best of ZZT

  • Part 1, Headhunter Village: Touching the bone hand will attempt to #GIVE GEM, which is not a valid command and will show an error message.
  • Part 1, Inside Pyramid: Touching a blue cane-shaped Object at the library will attempt to #GIVE POINTS, which is not a valid command and will show an error message.
  • Part 1, Pirates' Cavern: The pirates in the cavern are scripted to take health in units of 10 in an infinite loop to damage the player, but never check for failure. As such, if health is lower than 10, but higher than 0 - that is, not a multiple of 10 initially - the player cannot be killed by the pirates.

ZZT's Revenge

Ezanya

  • Citadel: Due to Dwarves on this board using shared code and zapping :SHOT labels, a situation may happen when shooting two Dwarves in quick succession leads to a state where it is no longer possible to kill any Dwarf.

References