Captain Bible engine specification
Project repository: https://github.com/peterkelly/captain-bible-re
This book specifies the observable behavior and data contracts of a compatible engine for Captain Bible in the Dome of Darkness. It is written for a reader who has never played the game and has never seen an earlier implementation.
The game data is declarative: an archive supplies palettes, artwork, maps, sound effects, music, text indexes, and scene programs. A compatible engine loads those resources, executes the scene programs, presents the same choices and challenges, and preserves the same persistent state. The host platform, programming language, renderer, audio library, and internal object model are implementation choices.
This is a clean-room behavioral specification. It intentionally contains no machine-code addresses, memory segments, compiler conventions, hardware interrupts, or instructions for reproducing an earlier program’s internal layout. Logical offsets that are part of a resource or save-file format are included because they are public inputs to a compatible implementation.
Normative language
The words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY mark requirements:
- MUST identifies behavior needed for compatibility with valid game data.
- SHOULD identifies a safe or useful behavior that does not change normal play.
- MAY identifies a host-specific implementation choice.
Unless stated otherwise, integers are little-endian, byte offsets begin at zero, strings use code page 437, and arithmetic stored in a script word wraps to 16 bits.
What an engine supplies
A complete engine has the following responsibilities:
- locate configuration, text companions, save files, and
DD1.DAT; - index and decompress named archive members;
- render a 320-by-200 indexed-color logical screen and palette effects;
- decode sound effects and play XMIDI music or a faithful conversion;
- join translation-specific verse indexes to companion text banks;
- execute all scene opcodes and coordinate scenes, threads, animation, dialogue, actions, maps, combat, and study interactions;
- maintain script variables, flags, faith, verses, exploration, and progression;
- read and write compatible save files; and
- expose equivalent keyboard, pointer, options, and export behavior.
The specification describes those responsibilities independently. The Conformance chapter brings them together into testable profiles.
The Game and Its Objective
Premise
The city has been enclosed by a Tower of Deception. Its field suppresses truth, and roaming deception Cybers capture and mislead the inhabitants. The Bible Corps opens a small breach and sends Captain Bible into the city.
Captain Bible has no innate superpower. His usable power comes from Scripture stored in a portable computer Bible. Teleportation begins the mission with the computer Bible empty, so verses must be collected from Scripture stations and applied to lies, locks, conversations, and other challenges.
Overall objective
The player must:
- enter each of seven principal buildings;
- explore its halls and rooms;
- collect useful verses;
- confront Cybers by matching their lies with appropriate verses;
- survive or avoid combat and environmental obstacles;
- rescue one victim in each building;
- return with all seven victims to crew the Unibot;
- drive the Unibot through its road network and destroy seven energy pylons;
- answer the Tower’s final study challenge; and
- destroy the Tower of Deception.
Failure occurs when faith is exhausted or when a scripted endgame challenge fails. Success proceeds through the final destruction and victory scenes. After the first building is completed, the remaining principal buildings may be tackled in any order. Leaving an unfinished building and returning can repopulate its Cybers according to the building-entry scene logic.
Normal play loop
Most of the game alternates between exploration and scripted interactions:
- In a hall, the player moves between connected map cells and chooses temporary actions such as Move, Get Verse, Unlock, or Confront Cyber.
- Entering a room starts a room-specific scene: Prayer, Trap, Jump Tunnel, Communications, or Victim.
- Dialogue advances one message at a time and may offer several responses.
- The study Bible asks the player to select and apply an acquired verse.
- Successful Cyber confrontation leads to manual or automatic combat.
- The map, faith display, computer Bible, powerup explanations, and options are available through persistent top-of-screen controls when the active scene permits them.
Scene programs define the detailed order. The engine provides reusable rendering, input, text, map, state, and timing services.
Hall actions and study
A Scripture station offers Get Verse. Taking it marks that verse as available in the Computer Bible and shows its reference and text.
A Cyber offers Confront Cyber. The Cyber states a lie and the Computer Bible opens so the player can apply a verse. Applying the expected verse defeats the lie and leads to combat. A related but not exact answer may follow a scene-defined retry without penalty; a wrong answer can reduce faith. Choosing Off before applying a verse retreats from the confrontation without penalty.
A locked side-room door offers Unlock. The game displays a true statement that must be matched to a supporting verse. The statement need not be a close paraphrase. A correct match clears the lock state so the room can be entered.
Manual and automatic action modes
Automatic Combat flag 0x37 changes both combat and Jump Tunnels:
- In manual Cyber combat, the usual actions are Attack, Defend, and Retreat. The player attacks during a vulnerability window and defends before an incoming hit. Several successful attacks may be required.
- In automatic combat, the actions are Combat and Retreat. The Combat result is selected by script random branches and can hurt neither party, hurt Captain Bible, or defeat the Cyber.
- In a manual Jump Tunnel, the player moves left or right to avoid incoming Cybers. In automatic mode, the script controls the run and its outcome.
The option cannot be changed in the middle of either interaction. Automatic Combat is distinct from installation no-combat mode: the former lets scripts choose results randomly, while the latter suppresses the engine’s normal faith-loss operation and prevents Jump Tunnel hits.
Difficulty
At the beginning of a new game the player selects Easy, Normal, or Difficult. The selection is stored as script variable 0 with values 0, 1, and 2.
Difficulty selects a distinct map resource for each building and changes faith loss:
| Difficulty | Map suffix | Faith-loss multiplier |
|---|---|---|
| Easy | E | one half, using integer division |
| Normal | N | one |
| Difficult | D | four |
The maps contain different verse and encounter populations. Scene programs also make individual challenges more severe at higher difficulty. In Difficult mode, Trap rooms can remove every powerup except Flight.
Faith and failure
Faith is stored in hundredths of one percent. Full faith is 10,000 and zero is empty. The status meter and detailed percentage display clamp out-of-range values for presentation. A loss operation scales its base amount by difficulty unless no-combat mode is active.
After input and scene processing, the engine MUST detect faith below zero, clamp it to zero, and enter the game-over scene. Prayer rooms can restore faith. Defeating a Zapper Cyber restores full faith.
Verses and powerups
Obtained verses are represented by persistent state bytes on the current text bank’s descriptors. Scripture stations reveal verses, dialogue and study screens apply them, and the map can show references without revealing the verse text.
Five durable capabilities have status icons and state flags:
| Flag | Capability | Player-facing effect |
|---|---|---|
0x30 | Sword | Improves combat attacks. |
0x31 | Shield | Improves defense in combat and obstacles. |
0x32 | No Trap | Makes Trap-room doors flash a warning. |
0x33 | Candle | Lights dark hall regions. |
0x34 | Flight | Enables flight where a scene supports it. |
Prayer rooms award these capabilities through study challenges. The exact challenge flow remains scene-driven rather than hard-coded by the engine.
Room types
Every building can contain the following room classes:
- Victim: hold the person who must be rescued for that building.
- Trap: require an appropriate verse to escape safely.
- Prayer: restore faith or award a powerup after a study interaction.
- Communications: let rescued victims discuss and transfer a verse.
- Jump Tunnel: move Captain Bible to another area while presenting an avoidance challenge.
The detailed interactions are:
- A Trap presents a lie that must be answered with the correct verse to escape safely. A nearly correct choice can allow another attempt. On Difficult, Trap consequences can clear every power except Flight.
- A Prayer room can restore faith or grant a requested power. A power request succeeds after the correct verse is applied; a wrong verse does not hurt Captain Bible.
- A Victim conversation requires several verse applications. The first victim provides access needed for the other buildings; later rescues return Captain Bible to that building’s entrance.
- A Communications room speaks through an already rescued victim, poses a fixed interpretation choice for one verse, and transfers that verse after a correct response. A wrong response or leaving without choosing can cost faith.
- A Jump Tunnel relocates Captain Bible after the manual or automatic avoidance sequence described above.
The first building has no Communications room. The world-map format encodes room class and entrance orientation; the World Maps and Navigation chapter gives the exact representation.
Cyber vulnerability guide
The combat art and timing communicate these intended attack windows:
| Cyber | Vulnerability or special behavior |
|---|---|
| Macho | Attack while its eye is open. |
| Armored | Attack while its lid is open. |
| Mantis | Attack while both arms are lowered. |
| Snake | Attack when it pauses in the lower-right corner. |
| Spider | Attack at the top of its jump; a hidden one may drop behind the player. |
| Leech | Attack while its lid is open; victory exposes the covered station. |
| Zapper | It can be walked under but drains faith; victory restores full faith. |
| Annoy | A one-time outdoor event that clears acquired verses and leaves without combat. |
Compatibility Contract
Behavioral, not structural, compatibility
A conforming engine MUST reproduce the behavior visible to scene programs, resource files, saves, and players. It does not need to reproduce any previous engine’s allocation strategy, pointers, processor mode, hardware API, or function decomposition.
The following are compatibility interfaces:
- archive directory records and compressed payloads;
- resource and external-file names;
PAL,ART,ABT,XMI,MAP, verse-index, and companion-text bytes;BINopcode encodings and absolute resource offsets;- signed 16-bit script state and flag identifiers;
- player-visible input, menus, dialogue, animation, audio, and timing order;
- scene, map, text, progression, and checkpoint transitions; and
.SV0,.SV1through.SV9, and.SVQfiles.
Compatibility profiles
Data profile
An implementation satisfies the data profile when it can parse every valid shipped resource and save file, reject malformed lengths without unsafe memory access, and expose decoded values to higher layers.
Shipped-game profile
An implementation satisfies the shipped-game profile when it can execute all 62 supplied scene programs and complete the game with equivalent visible state transitions. This profile exercises 122 of the 145 opcode values.
Full interpreter profile
An implementation satisfies the full interpreter profile when it also implements the 23 opcodes absent from shipped scene code according to this specification. A complete compatible engine SHOULD satisfy this profile so tools or restored content can use the full instruction set.
Resource offsets and host references
A word stored in a scene program is often an offset relative to the beginning
of that expanded BIN member. A clean implementation SHOULD represent it as
an integer or a bounds-checked slice, never as a host pointer. The same rule
applies to string offsets, animation-step offsets, branch targets, and embedded
tables.
Save-file text descriptors contain four historical pointer bytes. Those bytes are not portable identifiers. A clean implementation MUST rebuild text references from the saved bank and selector information and MAY preserve or zero the opaque pointer bytes when writing a save.
Valid-input assumption and defensive behavior
The normative game behavior assumes the supplied, internally consistent data set. A host implementation MUST still bounds-check all external data. It SHOULD fail atomically with a useful error on a malformed archive, truncated resource, invalid target, division by zero, or wrong-size save instead of partially mutating live state.
Defensive rejection is not a gameplay incompatibility. Silently changing a valid resource, reordering duplicate archive entries, changing signed arithmetic, or normalizing an opaque map value is.
Determinism
Given identical complete runtime state, including random state, and identical inputs, including the same outer-controller boundaries and elapsed deltas, script execution and resource decoding MUST be deterministic. The random state is not serialized, but the portable generator, call order, result range, and every script-visible mapping are normative and specified in Lifecycle and scheduling. A test harness MAY replace the startup state before any generator advance.
Engine Lifecycle
Startup
The engine startup sequence SHOULD be organized as follows:
- determine the game-data directory and player save prefix;
- read installation policy and user overrides;
- open and validate
DD1.DAT; - initialize graphics, input, audio, text, and save services;
- read the nine-slot save-label index or synthesize empty labels;
- load
RUN.ARTas the globally available cursor/movement artwork; - initialize a new session; and
- enter
LOGO.BIN, which begins the logo, title, and introduction chain.
A modern engine does not need the historical sound-driver files. It MUST honor the resulting music, effects, translation, mature-topic, and no-combat policy.
The initial resource sequence is content-driven:
LOGO.BIN -> LOGO.PAL, LOGO.ART, D003.ABT
TITLE.BIN -> TITLE.PAL, TITLE.ART, TITLE2.ART, MUS001.XMI
INTRO.BIN -> INTRO.ART
The engine only selects the initial LOGO scene; those programs request the
remaining loads and transitions.
New session
A new session clears all 100 signed script words, flags, text state, map state, scene-local tables, and transient UI state. It initializes the scene-name buffers, presents the introductory sequence and difficulty selector, and then enters gameplay with full faith.
The scene programs perform much of the initialization. The engine MUST avoid pre-populating script values that those programs expect to set themselves.
Main update cycle
One logical update cycle performs these responsibilities in a stable order:
- poll pointer and keyboard input and translate it into logical events;
- update active palette effects and animation slots;
- update or simulate sound-effect completion;
- run eligible scene-command threads;
- render display records and active overlays;
- service modal dialogue, choice, study, map, status, and options requests;
- apply a selected action or resumed bytecode target;
- detect faith exhaustion and other top-level state transitions; and
- present the completed 320-by-200 frame.
An implementation MAY split or combine these stages, but waits, modal screens, and animation must continue to observe the same ordering. In particular, a modal message may keep scene animation updating while its command thread is suspended.
Scene transition
A scene is identified by a base name and a secondary segment string. To enter a scene, the engine MUST:
- stop or release scene-owned render and audio state as required;
- clear the display list, action targets, navigation callbacks, animation definitions, and scene threads;
- load
<base>.BINfrom the archive; - retain the expanded bytes as mutable scene memory;
- start bytecode execution at offset zero; and
- let the scene program load its palettes, art, text bank, map, and audio.
Some commands deliberately patch bytes inside the loaded BIN image before a
later scene-change or resource-load command reads them. The expanded scene
buffer MUST therefore be writable for the lifetime of the scene.
Suspension and resumption
Scene execution is cooperative. A handler either continues to the next command, selects an absolute target, ends the current invocation, or yields. A yielding command records enough state to resume at either the same command or a target supplied by the UI.
Wait commands retry their condition. Dialogue commands retry without consuming their text operand if another modal dialogue is already active. Choice presentation resumes at the selected choice target. Study requests suspend the scene while the study Bible runs, then expose success and cancellation flags to later script commands.
Checkpoints, saves, and restore
The engine maintains both live state and a checkpoint copy. Scene opcode
0x55 copies selected live state into the checkpoint. Normal and quick saves
serialize both copies; saving does not implicitly refresh the checkpoint.
Restoring a state file loads both copies and then copies checkpoint fields to live state to reconstruct the resumable scene, text bank, descriptors, and map. This is why saving during a conversation may resume from the beginning of that scene rather than from the exact dialogue line. Disk restore leaves the current scene loop before applying that checkpoint; no command stream from the predecessor may execute against restored variables.
Opcode 67 performs the same retained restore without opening a disk selector:
it replaces live state from the already retained checkpoint, reconstructs the
checkpoint text bank and descriptor states, and enters the checkpoint scene.
The scene that issued 67 must not remain suspended awaiting host input.
Runtime random source
The portable runtime owns one unsigned 16-bit random state. Normal interactive
launches MAY initialize it from the host clock. A conformance or comparison
runner MUST be able to install an explicit state in 0..65535 before any
generator advance. New Game and scene changes do not reseed it, and the
original save format does not serialize it.
Each requested value advances the shared state and returns:
state = (state * 0x6255 + 0x3619) modulo 2^16
random = state >> 1
The multiplier is one modulo four and the increment is odd, giving a full
65,536-state period. The returned value is in the inclusive range 0..32767.
Starting from state 1, the first six returned values are 19511, 30543,
10098, 22502, 1941, and 10629.
Opcode 82, opcodes 8E and 91, the 20-swap X text-component shuffle,
and opcode 8B all consume this one stream. Opcode 82 stores
random % modulus. Opcodes 8E and 91 use random & 0xFF as their random
byte. The X shuffle consumes two values for each swap, first the left index
and then the right index. Opcode 8B chooses a starting descriptor with
random % descriptor_count, advances cyclically past state-zero descriptors,
clears the first set descriptor, and sets variable byte offset 0x5C to
3,000. It makes that request only when difficulty variable zero is 2, a text
bank has at least one descriptor, and at least one descriptor state is
nonzero; otherwise it does not advance the stream. Every request advances once
even when its modulus is one. An implementation MUST retain this call and
mapping order.
The status controller also consumes the stream while the transient timer at
variable byte offset 0x5C is active. An outer controller update performs one
refresh before scene-VM execution and one after it. Each eligible refresh:
- requires raw state flag
36and a nonzero signed timer; - subtracts the same batched elapsed-timer delta with 16-bit wrapping;
- advances the generator exactly once and uses
random & 3as X/Y flip flags for zero-basedSTUFF.ARTframe 27 at logical(10,10); and - clamps the timer to zero and removes that transient display when the resulting signed timer is nonpositive.
A refresh that reaches or crosses zero still consumes its value. If flag 36
is clear, the transient display is removed while the timer remains frozen and
the stream does not advance. Opcode 8B executes between the two refreshes,
so a timer it creates consumes the descriptor-selection value and then one
post-VM status value during that update; an already-active timer normally
consumes both status values. Consequently, the sequence of outer controller
boundaries and elapsed deltas is part of deterministic input while this timer
is active. Comparison runners MUST supply the same sequence in every
implementation.
Shutdown
The options menu offers an explicit exit with confirmation. A clean engine SHOULD flush any host audio and release resources. It MUST NOT silently save the game during ordinary exit.
Input and User Interface
Logical coordinate system
All scene coordinates and action targets use a 320-by-200 logical viewport. A host window MAY scale this viewport to any size, but pointer coordinates MUST be transformed back to the logical range and clamped to valid pixels. Pixel-art scaling SHOULD use nearest-neighbor sampling.
The primary pointer action is a left-button press. Keyboard and pointer paths must select the same logical actions.
Pointer position is host-input state, not scene state. Loading another scene
or starting a new session MUST preserve the current logical pointer coordinate
rather than snapping it to (0,0).
The shipped-game profile draws the DOS pointer itself and hides the platform
cursor over the viewport. It is composited last, clipped at the logical
edges, with palette index 1 at these offsets from (pointer_x,pointer_y):
(0,-6)..(0,-3)
(-6,0)..(-3,0) and (3,0)..(6,0)
(0,3)..(0,6)
Palette index 15 supplies the three-pixel shadow at (1,-4), (1,-3), and
(1,-2). Scaling MUST preserve these as logical pixels.
Keyboard input is event based. A held key MAY produce repeated press events at the host keyboard’s typematic rate, and each such event MUST re-enter the same dispatch path as the initial press. In particular, holding a cursor key continues to move through menus, Bible references, map cells, or the eight-pixel pointer steps described below. An implementation MUST NOT reduce the complete hold to one rising-edge action. The exact initial delay and repeat interval are host policy.
Exploration controls
During ordinary movement:
- arrow keys request movement in available directions while action selection is enabled;
- moving the pointer within range of an active target reveals its action label;
- clicking the active label selects it;
- Space moves the pointer through currently available temporary actions;
- Enter selects the current temporary action; and
- printable keys are translated through the DOS selector-key table described below.
Pointer targeting uses the following integer distance approximation in logical screen coordinates:
max(abs(pointer_x - target_x), abs(pointer_y - target_y))
+ floor(min(abs(pointer_x - target_x), abs(pointer_y - target_y)) / 2)
A target is eligible when this value is at most 70. The closest eligible
target wins. Equal distances preserve table order, with enabled opcode-02
navigation nodes considered before ordinary opcode-3A action targets. A
navigation node without an opcode-10 selector label remains a pointer
target: its opcode-02 X/Y geometry supplies the target coordinate. The
current unlabeled node is excluded so that coincident source and destination
nodes can still select the destination in table order. A click with no
eligible target MUST NOT activate the globally nearest action.
The resolved proximity target is shared by primary-click, Enter, and the starting point of Space cycling. Those keyboard paths MUST NOT substitute a label-only lookup: an unlabeled navigation node remains the current target and Enter activates it, while Space advances from its table position.
Space first resolves the current proximity selector, then searches forward
from the following record. Enabled scene-thread selectors are visited in
record order before active opcode-3A actions, and the search wraps from the
action table to the scene-thread table. A cycle candidate must have authored
X < 320 and Y < 200; this deliberately excludes the off-screen selector
sentinels and targets placed exactly at row 200. On success the logical
pointer moves to (X,Y+4), subject to host viewport clamping, and the
transient selector is recomputed. Cycling does not activate the target.
When global action selection is enabled, Enter activates the current
proximity selector and does nothing if there is none. A primary click uses the
pointer target path described above and likewise does nothing when no target
is eligible. Enter and a primary click set the opcode-64 confirmation latch
only while global action selection is disabled. This keeps exploration input
from accidentally satisfying a script wait. The reserved top 24 logical rows
do not establish a scene selector for Enter or pointer activation.
When global action selection is disabled, arrow keys do not search for direction labels. They move the logical pointer by eight pixels per press: Up and Down subtract or add 8 from Y, while Left and Right subtract or add 8 from X. The host pointer layer clamps or warps the result to the viewport.
The hover label is a transient display object anchored at the selected
target’s authored X/Y coordinates. It is shown only for targets with a
nonempty selector label. An unlabeled navigation target is therefore
clickable without displaying invented arrow or label artwork. The hover label
disappears when action selection is disabled, a modal interface is active,
the pointer is in the top 24 logical rows, or no labeled target is in range.
Direction selectors use the transparent STUFF.ART frames below:
| Selector | Zero-based frame |
|---|---|
.u | 7 |
.d | 8 |
.l | 9 |
.r | 10 |
Three-character numeric selectors use the form .XY, where X is the
zero-based slot in the scene’s loaded ART list and Y is a one-based frame
within that ART resource. The frame is drawn at the target coordinate with
its signed ART origin. Thus every combat program’s first resource load places
COMBTAGS.ART in slot 1, making .11, .12, .13, and .14 select its
ATTACK, DEFEND, RETREAT, and COMBAT frames without an engine-side
combat label table. Other scenes MAY use the same syntax with different
loaded resources; .23 appears in the shipped content.
Common selector strings are:
| Selector | Action |
|---|---|
.u, .d, .l, .r | Move up, down, left, or right |
.c | Confront Cyber |
.x | Unlock |
.v | Get Verse |
.11 | Attack |
.12 | Defend |
.13 | Retreat |
.14 | Combat |
Selector strings are data-driven; the engine MUST route them through the active action-target table rather than hard-coding actions by screen position.
Keyboard selection compares a translated key with the selector character
after the dot. Selectors beginning .1 instead compare against their third
character, so the four combat selectors expose keys 1 through 4. The
executable applies these letter translations before searching:
| Input | Selector key |
|---|---|
| A or S | 1 |
| D | 2 |
| R | 3 |
| G | v |
| U | x |
| Other letters | the same lowercase letter |
C first searches for an ordinary .c selector and, only if none is active,
falls back to selector key 4. This preserves Confront Cyber outside combat
while making C select automatic Combat inside an encounter. Searches consider
enabled scene-thread selectors before active opcode-3A targets, matching
pointer tie priority.
Persistent status controls
When permitted by the current scene, the top control row provides:
| Key | Control | Behavior |
|---|---|---|
F1 or lowercase b | Computer Bible | Browse obtained verses and optionally apply one. |
| F2 | Map | Show explored cells, room markers, and verse references. |
| F3 | Faith | Show the numerical faith percentage. |
| F4 | Sword | Explain the Sword powerup. |
| F5 | Shield | Explain the Shield powerup. |
| F6 | No Trap | Explain the No Trap powerup. |
| F7 | Candle | Explain the Candle powerup. |
| F8 | Flight | Explain the Flight powerup. |
| Escape | Game Options | Open the options menu. |
| F9 | Quick Load | Load <player>.SVQ; if it cannot be opened, start a new session. |
| F10 | Quick Save | Present one black-palette update, write <player>.SVQ, then restore the scene palette. |
These are context-sensitive controls. A scene may consume a function key as a normal continue action or suppress the status row. The engine MUST route input through the active UI state before applying global shortcuts.
An input consumed by a blocking or host-owned interface MUST NOT be applied again to the scene after that interface closes. In particular, Enter that accepts a save label or closes a status notice must not also set a script confirmation latch, and an Off click that closes the Computer Bible or map must not activate a scene target underneath it.
Modal ownership also applies while an interface remains open. Direction keys used by a choice menu or Bible browser navigate that interface; direction keys without a meaning in an ordinary dialogue MUST be ignored rather than selecting an exploration action underneath the dialogue.
Escape during an ordinary dialogue message or response-choice menu does not dismiss or select that modal. It temporarily replaces the modal with Game Options. Choosing Continue, or otherwise leaving the main options panel, MUST restore the same dialogue or choice menu with its preceding text and selection. Enter and a primary click remain the ordinary ways to advance a dialogue message.
The dialogue click target is the transient CONTINUE object, not the whole
viewport or panel. Its logical anchor is
(text_x + floor(text_width / 2), text_y + 5). The pointer selects it when
the same integer distance used for exploration targeting is at most 100.
Clicking elsewhere MUST leave the dialogue active.
The same two text interfaces propagate F1 through F10 to the top-level
dispatcher. F1 through F8 remain subject to global action selection; when
enabled, their status interface temporarily replaces and then restores the
dialogue or choice menu. F9 and F10 retain their unconditional Quick Load and
Quick Save meanings. The Computer Bible, map, and status notices consume
their own navigation and dismissal input instead of forwarding those keys.
Only unmodified function keys use these meanings. Shift-, Ctrl-, and
Alt-modified function keys retain distinct BIOS codes and MUST NOT be treated
as plain F1 through F10.
The lowercase b alias is top-level only: ordinary dialogue ignores it,
while a choice menu lowercases its accelerator input and may use either case
for a response beginning with B. The top-level test is against the ASCII
byte 62h, so Shift+B or Caps-Lock B does not open the Bible; Shift and Caps
together produce lowercase b and do.
Game Options uses the same text selector but owns function keys differently. F1 through F10 abort the current options selector and MUST NOT also perform the global shortcut. From the main options list this closes Game Options and restores any interrupted dialogue or choice. From a save/load slot list or a New Game/Quit confirmation, it returns to the main options list. The save-name editor ignores function keys.
The artwork and input gates are distinct. Persistent flag 36 controls
whether the Computer Bible, Map, Faith, and acquired-power artwork is drawn.
Global action selection independently enables F1 through F8 and the b
alias for the Computer Bible. A pointer press on status artwork requires both
gates, while a keyboard shortcut does not require the artwork flag. Faith at
exactly zero still selects the empty Faith frame; it does not itself hide the
row. Engine-owned modal interfaces suppress the ordinary row. Ordinary
dialogue and choice interfaces nevertheless propagate the explicitly listed
global commands.
The upper-right frame-11 disk indicator is separate from the conditional
status row. A primary press within its authored bounds opens Game Options even
when action selection or flag 36 is clear, matching Escape. An active modal
interface still consumes the press first.
The missing-file F9 behavior is historical rather than a recommended generic
save policy. The DOS state reader returns mode 1 when .SVQ cannot be opened,
and the top-level dispatcher treats mode 1 as New Game. A compatible engine
MUST therefore restart while retaining the selected translation and
music/effects/automatic-combat options. A present but malformed quick save
remains a load error under the stricter native-reader requirements.
F10 schedules a one-update palette blackout before writing the quick state. The framebuffer and saved fields are unchanged: only the displayed palette is black for that presentation. The following scene update restores the normal mapped and adjusted palette.
F3 and the acquired-power controls use the ordinary dialogue panel at logical
presentation (24,28,150). Faith is displayed as
Your faith is at NN%., where NN is the zero-padded integer result of
dividing variable 21 by 100. Full faith uses the separate text
Your faith is at 100%.. F4 through F8 respectively display:
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.
The ordinary status row uses transparent-zero frames from STUFF.ART at
their descriptor origins. Frame 4 is Computer Bible at (4,1), frame 32 is
Map at (23,1), and frames 22 through 26 are the five Faith-meter states at
(44,3). Acquired Sword, Shield, No Trap, Candle, and Flight powers display
frames 17 through 21 respectively. Frame 11 is the upper-right disk indicator
at (297,1). These are logical 320-by-200 coordinates; the standard output
expands them by 2 in both axes. Pointer activation uses the visible frame
bounds and must invoke the same interface as F1 through F8.
F2 is context-sensitive. Inside a building it shows explored and unexplored halls, room letters, stations, and verse references. Outside, it shows the city and colors a building gold after its victim has been rescued. One building uses a special view of broken and closed platforms, rooms, and doors. During the Unibot sequence, a lower-right map shows the vehicle’s node and heading.
F2 map modal
The ordinary F2 map is a 320-by-200 indexed modal built from MAP.ART; it is
not a host-drawn diagram or a panel layered over the current scene. Opening it
replaces the complete viewport with palette index zero, temporarily suppresses
the scene and status controls, and draws the composed map using the current
scene palette. MAP.ART has no companion map palette.
Frame numbers below are zero-based resource indexes. Frame 0 is the
189-by-167 blue enclosure at descriptor origin (44,26), and frame 1 is
the hinged OFF control at (64,27). The engine MUST clone frame 0 for each
opening. It then composites symbol frames into that clone with source color
zero transparent. A symbol’s destination is:
destination_x = caller_x + symbol.origin_x
destination_y = caller_y + symbol.origin_y
The mutated frame 0 and the unchanged frame 1 are then drawn at their own descriptor origins. Implementations MUST preserve indexed pixels through this composition; substituting generic lines, text, or geometric markers does not reproduce the resource artwork.
When exterior-map flag 39 is set, the normal grid is skipped. Frame 51 is
composited at caller offset (66,31). For each set rescue flag 3A through
40, the corresponding frame 52 through 58 is composited at the same offset.
This produces the seven-building city overview and turns rescued buildings
gold using the authored overlays.
When flag 39 is clear, the engine uses the packed world grid and exploration
rows as described in the world chapter. Sixteen-column maps first composite
frame 62 at (0,0) for the legend. Each cell uses caller position
(64 + 7*x, 34 + 5*y). Frames 3 through 18 are the 16 explored connection
shapes, and frames 24 through 39 are the corresponding unexplored shapes.
Frames 19 through 21 and 40 through 42 are the explored and unexplored room
approaches. Explored rooms add their class letter from frames 46 through 50
(V, T, P, C, or J). Frames 59 through 61 supply station and special
level-E markers. The exact frame selection is specified with the packed-map
model in the world chapter.
Escape, O, o, or a primary-button press on frame 1 closes the modal. A
press elsewhere does nothing. Enter does not close it. Arrow keys retain the
DOS map behavior of snapping the pointer along its five-pixel vertical and
seven-pixel horizontal grid:
Up: y = ((y - 57) / 5) * 5 + 52
Down: y = ((y - 57) / 5) * 5 + 62
Left: x = ((x - 104) / 7) * 7 + 97
Right: x = ((x - 104) / 7) * 7 + 111
The divisions truncate toward zero, matching signed 16-bit DOS division.
The mouse driver or host coordinate layer clamps the result to the logical
viewport.
When state flag 52 is set, the ordinary F2 map request is suppressed; the
late-game vehicle map remains owned by its scene program.
Dialogue and menus
A modal message advances on Enter or a primary press within its authored
Continue hit region. Escape transfers to Game Options without accepting the
message, as specified under persistent status controls. A choice menu uses Up
and Down to move the highlighted row, clamps at both ends, and uses Enter to
select it. Choice and options rows use the common text-selector proximity
model rather than rectangular buttons. Each row is anchored at logical
(text_x + 72,row_y + 2); the nearest anchor at distance 100 or less under
the integer metric defined above wins, with row order breaking ties. Pointer
motion outside all row regions clears the pointer highlight, and a click
there does nothing.
Home selects the first row and End selects the last row in these general text menus. Up and Down skip disabled rows; the direct Home/End edge selection does not. Activating a disabled row takes its disabled continuation instead of performing the unavailable command.
The general text-menu loop also accepts first-character accelerators. It lowercases alphabetic input and scans rows from the beginning, immediately activating the first row whose displayed text begins with that character. Digits and other printable keys pass through unchanged. This applies to dialogue choices as well as Game Options and its selectors and confirmations; duplicate initials therefore always choose the earliest row.
Keyboard matching follows the DOS conversion of the BIOS key word, not the
physical key alone. Plain and Shift+Enter produce ASCII 0D and activate
Enter behavior; Ctrl+Enter produces 0A and Alt+Enter is extended, so neither
activates it. Plain, Shift, and Ctrl Escape produce ASCII 1B; Alt+Escape is
extended and does not activate Escape behavior.
The numeric keypad follows the same converted values. Keypad Enter activates Enter behavior. In navigation mode its 8/2/4/6 keys are Up/Down/Left/Right, 7/1 are Home/End, and 9/3 are Page Up/Page Down. In numeric mode its digits and decimal point are ordinary printable input, while divide, multiply, minus, and plus are printable in either mode. Shift reverses the effective Num Lock mode as on the BIOS keyboard path. Keypad Left in navigation mode therefore also performs the save-name editor’s Left deletion.
Escape cancels a study or returns from interfaces where an Off action is
available. In the Computer Bible, Up and Down move one acquired verse, while
Page Up, Page Down, and the artwork arrows move 14 acquired verses. Apply uses
A or Enter only when encounter state enables it. Escape and O select Off.
A Ctrl+V press selects the configured expected verse when it is present in
the acquired list and moves to that verse’s page without applying it.
A pointer press on a reference row changes the selection without applying it;
the authored Apply control requires a separate press. Disabled artwork also
disables the corresponding pointer action. Exact artwork, coordinates, and
text styles are specified in Dialogue and Bible study.
Options menu
At the top-level scene input loop, Escape opens the options menu regardless of whether action selection and its status-control row are currently enabled. An active dialogue, study interface, map, notice, choice menu, save-name editor, or options sub-menu consumes Escape according to its own rules first. The options shortcut is therefore not gated like F1 through F8.
The options menu is an ordinary text-menu panel with logical presentation
(180,10,130). Its rows, in display order, are Continue, Load Game, Save
Game, New Game, the current translation name, Music On/Off, Sound Effects
On/Off, Automatic Combat On/Off, and Quit. The Automatic Combat row is omitted
when installation policy disables combat. Up and Down move through the rows;
Home and End select the first and last rows, and Enter or a click activates
the selected row.
The common SELECT artwork is anchored to the left of these rows because
their text X is at least 23: its anchor is (text_x - 11,row_y + 2). For
menus whose text begins before X 23, including ordinary dialogue choices, it
is anchored on the right at (text_x + text_width + 12,row_y + 2). Pointer
proximity is still measured from the independent text_x + 72 row anchor.
A newly opened text menu MUST resolve the current pointer even if the host
pointer has not moved since the preceding frame; a distant stationary pointer
therefore leaves the initial menu with no highlighted row.
A disabled row can be reached by keyboard edge selection, but if it is the
nearest pointer row it suppresses pointer selection rather than allowing a
more distant enabled row to win.
Automatic Combat cannot be toggled while the combat-active flag is set. Exit
and New Game require confirmation. Exit does not save. Save and load use the
nine labeled slots. A normal save lets the player edit a slot label of at most
26 bytes plus NUL. An existing (EMPTY) label is cleared before editing. If
the player commits an empty label, the engine substitutes Game 1 through
Game 9 according to the slot number.
The editor accepts ASCII 20..3B, 3F..5A, and 61..7A, except for & and
*. Backspace or Left removes the final byte, Enter commits, and Escape
cancels. The blocking DOS editor does not treat a mouse click as Enter. It
keeps the 170-pixel-wide ten-row slot panel visible: row zero becomes
Enter New Name, all nine slot labels remain in rows one through nine, and
only the selected slot is redrawn in selected style as its text changes. A
literal > cursor at the end of that label toggles every 700 reference-timer
units; an accepted edit or deletion hides it until the next toggle. The most
recently selected normal save/load slot is marked with << in the load
interface when its state file exists. Quick save/load temporarily uses suffix
Q and resets the suffix to 0, so it does not mark a numbered row. Continue
closes the menu without changing the game.
Escape follows the continuation owned by the current level. On the main
options screen it closes the interface. On a save/load selector or a New
Game/Quit confirmation it returns to the main options screen. During save-name
editing it rejects only the edited label: the selected slot is still saved
with its preceding label, or with the normal Game N default if that label
was (EMPTY), and the options interface then closes.
Alphabetic first-letter accelerators use the same row order as the displayed menu. A disabled row remains the first match and takes the disabled continuation rather than falling through to a later row with the same initial.
The save and load selectors use presentation (140,10,170). Their first row
is respectively Cancel Save Game or Cancel Load Game, followed by slots
one through nine. Confirmation menus retain the main panel position and use
the two rows Start New Game/Cancel or Quit Game/Cancel.
Faith and power descriptions use the ordinary blocking dialogue reader but are called directly by the status dispatcher. A plain F1 through F10 therefore ends that description through the reader’s negative return, but the dispatcher does not propagate or execute the function-key action. The interrupted scene dialogue or choice, if any, is restored. Modified function keys do not match this exit path.
Opening this interface removes the ordinary status-control artwork and pauses
the scene controller. Changing translation immediately reloads the current
text bank. This reload MUST preserve each runtime descriptor’s acquisition
state byte by descriptor index, so changing Bible version translates collected
verses instead of discarding them. This differs from scene opcode 6B, whose
handler explicitly clears all descriptor states after loading a different
bank. Music, effects, and Automatic Combat change their persistent state
immediately and redraw the menu with the matching On/Off string.
Pause behavior
Opening the options interface pauses ordinary scene interaction. Modal dialogue and study interfaces suspend their owning scene thread but MAY keep visual animation and audio service active. A host application losing focus MAY additionally pause, provided it does not advance script timers while paused.
Resource Names and DD1.DAT
External files
A compatible installation uses these game-owned inputs:
| Name | Purpose |
|---|---|
DD1.DAT | Named archive containing scenes, art, palettes, maps, audio, and verse indexes |
DDLA through DDLG, DDLR | Companion gameplay and study text |
SOUND.5 | Installation policy locks |
<player>.SV0, .SV1 through .SV9, .SVQ | Save labels and game states |
The historical SOUND.1 through SOUND.4 files contain platform-specific
audio drivers and timbres. A modern engine MAY ignore them and use native
audio services. SOUND.5 remains relevant because it carries content policy.
Archive directory
DD1.DAT begins with a 16-bit member count followed by that many 24-byte
records:
| Record offset | Size | Meaning |
|---|---|---|
0x00 | 8 | Zero-padded ASCII base name |
0x08 | 1 | Storage marker: 0 raw, 1 compressed |
0x09 | 3 | Zero-padded ASCII extension without a dot |
0x0C | 4 | Absolute payload offset |
0x10 | 4 | Expanded size |
0x14 | 4 | Stored size including payload magic |
All numeric fields are unsigned. Padding after the first NUL in either name field MUST be zero. Payloads MUST be contiguous in directory order, the first payload begins immediately after the directory, and the final payload ends at the end of the archive.
Every payload begins with ASCII GC. The two magic bytes count toward stored
size but not expanded size.
The supplied archive declares 369 entries:
| Extension | Count | Meaning |
|---|---|---|
ART | 143 | Indexed artwork and frames |
BIN | 62 | Scene bytecode and embedded data |
ABT | 41 | Compressed digital effects |
PAL | 37 | Complete indexed palettes |
| none | 33 | Translation-specific verse indexes |
XMI | 32 | Music sequences |
MAP | 21 | Building maps by level and difficulty |
Names are looked up case-insensitively after splitting base and extension. The archive contains duplicate names whose contents are identical. An engine MUST retain directory order and SHOULD return the first matching entry unless an inspection API explicitly addresses entries by index.
Raw payloads
For storage marker 0, the bytes after GC are the complete expanded resource.
The stored size MUST equal expanded size plus two.
Compressed payloads
Storage marker 1 uses an LZW-family dictionary with codes up to 12 bits. Codes 0 through 255 are literals. Dictionary entries contain a prefix code and one suffix byte.
Each dictionary pass proceeds as follows:
- initialize literal entries 0 through 255;
- read and emit one literal code;
- use it as the prefix of entry
0x100and set the next counter to0x101; - read a code, recursively expand and emit its phrase, save that code as the
prefix for the following entry, and save the phrase’s first byte as the
suffix of entry
counter - 1; - increment the counter and repeat; and
- when the counter reaches
0x1001, restart a dictionary pass by reading a new literal. There is no clear code.
The one-entry offset in step 4 provides the ordinary LZW case where a code can refer to the entry currently being completed.
Codes are stored in groups of at most eight. They are not one continuous bit
stream. For a group, read all high-bit plane bytes first and then one low byte
per code. Bit 0 of each plane byte belongs to the first code, bit 1 to the
second, and so on. The first plane supplies code bit 8, the next bit 9, then
bits 10 and 11. The number of planes grows when the dictionary counter passes
0x100, 0x200, 0x400, and 0x800.
The decoder MUST stop after exactly the declared expanded size and MUST also consume exactly the stored compressed body. Undefined codes, cycles, truncated planes or low bytes, output overflow, and unused trailing bytes are errors.
Resource naming conventions
Scene services construct several names:
- a scene base becomes
<base>.BIN; - art and palette commands append
.ARTand.PAL; - map level
AthroughGcombines withE,N, orD, then.MAP; - effect number
nbecomesD%03d.ABT; - music number
nbecomesMUS%03d.XMIorIBM%03d.XMIaccording to the selected music backend; and - a text translation letter plus bank letter addresses an extensionless
archive member and external companion
DDL<bank>.
Names embedded in mutable BIN data may be patched before use. Resource
lookup MUST read the current scene-buffer bytes, not a precomputed immutable
string table.
Validation policy
An engine MUST bounds-check the directory before following payload offsets and
MUST check GC before decoding. It SHOULD report the member name and directory
index in format errors. It MUST NOT accept overlapping payloads, expanded-size
mismatches, nonzero directory padding, or archive trailing data in strict
conformance mode.
Graphics and Palette Rendering
Logical display
The game renders into a 320-by-200 array of eight-bit palette indexes. The active palette maps those indexes to 256 RGB colors. A host renderer MAY use a texture, bitmap, software surface, or GPU pipeline, but scene coordinates and hit targets always remain in logical pixels.
The final image SHOULD be scaled without filtering. Aspect correction is a presentation option; it MUST NOT alter logical hit testing.
The DOS presentation surface is 640 by 400: each indexed scene pixel occupies a 2-by-2 output block. Modal glyphs are seven logical pixels high, each source pixel is likewise expanded to a 2-by-2 output block, and successive text rows have an eight-logical-pixel (16-output-pixel) pitch. A host MAY produce the same relationship by composing at 640 by 400 or by another equivalent scaling pipeline. Pointer coordinates over the doubled scene MUST be converted back to the 320-by-200 scene coordinate system, while modal text-row hit testing uses the composed panel geometry.
PAL resources
A PAL member is exactly 768 bytes: 256 consecutive red, green, blue triplets.
Every component is in the range 0 through 63. There is no header.
To produce an eight-bit host component while preserving both endpoints, use:
component8 = (component6 << 2) | (component6 >> 4)
Loading a palette replaces all 256 base entries. Scene effects may subsequently modify a range. The engine MUST retain an editable current palette rather than re-reading the immutable resource for every frame.
ART resources
An ART member begins with an array of 12-byte frame descriptors:
| Offset | Size | Type | Meaning |
|---|---|---|---|
0x00 | 2 | signed | Horizontal origin or anchor |
0x02 | 2 | signed | Vertical origin or anchor |
0x04 | 2 | unsigned | Width in pixels |
0x06 | 2 | unsigned | Height in pixels |
0x08 | 4 | unsigned | Absolute pixel-data offset in this member |
There is no explicit frame count. The first descriptor’s pixel offset marks the end of the descriptor array, so:
frame_count = first_pixel_offset / 12
Each frame owns exactly width * height row-major palette indexes. Pixel
blocks are contiguous in descriptor order and fill the remainder of the
member without padding. A parser MUST require a nonzero descriptor count,
12-byte table alignment, in-range offsets, contiguous blocks, and an exact end
at resource length.
Origins are signed anchor adjustments, not necessarily absolute screen positions. A sprite may therefore begin left or above its logical entity coordinate.
Drawing modes
The scene runtime supplies frame number, loaded-art slot, X, Y, 8.8 scale, and
render flags. Frame numbers in display records are one-based; zero suppresses
drawing. The renderer subtracts one before indexing an ART descriptor.
Two drawing modes are required:
- opaque: copy every source index, including zero;
- transparent-zero: skip writes for source index zero.
Render-flag bits 0 and 1 flip the two image axes. Scaling uses 0x0100 as
native size, but the stored word is an inverse 8.8 divisor rather than a
magnification factor. For each axis the reference renderer computes:
scaled_size = trunc(source_size * 256 / scale)
scaled_origin = trunc(signed_origin * 256 / scale)
Thus 0x0200 draws at half size and 0x0080 draws at double size. A zero
scale, or a scale large enough to reduce either dimension to zero, suppresses
the render slot. Division of a negative origin truncates toward zero.
Without reflection, the destination origin is anchor + scaled_origin.
Reflection also reflects the signed origin about the supplied anchor:
normal_left = anchor_x + scaled_origin_x
reflected_left = anchor_x - scaled_origin_x - scaled_width
normal_top = anchor_y + scaled_origin_y
reflected_top = anchor_y - scaled_origin_y - scaled_height
The renderer then reverses the corresponding source axis and clips to the
logical viewport. Applying the normal origin formula before merely reversing
the pixels is incorrect. LOGO.BIN depends on this distinction: its left
dome half is frame 4 reflected about X=303.
Composition and display records
Every display definition reserves a stable render slot in the mixed display list. Direct-object, animation, and scene-thread controllers update their own reserved slots even though their update routines run separately. Final scene composition paints the active slots in increasing display-list order; it MUST NOT regroup them by controller family. A later direct scenery record can therefore occlude an earlier animation or moving actor.
LOGO.BIN relies on this rule for its oval aperture. Its moving RUN.ART
actor uses an earlier display slot than three direct dome and bridge pieces.
Those later pieces cover the actor while it enters from the left and exits to
the right. This is ordinary display-list occlusion, not a geometric ellipse
clip operation.
Direct entries select an art slot with the low seven bits of their slot byte. Slot bit 7 hides the entry. An entry with a hidden slot or frame zero does not draw.
Animation entries obtain the same frame, art, position, scale, and flip values from the current nine-byte animation step. UI overlays, text, and the pointer are drawn by their active interface after scene composition. The pointer mask specified in Input and user interface is the final framebuffer layer.
Font styles
STUFF.ART frame 0 stores the three-valued proportional font atlas. Text
rendering maps source values 0, 1, and 2 through one of ten palette-index
triplets:
1: 1, 7, 3 2: 1, 37, 4
3: 16, 15, 17 4: 15, 84, 84
5: 0, 64, 70 6: 0, 32, 37
7: 15, 86, 90 8: 15, 74, 69
9: 15, 32, 36 10: 15, 1, 8
The Computer Bible encounter prompt uses style 7 for L cyber-lie text,
style 9 for P paraphrases, and style 10 for * victim-conversation text.
Palette operations
Scene programs can:
- replace the complete palette;
- fill an inclusive signed palette-adjustment range from a script variable;
- advance a signed phase and rotate an inclusive palette range;
- schedule palette updates; and
- start a blackout effect before a transition.
Range endpoints are inclusive. The rotate operation wraps its phase within the selected range. A blackout makes the next palette update all black and advances the effect’s countdown. Palette changes SHOULD be applied at frame boundaries to avoid partially updated presentation.
The F10 quick-save path uses a one-update blackout without a scene transition. It presents the existing indexed framebuffer through an all-black palette, then restores the current scene palette on the following update.
Text and UI graphics
Dialogue frames, action labels, maps, status icons, and most large UI elements are ordinary artwork or scene-driven compositions. Text strings are CP437.
STUFF.ART frame 0 is the shipped proportional font atlas. It is 257 by 14
pixels and contains two seven-row strips. It uses only source values 0, 1, and
2; the text style maps those values to three current-palette indexes. Glyphs
cover bytes 21h through 7Fh. Glyph 21h starts at atlas offset zero. Each
subsequent glyph begins after the preceding glyph’s width plus one pixel. At
glyph index 40h (byte 61h, lowercase a) the offset resets to the start
of atlas row 7. Space and control bytes draw nothing and advance three logical
pixels.
The executable supplies these 95 glyph widths, in byte order 21h through
7Fh:
1 3 5 5 5 1 1 2 2 5 3 2 3 1 3 4
3 3 3 3 4 4 3 4 4 1 2 3 3 3 4 4
4 4 4 4 3 3 4 4 3 4 4 3 5 4 4 4
4 4 4 3 4 5 5 5 5 3 0 0 0 0 0 0
4 4 4 4 4 4 4 4 1 4 4 1 5 4 4 4
4 4 4 3 4 5 5 5 4 3 0 0 0 0 0
The destination advance for a printable glyph is its width plus one logical pixel. Wrapping MUST measure those proportional advances against the logical wrap-width byte, not count fixed character cells. All seven source rows and all three mapped source values are drawn; source value zero is a style color, not transparency.
The recovered text-style triplets are:
| Style | Source 0 | Source 1 | Source 2 |
|---|---|---|---|
| 1 | 1 | 7 | 3 |
| 2 | 1 | 37 | 4 |
| 3 | 16 | 15 | 17 |
| 4 | 15 | 84 | 84 |
| 5 | 0 | 64 | 70 |
| 6 | 0 | 32 | 37 |
| 7 | 15 | 86 | 90 |
| 8 | 15 | 74 | 69 |
Ordinary Captain Bible and character dialogue uses style 2, adversary dialogue uses style 7, unselected choice rows use style 1, and the selected choice uses style 2.
Action captions are also shipped artwork. In STUFF.ART, frame 28 is
SELECT and frame 29 is CONTINUE; their descriptors are respectively
(-12,-3,24,7) and (-17,-3,35,7). The frame origin is applied to the label
anchor, source index zero is transparent, and every retained source pixel is
expanded to the same 2-by-2 output block as other logical artwork. A
shipped-game implementation MUST use this atlas, width table, style mapping,
and label artwork rather than a host or BIOS font.
The persistent status row is drawn from the same resource: Computer Bible is
frame 4, Map is frame 32, the five Faith states are frames 22 through 26, the
five acquired-power icons are frames 17 through 21, and the upper-right disk
indicator is frame 11. Their positive descriptor origins are absolute logical
screen placements when drawn with anchor (0,0).
Sound Effects and Music
Audio model
Digital effects and music are independent channels with separate enable settings. Scene commands can prepare, start, and wait for an effect and can replace the current music sequence. Preparing effect zero releases the retained sample and stops any playback. A host implementation MAY decode into any native sample or synthesizer representation.
Disabling effects MUST make subsequent effect-play commands silent, but MUST NOT stop an effect which is already playing or release its retained sample. While effects are disabled, a wait command uses the 100-unit silent fallback rather than waiting for that physical instance. Audio playback and completion tracking continue while a modal interface pauses the scene scheduler, so re-enabling effects before resuming can observe whether the instance has finished. Disabling music stops or suppresses music without disabling effects. Re-enabling music restarts the currently registered sequence when one exists. Requesting the already current nonzero music identifier is a no-op; requesting identifier zero stops music.
Returning from a scene while music is enabled is also a music-zero request. The active sequence MUST stop and its registered identifier MUST be cleared before the successor scene begins, so that the successor can start even the same numbered resource afresh. When music is disabled, that boundary request is suppressed just like a scene opcode: it MUST NOT discard the retained identifier. Re-enabling music can therefore restart the retained sequence until an enabled boundary or a different enabled music request replaces it.
Effect naming and playback
Effect identifier n selects D%03d.ABT. Opcode 57 prepares that member at
the supplied rate; all nonzero calls in shipped scenes use 9,000 Hz. It stops
the active effect, releases the preceding decoded buffer, decodes the new
member, and asks the digital driver to preformat it, but does not begin
playback. Preparing the already retained identifier is a no-op. Identifier and
rate both zero stop playback and release the retained sample rather than
loading D000.ABT.
Only one retained digital effect is required. Opcode 58 stops any active
instance, resets completion state, and starts or restarts the retained
preformatted sample. This two-command contract is visible in shipped sequences
such as LOGO.BIN, which prepare D003.ABT with 57 and then start it with
58.
The retained sample is scene-owned. Scene initialization stops active playback
and releases the prepared buffer before the new scene program runs. Starting a
new session performs the same release. An effect prepared by one scene MUST
therefore not remain available to opcode 58 in its successor.
With usable digital playback, the wait operation yields and retries while playback remains active. Without usable playback, the wait operation subtracts 100 from the calling script thread’s delay and advances past the command. The scheduler therefore resumes that thread after approximately 35 ms at 2,880 timer units per second. This delay begins at the wait command; it is not a global timer started by the prepare command.
The digital-audio fallback branch jumps if the driver is absent, effects are disabled, driver-state bit 0 is clear, or the explicit fallback word is nonzero. It falls through only when a driver is present, effects are enabled, the state bit is set, and the fallback word is zero. An implementation without a digital playback backend MUST take the branch.
ABT header
An ABT effect begins with nine bytes:
| Offset | Size | Meaning |
|---|---|---|
0x00 | 2 | Decoded sample count |
0x02 | 2 | Playback rate in hertz |
0x04 | 1 | Samples emitted by each delta block |
0x05 | 1 | Codec identifier |
0x06 | 2 | Auxiliary value; consumed but not used |
0x08 | 1 | Initial unsigned PCM sample |
The supplied effects use codec 2, 32 samples per delta block, and 9,000 Hz. The decoder MUST use the header fields rather than assuming those population values.
ABT command stream
The initial sample is output first. Commands then produce samples until the declared count is reached:
| Control form | Meaning |
|---|---|
| bit 7 set | Output (control << 1) & 0xFF as an absolute sample. |
| bit 7 clear, bit 6 set | Repeat the preceding sample control & 0x3F times. |
| bits 7 and 6 clear | Decode an adaptive delta block. |
For a delta block, bits 4 and 5 select mode and the low nibble plus one is the
step. Mode 1 uses one-bit codes and deltas [-step, +step]. Mode 2 uses
two-bit codes beginning at -2 * step. Modes 0 and 3 use four-bit codes
beginning at -8 * step. Build each table by adding step, skipping zero,
and retaining signed-byte wraparound.
Packed codes are read most-significant bits first. Add each signed delta to the preceding output sample and clamp the result to 0 through 255. Stop only when the declared output count has been produced. Truncation, output overrun, impossible block geometry, count mismatch, or unused input is a format error.
The result is unsigned eight-bit mono PCM at the header rate.
XMIDI resources
Music identifier n selects either MUS%03d.XMI or IBM%03d.XMI according
to the configured music family. The DOS startup selects the IBM family only
when bytes 0xD2..0xD4 of the installed SOUND.2 music driver are the ASCII
marker IBM; otherwise it selects MUS. A native engine using an original
installation SHOULD apply the same marker. Every supplied sequence contains:
FORM XDIR
INFO
CAT XMID
FORM XMID
TIMB
EVNT
IFF chunk sizes are big-endian and padded to even boundaries. The two-byte
sequence count inside INFO is little-endian. Supplied files declare one
sequence and contain one FORM XMID.
TIMB is an even-length array of patch/bank byte pairs. EVNT uses XMIDI’s
MIDI-like representation:
- bytes below
0x80before an event are additive delays; - channel statuses have their standard fixed parameter counts;
- note-on adds a variable-length duration after note and velocity;
- system-exclusive and meta events use variable-length payload sizes; and
FF 2F 00ends the track.
An optional zero pad may follow end-of-track. Parameter byte 0xFF occurs in
valid supplied data and MUST NOT be rejected by an over-strict generic MIDI
validator.
A modern engine MAY convert XMIDI events to another sequencer format or play them directly. Event order, delays, durations, program/bank choices, looping or replacement behavior, and the distinction between the MUS and IBM families must remain compatible.
XMIDI additive delay and duration units use a 120 Hz timebase. Music loops
after the sequence completes and remains independent of the digital-effect
channel. A native synthesizer MAY use the installed SOUND.4 AIL
two-operator timbres, a General MIDI sound set, or another host representation;
exact analog OPL waveforms are not required.
Text, Verses, and the Study Bible
Text architecture
Gameplay text is divided between translation-specific verse indexes inside
DD1.DAT and translation-independent external companion files. The engine
joins them into logical text records when a bank is loaded.
Four translations are identified by one letter:
| Letter | Index | Translation |
|---|---|---|
K | 0 | King James Version |
N | 1 | New International Version |
R | 2 | Revised Standard Version |
T | 3 | The Living Bible |
L MAY be accepted as an alias for translation index 3 in command-line
configuration. Text is CP437 and NUL-terminated unless a byte span supplies an
explicit boundary.
Banks
Bank letters are A through G and R. An extensionless archive name joins
translation and bank, for example NA. The companion is external file
DDL<bank>, for example DDLA.
There are 319 logical verse records per translation:
| Bank | Records | Companion length |
|---|---|---|
| A | 47 | 10,065 |
| B | 40 | 8,253 |
| C | 46 | 4,020 |
| D | 46 | 14,973 |
| E | 42 | 10,489 |
| F | 46 | 10,257 |
| G | 44 | 9,993 |
| R | 8 | 696 |
Verse-index format
Each ordinary record is:
| Relative offset | Size | Meaning |
|---|---|---|
+0 | 1 | Nonzero selector used by scene logic |
+1 | 2 | Offset into the companion file |
+3 | variable | NUL-terminated `citation |
The final record is only a zero selector plus a terminal companion offset. The terminal offset MUST equal companion-file length. Companion offsets are nondecreasing; equal offsets represent records with no companion components.
The text before | is the citation and the text after it is the verse. The
selector is a lookup key, not a component type. Selectors 0xE0 and above are
mature-topic records. The runtime bank loader MUST retain them even when the
no-mature policy is active. That policy removes the corresponding encounter
and Scripture cells while loading a new world map and omits the records only
from command-line study export.
Companion format
The companion is a sequence of tagged NUL-terminated strings without a header:
| Tag | Meaning |
|---|---|
L | Cyber lie |
P | Paraphrase or lock prompt |
W | Wrong guess response |
C | Correct guess response |
E | Explanation of the correct guess |
* | Conversation with a victim |
M | Numeric or internal metadata |
For one verse record, its companion span begins at its stored offset and ends
at the next record’s offset. Parse tagged strings only inside that span. A span
may contain repeated tags. Bank F has a 26-byte E preamble before its first
indexed span; it is not associated with a verse.
Runtime records
Loading a bank creates up to 66 runtime descriptors. A clean logical descriptor contains:
- the citation and verse string;
- selector byte;
- companion offset and span;
- parsed tagged components; and
- one persistent state byte.
Every index record occupies its original descriptor ordinal. In particular,
no-mature mode MUST NOT compact the table by removing selectors E0 and
above. Original save images created under the supplied no-mature installation
still contain those descriptors, and translation changes preserve state by
descriptor ordinal.
The state byte persists while that bank is active and through checkpoint save/restore. Requesting the already active bank leaves its descriptors alone. Loading a different bank rebuilds the descriptor set and clears all 66 state bytes; restoring a checkpoint rebuilds its saved bank and reapplies the saved state bytes. Scene commands find a descriptor by selector, set or clear its state, branch on it, or copy a selected component into mutable scene memory.
The mutable-memory copy command accepts more component selectors than the
interactive prompt mapping. V copies citation - verse; a byte from 0
through 9 copies that zero-based W occurrence; and ordinary tag bytes such
as C or E copy the first matching component. These textual forms include a
terminating NUL. Selector X instead writes a shuffled byte array containing
0..W-count-1 plus literal C, stores its length in variable 27, and does not
append a NUL. The runtime performs 20 random swaps, consuming the shared
portable random stream once for the left index and once for the right index of
every swap. Successful copying sets state flag 22; a missing record or
component clears it and leaves the destination bytes unchanged.
Computer Bible
The F1 interface displays descriptors whose state marks the verse as obtained. References are ordered in Bible order. Selecting a reference shows its verse. Page controls appear when the list exceeds one screen.
Apply is available only when a scene has configured an expected selector and
component. Applying the expected descriptor sets state flag 0x14. Leaving
without that match sets flag 0x15. Both flags are cleared before each
interactive study request.
The expected record can also carry one success continuation. Opcode 4F
associates it with a navigation node, while opcode 51 associates it with a
BIN target and scheduler thread. Applying the matching record starts that
movement or command stream after the modal browser closes. Opcode 15
selects a record while clearing both continuations, and opcode 50 clears the
active selection. A host must not merely set the result flag and discard these
configured continuations.
Prompt component selection
Scene opcode 0x7D stores a prompt byte and reads the expected selector from
the variable encoded by its word operand. The prompt byte chooses:
| Value | Component shown |
|---|---|
0x00, 0x09 | No prompt |
0x2A (*) | Victim-conversation text |
0x64 | P paraphrase text |
| other nonzero | L Cyber-lie text |
The prompt MUST be resolved from the descriptor selected by the configured selector even when that descriptor has not been obtained. Obtained state controls which records can be selected as answers; it does not hide the question. The study screen compares by descriptor selector. It does not compare rendered text strings.
Display substitutions
Dialogue and study strings may contain placeholders expanded from the selected text descriptor. The scene chooses the active descriptor before display. A compatible implementation MUST preserve the distinction between verse text, citations, and companion components and must apply substitutions before line wrapping.
An ampersand (&) in a display string substitutes the selected descriptor’s
complete index string, not only its citation. The stored citation|verse
separator is rendered as space-hyphen-space, producing
citation - verse. For example, the script text Verse loaded: & displays
the verse reference followed by its full text.
The vertical bar is an internal separator in the selected record. It is not an
independent placeholder in the script’s display string. An asterisk (*)
introduces the selected companion component used by the corresponding
conversation path.
Scene-program virtual machine
Purpose and execution model
BIN resources are programs for a small scene virtual machine. A compatible
engine MUST execute these programs; it MUST NOT treat the shipped scenes as a
fixed list of hard-coded screens.
There are 62 BIN members and no generic BIN header. Most members are code
from beginning to end. CP2.BIN has code through offset 1D54 followed by a
256-byte data trailer. ROOM3.BIN has code ranges 0000..0335,
0C96..1753, and 1768 through the end, separated by reserved zero-filled
regions. Code and data offsets remain relative to the beginning of the member.
A program is addressed from byte offset zero. The instruction pointer and all
branch, call, callback, and action targets are absolute byte offsets within the
expanded resource. Integer operands are little-endian. Normal execution reads
an opcode, reads exactly the operands declared below, performs its effect, and
continues at the following byte. Opcode values outside 0x01 through 0x91
are invalid.
The VM owns 100 signed 16-bit variables. An encoded variable operand is the
even byte offset 2 * index, not the index itself. Valid variable operands are
therefore even values from 0 through 198. Arithmetic results use 16-bit word
semantics. Comparisons and division are signed where stated. An implementation
MUST diagnose an odd or out-of-range variable operand.
The following operand notation is used:
| Code | Encoding |
|---|---|
B | Unsigned byte. |
H | Unsigned little-endian word; its interpretation may be signed. |
z | Inline NUL-terminated CP437 string. |
p | Inline NUL-terminated CP437 string, or FF followed by a word giving the resource-relative offset of a NUL-terminated string. |
9 | One nine-byte animation-step record. |
s | One additional word if the immediately preceding word is negative when interpreted as signed. |
Only p recognizes the FF reference form. The z strings in opcodes
01, 0D, 4D, and 6D are always inline. In BHs, a nonnegative word is
the callback target and uses default thread -1; a negative word stores its
negation as the thread slot and the following word is the callback target. A
command that suspends before consuming its operands MUST leave its instruction
pointer at that command so it can be retried.
Complete instruction set
Names in this table are specification mnemonics, not names that an implementation must expose. “Unused” means no shipped scene program invokes the command; its byte layout and direct effect are nevertheless part of the full-interpreter compatibility profile.
| Op | Schema | Mnemonic and required effect |
|---|---|---|
01 | z | load_art: load the named ART member into the next art slot. |
02 | BHHH | create_scene_thread: create a scene thread from slot, X, Y, and scale and add its display object. |
03 | BBHHB | add_native_scale_display_object: add frame, art slot, X, Y, and flags at scale 0x0100. Unused. |
04 | BBHHHB | add_scaled_display_object: add frame, art slot, X, Y, scale, and flags. |
05 | none | return_minus_one: stop the current interpreter invocation with result -1. |
06 | H | begin_animation_sequence: declare an animation with the supplied step interval; contiguous following 07 records belong to it. |
07 | 9 | animation_step: advance over one animation record in the format defined in Scene runtime. Associate it with the most recent 06 only when one exists; records before the first 06 are valid unowned inline data. |
08 | BB | start_animation: start an animation with the supplied animation slot and mode. |
09 | B | stop_animation: stop an animation and release its render slot. |
0A | none | wait_for_scene_thread_movement: suspend until primary movement state is 0 or 2. |
0B | BB | add_navigation_edge: append an undirected edge in the insertion order used by the bounded iterative-deepening route search. |
0C | BBp | add_scene_entry: associate an entry name with its initial/current node byte followed by its destination node byte. |
0D | zz | change_scene: request the named scene and secondary entry/segment, ending current scene execution. |
0E | none | nop: continue. Unused. |
0F | H | adjust_thread_delay: subtract the word from the current scheduler thread’s signed countdown; a resulting negative delay yields at the next command boundary until logical time makes it nonnegative. |
10 | BHHp | configure_scene_thread_action: set thread selector, X, Y, and action label; the selector starts enabled when its scene thread is created. |
11 | BHs | add_navigation_arrival_handler: map destination node to target and optional explicit thread slot using the BHs rule. |
12 | BHs | add_navigation_departure_handler: map source node to target and optional explicit thread slot using the BHs rule. |
13 | H | remove_dialogue_choice: remove the first choice whose target matches. Unused. |
14 | p | show_adversary_dialogue: show text using adversary presentation and wait for dismissal. |
15 | B | select_study_record: select a text descriptor and clear both study-result continuations. |
16 | HHH | set_palette_adjustment_range_from_variable: fill an inclusive signed brightness-adjustment range with the value of a variable and refresh the palette. |
17 | BHs | add_forward_edge_departure_handler: add a departure callback for traversal in the opcode-0B edge’s stored first-to-second order; B is the zero-based navigation-edge index. |
18 | BHs | add_reverse_edge_departure_handler: add a departure callback for traversal from the stored second node to the first; B is the zero-based navigation-edge index. Unused. |
19 | BHs | add_reverse_edge_arrival_handler: add an arrival callback for traversal from the stored second node to the first; B is the zero-based navigation-edge index. |
1A | BHs | add_forward_edge_arrival_handler: add an arrival callback for traversal in the stored first-to-second order; B is the zero-based navigation-edge index. |
1B | H | prime_primary_scene_thread_timer: store the negated value as the primary movement timer and set the transition latch. Unused. |
1C | B | enable_scene_thread_action: enable one scene thread as a selector. |
1D | B | disable_scene_thread_action: disable one scene thread as a selector. |
1E | HH | copy_variable: copy source variable to destination variable. |
1F | HH | set_variable: store immediate, then destination variable. |
20 | HH | jump_if_zero: variable, target. |
21 | HH | jump_if_nonzero: variable, target. |
22 | HHH | jump_if_variables_equal: left variable, right variable, target. |
23 | HHH | jump_if_variable_equals: variable, immediate, target. |
24 | HHH | jump_if_variables_not_equal: left variable, right variable, target. |
25 | HHH | jump_if_variable_not_equal: variable, immediate, target. |
26 | HHH | jump_if_variable_greater_than_variable: signed left variable, right variable, target. |
27 | HHH | jump_if_variable_greater_than: signed variable, immediate, target. |
28 | HHH | jump_if_variable_less_than_variable: signed left variable, right variable, target. |
29 | HHH | jump_if_variable_less_than: signed variable, immediate, target. |
2A | HH | add_variable: add source variable to destination. |
2B | HH | add_to_variable: add immediate to destination. |
2C | HH | subtract_variable: subtract source variable from destination. |
2D | HH | subtract_from_variable: subtract immediate from destination. |
2E | HH | multiply_variables: signed-multiply destination by source variable. Unused. |
2F | HH | multiply_variable: signed-multiply destination by immediate. |
30 | HH | divide_variables: signed-divide destination by source variable. Unused. |
31 | HH | divide_variable: signed-divide destination by immediate. |
32 | H | increment_variable: increment variable. |
33 | H | decrement_variable: decrement variable. |
34 | H | call: save the following offset and jump to target. |
35 | none | return: resume the saved call return offset. |
36 | B | set_text_record_state: set and select a text record. |
37 | B | clear_text_record_state: clear a text record. Unused. |
38 | BH | jump_if_text_record_set: selector, target. |
39 | BH | jump_if_text_record_clear: selector, target. Unused. |
3A | HHHp | add_action_target: target, X, Y, and label. |
3B | B | enable_action_target: set an action’s active byte. |
3C | B | disable_action_target: clear an action’s active byte. |
3D | H | jump: set the instruction pointer to target. |
3E | BH | start_scene_thread_at: freshly activate the named slot at the target, clear stale timing/call state, and run it before the caller continues. |
3F | B | wait_for_animation: continue for state 0, 5, or 6; otherwise suspend and retry. |
40 | B | set_scene_thread_motion_state: set current thread motion state; state 0 releases its normal actor render slot, while state 2 immediately performs a movement update. |
41 | none | enable_action_selection: enable screen actions for the current scene. |
42 | none | disable_action_selection: disable screen actions for the current scene. |
43 | BBHHHB | add_scaled_display_object: same contract as 04. |
44 | Hp | add_dialogue_choice: append target and text to the current choice list. |
45 | none | clear_dialogue_choices: clear all choices and dialogue state. |
46 | none | present_dialogue_choices: suspend until a choice supplies a new target. |
47 | B | set_modal_menu_selection: seed the selection consumed by the modal text-menu path. Unused. |
48 | p | show_character_dialogue: show text using character presentation and wait for dismissal. |
49 | none | request_study_bible: enter the modal study interface and suspend the thread. |
4A | none | nop: continue. Unused. |
4B | none | nop: continue. Unused. |
4C | B | fill_screen: fill all 320×200 pixels with the palette index. |
4D | z | load_palette: load named PAL member. |
4E | p | show_captain_bible_dialogue: show text using Captain Bible presentation and wait for dismissal. |
4F | BB | configure_study_navigation_success: set record selector and node entered on study success. Unused. |
50 | none | clear_study_record_selection: clear both active study selectors. Unused. |
51 | BHB | configure_study_thread_success: set record selector, target, and thread slot started on success. |
52 | B | play_music: load and start numbered music. |
53 | B | set_scene_thread_origin: initialize the primary object’s nodes and X/Y/scale from the selected opcode-02 geometry without replacing its retained destination, then start a minimum same-node controller traversal. |
54 | B | move_scene_thread_to: retain the requested navigation node as the desired destination; start a route while idle or replace only the destination while an edge remains active. |
55 | none | snapshot_state: copy live state to the retained checkpoint state. |
56 | none | nop: continue. Unused. |
57 | BH | prepare_sound_effect: stop/release the preceding sample, decode and preformat the numbered effect at the supplied rate; 0,0 only stops and releases. |
58 | none | start_prepared_sound_effect: stop any active instance and start or restart the retained preformatted sample. |
59 | none | wait_for_sound_effect: with a usable digital backend, yield and retry while playback remains active; without one, subtract 100 from the calling thread’s delay and resume after this command. |
5A | H | jump_if_digital_audio_fallback: jump when the digital driver is absent, effects are disabled, backend state bit 0 is clear, or the fallback word is nonzero. Fall through only when the first three checks indicate usable playback and the fallback word is zero. |
5B | B | set_scene_thread_direction: select one of four orientations and its render offset. |
5C | BBB | configure_captain_bible_dialogue: set Captain Bible text X, text Y, and wrap width. |
5D | BBB | configure_character_dialogue: set character text X, text Y, and wrap width. |
5E | H | set_deferred_scene_thread_target: schedule a target later started in thread slot 2. Unused. |
5F | BBB | start_linked_animation: animation, linked animation, mode. |
60 | none | nop: continue. |
61 | B | stop_scene_thread: deactivate a scheduler slot. |
62 | H | store_mouse_x: store logical mouse X in variable. |
63 | H | store_mouse_y: store logical mouse Y in variable. |
64 | H | jump_if_confirm_pressed: consume the Enter-or-click latch and jump if it was set. |
65 | BB | clear_display_object_frames: first and count; set each selected frame to zero. Unused. |
66 | BBBB | advance_display_object_frames: first, count, minimum, maximum; increment and wrap selected frame bytes. Unused. |
67 | none | request_restore_saved_game: leave the scene loop and restore selected retained state. |
68 | H | adjust_variable_1280_once: subtract 1280 if above 640, then add 1280 if below -639; do each test once. |
69 | HH | load_bin_word: immediate resource offset, destination variable. |
6A | HH | patch_bin_word_from_variable: immediate resource offset, source variable. |
6B | B | load_text_bank: replace active bank and clear all 66 descriptors. |
6C | HHHH | rotate_palette_range: inclusive minimum, maximum, signed step, phase variable; update, wrap, and map the range. |
6D | z | load_palette: same contract as 4D. |
6E | B | start_primary_scene_thread_overlay: start a resource-driven overlay on thread zero. Unused. |
6F | none | wait_for_primary_scene_thread_overlay: suspend while that overlay is active. Unused. |
70 | none | unload_last_art: release the most recently loaded art slot. |
71 | HH | load_bin_word_indirect: offset variable, destination variable. |
72 | none | yield_scene_thread: write -1 to the current scheduler delay and yield; a later controller update advances the delay and resumes at the following command. |
73 | BH | jump_if_state_flag_clear: flag, target. |
74 | BH | jump_if_state_flag_set: flag, target. |
75 | B | clear_state_flag: clear a bit in the 128-bit flag bank. |
76 | B | set_state_flag: set a bit in the 128-bit flag bank. |
77 | none | process_current_map_cell: derive scene context and adjacent interactions from the current map coordinate. |
78 | B | load_map: load level letter plus current difficulty code. |
79 | none | clear_navigation_handlers: clear scene-entry, directional-edge, node-arrival, and node-departure callback lists; preserve opcode-0B navigation edges and any movement already in progress. |
7A | HH | patch_bin_byte_from_variable: immediate resource offset, source variable; write its low byte. |
7B | H | set_current_map_cell_kind: preserve the old high nibble and OR in the source variable’s low byte. |
7C | H | set_current_map_cell_parameter_a: write current cell byte 1 from variable. |
7D | BH | configure_study_prompt: companion-text component and variable whose value is the record selector. |
7E | none | blackout_palette: schedule an immediate black-palette transition. |
7F | H | set_current_map_cell_parameter_b: write current cell byte 2 from variable. |
80 | BH | jump_if_animation_active: animation, target; jump when state is nonzero. |
81 | H | reduce_faith: apply difficulty-scaled loss unless no-combat mode suppresses it. |
82 | HH | set_variable_random_modulo: advance the shared portable random state and store its 15-bit result modulo the immediate in the destination variable. |
83 | HBH | copy_text_record_component_to_bin: record-selector variable, component, destination resource offset; set flag 22 on success and clear it when the component is absent. |
84 | HH | load_bin_byte: immediate resource offset, destination variable; sign-extend the byte. |
85 | B | hide_display_object: set the selected display object’s hidden bit. |
86 | B | show_display_object: clear the selected display object’s hidden bit. |
87 | none | normalize_map_cells: apply the progression-dependent map normalization rules. |
88 | none | clear_text_record_states: clear state byte 4 in all 66 descriptors. |
89 | none | mark_current_map_cell_explored: set bit X in exploration row Y. |
8A | BH | jump_if_animation_finished: animation, target; jump for state 0, 5, or 6. |
8B | none | consume_random_text_record: in difficulty state 2, choose a starting descriptor with the shared random result modulo the loaded descriptor count, scan cyclically to a set state byte, clear it, and store 3000 at variable offset 5C. Unused. |
8C | H | jump_if_no_combat: jump to target when installation no-combat mode is set. |
8D | H | jump_if_file_missing: test active player-prefix plus save suffix and jump on open failure. |
8E | none | randomize_flags_23_to_27: draw a random byte and copy its bits 0 through 4 into state flags 23 through 27. |
8F | HH | and_variables: destination AND source variable. Unused. |
90 | HH | and_variable: destination AND immediate. Unused. |
91 | HH | set_variable_random_byte_modulo: divisor, destination variable; draw a random byte and store it modulo the immediate divisor. |
Shared random bytes
Each execution of opcode 8E or 91 draws a random byte in the inclusive
range 0..255. It advances the shared portable generator once and uses the
low byte of the returned 15-bit value. These commands have no map-coordinate,
world-state, or host-memory dependency.
Mutable program data
Opcodes 69, 6A, 71, 7A, 83, and 84 deliberately read or modify
bytes in the expanded current BIN resource. Each scene instance therefore
needs a writable resource buffer. Changes last for that loaded scene instance
only. Bounds MUST be checked before every access. The shipped programs use
this feature to construct resource names and copy text components, so an
engine that keeps program bytes immutable is not compatible.
Calls, threads, and suspension
Calls and scheduler threads are distinct. 34/35 provide subroutine
control flow inside one command stream. Scene-thread commands create or resume
additional instruction pointers. Waiting commands yield without busy-waiting;
the engine retries them in later update cycles after animations, audio, input,
or modal UI state has changed.
An implementation MAY use coroutines, state machines, or ordinary records. It MUST preserve externally visible ordering: program mutations completed before a suspension are visible to all later threads and rendering in that update cycle.
Scene runtime
Scene state
Entering a scene creates a fresh scene-local runtime while preserving global
game state. The engine MUST load the requested BIN, reset its display list,
action list, dialogue choices, navigation graph and callbacks, animations,
and scene threads, then execute the program from offset zero. Art and palette
loads performed by the program populate that scene’s rendering state.
Scene changes are deferred requests. Once requested, the current update MUST stop accepting scene actions, finish safe cleanup, and enter the named scene with the named secondary entry string. Resource names are case-insensitive.
Display objects
The scene maintains an ordered display list. A logical display object has:
- a type identifying direct, thread-driven, or animation-driven rendering;
- X and Y coordinates in the 320×200 logical space;
- a 16-bit scale where
0x0100is native scale; - render flags, including horizontal and vertical reflection;
- an ART slot and frame number; and
- a hidden state.
Direct, thread, and animation declarations append objects to one mixed display list. Their controllers update the frame and transform in the corresponding stable slot, and all active slots MUST be painted in global list order. Hiding an object preserves the record and its animation state but omits its pixels. Showing it makes the current state visible again.
The on-disk animation-step record is exactly nine bytes:
| Offset | Type | Meaning |
|---|---|---|
| 0 | u8 | One-based ART frame; zero suppresses drawing. |
| 1 | u8 | ART slot. |
| 2 | i16le | X. |
| 4 | i16le | Y. |
| 6 | u16le | Scale. |
| 8 | u8 | Render flags. |
An animation definition consists of the interval word from opcode 06
followed by its contiguous 07 records. Runtime animation state MUST retain
the first and current step, interval, optional linked animation, mode/state,
and display object. Starting resets the selected sequence as required by its
mode; stopping releases its visible object. The mode byte is also the live
state observed by the VM.
Opcode 07 itself only advances over a nine-byte record. An implementation
MUST accept such records before any opcode 06 and leave them unowned rather
than rejecting the scene. CP1.BIN begins with six of these records after a
direct display object and before its first animation definition. Later
animation definitions continue to own the contiguous records following their
respective opcode 06.
Its complete resource-visible transition table is:
An unlinked animation renders the current step’s absolute X, Y, and scale. A linked animation recursively resolves its parent animation and applies the child’s signed delta from the child’s first step:
x = parent.x + (current.x - first.x)
y = parent.y + (current.y - first.y)
scale = parent.scale + (current.scale - first.scale)
The arithmetic has 16-bit wrapping behavior. Frame, ART slot, and render flags still come from the child’s current step. A link whose parent is stopped or otherwise not renderable suppresses the child as well. Implementations MUST reject or safely suppress invalid and cyclic links rather than recurse without a bound.
| Start mode | Initial step | Direction | End-of-sequence behavior |
|---|---|---|---|
| 1 | First | Forward | Set state 0 and release the visible render slot. |
| 2 | Last | Backward | Set state 0 and release the visible render slot. |
| 3 | First | Forward | Wrap to the first step, retaining state 3. |
| 4 | Last | Backward | Wrap to the last step, retaining state 4. |
| 5 | First | None | Remain on the first step in terminal state 5. |
| 6 | Last | None | Remain on the last step in terminal state 6. |
| 7 | First | Forward | Back up to the last valid step and change to state 8. |
| 8 | First | Backward | Retain the first step and change to state 7. |
| 9 | First | Forward | Remain on the last step and change to terminal state 6. |
| 10 | Last | Backward | Remain on the first step and change to terminal state 5. |
Modes 7 and 8 therefore form a ping-pong pair. A forward overrun in mode 7 retains the last step before changing to mode 8; a backward underrun in mode 8 retains the first step before changing to mode 7. Mode 8 is unusual because an explicit start begins at the first step even though its first attempted move is backward. Starting a mode renders its initial step immediately and initializes its countdown from the negated sequence interval. Each animation update adds the logical tick increment; when the countdown becomes positive, it performs the table’s step and subtracts the interval again.
State values 0, 5, and 6 are terminal for the VM’s wait and finished predicates, even though states 5 and 6 retain and render their endpoint. Stopping any mode sets state 0 and releases its visible render slot. The precise subdivision of the remaining private timing storage is not exposed and need not be reproduced as a host data layout.
Scene threads and movement
At least ten independent scene command streams MUST be supported because the shipped content addresses ten scheduler slots. A thread retains its active or suspended status, next command offset, delay, motion state, navigation node, direction, position, selector label and coordinates, and any active movement or overlay.
Each update decreases eligible delays, resumes ready command streams, advances
movement and animations, and resolves completed arrival/departure callbacks.
The reference supplies the elapsed timer delta to these controllers; it does
not add one merely because one host frame was presented. The controller clock
runs at 2,880 units per second. Its normal interrupt contribution is 24 units,
and the elapsed delta supplied to a single update is capped at 400. Movement
steps use an interval of 20 (about 6.94 ms), animation steps commonly use 40
(about 13.89 ms), and ordinary scheduler delays such as 3,000 last about 1.04
seconds. A host may
subdivide or batch updates, but it MUST accumulate elapsed time at this rate
and preserve command-boundary ordering. While the opcode-8B transient-status
timer is active, an outer controller boundary is itself behavioral because it
controls shared-random-stream consumption.
A real-time graphical frontend MUST target the reference’s normal controller
and presentation cadence of approximately 120 Hz. It must compose and submit
one visible frame after each ordinary boundary, whose elapsed delta is normally
24 units. Small host-clock jitter may place neighboring deltas around 24, but a
frontend MUST NOT deliberately use a 60 Hz cadence whose routine delta is
about 46 units. COMBAT4.BIN, for example, uses 30- and 40-unit animation
intervals, so such batching can advance past an authored frame before that
frame is ever presented.
After a genuine host stall, the reference can accumulate several interrupts, cap their combined delta at 400, advance through multiple animation records, and compose only the resolved state. A graphical host MAY reproduce that exceptional catch-up behavior. A non-visual host MAY batch simulation and omit intermediate presentation entirely, but that mode does not establish visual conformance. Comparison runs MUST supply the same sequence of controller boundaries and elapsed deltas; they MUST NOT coalesce or subdivide those logical updates merely to change presentation frequency. Movement must be smooth enough for the original art and action coordinates, and waits must not complete before their controller reaches the states named in the bytecode chapter.
The scheduler delay is a signed countdown. A newly runnable stream has delay
zero. Opcode 0F subtracts its operand from that value, commonly making it
negative, and yields that invocation. The outer update adds the logical
elapsed-tick increment to negative delays and does not enter that stream again
until its delay is nonnegative. Eligibility is checked on invocation entry,
not between every pair of commands. Changes that a synchronous same-slot
callback makes to the shared active, suspended, or delay state therefore do
not pause the already-running outer invocation; only a yield from the outer
invocation returns its cursor to the scheduler. This is how nested callbacks
preserve DOS command ordering while palette fades and input-polling loops
remain paced.
Starting a thread at a new target is a fresh invocation, not a resume. The
engine replaces the cursor, marks the slot active and unsuspended, clears its
previous delay and bytecode return stack, and makes it immediately eligible.
Opcode 3E executes the named slot synchronously until it yields before
continuing the caller. Action selection, navigation callbacks, and successful
study callbacks apply the same fresh state; modal dismissal instead resumes
the suspended invocation and therefore preserves its return stack and delay.
When multiple navigation callbacks match, each fresh invocation MUST run
synchronously through its first yield or return before the next record is
examined. A later callback assigned to the same slot may consequently replace
an invocation left runnable by an earlier callback. The cursor published by a
later yield of the resumed outer invocation likewise replaces the nested
callback cursor, while their changes to the shared delay accumulate.
Scene-thread movement artwork
Opcode 02 records are navigation-node geometry, not independent visible
actors. The primary movement controller uses the source node’s display slot
and renders one global RUN.ART sprite while traversing an edge. Opcode 53
sets both current-node identifiers, snaps X, Y, and scale to that node, and
starts the ordinary controller with a minimum same-node traversal without
changing the retained desired destination. It MUST NOT put the actor directly
into idle state. The traversal preserves the previous walking phase,
suppresses turn-pose selection because its distance is one step, and
dispatches the destination-node arrival callback when it completes. A
following opcode 54 therefore replaces the desired destination while this
initial traversal remains active. Merely changing the identifiers without the
same-node controller lifecycle is incompatible.
For one edge, let dx, dy, and ds be the differences in X, Y, and scale,
and let average_scale = (start_scale + end_scale) / 2. The reference derives
the number of 20-unit interpolation steps as:
planar = (max(abs(dx), abs(dy)) + min(abs(dx), abs(dy)) / 2)
* average_scale / 256
depth = abs(ds)
steps = max(1, max(planar, depth) + min(planar, depth) / 2)
Coordinates and scale use fixed-point increments across those steps. The
horizontal direction is right when dx > 0 and left otherwise. It switches
to the away/toward orientation when 2 * abs(ds) exceeds
abs(dx) * average_scale / 256; nonnegative ds selects away and negative
ds selects toward.
The walking phase advances by 28 modulo 0x0600 per interpolation step. Its
high byte selects one of six positions. Direction offsets are 0 for left and
right, 24 for away, and 12 for toward. An idle actor adds six to that index.
The resulting index selects a one-based RUN.ART frame from this exact table:
01 02 03 04 05 06 13 13 13 13 13 13
07 08 09 0A 0B 0C 14 14 14 14 14 14
0D 0E 0F 10 11 12 15 15 15 15 15 15
Left-facing horizontal frames set horizontal reflection; the other three
orientations do not. A moving actor remains at the display-list position of
the opcode-02 record that created its controller slot. Later display records
can therefore occlude it.
The walking phase MUST remain continuous when the controller reaches an
intermediate node and starts another edge. It resets to 0x0200 only when the
controller determines that no further edge is required or available and
enters idle state.
Changing orientation can insert a stationary turn pose before interpolation.
A left/right horizontal reversal uses one-based RUN.ART frame 20. A direct
toward/away reversal uses frame 19. Both poses block coordinate and walking
phase advancement for 224 controller units; the override frame is displayed
while that countdown is nonzero. Horizontal-to-depth and depth-to-horizontal
changes do not use either special pose. A minimum one-step traversal also
does not select a turn pose.
Navigation edges are undirected. Route selection is iterative-deepening depth-first search for paths of one through 20 edges. Each depth attempt scans edge records in insertion order and, within one record, tests traversal from the stored first endpoint before traversal from the second endpoint. The search has no visited-node exclusion. A route requiring more than 20 edges is unreachable to the controller even if the graph contains a longer path.
A movement request retains one desired destination. It starts as 0xFF,
persists across scene replacement, and is reset to the current node only when
the controller resolves final idle or an ordinary request has no route. If
interpolation is already active, a different request replaces only that
destination; it MUST NOT reset the current edge, its position, timing, walking
phase, turn pose, or callbacks. A request equal to the retained destination is
a no-op. After completing an edge and its arrival callbacks, the controller
searches again from the reached node toward the latest desired destination.
Thus callbacks can change the graph or desired destination before the next
search.
For each traversed edge, the controller updates the current node and processes
arrival callbacks before replanning. The forward and reverse callback families
distinguish traversal relative to the edge’s stored node order. The byte
operand of opcodes 17 through 1A is the zero-based index of the opcode-0B
edge record, not a node number. For each edge, opcode 17 is forward
departure, 18 is reverse departure, 19 is reverse arrival, and 1A is
forward arrival. The engine dispatches the matching directional edge callback
and node callback in phase-specific order:
- Initial movement searches, runs the source-node departure, runs the directional edge departure, and then initializes the selected edge.
- State-five continuation searches, initializes the selected edge, runs its directional edge departure, and then runs the source-node departure.
- Edge completion first makes the interpolation state inactive for movement requests, then runs the directional edge arrival and destination-node arrival callbacks. A first arrival callback may therefore start a fresh edge while a later callback retargets that active edge.
If no route exists, the request MUST leave the object at its current node but
still run a minimum same-node controller traversal before becoming idle. A
request whose destination is already the retained desired node is a no-op.
Opcode 53 leaves that retained destination unchanged.
Opcode 79 clears navigation callback registrations only. It MUST preserve the
navigation graph and any movement already in progress.
After the initial offset-zero invocation has built the entry, edge, and
callback tables and returns, scene entry dispatch matches the requested
secondary entry string case-insensitively. The matching opcode-0C record’s
first node byte is the initial/current node and its second byte is the
destination. That traversal uses the ordinary departure and arrival callback
machinery; it is not a direct jump to a hard-coded scene-specific target. For
example, the startup seg entry in LOGO.BIN traverses its declared 3–4
edge and dispatches the arrival handler attached to node 4.
Opcode 05 terminates the offset-zero invocation before this traversal
begins. Starting a nonempty entry route MUST NOT let that loader invocation
fall through to the bytes after opcode 05; only a configured callback may
reactivate a thread at its target. A departure callback activated by entry
dispatch runs as a fresh invocation after the loader has returned and MUST NOT
be deactivated by that loader’s opcode 05. Otherwise a departure callback
can be lost, or a destination arrival handler can run once as fallthrough and
again when movement completes.
Actions
An action target is logically:
| Field | Type |
|---|---|
| Program target | u16 absolute BIN offset |
| X, Y | u16, u16 logical coordinates |
| Label | string reference |
| Active | boolean |
The engine displays or otherwise exposes active actions when global action selection is enabled. Selecting an action resumes execution at its target. Thread selectors use the same player-facing interaction model but are attached to a moving scene object. The engine MUST preserve list order for keyboard cycling and deterministic hit testing.
Each scene-thread motion state also controls that thread’s normal actor render
slot. State zero releases the slot and suppresses the ordinary actor sprite.
This permits a scene animation to supply a temporary replacement pose without
drawing both poses. Nonzero states allow the normal actor controller to submit
its current frame. State two additionally requests an immediate motion update
when written by opcode 40.
A newly entered scene starts with global action selection enabled. A newly
created scene-thread target is also enabled. Opcode 10 optionally assigns a
selector label and separate selector coordinates; opcodes 1C and 1D
enable or disable that individual target, and opcodes 41 and 42 gate all
targets for the current scene.
New opcode-02 records initialize their separate selector anchor to
(30000,30000). Opcode 10 replaces that sentinel with the authored
selector X/Y and string. A thread which never receives opcode 10 therefore
cannot enter proximity selection, hover rendering, keyboard matching, or the
Space cycle, but remains in pointer hit-testing through its opcode-02
navigation geometry. The current unlabeled node is not eligible. This permits
a clickable passage to be represented solely by navigation geometry.
An empty opcode-10 selector string prevents keyboard matching and hover
artwork. Space-cycle eligibility itself follows the enabled state and
on-screen selector coordinates rather than testing the string. Ordinary
opcode-3A actions still require their authored selector string.
This initialization is observable in FIRST.BIN: it configures node 2 at
(282,124) with selector .r but never executes 1C or 41 on the initial
path. After the entry animation completes, that target must therefore be
available by pointer activation, the Right arrow, and the selector’s keyboard
key. Treating either initial enable state as false leaves the first explorable
scene inescapable.
GANTRY.BIN demonstrates the unlabeled case. Nodes 2 and 3 are both at
(190,70), the seg entry settles on node 2, and node 3 has the arrival
callback that enters CP1. Neither node has an opcode-10 label. Excluding
the current node and retaining table order makes a click near the opening
select node 3 and enter the Unibot.
Palette, audio, and modal waits
Palette mapping and blackout effects are advanced as part of the scene update.
Loading a palette resets a 256-entry source-index map to identity and clears a
signed adjustment word for every output index. Palette output index i is
computed component-wise as:
clamp(base_palette[mapping[i]] + adjustment[i], 0, 63)
Opcode 16 fills an inclusive adjustment range. Opcode 6C advances and
wraps its phase inside the inclusive range, then maps successive output
indexes to successive source indexes beginning at that phase and wrapping at
the range end. Rotation changes this mapping; it does not destructively rotate
the already adjusted RGB output.
Audio waits complete on actual playback completion when audio is enabled and on an equivalent logical timer when it is muted or unavailable. This prevents muting sound from changing scene progression.
Dialogue, choice menus, study screens, pause screens, save/restore screens, and help/status screens are modal. While one is active, ordinary scene actions do not advance. Animations and audio MAY continue where that does not alter program outcomes. Dismissing a modal interface resumes the suspended command at the specified continuation.
The standalone Computer Bible, map, Faith/power notices, options, and
save-name editor are blocking top-level interfaces: the scene controller is
not invoked while they remain open. A study screen requested by scene opcode
49 instead belongs to the requesting command stream; that owner remains
suspended until Apply or Off supplies the result.
Capacity and validation
A clean implementation SHOULD use dynamically sized collections. It MUST support every table size used by the shipped resources, including 66 text descriptors and ten scene-thread slots. It MUST reject invalid resource offsets, unterminated strings, impossible frame references, and table growth that would exhaust its configured safety limits with a descriptive error, rather than corrupting adjacent state.
World model and exploration
Levels and maps
The adventure contains seven levels, identified A through G, and three
difficulty variants: Easy, Normal, and Difficult. A map member is named
by level letter plus difficulty code and .MAP, for example CE.MAP.
Each map is exactly 768 bytes: a headerless, row-major 16×16 grid of three-byte mutable cells.
cell_offset = 3 * (16 * y + x)
+0 packed connections and location kind
+1 parameter A
+2 parameter B
Coordinates are 0 through 15. In byte zero, the high nibble is a connection
mask: 10 up, 20 down, 40 left, 80 right. The low nibble is interpreted
according to whether the connection mask is zero.
The loaded grid is live game state, not a read-only map asset. Encounters, unlocked doors, exposed stations, and cleared locations modify it. Both live and checkpoint copies are persisted.
Hall cells
When at least one connection bit is set, known low-nibble kinds are:
| Kind | Feature and behavior |
|---|---|
0 | Empty connected hall. |
1 | Macho Cyber; enters combat 1. |
2 | Armored Cyber; enters combat 2. |
3 | Mantis Cyber; enters combat 3. |
4 | Snake Cyber; enters combat 4. |
5 | Spider Cyber; enters combat 5. |
6 | Leech-covered Scripture station; enters combat 6. |
7 | Zapper Cyber; enters combat 7 and can drain faith while passed. |
9 | Hidden Spider trigger; a successful trigger changes it to kind 5. |
A | Available Scripture station; parameter A selects its verse. |
B | Cleared encounter. |
E | Level exit. |
Kind 8 is absent from shipped connected cells. Kinds C, D, and F
are used as contextual environmental or visual states but have no separately
required player-facing identity. A compatible engine MUST retain and pass
them to scene logic without remapping them.
For Cyber cells, parameter A selects the lie or study content. For kind 6,
parameter B is the verse exposed by victory. Defeating combat 1–5 or 7 sets
the kind to B. Defeating combat 6 sets kind A, copies parameter B into A,
and clears B. Retreat leaves the cell unchanged.
Side rooms
With a zero connection mask, kinds 1 through 15 encode five room classes and three entrance orientations:
room_class = (kind - 1) / 3
entrance_code = (kind - 1) % 3
| Kinds | Class | Purpose |
|---|---|---|
1–3 | Victim | A level-specific captive and conversation. |
4–6 | Trap | A temptation/trap requiring study interaction. |
7–9 | Prayer | Prayer and spiritual instruction. |
A–C | Communications | Communications and study content. |
D–F | Jump Tunnel | Alternate movement challenge. |
Within every row, the first kind is a room east of the hall with a west-side
entrance, the second is west with an east-side entrance, and the third is
north with a south-side entrance. There is no room-below encoding. The seven
victim scenes are JELO, FEAR, CULT, LAW, RICH, DENY, and NAGE.
Parameters are class-specific mutable state. Current-cell processing copies A and B into VM variables 17 and 18. In a Trap, A selects encounter study content. The parameter B of any correctly oriented adjacent room is copied to variable 23 for right, 24 for left, or 25 for above. Trap scripts use these values as locked-door prompts, and a correct answer clears that byte. Other classes MUST retain their parameters even when a new implementation does not attach a generic name to them.
Current-cell derivation
The current coordinates are variables 11 and 12. Processing starts by copying
the current A and B to variables 17 and 18, clearing variables 23 through 25,
copying the current low kind to variable 13, and clearing flags 00 through
2F.
For a zero-connection cell, it sets flag 10, subtracts one from variable
13, and stores the signed quotient and remainder after division by three in
variables 13 and 14. This is the room class and entrance derivation above.
The all-zero cell consequently produces class 0 and entrance -1, matching
the original signed division even though shipped scene flow does not use that
value as a room.
For a connected hall cell, the following flags describe the current cell and its immediately adjacent, correctly oriented rooms:
| Flag | Meaning |
|---|---|
00 | Current cell connects down. |
01 | Current cell connects right. |
02 | A west-entrance room is immediately right. |
03 | Current cell connects left. |
04 | An east-entrance room is immediately left. |
05 | Current cell connects up. |
11 | A south-entrance room is immediately above. |
16–18 | Parameter B is nonzero for the rooms at right, left, and above. |
1F–21 | The rooms at right, left, and above are Trap-class rooms. |
The three adjacent rooms’ B values, regardless of class, become variables 23,
24, and 25. A room is recognized only when its complete packed byte is below
10; connected cells with the same low kind are not rooms.
When flag 05 is clear, processing stops after the immediate context. When it
is set, the executable builds a forward perspective by scanning decreasing Y.
The scan is clipped at the top map boundary:
| Row relative to current | Direct cell | Right-side room | Left-side room |
|---|---|---|---|
-1 | low kind to variable 14; connections right/left/up set 06/08/0A | presence 07, nonzero B 19 | presence 09, nonzero B 1A |
-2 | low kind to variable 15; connections right/left/up set 0B/0D/0F; south-entrance room presence 12, nonzero B 1B | presence 0C, nonzero B 1C | presence 0E, nonzero B 1D |
-3 | south-entrance room presence 13, nonzero B 1E | not scanned | not scanned |
Variables 14 and 15 retain their prior values if their respective forward rows are not scanned. The perspective flags do not require a continuous chain of up-connection bits beyond the current cell; the current up bit alone gates the complete bounded scan.
An engine MUST perform these derivations before dispatching the matching hall or room scene.
Resource loading and mature-topic policy
The map-load command compares its requested level letter with variable 16. When they are equal, loading is a complete no-op: it MUST preserve the mutable grid and the exploration rows even if the difficulty variable has changed.
For a different level, the engine selects the E, N, or D map from
difficulty variable zero, installs the new grid, writes the level letter to
variable 16, and clears variables 37 through 52, which are the sixteen
exploration-row bitmaps.
When no-mature policy is active, the newly loaded resource is rewritten cell-by-cell. A cell is removed exactly when all of these are true:
- its connection nibble is nonzero;
- its low kind is between
1andA, inclusive; and - parameter A or parameter B is at least
E0.
Removal preserves the connection nibble, clears the low kind to zero, and
clears both parameters. Zero-mask rooms, empty connected halls, connected
kinds B through F, and parameters below E0 remain unchanged. This
rewrite occurs only while loading a different map resource; save restoration
does not reapply it. Runtime Bible descriptors are not removed.
Map-cell kind mutation preserves the high connection nibble. Opcode 7B
technically ORs the complete low byte of its source variable without masking;
the shipped programs supply only 00, 05, 0A, 0B, or 0C. Full VM
compatibility MUST preserve this unmasked behavior.
Exploration map
Exploration state is sixteen 16-bit rows. Visiting (x,y) performs:
explored[y] |= 1 << x
The F2 map displays the 16×16 layout, connections, known room letters
V, T, P, C, and J, and stations or communications references.
Explored areas are gold and unexplored areas gray. The view MUST not itself
mark new cells explored.
For map levels other than E, map rendering visits all rows and columns in
row-major order. At (x,y), the exploration bit chooses a one-based symbol
base of 4 when set and 25 when clear. A connected cell composites one-based
frame base + (connections >> 4). A nonzero zero-mask room instead
composites base + 16 + ((kind - 1) % 3) for its entrance and, when explored,
one-based frame 47 + ((kind - 1) / 3) for its class letter. These one-based
numbers are the DOS drawing-call convention; subtract one to index
MAP.ART.
Connected kinds 6 and A add a Scripture-state marker. Kind A takes its
text selector from parameter A; kind 6 takes it from parameter B. If the
matching text descriptor has nonzero state, one-based frame 4 is composited;
otherwise one-based frame 60 is used. A missing or zero selector counts as
not obtained.
Level E displays only source columns 8 through 15 at output columns 0
through 7 and tests exploration bits 8 through 15. It omits the frame-62
legend. Cells whose complete packed byte has remainder zero modulo three add
one-based frame 61 when explored or 62 when unexplored. For cells with
connection nibble F, kinds D, F, and all other kinds respectively use
adjustments 20, 21, and 19 added to the explored/unexplored base. Kinds 6
and A then apply the same Scripture-state marker rule, except that their
parameter B or A selector is read from the corresponding cell in source
columns 0 through 7. This split-half lookup is intentional. The special
branch MUST not be approximated by cropping the ordinary 16-column rendering.
Map coordinates and world state provide no input to opcodes 8E and 91.
Each command consumes the shared portable generator defined in
Lifecycle and scheduling. Implementations MUST NOT synthesize
a coordinate object or read coordinate-derived host memory for either command.
Progression normalization
When a scene invokes the normalization command, the engine scans the complete grid in column-major traversal order. Each cell is transformed independently:
| Cell condition | Result |
|---|---|
| Connection nibble is zero | Clear parameter B; leave packed kind and A unchanged. |
Connected kind is 6 | Change kind to A, copy B to A, and clear B. |
Connected kind is 1 through 9 other than 6 | Change kind to B; retain A and B. |
| Any other connected kind | Leave all three bytes unchanged. |
Every kind replacement preserves the connection nibble. The operation does not consult a separate progression flag: scene programs remain authoritative about when it runs. Implementations MUST NOT narrow these transformations to only the current cell or to only a presumed encounter subtype.
Dialogue and Bible study
Dialogue presentations
The game has three dialogue channels:
- adversary dialogue (
14), used for a Cyber’s lie; - character dialogue (
48), used for victims, bosses, and other characters; - Captain Bible dialogue (
4E), also used for some narration or captions.
Each displays translated CP437 text in a modal panel using a three-byte
presentation tuple containing logical text X, text Y, and wrap width. Opcode
5C configures the Captain Bible channel. Opcode 5D configures the character
channel, which the adversary presentation also uses. The panel frame extends
slightly beyond this text rectangle. A compatible engine MUST apply the tuple,
wrap within its width, and preserve the channel’s command timing.
The BOSS introduction configures [4, 30, 150] for Captain Bible and
[162, 89, 150] for the other character. These place the two speakers at the
upper left and lower right respectively. FACE instead uses [80, 12, 180]
for adversary messages and [80, 170, 180] for Captain Bible. These shipped
values and the corresponding DOS captures establish all three byte roles.
Dialogue text uses the proportional seven-row atlas in STUFF.ART frame 0.
The renderer expands each logical atlas pixel to 2 by 2, advances by the
executable’s per-character width plus one, and wraps by measured logical
width. Captain Bible and character messages use text style 2, whose atlas
values 0,1,2 map to palette indexes 1,37,4. Adversary messages use style 7,
mapping them to 15,86,90. The CONTINUE caption is not generated text: it
is transparent-zero STUFF.ART frame 29, positioned by its signed origin over
the horizontal center of the panel.
If a dialogue command is reached while another modal message is active, it
suspends before consuming its p operand. After the modal state clears, the
same command is retried, consumes the text, and presents it. Enter dismisses
ordinary dialogue. A primary click dismisses it only when the DOS proximity
test selects the CONTINUE object’s logical anchor. Escape instead opens
Game Options and leaves the dialogue active for redrawing after the options
interface returns.
Choices
A choice consists of an absolute BIN target and a text string. Opcode 45
clears the list, 44 appends choices in display order, 13 can remove the
first target match, and 46 presents the menu. Selecting a choice clears the
modal state and resumes the suspended thread at that choice’s target.
Keyboard Up and Down clamp at the first and last rows, visually identify the
current selection, and activate it with Enter. Pointer motion selects the
nearest row anchor within the common text selector’s proximity radius; a
primary click activates that row. Each row anchor is at logical
(text_x + 72, row_y + 2). Distance is
max(abs(dx),abs(dy)) + floor(min(abs(dx),abs(dy))/2), the limit is 100,
and equal distances choose the earlier row. Moving outside every eligible
region removes the pointer highlight. A click there MUST NOT activate the
previously selected row. Empty choice lists MUST be treated as malformed
content rather than leaving the player in an inescapable modal screen.
Unselected rows use text style 1 (1,7,3), while the selected row uses style
2 (1,37,4). The SELECT caption is transparent-zero STUFF.ART frame 28,
not a host-font label. Its logical anchor is (text_x - 11,row_y + 2) when
text_x >= 23, or (text_x + text_width + 12,row_y + 2) otherwise. The
frame’s signed origin then positions its pixels beside the selected row.
Study browser
The Study Bible is both a player reference (F1) and a gameplay answer screen. It displays translated text-bank records and supports navigation among the available record descriptors. A descriptor has a one-byte selector, component references, a persistent state byte, and a span. The active bank contains no more than 66 descriptors.
The standalone F1 view lets the player browse acquired or otherwise available material and return without changing an encounter continuation. Encounter study mode is configured by bytecode:
15directly selects an expected record, or7Dselects a component and reads the expected selector from a script variable.4For51defines the success continuation, either a navigation node or a command target and thread.49opens the browser and suspends the scene thread.- Choosing the expected descriptor takes the configured success path and
sets flag
14where the surrounding conversation uses result flags. - Leaving without the expected match sets flag
15for those conversations and resumes their failure or retry path.
The browser clears both result flags before accepting encounter input. It MUST compare stable record selectors, not translated display strings. This keeps gameplay identical in every supported translation.
DOS presentation
The Computer Bible is a full-screen 320-by-200 indexed modal. Opening it
clears the viewport to palette index zero and suppresses the scene and status
row. It is not a generic white panel drawn over the current scene. The modal
loads BOOK.ART (resource ID 0x239) and draws its transparent-zero frames
with the current scene palette.
BOOK.ART contains the following zero-based frames:
| Frame | Descriptor (x, y, w, h) | Purpose |
|---|---|---|
| 0 | (28, 27, 266, 150) | Complete open-book chassis and enabled controls |
| 1 | (85, 162, 29, 12) | Enabled Apply control |
| 2 | (36, 26, 26, 12) | Off control |
| 3 | (34, 85, 13, 14) | Enabled page-up control |
| 4 | (34, 106, 13, 14) | Enabled page-down control |
| 5 | (34, 85, 13, 14) | Disabled page-up control |
| 6 | (34, 106, 13, 14) | Disabled page-down control |
| 7 | (85, 162, 29, 12) | Disabled Apply control |
Frame 0 and the page and Apply overlays use caller anchor (0,24). Frame 2
uses caller anchor (9,24). The resulting logical bounds are (28,51) for
the chassis, (45,50) for Off, (34,109) and (34,130) for the arrows, and
(85,186) for Apply. The bottom of the chassis and controls is clipped by the
200-line viewport exactly as in the DOS renderer.
The acquired-reference list begins at (52,70), contains 14 rows, and uses
an eight-pixel line pitch. Each reference is clipped to 100 logical pixels.
The selected row uses text style 5, whose font-atlas values 0,1,2 map to
palette indexes 0,64,70; unselected rows use style 6, mapping them to
0,32,37. The selected citation is drawn at (172,61) with a 110-pixel
limit. Its verse begins at (168,74), wraps to 114 logical pixels, and uses
style 5 with the same eight-pixel pitch. With no acquired records the right
page displays the executable’s exact message NO VERSES LOADED; both page
arrows and Apply use their disabled artwork.
When opcode 7D configured a prompt, the browser resolves its expected
descriptor directly from the active text bank, including when that record is
not acquired. Prompt values zero and nine suppress the prompt. Value 2A
selects its * component and text style 10 (15,1,8); value 64 selects P
and style 9 (15,32,36); every other value selects L and style 7
(15,86,90).
The resolved prompt starts at logical (6,22), wraps to 309 logical pixels,
and occupies at most three rows with an eight-pixel pitch. A | byte forces a
row break. These rows appear in the black area above the book, whose visible
chassis begins at logical Y 51. A host MUST retain this prompt while opening
the modal; displaying only the acquired-reference list loses required
encounter information.
Up and Down move one acquired record and clamp at the ends. Page Up, Page
Down, and the two artwork arrows move by 14 acquired records. Enter or A
activates Apply only while an encounter has enabled Bible application; the F1
reference browser always displays disabled Apply artwork and cannot produce a
study answer. Ctrl+V selects the configured expected verse when that verse is
in the acquired list, moving to its 14-row page without applying it; it does
nothing in the standalone F1 browser or when the verse has not been acquired.
Escape, O, or a primary press on Off closes the modal. A primary press on a
reference row selects that row but does not apply it; a separate press on
enabled Apply is required. Page and Apply controls ignore presses while
disabled, and presses outside all authored bounds do nothing.
Text expansion
Before display, the engine resolves the selected record/component, expands the data-defined placeholders described in Text resources, applies the current translation, and wraps for the destination panel. Resource bytes remain immutable; descriptor state and scene-local expanded strings are kept separately except when a scene explicitly invokes the bytecode self-modifying copy command.
Study choices and verse acquisitions update descriptor state through opcodes
36–39. The same state mechanism can represent an obtained verse, a
completed conversation step, or another text-related condition; the scene
program determines the meaning. Loading a new text bank or executing opcode
88 clears all 66 state bytes as specified.
Player-facing mechanics
Cybers confront Captain Bible with false statements. The player studies the available Bible passages and selects the passage that answers the lie. Correct study results advance dialogue, unlock doors, rescue victims, destroy pylons, or enable combat actions depending on context. An incorrect record or leaving the browser follows the scene-defined failure, retry, or faith-loss branch.
The engine MUST never infer correctness by wording, verse number, or locale. Only selectors and continuations embedded in the resources define it.
Combat
Model
Combat is script-driven. The seven programs COMBAT1 through COMBAT7
compose ordinary VM variables, flags, animations, actions, random branches,
sound effects, and faith changes. A compatible engine MUST execute those
programs rather than inventing a separate health/damage simulation.
The encounters correspond to:
| Program | Opponent | Player actions |
|---|---|---|
COMBAT1 | Macho Cyber | Attack, Defend, Retreat, Combat |
COMBAT2 | Armored Cyber | Attack, Defend, Retreat, Combat |
COMBAT3 | Mantis Cyber | Attack, Defend, Retreat, Combat |
COMBAT4 | Snake Cyber | Attack, Defend, Retreat, Combat |
COMBAT5 | Spider Cyber | Attack, Defend, Retreat, Combat |
COMBAT6 | Leech Cyber covering a station | Attack, Retreat, Combat |
COMBAT7 | Zapper Cyber | Attack, Defend, Retreat, Combat |
The action selectors are .11 Attack, .12 Defend, .13 Retreat, and
.14 Combat. They are ordinary scene action targets and their program targets
define the outcome. An action is available only while its target record is
active. Manual mode normally activates Attack, Defend, and Retreat. Automatic
mode normally activates Combat and Retreat. Combat 6 has no Defend target.
The numeric selector syntax draws the corresponding one-based frame from
loaded ART slot 1. Because every encounter loads COMBTAGS.ART first, the
hover labels are the four authored frames rather than host-rendered text.
Keyboard A (or S), D, and R choose Attack, Defend, and Retreat. C chooses an
active ordinary .c target if one exists, otherwise it chooses Combat.
Player understanding
Captain Bible does not have hit points. His 0–100% Faith meter is the loss
condition. Cyber attacks and incorrect choices reduce faith. At negative
faith, the engine clamps it to zero and enters the OVER scene.
The player answers a Cyber’s lie through study before the physical encounter.
Within combat, Attack and Defend manipulate timing and animations. The Combat
action enters a script-defined randomized exchange. Sword flag 30 and Shield
flag 31 affect those branches and the duration of useful windows. Retreat
exits without defeating the Cyber. The scripts, not the engine, decide which
animation phase makes each action effective.
Automatic Combat is persistent flag 37. Ordinary combat sets flag 38
while active; the options menu MUST prevent changes to Automatic Combat during
that interval. Combat 6 intentionally does not use the lock.
Faith loss
Opcode 81 supplies a base loss in internal faith units. The engine applies:
| Difficulty | Applied loss |
|---|---|
| Easy | base / 2, integer division |
| Normal | base |
| Difficult | base × 4 |
No-combat installation mode suppresses the subtraction. The shipped static loss sites are:
| Program | Base values |
|---|---|
COMBAT1 | 533, 2011 |
COMBAT2 | 107, 102, 502 |
COMBAT3 | 1037, 531, 2011, 1703 |
COMBAT4 | 596, 1005 |
COMBAT5 | 213, 2009 |
COMBAT6 | none |
COMBAT7 | 233, 207 |
These are possible branch sites, not values that all occur in one fight.
Outcome contract
Victory in combat 1–5 and 7 changes the current connected map-cell kind to
B. Combat 6 changes it to A, copies parameter B to parameter A, and clears
B, revealing the covered Scripture station. Retreat skips these mutations,
so returning to the location preserves the encounter.
The Zapper victory alternates faith between 1 and 10000 five times as a visual meter flash and ends at full faith. This reward occurs before the cell is marked cleared.
Every encounter eventually selects the appropriate level hall scene and starts a palette blackout. Two special hall variants may be selected by scene state. Program self-patching constructs the hall name from the current level letter. Scene logic also clears parameter A at the common exit where directed by the resource.
Animation and synchronization
Combat animation records use the format in Scene runtime. Actions commonly disable choices, start one or more linked animations, play a sound, and suspend until completion. Muted audio MUST use an equivalent logical completion timer. Random modulo results select branches; deterministic test runs MAY inject a known initial state. Ordinary play MUST use the shared generator and consumption order defined in Lifecycle and scheduling without reseeding it. An interactive host SHOULD choose a varying initial state unless the player explicitly requests deterministic play.
Dispatching an action target starts scheduler slot zero as a fresh invocation:
the target replaces its cursor, the slot becomes active and unsuspended, its
stale delay and call stack are cleared, and it is eligible during the same
controller update. Opcode 3E applies the same fresh-start state to its named
slot and runs that slot immediately before its caller continues. Combat idle
loops deliberately accumulate negative delays, so retaining the old delay
would make a selected action appear unresponsive.
POWER is the in-combat study/power scene. It can patch a numbered
combat1 name and return to the corresponding encounter. It is not the
game-over scene. Implementations MUST support this entry even though the exact
set of all scene paths that invoke it is not a separate engine invariant.
State and progression
Primary variables
The persistent script state is an array of 100 signed 16-bit words. New game initialization clears all words before assigning starting values. Scene programs may use unnamed words as temporaries; a compatible engine MUST save all 100, not only the named fields.
The following indexes have cross-scene meanings:
| Index | Meaning |
|---|---|
| 0 | Difficulty: 0 Easy, 1 Normal, 2 Difficult. |
| 11 | Current map X. |
| 12 | Current map Y. |
| 13 | Current connected kind or zero-mask room class. |
| 14 | Room entrance code or contextual neighboring kind. |
| 15 | Low kind of the cell two rows ahead during an upward hall view. |
| 16 | Current map level letter as a character value. |
| 17 | Current cell parameter A. |
| 18 | Current cell parameter B. |
| 21 | Faith, from 0 through 10000. |
| 23–25 | Adjacent oriented-room parameter B at right, left, and above. |
| 37–52 | Sixteen exploration bitmap rows, one word per map Y. |
| 53 | Unibot turn/rotation value. |
| 54 | Current Unibot node. |
| 55 | Heading: 0 north, 1 east, 2 south, 3 west. |
| 56–62 | Completion of pylons 1 through 7. |
| 63 | Next Unibot node. |
| 64 | Active pylon number; 100 means none. |
| 65 | Tower state: 0, 1, 2, or 9. |
Faith is stored in hundredths of a displayed percentage point. New play starts
at 10000. F3 displays it as a percentage, and ordinary rendering clamps values
to the 0–10000 range. A negative value after input processing is clamped to
zero and requests OVER.
Flag bank
Variables 3 through 10 also form a 128-bit boolean bank. Flag n resides in:
word = 3 + (n >> 4)
mask = 1 << (n & 15)
Flags 00 through 2F include transient movement and action context and are
rebuilt by current-cell processing. Higher flags carry durable abilities and
progress. The map-derived ranges are:
| Flag range | Current-cell context |
|---|---|
00–05 | Current down/right/left connections and immediate side rooms. |
06–0A | Connections and side rooms one row forward. |
0B–0F | Connections and side rooms two rows forward. |
10–13 | Current cell is a room, or direct rooms one to three rows forward. |
16–1E | The corresponding adjacent/forward room has nonzero parameter B. |
1F–21 | Immediate right, left, or upper room is Trap class. |
The world-model chapter gives the exact individual mapping and scan gates.
Flags 14, 15, and 22 through 2F are cleared by this operation but not
set from map geometry.
Other important identifiers are:
| Flag | Meaning |
|---|---|
14 | Study selection matched the expected record. |
15 | Study browser was left without that match. |
30 | Sword power. |
31 | Shield power. |
32 | No Trap power. |
33 | Candle power. |
34 | Flight power. |
36 | Persistent status-control artwork is visible. |
37 | Automatic Combat enabled. |
38 | Ordinary combat active; option is locked. |
39 | Select the exterior seven-building overview in the F2 map. |
3A–40 | Victims JELO, FEAR, CULT, LAW, RICH, DENY, NAGE rescued. |
42–48 | Corresponding crew members aboard the Unibot. |
52 | Suppress the ordinary F2 map during the Unibot sequence. |
54 | One-time Annoy Cyber event completed. |
The status bar exposes the five powers as F4 through F8 icons. They are boolean capabilities, not consumable counters, unless a scene explicitly clears them.
Ordinary progression
Each level is an explorable grid containing halls, Cybers, side rooms, Scripture stations, and an exit. The player acquires verses, studies them to answer lies and unlock interactions, rescues the level’s victim, and reaches the exit. Scene programs set the seven rescue flags at the successful conversation points. The engine MUST preserve the live map, variables, flags, text-record states, and scene name across save/restore.
At the gantry, each rescue flag 3A–40 is mirrored to crew flag 42–48.
The craft requires all seven crew flags; with fewer, it reports the missing
count and does not depart. Departure clears Sword, Shield, No Trap, Candle,
and Flight, initializes Unibot node and heading state, and enters the final
road network.
Unibot graph
The final network is a 16-node graph embedded in CP2.BIN. Direction slots
are north, east, south, west; 100 is blocked. All open edges are reciprocal.
| Node | Type | Map coordinate | Transition | N | E | S | W |
|---|---|---|---|---|---|---|---|
| 0 | road/start | (270,170) | 0 | 1 | 100 | 100 | 100 |
| 1 | road | (270,165) | 0 | 14 | 4 | 0 | 2 |
| 2 | road | (265,165) | 0 | 9 | 1 | 3 | 100 |
| 3 | pylon 1 | (265,170) | 800 | 2 | 100 | 100 | 100 |
| 4 | road | (275,165) | 0 | 6 | 5 | 100 | 1 |
| 5 | pylon 2 | (280,165) | 480 | 100 | 100 | 100 | 4 |
| 6 | road | (275,160) | 0 | 15 | 7 | 4 | 100 |
| 7 | pylon 3 | (280,160) | 480 | 100 | 100 | 100 | 6 |
| 8 | pylon 4 | (275,150) | 160 | 100 | 100 | 15 | 100 |
| 9 | road | (265,160) | 0 | 12 | 100 | 2 | 10 |
| 10 | pylon 5 | (260,160) | -160 | 100 | 9 | 100 | 100 |
| 11 | pylon 6 | (260,155) | -160 | 100 | 12 | 100 | 100 |
| 12 | road | (265,155) | 0 | 100 | 13 | 9 | 11 |
| 13 | pylon 7 | (270,155) | 480 | 100 | 100 | 100 | 12 |
| 14 | Tower | (270,160) | 160 | 100 | 100 | 1 | 100 |
| 15 | road | (275,155) | 0 | 8 | 100 | 6 | 100 |
The player can turn right (.r), turn left (.l), or move forward (.u).
Turns wrap heading modulo four. Forward follows the adjacency entry for the
current heading and is unavailable when it is 100.
On scene entry, endpoint nodes normalize to adjacent roads:
3→2, 5→4, 7→6, 8→15, 10→9, 11→12, 13→12, and 14→1.
The engine MUST honor the script’s table reads and this normalization rather
than substituting Euclidean movement.
CP2.BIN is 1E55 bytes. Its commands occupy 0000..1D54; the trailer is
four consecutive signed-word tables:
| Offset | Words | Meaning |
|---|---|---|
1D55 | 64 | 16 nodes × north, east, south, west destinations. |
1DD5 | 16 | Node type: 0 road, 1 pylon, 2 Tower. |
1DF5 | 16 | Opaque transition values. |
1E15 | 32 | Sixteen lower-right map (x,y) coordinate pairs. |
The first eligible road event loads ANNOY, clears the player’s acquired
verse states, and sets flag 54. Later road visits skip it.
Pylons and ending
Pylon endpoints map to variables 56 through 62 and study selectors 11
through 17. A completed pylon is skipped. Correct study sets its variable to
one, destroys it, and recovers the associated crew member. A wrong answer
enters OVER.
The Tower at node 14 requires all seven variables to be nonzero; arriving
early also enters OVER. With the gate satisfied, FACE and CP3 implement:
| State | Behavior | Result |
|---|---|---|
| 0 | Tower threatens; crew recalls Captain Bible. | set 1 and return to FACE |
| 1 | Tower presents hopelessness; study selector 20. | correct: 2; wrong: 9 |
| 2 | Captain rejects control. | KABLAM, then WIN |
| 9 | Captain surrenders. | OVER |
There is no additional engine-defined victory condition. Scene programs and these persistent values are the progression authority.
Save and restore
File family
The active player prefix defaults to DDGAMES and may be replaced by a
non-option command-line argument. It is combined with these suffixes:
| File | Purpose |
|---|---|
.SV0 | Nine-slot label index. |
.SV1–.SV9 | Normal save states. |
.SVQ | Quick-save state used by F10 and F9. |
The prefix is not stored inside a save. Files contain no header, checksum, version, or compression. Compatibility is determined by suffix and exact size.
For an original filename-compatible launch, an extensionless prefix SHOULD be at most eight characters. A complete path prefix is also valid. Native ports MAY allow longer host paths as long as the resulting save family remains unambiguous.
Label index
SV0 is exactly 243 bytes: nine 27-byte CP437 NUL-terminated label buffers.
Slot n begins at (n - 1) * 27. Bytes after the first NUL are semantically
irrelevant and MAY be zeroed by a new implementation. A missing index behaves
as nine (EMPTY) labels. User-entered labels are limited to 26 encoded bytes
plus NUL. The normal-save editor clears an existing (EMPTY) label before
editing. If the accepted result is empty or (EMPTY), it MUST become
Game 1 through Game 9 according to the slot. Normal saving updates the
relevant label and rewrites the complete index. Quick save does not require an
index entry.
State layout
Every normal or quick state is exactly 2752 bytes:
| Offset | Size | Meaning |
|---|---|---|
000 | 200 | Checkpoint copy of 100 signed variables. |
0C8 | 200 | Live copy of 100 signed variables. |
190 | 66 | Checkpoint text-descriptor state bytes. |
1D2 | 660 | 66 live ten-byte text descriptors. |
466 | 20 | Checkpoint scene-resource C-string buffer. |
47A | 20 | Live scene-resource C-string buffer. |
48E | 20 | Checkpoint scene extension/entry C-string buffer. |
4A2 | 20 | Live scene extension/entry C-string buffer. |
4B6 | 2 | Translation index. |
4B8 | 2 | Music-enabled word. |
4BA | 2 | Effects-enabled word. |
4BC | 2 | Checkpoint text-bank character. |
4BE | 2 | Live text-bank character. |
4C0 | 768 | Live world map. |
7C0 | 768 | Checkpoint world map. |
Words are little-endian. The four 20-byte fields are NUL-terminated CP437 buffers; bytes following NUL are not semantic.
Each live descriptor is:
| Offset | Size | Meaning |
|---|---|---|
| 0 | 2 | Legacy pointer word; nonportable. |
| 2 | 2 | Legacy pointer word; nonportable. |
| 4 | 1 | Persistent record state. |
| 5 | 1 | Record selector. |
| 6 | 2 | Offset in the companion text stream. |
| 8 | 2 | Span in that stream. |
A portable engine MUST reconstruct the first four bytes from its own loaded text representation and MUST NOT interpret serialized pointer words as host addresses. When writing an original-format save it SHOULD emit zero for those words. Selectors, offsets, spans, and state bytes MUST be preserved.
Checkpoints
The checkpoint is distinct from the act of writing a file. New-session setup
and bytecode opcode 55 copy live variables, each descriptor’s state byte,
the scene strings, bank character, and live map to their checkpoint fields.
Saving serializes both copies without first refreshing the checkpoint.
Restoring a selected save loads both serialized copies, copies the checkpoint
fields into live state, reconstructs text descriptors, applies the persisted
translation/music/effects settings, and resumes the checkpoint scene. The
predecessor scene MUST NOT receive another controller update after the restore
has been accepted. The serialized live fields remain part of the compatible
file even though this restore step replaces them. Opcode 67 requests the
retained restore path.
F9 temporarily selects suffix Q and invokes the same historical state
reader. If .SVQ cannot be opened, its failure return becomes top-level mode
1, which initializes a new session instead of presenting an error. This
restart retains the current translation, music, effects, and automatic-combat
options, just like the explicit New Game path. Failure to open a numbered
state does not imply this behavior because the slot interface does not offer
missing states for loading.
Robustness requirements
Original-format readers MUST reject files whose size is not exactly 243 or 2752 bytes, short reads, unterminated semantic buffers, invalid translation or bank values, or out-of-range structural descriptor spans. Loading MUST be atomic: a rejected file cannot partially replace a running session. Resource validation needed to construct the checkpoint scene is part of that atomic operation.
Original-format writers SHOULD write a temporary sibling, flush it, and atomically replace the destination where the host platform supports that. These safety guarantees intentionally improve on historical partial-read and partial-write behavior without changing valid-save compatibility.
Configuration and launch options
Installation policy file
SOUND.5 is exactly four bytes and contains policy, not sound-driver data:
| Offset | Type | Meaning |
|---|---|---|
| 0 | u16le | Translation lock; 0x0070 means unlocked, otherwise 0–3. |
| 2 | u8 | Mature-topic permission; only DB permits, any other value forces filtering. |
| 3 | u8 | Value ORed into no-combat mode. |
An explicit -bX gameplay translation is retained instead of being replaced
by the installation translation. With no -bX, the installation translation
selects gameplay text. The -sX export selector is separate and is replaced
by an installation translation lock. Restrictions combine monotonically:
command-line options can request filtering or no-combat, but cannot relax an
installation restriction.
A portable engine MAY store equivalent policy in another configuration system
for native installations. When pointed at an original game directory, it MUST
interpret SOUND.5 as above.
Command line
Option letters are case-insensitive. The compatible launch interface is:
| Form | Effect |
|---|---|
-t | Suppress mature-topic map encounters and omit them from text export. |
-bX | Translation: K=0, N=1, R=2, L or T=3. |
-c | Enable no-combat mode, suppressing faith loss through opcode 81. |
-idirectory | Use the directory, with a path separator added, for installation configuration and audio assets. |
-sXfilename | Export study material in translation X to the filename. |
-gXX | Set a two-decimal-digit export mask. |
| non-option | Replace the DDGAMES player/save prefix. |
An unknown option MUST produce a clear error. A native implementation MAY also offer modern long options, provided these forms remain supported in its original-compatibility launch profile.
Text export
The export mask has these bits:
| Bit | Value | Output |
|---|---|---|
| 0 | 1 | Numbered lie/verse heading. |
| 1 | 2 | Cyber lie (L component). |
| 2 | 4 | Paraphrase or lock text (P / *). |
| 3 | 8 | Conversation with victim (W). |
| 4 | 16 | Communications material (W, C, and E as applicable). |
| 5 | 32 | Citation and verse text. |
M metadata is not printed. The text is CP437-compatible text, wrapped at 70
columns, with the game study-file heading and section labels. Mature selectors
E0 and above are excluded when filtering is active. Installation translation
locking overrides the -sX export selector.
The original-compatible export is a CP437 byte stream with DOS CR LF line
endings. It begins with:
CAPTAIN BIBLE IN DOME OF DARKNESS
NUL
Here NUL is one literal zero byte on its own terminated line, not the three
printed letters. Banks appear in A B C D E F G R order with these headings:
BUILDING A - Round Tubes with Fireballs - Relative Moralist
BUILDING B - Huge Dark Blue - Fearful
BUILDING C - The First Building - Cultist
BUILDING D - Purple Lights - Legalist
BUILDING E - Wall with Platforms - Greedy
BUILDING F - Green and Purple - Drug Abuser
BUILDING G - Caves - New Ager
BUILDING R - In the Unibot
A form-feed byte followed by two line endings separates successive banks.
When mask bit 0 is set, #NN uses the one-based record ordinal within its
bank, not the selector. Filtering a mature record leaves the later ordinals
unchanged. Selected companion components retain their resource order and use
the labels CYBER LIE:, PARAPHRASE:, CONVERSATION WITH VICTIM:, WRONG GUESS:, CORRECT GUESS:, and EXPLANATION OF CORRECT GUESS:. Verse output
uses VERSE: followed by citation - verse.
Wrapping is byte-oriented. While more than 70 bytes remain, search only the first 70 bytes for the last ASCII space, emit the bytes before it, and discard that separator plus every immediately following space. If there is no space, emit 70 bytes. The final 70 or fewer bytes form the last line. This strict first-70 search means a word that would end exactly in column 70 is moved to the next line when more text follows.
After two blank lines, the export ends with these lines and a literal NUL byte:
This study is copyrighted material as stated in the User Guide.
Please respect the rights of the owners of the copyrights.
In-game settings
The options interface controls Bible translation when unlocked, music,
effects, and Automatic Combat. Music and effects are words in save state and
take effect immediately. Disabling effects suppresses later starts without
interrupting a digital sample already in progress; effect waits use their
silent fallback while the setting remains Off. Automatic Combat is flag 37
and is not changeable while ordinary combat-active flag 38 is set.
Native ports MAY offer volume, scaling, controller, accessibility, or language presentation options. Such additions cannot change scene variable values, resource selection, study correctness, or saved original-format fields unless the player explicitly opts into a non-compatible mode.
Conformance
Test environment
Conformance tests operate on a legally supplied original data directory. They do not require an earlier engine. Tests SHOULD use fresh temporary save prefixes, a deterministic input clock, a controllable random source, and muted audio with logical completion enabled.
An engine SHOULD report its supported profile from Compatibility contract: data, shipped game, or full interpreter.
Data-profile tests
The parser test suite MUST establish all of the following:
DD1.DATyields 369 ordered entries with the extension population listed in Resources.- Every member expands to its declared size and consumes its complete stored payload. Raw and compressed members are both exercised.
- All 37 palettes are 768 valid six-bit RGB components.
- All 143 artwork members have a valid 12-byte descriptor table and contiguous pixel blocks.
- All 41 effects decode to their exact declared sample counts without unused bytes.
- All 32 XMIDI members have valid IFF bounds and a terminating event.
- The 21 maps cover levels A–G and difficulties E/N/D and are each 768 bytes.
- All four translations and eight banks join to 319 logical records with the bank counts and companion lengths specified in Text.
- Every
BINcode region decodes without an invalid instruction or operand overrun.CP2.BINhas code0000..1D54and a 256-byte data trailer;ROOM3.BINhas code ranges0000..0335,0C96..1753, and1768through its end. - Original saves are accepted only at the exact index and state sizes.
Malformed-input fixtures SHOULD cover overflowed offsets, truncated strings, invalid LZW phrases, cyclic dictionary references, invalid frame tables, wrong output counts, IFF chunk overruns, descriptor spans outside companions, bad branch targets, division by zero, and short saves. Every rejection MUST be bounded and atomic.
VM tests
A full-interpreter suite MUST encode and run at least one legal instance of
every opcode 01 through 91, including both BH and BHH paths for each
BHs opcode and both inline and FF-offset forms for every p opcode. It
MUST verify:
- little-endian operand reads and exact next-instruction offsets;
- signed comparison, multiply, divide, remainder, threshold, and 16-bit wrap;
- variable byte-offset validation and flag-bank mapping;
- absolute branches, nested calls, callback targets, and cooperative waits;
- mutable
BINbyte and word reads/writes; - display, action, dialogue, navigation, map, palette, text, and audio effects;
- all five no-ops and every command marked unused by shipped content; and
- deterministic suspension and resumption around input, animation, and audio.
Random-source coverage MUST accept every initial state in 0..65535, including
the endpoints whose first outputs are 6,924 and 27,106. Starting from state 1,
it MUST check the prefix 19511, 30543, 10098, 22502, 1941, 10629. It MUST
interleave opcodes 82, 8E, and 91 to prove that each advances the one
shared state exactly once and that the latter two use the returned value’s low
byte. One X component test MUST prove that 20 left-then-right swaps consume
exactly 40 values.
Full-interpreter coverage MUST also exercise opcode 8B and the transient
status timer it starts. It MUST distinguish one post-VM status draw when 8B
creates the timer from the usual two status draws for an already-active timer,
verify that an expiring refresh still advances once, and verify that clearing
flag 36 freezes the timer without advancing the stream. A batching test MUST
also show that one update carrying delta seven makes two status draws, while
seven separate delta-one updates make fourteen; comparison replays must retain
the same boundary sequence.
Movement-controller coverage MUST distinguish intermediate-edge arrival from
final idle state. It MUST verify continuous walking phase across a multi-edge
route, the final 0x0200 phase reset, active destination replacement without
restarting the current edge, and replanning after arrival. It MUST cover the
224-unit horizontal frame-20 and toward/away frame-19 turn poses, prove that a
horizontal/depth change has no special pose, and exercise opcode 53 plus an
unreachable request as minimum same-node traversals. Route-search tests MUST
accept a 20-edge path, reject a 21-edge path, preserve edge insertion and
endpoint order, and exercise competing equal-length routes. Tests MUST also
cover the distinct initial and state-five departure ordering, synchronous
same-slot callbacks through a yield or return, directional-edge-before-node
arrival, arrival-time start then retarget behavior, and retained-destination
persistence across opcode 53 and scene replacement. A nested same-slot test
MUST prove that a callback delay does not pause the already-running outer
invocation, and that the outer yield publishes its cursor and cumulative
delay.
The shipped-game profile may omit execution tests for unused opcodes but MUST still decode their operand lengths so tooling can safely traverse a resource.
Scenario tests
At minimum, an end-to-end implementation SHOULD automate or manually verify:
| Scenario | Required observation |
|---|---|
| Startup | Logo, title, introduction, difficulty selection, and first hall are reachable. |
| Input | Keyboard and pointer choose the same scene actions and modal rows. |
| Study | A wrong selector follows failure; the expected selector follows success in every translation. |
| Map | Moving marks the proper exploration bit; F2 does not change it. |
| Station | Get Verse sets the intended descriptor state and displays its reference. |
| Trap | Correct adjacent-door study clears parameter B; failure follows scene logic. |
| Combat | Attack/Defend/Retreat/Combat dispatch to script targets; retreat preserves the cell. |
| Difficulty | One loss site applies half, normal, and fourfold faith loss. |
| Leech | Victory changes kind 6 to A and transfers B to A. |
| Zapper | Victory ends at faith 10000 and changes the cell to B. |
| Save | Normal and quick saves round-trip state; restore resumes checkpoint rather than arbitrary live dialogue. |
| Crew gate | Six victims cannot depart; seven victims can. |
| Pylon | Correct study completes one variable; wrong study enters OVER. |
| Tower | Incomplete pylons fail; correct final answer reaches WIN; wrong reaches OVER. |
Rendering and audio comparisons
Exact host RGB framebuffer bytes are not required after scaling, but the unscaled indexed 320×200 composition SHOULD be suitable for golden-image comparison at stable scene points. Tests SHOULD separately verify opaque zero, transparent zero, both flips, clipping, origin offsets, native and non-native scale, hidden objects, list order, palette rotation, and blackout.
A real-time presentation test SHOULD exercise the normal approximately 120 Hz cadence for at least one second and verify that its elapsed deltas total 2,880 units without any routine batch reaching 30 units. A synthetic or shipped multi-step animation with 30- and 40-unit intervals SHOULD verify that every authored frame is submitted under that cadence. A separate long-stall case MAY resolve several records into one presented frame, matching the reference’s capped catch-up behavior.
Audio tests compare decoded ABT sample bytes and XMI event sequences before host resampling or synthesis. Different audio backends MAY produce different analog output. Muting effects MUST not change the frame or VM update on which an audio wait completes under the test clock. A frontend that synthesizes or converts music SHOULD exercise every identifier in its configured family and confirm that each resource produces a nonempty, non-silent sequence that can be stopped, replaced, and looped independently of effects.
Save interoperability
A state written by a conforming engine in original-format mode MUST be readable by another conforming engine. Tests ignore bytes after the first NUL in fixed string buffers and the four pointer bytes of each text descriptor, but compare every other byte or logical field. Loading a rejected save MUST leave a hash of the complete running game state unchanged.
Completion criterion
Shipped-game conformance means the complete objective described in The game and its objective can be completed from a new session in all three difficulties without engine-specific data patches. Save/load, both input methods, every room class, all seven combat programs, all translations, the Unibot graph, both Tower outcomes, and normal exit must remain usable.
Compatibility boundaries
What this specification does not require
A compatible engine does not need to emulate a particular CPU, operating system, memory model, graphics adapter, sound card, driver interface, file handle convention, allocator, or compiler runtime. It does not need to keep logical tables at fixed host addresses or serialize host pointers.
The following presentation choices are deliberately portable:
- host window size, fullscreen mode, and aspect correction;
- update frequency and interpolation, provided script-visible ordering holds;
- keyboard layout mapping and controller support in addition to original keys;
- injection of a deterministic initial random seed for testing, provided the portable generator and call order remain unchanged;
- native XMIDI synthesis, conversion, and host resampling;
- crash reporting, logging, save-write atomicity, and malformed-data messages;
- internal object types, collection capacities above shipped requirements, and coroutine or scheduler implementation; and
- optional accessibility features that do not alter game state or answers.
Opaque but preserved values
Some valid data participates in compatibility without a justified universal name. Implementations MUST preserve and expose it to scene logic:
- connected map kinds
C,D, andFand context-specific cell parameters; - the
ABTauxiliary header word; - per-node Unibot transition values;
- unrecognized companion
Mmetadata strings; and - the two legacy pointer words in a loaded original save, which are ignored or zeroed rather than dereferenced.
Opaque does not mean disposable. A value may influence a scene through normal resource reads even when no cross-scene semantic name is assigned.
Underdetermined timing and animation details
The resource-visible animation definition, its nine-byte steps, linking, start/stop operations, mode transitions, terminal states, display output, and wait predicates are normative. The transition table for modes 1 through 10 is specified in Scene runtime. Two private runtime timing bytes are not specified as a host data structure. A shipped-game implementation must nevertheless make the supplied sequences loop, reverse, stop, and reach their wait states with equivalent visible ordering. An implementation claiming cycle-exact animation compatibility must document its additional wall-clock timing model separately.
Runtime controller values use 2,880 timer units per second. The reference
normally receives those units in increments of 24 and caps one update’s delta
at 400. Scene delay values, the silent 100-unit effect wait, and the unused
opcode-8B transient-status timer use this scale. The effect wait corresponds
to approximately 35 ms. An already-active status timer subtracts the delta
twice per controller update and therefore normally lasts about 0.52 seconds;
its creation update performs only the post-VM subtraction. A host MAY batch
several elapsed units for non-visual execution, but MUST advance timers at
2,880 units per second rather than by one unit per rendered frame. A real-time
graphical frontend MUST target the normal approximately 120 Hz controller
cadence and present after each ordinary approximately 24-unit update. It MUST
NOT intentionally use a 60 Hz presentation loop: its approximately 46-unit
batch can skip shipped 30- and 40-unit animation records. A genuine stall may
still use the reference’s capped catch-up update and expose only its final
resolved frame. Exact interrupt phase and sub-frame jitter remain outside the
base shipped-game profile.
Font and text layout
Strings, CP437 decoding, text order, component selection, substitutions,
70-column export wrapping, modal regions, and scene hit coordinates are
normative. The exact bitmap glyph source is STUFF.ART frame 0, with
proportional widths stored by the executable and specified in
Graphics and palette rendering. The action captions are other
frames of the same resource. Their pixels, palette remapping, proportional
advances, wrapping, and 2-by-2 presentation are part of the base shipped-game
profile. A BIOS or host replacement font does not conform to that profile.
Content-defined behavior
The engine must not promote a coincidence in current content into an engine
rule. Examples include the exact caller set for POWER, scene-local meanings
of unnamed temporary variables, visual identities for opaque hall kinds, and
which random branch corresponds to a named animation phase. The BIN
program, resources, and persistent selectors remain authoritative.
Conversely, absence from shipped content does not erase a VM command. The 23 unused opcodes are required by the full-interpreter profile and are specified by their direct logical effects in Scene-program virtual machine.
Extension policy
A port MAY add a versioned native save format, high-resolution UI, remastered audio, translations, or new content. Original-format resources and saves must still be available through a clearly identified compatibility mode. Extensions SHOULD use new names or explicit version markers instead of overloading opaque bytes in original files.