I6 Template Layer

Inform 7 6M62ContentsIntroductionFunction IndexRules Index

ZMachine.i6t

ZMachine contents

Summary.

This segment closely parallels Glulx.i6t, which provides exactly equivalent functionality (indeed, usually the same-named functions and in the same order) for the Glulx VM. This is intended to make the rest of the template code independent of the choice of VM, although that is more of an ideal than a reality, because there are so many fiddly differences in some of the grammar and dictionary tables that it is not really practical for the parser (for instance) to call VM-neutral routines to get the data it wants out of these arrays.

Variables and Arrays.

20Global top_object; ! largest valid number of any tree object 21Global xcommsdir; ! true if command recording is on 22Global transcript_mode; ! true if game scripting is on 23 24Constant INPUT_BUFFER_LEN = 120; ! Length of buffer array 25 26Array buffer -> 123; ! Buffer for parsing main line of input 27Array buffer2 -> 123; ! Buffers for supplementary questions 28Array buffer3 -> 123; ! Buffer retaining input for "again" 29Array parse buffer 63; ! Parse table mirroring it 30Array parse2 buffer 63; ! 31 32Global dict_start; 33Global dict_entry_size; 34Global dict_end;

Starting Up.

VM_Initialise() is almost the first routine called, except that the "starting the virtual machine" activity is allowed to go first.

41[ VM_Initialise i; 42    standard_interpreter = HDR_TERPSTANDARD-->0; 43    transcript_mode = ((HDR_GAMEFLAGS-->0) & 1); 44 45    dict_start = HDR_DICTIONARY-->0; 46    dict_entry_size = dict_start->(dict_start->0 + 1); 47    dict_start = dict_start + dict_start->0 + 4; 48    dict_end = dict_start + ((dict_start - 2)-->0) * dict_entry_size; 49 50    buffer->0 = INPUT_BUFFER_LEN; 51    buffer2->0 = INPUT_BUFFER_LEN; 52    buffer3->0 = INPUT_BUFFER_LEN; 53    parse->0 = 15; 54    parse2->0 = 15; 55 56    top_object = #largest_object-255; 57 58    #ifdef FIX_RNG; 59    @random 10000 -> i; 60    i = -i-2000; 61    print "[Random number generator seed is ", i, "]^"; 62    @random i -> i; 63    #endif; ! FIX_RNG 64];

Enable Acceleration.

This rule enables use of March 2009 extension to Glulx which optimises the speed of Inform-compiled story files, so for the Z-machine it has no effect.

71[ ENABLE_GLULX_ACCEL_R; 72    rfalse; 73];

Release Number.

Like all software, IF story files have release numbers to mark revised versions being circulated: unlike most software, and partly for traditional reasons, the version number is recorded not in some print statement or variable but is branded on, so to speak, in a specific memory location of the story file header.

VM_Describe_Release() describes the release and is used as part of the "banner", IF's equivalent to a title page.

86[ VM_Describe_Release i; 87    print "Release ", (HDR_GAMERELEASE-->0) & $03ff, " / Serial number "; 88    for (i=0 : i<6 : i++) print (char) HDR_GAMESERIAL->i; 89];

Keyboard Input.

The VM must provide three routines for keyboard input:

(a) VM_KeyChar() waits for a key to be pressed and then returns the character chosen as a ZSCII character. (b) VM_KeyDelay(N) waits up to N/10 seconds for a key to be pressed, returning the ZSCII character if so, or 0 if not. (c) VM_ReadKeyboard(b, t) reads a whole newline-terminated command into the buffer b, then parses it into a word stream in the table t.

There are elaborations to due with mouse clicks, but this isn't the place to document all of that.

105[ VM_KeyChar win key; 106    if (win) @set_window win; 107    @read_char 1 -> key; 108    return key; 109]; 110 111[ VM_KeyDelay tenths key; 112    @read_char 1 tenths VM_KeyDelay_Interrupt -> key; 113    return key; 114]; 115[ VM_KeyDelay_Interrupt; rtrue; ]; 116 117[ VM_ReadKeyboard a_buffer a_table i; 118    read a_buffer a_table; 119    #ifdef ECHO_COMMANDS; 120    print "** "; 121    for (i=2: i<=(a_buffer->1)+1: i++) print (char) a_buffer->i; 122    print "^"; 123    #ifnot; 124    i=0; ! suppress compiler warning 125    #endif; 126 127    #Iftrue (#version_number == 6); 128    @output_stream -1; 129    @loadb a_buffer 1 -> sp; 130    @add a_buffer 2 -> sp; 131    @print_table sp sp; 132    new_line; 133    @output_stream 1; 134    #Endif; 135];

Buffer Functions.

A "buffer", in this sense, is an array containing a stream of characters typed from the keyboard; a "parse buffer" is an array which resolves this into individual words, pointing to the relevant entries in the dictionary structure. Because each VM has its own format for each of these arrays (not to mention the dictionary), we have to provide some standard operations needed by the rest of the template as routines for each VM.

The Z-machine buffer and parse buffer formats are documented in the DM4.

VM_CopyBuffer(to, from) copies one buffer into another.

VM_Tokenise(buff, parse_buff) takes the text in the buffer buff and produces the corresponding data in the parse buffer parse_buff – this is called tokenisation since the characters are divided into words: in traditional computing jargon, such clumps of characters treated syntactically as units are called tokens.

LTI_Insert is documented in the DM4 and the LTI prefix stands for "Language To Informese": it's used only by translations into non-English languages of play, and is not called in the template.

160[ VM_CopyBuffer bto bfrom i; 161    for (i=0: i<INPUT_BUFFER_LEN: i++) bto->i = bfrom->i; 162]; 163 164[ VM_PrintToBuffer buf len a b c; 165    @output_stream 3 buf; 166    switch (metaclass(a)) { 167        String: print (string) a; 168        Routine: a(b, c); 169        Object, Class: if (b) PrintOrRun(a, b, true); else print (name) a; 170    } 171    @output_stream -3; 172    if (buf-->0 > len) print "Error: Overflow in VM_PrintToBuffer.^"; 173    return buf-->0; 174]; 175 176[ VM_Tokenise b p; b->(2 + b->1) = 0; @tokenise b p; ]; 177 178[ LTI_Insert i ch b y; 179    ! Protect us from strict mode, as this isn't an array in quite the 180    ! sense it expects 181    b = buffer; 182 183    ! Insert character ch into buffer at point i. 184    ! Being careful not to let the buffer possibly overflow: 185    y = b->1; 186    if (y > b->0) y = b->0; 187 188    ! Move the subsequent text along one character: 189    for (y=y+2 : y>i : y--) b->y = b->(y-1); 190    b->i = ch; 191 192    ! And the text is now one character longer: 193    if (b->1 < b->0) (b->1)++; 194];

Dictionary Functions.

Again, the dictionary structure is differently arranged on the different VMs. This is a data structure containing, in compressed form, the text of all the words to be recognised by tokenisation (above). In I6 for Z, a dictionary word value is represented at run-time by its record number in the dictionary, 0, 1, 2, ..., in alphabetical order.

VM_InvalidDictionaryAddress(A) tests whether A is a valid record address in the dictionary data structure.

VM_DictionaryAddressToNumber(A) and VM_NumberToDictionaryAddress(N) convert between record numbers and dictionary addresses.

210[ VM_InvalidDictionaryAddress addr; 211    if ((UnsignedCompare(addr, dict_start) < 0) || 212        (UnsignedCompare(addr, dict_end) >= 0) || 213        ((addr - dict_start) % dict_entry_size ~= 0)) rtrue; 214    rfalse; 215]; 216 217[ VM_DictionaryAddressToNumber w; return (w-(HDR_DICTIONARY-->0 + 7))/9; ]; 218[ VM_NumberToDictionaryAddress n; return HDR_DICTIONARY-->0 + 7 + 9*n; ];

Command Tables.

The VM is also generated containing a data structure for the grammar produced by I6's Verb and Extend directives: this is essentially a list of command verbs such as DROP or PUSH, together with a list of synonyms, and then the grammar for the subsequent commands to be recognised by the parser.

228[ VM_CommandTableAddress i; 229    return (HDR_STATICMEMORY-->0)-->i; 230]; 231 232[ VM_PrintCommandWords i da j; 233    da = HDR_DICTIONARY-->0; 234    for (j=0 : j<(da+5)-->0 : j++) 235        if (da->(j*9 + 14) == $ff-i) 236            print "", (address) VM_NumberToDictionaryAddress(j), " "; 237];

SHOWVERB support.

Further VM-specific tables cover actions and attributes, and these are used by the SHOWVERB testing command.

244#Ifdef DEBUG; 245[ DebugAction a anames; 246    if (a >= 4096) { print "<fake action ", a-4096, ">"; return; } 247    anames = #identifiers_table; 248    anames = anames + 2*(anames-->0) + 2*48; 249    print (string) anames-->a; 250]; 251 252[ DebugAttribute a anames; 253    if (a < 0 || a >= 48) print "<invalid attribute ", a, ">"; 254    else { 255        anames = #identifiers_table; anames = anames + 2*(anames-->0); 256        print (string) anames-->a; 257    } 258]; 259#Endif;

RNG.

No routine is needed for extracting a random number, since I6's built-in random function does that, but it's useful to abstract the process of seeding the RNG so that it produces a repeatable sequence of "random" numbers from here on: the necessary opcodes are different for the two VMs.

268[ VM_Seed_RNG n; 269    if (n > 0) n = -n; 270    @random n -> n; 271];

Memory Allocation.

This is dynamic memory allocation: something which is never practicable in the Z-machine, because the whole address range is already claimed, but which is viable on recent revisions of Glulx.

279[ VM_AllocateMemory amount; 280    return 0; 281]; 282 283[ VM_FreeMemory address; 284];

Audiovisual Resources.

The Z-machine only barely supports figures and sound effects. I7 only allows us to use them for Version 6 of the Z-machine, even though sound effects have a longer pedigree and Infocom used them on some version 5 and even some version 3 works: really, though, from an I7 point of view we would prefer that anyone needing figures and sounds use Glulx instead.

294[ VM_Picture resource_ID; 295    #IFTRUE #version_number == 6; ! Z-machine version 6 296    @draw_picture resource_ID; 297    #ENDIF; 298]; 299 300[ VM_SoundEffect resource_ID; 301    #IFTRUE #version_number == 6; ! Z-machine version 6 302    @sound_effect resource_ID; 303    #ENDIF; 304];

Typography.

Relatively few typographic effects are available on the Z-machine, so that many of the semantic markups for text which would be distinguishable on Glulx are indistinguishable here.

312[ VM_Style sty; 313    switch (sty) { 314        NORMAL_VMSTY, NOTE_VMSTY: style roman; 315        HEADER_VMSTY, SUBHEADER_VMSTY, ALERT_VMSTY: style bold; 316    } 317];

Character Casing.

The following are the equivalent of tolower and toupper, the traditional C library functions for forcing letters into lower and upper case form, for the ZSCII character set.

325[ VM_UpperToLowerCase c; 326   switch (c) { 327        'A' to 'Z': c = c + 32; 328        202, 204, 212, 214, 221: c--; 329        217, 218: c = c - 2; 330        158 to 160, 167 to 168, 208 to 210: c = c - 3; 331        186 to 190, 196 to 200: c = c - 5 ; 332        175 to 180: c = c - 6; 333   } 334   return c; 335]; 336 337[ VM_LowerToUpperCase c; 338   switch (c) { 339        'a' to 'z': c = c - 32; 340        201, 203, 211, 213, 220: c++; 341        215, 216: c = c + 2; 342        155 to 157, 164 to 165, 205 to 207: c = c + 3; 343        181 to 185, 191 to 195: c = c + 5 ; 344        169 to 174: c = c + 6; 345   } 346   return c; 347];

The Screen.

Our generic screen model is that the screen is made up of windows: we tend to refer only to two of these, the main window and the status line, but others may also exist from time to time. Windows have unique ID numbers: the special window ID -1 means "all windows" or "the entire screen", which usually amounts to the same thing.

Screen height and width are measured in characters, with respect to the fixed-pitch font used for the status line. The main window normally contains variable-pitch text which may even have been kerned, and character dimensions make little sense there.

Clearing all windows (WIN_ALL here) has the side-effect of collapsing the status line, so we need to ensure that statuswin_cursize is reduced to 0, in order to keep it accurate.

366[ VM_ClearScreen window; 367    switch (window) { 368        WIN_ALL: @erase_window -1; statuswin_cursize = 0; 369        WIN_STATUS: @erase_window 1; 370        WIN_MAIN: @erase_window 0; 371    } 372]; 373 374#Iftrue (#version_number == 6); 375[ VM_ScreenWidth width charw; 376    @get_wind_prop 1 3 -> width; 377    @get_wind_prop 1 13 -> charw; 378    charw = charw & $FF; 379    return (width+charw-1) / charw; 380]; 381#Ifnot; 382[ VM_ScreenWidth; return (HDR_SCREENWCHARS->0); ]; 383#Endif; 384 385[ VM_ScreenHeight; return (HDR_SCREENHLINES->0); ];

Window Colours.

Each window can have its own foreground and background colours.

The colour of individual letters or words of type is not controllable in Glulx, to the frustration of many, and so the template layer of I7 has no framework for handling this (even though it is controllable on the Z-machine, which is greatly superior in this respect).

396[ VM_SetWindowColours f b window; 397    if (clr_on && f && b) { 398        if (window == 0) { ! if setting both together, set reverse 399            clr_fgstatus = b; 400            clr_bgstatus = f; 401            } 402        if (window == 1) { 403            clr_fgstatus = f; 404            clr_bgstatus = b; 405        } 406        if (window == 0 or 2) { 407            clr_fg = f; 408            clr_bg = b; 409        } 410        if (statuswin_current) 411            @set_colour clr_fgstatus clr_bgstatus; 412        else 413            @set_colour clr_fg clr_bg; 414    } 415]; 416 417[ VM_RestoreWindowColours; ! compare I6 library patch L61007 418    if (clr_on) { ! check colour has been used 419        VM_SetWindowColours(clr_fg, clr_bg, 2); ! make sure both sets of variables are restored 420        VM_SetWindowColours(clr_fgstatus, clr_bgstatus, 1, true); 421        VM_ClearScreen(); 422    } 423    #Iftrue (#version_number == 6); ! request screen update 424    (0-->8) = (0-->8) | $$00000100; 425    #Endif; 426];

Main Window.

The part of the screen on which commands and responses are printed, which ordinarily occupies almost all of the screen area.

VM_MainWindow() switches printing back from another window, usually the status line, to the main window. Note that the Z-machine implementation emulates the Glulx model of window rather than text colours.

437[ VM_MainWindow; 438    if (statuswin_current) { 439        if (clr_on && clr_bgstatus > 1) @set_colour clr_fg clr_bg; 440        else style roman; 441        @set_window 0; 442    } 443    statuswin_current = false; 444];

Status Line.

Despite the name, the status line need not be a single line at the top of the screen: that's only the conventional default arrangement. It can expand to become the equivalent of an old-fashioned VT220 terminal, with menus and grids and mazes displayed lovingly in character graphics, or it can close up to invisibility.

VM_StatusLineHeight(n) sets the status line to have a height of n lines of type. (The width of the status line is always the width of the whole screen, and the position is always at the top, so the height is the only controllable aspect.) The n=0 case makes the status line disappear.

VM_MoveCursorInStatusLine(x, y) switches printing to the status line, positioning the "cursor" – the position at which printing will begin – at the given character grid position (x, y). Line 1 represents the top line; line 2 is underneath, and so on; columns are similarly numbered from 1 at the left.

465[ VM_MoveCursorInStatusLine line column; ! 1-based position on text grid 466    if (~~statuswin_current) { 467         @set_window 1; 468         if (clr_on && clr_bgstatus > 1) @set_colour clr_fgstatus clr_bgstatus; 469         else style reverse; 470    } 471    if (line == 0) { 472        line = 1; 473        column = 1; 474    } 475    #Iftrue (#version_number == 6); 476    Z6_MoveCursor(line, column); 477    #Ifnot; 478    @set_cursor line column; 479    #Endif; 480    statuswin_current = true; 481]; 482 483#Iftrue (#version_number == 6); 484[ Z6_MoveCursor line column charw charh; ! 1-based position on text grid 485    @get_wind_prop 1 13 -> charw; ! font size 486    @log_shift charw $FFF8 -> charh; 487    charw = charw / $100; 488    line = 1 + charh*(line-1); 489    column = 1 + charw*(column-1); 490    @set_cursor line column; 491]; 492#Endif; 493 494#Iftrue (#version_number == 6); 495[ VM_StatusLineHeight height wx wy x y charh; 496    ! Split the window. Standard 1.0 interpreters should keep the window 0 497    ! cursor in the same absolute position, but older interpreters, 498    ! including Infocom's don't - they keep the window 0 cursor in the 499    ! same position relative to its origin. We therefore compensate 500    ! manually. 501    @get_wind_prop 0 0 -> wy; @get_wind_prop 0 1 -> wx; 502    @get_wind_prop 0 13 -> charh; @log_shift charh $FFF8 -> charh; 503    @get_wind_prop 0 4 -> y; @get_wind_prop 0 5 -> x; 504    height = height * charh; 505    @split_window height; 506    y = y - height + wy - 1; 507    if (y < 1) y = 1; 508    x = x + wx - 1; 509    @set_cursor y x 0; 510    statuswin_cursize = height; 511]; 512#Ifnot; 513[ VM_StatusLineHeight height; 514    if (statuswin_cursize ~= height) 515        @split_window height; 516    statuswin_cursize = height; 517]; 518#Endif; 519 520#Iftrue (#version_number == 6); 521[ Z6_DrawStatusLine width x charw scw; 522    (0-->8) = (0-->8) &~ $$00000100; 523    @push say__p; @push say__pc; 524    BeginActivity(CONSTRUCTING_STATUS_LINE_ACT); 525    VM_StatusLineHeight(statuswin_size); 526    ! Now clear the window. This isn't totally trivial. Our approach is to select the 527    ! fixed space font, measure its width, and print an appropriate 528    ! number of spaces. We round up if the screen isn't a whole number 529    ! of characters wide, and rely on window 1 being set to clip by default. 530    VM_MoveCursorInStatusLine(1, 1); 531    @set_font 4 -> x; 532    width = VM_ScreenWidth(); 533    spaces width; 534    ClearParagraphing(8); 535    if (ForActivity(CONSTRUCTING_STATUS_LINE_ACT) == false) { 536        ! Back to standard font for the display. We use output_stream 3 to 537        ! measure the space required, the aim being to get 50 characters 538        ! worth of space for the location name. 539        VM_MoveCursorInStatusLine(1, 2); 540        @set_font 1 -> x; 541        TEXT_TY_Say(left_hand_status_line); 542        @get_wind_prop 1 3 -> width; 543        @get_wind_prop 1 13 -> charw; 544        charw = charw & $FF; 545        @output_stream 3 StorageForShortName; 546        TEXT_TY_Say(right_hand_status_line); 547        @output_stream -3; scw = HDR_PIXELSTO3-->0 + charw; 548        x = 1+width-scw; 549        @set_cursor 1 x; TEXT_TY_Say(right_hand_status_line); 550    } 551    ! Reselect roman, as Infocom's interpreters go funny if reverse is selected twice. 552    VM_MainWindow(); 553    ClearParagraphing(8); 554    EndActivity(CONSTRUCTING_STATUS_LINE_ACT); 555    @pull say__pc; @pull say__p; 556]; 557#Endif;

Quotation Boxes.

No routine is needed to produce quotation boxes: the I6 box statement generates the necessary Z-machine opcodes all by itself.

Undo.

These simply wrap the relevant opcodes.

567[ VM_Undo result_code; 568    @restore_undo result_code; 569    return result_code; 570]; 571 572[ VM_Save_Undo result_code; 573    @save_undo result_code; 574    return result_code; 575];

Quit The Game Rule.

580[ QUIT_THE_GAME_R; 581    if (actor ~= player) rfalse; 582    QUIT_THE_GAME_RM('A'); 583    if (YesOrNo()~=0) quit; 584];

Restart The Game Rule.

589[ RESTART_THE_GAME_R; 590    if (actor ~= player) rfalse; 591    RESTART_THE_GAME_RM('A'); 592    if (YesOrNo()~=0) { 593        @restart; 594        RESTART_THE_GAME_RM('B'); new_line; 595    } 596];

Restore The Game Rule.

601[ RESTORE_THE_GAME_R; 602    if (actor ~= player) rfalse; 603    restore Rmaybe; 604    RESTORE_THE_GAME_RM('A'); new_line; 605    rtrue; 606    .RMaybe; RESTORE_THE_GAME_RM('B'); new_line; 607];

Save The Game Rule.

612[ SAVE_THE_GAME_R flag; 613    if (actor ~= player) rfalse; 614    #IFV5; 615    @save -> flag; 616    switch (flag) { 617        0: SAVE_THE_GAME_RM('A'); new_line; 618        1: SAVE_THE_GAME_RM('B'); new_line; 619        2: RESTORE_THE_GAME_RM('B'); new_line; 620    } 621    #IFNOT; 622    save Smaybe; 623    SAVE_THE_GAME_RM('A'); new_line; rtrue; 624    .SMaybe; SAVE_THE_GAME_RM('B'); new_line; 625    #ENDIF; 626];

Verify The Story File Rule.

This is a fossil now, really, but in the days of Infocom, the 110K story file occupying an entire disc was a huge data set: floppy discs were by no means a reliable medium, and cheap hardware often used hit-and-miss components, as on the notorious Commodore 64 disc controller. If somebody experienced an apparent bug in play, it could easily be that he had a corrupt disc or was unable to read data of that density. So the VERIFY command, which took up to ten minutes on some early computers, would chug through the entire story file and compute a checksum, compare it against a known result in the header, and determine that the story file could or could not properly be read. The Z-machine provided this service as an opcode, and so Glulx followed suit.

642[ VERIFY_THE_STORY_FILE_R; 643    if (actor ~= player) rfalse; 644    @verify ?Vmaybe; 645    jump Vwrong; 646    .Vmaybe; VERIFY_THE_STORY_FILE_RM('A'); new_line; rtrue; 647    .Vwrong; 648    VERIFY_THE_STORY_FILE_RM('B'); new_line; 649];

Switch Transcript On Rule.

654[ SWITCH_TRANSCRIPT_ON_R; 655    if (actor ~= player) rfalse; 656    transcript_mode = ((0-->8) & 1); 657    if (transcript_mode) { SWITCH_TRANSCRIPT_ON_RM('A'); new_line; rtrue; } 658    @output_stream 2; 659    if (((0-->8) & 1) == 0) { SWITCH_TRANSCRIPT_ON_RM('C'); new_line; rtrue; } 660    SWITCH_TRANSCRIPT_ON_RM('B'); new_line; VersionSub(); 661    transcript_mode = true; 662];

Switch Transcript Off Rule.

667[ SWITCH_TRANSCRIPT_OFF_R; 668    if (actor ~= player) rfalse; 669    transcript_mode = ((0-->8) & 1); 670    if (transcript_mode == false) { SWITCH_TRANSCRIPT_OFF_RM('A'); new_line; rtrue; } 671    SWITCH_TRANSCRIPT_OFF_RM('B'); new_line; 672    @output_stream -2; 673    if ((0-->8) & 1) { SWITCH_TRANSCRIPT_ON_RM('C'); new_line; rtrue; } 674    transcript_mode = false; 675];

Announce Story File Version Rule.

680[ ANNOUNCE_STORY_FILE_VERSION_R ix; 681    if (actor ~= player) rfalse; 682    Banner(); 683    print "Identification number: "; 684    for (ix=6: ix <= UUID_ARRAY->0: ix++) print (char) UUID_ARRAY->ix; 685    print "^"; 686    ix = 0; ! shut up compiler warning 687    if (standard_interpreter > 0) { 688        print "Standard interpreter ", 689            standard_interpreter/256, ".", standard_interpreter%256, 690            " (", HDR_TERPNUMBER->0; 691        #Iftrue (#version_number == 6); 692        print (char) '.', HDR_TERPVERSION->0; 693        #Ifnot; 694        print (char) HDR_TERPVERSION->0; 695        #Endif; 696        print ") / "; 697    } else { 698        print "Interpreter ", HDR_TERPNUMBER->0, " Version "; 699        #Iftrue (#version_number == 6); 700        print HDR_TERPVERSION->0; 701        #Ifnot; 702        print (char) HDR_TERPVERSION->0; 703        #Endif; 704        print " / "; 705    } 706    print "Library serial number ", (string) LibSerial, "^"; 707    #Ifdef LanguageVersion; 708    print (string) LanguageVersion, "^"; 709    #Endif; ! LanguageVersion 710    #ifdef ShowExtensionVersions; 711    ShowExtensionVersions(); 712    #endif; 713    say__p = 1; 714];

Descend To Specific Action Rule.

There are 100 or so actions, typically, and this rule is for efficiency's sake: rather than perform 100 or so comparisons to see which routine to call, we indirect through a jump table. The routines called are the -Sub routines: thus, for instance, if action is ##Wait then WaitSub is called. It is essential that this routine not be called for fake actions: in I7 use this is guaranteed, since fake actions are not allowed into the action machinery at all.

Strangely, Glulx's action routines table is numbered in an off-by-one way compared to the Z-machine's: hence the +1.

729[ DESCEND_TO_SPECIFIC_ACTION_R; 730    indirect(#actions_table-->action); 731    rtrue; 732];

Veneer.

737[ OC__Cl obj cla j a n objflag; 738 739    @jl obj 1 ?NotObj; 740    @jg obj max_z_object ?NotObj; 741    @inc objflag; 742    @je cla K1_room ?~NotRoom; 743    @test_attr obj mark_as_room ?rtrue; 744    @rfalse; 745    .NotRoom; 746    @je cla K2_thing ?~NotObj; 747    @test_attr obj mark_as_thing ?rtrue; 748    @rfalse; 749    .NotObj; 750 751    @je cla Object Class ?ObjOrClass; 752    @je cla Routine String ?RoutOrStr; 753 754    @jin cla 1 ?~Mistake; 755 756    @jz objflag ?rfalse; 757    @get_prop_addr obj 2 -> a; 758    @jz a ?rfalse; 759    @get_prop_len a -> n; 760 761    @div n 2 -> n; 762    .Loop; 763    @loadw a j -> sp; 764    @je sp cla ?rtrue; 765    @inc j; 766    @jl j n ?Loop; 767    @rfalse; 768 769    .ObjOrClass; 770    @jz objflag ?rfalse; 771    @je cla Object ?JustObj; 772 773    ! So now cla is Class 774    @jg obj String ?~rtrue; 775    @jin obj Class ?rtrue; 776    @rfalse; 777 778    .JustObj; 779    ! So now cla is Object 780    @jg obj String ?~rfalse; 781    @jin obj Class ?rfalse; 782    @rtrue; 783 784    .RoutOrStr; 785    @jz objflag ?~rfalse; 786    @call_2s Z__Region obj -> sp; 787    @inc sp; 788    @je sp cla ?rtrue; 789    @rfalse; 790 791    .Mistake; 792    RT__Err("apply ofclass for", cla, -1); 793    rfalse; 794]; 795 796[ Unsigned__Compare x y u v; 797    @je x y ?rfalse; ! i.e., return 0 798    @jl x 0 ?XNegative; 799    ! So here x >= 0 and x ~= y 800    @jl y 0 ?XPosYNeg; 801 802    ! Here x >=0, y >= 0, x ~= y 803 804    @jg x y ?rtrue; ! i.e., return 1 805    @ret -1; 806 807    .XPosYNeg; 808    ! Here x >= 0, y < 0, x ~= y 809    @ret -1; 810 811    .XNegative; 812    @jl y 0 ?~rtrue; ! if x < 0, y >= 0, return 1 813     814    ! Here x < 0, y < 0, x ~= y 815    @jg x y ?rtrue; 816    @ret -1; 817]; 818 819[ RT__ChLDW base offset; 820    @loadw base offset -> sp; 821    @ret sp; 822];