http://blog.zarfhome.com/2022/02/these-violent-delights.html Zarf Updates Interactive fiction, narrative in games, and so on * Zarfhome * My IF * IFTF * Older Projects * About Me Tuesday, February 22, 2022 These violent delights I've spent the past few days working on an Inform 6 compiler patch to tighten up generated code. While I was working, I tweeted this screenshot of a buggy game: [AVvXsEjzG6I0VJzqhg7pMIx41UzrZaIVeERyt37LkGKwP7E6bmuHfrMyajtJgBleYJor_twT-v5GACOlOoYQb5yvly] I feel silly posting an image of text, so here's that again: At End Of Road Violence isn'Violence isn't tease oti n. Welcome to Adventure! Violence isn't the answer to this one.Violence isn't the answer to this one.Release 5 / Serial number 961209 / Inform v6.37 Library Violence isn't the answer to this one. S At End Of Road Violence isn't the answer to this one. >east Inside Building Violence isn't the answer to this one. Violence isn't the answer to this one. Violence isn't the answer to this one. Violence isn't the answer to this one. Violence isn't the answer to this one. > I tweeted it because it was funny; my followers volubly agreed. (Thanks folks!) But how did this happen? Hey, I haven't written a code post in a while. Let's dig into it. --------------------------------------------------------------------- My patch was intended to strip out dead code. What does this mean? Well, say you write an Inform 6 routine like this: [ Func; if (false) { print "Goodbye cruel world."; return; } print "Hello."; ]; You may not know I6, but you can probably tell that this function just prints "Hello." The if statement is always false and never happens. But the Inform compiler was compiling the first print and return statements anyway. I wanted to avoid wasting that space. In this simple example, dead-code stripping is easy. In fact the I6 compiler was already detecting the dead if statement; it just needed a nudge to skip generating code for it. However, more complicated code samples were, well, more complicated, and I spent a few days massaging the algorithms to get it all right. The crux of these changes was a flag called execution_never_reaches_here, which means exactly what it says, because Graham Nelson likes self-documenting code. If that flag is set, you can skip generating output. The fun part is making sure the flag gets turned on and off at all the right times as you compile your function. (Okay, execution_never_reaches_here doesn't mean exactly what it says, because I've stuffed some bit flags into it. I won't get into why. Not relevant to this story.) Now, there's another step to this patch. In Z-code (and Glulx, but we're testing Z-code here), strings and code are stored separately in memory. I'd gotten the compiler to skip generating the print and return statements, but the string "Goodbye cruel world." was still being stored in string memory. So I went into the compiler's compile_string() function and added a couple of lines pretty much like this: if (execution_never_reaches_here) { return 0; } The point of this change is that if your print statement isn't compiling into code, who cares about the string that's being printed? compile_string() has to return something, because its type is int. but the value returned will be dropped on the floor. So we might as well return zero. So that's easy, right? Three lines (okay, eight lines with comments and another check which isn't important right now). Compile the compiler, compile Advent.inf, test. Welcome to Adventure! Violence isn't the answer to this one.Violence isn't the answer to this one.Release 5 / Serial number 961209 / Inform v6.37 Library Violence isn't the answer to this one. S What? What? If you're really in tune, you may already have guessed my mistake. If not, I'll draw out the suspense by one more paragraph break... ...I spent all that time making sure that execution_never_reaches_here was handled correctly inside a function. But I forgot to check what happened to it between functions. In fact I had written some cleanup code to set execution_never_reaches_here = -1 at the end of each function! And then forgot I had done that. Of course the value -1 is considered true, or truthy, as we say. So whenever the compiler ran into a string that was outside a function -- say a global constant, or part of an object -- it obligingly skipped over it and returned zero. The fix was easy: just clean up execution_never_reaches_here to be zero (false) between functions, instead of -1. Then everything works right. --------------------------------------------------------------------- But wait. I haven't explained anything! Why does skipping strings cause the game to print "Violence isn't the answer to this one" all over the place? That line is familiar to any Inform user. It's probably the most recognizable message in the Inform library. It's what happens when you ATTACK or BREAK something that the game hasn't defined as breakable. (Or if you HIT it, or FIGHT, KILL, MURDER, SMASH, WRECK... A lot of synonyms.) Library default messages are a subtle art. You need a response which is appropriate to anything the player might try to BREAK -- in any game, including ones you haven't imagined. This is tricky! Say the library default was "That's unbreakable." This would be appropriate for BREAK STATUE, but it reads off-key for FIGHT FROG -- it's not obvious how a frog can be unbreakable. And for SMASH WINEGLASS it's outright confusing. (Yes, the author should write a better response for SMASH WINEGLASS. But it's the library's job to provide something adequate if they don't.) "Nothing obvious happens" isn't bad, but it's not great either. It implies that the player attempted violence, but failed to make a dent. Again, for fragile objects, this is misleading. You can get away with "Nothing happens" for PUSH actions, since pushing is implicitly... kind of boring. (Maybe the object rolls freely; maybe it's fixed in place. You can describe both outcomes as "nothing happens.") But violence really should have a visible effect in some cases. What we want is a response which implies that the player decided not to do that, and moreover should try something else, because no ATTACK action has been implemented. But we can't say "Violence isn't the answer," because something else in the game might be genuinely attackable! (Zork, after all, has just three FIGHTable monsters -- but you can't win if you never fight.) We have to say "Violence isn't the answer to this one," and leave the player's options open. So "Violence isn't the answer to this one" is a carefully-tuned compromise. It's held up very well over the thirty years of Inform's history. (The final rule of library defaults is that they should be neutral and unassuming in tone. "Violence isn't the answer to this one" fails this spectacularly -- it's way too memorable! But we have to allow Graham Nelson a few opinionated moments, and anyway my bug wouldn't have been funny without it. Remember my bug? This is a song about my bug.) --------------------------------------------------------------------- Recall my mistake. I was omitting certain strings from the game -- all strings that occur outside functions -- and instead recording their value as zero. It's clear, looking at the screenshot, that "Welcome to Adventure!" occurs inside a function. So do phrases like "Release" and "Serial number" and "Library". But the library version must be a string constant. The same goes for room and object descriptions. Those get zeroed. And for some reason, a zero value gets printed as "Violence isn't the answer to this one." (Room names like "Inside Building" and "At End Of Road" are actually outside functions too, but the way they're compiled means they come through in the clear. I'm not getting into all the details.) The status line is particularly messy. It turns out the game is trying to print "Score:" and "Moves:" in the upper right. Those overflow the status line and go completely wrong. The mix of right and wrong text can get even sillier. At one point I got it to say: You can also see Violence isn't the answer to this one.set of keysViolence isn't the answer to this one.Violence isn't the answer to this one. tasty foodViolence isn't the answer to this one.Violence isn't the answer to this one.small bottle (in Violence isn't the answer to this one.Violence isn't the answer to this one.Violence isn't the answer to this one. bottled water) here. ...which looks indescribable until you realize that the game is stitching together a list with string constants like "a" and "some". The object names are fine; the short words get replaced. So that all makes sense... except we still haven't explained how zero turns into, you know, violence. You might guess that "Violence isn't the answer to this one" is the first, or rather zeroth, string in the game's string table. And you're right! But how did it get there? It's certainly not alphabetically first. It's not the first string constant defined in the game. It's not the first string defined in the English.h library file. It's not even the first string defined in the LanguageLM() function in that file. This puzzled me severely... until I remembered a quirk of the I6 compiler. (It has many quirks.) In a print statement, short strings are compiled inline in the function code. (Z-code allows this.) Longer strings -- anything over 32 characters -- go into the string table. I'm pretty sure this rule was a hacky way to keep function size down. It's hard to generate code for very long functions, because Z-machine branch opcodes have limited range. Game authors usually don't write very long functions -- but they do sometimes write functions which print a lot of text. If all of that text gets inlined, the function gets too long to manage. So the compiler got tweaked to move larger blocks of text to the string table, which doesn't have the same size limits. So that's the last link in our debugging chain. "Violence isn't the answer to this one" isn't the first string in the game -- but it is the first long string in the library which appears in a function. Short strings get inlined; strings outside of functions get dropped. The library is compiled before the game. So "Violence isn't the answer to this one" is the first string in the game which makes it into the string table. That makes it the zeroth string entry... and everything else follows. Whew! --------------------------------------------------------------------- What's impressive about this bug, really, is that it's good comedy. "Welcome to Adventure" is an instantly recognizable hook (to you and me). Then you spot "At End of Road" and "Inside Building", which confirms what you're looking at -- after you think about it for a second. But it's full of repetitions of the "Violence" line, which is also recognizable, but wrong. The I6 header block is familiar, but it's broken up. You consider the theory that the "violence" line has been stuck in random places. But then the status line (familiar!) breaks the pattern again with partial words, and then that "tease oti n". By this point you're completely at sea. (You gotta break up the "spam spam spam" chant with "spammity-spam, wonderful spam" sometimes. Same idea.) Honestly, it's exactly the right balance of Adventure text and "Violence isn't the answer to this one." If I were hand-tuning it I might stick "shiny brass lamp" somewhere in the last five lines, just to trip people up one more time. But it doesn't need it. And I swear this was all entirely accidental. I didn't do a thing except type EAST. Sometimes the universe just hands it to you. (Thanks to @ddfreyne for the reply which turned into the title of this post.) on February 22, 2022 # Labels: adventure, bugs, code, compilers, debugging, inform 6, violence 2 comments: 1. [blogger_lo] HunterFebruary 22, 2022 at 8:53 PM Baked beans are off. ReplyDelete Replies Reply 2. [blank] Dan EfranFebruary 23, 2022 at 9:56 AM Now you see the violence inherent in the system! ReplyDelete Replies Reply Add comment Load more... Older Post Home Subscribe to: Post Comments (Atom) Your Host [zarfmask-8] Andrew Plotkin # @zarfeblong Search This Blog [ ] [Search] Tags boston cyan design game design glulx hadean lands interactive fiction kickstarter meanwhile pr-if puzzles seltani uru zarf zarfplan Blog Archive * V 2022 (9) + V Feb 2022 (3) o These violent delights o Shade and Heliopause fall off the iOS App Store o Job status update: 2022 + > Jan 2022 (6) * > 2021 (29) + > Dec 2021 (1) + > Nov 2021 (1) + > Oct 2021 (2) + > Sep 2021 (2) + > Aug 2021 (4) + > Jul 2021 (3) + > Jun 2021 (4) + > May 2021 (8) + > Apr 2021 (1) + > Feb 2021 (2) + > Jan 2021 (1) * > 2020 (36) + > Dec 2020 (3) + > Nov 2020 (2) + > Sep 2020 (4) + > Aug 2020 (4) + > Jul 2020 (4) + > May 2020 (3) + > Apr 2020 (2) + > Mar 2020 (1) + > Feb 2020 (4) + > Jan 2020 (9) * > 2019 (27) + > Dec 2019 (1) + > Nov 2019 (3) + > Oct 2019 (1) + > Sep 2019 (2) + > Aug 2019 (4) + > Jul 2019 (2) + > Jun 2019 (5) + > May 2019 (1) + > Apr 2019 (2) + > Mar 2019 (1) + > Jan 2019 (5) * > 2018 (32) + > Dec 2018 (2) + > Nov 2018 (6) + > Sep 2018 (2) + > Aug 2018 (1) + > Jul 2018 (1) + > Jun 2018 (4) + > May 2018 (4) + > Apr 2018 (3) + > Mar 2018 (4) + > Jan 2018 (5) * > 2017 (27) + > Dec 2017 (2) + > Oct 2017 (1) + > Sep 2017 (3) + > Aug 2017 (7) + > Jul 2017 (2) + > Jun 2017 (3) + > May 2017 (4) + > Feb 2017 (3) + > Jan 2017 (2) * > 2016 (30) + > Dec 2016 (1) + > Nov 2016 (2) + > Oct 2016 (2) + > Sep 2016 (2) + > Aug 2016 (2) + > Jul 2016 (4) + > Jun 2016 (5) + > Apr 2016 (3) + > Mar 2016 (2) + > Feb 2016 (4) + > Jan 2016 (3) * > 2015 (29) + > Dec 2015 (1) + > Nov 2015 (2) + > Oct 2015 (3) + > Sep 2015 (3) + > Aug 2015 (1) + > Jun 2015 (2) + > May 2015 (4) + > Apr 2015 (2) + > Mar 2015 (5) + > Feb 2015 (2) + > Jan 2015 (4) * > 2014 (31) + > Dec 2014 (3) + > Nov 2014 (1) + > Oct 2014 (6) + > Sep 2014 (2) + > Aug 2014 (3) + > Jul 2014 (3) + > Jun 2014 (1) + > May 2014 (3) + > Apr 2014 (2) + > Mar 2014 (1) + > Feb 2014 (2) + > Jan 2014 (4) * > 2013 (25) + > Dec 2013 (2) + > Nov 2013 (2) + > Oct 2013 (3) + > Sep 2013 (3) + > Aug 2013 (1) + > Jul 2013 (3) + > Jun 2013 (1) + > May 2013 (1) + > Apr 2013 (2) + > Mar 2013 (2) + > Feb 2013 (2) + > Jan 2013 (3) * > 2012 (34) + > Dec 2012 (2) + > Nov 2012 (1) + > Oct 2012 (5) + > Sep 2012 (6) + > Aug 2012 (2) + > Jul 2012 (2) + > Jun 2012 (4) + > May 2012 (3) + > Apr 2012 (1) + > Mar 2012 (4) + > Feb 2012 (1) + > Jan 2012 (3) * > 2011 (45) + > Dec 2011 (2) + > Nov 2011 (5) + > Oct 2011 (1) + > Sep 2011 (3) + > Aug 2011 (3) + > Jul 2011 (3) + > Jun 2011 (3) + > May 2011 (5) + > Apr 2011 (4) + > Mar 2011 (7) + > Feb 2011 (5) + > Jan 2011 (4) * > 2010 (36) + > Dec 2010 (3) + > Nov 2010 (8) + > Oct 2010 (2) + > Sep 2010 (2) + > Aug 2010 (1) + > Jul 2010 (3) + > Jun 2010 (1) + > May 2010 (2) + > Apr 2010 (2) + > Mar 2010 (3) + > Feb 2010 (4) + > Jan 2010 (5) * > 2009 (37) + > Dec 2009 (3) + > Nov 2009 (4) + > Oct 2009 (5) + > Sep 2009 (2) + > Jul 2009 (5) + > Jun 2009 (2) + > May 2009 (4) + > Apr 2009 (2) + > Mar 2009 (2) + > Feb 2009 (5) + > Jan 2009 (3) * > 2008 (48) + > Dec 2008 (3) + > Nov 2008 (8) + > Oct 2008 (5) + > Sep 2008 (4) + > Aug 2008 (3) + > Jul 2008 (5) + > Jun 2008 (4) + > May 2008 (1) + > Apr 2008 (4) + > Mar 2008 (6) + > Feb 2008 (5) Subscribe To [arrow_drop] [icon_feed1] Posts [subscribe-] [subscribe-] [icon_feed1] Atom [arrow_drop] [icon_feed1] Posts [arrow_drop] [icon_feed1] Comments [subscribe-] [subscribe-] [icon_feed1] Atom [arrow_drop] [icon_feed1] Comments Games Are For Everyone Games Are For Everyone Report Abuse Content is copyright by Andrew Plotkin. Powered by Blogger.