Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Script State and Progression

Primary-state structure

The 200-byte live block at DS:727A is the scene interpreter’s shared state. New-game initialization writes 100 zero words with rep stosw, and the save system copies exactly those 100 words to and from the checkpoint block at DS:7BF2. Values are signed 16-bit integers where arithmetic or comparison requires a sign.

BIN operands do not store variable ordinals. They store even byte offsets from DS:727A; the interpreter shifts an offset right once before indexing the word array. Thus operand 0x002A identifies variable 21:

address = DS:727A + encoded_offset
index   = encoded_offset / 2

Across all 64 recovered BIN code regions, the core variable instructions reference 39 of the 100 slots. Every one of their encoded operands is even and below 200. Many high-numbered slots are scene-local temporaries rather than persistent player attributes.

Identified variables

IndexByte offsetDS addressCurrent meaningEvidence
00x00727ADifficulty: 0 Easy, 1 Normal, 2 DifficultDifficulty input writes these values; map loading indexes END; faith damage branches on the same values.
110x167290Current map XUsed in every 3*x cell calculation and exploration-bit update.
120x187292Current map YUsed in every 48*y cell calculation and as the exploration-row index.
160x20729ACurrent map level letterload_map_resource compares and caches its level argument here.
170x22729CCurrent cell parameter Aprocess_current_map_cell copies cell byte +1 here.
180x24729ECurrent cell parameter Bprocess_current_map_cell copies cell byte +2 here.
210x2A72A4Faith in hundredths of a percentInitialized and clamped to 10,000; the F3 display divides by 100.

The first gameplay entry in FIRST.BIN demonstrates the interface directly: it stores X=0 in offset 0x16, Y=6 in offset 0x18, and faith=10,000 in offset 0x2A before processing the current map cell.

Map processing also produces several context-local words for the hall scene:

IndexDS addressMap-processing role
137294Connected-cell low kind, or zero-mask room class 04.
147296Room entrance code 02, or a neighboring-cell kind during hall processing.
2372A8Parameter B of a Trap room immediately right of the hall.
2472AAParameter B of a Trap room immediately left of the hall.
2572ACParameter B of a Trap room immediately above the hall.

These are not durable player attributes. process_current_map_cell rebuilds them from the live map, and other scenes can reuse the same general-purpose slots. The world-map chapter describes the room quotient/remainder and the adjacent Trap interaction that establish these meanings.

Variable bytecode

The core instruction family is now recovered. In this table, var operands are encoded byte offsets, value is a signed immediate where relevant, and target is an absolute BIN file offset.

OpcodeOperandsEffect
0x1Esource, destinationCopy a variable.
0x1Fvalue, destinationStore an immediate value.
0x20 / 0x21var, targetJump if zero / nonzero.
0x22 / 0x24left, right, targetJump if two variables are equal / unequal.
0x23 / 0x25var, value, targetJump if a variable equals / does not equal an immediate.
0x26 / 0x28left, right, targetSigned jump if left is greater than / less than right.
0x27 / 0x29var, value, targetSigned jump if a variable is greater than / less than an immediate.
0x2A / 0x2Bsource-or-value, destinationAdd a variable / immediate to the destination.
0x2C / 0x2Dsource-or-value, destinationSubtract a variable / immediate from the destination.
0x2E / 0x2Fsource-or-value, destinationSigned multiply the destination by a variable / immediate.
0x30 / 0x31source-or-value, destinationSigned divide the destination by a variable / immediate.
0x32 / 0x33varIncrement / decrement.
0x8F / 0x90source-or-value, destinationBitwise-AND the destination with a variable / immediate.

The disassembler annotates known operands with both forms, for example var[21:faith]@0x002a. This also prevents immediate values and jump targets from being mistaken for variable numbers.

Boolean state flags

Variables 3 through 10, at DS:7280..728F, are also treated as a 128-bit flag bank. Identifier n selects word n >> 4 and mask 1 << (n & 15). Dedicated helpers test, set, and clear one identifier using mask and inverted-mask tables in the executable.

OpcodeOperandsEffect
0x73flag, targetJump if the flag is clear.
0x74flag, targetJump if the flag is set.
0x75flagClear the flag.
0x76flagSet the flag.

The scene corpus uses 78 distinct identifiers through 0x55. They mix temporary navigation/action state with durable progression. When the current map cell is processed, the executable clears the first three flag words (0x00..0x2F) and rebuilds movement and action availability from the cell and its neighbors. Flags at 0x30 and above survive that operation.

Five durable identifiers map exactly to the F4 through F8 status icons:

FlagCapability
0x30Sword
0x31Shield
0x32No Trap
0x33Candle
0x34Flight

Flag 0x36 is the status-artwork gate. The renderer at 0x641B releases the Computer Bible, Map, Faith, and power slots and returns when it is clear; several shipped conversation scenes clear it and restore it around their authored sequences. The main loop separately gates F1 through F8 on action selection word 004C. Its pointer-status path requires flag 0x36 as well, so keyboard shortcuts can remain active when the row is hidden but clicks cannot. The branch at 0x8845 also maps ASCII b to the same Computer Bible routine as F1 whenever word 004C is nonzero. The renderer clamps Faith for frame selection and retains the empty frame at exactly zero.

The same renderer owns the dormant transient started by unused opcode 8B. While flag 0x36 and timer word 005C are nonzero, its pre- and post-VM refreshes each consume the runtime random source and use the low two bits as flip flags for zero-based STUFF.ART frame 27. The exact timer and call-order rules are recorded under Runtime random source.

Two adjacent flags control automatic combat:

FlagMeaning
0x37Automatic Combat option is enabled.
0x38An ordinary combat scene is active; lock the option against changes.

The Game Options routine displays the on/off state from 0x37. When 0x38 is set it assigns the Automatic Combat row a disabled target instead of the normal toggle target. COMBAT1 through COMBAT5 and COMBAT7 set and clear 0x38 around their shared encounter lifetime. The exceptional guard program COMBAT6 does neither.

Game-options dispatcher

The main input loop handles Escape at 0x88FE after the action-selection branches have rejoined. It calls the options routine at 0x2F36 without testing word 004C, so the menu remains available while the status-control row and ordinary scene actions are disabled. Ordinary dialogue and choice routines translate their own Escape input into status event 12, which poll_input_event at 0x7C3F returns to this dispatcher. Their modal state remains active and is redrawn after Game Options closes. Other modal interfaces may consume Escape locally.

The dialogue routine at 0x2A89 and text selector at 0x2758 similarly propagate BIOS F-key values BBh..C5h through status word 0052. poll_input_event adds BBh back. The main loop sends F1..F8 to the status switch only when word 004C is nonzero, but its F9 quick-load and F10 quick-save tests at 0x892D and 0x890A are outside that gate. Status screens entered this way are synchronous: returning from one redraws an interrupted dialogue or choice unless a load changed the session.

Faith and power descriptions take a subtly different route. Status dispatcher 0x83B6 calls wrapper 0x2AFD, which invokes the same dialogue reader but ignores its return value. A plain F1 through F10 still makes the reader return negative and closes the description, yet that key never returns to the main dispatcher and cannot open another status screen or trigger quick save/load. The next scene poll redraws any interrupted dialogue or choice.

The Computer Bible’s printable b alias is different. Its comparison at 0x884C..0x885F is an exact test against 62h and is reached only after a blocking text routine has returned the key to the main dispatcher. Ordinary dialogue does not return b, and the common choice selector consumes it as a first-letter row accelerator when one matches. Only a top-level lowercase b therefore opens the Bible; Shift+B and Caps-Lock B do not, while Shift plus Caps produces lowercase ASCII and does.

Because those routines are blocking, the key or pointer event used to leave one is consumed before the main dispatcher resumes. It cannot also activate the restored dialogue, set a scene confirmation latch, or select an underlying world target during the same poll.

The status switch at 0x83B6 has 13 entries. Entries 0 through 7 are the Computer Bible, Map, Faith, and five powers; entries 8 through 11 are no-ops; entry 12 calls the same options routine. The click dispatcher at 0x87E0 special-cases entry 12 before testing action selection or flag 0x36. This is the always-present upper-right disk indicator, so its authored bounds remain an options control even when the rest of the status row is hidden.

The options routine at 0x2F36 temporarily replaces the active text-menu table and builds its rows in this exact order:

  1. Continue
  2. Load Game
  3. Save Game
  4. New Game
  5. the translation string indexed by word 007C
  6. Music On or Music Off from word 0048
  7. Sound Effects On or Sound Effects Off from word 004A
  8. Automatic Combat On or Off from flag 37
  9. Quit

The installation no-combat word at 0058 suppresses row 8. Flag 38 replaces that row’s target with -2, the menu’s disabled value. A translation lock leaves the current translation row visible but prevents its target from cycling word 007C.

Before calling select_from_text_menu, the routine sets the text origin to logical (180,10) and the width to 130. choose_save_slot at 0x2B6F uses ten rows at (140,10) with width 170: the appropriate Cancel string followed by all nine labels. The confirmation helper at 0x2EEC builds two rows from Start New Game or Quit Game plus Cancel.

For each load row, choose_save_slot compares the mutable filename suffix at DS:0045 with that row’s digit and confirms that the state file exists. A match is copied to a temporary buffer and has literal << appended. Selecting a normal save or load leaves its digit as the active suffix; the F9/F10 path temporarily uses Q and restores suffix 0, so quick operations do not mark a numbered row.

The save-name routine at 0x2DF7 copies the selected label to a 27-byte local buffer and clears it first when it equals (EMPTY). Its editor at 0x2C8B limits input to 26 bytes and accepts ASCII ranges 20..3B, 3F..5A, and 61..7A, except for & and *. Backspace (08h) and Left (CBh) delete, Enter accepts, and Escape cancels. The local timer at 0x2CA5..0x2CD6 toggles a literal > at the current end every 02BCh (700) reference-timer units. Editing or deleting writes a new terminator and hides the cursor until the next toggle. After acceptance, initialize_empty_save_slot at 0x815A replaces an empty or (EMPTY) label with Game 1 through Game 9.

Before entering that editor, 0x2DF7 rebuilds a ten-row text table: disabled row zero points to Enter New Name, and rows one through nine retain every save label. It calls select_from_text_menu with the chosen slot plus one so the complete panel is drawn without entering the selector loop, releases the SELECT render slots, and passes the chosen row’s render slot and text coordinates to 0x2C8B. The editor therefore redraws only that row in style 2 while all other labels remain visible in style 1.

The common selector’s Escape branch at 0x278E reaches 0x28A3 and returns -1. The options caller distinguishes that value by nesting level. At 0x3267 it exits the main menu; the save/load calls at 0x3257 and 0x3276 loop back to rebuild the main menu; and the confirmation wrapper at 0x2EEC also reports a negative selection as unconfirmed. The label editor differs: 0x2ECC skips copying the edited local buffer on Escape, but the caller at 0x3261 still continues to save_selected_slot. Thus Escape there rejects the label edit without cancelling the selected save operation.

The function-key branch at 0x2758..0x2772 also makes the selector return -1, after recording the BIOS value relative to BBh in word 0052. Dialogue and choice callers expose that negative return directly to poll_input_event, but Game Options consumes it inside its own call stack. The main selector consequently closes at case -1/0x3267; a negative save/load selection returns nonzero to 0x325D or 0x327C and rebuilds the main menu; and a negative confirmation becomes false at 0x2F2B. No top-level F1..F10 action runs from that same key. The save-name editor at 0x2C8B uses a separate input loop and rejects function-key values.

F9’s state replacement is also a loop boundary, not a deferred scene request inside the controller. The branch at 0x8934 reads .SVQ, stores its return mode at 007A, and reaches the return test at 0x8966. A successful read therefore exits main_menu_and_game_loop. game_main stops boundary music, dispatches mode 2 at 0x8C4A..0x8C75, copies the retained checkpoint buffers to live state, and only then re-enters the scene loop. The old scene cannot execute another update with the restored variables.

F10 takes the neighboring but non-transitioning path. At 0x8911 it calls start_palette_blackout(1), which stores 0081 in the palette-effect word. The immediate compositor call at 0x8920 runs update_palette_effect; that clears the high bit, writes all 256 VGA entries black, decrements the effect to zero, and renders before write_save_state. The next compositor update reapplies the mapped scene palette. The save data and indexed framebuffer are not themselves blackened.

The same selector supplies first-letter accelerators. Its loop at 0x27AA..0x28E7 lowercases the key, scans menu rows from zero, compares the first displayed byte, and activates the first match. It does not skip the disabled target value -2; that value reaches the normal rebuild continuation. Options, slot selectors, and confirmation panels all inherit this ordering.

Home (C7) and End (CF) assign the first and last menu indices directly at 0x27BD and 0x27EF. The neighboring Up/Down paths inspect and skip disabled row marker FE; the edge assignments do not. Enter on such a directly selected disabled row returns target -2, which rebuilds the appropriate options level.

Selection dispatch proves the immediate settings behavior: translation is advanced modulo four and reloads the current text bank, music and effects XOR their enable bytes with one, and Automatic Combat toggles flag 37. The translation path at 0x31D0 calls load_text_bank directly. That loader rewrites descriptor offsets +0, +2, +5, +6, and +8, but deliberately leaves acquisition state byte +4 unchanged. Collected records therefore remain collected when their citation and verse strings change translation. Opcode 6B differs because its handler clears all 66 +4 bytes after the load.

The effects case at 0x31F5 consists only of xor byte [0x4A],1 followed by the common menu rebuild. It does not call release_sound_effect_buffer, digpak_stop_current_sound, or any other audio routine. An active effect therefore continues after the option changes to Off, although later opcode 58 starts are suppressed. Opcode 59 independently tests the same effects word at 0x5125; while it is zero the script takes the 100-unit silent fallback rather than polling the still-running driver.

Continue, a completed save, and a completed load close the interface. New Game and Quit only proceed after their confirmation menu.

The seven victim scenes each set a distinct rescue flag at successful progression points:

FlagScene / victim identifier
0x3AJELO
0x3BFEAR
0x3CCULT
0x3DLAW
0x3ERICH
0x3FDENY
0x40NAGE

GANTRY.BIN tests those seven flags and mirrors the set members into 0x42..0x48 before the Unibot sequence. The bytecode proves the one-to-one transition. CP1.BIN counts those later flags as the rescued crew physically present aboard the Unibot and requires all seven before departure.

One further durable flag is specific to the Unibot road network:

FlagMeaning
0x54The one-time Annoy Cyber verse-loss event has occurred.

The late-game programs also give exact meanings to variables 53 through 65:

IndexByte offsetCurrent meaning
530x6AUnibot turn/rotation offset.
540x6CCurrent Unibot node.
550x6EHeading: north 0, east 1, south 2, west 3.
56–620x70..0x7CPylons 1–7 rescued/destroyed.
630x7ENext node selected by forward movement.
640x80Active pylon number; 100 means none.
650x82Tower confrontation state: 0, 1, 2, or failure 9.

ROBOT.BIN initializes variables 53 through 55. CP2.BIN uses variables 56 through 62 as both pylon-completion state and the seven-part Tower gate. FACE.BIN and CP3.BIN alternate on variable 65 to implement the final study prompt and its success/failure branches. See the Unibot and endgame chapter for the complete graph and state machine.

Two lower flags carry the result of a conversation’s study-Bible prompt:

FlagConversation meaning
0x14The player selected the expected text descriptor.
0x15The player left the browser without that match.

The browser clears both flags before accepting input. Victim scenes branch on them after requesting the study screen, so they are transient result flags rather than durable progression markers. See the conversation-flow chapter for the complete prompt and suspension sequence.

Faith

Faith is variable 21 and uses a 0–10,000 scale, so one displayed percentage point is 100 internal units. The status renderer clamps values above 10,000 and below zero before selecting meter artwork. The F3 detail screen divides by 100 and writes the resulting two integer digits into the mutable Your faith is at 00%. template. A separate Your faith is at 100%. string handles 10,000.

The status-control dispatcher at 0x83B6 passes every Faith or power notice to the ordinary dialogue routine with logical presentation values (24,28,150). The five power messages are executable strings rather than generated labels:

  • Helps you hit harder during battle.
  • Helps protect you during battle.
  • Warns of rooms that are traps.
  • Causes dark halls to be lit.
  • Lets you fly in some places.

Opcode 0x81 passes an immediate loss to reduce_faith at 0x3979:

  • Easy divides the loss by two.
  • Normal applies it unchanged.
  • Difficult multiplies it by four.
  • No-combat mode suppresses the subtraction.

This directly supports the manual’s statement that Easy mode loses faith less readily and connects the installation no-combat option to the same damage path.

Faith exhaustion is checked centrally after input processing rather than by individual scene scripts. handle_faith_depletion at 0x7B12 clamps a negative value to zero and calls enter_game_over_scene at 0x1B86. That routine selects the initialized resource strings OVER and seg, sets the pending-scene state, and starts the accompanying palette effect. The POWER.BIN resource is a separate in-combat study interface and must not be confused with this game-over transition.

One encounter also raises faith rather than reducing it: the Zapper victory subroutine in COMBAT7.BIN alternates direct assignments of 1 and 10,000, ending at the maximum. This implements the special full-faith reward stated in the manual.

Text-record progression state

Each loaded text descriptor has a persistent byte at record offset +4. The save chapter describes its compact checkpoint copy and serialized live records. The bytecode interpreter addresses these bytes by the descriptor’s one-byte selector:

OpcodeOperandsEffect
0x36selectorSet the matching record’s state byte and select it.
0x37selectorClear the matching record’s state byte.
0x38selector, targetJump if the matching state byte is set.
0x39selector, targetJump if the matching state byte is clear.
0x88noneClear all 66 loaded state bytes.

This is the persistent bridge between dialogue/study records and scene control flow. The exact user-facing meaning varies by record: the same mechanism can represent an obtained verse, completed interaction, or another text-related condition.

Save inspection

Show named, nonzero, or checkpoint-different variables and decode the flag bank with:

tools/inspect_save.py CB/DDGAMES.SV9 --variables

The supplied saves have no active boolean flags. Both copies keep variable 16 at -1. Their live copies vary at general-purpose variable 28, and SV9 also has variable 27 set to 5; static evidence does not justify assigning gameplay meanings to those temporary slots.

Relevant executable functions

Load offsetCurrent name
0x1191initialize_script_state
0x1B86enter_game_over_scene
0x3979reduce_faith
0x43F5test_state_flag
0x4413set_state_flag
0x4433clear_state_flag
0x5B24get_text_record_state
0x5B76set_text_record_state
0x5BBFclear_text_record_state
0x7B12handle_faith_depletion

Offsets use the unpacked load-module convention documented elsewhere in this book.