Post subject: Zork (no, really!)
Joined: 7/25/2007
Posts: 109
You might think that the only reason to do a TAS of this text adventure would be for the whole grue thing. And yeah, that is half of the reason why I suggest it. >_> But besides that, the game also has some interesting bugs that might make a playaround viable. A long while back, there used to be this site that listed bugs in old Infocom games. I managed to save that site before the host went down, though.
1. Many commands which produce strange output in later versions, give no response at all in Version 2, and the game simply goes on to the next turn. For example, the commands READ MAILBOX, and EAT ME give no response at all in Version 2, but in the next version (Version 5), you get "How can I read a mailbox?" and "I don't think that the you would agree with you." -- Alan Franzman 2. THE SLIDE BUG: In Version 2, when you drop items down the slide, the game first tells you that it's done, then tells you it can't be done, and then removes the item from the game entirely. For example: EXAMINE "" >DROP TRIDENT IN SLIDE "The crystal trident falls through the slide and is gone." "I can't do that." The trident is now out of the game. -- Alan Franzman 3. The earliest releases of Zork I (Versions 2 and 5) can become very confused if you nest objects too deeply in your inventory, such as putting the lunch in the sack, then the sack in the coffin. You may get very spurious output from the INVENTORY command like "Such language in a high-class establishment like this!" messages, with other random junk interspersed in it. This can also lead to the object hierarchy getting screwed up, in such a way that an INVENTORY might claim that you are carrying a bunch of rooms around! ADDITIONAL: Technically this bug is present in all versions of the game. In Versions 2 and 5 it occurs when three objects are nested (i.e. canary in egg in sack). In later versions it only occurs when 7 objects are nested, which is difficult but not impossible to create. Take the canary, egg, sack, buoy, coffin, and (inflated) boat to the Shaft Room, and issue the following commands: PUT EGG IN SACK. PUT CANARY IN EGG. PUT SACK IN BUOY. PUT BUOY IN COFFIN. PUT BOAT IN BASKET. PUT COFFIN IN BOAT. LOOK. (As everything can't be carried simultaneously, some TAKE and DROP commands may need to be interspersed here.) When you LOOK, a random phrase from somewhere else in the game may be mixed with the output. In the Solid Gold version, the phrase "up to your ankles" (from when the water is rising in the Maintenance Room) will be before the description of the canary. In other versions there are different effects, some random phrases, and some gibberish. -- Michael the Great 4. In Versions 2 and 5, commands such as GO DOOR or GO TREE can send players into random locations. -- New Zork Times 5. In Version 5, the command HIT MIRROR WITH SWORD will generate combat responses, such as "The mirror parries", or "The mirror dies in a cloud of sinister black fog." In Version 2, you miss the mirror and the game crashes with a Stack Overflow error. -- Graeme Cree 6. In Versions 2 and 5, the command GIVE AXE TO TROLL will generate a response like: "The troll accepts your gift, and not having the most discriminating taste, eats it. The troll, dismarmed, is cowering and begging for forgiveness in the gutteral language of the trolls." (In Version 2, he "gleefully" eats it. Picky, picky.) Though the troll can no longer attack you, he still blocks the exits and parries, while continuing to beg for forgiveness. -- Graeme Cree 7. In Versions 2 and 5, if you give the troll to himself, he eats himself and disappears. He still bars you from leaving the room though, only now he can't be attacked. -- Graeme Cree NOTE: In fact, as Ethan Dicks has pointed out, virtually anything can be given to the troll (or thief) in this version: your hands, your eyes, the eastern wall, a compass direction, even "ME". The troll will then eat them and you will be unable to interact with them for the rest of the game (although you can still go "east" if you give "east" to the troll). The thief of course will leave them lying around instead of eating them. 8. In Versions 2 and 5, if you give the troll to the thief, the thief puts the troll in his bag, but the troll still blocks the exits. If the thief is killed, the troll reappears. -- Graeme Cree 9. In all versions after Version 5, if you try to use a compass direction while in the boat, the game will (reasonably) tell you that you can't go that way in a magic boat. In Versions 2 and 5, it will tell you that you can't go that way in a tan label. -- Graeme Cree 10. In all versions after Version 5, when you reach the portion of the river where the buoy is, the description will tell you that the buoy is "(outside the magic boat)". Versions 2 and 5, tells you that the buoy is "(in the room)"! -- Graeme Cree 11. If you try to GET OUT OF THE BOAT while floating down the river, all versions of the game tell you that you realize "just in time" that such a course would be suicidal. In all versions before and after Version 5, the game prevents you from leaving the boat. In Version 5, it lets you leave and kills you, despite having told you that you had realized not to do it. -- Graeme Cree 12. THE FLOODGATES BUG: In version 5, if the nest is not taken at location Up a Tree, the room description eventually changes to a torrential flood of output, several screens long, containing a mishmash of fragments of room, noun, and action descriptions that you see whenever you enter the room (except in superbrief mode), or do a LOOK. It isn't clear precisely what causes it, but it happens every time if you go down into the GUE, through the maze, grab the bag, come out through the Cyclops Room into the Living Room, and outside to the tree. It might be triggered by a certain point level, or something else. If you try to TAKE the nest after the bug has triggered, the game will lock up. Taking the egg before or after has no effect. -- Graeme Cree 13. In Versions 2, 5 and 15, shaking an open container that isn't empty may crash the game. This works with the sack, but not the bottle. -- New Zork Times 14. In Versions 2, 5, 15, 23, 25, 26, and 28, you can bump your head on things such as the river by trying to ENTER them. -- New Zork Times 15. THE WIMPY PHANTOM THIEF BUG: You know, that thief is really tough in combat. Why not fight his phantom instead? He doesn't fight back. In Versions 2, 5, 15, 23, 25, 26, 38, and 30, engage the thief in combat (outside of his lair). Wait until he robs you and leaves; you will see this message: The other occupant just left, still carrying his large bag. You may not have noticed that he robbed you blind first. At this point, continue to attack the thief, using the AGAIN command. You will continue to attack the no-longer-present thief, but he will not fight back, and eventually you can do enough damage to kill him. A couple of points; this won't work if you attack the thief with the sword, since when he "robs you blind", he takes the sword as well as your treasures. You must use the axe or nasty-looking knife. Also this only works with the AGAIN command, (with AGAIN representing ATTACK THIEF WITH AXE, or something like that) not by attacking him directly. Thirdly, it may take several tries to get this to work, since the thief does not always rob you in the middle of a fight. Sometimes he stays and fights (and kills you). Killing the phantom thief kills the real one as well, and you can now explore the game without interference from him, even when you find his lair. Sound great? Well, there is one slight hitch. Remember the things the thief took when he robbed you blind? He never had a chance to stow them in his lair, and they are nowhere to be found when you kill the phantom (his stiletto is left behind, but not his bag). This means any treasures that he took are out of the game, and the game may be unwinnable. Therefore, the only way to use this bug to your profit is to engage the thief in combat when you are carrying no treasures (except the sword, which he will take). -- Kevin (Piouhgd@aol.com) 16. In Versions 2, 5, 15, 23, 25, 26, 28, and 30, you can take the torch into the Gas Room without setting off an explosion, provided that it is inside the Raft or the Coffin (open or closed). -- Allen Garvin 17. In versions 5, 15, 23, 25, 26, 28, and 30, the single-line description of the torch and candles stays unchanged after they have been removed from their initial locations. For example, put the torch into the gold coffin, close it, then open it again. You will see: >OPEN COFFIN The gold coffin opens. Sitting on the pedestal is a flaming torch, made of ivory. OR: >OPEN COFFIN The gold coffin opens. On the two ends of the altar are burning candles. In versions 75, 76, 88, and 52, this is fixed, and you get these responses: >OPEN COFFIN Opening the gold coffin reveals a torch. >OPEN COFFIN Opening the gold coffin reveals a pair of candles. This doesn't seem to work in Version 2, if only because in that one, the coffin is so much smaller, and can't contain either the torch or candles. -- Graeme Cree 18. In Versions 2, 5, 15, 20, 23, 25, 26, 28, and 30 you can take the boat while already in it. DROP the (inflated) boat in the living room. Then GET IN BOAT, PUT BOAT IN CASE, and then GET ALL FROM CASE. The game will respond: magic boat: Taken. nail: It's not in that! At this point you will be both occupying and carrying the boat. A LOOK command (and attempting to move) will tell you that you are in the boat, but it will not show up in an INVENTORY command. Once you get out of the boat, it will be placed in your inventory. This assumes that you are playing the game from the original Infocom interpreter. Frotz 2.32 will put up with none of this nonsense, and will crash the game when you try to GET ALL FROM CASE. In versions 75, 76, 88, and 52 this does not work, because you are prohibited from PUTting things unless you TAKE them first. But the lack of such a prohibition in earlier versions may be responsible for numerous bugs. For example in versions 30 and earlier, you can take the raft to the Treasure Room, and PUT CHALICE IN RAFT without first defeating the Thief in combat. -- Allen Garvin 19. THE PICKUP BUG: In games like Planetfall and Enchanter, people have reported situations where one could exceed their normal weight limits, and carry something that they could not pick up themself, simply by having someone hand it to them. I have never regarded these as bugs, reasoning that if you're holding an armload of equipment it's easier to take something that someone gives you than bending over and picking it up. I can't defend this bug, though. In all versions up to Version 30 (except perhaps Version 2 again, where the containers are so much smaller), you can pick up items that you normally couldn't, simply by putting them into something else that you're already carrying. For example, try this: 1) Put the bell and axe (nothing else) into the gold coffin, and leave it open. 2) Have only the lunch, garlic and coffin (loaded with bell and axe) in your inventory, with the empty sack at your feet. If you then try to PICK UP SACK you will be told "Your load is too heavy.", but if you PUT SACK IN COFFIN it works, and you're told "Done." You can signifigantly exceed your weight limits this way if you pick up items by putting them in the coffin and then take them out of the coffin to make room for new items, but there are limits. Eventually things will start slipping from your fingers and falling to the ground when you try to take them out of the coffin. In versions 75 and later, you cannot put something into something else unless you already have it in your inventory when you issue the command. --Fredrik Ekman 20. THE PHANTOM BOAT: In Versions 2, 5, 15, 20, 23, 25, 26, 28, 30, 75 and 76, you may continue to occupy the boat after you've destroyed it. Inflate the boat, and drop it. GET IN BOAT, then CUT BOAT WITH SWORD. The game will reply: "Your skillful swordsmanship slices the magic boat into innumerable slivers which evaporate instantaneously." However, you will still be considered to be in the boat for all locational purposes, save that you will not be able to refer to it. If you were in the water when you destroyed the boat, you will still be afloat. Unfortunately, in most versions you will not be able to get out of the boat, (such as by saying DISEMBARK BOAT) not being able to refer to it, nor will you be able to give it directional commands (if you are in the river), which means that you are trapped, and the game is unwinnable. In versions 75 and 76, you will at least be able to escape the phantom boat by typing STAND, a command that exits the boat without directly referring to it. Earlier versions do not know this word. The same thing will happen if, instead of destroying the boat, you give it to the thief while inside it, and he leaves with it. -- Allen Garvin 21. If you knock the thief unconscious and try to take his stiletto, the game says that it is "white hot," and that you drop it -- obviously some sort of protective magic. However, in Versions 2, 5, 15, 23, 25, 26, 28, 30, and 75, if you try to PUT STILETTO IN SACK (for example) it will let you, but when the thief wakes up, he continues to attack you with the stiletto, even if you have moved it to a different room! In later versions, it refuses to let you put it in the sack at all, saying that you don't have the stiletto. -- B.J. Parker 22. BIRTHDAY CANDLES BUG: In all versions except the Solid Gold (and Mini-Zork), Zork 1 apparently uses trick relighting candles. The first time you go to the Temple, type BLOW OUT CANDLES, *without* taking them, then type LOOK. The description will say "On the two ends of the altar are burning candles.", but if you examine them, it will say that they are out. If you try to light them, you will be told "The candles are lit," instead of "already lit" which is what it says when they are burning. If you do an INVENTORY command, the candles will not say "(providing light)". However, the room description is correct, and all of the other indicators are wrong. The candles *are* still lit and will quickly become too small to use unless you put them out for real, by taking them and either dropping them, or lighting and blowing them out again. The only reliable way to blow out the candles is to hold them while you do it. This bug drove me, well, buggy, until I tracked it down, trying to figure out why my candles were being destroyed even though I knew I had blown them out. -- Graeme Cree 23. Here's a variation on the Starcross disk bug. If the sack and the bottle are both empty, it's possible to put the sack in the bottle, then put the bottle in the sack, causing both to vanish. This exists in all versions except the Solid Gold, Mini-Zork, and Version 2 (because the bottle is too small to hold the sack in that one). -- New Zork Times 24. BOTTLE BUG: In all versions except the Solid Gold and MiniZork, you can POUR WATER regardless of whether the bottle is open or closed. -- Graeme Cree 25. TRAPDOOR BUG: In versions 75, 76 and 88, there is a way to open the trapdoor from the inside. At the beginning of the game, enter the following commands: N. E. OPEN WINDOW. W. W. TAKE LAMP. MOVE RUG. LIGHT. OPEN TRAPDOOR. D. N. G. When you enter the G (or AGAIN) command, you will be told "The trap door opens." You can then return to the Living Room, and the trapdoor will not be barred behind you when you descend to the Cellar later. The order of the commands matters. If you reverse the order of LIGHT, and OPEN TRAPDOOR, then you will be told "It is already on" when you type AGAIN. For some reason the AGAIN command thinks you are referring to the lamp. It isn't clear if this is a bug or a playtesting device that Infocom forgot to remove. -- Allen Garvin NOTE: Matthew Russotto suggests that this bug is a combination of another bug wherein AGAIN did not check to see if the object referred to was still present, and a deliberate change that made AGAIN ignore directional commands. The Phantom Thief Bug was another manifestation of this, but that bug was patched separately, rather than fixing the underlying problem. Therefore, AGAIN in this case refers to your opening of the trapdoor three moves ago, because all commands in between have been directional. He points out that similar tricks can be used to disarm the troll, and to attack him when he is not present (a la, the Phantom Thief Bug). From the Troll Room, the commands TAKE AXE, S, AGAIN, will let you take the axe while you are in the cellar, even though it will be invisible to LOOK and INVENTORY commands. Also from the Troll Room, the commands, ATTACK TROLL WITH SWORD, S, AGAIN, gives you the Phantom Troll Effect (similar to the Phantom Thief), allowing you to attack the Troll while he isn't present, and can't fight back. 26. Another container bug, but this one is in all versions, even the Solid Gold edition (Except for Version 2 with its notoriously small containers again). If you put the (inflated) raft in the coffin, then the coffin in the raft, both will disappear. This does not work the other way around, however. If you put the coffin in the raft first, then the raft in the coffin, you get a message saying that there is no room. In Mini-Zork, this bug not only appears, but the game locks up to boot (no pun intended). -- Graeme Cree 27. THE RAFT OF HOLDING: When you put objects in the raft and then deflate it, they effectively cease to exist, allowing you to carry as much weight and as many objects as the raft will hold (it won't hold the gold coffin, unfortunately, and of course sharp objects are out). This is because the game handles the pile of plastic and the raft as two separate objects, and there's no provision for handling any objects inside the raft when the switch is made. -- Stu Galley in The New Zork Times 28. TEETH OVERBOARD: All versions of the game (except Mini-Zork) have an odd problem synonyming the word overboard (which really shouldn't be a noun at all, should it?). In versions 2-28, overboard is a synonym for object 212, in version 30 object189, and in versions 75 onward as teeth (!), as can be seen by typing EXAMINE OVERBOARD on the first turn of the game. -- Gunther Schmidl
There could be more to find on other sites or by experimentation. If we're speaking strictly speed, then bug 4 may be able to get us to the end without doing much, but of course, that wouldn't be interesting. It'd be cool if a glitchy playthrough could be made using all the bugs on the list. The only problem will come in the form of actually finding an old enough version to exploit most of them.
Joined: 1/11/2009
Posts: 44
I think this could be viable. It might be tricky finding the best speed to go so that people can follow the action while still getting a good sense of WTF. This site has a long list of bugs that weren't on Graeme Cree's site; using as many glitches as possible off of both lists would be quite a task. Finding an old version isn't a problem; the Interactive Fiction Archive has patches to convert Infocom games from one version to pretty much any other here. This has got my interest. It'd be cool to see a playaround, even if it didn't get published.
Active player (452)
Location: Houston
Joined: 12/16/2008
Posts: 458
Location: Houston
was waiting from someone to do an old infocom game, I can't wait
Post subject: Rise from the grave, Zork thread.
Active player (422)
Joined: 9/25/2011
Posts: 652
I'm doing a Zork I run (yes, really!), specifically a 100% run aiming for vault. One big question I have, though is: What's the end screen for this game? I could pull up the score screen after hitting a score of 350 to show that I've achieved 100%, but that still leaves a few screens untraversed in the game. Since they don't count for score, one could argue that they aren't really needed to "beat" the game. Alternatively, I could end after it dumps you to the command prompt, but it would make for a very confusing and even less entertaining run, and there'd be no easy way for viewers to verify that 100% was actually reached. On a separate topic, I don't see any specific glitches that will help me out in this run, though feel free to suggest.
DrD2k9
He/Him
Editor, Expert player (2558)
Location: US
Joined: 8/21/2016
Posts: 1192
Location: US
Isn't there an exit to go through to complete the game? I think the pathway doesn't open up until all the treasures are in the trophy case thing inside the house. If I remember correctly, once the path opens, taking it leads to an endgame which is essentially the lead-in to Zork 2. EDIT: With being able to buffer key-presses, this could be a really quick run. Also I think you can stack directions and commands. Typing 'NW' will go Northwest Typing 'N W' will go north then west. (it may require a comma between commands 'N, W' ) I believe you can even do room actions within a stream of direction commands. 'N W Pickup X E E S' EDIT 2: I'd be willing to help if you want to collaborate.
Player (31)
Location: Amsterdam
Joined: 8/29/2011
Posts: 1207
Location: Amsterdam
Great idea, and a classic worthy of a run.
c-square wrote:
One big question I have, though is: What's the end screen for this game? I could pull up the score screen after hitting a score of 350 to show that I've achieved 100%, but that still leaves a few screens untraversed in the game.
In a game that keeps its own score-out-of-maximum or completion percentage, a 100% run means using that value from the game, and not something arbitrary like "enter all rooms".
DrD2k9 wrote:
this could be a really quick run.
Yep. Well we also have CD-Man in one nanosecond :) Frankly I think the best way of showing this run is via a text transcript (with all inputs and outputs, so the player can read at his own speed). I suppose site policy requires a movie (e.g. on Youtube) but you could easily have a text file in addition to that.
Active player (422)
Joined: 9/25/2011
Posts: 652
Happy to have the help! It looks like I might have bitten off more than I can chew. There’s a bunch of things that have to go just right on the run (killing the Troll in one hit, getting the egg stolen early, not getting hurt by the thief x2, having candles blown out on one particular move) that it’s going to take a lot of work finding the right starting clock time to set the RNG properly.
Active player (422)
Joined: 9/25/2011
Posts: 652
DrD2k9 wrote:
Typing 'N W' will go north then west. (it may require a comma between commands 'N, W' )
I can confirm this works (with commas)
DrD2k9 wrote:
I believe you can even do room actions within a stream of direction commands. 'N W Pickup X E E S'
This only works up to the pickup part. For example, the first commands in the game chain like this "N,N,U,GET EGG" and that works. But this gives an error: "N,N,U, GET EGG,D"
DrD2k9 wrote:
EDIT 2: I'd be willing to help if you want to collaborate.
Sounds great! FYI, if we have to do much RNG digging, my part may have to wait until the new year, as my free time is very limited right now.
Post subject: Baseline TAS for Apple II
Sand
He/Him
Editor, Player (168)
Joined: 6/26/2018
Posts: 238
Here's a first draft of a TAS of Zork I for Apple II. It uses revision 88 of the game, which, according to a survey by Data Driven Gamer, is "by far the most widely distributed version". The total time is 18871 frames or 5:14.52, in 258 moves. User file #638893574504520990 Link to video (Since this TAS is for Apple II, I debated about whether to add it to this thread here in the MS-DOS Games forum or start a new thread in the Other Computers forum. I decided it's probably better to keep all the discussion for the game in one thread, even if it's a multi-platform game.) You may have seen that I've been working on scripting TASes with Lua and that I used the technique to improve the "fastest eaten by a grue" TAS. This is a straightforward extension to the full game. I didn't do independent route research for this run; instead I implemented Gym Slow's RTA route, unmodified except for a SUPER command at the beginning to activate superbrief mode. There are various small changes that would make the run faster taking the Apple II platform into account, as well as potential larger route changes taking advantage of the RNG manipulation that is possible in a TAS. I haven't done any of those. My idea was to let this run serve as a baseline to compare future improvements against. The run being scripted in Lua (actually Fennel) means that there is a script (100%.fnl) that contains a lot of code like this:
;; "80-COLUMN DISPLAY? (Y/N)"
(enter-text "n")

;; Skip over some loading. Get to somewhere near the first input prompt.
(savestate.next-frame 300)

(enter-commands ["super"])

(wait-for-keyboard-ready)
(savestate.next-frame 1) ;; RNG manipulation (avoid songbird).
; TODO use Shift or other keys for RNG manipulation.

;; Enter the schedule of commands.
;; TODO joining with "." can save time and alter RNG.
(enter-commands
  [
   ;; Climb the tree, get egg, and climb down again.
   "n"
   "n"
   "u"
   "get egg"
   "d"

   ;; Enter the house.
   "s"
   "e"
   "open"

   ;; ...
   ])
You can modify the run by modifying the script and re-running it in BizHawk. However, even small changes will require re-syncing RNG. I haven't done anything yet to automate RNG manipulation, which is needed to avoid unwanted random encounters, win combat quickly, and prevent certain other random events. I implemented RNG manipulation in this run by inserting 1-frame delays, though I think there are better ways to do it. You can see one instance of RNG manipulation in the snippet above, the (savestate.next-frame 1). More about RNG below. Other notes on this run and thoughts on possible improvements: 40-column mode vs. 80-column mode. The first thing the game does is offer a choice between 40-column mode and 80-column mode. 80-column mode looks nice: it has more text on the screen and uses both upper- and lowercase letters. I did a quick test of commands up to collecting the sword and lamp in the living room; it was 1279 frames in 40-column mode and 1341 frames in 80-column mode. So I guess 40-column mode is generally faster. But the choice of 40 or 80 columns could be considered a speed/entertainment tradeoff. Ultimately, I'd like to make the control script adaptively manipulate RNG, such that the run can be played back in either mode. Brief versus verbose. Besides the default verbose mode, the game offers a brief mode (which only shows you the long description of a room the first time you enter it, and a short description thereafter) and a superbrief mode (which always shows you the short description). Superbrief obviously saves time, as screen output on the Apple II is less than instant. Like the number of columns, the level of output verbosity could be considered a speed/entertainment tradeoff. (And similarly, any change will require re-synchronizing RNG.) RNG. Speaking of RNG, the good and bad news is that the RNG in the Apple II version is very sensitive, changing with granularity smaller than a frame. I suspect (but have not confirmed) the Apple II port of Zork uses the built-in KEYIN random number generator, which simply rapidly increments a 16-bit number whenever waiting for keyboard input. This is good in the sense that, in principle, you can probably get any RNG output you need with less than a frame of waiting. It's bad in that it's hostile to console verification, and the Virtu core doesn't provide easy access to sub-frame inputs, as far as I know. In this run, I've done RNG manipulation by manually inserting frame delays. But I also experimented with toggling the Shift key while entering commands, and that alone seems to change the CPU cycles enough to affect RNG. I think it should be possible to eventually do such RNG manipulation automatically, using the script, but it will require more work to understand the game's memory layout, in order to read the results of commands. You can see a listing of the part of the ROM that increments the 16-bit random number in the technical reference manual:
C83B:E6 4E          57 GETKEY  INC  RNDL      ;BUMP RANDOM SEED
C83D:D0 02   C841   58         BNE  GETK2
C83F:E6 4F          59         INC  RNDH
C841:AD 00 C0       60 GETK2   LDA  KBD       ;KEYPRESS?
C844:10 F4   C83B   61         BPL  GETKEY    ;=>NOPE
C846:8D 10 C0       62         STA  KBDSTRB   ;CLEAR STROBE
C849:60             63         RTS
Connecting commands with dots. It's possible to enter multiple commands at once, separated by periods. This capability is given in the manual. (c-square, your N,N,U,GET EGG,D in Post #476712 would have worked if you used periods instead of commas.) It was the main trick used to speed up the "fastest eaten by a grue" TAS, where it enabled ending input early. I didn't use this feature in this run, but I did a quick test, and to my dismay it appears that entering multiple commands on one line is slightly faster. N.N.U.GET EGG.D was 1690 frames in my test, compared to 1709 frames with the commands on separate lines. I say "to my dismay" because this would be a pretty extreme speed/entertainment tradeoff, a small gain in speed for a large decrease in entertainment. It's nicer to watch a run where the results of commands are close to the commands that caused them, not separated from them by pages of scrolling. I'm tempted not to make use of this trick, except perhaps when traversing long distances, such as through the mazes. Even if you were to enter every command of the run on one long line, it wouldn't allow ending input super early, because you would still have to press keys to scroll through all the [MORE] prompts and get to the ending. I tried joining just the last few commands, and buffering a Space press to clear the final [MORE] (as in Post #536873), but it doesn't work in this full-game run: after winning, the game seems to clear the keyboard buffer, or something, such that you have to wait for the final [MORE] to appear before you can dismiss it. Two-part commands. The Gym Slow route occasionally splits one command into two parts, in order to reduce keystrokes. This is when you enter a partial command, then the game asks for clarification, and you enter the word that's missing. For example, the route uses PUT SOLID and CASE to put the ("solid") gold coffin in the trophy case, because that saves having to enter the word IN:
>PUT SOLID
WHAT DO YOU WANT TO PUT THE SOLID IN?
>CASE
DONE.
This is good for an RTA run on a modern computer where the output is instantaneous, but on the Apple II it's probably better to spend some extra frames on input so the game doesn't have to emit so much output: PUT SOLID IN CASE. (Incidentally, these two-part commands don't work when entered as a single line separated by a period: you can't do PUT SOLID.CASE.) A similar case is in the dam maintenance room, where this route does PUSH ALL. There's only one thing in the room you need to push, but this command results in a screenful of output as you try to push every item (including the one you need to push). It may be faster to be more specific in the command so that there's less output. Early thief kill? This route leaves the battle with the thief until near the end. But before that, there are two occasions where you enter the thief's hideout in order to warp to the temple; each of these displays a long message about the thief entering the room and requires RNG manipulation to avoid getting killed or wounded (which would reduce carrying capacity). It may be possible, with RNG manipulation, to kill the thief at the first encounter. This would ease the rest of the run, as there would be no need to worry about random thief encounters or combat in the hideout. I haven't yet checked to see how combat works in the source code. It's conceivable that a route would call for getting robbed by the thief at a time when you're carrying lots of treasure far from the house, as that would effectively warp the treasure to be near the house, and free up your carrying capacity to pick up more items. I hope that's not required, because it would require careful manipulation. The thief's attack isn't a random probability per room; he actually physically moves around the cave at random. The route already uses the "raft of holding" bug (bug #27 in Graeme Cree's list) for the hardest part of inventory weight management. Vampire bat RNG manipulation? Normally, you're supposed to use the garlic to incapacitate the bat, which otherwise picks you up and drops you in a random place in the coal mine. It may be possible to instead allow the bat to grab you, and manipulate it to drop you deep in the mine, saving one of the traversals through the mine. (The current route traverses the mine a total of four times, twice heading in and twice heading out.) It's possible for the bat to drop you outside, in the squeaky room, so it wouldn't be necessary to have the garlic even on the way out. Bypass torch/dumbwaiter puzzle? Bug #41 in Nathan Simpson's list says that you don't need the torch in order to get light into the drafty room past the coal mine. There's a trick where you can store the candles in the sack and still fit through the small opening. I don't think it would save a ton of time, because you'd still need to get to the end of the mine and back to put the coal in the dumbwaiter, but it could free up routing possibilities with regard to when the torch gets deposited in the trophy case. The page says it only works if you extinguish and re-light the candles, so it would require getting the matchbook from the dam lobby, which the route currently doesn't do.
Skilled player (1647)
Joined: 7/1/2013
Posts: 464
Exciting stuff, Sand!
CoolKirby
He/Him
Editor, Experienced player (710)
Joined: 11/8/2010
Posts: 4289
Very nice progress, Sand! I agree that the period trick can probably be used for directional input as it's otherwise just boring movement.
Post subject: RNG manipulation with ASCII codes
Sand
He/Him
Editor, Player (168)
Joined: 6/26/2018
Posts: 238
Sand wrote:
RNG. Speaking of RNG, the good and bad news is that the RNG in the Apple II version is very sensitive, changing with granularity smaller than a frame. I suspect (but have not confirmed) the Apple II port of Zork uses the built-in KEYIN random number generator, which simply rapidly increments a 16-bit number whenever waiting for keyboard input. This is good in the sense that, in principle, you can probably get any RNG output you need with less than a frame of waiting. It's bad in that it's hostile to console verification, and the Virtu core doesn't provide easy access to sub-frame inputs, as far as I know. In this run, I've done RNG manipulation by manually inserting frame delays. But I also experimented with toggling the Shift key while entering commands, and that alone seems to change the CPU cycles enough to affect RNG. I think it should be possible to eventually do such RNG manipulation automatically, using the script, but it will require more work to understand the game's memory layout, in order to read the results of commands.
The source code for many versions of Infocom's Z-Machine interpreters is online, so we can easily see how the RNG works in the Apple II version. As I suspected, the Apple II Z-Machine interpreter (ZIP) uses the KEYIN random number generator built into the Apple II firmware, which rapidly increments a 16-bit counter at addresses 0x4e and 0x4f (which the ZIP calls RNUM1 and RNUM2) while waiting for a key to be pressed. But there's a twist: after a key is typed, the ZIP mixes the ASCII code into the RNG state. This is good news, because it means you can manipulate RNG by introducing variations into the commands you enter. In particular, you can easily affect RNG state by swapping lowercase letters to uppercase, with no loss of time. The ZIP's GETKEY subroutine calls the Apple II RDKEY subroutine, which in turn calls KEYIN. KEYIN returns an ASCII code in the A register and leaves RNUM1 and RNUM2 in some hard-to-predict state. At the end of GETKEY, the ZIP further tweaks the random number state by adding the ASCII code into RNUM1 and xoring it into RNUM2: https://github.com/erkyrath/infocom-zcode-terps/blob/d5ac95a838/apple/zip/machine.g#L100
	ADC RNUM1		;FUTZ WITH RANDOM
	STA RNUM1
	EOR RNUM2
	STA RNUM2
Here's an example of how these observations could be applied. Certain rooms in the overworld are designated "forest rooms". Whenever you take a turn in a forest room, there's a 15% random chance that the game will print the message "You hear in the distance the chirping of a song bird." (Which is a hint for a later puzzle.) We want to avoid that random event, because every line of output takes time. Suppose you enter the command get egg while in the room UP-A-TREE, and, in addition to picking up the egg, you unluckily get the songbird message. Then you can backtrack and try the command get egG. If that doesn't work, try get eGg, then get eGG, and so on. If you exhaust all the letter case possibilities of the most recent command, you can continue with the next most recent, and so on, until you get the RNG you need. I hope to automate this style of RNG manipulation to enable flexible experimentation with routing. As things stand now, if you wanted to try s.e instead of n.e to get behind the house at the beginning of the run, for example, it would desync all later random events. Automated RNG manipulation might also make it possible to play (what is effectively) the same run in either 40-column more or 80-column mode, or in either brief more or verbose mode. That would not normally be possible, because the timing, and hence the RNG, in the various modes would be different. But with a Lua script automatically adjusting the letter case of commands to make sure random rolls always come out the right way, it may be possible for the same route to work in either mode. One movie might have get eGg where the other has get EGg, but they would be effectively the same. Automated RNG manipulation may also make the run robust to timing changes in a future version of the emulator. The main RNG events I can think of that will require manipulation are the songbird, troll and thief combat, candles being blown out in TINY-CAVE, and bat random drops. (I was surprised to learn that the movement of the thief is not random: rather he warps from room to room in order, skipping certain rooms.) The hardest one will probably be the thief combat, where we'll need a series of low-probability attack and defense rolls. But even that I think will be tractable.
Post subject: Loud Room input illusion
Sand
He/Him
Editor, Player (168)
Joined: 6/26/2018
Posts: 238
Sand wrote:
Connecting commands with dots. It's possible to enter multiple commands at once, separated by periods. This capability is given in the manual. (c-square, your N,N,U,GET EGG,D in Post #476712 would have worked if you used periods instead of commas.) It was the main trick used to speed up the "fastest eaten by a grue" TAS, where it enabled ending input early. I didn't use this feature in this run, but I did a quick test, and to my dismay it appears that entering multiple commands on one line is slightly faster. N.N.U.GET EGG.D was 1690 frames in my test, compared to 1709 frames with the commands on separate lines. I say "to my dismay" because this would be a pretty extreme speed/entertainment tradeoff, a small gain in speed for a large decrease in entertainment. It's nicer to watch a run where the results of commands are close to the commands that caused them, not separated from them by pages of scrolling. I'm tempted not to make use of this trick, except perhaps when traversing long distances, such as through the mazes.
There's another place where joining multiple commands with dots doesn't work. The Loud Room, where—amazingly—the game sets up a simulation of the main game loop, complete with fake > prompt, in order to implement the echo puzzle. If there are any commands remaining when you enter the Loud Room (P-CONT is not empty), they get "lost in the noise".
The Troll Room There is a bloody axe here. >e.e.e.echo.get bar East-West Passage Round Room Loud Room On the ground is a large platinum bar. The rest of your commands have been lost in the noise. >
If you include additional commands after the command to solve the puzzle, it looks like they are ignored.
Loud Room On the ground is a large platinum bar. >echo.e The acoustics of the room change subtly. Loud Room On the ground is a large platinum bar. >
Post subject: Automated RNG manipulation
Sand
He/Him
Editor, Player (168)
Joined: 6/26/2018
Posts: 238
Sand wrote:
I hope to automate this style of RNG manipulation to enable flexible experimentation with routing. As things stand now, if you wanted to try s.e instead of n.e to get behind the house at the beginning of the run, for example, it would desync all later random events. Automated RNG manipulation might also make it possible to play (what is effectively) the same run in either 40-column more or 80-column mode, or in either brief more or verbose mode. That would not normally be possible, because the timing, and hence the RNG, in the various modes would be different. But with a Lua script automatically adjusting the letter case of commands to make sure random rolls always come out the right way, it may be possible for the same route to work in either mode. One movie might have get eGg where the other has get EGg, but they would be effectively the same. Automated RNG manipulation may also make the run robust to timing changes in a future version of the emulator.
I've done the automation of RNG manipulation and used it to execute the same route as before under all 4 settings combinations of 40/80 columns and brief/superbrief. Here is how the times work out, from the fastest to the slowest settings:
ColumnsBrevityFramesTime
40superbrief187755:12.92
80superbrief198275:30.45
40brief234336:30.55
80brief251396:58.98
As you can see, 80 columns is about 6% slower than 40 columns, and brief mode is about 25% slower than superbrief mode. Even though it's the slowest combination, I plan to use "80 columns, brief" as the settings for the run, in a speed/entertainment tradeoff. Brief mode shows the room descriptions that are never shown in superbrief mode. 80 columns has distinct uppercase and lowercase letters, which is nicer to read and makes the mechanism of RNG manipulation visible. User file #638973083207267679 is the movie file for "80 columns, brief". Here are encodes for all 4 combinations of settings: 40 columns, superbrief (5:12.92) Link to video 80 columns, superbrief (5:30.45) Link to video 40 columns, brief (6:30.55) Link to video 80 columns, brief (6:58.98) Link to video As before, the commands of the route are input by a Fennel script. The big enhancement in this revision of the script is automatic RNG manipulation by means of varying letter case. As I've discussed, the Apple II Z-machine interpreter's RNG is sensitive to timing at almost the cycle level, so any small change, such as from 40 to 80 columns or from brief to superbrief mode, requires redoing every manipulation. Luckily, the RNG state does not depend only on timing—it also mixes in the player's keyboard input. Whether letters are uppercase or lowercase doesn't matter to the game's text parser, but uppercase and lowercase letters do affect the game's RNG state differently. When the route needs a certain RNG outcome, it tries the command and checks if it worked; if not, then it backtracks, flips the case of an earlier letter, and tries again. (In 40-column mode, there's no visual difference between uppercase and lowercase, but this procedure is still happening.) For example, during the troll battle, we need a few random events to go our way:
  1. The troll must not attack us immediately on entering the room (68% chance)
  2. We must get a one-hit kill on the troll (2 in 9 chance)
  3. We must get the shortest combat result remark (1 in 3 chance)
Here are the case variations that lead to the desired outcome under each of the 4 combinations of settings. Case variation can reach back more than one command if necessary, as far as it needs to.
ColumnsBrevityCommand
40superbriefN;n;hit iT
80superbriefn;n;Hit it
40briefn;n;hit It
80briefn;n;hIT IT
Whenever you see capital letters, some kind of RNG manipulation is happening. If there's no obvious purpose to the manipulation, it's probably to prevent the thief from moving items on his circuit through the dungeon. Even though it's using the same route, this version of "40 columns, superbrief" is slightly faster than the one in Post #537196, because of the use of case variation rather than frame delays for RNG manipulation. How this looks in the code is, ranges of commands can be annotated with "must-not" and "must" events. As the script enters commands, it registers BizHawk events representing undesired or unnecessary outcomes. If, while entering the range of commands, any "must-not" events happen, or if, after entering the range of commands, not all of the "must" events have happened, the script backtracks and tries a different case variation. For example, below, we annotate the n command that enters the Troll Room with a "must-not" event for the VILLAIN-BLOW function being called. On the hit it command to attack the troll, we put a "must" event for the enemy being hit with a certain outcome (KILLED) and random remark (3).
(enter-commands
  [
   ; ...

   ;; Get the painting and go back to the cellar.
   "s"
   "e"
   "get"
   "w"
   "n"

   ;; Defeat the troll. The troll has a <PROB 33> chance of attacking as
   ;; soon as we enter the room, before we get a chance to act.
   [{:must-not [(make-check-exec-zaddr VILLAIN-BLOW-ZADDR)]}
    [
     "n"
     ]]
   ;; Require a one-hit kill with the shortest remark
   ;; "The troll takes a fatal blow and slumps to the floor dead."
   ;; https://github.com/historicalsource/zork1/blob/34cc828c4f/1actions.zil#L3574
   [{:must [(make-hero-blow-res-remark KILLED 3)]}
    [
     "hit it"
     ]]

   ; ...
   ])
The battle with the thief, near the end of the run, is the most RNG-intensive part of the run. Here, I relaxed the manipulation to require only certain combat outcomes, without also requiring the shortest random remark. This was just to speed up the manipulation. By my estimate, an optimal fight, including remarks, has about a 1 in 3000 chance. Ignoring remarks, it's about 1 in 60. It may be possible to do a more thorough optimization when the run is close to being finished. I also haven't checked to see if the slight difference in remark lengths actually makes a difference with respect to time. With automatic RNG manipulation in place, I now have freedom to experiment with changes to the route.
Post subject: Route changes, virtual memory
Sand
He/Him
Editor, Player (168)
Joined: 6/26/2018
Posts: 238
I started experimenting with changes to the route and measuring the effect they have on completion time. After a few easy wins, I encountered an interesting situation: a faster way to do one segment that ends up being slower overall by the end of the run. After investigation, I suspect that the cause is the game's virtual memory system, which swaps content into memory from the floppy disk. Here's a summary of the effects of changes so far:
Change
Time difference
Comment
Baseline (Post #538557)
25139 frames
Change ulysse to odysse
−34 frames
?
Combine two-part commands (e.g. put solid;caseput solid in case)
−1479 frames
Use more conventional words (pealring, heapcoal)
+3 frames
?
Stack directional commands (w;s;e;uw.s.e.u)
−781 frames
temple rather than pray after getting coffin
+152 frames
???
The first change in the table, changing ulysse to odysse, I did for reasons of canonicity. (The cyclops is Greek, so he should know the Greek name, not the Latin name; also the acrostic in the black book spells out ODYSSEUS.) The strings are the same length, so it should be an inconsequential change—but it saves 34 frames. I am not sure, but the reason may have something to do with the virtual memory concerns I'll describe below. Joining two-part commands, as suggested in Post #537196, proves to be a big win on the Apple II implementation. Even thought it's more keystrokes, it saves a round trip through the parser and interpreter. It possibly also avoids some code paths that then don't have to be paged into memory. I thought the words peal and heap in the Gym Slow route were a bit weird and wanted to change them to the more normal ring and coal. This again should be an inconsequential change, but it results in a minuscule loss of frames. Why? I'm not sure. Stacking commands with . also turns out to be a big win. But, now that I've seen how it looks in a movie, I'm not so sure I like it and may decide to restrict myself to one command per input line. The last one is the interesting one. After getting to the gold coffin, you need to use a warp to return it to the trophy case, because it's too large to get out any other way. The Gym Slow route uses the pray command to warp back out to the forest, then re-enters the house from outside. I had the idea of instead using temple to warp to the thief's hideout, then take the secret passage back to the living room. This is how it breaks down:
pray frames
pray command
temple frames
temple command
Difference
4334
open it
4334
open it
0
4511
u.s
4511
u
0
4623
pray
4547
tEmPLE
−76
4721
e.s.e.w.w
4656
d.E.e
−65
4940
put solid in case
4814
put solid in case
−126
5146
open trap
4983
open trap
−163
By the time the two routes reconverge with put solid in case, the temple route is 126 frames ahead. But the lead doesn't remain constant: you can see that in the next command, it grows to 163 frames. The temple route's lead grows and shrinks until eventually it falls behind around frame 15000 and finishes over 150 frames slower. The left side of the graph below compares the long-term timing differences of the two slightly different routes. Two subgraphs. The left is a line graph with the title "Frames to reach each input line". The x-axis is "line" and the y-axis is "frames". There are two series: PRAY and TEMPLE. The two series are identical until about line 250/frame 5000, then TEMPLE starts to have slightly fewer frames per line. At about line 800/frame 15000, PRAY moves ahead and finishes at about line 1600 about 150 frames faster. The subgraph on the right is titled "Swaps". Its y-axis is "frame" and is aligned with the left subgraph. Two columns of a few hundred horizontal lines, colored to match the PRAY and TEMPLE lines in the other graph, show at what frames swaps occurred. They are identical up to about frame 5000, then they diverge. What's going on? I thought about it a lot. My best guess is that is has to do with the virtual memory system of the Z-machine. The game's story file is over 80 KB large, but the Apple II has only 64 KB of memory (and not all of that can be freely used). So what the game does is swap information from the disk into memory on demand. A page table keeps track of what pages in memory correspond to what pages on disk. Once the page table is full, every page that is swapped in from disk necessarily evicts a page that already existed; if that other page is needed again later, it will have to be swapped back in. I suspect that the praytemple route change, even though it uses fewer and faster commands, perturbs the contents of the page table in such a way that eventually requires doing more swaps and spending more time waiting for the disk. The right-hand side of the graph above shows the frames at which swaps occur in the two routes. They are identical up to the route change; then they diverge. There are some similar patterns, but one is not simply the shift of the other. In total, the pray route uses 655 swaps and the temple route 670. This kind of thing is hard to optimize, when a small change can have far-reaching and unpredictable effects. It does suggest a heuristic, which is to try to minimize the ranges of addresses that are accessed. I haven't checked, but that may be what's going on with the ulysseodysse change. In the source code, ODYSSEUS is the main syntax element, while ULYSSES is marked as a synonym. It may be that these two words are stored in different tables in memory, and that odysse requires only one memory access while ulysse requires two. That's just a guess, I haven't checked. I plan to try switching some commands to use the main word rather than synonyms, even when the synonym is shorter. It may explain why pealring had heapcoal an effect. In this case, though, RING and COAL are the main syntax elements. Maybe the lists of synonyms is near a page boundary, or something. I haven't checked. Differences in page tables is also the kind of thing where changes elsewhere in the route may end up making the temple variant faster after all.
Patashu
He/Him
Joined: 10/2/2005
Posts: 4157
Brought this up to a friend who knows more Apple II: "Ah yup, that's a thing - one extra cycle per instruction that reads two bytes, if the first byte is at an address ending in 0xFF. Though looking at my 6502 reference, not for all instructions that can cross page boundaries? Only some have the +1 cycle annotation."
Puzzle gamedev https://patashu.itch.io Famitracker musician https://soundcloud.com/patashu Programmer, DDR grinder, enjoys the occasional puzzle game/shmup.
Sand
He/Him
Editor, Player (168)
Joined: 6/26/2018
Posts: 238
The 6502 taking an extra cycle for certain instructions when they span a 256-byte boundary is a thing, but that's not what I'm referring to. The ZIP virtual memory system is responsible for loading parts of the game from disk into memory, on demand as they become required. The size of pages in the virtual memory system also happens to be 256 bytes, but it's not related to the 6502 page boundary thing. The Z-machine program counter ZPC is a 24-bit integer, even though the Apple II only has a 16-bit address space. There's a routine called NEXTPC that tries to retrieve the next byte of "Z-code" (code for the abstract virtual Z-machine). When NEXTPC reaches the end of a page, it needs to ensure that the next page is loaded into memory somewhere. It first checks the page table PTABL/PTABH to see if the required page of Z-code (on disk, in 24-bit Z-address space) has already been stored in memory (in Apple II 16-bit address space). If not, the virtual memory searches for an unused page in memory, or evicts an existing page to make room for the new page that is now required. (The page eviction is supposed to use some kind of LRU strategy, though I suspect there is some weird behavior because access "timestamps" are only 8-bit and cyclic.) Small changes in how the page table is populated could cause long-term and hard-to-predict changes in the frequency of disk access. There are places in the game code (now I'm talking about the virtual Z-code of the game itself, not the 6502 assembly code of the Z-machine interpreter) that iterate through a list of candidates, for example SEARCH-LIST that consults a table of synonyms. If such a table happened (purely by chance in the compilation process) to cross a page boundary, you might get large timing differences depending on whether the thing you are searching for is early or late in the table (before or after the page boundary). Searching for something near the beginning of the list might require only 1 page to be loaded from disk, while searching to the end might require loading 2 pages. I guess it would, in principle, be possible to audit the Z-code story file to find all such tables that cross page boundaries. But it shows how it could be better, in some cases, to use a longer synonym to refer to something: even if it requires more keystrokes, it might avoid a disk access.
Post subject: A route that uses the thief to carry items
Sand
He/Him
Editor, Player (168)
Joined: 6/26/2018
Posts: 238
Link to video I've prepared a draft of a new, faster route. It's about 18% faster than the baseline route of Post #538557 and 9% faster than the baseline+tweaks route of Post #538861.
Route
Moves
Frames
Time
baseline
258
25139
6:58.98
baseline+tweaks
257
22848
6:20.80
this
236
20685
5:44.75
The baseline runs used the Gym Slow route which is designed for RTA play. That route is built around the "boat of holding" glitch whereby you store items in the deflated boat (where they are weightless) to work around inventory weight limits. (The player character can carry only 95 units of weight.) This new route is built around a different trick: getting the thief to carry treasure for us. The thief is an NPC that wanders the dungeon along a set route and causes mischief such as engaging the player in combat or stealing items. Unlike the player, the thief has unlimited carrying capacity. He has a high chance of picking up treasure he sees, and with RNG manipulation we can make it 100%. He drops treasure off periodically at his lair, the TREASURE-ROOM, which is conveniently located just 3 moves away from the LIVING-ROOM and the trophy case where you need to eventually deposit the treasures. He'll also instantly warp to the TREASURE-ROOM when the player is there. The main constraint on the thief's ability to help with treasure collection is that he does not steal from rooms the player has not yet been to. A main theme of this route is efficiently "visiting" rooms that contain treasure, in order to make the treasure eligible for taking by the thief. (There are, however, several special cases that require special handling, such as a few rooms and items that are marked "sacred" and are off-limits to the thief.) The only treasure that must be held by the thief at some point is the EGG, because only the thief can open it to expose another treasure (the CANARY) that is inside. This is a schematic summary of the route. Here's a map to follow along.
  1. Get the EGG, SWORD, and LAMP, defeat the troll, then go through the maze, getting the BAG-OF-COINS on the way. The cyclops is located at the entrance to the thief's TREASURE-ROOM. Scare away the cyclops, which opens a quick path back to the LIVING-ROOM. Drop the EGG and BAG-OF-COINS for the thief to pick up later; also drop the SWORD to free up 30 units of inventory space. (We won't need the sword again until the final battle with the thief.) Enter the TREASURE-ROOM and use a magic word to warp to NORTH-TEMPLE.
  2. Get the COFFIN (which is a "sacred" item the thief cannot move for us), use a magic word to drop it in the TREASURE-ROOM, then warp right back. Get the TORCH and SCEPTRE, which are not only treasures but also tools we will need later. Take the BELL, BOOK, and CANDLES to the ENTRANCE-TO-HADES (manipulating RNG to keep the CANDLES from blowing out) and do a ritual to open the gates. Visit the crystal SKULL past the gates, leaving it for the thief to get. Then go to the dam area, passing through the EW-PASSAGE on the way to earn 5 points.
  3. In the dam MAINTENANCE-ROOM, get the SCREWDRIVER and the WRENCH and use the WRENCH to drain the RESERVOIR and make it passable. Visit the TRUNK and TRIDENT past the reservoir, solve the LOUD-ROOM puzzle to make the BAR eligible for taking by the thief, then take the INFLATABLE-BOAT and PUMP to WHITE-CLIFFS-SOUTH, adjacent to the river.
  4. Inflate the boat and use it to cross the river. On the river there is a BUOY that contains an EMERALD. The thief cannot enter water rooms, so we must collect the EMERALD, not just visit it. Take the SHOVEL and dig up the SCARAB, leaving it for the thief. Go to ARAGAIN-FALLS and wave the SCEPTRE to make a rainbow bridge. Take the bridge down to the base of the waterfall, visiting the POT-OF-GOLD, and return to the house through the overworld. Drop off the junk and treasure we're carrying, except for the LAMP, SCREWDRIVER, TORCH, and GARLIC, which we'll need in the coal mine.
  5. Take the TREASURE-ROOM warp to NORTH-TEMPLE again, then warp from MIRROR-ROOM-2 to MIRROR-ROOM-1. Holding the GARLIC incapacitates the bat, which lets us get the JADE. (The BAT-ROOM is a sacred room, so we have to pick this one up.) Put the SCREWDRIVER and TORCH in the dumbwaiter. Dispose of the GARLIC in order to take advantage of the bat's attack. When the GARLIC is not present, the bat seizes you and takes you to a random room in the vicinity of the coal mine. By manipulating RNG, we force the bat to drop us at the far end of the mine, saving travel time. Take the COAL, walk out through the mine, place the COAL in the dumbwaiter, and lower the dumbwaiter. Use the bat to warp over the mine again, drop everything to get through the narrow passage, collect the items from the dumbwaiter, and convert the COAL into the DIAMOND using the machine. Raise the dumbwaiter, walk back out through the mine, recollect the TORCH and DIAMOND from the dumbwaiter, and take the slide back to the CELLAR below the LIVING-ROOM.
  6. Take a short detour to the GALLERY to get the PAINTING. The thief could get this one, but we're now too close to the end of the run for him to have time to get there. Go to the LIVING-ROOM to drop off the treasure we're carrying. Go through the cyclops hole, pick up the SWORD, and enter the thief's lair.
  7. Entering the TREASURE-ROOM causes the thief to warp there, along with the treasure he's carrying. We manipulate an ideal sequence of random combat outcomes: 3 MISSEDs from the thief, and 2 SERIOUS-WOUNDs and a KILLED from the player. There is now a pile of treasure here we have to carry to the nearby LIVING-ROOM. We start with the CANARY (plus as much else as we can carry): we must take it outside to the forest for one final piece of treasure. Use a magic word to warp to NORTH-TEMPLE, then another to warp from SOUTH-TEMPLE to FOREST-1. Wind the CANARY to make the songbird drop the BAUBLE. Re-enter the house and drop off the treasure. Make two round trips through the cyclops hole to fetch the rest of the treasure.
As before, the route is scripted with a Fennel program. You can see the commented code with individual commands here. The route is fun—the manipulation of the thief and the bat makes it different from RTA. I think there's potential to bring the time still lower. I've marked some potential changes with TODO comments in the source code. These are some ideas I have:
  • Save the COFFIN for last, rather than doing the awkward back-and-forth warp as soon as we get it. Collect the COFFIN on the final go-round with the CANARY, because we have to pass through that area anyway.
  • Maybe also leave the TORCH for last, or just visit it for the thief to pick up. The CANDLES work equally well as a light source in the dumbwaiter puzzle in the coal mine.
  • Consider collecting treasure as long as there's inventory space. I haven't audited which segments of the route have excess inventory capacity that might be used for carrying treasure. It'll be worth it if we can save one round trip during the final treasure haul.
  • Dropping the LAMP for the final treasure haul will leave room for slightly more treasure per trip. There are 2 dark rooms between the TREASURE-ROOM and the LIVING-ROOM, but entering the first dark room is safe, and the second one can be RNG-manipulated to prevent a grue attack.
  • I want to try a reordering where you do the coal mine before the river area. The slide after the coal mine returns you basically to the house, which is relatively close to the bottom of the waterfall. Waving the SCEPTRE from the bottom works to get up to the SCARAB and EMERALD, then return to the house through the overworld.
Post subject: An experiment with seeded runs to check for unseen random events
Sand
He/Him
Editor, Player (168)
Joined: 6/26/2018
Posts: 238
Sand wrote:
After a few easy wins, I encountered an interesting situation: a faster way to do one segment that ends up being slower overall by the end of the run. After investigation, I suspect that the cause is the game's virtual memory system, which swaps content into memory from the floppy disk.
I had a hypothesis: the reason why apparently inconsequential changes like this one result in differences in frame count has to do with an altered RNG state causing different random events to happen, invisibly offscreen. I did an experiment of executing the exact same run several times, "seeding" each one with a different no-op command to tweak the RNG. Then I recorded a trace of ZPC, the program counter in the Z-machine virtual machine, in order to see if anything different happened across the runs. The outcome was negative: differently seeded runs did not have differences in their ZPC traces (except for some minor explainable differences). However, the runs did show differences in their virtual memory swap traces. So as I suspected in Post #538861, virtual memory makes the difference, though I still don't understand why the same sequence of commands should sometimes cause virtual memory pages to be swapped in a different order. The code and data of the experiment are at https://repo.or.cz/zork1-appleii-tas.git/tree/56937043aa483435a6e7ad6021e3ee97f2a0dd48:/experiments/rng-tweak. Notes on the results are in notes.txt. I started with the 20685-frame run of Post #544754. I ran it again four times, each time starting the run with a different four-digit number command (which is a no-op in the game logic, but affects the RNG and virtual memory page table):
>1248
I don't know the word "1248".
>4567
I don't know the word "4567".
>0909
There was no verb in that sentence!
>5280
I don't know the word "5280".
It was an accident that the 0909 seed caused the interpreter to follow a different code path ("There was no verb in that sentence!"), but it turns out to be a useful point of comparison because it affects the page table differently. The runs finish with different frame counts. The difference between the slowest and the fastest is 197 frames, more than 3 seconds:
Seed
Frames
original
20685
1248
20488
4567
20522
0909
20559
5280
20488
There is an easily explainable difference in ZPC traces across the runs: more or fewer iterations through the REMARK routine (disassembly) that prints combat results. This is from the final thief battle. Though I constrain the result of each round of combat, I left the random remark that is printed for each result unconstrained, so as to require less RNG brute forcing. So in one seed the remark for a SERIOUS-WOUND result might be "A savage blow on the thigh! The thief is stunned but can still fight!" which has 3 elements because it refers to F-DEF (i.e., the word "thief"); while in another it might be "Slash! Your blow lands! That one hit an artery, it could be serious!" which has just 1 element. Other than the REMARK thing, though, and the minor differences in the first "seed" command, there's no difference in ZPC traces across the runs. So my guess that there might be some offscreen random events happening was not confirmed, at least for this set of seeds. Virtual memory has an interesting story, though. First, there are differences in the total number of page swaps:
Seed
Number of swaps
original
563
1248
554
4567
553
0909
584
5280
554
I compared traces of what virtual memory page gets swapped into what page table slot at what time. 3 of the 4 seeded runs (1248, 4567, 5280) are almost the same in this regard. 1248 and 5280 have the exact same sequence of swaps, in fact (though sometimes the swaps occur 1 frame earlier or later). 4567 is similar to those two, but it seems to sometimes swap the same pages into the same set of slots, in a different order. It has 1 fewer swap overall; despite that, it's not the fastest. The original, non-seeded run, and seed 0909 that results in a different response, diverge from these other three. They show behavior like Post #538861: the page table follows a different trajectory, and the run sometimes gains frames, sometimes loses frames. Starting with a different set of loaded pages at the first command has an effect that echoes until the end of the run. Inserting a useless command at the beginning of the run can actually result in reaching the end faster. Conclusion: random variations between two very similar runs are attributable to differences in virtual memory swapping behavior, which is hard to control. But more swaps is not always slower. At least I've ruled out the possibility that the differences are due to unseen random in-game events.
Post subject: Experiments with equivalent ways of expressing commands
Sand
He/Him
Editor, Player (168)
Joined: 6/26/2018
Posts: 238
The Gym Slow route is optimized for reducing keystrokes. The idea is that if you're playing on a modern PC, the reaction to your commands is instant and the bottleneck is how fast you can type them in. On the emulated Apple II with its emulated floppy drive, however, fewer keystrokes is not necessarily faster. There can be many ways to type a command that have an equivalent effect. Expressing a command in a different way can make it run faster, even if sometimes means typing more characters. I saw an instance of this while I was preparing the new route of Post #544754. Near the end, there's a place where we are holding many pieces of treasure, and the lamp. We want to put all the treasure in the trophy case, but keep the lamp. I found that the straightforward command put all but lamp in case was about 51 frames slower than put all treasu in case, which has the same effect. (The Zork I command parser only looks at the first 6 letters of each word. So you can always abbreviate treasure to treasu. That's separate from the main point.) The game's command parser is complicated and I am far from fully understanding it, but you can get some insight into how it works by seeing how objects are defined. Every object has a set of SYNONYM words and a set of ADJECTIVE words. Take the definition of SCARAB for example:
<OBJECT SCARAB
	(IN SANDY-CAVE)
	(SYNONYM SCARAB BUG BEETLE TREASURE)
	(ADJECTIVE BEAUTI CARVED JEWELED)
	(DESC "beautiful jeweled scarab")
	(FLAGS TAKEBIT INVISIBLE)
	(SIZE 8)
	(VALUE 5)
	(TVALUE 5)>
We see the SYNONYMs of the object are scarab, bug, beetle, and treasure. Its ADJECTIVEs are beautiful, carved, and jeweled. This means that any of these commands work to pick up the scarab:
  • get scarab
  • get bug
  • get beetle
  • get jeweled scarab
  • get beautiful jeweled scarab
and even:
  • get treasure (if there's nothing else associated with the word "treasure" in sight)
  • get jeweled (if there's nothing else "jeweled" in sight)
and even:
  • get it (if the scarab is the most recent thing the game has mentioned)
That's the intuition behind the Gym Slow route using get solid instead of get coffin, and deflat it instead of deflat boat. But different options take different code paths through the parser and take different amounts of time to execute. (And modify the virtual memory page table in different ways, most likely.) You can see this in the Visible Zorker. Uncheck the "Collapse non-printing calls" and observe the differences in the call stack when you open the MAILBOX in different ways:
  • open mailbox (by SYNONYM, 52 calls)
  • open box (by SYNONYM, 52 calls)
  • open small (by ADJECTIVE, 51 calls)
  • open small mailbox (by ADJECTIVE and SYNONYM, 57 calls)
  • open it (by it, 89 calls)
  • open all mailbox (50 calls)
And different ways of getting the coffin:
  • get coffin (70 calls)
  • get solid (69 calls)
  • get solid coffin (78 calls)
  • get (63 calls)
  • get all (58 calls)
In general, it seems that it should be avoided, that it's slightly faster (fewer subroutine calls, anyway) to refer to objects by an ADJECTIVE rather than a SYNONYM, that letting the game infer an object can be faster than specifying it (both in typing and in computation time), and that adding all seems to save the parser a bit of work in searching for what object you're referring to. I did an experiment with changing most of the object references in the run of Post #544754 to change most of the object references to use a SYNONYM. The results are promising but mixed: the run was overall faster by about 1%, and the specific commands that were changed seemed to have a beneficial effect on balance, but there is a lot of noise such that it's hard to say for sure it's better. (The noise, I believe, comes from the Z-machine interpreter's virtual memory paging system, which is hard to predict and control; see Post #538861 and Post #544799.) Here's a table of the original and changed commands, as well as frame timestamps and the advantage of the SYNONYM commands (positive is better):
Chg?
Before frames
Before command
After frames
After command
Cumulative advantage
Per-command advantage
521
N.n.u
521
N.n.u
0
0
761
get egg
761
get egg
0
0
920
d.s.e
920
d.s.e
0
0
1016
open
1016
open
0
0
1102
w.w
1102
w.w
0
0
1331
get all
1331
get all
0
0
1454
tug it
1454
tug rug
0
21
1554
open it
1533
open cover
21
20
1643
light
1602
light
41
0
1711
D.n
1670
d.n
41
20
1927
HIT It
1866
HIT troll
61
9
2161
w.s.e.u
2091
w.s.e.u
70
69
2487
get old
2348
get bag
139
14
2645
sw.e.s.se
2492
sw.e.s.se
153
0
2890
odysse
2737
odysse
153
15
2979
drop all but lamp
2811
DRoP aLl but lamp
168
12
3121
u
2941
U
180
2
3366
temple
3184
temple
182
0
3476
get
3294
get
182
7
3600
d
3411
D
189
0
3668
get
3479
get
189
2
3727
open it
3536
open casket
191
38
3854
get sharp
3625
get sceptr
229
0
3937
u
3708
u
229
15
3983
TReasu
3739
TReasu
244
5
4103
DrOp solid
3854
DRop casket
249
7
4216
temple
3960
temple
256
−8
4263
n
4015
n
248
1
4354
get
4105
Get
249
43
4472
s.s
4180
s.s
292
31
4612
get all
4289
get all
323
−86
4701
d.d
4464
d.d
237
2
4857
drop pair
4618
drop pair
239
−67
4941
ring bell
4769
ring bell
172
−13
5035
get
4876
get
159
−36
5115
read
4992
read
123
0
5179
drop pair,book
5056
drop pair,book
123
−11
5270
s.n.u.n.n.n.w.n.ne.e.n.n
5158
s.n.u.n.n.n.w.n.ne.e.n.n
112
−16
6009
push all
5913
push all
96
25
6255
get all tool
6134
get all tool
121
−10
6370
s.s
6259
s.s
111
−68
6523
set nut with wrench
6480
set nut with wrench
43
−21
6639
d
6617
d
22
−1
6716
get
6695
get
21
27
6818
drop wrench
6770
drop wrench
48
23
6904
wait
6833
wait
71
0
6939
g
6868
g
71
−6
6974
g
6909
g
65
1
7006
u.w.n.n
6940
u.w.n.n
66
−5
7314
get
7253
get
61
−12
7384
N.s.s.s.se.d
7335
n.s.s.s.se.d
49
13
7813
echo
7751
echo
62
13
7881
E.e.s
7806
e.e.s
75
5
8096
drop boat
8016
drop boat
80
−16
8184
pump it up
8120
pump up boat
64
37
8354
put sharp in it
8253
put sceptr in it
101
−2
8539
Board
8440
board
99
−4
8610
launch
8515
launch
95
−15
8760
get red
8680
get buoy
80
46
8925
e
8799
e
126
15
9006
disemb
8865
disemb
141
−5
9066
open red
8930
open buoy
136
7
9152
get it
9009
get emeral
143
20
9242
n
9079
n
163
4
9335
get
9168
get
167
1
9408
ne
9240
ne
168
2
9477
dig sand with shovel
9307
dig sand with shovel
170
0
9619
g
9449
g
170
−11
9701
g
9542
g
159
−12
9752
g
9605
g
147
2
9803
sw.s
9654
sw.s
149
18
9914
get sharp
9747
get sceptr
167
−4
10002
s
9839
s
163
14
10070
wave sharp
9893
wave sceptr
177
−24
10151
W.w.sw.u.u.nw.w.w
9998
W.w.sw.u.u.nw.w.w
153
12
10644
open bag
10479
open bag
165
11
10759
Get clove
10583
gEt clove
176
−26
10864
w
10714
w
150
2
10930
open case
10778
open case
152
−30
11002
put all in it
10880
put all in case
122
21
11272
get clove,lamp,screw,torch
11129
get clove,lamp,driver,torch
143
21
11509
opEn trap
11345
OpEn cover
164
−4
11607
w.w.u
11447
W.w.u
160
5
12013
temple
11848
temple
165
−6
12086
s.d.n
11927
s.d.n
159
20
12212
pat enormo
12033
rub enormo
179
−45
12300
n.w.n.w.n
12166
n.w.n.w.n
134
33
12665
get
12498
get
167
−23
12754
e
12610
e
144
3
12848
put torch,tool in cage
12701
put torch,tool in cage
147
46
13079
eat clove
12886
EAT CLOve
193
20
13214
w
13001
W
213
0
13339
d.s
13126
d.s
213
2
13435
get
13220
get
215
15
13507
n.u.u.n.e.s.n
13277
n.u.u.n.e.s.n
230
6
13799
get
13563
get
236
−7
13864
u.s
13635
u.s
229
9
14010
put coal in cage
13772
put coal in cage
238
−1
14140
Lower cage
13903
Lower cage
237
−6
14222
W
13991
W
231
−12
14314
drop all
14095
drop all
219
−3
14405
w
14189
w
216
9
14454
w
14229
w
225
12
14560
get all from cage
14323
get all from cage
237
14
14687
s
14436
s
251
5
14779
open lid
14523
open lid
256
1
14848
put coal in it
14591
put coal in lid
257
41
14990
close it
14692
close lid
298
33
15090
set switch
14759
set switch
331
−9
15190
open lid
14868
open lid
322
9
15268
Get
14937
get
331
1
15336
n
15004
n
332
2
15399
put all in cage
15065
put all in cage
334
1
15515
e
15180
e
335
2
15563
e
15226
e
337
0
15626
get
15289
get
337
1
15678
u.U.n.e.s.n.u.s
15340
u.u.n.e.s.n.u.s
338
11
15982
lift cage
15633
lift cage
349
1
16047
Get huge,torch
15697
get diamon,torch
350
−5
16150
w
15805
W
345
24
16269
s.d.s.e
15900
s.d.s.e
369
−9
16473
get
16113
get
360
27
16555
w.n.u
16168
w.n.u
387
−13
16727
pUt all treasu in case
16353
put all treasu in case
374
−16
16875
w.w
16517
W.W
358
−14
16975
Get
16631
get
344
−16
17050
U
16722
u
328
9
17284
hIt mAn
16947
HIt MAn
337
−33
17493
g
17189
g
304
−10
17569
G
17275
g
294
0
17761
drop sword
17467
drop sword
294
10
17922
get canary,pot,solid
17618
get canary,pot,casket
304
−24
18119
temple
17839
temple
280
8
18179
s
17891
s
288
0
18220
pray
17932
Pray
288
1
18302
E
18013
e
289
0
18333
wind golden
18044
wind canary
289
−56
18410
get
18177
get
233
16
18479
s.e.w.w
18230
s.e.w.w
249
−15
18747
put all treasu in case
18513
put all treasu in case
234
7
18953
w.w.u
18712
w.w.u
241
−19
19174
get all treasu
18952
get all treasu
222
−44
19402
d.e.e
19224
d.e.e
178
13
19583
put all treasu in case
19392
put all treasu in case
191
4
19817
w.w.u
19622
w.w.u
195
1
19954
get all treasu
19758
get all treasu
196
14
20066
d.e.e
19856
d.e.e
210
−2
20258
put all in case
20050
put all in case
208
31
20481
e.e.n.w.sw.w
20242
e.e.n.w.sw.w
239
You notice that even during stretches where the commands do not change, the advantage of the SYNONYM run fluctuates; this is the virtual memory thing I mentioned. If we isolate just the rows where the command changed, we see that, for the most part, the per-command advantage is positive. Not always, though: if there's a pattern, it's that changing sharp to sceptr did not show an advantage. (Keeping in mind the caveat that even identical commands had positive or negative advantage, so any effect may be illusory.) It is pretty consistently observable, however, that changing it to a specific synonym is beneficial.
Chg?
Before frames
Before command
After frames
After command
Cumulative advantage
Per-command advantage
1454
tug it
1454
tug rug
0
21
1554
open it
1533
open cover
21
20
1927
HIT It
1866
HIT troll
61
9
2487
get old
2348
get bag
139
14
3727
open it
3536
open casket
191
38
3854
get sharp
3625
get sceptr
229
0
4103
DrOp solid
3854
DRop casket
249
7
8184
pump it up
8120
pump up boat
64
37
8354
put sharp in it
8253
put sceptr in it
101
−2
8760
get red
8680
get buoy
80
46
9066
open red
8930
open buoy
136
7
9152
get it
9009
get emeral
143
20
9914
get sharp
9747
get sceptr
167
−4
10070
wave sharp
9893
wave sceptr
177
−24
11002
put all in it
10880
put all in case
122
21
11272
get clove,lamp,screw,torch
11129
get clove,lamp,driver,torch
143
21
11509
opEn trap
11345
OpEn cover
164
−4
12212
pat enormo
12033
rub enormo
179
−45
14848
put coal in it
14591
put coal in lid
257
41
14990
close it
14692
close lid
298
33
16047
Get huge,torch
15697
get diamon,torch
350
−5
17922
get canary,pot,solid
17618
get canary,pot,casket
304
−24
18333
wind golden
18044
wind canary
289
−56
I did another, similar experiment with using ADJECTIVEs. The results were similar, only very slightly slower overall than the SYNONYM experiment, but not enough to show a conclusive difference.
Post subject: A greedy strategy for choosing equivalent commands
Sand
He/Him
Editor, Player (168)
Joined: 6/26/2018
Posts: 238
On the topic of different but equivalent commands taking different amounts of time to execute, I did an experiment with a greedy strategy. I thought up a few equivalent alternatives for selected commands, such as get / get bell / get small / get all for picking up the BELL. Then, starting from the beginning, I tried all of the alternatives for the commands that had them, and kept the alternative that reached the next command prompt with the lowest frame count. And so on to the end of the run. Such a strategy is not guaranteed to find an optimal sequence of commands. As we've seen, a command that is faster in isolation may cause later commands to run slower. (I suspect it's because of differences in the virtual memory page table and resulting changes in access patterns to the emulated floppy disk drive.) Nevertheless, the greedy strategy is a decent heuristic, and in this case it cuts 688 frames, or over 11 seconds, from the previous record of 20447 frames. The new record is 19759 frames, or 5:29.32 at 60 FPS. That's not a bad improvement, considering it doesn't involve any macroscopic changes to the route. Two alternative commands saved a lot of time on their own. These were 205 frames from changing the two-part command put all in case;get clove,lamp,driver,torch to the one-part command put all but clove,lamp,screw,torch in case; and 96 frames from changing push all to push yellow in the dam maintenance room. The rest of the time savings came from just a lot of small cumulative improvements. The table of command alternatives and their timing is below. First, some general observations:
  • The choice between SYNONYM and ADJECTIVE, when referring to objects, doesn't matter much. The difference between in time sceptr (a SYNONYM) and sharp (an ADJECTIVE) is always small; sometimes the former is faster and sometimes the latter. The length of the word matters for speed of entry, so choose a short one. It also seems to matter where in the SYNONYM/ADJECTIVE list the word is. Words that are later in the lists seem to take slightly more time to access, on average. You can see this in the difference of book and page (positions 1 and 3, respectively, in the SYNONYM list of BOOK).
  • When a room contains a single gettable object, just plain get is usually the fastest way to pick it up, with get all close behind. get SYNONYM and get ADJECTIVE are usually slower by 10 or more frames. With other verbs, too, leaving the object to be inferred by the parser is usually faster. (See launch versus launch boat for example, where the former is faster even accounting for the different command lengths.)
  • When possible, identifying a group of objects with all SYNONYM or all ADJECTIVE, where SYNONYM or ADJECTIVE is something they have in common, is usually faster than alternative formulations like all but OTHERS. See at the end of the run, where drop all treasu in case is better than drop all but lamp in case.
  • drop X in Y and move X in Y both tend to be slightly faster ways to achieve the V-PUT effect than put X in Y, even though they are 1 character longer to type. There are other, exotic, ways to do a V-PUT, like hurl X in Y, squeeze X on Y, and apply X to Y, but they are not faster.
  • The order in which multiple items are listed (when separated by commas) can have a small effect on timing. See for example get huge,torch versus get torch,huge.
  • The word it is so much slower than other ways of referring to objects it's not worth considering.
I think I can automate greedy optimization like this in the Fennel script that coordinates the TAS. List alternatives for commands, try them all, and keep the fastest one. But it will increase the time needed for emulation and slow down testing and iteration, so I may leave it for a finishing touch after settling large-scale route changes. Here's the table of alternatives I tried. Indented lines following a command show alternatives and their cost in frames relative to the chosen command (higher numbers are slower).
"n.n.u"
"get egg"
     +2 "get encrus"
     +2 "get jewele"
     +4 "get treasu"
"d.s.e"
"open"
    +13 "open kitche"
    +14 "open small"
    +15 "open window"
"w.w"
"get old,lamp"
     +1 "get old,brass"
     +2 "get lamp,old"
     +3 "get brass,old"
     +4 "get all"
     +4 "get old and lamp"
     +4 "get sword,lamp"
     +5 "get sword,brass"
"tug rug"
     +2 "tug carpet"
    +11 "tug large"
    +13 "tug orient"
"open trap"
     =0 "open dusty"
     +1 "open cover"
     +3 "open trapdo"
"light"
"d.n"
"hit nasty"
     =0 "hit nasty with sword"
     +3 "attack nasty"
     +4 "hit troll"
     +5 "fight troll"
     +7 "attack troll"
    +11 "hit nasty with old"
    +19 "hit it"
    +20 "hit him"
"w.s.e.u"
"get bag"
     +2 "get all bag"
     +2 "get all old"
     +2 "get coins"
     +5 "get old"
"sw.e.s.se"
"odysse"
     +1 "ulysse"
"drop all but lamp"
     +1 "drop all old,egg"
    +24 "drop egg,all old"
    +38 "drop egg,bag,sword"
"u"
"treasu"
     =0 "temple"
"get"
    +12 "get all"
    +17 "get bell"
    +18 "get small"
    +22 "get all bell"
"d"
"get"
    +25 "get all"
    +32 "get solid"
    +33 "get casket"
    +34 "get coffin"
"open solid"
     +1 "open casket"
    +26 "open it"
"get sharp"
     +2 "get scepte"
     +2 "get sceptr"
"u"
"treasu"
     =0 "temple"
"drop solid"
"temple"
     =0 "treasu"
"n"
"get"
     +2 "get all"
    +10 "get flamin"
    +11 "get ivory"
    +11 "get torch"
"s.s"
"get all"
    +27 "get book,pair"
    +27 "get page,pair"
    +36 "get pair,book"
"d.d"
"drop pair"
     +2 "drop candles"
    +31 "drop flamin"
"ring bell"
     =0 "ring small"
"get"
    +10 "get pair"
"read"
    +14 "read book"
    +15 "read page"
"drop pair,book"
     =0 "drop book,pair"
     +1 "drop page,pair"
     +1 "drop pair,page"
"s.n.u.n.n.n.w.n.ne.e.n.n"
"push yellow"
    +17 "push yellow switch"
    +43 "push all switch"
    +96 "push all"
"get all tool"
    +22 "get screw,wrench"
    +23 "get wrench,screw"
    +27 "get wrench,screwd"
    +29 "get all"
"s.s"
"set bolt with wrench"
     +1 "set nut with wrench"
     +2 "turn bolt with wrench"
     +2 "turn nut with wrench"
"d"
"get"
     =0 "get all"
    +10 "get boat"
    +11 "get pile"
"drop wrench"
"z"
     +3 "wait"
    +91 "hi" "hi" "hi"
"z"
     +6 "wait"
    +13 "g"
"z"
"u.w.n.n"
"get"
     =0 "get all"
    +11 "get pump"
"n.s.s.s.se.d"
"echo"
"e.e.s"
"drop boat"
     +1 "drop pile"
     +3 "drop plasti"
"pump up boat"
     +4 "inflat boat with pump"
    +39 "pump up boat with pump"
"drop sceptr in boat"
     =0 "drop scepte in boat"
     +1 "move sceptr in boat"
     +3 "apply sharp to boat"
     +3 "drop sharp in boat"
     +3 "move sharp in boat"
     +6 "squeeze sharp on boat"
    +14 "put sharp in boat"
    +15 "apply sceptr to boat"
    +18 "squeeze sceptr on boat"
    +30 "put sharp in it"
    +32 "put scepte in it"
    +32 "put sceptr in it"
    +34 "put sharp in magic"
    +36 "put scepte in boat"
    +36 "put sceptr in boat"
"enter boat"
     +9 "board"
    +22 "get in"
    +23 "board boat"
    +28 "get in boat"
"launch"
    +14 "launch boat"
"get red"
     =0 "get all"
     +2 "get buoy"
"e"
"disemb"
     +8 "get out boat"
    +14 "get out"
    +15 "disemb boat"
    +16 "leave boat"
"open red"
     +2 "open buoy"
"get large"
     +3 "get emeral"
    +14 "get treasu"
    +28 "get it"
"n"
"get all"
     +1 "get"
    +14 "get shovel"
"ne"
"dig sand with shovel"
"g"
    +50 "dig sand with shovel"
"g"
"g"
"sw.s"
"get sceptr"
     =0 "get scepte"
     +2 "get sharp"
"s"
"wave sharp"
     +4 "wave sceptr"
     +5 "wave scepte"
"w.w.sw.u.u.nw.w.w"
"open brown"
     +1 "open bag"
     +4 "open sack"
"get clove"
     +1 "get garlic"
"w"
"open trophy"
     =0 "open case"
"put all but clove,lamp,screw,torch in case"
     +1 "drop all but clove,lamp,driver,torch in case"
     +1 "drop all but torch,driver,lamp,clove in case"
     +2 "drop all but clove,lamp,screwd,torch in case"
     +3 "put all but clove,lamp,driver,torch in case"
     +3 "put all but torch,driver,lamp,clove in case"
     +4 "put all but clove,lamp,screwd,torch in case"
     +6 "drop all but clove,lamp,screw,torch in case"
     +7 "move all but clove,lamp,screw,torch in case"
    +26 "drop sharp,shovel,large,red,pump in case"
    +28 "put sharp,shovel,large,red,pump in case"
   +205 "put all in case" "get clove,lamp,driver,torch"
"open trap"
     =0 "open dusty"
     +4 "open cover"
     +6 "open trapdo"
"w.w.u"
"temple"
     =0 "treasu"
"s.d.n"
"rub enormo"
     =0 "pat enormo"
     =0 "pat reflec"
     +1 "rub mirror"
     +1 "rub reflec"
     +6 "rub all enormo"
"n.w.n.w.n"
"get"
    +19 "get all jade"
    +21 "get figuri"
    +23 "get all"
"e"
"drop torch,screw in cage"
     +2 "put torch,screw in cage"
     +3 "drop tool,torch in cage"
     +3 "drop torch,screwd in cage"
     +3 "drop torch,tool in cage"
     +4 "put tool,torch in cage"
     +5 "put torch,screwd in cage"
     +5 "put torch,tool in cage"
"eat"
    +28 "eat clove"
    +29 "eat garlic"
"w"
"d.s"
"get"
    +20 "get all"
    +26 "get coal"
    +28 "get small"
"n.u.u.n.e.s.n"
"get"
    +25 "get all"
    +34 "get bracel"
    +34 "get jewel"
    +36 "get sapphi"
"u.s"
"drop coal in cage"
     +1 "move coal in cage"
     +2 "hurl coal in cage"
     +2 "put coal in cage"
     +3 "drop coal down cage"
     +3 "put pile in cage"
     +3 "put small in cage"
    +16 "apply coal to cage"
    +17 "put all coal in cage"
    +19 "squeeze coal on cage"
"lower cage"
     +2 "lower basket"
"w"
"drop all"
"w"
"w"
"get all from cage"
"s"
"open lid"
     +1 "open dryer"
     +2 "open machin"
"drop coal in lid"
     +1 "move coal in lid"
     +2 "put coal in lid"
"close lid"
"set switch"
"open lid"
"get"
     +6 "get huge"
    +11 "get diamon"
"n"
"put all in cage"
"e"
"e"
"get"
     +6 "get all"
    +10 "get lamp"
"u.u.n.e.s.n.u.s"
"lift cage"
     +1 "raise cage"
"get huge,ivory"
     +1 "get huge,torch"
     +1 "get ivory,huge"
     +1 "get torch,huge"
     +5 "get diamon,torch"
    +11 "get all from cage"
    +14 "get all treasu from cage"
"w"
"s.d.s.e"
"get"
     +1 "get all"
     +7 "get art"
    +11 "get painti"
"w.n.u"
"put all treasu in case"
    +11 "put all but lamp in case"
"w.w"
"get"
     +3 "get all"
     +7 "get old"
    +10 "get sword"
"u"
"hit man"
     +3 "hit thief"
"g"
    +53 "hit man"
"g"
"drop old"
     +2 "drop sword"
"get solid,pot,canary"
     =0 "get pot,canary,solid"
     =0 "get solid,canary,pot"
     +2 "get canary,pot,solid"
     +5 "get canary,pot,casket"
"temple"
     =0 "treasu"
"s"
"pray"
"e"
"wind canary"
     =0 "wind golden"
"get"
     =0 "get all"
    +10 "get bauble"
    +12 "get brass"
"s.e.w.w"
"drop all treasu in case"
     =0 "move all treasu in case"
     +2 "put all treasu in case"
    +12 "drop all but lamp in case"
    +14 "put all but lamp in case"
"w.w.u"
"get all treasu"
"d.e.e"
"drop all treasu in case"
"w.w.u"
"get all treasu"
"d.e.e"
"drop all treasu in case"
     +2 "put all treasu in case"
    +18 "drop all in case"
    +20 "put all in case"
YoshiRulz
Any
Editor, Emulator Coder
Location: 🇦🇺 Sydney, Australia
Joined: 8/30/2020
Posts: 216
Location: 🇦🇺 Sydney, Australia
I think something like MCTS would be a good fit for that: start with your naive route, then make random perturbations and keep the fastest, moving towards a local minimum.
I contribute to BizHawk as Linux/cross-platform lead, testing and automation lead, and UI designer. This year, I'm experimenting with streaming BizHawk development on Twitch. nope Links to find me elsewhere and to some of my side projects are on my personal site. I will respond on Discord faster than to PMs on this site.
Hey look buddy, I'm an engineer. That means I solve problems. Not problems like "What is software," because that would fall within the purview of your conundrums of philosophy. I solve practical problems. For instance, how am I gonna stop some high-wattage thread-ripping monster of a CPU dead in its tracks? The answer: use code. And if that don't work? Use more code.
Sand
He/Him
Editor, Player (168)
Joined: 6/26/2018
Posts: 238
YoshiRulz wrote:
I think something like MCTS would be a good fit for that: start with your naive route, then make random perturbations and keep the fastest, moving towards a local minimum.
I think Monte Carlo tree search and the process you describe are two different things; but the process you describe is certainly a way to take a given route and wiggle it into a local minimum. One challenge is that testing a minor perturbation to the route is not exactly cheap, computationally. It's automated with the driver script, but it still takes at least tens of minutes to execute a complete run, or longer depending on how much work the RNG manipulation takes (the final battle with the thief is especially variable because a lot of low-probability rolls have to happen in sequence). You can cache the state of the game before the change using something like Post #544738, but everything that comes after has to be re-emulated. So it'll depend on one's appetite for letting it run and when you decide to call it done. The incremental greedy technique has the advantage that you're only timing each command in isolation, choosing the local fastest, and then moving on. So you can more quickly test a lot more variations. The disadvantage, of course, is that you're only looking at the timing of that one command, and not how it may effect other commands much later in the future. Really I'd like to get a better handle on what's going on with the page table (if indeed that is the source of the variation) and see if it can be tamed in some way.
Post subject: Progress, COFFIN last
Sand
He/Him
Editor, Player (168)
Joined: 6/26/2018
Posts: 238
Here is a progress update. User file #639240050613301063 finishes the game in 19263 frames (5:21.05), about 8 seconds faster than the 19759 frames (5:29.32) of Post #544899. The transcript is here. You can see the changes to the orchestration program here. The biggest change is saving the COFFIN for last and hauling it outside in the same trip with the CANARY at the end. There are other, more minor optimizations, some of which I think are fun:
  • In LIVING-ROOM, in order to pick up the LAMP and SWORD, I used to do get all followed by light:
    >get all
    trophy case: The trophy case is securely fastened to the wall.
    sword: Taken.
    brass lantern: Taken.
    carpet: The rug is extremely heavy and cannot be carried.
    
    >light
    (brass lantern)
    The brass lantern is now on.
    
    I changed it to light followed by get.
    >light
    (brass lantern)
    (Taken)
    The brass lantern is now on.
    
    >get
    (sword)
    Taken.
    
    The light command has an implicit take for whatever you're lighting. After that, the sword is the only thing left in the room with TAKEBIT set, so get suffices to take it.
  • There's a tricky new interaction with the bat in BAT-ROOM. We need to enter the BAT-ROOM carrying the GARLIC so the bat won't attack us, pick up the JADE, proceed to the SHAFT-ROOM, drop some things off in the dumbwaiter, then return to BAT-ROOM without the GARLIC so the bat does attack us. It used to work like this:
    Bat Room
    You are in a small room which has doors only to the east and south.
    In the corner of the room on the ceiling is a large vampire bat who is
    obviously deranged and holding his nose.
    There is an exquisite jade figurine here.
    
    >get
    (jade figurine)
    Taken.
    
    >e
    Shaft Room
    This is a large room, in the middle of which is a small shaft descending
    through the floor into darkness below. To the west and the north are exits from
    this room. Constructed over the top of the shaft is a metal framework to which
    a heavy iron chain is attached.
    At the end of the chain is a basket.
    
    >drop torch,screw in cage
    torch: Done.
    screwdriver: Done.
    
    >eat
    (clove of garlic)
    What the heck! You won't make friends this way, but nobody around here is too
    friendly anyhow. Gulp!
    
    >w
    Bat Room
    You are in a small room which has doors only to the east and south.
    A large vampire bat, hanging from the ceiling, swoops down at you!
        Fweep!
        Fweep!
        Fweep!
    
    
    The bat grabs you by the scruff of your neck and lifts you away....
    
    Ladder Top
    
    I changed it to do this instead:
    Bat Room
    You are in a small room which has doors only to the east and south.
    In the corner of the room on the ceiling is a large vampire bat who is
    obviously deranged and holding his nose.
    There is an exquisite jade figurine here.
    
    Shaft Room
    This is a large room, in the middle of which is a small shaft descending
    through the floor into darkness below. To the west and the north are exits from
    this room. Constructed over the top of the shaft is a metal framework to which
    a heavy iron chain is attached.
    At the end of the chain is a basket.
    
    >drop torch,screw in cage
    torch: Done.
    screwdriver: Done.
    
    >w
    Bat Room
    In the corner of the room on the ceiling is a large vampire bat who is
    obviously deranged and holding his nose.
    There is an exquisite jade figurine here.
    
    >eat
    (clove of garlic)
    What the heck! You won't make friends this way, but nobody around here is too
    friendly anyhow. Gulp!
    
    >get jade,bat
    jade figurine: Taken.
    bat:     Fweep!
        Fweep!
        Fweep!
    
    
    The bat grabs you by the scruff of your neck and lifts you away....
    
    Ladder Top
    
    How this works is we simply pass through BAT-ROOM the first time, without picking up the JADE. We stop at the dumbwaiter, then return to BAT-ROOM, still holding the GARLIC. We eat the GARLIC, which reactivates the bat, though it doesn't immediately attack us. We pick up the JADE and attempt to pick up the bat on the same turn, which makes the bat attack and carry us to where we want to be. Check out the text alignment bug on the "Fweep!" message from the game not planning for this interaction.
Next, I'm planning to test a significant change to the route. Currently, it goes: Temple, Dam, Falls, Mine. But I have a feeling Temple, Dam, Mine, Falls will be faster. Dam must come before Mine because you need the SCREWDRIVER in the mine. Dam must come before Falls because you need the boat for the river. Temple must come before Mine because you need a second source of light (the TORCH or the CANDLES). Temple must come before Falls because you need the SCEPTRE for the rainbow bridge.
Post subject: Combat remark timing
Sand
He/Him
Editor, Player (168)
Joined: 6/26/2018
Posts: 238
Another thing I did is time random combat remarks. When you or the enemy takes a turn in combat, there can be one of six results: MISSED, UNCONSCIOUS, KILLED, LIGHT-WOUND, SERIOUS-WOUND, STAGGER. These are looked up in probability tables depending on the strength of the attacker and defender. But for each combat result, there is also a table of flavor remarks. This gives the illusion of more variety in combat than there really is. For example, the player landing a SERIOUS-WOUND result can display any one of these remarks, uniformly at random:
  • "The enemy receives a deep gash in his side."
  • "A savage blow on the thigh! The enemy is stunned but can still fight!"
  • "Slash! Your blow lands! That one hit an artery, it could be serious!"
  • "Slash! Your stroke connects! This could be serious!"
I ran an experiment to time how long it takes to print all the relevant combat remarks, in order to see whether it matters to use RNG manipulation to control them. In short, it matters, but only by 1 or 2 frames per remark. When the player is the attacker and the result is KILLED, all three remarks actually take the same amount of time:
Remark #
Frames (80 columns)
Frames (40 columns)
Remark
1
125
119
"It's curtains for the troll as your sword removes his head."
2
125
119
"The fatal blow strikes the troll square in the heart: He dies."
3
125
119
"The troll takes a fatal blow and slumps to the floor dead."
When the player is the attacker and the result is SERIOUS-WOUND, remark 4 is faster than the others by 1 or 2 frames:
Remark #
Frames (80 columns)
Frames (40 columns)
Remark
1
42
42
"The thief receives a deep gash in his side."
2
42
42
"A savage blow on the thigh! The thief is stunned but can still fight!"
3
42
41
"Slash! Your blow lands! That one hit an artery, it could be serious!"
4
41
40
"Slash! Your stroke connects! This could be serious!"
When the thief is the attacker and the result is MISSED, remark 2 is faster than the others by 1 or 2 frames:
Remark #
Frames (80 columns)
Frames (40 columns)
Remark
1
45
44
"The thief stabs nonchalantly with his stiletto and misses."
2
44
42
"You dodge as the thief comes in low."
3
46
44
"You parry a lightning thrust, and the thief salutes you with a grim nod."
4
45
44
"The thief tries to sneak past your guard, but you twist away."
Despite knowing that some combat remarks are faster than others, I'm not currently constraining the remarks during the final thief fight. That's because the fight already requires manipulating a sequence of low-probability combat results, and manipulating the remarks as well would require even more RNG grinding.

1790165178