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.