AGI Behavioral Specification
Project repository: peterkelly/agi-re
This book specifies the externally observable behavior of the Adventure Game Interpreter family examined by the clean-room project. Its intended reader is an independent implementer who has not seen the original interpreter, disassembly, or reverse-engineering evidence.
The specification defines what a compatible engine must do. It does not prescribe a programming language, architecture, memory layout, operating system, internal data structure, or algorithm unless a choice has an observable effect that game data can depend on.
Clean-room role
The reverse-engineering evidence is maintained separately under docs/. That
book records how behavior was discovered and may discuss the original DOS
implementation. This book contains only the resulting portable behavioral
contracts.
An implementation team should be able to work from this book alone. It should not need the original game interpreter, the evidence book, local analysis scripts, or knowledge of the original machine-code organization.
Status
This specification is under construction. The evidence project already covers many AGI subsystems, but those findings must be deliberately restated here in implementation-independent terms before they become part of the clean-room deliverable.
Incomplete or uncertain findings remain in the evidence book. Their absence from this book means they are not yet a normative compatibility requirement.
Scope and Conformance
Behavioral boundary
This specification describes behavior that can be observed by valid AGI game data, by a player providing input, or through an engine-produced artifact such as rendered output, sound events, or a saved game.
The behavioral contract includes, where applicable:
- accepted resource and bytecode formats;
- game-visible variables, flags, strings, objects, and inventory state;
- ordering and timing of state transitions;
- logic control flow and operation results;
- picture, view, text, menu, and status-line output;
- keyboard and text-input behavior;
- object movement, animation, collision, and priority behavior;
- sound-event scheduling and completion behavior;
- room changes, restart, save, and restore behavior; and
- observable differences between interpreter versions.
The behavioral contract does not include the original interpreter’s addresses, register use, instruction sequences, overlays, DOS memory organization, heap layout, routine boundaries, or inferred source-code structure. A conforming engine may organize its implementation in any way that preserves the specified observations.
Current compatibility target
The primary display target is the full 16-color EGA behavior exercised by valid local game data. Other display adapters and modes are specified only when they are deliberately added as compatibility targets.
Text output is specified at the character-cell boundary: text bytes, attributes, rows, columns, rectangles, blocking behavior, and save/restore of covered regions are part of the core contract. Exact glyph bitmaps are a font profile input, not an interpreter semantic in the current portable core. A bitmap-identical screenshot claim for text output must state the font bitmap set it uses in addition to the interpreter profile.
Behavior caused by malformed data escaping a resource and causing the original interpreter to read or execute unrelated memory is outside this specification. Bounded malformed input may be specified when its effects remain within the normal resource-processing contract.
Hardware-specific implementation effects are included only to the extent that games or players can observe a required result. For example, a sound scheduler and its completion event can be normative without requiring an implementation to reproduce the original hardware driver’s instruction sequence.
Versioned behavior
AGI is a family of interpreter versions rather than one immutable program. The specification identifies a common contract and records version-specific variants when valid game data can observe a difference.
An implementation claiming compatibility with a particular version must follow the common rules plus every variant assigned to that version. Differences that exist only in the original implementation but cannot affect valid observable behavior do not create specification variants.
Conformance
Given the same valid game resources, initial state, player input sequence, and specified timing conditions, a conforming implementation must produce the same game-visible state transitions and externally observable results described by this book.
When the specification permits nondeterminism, conformance means producing a result within the stated set or distribution constraints rather than matching one recorded run exactly. When timing is expressed in interpreter cycles, an implementation may use a different real-time mechanism as long as the game-visible ordering and rate requirements are preserved.
Compatibility tests are supporting oracles for these contracts. They do not replace the written specification, and test implementation details are not normative unless the corresponding behavior is stated in this book.
Version Profiles
AGI compatibility is defined against an interpreter profile. A profile selects the common rules in this book and any explicitly listed variants. Version numbers identify observed families; they do not imply that every build with a similar number has identical behavior.
AGI 2.411 profile
The 2.411 profile is specified for full-EGA valid-data gameplay. It uses four
split resource directories and direct five-byte-header volume records. Its
valid action range is 0x00..0xa9; conditions remain 0x00..0x12.
Configured-message actions 0x97 and 0x98 consume the same four operands as
later profiles: message selector, row, column, and width. Restart action 0x80
always presents its confirmation prompt; f16 does not bypass it. The heap
diagnostic omits the later rm.0, etc. line.
Picture commands 0xf9 and 0xfa use the early point-plot profile. Command
0xf9 consumes and ignores one raw byte. Command 0xfa reads coordinate pairs
without seed bytes and writes one ordinary pixel per complete pair. It does not
draw shaped or stippled brushes. Other full-EGA picture, view, object, motion,
collision, composition, and refresh behavior follows the common contracts.
Automatic direction-based loop selection uses the four-loop table only for
views with exactly four loops.
The early sound profile has no attenuation-envelope evolution between events.
Only device selector 0 uses one channel and PC-speaker divisor output; every
nonzero selector advances all four channels. Four-channel tone output always
emits both bytes of the tone word. Event attenuation adds the global adjustment
and clamps to 0x0f, but receives no envelope or device-2 adjustment.
The selected KQ2 data supplies the binary-save dimensions listed in the persistence chapter.
AGI 2.440 profile
The 2.440 profile shares the 2.411 resource container, action range, condition range, configured-message encoding, exact-four-loop selection, early sound channel selection, absence of attenuation envelopes, and shortened save block 1.
It adds the shaped and stippled 0xf9/0xfa pattern behavior specified in the
picture chapter, using the shaped-brush v2 radius-one shape and horizontal
limit. Restart action 0x80 proceeds without displaying confirmation
when f16 is set. Four-channel tone output emits the high tone byte and
suppresses the low byte when the high byte’s top three bits are all set. Its
heap diagnostic still omits the later rm.0, etc. line.
The selected LSL1 data supplies the binary-save dimensions listed in the persistence chapter.
The observed 2.439 build has the same action and condition contracts as 2.440. Every uniquely mapped full-EGA renderer, object, motion, restart, save, picture-pattern, and sound routine has the same behavior after relocation. It therefore selects the 2.440 behavioral rules for those domains. Its selected game data supplies different save dimensions.
AGI 2.917 profile
The 2.917 profile is specified for full-EGA valid-data gameplay. It uses the
same split v2 resource container as 2.936. Its valid action range is
0x00..0xad; opcodes 0xae and 0xaf are not available. Conditions
0x00..0x12 and every shared action follow the common contracts.
Its picture, view, object, input, sound, room, replay, and primary full-EGA rendering behavior follow the common core. Automatic direction-based loop selection uses the two-loop and three-loop tables as specified in the object chapter, and uses the four-loop table only for a view with exactly four loops. A view with more than four loops does not receive automatic direction-based loop selection in this profile.
Pattern plots use the shaped-brush v2 radius-one shape and horizontal limit specified in the picture chapter.
The selected KQ1 and PQ1 data supply separate binary-save dimensions listed in the persistence chapter. Those counts and lengths are game-data properties, not universal constants for every 2.917 game.
The observed 2.915 build has the same action and condition contracts and the same uniquely mapped full-EGA subsystem behavior as 2.917. The observed second 2.917 build differs from the selected KQ1 interpreter only in its embedded game signature. Both therefore select this profile, with separate game-data dimensions.
AGI 2.936 profile
The 2.936 profile is currently the most complete profile in this specification. Unless a chapter says otherwise, promoted logic, state, graphics, object, input, persistence, and sound rules currently refer to this profile.
Its resource container uses four separate directory files and direct resource records as specified in Resource Containers.
The valid action opcode range is 0x00..0xaf. The valid condition opcode range
is 0x00..0x12, in addition to the structural condition markers described by
the logic-bytecode specification.
The selected SQ2 and KQ3 data supply separate binary-save dimensions listed in the persistence chapter.
Pattern plots use the shaped-brush v2 radius-one shape and horizontal limit specified in the picture chapter.
AGI 3.002.086 profile
The 3.002.086 profile is specified for full-EGA valid-data gameplay from the
full KQ4 release. It uses the combined and compressed v3 resource container.
Its valid action range is 0x00..0xb1; conditions remain 0x00..0x12.
Action 0xb0 consumes one ignored byte and otherwise has no effect. Action
0xb1 sets the menu interaction gate. Shared action 0xad increments the
key-release event gate modulo 256, as in the v2 profiles. The script key map
holds 39 entries. Immediate room destinations are not remapped, and input-width
actions 0xa3 and 0xa4 retain their v2 effects.
Its inventory-selector temporary state, block-3 save transform, restart
prompt-marker handling, and motion-mode-4 preservation follow the later v3
profiles. Its automatic direction-based loop selection follows 2.936: every
view with four or more loops uses the four-direction table without an f20
gate.
Pattern plots use the v3 radius-one center-row shape and horizontal limit specified in the picture chapter.
This profile has one screen-boundary variant. A due movement proposal whose left X coordinate is exactly zero is clamped to zero and reports left-boundary code 4. Later promoted profiles accept exact zero without reporting a boundary; both behaviors clamp negative proposals to zero and report code 4.
The selected full KQ4 data supplies the binary-save dimensions listed in the persistence chapter. Present-looking directory entries whose selected volume files are absent are outside this valid-data profile.
AGI 3.002.149 profile
The 3.002.149 profile uses the combined and compressed v3 resource container. Expanded logic, picture, view, and sound payloads retain the same resource families as the 2.936 profile.
Pattern plots use the v3 radius-one center-row shape and horizontal limit specified in the picture chapter.
This profile accepts action slots through 0xb5. Slots 0xb0, 0xb2,
0xb3, and 0xb4 consume their declared operands and otherwise have no
effect. The additional observable actions are:
| Opcode | Operands | Behavior |
|---|---|---|
0xb1 | one immediate byte | Set the menu interaction gate. Zero prevents a pending menu request from entering modal menu interaction; nonzero permits it. |
0xb5 | none | Clear the key-release event gate. |
For this profile, shared action 0xad sets the key-release event gate instead
of incrementing it. The script key-map holds 49 entries rather than 39.
Immediate room-switch action 0x12 normally passes its destination to the
ordinary room-change operation unchanged. The observed Gold Rush build adds a
game-specific alias: destination values 0x7e, 0x7f, and 0x80 select room
0x49. The observed MH2 build has the same 3.002.149 core but does not apply
these aliases. A conformance claim must identify which build variant it uses.
The object-and-inventory portion of a saved game uses a profile-specific XOR transform. The save envelope and transform bytes are specified in the persistence chapter. The observed Gold Rush save block layout is byte-mapped there, including canonical and byte-preserving rules for reserved serialized state.
Input-width actions 0xa3 and 0xa4 have no effect in this profile. Normal
EGA text input remains available without those width-control branches. Action
0xa9 still restores active saved-window state but has no input-width override
to clear.
For automatic direction-based loop selection, exactly-four-loop views retain
the 2.936 four-or-more-loop behavior. Views with more than four loops apply
that selection table only while f20 is set.
AGI 3.002.102 profile
The 3.002.102 profile is specified for full-EGA valid-data gameplay. Its expanded picture and view formats, picture command execution, logical raster rules, view/cel composition, object update lists, placement, collision, movement, animation, and refresh ordering follow the common behavioral core. Alternate display-mode branches remain outside the current target.
Pattern plots use the v3 radius-one center-row shape and horizontal limit specified in the picture chapter.
This profile uses the combined and compressed v3 resource container and accepts
action slots through 0xb5. Its extra slots 0xb0..0xb5 have the same operand
contracts and effects specified above for 3.002.149, including the menu gate
and release-gate clear action. Shared action 0xad sets the release gate to one.
Unlike 3.002.149, its script key map holds 39 entries, immediate room-switch
destinations are not remapped, and input-width actions 0xa3 and 0xa4 retain
their 2.936 effects. Closing text-window state also clears the input-width
override. Its restart prompt-marker branch, object motion-mode-4 preservation,
inventory-selector temporary state, and block-3 save transform follow the
3.002.149 variants.
Automatic direction-based loop selection follows the 3.002.149 rule: exactly
four loops use the direction table unconditionally, while views with more than
four loops require f20.
The selected KQ4D demo data supplies the save dimensions listed in the persistence chapter. Those dimensions are game data, not universal constants for every 3.002.102 game.
The observed 3.002.107 build has the same action and condition contracts as 3.002.102. Every currently mapped full-EGA core routine has the same behavior after relocation. Unmapped startup and alternate paths remain outside that equivalence statement.
AGI 2.089 profile
The 2.089 profile is specified for full-EGA valid-data gameplay. It uses the
split direct-resource container, but stores
its inventory metadata file in expanded plain form instead of applying the
later repeating-key transform. It accepts actions 0x00..0x9a and conditions
0x00..0x12.
Action 0x86 consumes no operand. It stops active sound, performs process
shutdown cleanup, and terminates without asking for confirmation. Actions
0x9b and later are unavailable.
Position actions 0x25 and 0x26 first change the object’s current position.
If the object is currently drawn, they remove its old rendered state, then
copy the new coordinates into its previous-position snapshot. This differs in
ordering from 2.272.
The earlier-drawn object partition is composed in ascending object-number order, regardless of baseline or fixed-priority drawing key. The later-drawn partition is composed in ascending drawing-key order with object number as the tie breaker. This is the ordering exception defined in the object chapter; 2.272 sorts both partitions.
Pictures support commands only through seed fill 0xf8. Automatic
direction-loop selection is evaluated on every eligible post-logic pass and
uses the four-direction table only for exactly four loops. The profile exposes
six string slots. Its word-sequence condition requires an exact operand/parser
count and does not give 0x270f tail-terminator meaning.
Showing a prepared picture presents it and marks it shown, but does not clear
f15 or close an active text window. Object-distance action 0x45 stores the
low byte of the center-X-plus-baseline-Y distance; values above 255 wrap
instead of saturating at 254. Target-motion actions 0x51 and 0x52 defer
their first direction calculation and already-satisfied completion until the
next eligible target-motion update.
Action 0x4d clears direction but leaves autonomous-motion mode unchanged.
For object 0 it also clears v6 and selects object-to-v6 coupling. Action
0x4e leaves both direction and autonomous-motion mode unchanged. For object
0 it clears v6, selects v6-to-object coupling, and clears the remembered
navigation event used by the old movement pass.
The inventory display action always shows an acknowledgement-only carried-item
list. It does not inspect the inventory-interaction flag and does not modify
v25.
Sound event scheduling and channel participation follow the early profile in the sound chapter. Four-channel events emit both tone bytes and use the 2.089 control-byte rule, including its device-2 adjustment. The selected SQ1 data uses 17 runtime object records: this profile treats the metadata header’s object value as a count, not as a maximum index. Saves use the four-block 2.089 envelope specified in the persistence chapter.
AGI 2.230 profile
The 2.230 profile is specified for full-EGA valid-data gameplay. It uses the
split direct-resource container and stores inventory metadata in expanded
plain form. It accepts actions 0x00..0x9a and conditions 0x00..0x12.
Action 0x86 is the same zero-operand unconditional exit as profile 2.089.
Actions 0x9b and later are unavailable. Position actions 0x25 and 0x26,
however, use the 2.272 ordering: they update current and previous coordinates
together without first removing drawn state. Both object partitions use
drawing-key order with object number as the tie breaker.
Views use the packed loop-header format defined in the view chapter. The low
nibble is the cel count, bits 0x30 retain the shared loop’s mutable
orientation, and bits 0x40/0x80 enable loop-wide row mirroring when the
selected orientation changes. Action 0x31 therefore masks the loop header to
its low nibble before returning the highest valid cel index. This packed
loop-level orientation is unique among the currently promoted profiles.
Pictures dispatch commands only through 0xf8. Automatic direction-loop
selection is cadence-independent and applies the four-direction table only to
exactly-four-loop views. The profile has six string slots and recognizes
0x270f as the word-sequence tail terminator. Showing a prepared picture,
object-distance overflow, and initial target-motion deferral follow the common
early rules.
Movement-clear actions follow profile 2.089: 0x4d clears direction but leaves
autonomous mode active, while 0x4e leaves both unchanged and applies only the
object-0 coupling/navigation effects. Inventory display is acknowledgement-only
and never writes v25.
Sound uses the 2.089 early output rule: selector zero advances one channel and every nonzero selector advances four; four-channel tones emit both bytes; and device 2 adds three to attenuation nibbles below eight without applying the global whole-byte adjustment. The selected XMAS.230 data uses 18 runtime object records, 200 replay pairs, and the five-block envelope and dimensions specified in the persistence chapter.
AGI 2.272 profile
The 2.272 profile is specified for full-EGA valid-data gameplay. It uses the
split direct-resource container and stores
inventory metadata in expanded plain form. It accepts actions 0x00..0xa0
and conditions 0x00..0x12.
Action 0x86 consumes one selector byte. Selector 1 terminates immediately;
other values display a confirmation request and terminate only when accepted.
Action 0x9b consumes two bytes without changing state. Actions 0x9c through
0xa0 consume the same operands as the later menu actions but otherwise do
nothing; this profile parses menu-construction bytecode without constructing a
menu.
Position actions 0x25 and 0x26 write both current and previous-position
coordinates directly and do not first remove an already drawn object.
Pictures support commands only through 0xf8. Automatic direction-loop
selection is evaluated independently of cadence and uses the four-direction
table only for exactly four loops. The profile exposes six string slots and
already recognizes 0x270f as the word-sequence tail terminator.
Showing a prepared picture, object-distance overflow, and initial target-motion
calculation follow the same early rules as profile 2.089. Action 0x4d has the
later behavior of clearing both direction and autonomous-motion mode. Action
0x4e clears autonomous-motion mode while retaining direction.
Inventory display is acknowledgement-only and leaves v25 unchanged, as in
profile 2.089. The interactive inventory selector is not available.
Both object partitions are composed in ascending drawing-key order, with object number breaking ties. Early sound scheduling uses one channel only for device selector zero and otherwise advances all four. Four-channel events emit both tone bytes and use the device-2 plus whole-byte adjustment rule in the sound chapter. The selected XMAS data uses 18 runtime object records and the five-block 2.272 save envelope.
Other observed versions
Other versions have been identified, but they do not yet have normative profiles in this book. Similarity to a profile above must not be assumed until observable differences have been checked and promoted into the specification.
Conformance Matrix
This chapter classifies the contracts in this book. It distinguishes behavior shared by the promoted profiles, explicit profile variants, dimensions supplied by the selected game data, unresolved behavior, and behavior outside the current target.
The classifications are:
| Classification | Meaning |
|---|---|
| Common | The stated contract applies to all promoted profiles. |
| Profile variant | The profile selects a stated alternative. |
| Game data | The format and interpretation are fixed, but counts, names, sizes, or values come from the selected game. |
| Partial | Some observable behavior is specified, but the listed gap prevents a complete claim for that domain. |
| Outside target | No compatibility claim is made for this behavior. |
An unlisted difference must not be inferred from a version-number similarity.
The common core applies to profiles 2.089, 2.230, 2.272, 2.411, 2.440, 2.917, 2.936, 3.002.086, 3.002.102, and 3.002.149. The two-column tables compare 2.936 and 3.002.149, while separate tables select the other promoted variants.
Common behavioral core
These contracts apply to every promoted profile unless a profile-variant row below says otherwise:
| Domain | Contract |
|---|---|
| Logic payloads | Code-length framing, message table, message XOR decoding, control-flow markers, conditions 0x00..0x12, and shared action semantics. |
| Runtime values | Unsigned byte variables, packed flags, 40-byte strings, parsed words, inventory locations, object selectors, and cycle-visible state transitions. |
| Pictures | Expanded command stream, prepare/overlay/show lifecycle, visual and priority/control channels, line/fill/pattern raster rules, and terminator behavior. |
| Views | Expanded view/loop/cel structure, row-run decoding, transparency, mirroring, baseline placement, and priority composition. |
| Objects | Lifecycle, placement, cadence, movement, collision, priority/control acceptance, cel cycling, draw partitions, and refresh ordering. |
| Input and text | Event records, parser normalization and matching, mapped keys, modal input, text geometry, font-input boundary, inventory selection, and menu construction. |
| Rooms and replay | Room-switch state changes, top-level re-entry, replay-pair language, checkpoints, and replay-without-rerecording. |
| Sound | Resource event structure, per-channel countdown scheduling, terminators, tone/divisor output, attenuation command output, completion flags, and stop behavior. |
Profile variants
Resource and bytecode variants
| Domain | 2.936 | 3.002.149 |
|---|---|---|
| Resource directory | Four split family directories. Any entry with first-byte high nibble 0xf is absent. | One combined prefixed directory, with split-directory fallback. Only ff ff ff is absent. |
| Volume record | Five-byte header; payload stored directly. | Seven-byte header; direct, dictionary-expanded, or picture-nibble-expanded payload. |
| Action range | 0x00..0xaf. | 0x00..0xb5. |
| Extra actions | None. | 0xb1 sets menu gate; 0xb5 clears release gate; 0xb0, 0xb2, 0xb3, and 0xb4 consume operands without another effect. |
| Condition range | 0x00..0x12. | 0x00..0x12. |
Runtime and presentation variants
| Domain | 2.936 | 3.002.149 |
|---|---|---|
| Script key-map capacity | 39 entries. | 49 entries. |
Release-event gate action 0xad | Increment modulo 256. | Set to one. |
| Menu interaction gate | No separate gate. | Action 0xb1 selects whether a pending request may enter modal interaction. |
Input-width actions 0xa3/0xa4 | Enable and clear the fixed width override. | No effect. |
| Immediate room aliases | None. | None in the base MH2 build; the Gold Rush build maps 0x7e, 0x7f, and 0x80 to 0x49. |
| Direction-selected loops | Four-or-more-loop table applies without f20. | Exactly four loops always use the table; more than four require f20. |
Persistence variants
| Domain | 2.936 | 3.002.149 |
|---|---|---|
| Save envelope | 31-byte description plus five u16le length-prefixed blocks. | Same envelope. |
| Block layout | All five observed game blocks are mapped in the persistence chapter. | All five observed Gold Rush blocks are mapped in the persistence chapter. |
| Block transform | No transform for block 3. | Block 3 is XOR-transformed with the specified repeating Avis Durgan key. |
| Binary save interchange status | Defined for the mapped game/profile dimensions; reserved bytes use canonical initialization and byte preservation. | Defined for the mapped Gold Rush/profile dimensions; reserved bytes use canonical initialization and byte preservation. |
2.089 variant selection
| Domain | 2.089 behavior |
|---|---|
| Resource container | Split direct resources; inventory metadata is stored expanded. |
| Action and condition ranges | Actions 0x00..0x9a; conditions 0x00..0x12. |
| Exit and menu actions | 0x86 is an unconditional zero-operand exit; 0x9b and later are unavailable. |
| Position and composition | Position actions remove old drawn state before updating the saved position. The earlier partition uses object-number order; the later partition uses drawing-key order. |
| Picture commands | Dispatch 0xf0..0xf8; no pattern-command slots. |
| Show picture | Present and mark shown without clearing f15 or closing an active text window. |
| Direction-selected loops | Evaluate every eligible post-logic pass, without a cadence gate; exactly four loops use the four-loop table. |
| String and parser profile | Six string slots; exact-count word matching without the 0x270f tail terminator. |
| Object distance and target motion | Store wrapped low-byte distance; defer the first target direction/completion calculation. |
| Movement-clear actions | 0x4d clears direction but not autonomous mode; 0x4e leaves both unchanged and only applies object-0 coupling/navigation effects. |
| Inventory display | Always acknowledgement-only; never writes a selected item to v25. |
| Sound output | Selector zero uses one channel; all others use four. Emit both tone bytes; device 2 adds 3 to low attenuation values below 8, then emit the control byte. |
| Save envelope | Four blocks; no logic-resume block. Block 1 has a complete semantic partition. |
| Binary save interchange status | Existing saves can be parsed and restored state can be preserved; pristine synthesis requires canonical initial reserved bytes that are not currently specified. |
2.230 variant selection
| Domain | 2.230 behavior |
|---|---|
| Resource container | Split direct resources; inventory metadata is stored expanded. |
| Action and condition ranges | Actions 0x00..0x9a; conditions 0x00..0x12. |
| Exit and menu actions | 0x86 is an unconditional zero-operand exit; 0x9b and later are unavailable. |
| Position and composition | Position actions update current/saved coordinates together. Both draw partitions use drawing-key order. |
| View loop encoding | The loop-header low nibble is the cel count; upper bits hold mutable loop-wide orientation and mirroring state. Action 0x31 masks to the low nibble. |
| Picture commands | Dispatch 0xf0..0xf8; no pattern-command slots. |
| Show picture | Same retained-f15 and retained-window behavior as 2.089. |
| Direction-selected loops | Same cadence-independent, exact-four behavior as 2.089. |
| String and parser profile | Six string slots; 0x270f has tail-terminator meaning. |
| Object distance and target motion | Same wrapped distance and deferred first target update as 2.089. |
| Movement-clear actions | Same as 2.089: 0x4d clears direction but not autonomous mode; 0x4e leaves both unchanged and only applies object-0 coupling/navigation effects. |
| Inventory display | Always acknowledgement-only; never writes a selected item to v25. |
| Sound output | Same as 2.089: selector zero uses one channel; all others use four; emit both tone bytes and apply only the device-2 low-attenuation adjustment. |
| Save envelope | Five blocks with a semantically partitioned 0x03db block 1. |
| Binary save interchange status | Existing state bytes can be parsed and preserved; pristine synthesis requires canonical initial reserved bytes that are not currently specified. |
2.272 variant selection
| Domain | 2.272 behavior |
|---|---|
| Resource container | Split direct resources; inventory metadata is stored expanded. |
| Action and condition ranges | Actions 0x00..0xa0; conditions 0x00..0x12. |
| Exit and menu actions | 0x86 consumes its selector; 0x9b consumes two bytes; 0x9c..0xa0 consume their operands without constructing menu state. |
| Position and composition | Position actions update current/saved coordinates together. Both draw partitions use drawing-key order. |
| Picture commands | Dispatch 0xf0..0xf8; no pattern-command slots. |
| Show picture | Same retained-f15 and retained-window behavior as 2.089. |
| Direction-selected loops | Same cadence-independent, exact-four behavior as 2.089. |
| String and parser profile | Six string slots; 0x270f has tail-terminator meaning. |
| Object distance and target motion | Same wrapped distance and deferred first target update as 2.089. |
| Movement-clear actions | 0x4d clears direction and autonomous mode; 0x4e clears autonomous mode while retaining direction. |
| Inventory display | Always acknowledgement-only; never writes a selected item to v25. |
| Sound output | Early channel selection; emit both tone bytes; apply the device-2 adjustment, then adjust and signed-clamp the entire control byte. |
| Save envelope | Five blocks with a semantically partitioned 0x03db block 1. |
| Binary save interchange status | Existing saves can be parsed and restored state can be preserved; pristine synthesis requires canonical initial reserved bytes that are not currently specified. |
2.411 variant selection
| Domain | 2.411 behavior |
|---|---|
| Resource container | Four split family directories and five-byte direct volume records. |
| Action and condition ranges | Actions 0x00..0xa9; conditions 0x00..0x12. |
| Configured messages | 0x97 and 0x98 consume message, row, column, and width. |
| Restart confirmation | Always display the confirmation prompt; f16 does not bypass it. |
| Heap diagnostic | Omit the rm.0, etc. line. |
| Pattern commands | 0xf9 consumes and ignores one byte; 0xfa writes one pixel per X,Y pair without seeds. |
| Direction-selected loops | Exactly four loops use the four-loop table; larger counts do not receive automatic selection. |
| Sound channels | Selector 0 uses channel 0; every nonzero selector uses all four channels. |
| Sound command output | No attenuation envelopes or device-2 adjustment; always emit both non-PC tone bytes. |
| Save envelope | Five blocks with a 0x05df block 1 and no block-3 transform. |
| Binary save interchange status | Defined for the mapped KQ2 dimensions. |
2.440 variant selection
| Domain | 2.440 behavior |
|---|---|
| Resource, opcode, configured-message, loop, and save profile | Same early boundaries as 2.411. |
| Restart confirmation | f16 bypasses the prompt and accepts restart immediately. |
| Heap diagnostic | Omit the rm.0, etc. line. |
| Pattern commands | Use the common shaped/stippled 0xf9/0xfa behavior. |
| Sound channels and attenuation | Early selector rule and no attenuation envelopes or device-2 adjustment. |
| Tone-byte output | Suppress the low byte when the high byte’s top three bits are all set. |
| Binary save interchange status | Defined for the mapped LSL1 dimensions. |
2.917 variant selection
| Domain | 2.917 behavior |
|---|---|
| Resource container | Four split family directories and five-byte direct volume records. |
| Action range | 0x00..0xad; 0xae and 0xaf are not valid actions. |
| Condition range | 0x00..0x12. |
| Script key-map capacity | 39 entries. |
Release-event gate action 0xad | Increment modulo 256. |
| Input-width actions | 0xa3 and 0xa4 retain their common effects. |
| Immediate room aliases | None. |
| Direction-selected loops | Exactly four loops use the four-loop table; more than four do not receive automatic selection. |
| Pictures, views, and objects | The full-EGA command, raster, composition, movement, collision, animation, update-list, and refresh contracts are common. |
| Save envelope | Five length-prefixed blocks with no block-3 transform. |
| Binary save interchange status | Defined for the mapped KQ1 dimensions; reserved bytes use canonical initialization and byte preservation. |
3.002.086 variant selection
| Domain | 3.002.086 behavior |
|---|---|
| Resource container | Combined v3 directory and seven-byte volume records with direct, dictionary-expanded, or picture-nibble-expanded payloads. |
| Action range and extra slots | 0x00..0xb1; 0xb0 consumes one ignored byte, and 0xb1 sets the menu gate. |
| Condition range | 0x00..0x12. |
| Script key-map capacity | 39 entries. |
Release-event gate action 0xad | Increment modulo 256. |
| Input-width actions | 0xa3 and 0xa4 retain their v2 effects. |
| Immediate room aliases | None. |
| Direction-selected loops | Four-or-more-loop table applies without f20. |
| Exact-zero left proposal | Clamp to zero and report left-boundary code 4. |
| Pictures, views, and objects | Other full-EGA command, raster, composition, movement, collision, animation, update-list, and refresh contracts are common. |
| Save envelope and transform | Five length-prefixed blocks; block 3 uses the v3 repeating-key XOR transform. |
| Binary save interchange status | Defined for the mapped full KQ4 dimensions; block 1 uses the 2.936 partition and reserved-state rules. |
3.002.102 variant selection
| Domain | 3.002.102 behavior |
|---|---|
| Resource container | Combined v3 directory and seven-byte volume records with direct, dictionary-expanded, or picture-nibble-expanded payloads. |
| Action range and extra slots | 0x00..0xb5; extra slots have the 3.002.149 contracts and effects. |
| Script key-map capacity | 39 entries. |
| Release and menu gates | 0xad sets the release gate to one; 0xb5 clears it; 0xb1 sets the menu interaction gate. |
| Input-width actions | 0xa3 and 0xa4 retain their 2.936 effects; closing window state clears the override. |
| Immediate room aliases | None. |
| Direction-selected loops | Exactly four loops always use the table; more than four require f20. |
| Pictures, views, and objects | The full-EGA command, raster, composition, movement, collision, animation, update-list, and refresh contracts are common. |
| Save envelope and transform | Five length-prefixed blocks; block 3 uses the v3 repeating-key XOR transform. |
| Binary save interchange status | Defined for the mapped KQ4D demo dimensions; reserved bytes use canonical initialization and byte preservation. |
Game-data dimensions
These values are not universal interpreter constants:
| Dimension | Source and effect |
|---|---|
| Resource presence and count | Directory entries determine which numbered logic, picture, view, and sound resources exist. |
| Resource bytes | Logic behavior, pictures, cels, sounds, messages, and dictionary words come from the selected game. |
| Combined-container prefix | The selected game supplies the v3 directory and volume filename prefix. |
| Inventory item count and names | The decoded inventory metadata header and item/name region define them. |
| Drawable-object count | The selected profile and game data define the runtime object-table capacity. In the fully mapped profiles this is maximum_drawable_object_index + 1 from inventory metadata; that derivation must not be assumed for an unmapped profile. |
| Save blocks 2 and 3 lengths | Object count and decoded inventory metadata length determine them. |
| Replay storage length | The configured replay-pair capacity determines block 4 length. |
| Logic-resume block length | The cache population at save time determines block 5 length. |
| Signature and save stem | The accepted game signature establishes candidate validation and filename stem. |
| Room and object initial state | Logic resources and inventory locations establish game-specific state within the common transition rules. |
Conformance tests must state both the interpreter profile and selected game data. A result obtained with one game does not establish another game’s counts, resource availability, names, save dimensions, or scripted behavior.
Partial domains
| Domain | Specified portion | Remaining limitation |
|---|---|---|
| Additional interpreter versions | They may be inventoried separately. | They have no normative profile until each observable delta is promoted. |
Outside the current target
The following behavior is excluded from a current full-EGA conformance claim:
- malformed resources that escape their declared payload and consume unrelated memory or execute unintended code;
- non-EGA display adapters and alternate display-mode rendering;
- exact text-glyph bitmaps unless a claim explicitly supplies a font profile;
- exact analog waveform synthesis beyond the specified sound-event, tone/divisor, and attenuation-command output boundary;
- original process memory organization, addresses, overlays, allocation strategy, and instruction-level timing; and
- unspecified failure presentation such as out-of-memory UI unless a chapter explicitly promotes that path.
Claim requirements
A 2.089 full-EGA gameplay claim requires the common core plus every 2.089 resource, opcode-range, exit, picture, parser, object, motion, inventory, composition, and sound variant. A 2.089 SQ1 binary save interchange claim additionally requires the selected SQ1 dimensions and a supplied rule for canonical initial reserved bytes or preservation of bytes from an existing save/state image.
A 2.230 full-EGA gameplay claim requires the common core plus every 2.230 resource, opcode-range, exit, packed-view-loop, picture, parser, object, motion, inventory, composition, and sound variant. A 2.230 XMAS.230 binary save interchange claim additionally requires the selected XMAS.230 dimensions and a supplied rule for canonical initial reserved bytes or preservation of bytes from an existing save/state image.
A 2.272 full-EGA gameplay claim requires the common core plus every 2.272 resource, opcode-range, exit/menu-stub, picture, parser, object, motion, inventory, composition, and sound variant.
A 2.272 XMAS binary save interchange claim additionally requires the selected XMAS dimensions and a supplied rule for canonical initial reserved bytes or preservation of bytes from an existing save/state image.
A 2.411 full-EGA gameplay claim requires the common core plus every 2.411 opcode-range, restart, diagnostic, point-pattern, object-loop, and sound-output variant. A 2.411 KQ2 binary save interchange claim additionally requires the mapped KQ2 dimensions, shortened block-1 partition, five-block mapping, first-match logic-resume lookup, replay reconstruction, and reserved-state rules.
A 2.440 full-EGA gameplay claim requires the common core plus every 2.440 opcode-range, restart, diagnostic, pattern, object-loop, and sound-output variant. A 2.440 LSL1 binary save interchange claim additionally requires the mapped LSL1 dimensions and the same early persistence rules.
A 2.936 full-EGA gameplay claim requires the common core, every 2.936 variant, selected-game dimensions, and all non-persistence state transitions. A 2.936 binary save interchange claim additionally requires the five-block mapping, first-match logic-resume lookup, replay reconstruction, and canonical initialization or byte-preservation of reserved state as applicable.
A 2.917 full-EGA gameplay claim requires the common core plus every 2.917 resource, opcode, input, room, and object-loop variant.
A 2.917 KQ1 binary save interchange claim additionally requires the mapped KQ1 dimensions, five-block mapping, first-match logic-resume lookup, replay reconstruction, and canonical initialization or byte-preservation of reserved state as applicable.
A 2.917 PQ1 binary save interchange claim uses the same profile rules with the mapped PQ1 object, inventory, replay, signature, and logic-resume dimensions.
A 2.936 KQ3 binary save interchange claim uses the 2.936 profile rules with the mapped KQ3 object, inventory, replay, signature, and logic-resume dimensions.
A 3.002.086 full-EGA gameplay claim requires the common core plus every 3.002.086 resource, opcode, input, menu, room, loop-selection, and exact-zero left-boundary variant.
A 3.002.086 full KQ4 binary save interchange claim additionally requires the mapped full-game dimensions, five-block mapping, block-3 transform, first-match logic-resume lookup, replay reconstruction, and canonical initialization or byte-preservation of reserved state as applicable.
A 3.002.149 full-EGA gameplay claim requires the common core plus every 3.002.149 resource, opcode, input, menu, room, and object-loop variant, and must state whether it selects the base room behavior or the Gold Rush alias variant.
A 3.002.149 Gold Rush binary save interchange claim additionally requires the five-block mapping, block-3 transform, first-match logic-resume lookup, replay reconstruction, and canonical initialization or byte-preservation of reserved state as applicable.
A 3.002.102 full-EGA gameplay claim requires the common core plus every 3.002.102 resource, opcode, input, menu, room, and object-loop variant.
A 3.002.102 KQ4D demo binary save interchange claim additionally requires the selected demo’s dimensions, five-block mapping, block-3 transform, first-match logic-resume lookup, replay reconstruction, and canonical initialization or byte-preservation of reserved state as applicable.
No claim for another interpreter version follows automatically from a promoted profile.
Conformance Results
This chapter defines a portable result format for comparing an implementation with reference observations. It is a test interchange convention, not a requirement on an implementation’s internal renderer, state layout, or test framework.
Bundle envelope
A result bundle is a UTF-8 JSON object with these fields:
| Field | Meaning |
|---|---|
format | The literal agi-clean-room-conformance-results. |
format_version | Integer 1 for this format. |
suite_id | Stable identifier for the selected case set. |
profile | Interpreter behavior profile claimed by the producer. |
producer | Human-readable identifier for the producing engine or oracle. |
cases | Array of case result objects. |
Each case has a stable string id, a status, and zero or more observations.
Status ok means the producer completed the case and emitted all required
observations. A producer may use error when execution or observation failed;
an error does not constitute a behavioral mismatch and cannot satisfy the
case.
Case identifiers are unique within a bundle. A comparison requires every reference case. A missing case, an unexpected additional case, an error, or a different deterministic observation is a failed comparison.
A successful case contains at least one observation. The reference case defines which observation fields are required. A candidate may emit additional observation fields, but it must exactly satisfy every observation required by the reference.
Canonical visual frame
The frame observation represents the visible game area after the case’s
specified synchronization point. It has these fields:
| Field | Required value |
|---|---|
width | 160 |
height | 168 |
pixel_format | ega16-indexed-row-major |
sha256 | Lowercase hexadecimal SHA-256 digest of the canonical bytes. |
artifact | Optional path, relative to the bundle, containing those bytes. |
The artifact contains exactly 26,880 bytes with no header. Each byte is one
EGA palette index in the range 0 through 15. Bytes proceed left to right, then
top to bottom. Coordinate (0, 0) is the upper-left pixel of the 160 by 168
game area. The format contains logical pixels, independent of host window
size, display scaling, aspect-ratio correction, or RGB palette calibration.
The digest is sufficient for an exact match. When both bundles provide valid
artifacts, a comparator may additionally report the number and bounding box of
differing logical pixels. Producers must verify that an artifact’s digest
matches its declared sha256 before comparing it.
Portable values
The values observation is a JSON object containing named semantic results.
It is used when pixels are not the complete result, including script-visible
state, input outcomes, ordered sound commands, and save/restore transitions.
Its member names and shapes are defined by the individual test case.
Values may contain only JSON null, Boolean, integer, string, array, and object values. Floating-point numbers are not allowed. Object keys are strings and their serialized order is insignificant. Array order is significant. Integer comparison is mathematical and does not prescribe a host integer width.
A comparison recursively requires identical member names, value types, scalar
values, and array order. Difference paths use JSON Pointer escaping. For
example, a mismatch in variable v3 under a variables member is reported at
/variables/v3.
Names describe externally observable concepts rather than interpreter storage. For example, a case may emit:
{
"values": {
"variables": {"v3": 7},
"flags": {"f9": true},
"sound_commands": [
{"tick": 0, "channel": 0, "kind": "tone", "divisor": 1193},
{"tick": 3, "channel": 0, "kind": "silence"}
]
}
}
A persistence case should report the semantic state and continuation outcome that survive a save/restore operation. It should not expose an implementation’s memory addresses or internal object representation. Raw save bytes are an appropriate additional result only for a profile whose binary save interchange format is part of that case’s conformance claim.
Nondeterministic cases
Only cases whose required observations are deterministic belong in a strict comparison suite. Behavior that permits multiple outcomes requires a case-specific acceptance rule or a controlled input such as a supplied random choice. An exploratory observation must not be converted into a deterministic failure merely by recording one reference run.
For example, autonomous random motion permits a stationary result when the selected direction is zero. Such an unconstrained run is not part of the strict visual-frame suite. A test that supplies the random choice, or that tests a deterministic transition such as clearing random motion before the next movement update, may be included.
An implementation in any language conforms to the interchange convention when it emits the required fields, canonical frame bytes, and/or portable values defined by the selected cases. No particular adapter, executable, or test framework is required.
Resource Containers
AGI game data is divided into four resource families:
| Family | Purpose |
|---|---|
| Logic | Executable game bytecode and its messages. |
| Picture | Commands that construct the room’s visual and priority/control surfaces. |
| View | Animated cel images used for actors, objects, and previews. |
| Sound | Timed tone and attenuation events. |
Within a family, bytecode addresses a resource by an unsigned 8-bit resource number. A directory maps that number to a volume file and byte offset. Loading a resource removes its volume-record header and, when required by the profile, expands its stored representation before passing the payload to the family-specific decoder.
All 16-bit integers in the container formats are little-endian.
Only the first 256 entries of each directory section are addressable. Bytes after those entries do not define additional resources, even when they can be grouped into directory-like triples.
Directory entries
Both current container profiles use three-byte directory entries:
volume = entry[0] >> 4
offset = ((entry[0] & 0x0f) << 16) | (entry[1] << 8) | entry[2]
The volume occupies the high nibble of the first byte. The remaining 20 bits form a big-endian byte offset within that volume.
Each profile defines its own absent-entry rule. A conforming engine must apply the rule for the selected profile before attempting to open a volume.
Version 2 split container
The split container uses one directory file per family:
| Family | Directory file |
|---|---|
| Logic | LOGDIR |
| Picture | PICDIR |
| View | VIEWDIR |
| Sound | SNDDIR |
Each complete three-byte group in a directory is one resource entry. An entry
is absent when the high nibble of its first byte is 0xf. Consequently,
ff ff ff is absent, but it is not the only byte pattern treated as absent.
Volume files are named VOL.N, where N is the decoded volume number. A
resource record at the directory offset has this five-byte header:
| Byte | Meaning |
|---|---|
0 | Magic byte 0x12. |
1 | Magic byte 0x34. |
2 | Volume number; must match the directory entry. |
3..4 | Payload length. |
The header is followed by exactly payload length bytes. The split profile
does not transform those bytes: the stored payload is the expanded resource
payload.
Inventory metadata file
The inventory metadata file has the following expanded form:
item_table_size:u16le
maximum_drawable_object_index:u8
runtime_inventory_data[]
Profiles 2.089, 2.230, and 2.272 store these expanded bytes directly. The later observed version-2 profiles XOR-decode the file byte for byte with the repeating ASCII key:
Avis Durgan
runtime_inventory_data begins with item_table_size / 3 item entries. The
size must be divisible by three. Each entry is:
name_offset:u16le
location:u8
Name offsets are relative to the start of runtime_inventory_data. The name
pool begins at item_table_size; names are zero-terminated byte strings. More
than one item may refer to the same name. The one-based drawable-object record
count is maximum_drawable_object_index + 1.
For the observed 2.936 game data, the header is 78 00 14: 40 inventory
entries occupy 120 bytes and 21 drawable-object records are available. The
complete decoded file is 331 bytes, leaving 328 runtime bytes after the header.
Version 3 combined container
The combined container uses a game-specific filename prefix P. Its primary
directory is named PDIR, and its volumes are named PVOL.N. For example, a
prefix of GR produces GRDIR and GRVOL.N.
The first eight bytes of the combined directory are four section offsets:
| Bytes | Section |
|---|---|
0..1 | Logic entries. |
2..3 | Picture entries. |
4..5 | View entries. |
6..7 | Sound entries. |
The logic section extends from its offset to the picture offset, the picture
section to the view offset, the view section to the sound offset, and the sound
section to end of file. Up to the first 256 complete three-byte groups in each
section are directory entries. In this profile, only the exact sequence
ff ff ff denotes an absent resource.
If the combined directory cannot be opened, this profile falls back to the four split directory filenames. This fallback changes directory discovery but does not change the seven-byte volume-record format below.
Volume record
A combined-profile volume record has this header:
| Byte | Meaning |
|---|---|
0 | Magic byte 0x12. |
1 | Magic byte 0x34. |
2 | Volume metadata. Bit 0x80 selects picture-nibble expansion; the low nibble identifies the volume when that bit is set. |
3..4 | Expanded payload length. |
5..6 | Stored payload length. |
When metadata bit 0x80 is clear, the entire metadata byte must match the
directory volume number. When it is set, the metadata low nibble must match.
Exactly stored length bytes follow the header.
The payload transform is selected in this order:
- If metadata bit
0x80is set, apply picture-nibble expansion. - Otherwise, if stored and expanded lengths are equal, use the bytes directly.
- Otherwise, apply dictionary expansion.
The resulting payload must contain exactly the declared expanded length.
Dictionary expansion
Dictionary-compressed data is a least-significant-bit-first stream of variable
width codes. Codes do not have to begin on byte boundaries. A valid compressed
resource begins with reset code 0x100.
| Code | Meaning |
|---|---|
0x000..0x0ff | One literal byte. |
0x100 | Reset the dictionary and code width. |
0x101 | End the expanded stream. |
0x102 and above | Dictionary entries. |
After reset, the code width is 9 bits and the next dictionary index is
0x102. The first non-end code after reset is expanded and emitted without
adding an entry.
For each later ordinary code:
- Expand the code to its byte string. The special next-code case expands to the previous string followed by the previous string’s first byte.
- Emit the expanded string.
- Add
previous string + first byte of current stringat the next dictionary index. - Advance the next index and make the current string the previous string.
When the next index reaches 0x200, increase the code width to 10 bits. When
it reaches 0x400, increase the width to 11 bits. A reset returns the width to
9 bits, clears generated entries, and restores the next index to 0x102.
Expansion ends at code 0x101. For a valid record, the number of emitted bytes
equals the expanded length in the volume header.
Picture-nibble expansion
The picture transform packs an expanded picture command stream as a sequence of nibbles. Nibbles are stored high nibble first within each byte.
Ordinary expanded bytes use two nibbles: high nibble followed by low nibble.
The color operand immediately following picture command 0xf0, and the
control-color operand immediately following command 0xf2, use only one
nibble. After that operand, ordinary two-nibble decoding resumes.
For example:
stored: f0 af 2b ff
expanded: f0 0a f2 0b ff
Expansion stops after emitting command 0xff. A low zero nibble may pad the
stored stream when the meaningful nibble count is odd. The emitted byte count
must equal the expanded length in the volume header.
Valid-data boundary
This chapter defines valid resource lookup and expansion. It does not specify behavior for offsets outside a volume, incorrect magic, mismatched volume metadata, truncated records, unknown dictionary codes, or expansion beyond the declared length. Such data is not part of the current compatibility target.
Core Runtime State
This chapter defines the portable state shared by the behavior in later chapters. It describes values and lifetimes, not a required memory layout.
Unless a version profile says otherwise, the rules in this chapter currently apply to the AGI 2.936 profile.
Byte variables
The game has 256 variables, numbered v0 through v255. Each stores an
unsigned 8-bit value.
The common numeric operations have these value rules:
- increment saturates at
255; - decrement saturates at
0; - addition and subtraction retain the low 8 bits;
- multiplication retains the low 8 bits of the product;
- division stores the unsigned 8-bit quotient; and
- indirect operations use a variable’s value as the index of another variable.
Unsigned variable comparisons compare values in the range 0..255.
Division by zero is outside the valid-bytecode contract until a defined
observable result is added to this specification.
Some variables also have engine-defined roles. Those roles do not change the
fact that scripts may read and write the same byte. In the current profile,
v0 identifies the current room and v10 controls cycle pacing.
Flags
The game has 256 Boolean flags, numbered f0 through f255. Operations may
set, clear, toggle, or test a flag directly, or use a byte variable as the flag
number.
Some flags also carry engine-defined state. For example, sound playback tests
f9, and room entry logic commonly tests a new-room flag. A subsystem chapter
defines the observable meaning whenever an engine operation assigns one.
Strings and parsed words
The current profile exposes twelve string slots, numbered s0 through s11.
Each slot has 40 bytes of storage. Operations that edit, copy, compare, or parse
a slot define their own truncation and normalization rules.
Parsed input is distinct from string storage. The parser produces at most ten nonzero dictionary word identifiers, an optional unknown-word position, and parser status flags. Words whose dictionary identifier is zero do not occupy a parsed-word slot. Conditions that match parsed input consume this parsed state, not the original string bytes.
Logic activations
A loaded logic resource consists of bytecode, messages, and resumable execution state. The engine tracks a current logic activation containing at least:
- the logic resource number;
- the bytecode start;
- the current instruction position; and
- the resource’s message table.
Logic 0 is invoked once per top-level engine cycle. A logic may call another logic. Nested calls preserve the caller’s current activation and restore it when the callee returns.
Loading and executing are separate operations. A logic may remain loaded after execution, while a logic loaded only for a nested call may be discarded when that call returns. Explicit resume-position operations may save the current instruction position and later restore the logic’s entry position.
Resource lifecycle
Each resource family has an independent loaded-resource set keyed by resource number. The portable lifecycle is:
| State | Meaning |
|---|---|
| Unloaded | The resource has no payload available to consumers. |
| Loaded | Directory lookup, volume reading, and expansion have produced the typed resource payload. |
| Selected | A logic activation, picture operation, object, or sound operation is using the loaded resource. |
| Discarded | The resource is no longer loaded; a later use requires another load. |
Loading an already loaded resource does not create a second game-visible identity or change its retention order. Successful first loads within each picture or view family establish an order. Discarding one of those resources also discards every resource loaded later in the same family. For example, if views 4, 9, and 12 were first loaded in that order, discarding view 9 leaves only view 4 retained. Views 9 and 12 must be loaded again before later use.
A valid discard names a retained resource that is not still selected by a live object or in-progress picture operation. Behavior that continues to use a discarded payload is outside the valid lifecycle contract. A family reset invalidates every retained resource in that family.
Room changes clear transient resource and object state, load the destination logic, update the current-room variables, and establish new-room state. The resource-event replay used by restore is specified separately from ordinary loading because replay must rebuild selected resources without recursively recording the replay itself.
Objects and inventory items
Persistent drawable objects are identified by an unsigned byte index. Their portable state includes:
- current and previous position;
- selected view, loop, and cel;
- cel width and height;
- step size and direction;
- animation interval and animation mode;
- motion mode and mode-specific parameters;
- priority and control behavior;
- participation in update and draw processing; and
- collision, horizon, cycling, and direction-selection options.
The original storage representation is not part of this specification. Object operations are defined in terms of the fields and transitions above.
Inventory items have an item number, display name, and one-byte location or
state value. Location value 0xff means the item is carried for the inventory
selection operation.
Logical graphics state
The full-EGA target uses a logical picture area 160 cells wide and 168 cells high. Each cell has two independent four-bit values:
- a visual color in
0..15; and - a priority/control value in
0..15.
Picture commands may modify either or both values. View cels contribute visual color and object priority according to the drawing rules. Normal display output shows the visual values; diagnostic priority display exposes the priority/control values.
The logical resolution is the compatibility coordinate system. A display backend may scale those cells to physical pixels as long as the resulting logical image and input coordinate behavior are preserved.
Top-level cycle order
Timer and sound ticks are asynchronous inputs to the cycle rather than an ordered substep inside it. Timer ticks advance the elapsed-time state and the cycle-pacing counter. While sound is active, sound ticks advance the playback state specified in the sound chapter. A deterministic conformance test must therefore supply the same tick and input-event sequence relative to cycle boundaries; an implementation does not have to reproduce interrupt delivery between individual bytecode instructions.
At each synchronous cycle boundary, the engine performs this observable order:
- Wait until the pacing counter reaches
v10, then clear that counter for the next accumulation period. - Clear all transient mapped-event status values and clear
f2andf4. - Update due timed input sources, then process pending input and any requested
modal menu interaction. This input phase clears the previous raw-key byte
v19and parser count/error bytev9before consuming events. Mapped status events produced here remain visible to logic for the rest of the cycle. - Apply the configured object-0/global-direction mirror.
- Perform the pre-logic autonomous-motion and configured-boundary pass for eligible objects.
- Remember the status-line values
v3andf9, then execute logic 0, including nested logic calls made by that bytecode. - If logic execution requests immediate re-entry, clear
v9,v4,v5, andf2, update the rememberedv3value, and invoke logic 0 again without repeating pacing, input, direction mirroring, or pre-logic motion. Repeat this rule until logic 0 completes normally. - Restore object 0’s direction from the global direction byte. If
v3orf9changed during logic execution and the status line is enabled, redraw the status line. - Clear object event bytes
v4andv5and cycle flagsf5,f6, andf12. These values remain available to logic until this point. - If alternate text mode is inactive, perform direction-based loop selection, animation timers, object movement, drawing, and dirty-region refresh. If alternate text mode is active, skip that entire post-logic object-update stage for this cycle.
v10 is the cycle-speed value. The pacing stage waits until at least that many
timer increments have accumulated and then begins a new accumulation period.
Lower values therefore permit faster logic cycles. A host may use a different
clocking mechanism, but script-visible ordering and relative pacing must remain
equivalent.
Calling the current room logic is not an implicit fifth engine stage. Games
normally perform that dispatch from logic 0, commonly by calling the logic
whose number is stored in v0.
Logic Bytecode
This chapter defines the logic resource framing, bytecode stream grammar, all conditions, and the first promoted action families. Unless a version profile says otherwise, opcode ranges and effects refer to AGI 2.936.
Logic payload
An expanded logic resource has this layout:
u16 code_length
u8 bytecode[code_length]
u8 message_count
u16 message_offset[message_count + 1]
u8 encrypted_message_text[]
The bytecode begins immediately after code_length. The message table begins
immediately after the bytecode. Every message offset is relative to the first
byte of the message-offset table.
Offset entry 0 identifies the end of the encrypted message-text region.
Entries 1 through message_count identify the corresponding game-visible
messages. A zero offset does not identify a valid message.
The encrypted region begins after all message_count + 1 table entries and
ends at table entry 0. Its bytes are XORed with the repeating ASCII key:
Avis Durgan
The key restarts after its final n byte. The offset table is not encrypted.
Messages in the resulting text region are zero-terminated byte strings.
Operand notation
An action or condition opcode is followed by its operands. Unless an operation states otherwise, each operand occupies one byte.
| Notation | Meaning |
|---|---|
imm | The operand byte itself. |
vN | Variable whose index is the operand byte. |
fN | Flag whose index is the operand byte. |
item | Inventory-item number. |
object | Persistent-object number. |
resource | Logic, picture, view, or sound resource number. |
message | Message number in the current logic resource. |
An operation described as variable-selected reads the operand byte as a variable index and then uses that variable’s value as the effective argument.
Main stream grammar
The main bytecode stream recognizes three structural opcodes before ordinary action dispatch:
| Byte | Encoding | Behavior |
|---|---|---|
0x00 | 00 | End the current logic invocation and return normally to its caller, if any. |
0xfe | fe delta:s16le | Add the signed displacement to the position immediately after the displacement. |
0xff | ff conditions ff delta:s16le | Evaluate a conditional block as described below. |
Bytes 0x01..0xaf are action opcodes in the 2.936 profile. Bytes
0xb0..0xfb are not valid actions in that profile. Bytes 0xfc and 0xfd
are condition-list markers and are invalid in the ordinary action stream.
Profiles 2.411 and 2.440 end at 0xa9. The 2.917 profile ends at 0xad.
Profile 3.002.086 extends the range through 0xb1; profiles 3.002.102 and
3.002.149 extend it through 0xb5, as specified in
Version Profiles.
Conditional blocks
A conditional block has this form:
ff
condition-list
ff
false-delta:s16le
true-path bytecode
When the list succeeds, execution skips the two displacement bytes and begins the true path. When the list fails, the signed displacement is added to the position after those two bytes.
The condition list recognizes these markers:
| Byte | Meaning |
|---|---|
0xfc | Begin or end an OR group. |
0xfd | Invert the result of the next predicate. |
0xff | End the condition list. |
Outside an OR group, every predicate must be true. A false predicate fails the list immediately.
The first 0xfc begins an OR group. Predicates are evaluated until one is
true or a second 0xfc is reached. A true term satisfies the group and skips
the remaining terms through the closing marker. Reaching the closing marker
without a true term fails the entire list. Evaluation then resumes with normal
AND behavior after a successful group.
0xfd affects one predicate only and is cleared after that predicate has been
evaluated.
Condition opcodes
All comparisons of variables and coordinates below are unsigned and inclusive where bounds are stated.
Scalar and flag conditions
| Opcode | Operands | True when |
|---|---|---|
0x00 | none | Never. |
0x01 | variable, immediate | The variable equals the immediate byte. |
0x02 | variable A, variable B | A equals B. |
0x03 | variable, immediate | The variable is less than the immediate byte. |
0x04 | variable A, variable B | A is less than B. |
0x05 | variable, immediate | The variable is greater than the immediate byte. |
0x06 | variable A, variable B | A is greater than B. |
0x07 | flag | The selected flag is set. |
0x08 | variable | The flag whose number is stored in the variable is set. |
Inventory and event conditions
| Opcode | Operands | True when |
|---|---|---|
0x09 | item | The item’s location/state byte is 0xff. |
0x0a | item, variable | The item’s location/state byte equals the variable value. |
0x0c | status number | The selected mapped-event status byte is nonzero. |
0x0d | none | A raw key event is available, or a previously captured raw key remains pending. |
Condition 0x0d stores the low byte of a newly captured raw key in v19.
If v19 is already nonzero, the condition succeeds without dequeuing another
event. While looking for a key, non-key events are discarded. A zero key value
does not satisfy the condition.
Parsed-input condition
Condition 0x0e is variable length:
0e count:u8 word_id:u16le[count]
It can succeed only when parsed-input-ready state is set, parsed input is nonempty, and the current parsed input has not already matched successfully. It compares operand word identifiers with parsed words in order.
Operand word ID 0x0001 matches any one parsed word. Operand word ID 0x270f
terminates the pattern successfully even when additional parsed words remain.
A successful match marks the current parsed input as consumed by a successful
word-sequence condition. A mismatch is false and does not set that marker.
A terminator-only pattern may also match parser state that stopped at an unknown token, provided the parser recorded a nonzero token/error position.
Because this condition is variable length, condition scanners must skip one
count byte followed by count * 2 word-ID bytes.
String condition
Condition 0x0f takes two string-slot numbers. It independently normalizes
both slots, then compares the resulting zero-terminated strings exactly.
Normalization removes space, tab, ., ,, ;, :, apostrophe, !, and
-, and converts ASCII A..Z to lowercase. This is direct string comparison;
it does not use dictionary parsing or parsed-word state.
Object rectangle conditions
Conditions 0x0b, 0x10, 0x11, and 0x12 each take:
object, left, top, right, bottom
The vertical coordinate is always the object’s baseline Y and must satisfy
top <= baseline <= bottom.
| Opcode | Horizontal point or span | True when |
|---|---|---|
0x0b | Left X | left <= x <= right. |
0x10 | Full span from X through x + width - 1 | Both ends lie within the horizontal bounds. |
0x11 | x + floor(width / 2) | The center lies within the horizontal bounds. |
0x12 | x + width - 1 | The right edge lies within the horizontal bounds. |
Condition bytes 0x13..0xfb are not valid predicates in the 2.936 profile,
except for structural markers 0xfc, 0xfd, and 0xff.
Action opcodes: scalar state
| Opcode | Operands | Behavior |
|---|---|---|
0x01 | variable | Increment, saturating at 255. |
0x02 | variable | Decrement, saturating at 0. |
0x03 | variable, immediate | Assign the immediate byte. |
0x04 | destination variable, source variable | Copy the source value. |
0x05 | variable, immediate | Add and retain the low 8 bits. |
0x06 | destination variable, source variable | Add the source and retain the low 8 bits. |
0x07 | variable, immediate | Subtract and retain the low 8 bits. |
0x08 | destination variable, source variable | Subtract the source and retain the low 8 bits. |
0x09 | index variable, source variable | Store the source value in the variable whose index is held by the index variable. |
0x0a | destination variable, index variable | Read the variable whose index is held by the index variable into the destination. |
0x0b | index variable, immediate | Store the immediate byte in the variable whose index is held by the index variable. |
Action opcodes: flags
| Opcode | Operands | Behavior |
|---|---|---|
0x0c | flag | Set the flag. |
0x0d | flag | Clear the flag. |
0x0e | flag | Toggle the flag. |
0x0f | variable | Set the flag whose number is stored in the variable. |
0x10 | variable | Clear the flag whose number is stored in the variable. |
0x11 | variable | Toggle the flag whose number is stored in the variable. |
Action opcodes: room and logic control
| Opcode | Operands | Behavior |
|---|---|---|
0x12 | destination logic/room | Switch immediately to the selected room state. |
0x13 | variable | Switch to the room whose number is stored in the variable. |
0x14 | logic resource | Load and retain the logic resource. |
0x15 | variable | Load and retain the logic resource whose number is stored in the variable. |
0x16 | logic resource | Invoke the selected logic. |
0x17 | variable | Invoke the logic whose number is stored in the variable. |
A logic invocation temporarily makes the callee the current logic, including
its message table. The caller activation is restored afterward. A callee that
was not already retained may be unloaded after the call. Ordinary opcode
0x00 termination returns to the caller’s next action. A callee action that
deliberately aborts logic flow with a zero continuation result propagates that
abort and ends the caller’s current invocation as well.
Room switching performs these observable state changes:
- Stop active sound and reset transient update, parser, object, and room resource state while retaining the global logic needed for the next cycle.
- Copy old
v0tov1, store the destination inv0, and preserve object 0’s selected view number inv16. - Load the destination logic resource.
- Consume entry-boundary selector
v2, repositioning object 0 when selected, then clearv2. - Set the new-room flag
f5and refresh normal status/input display state. - Terminate the current logic invocation. The next top-level pass begins with logic 0; the destination logic is not executed implicitly by the room-switch action.
Entry-boundary selectors are:
v2 | Object 0 placement |
|---|---|
1 | Baseline Y becomes 167. |
2 | X becomes 0. |
3 | Baseline Y becomes 37. |
4 | X becomes 160 - object width. |
The 3.002.149 profile applies its room-number aliases before these common room effects.
Action opcodes: picture and view resources
| Opcode | Operands | Behavior |
|---|---|---|
0x18 | variable | Load the picture whose number is stored in the variable. |
0x19 | variable | Clear/reset the logical picture surfaces, then decode the already loaded picture selected by the variable. The result is not required to become visible until a show action. |
0x1a | none | Present the prepared logical visual surface, clear f15, and mark the picture as shown. |
0x1b | variable | Discard the loaded picture selected by the variable and every picture retained after it. |
0x1c | variable | Decode the already loaded picture selected by the variable over the existing logical surfaces without the prepare-time clear. Visibility still requires a show action. |
0x1d | none | Temporarily display priority/control values, wait for an input event, then restore the normal visual display. |
0x1e | view resource | Load the selected view resource. |
0x1f | variable | Load the view whose number is stored in the variable. |
0x20 | view resource | Discard the selected loaded view and every view retained after it. |
Loading, preparing, overlaying, and showing a picture are distinct operations. An engine must not make an overlay visible merely because its logical surfaces changed when no show operation followed.
Action opcodes: object setup and view selection
| Opcode | Operands | Behavior |
|---|---|---|
0x21 | object | If not already update-eligible, enable update eligibility and automatic cel cycling, select the later-drawn partition, and clear direction, motion mode, and frame-cycling mode. Otherwise leave the object unchanged. |
0x22 | none | Clear active and update-eligible state from every persistent object. |
0x23 | object | Activate an object that has a selected cel, snapshot its current cel and position, and add it to update/draw processing. |
0x24 | object | Deactivate the object and remove it from update/draw processing. |
0x25 | object, X, Y | Set both current and previous position to the immediate coordinates. |
0x26 | object, X variable, Y variable | Set both current and previous position from variables. |
0x27 | object, X destination, Y destination | Store current X and baseline Y in the destination variables. |
0x28 | object, X-delta variable, Y-delta variable | Add signed 8-bit deltas to current position, clamp negative underflow to zero, mark the object newly positioned, and run placement. |
0x29 | object, view | Bind the loaded view, then select its default loop and cel. |
0x2a | object, view variable | Bind the loaded view selected by a variable. |
0x2b | object, loop | Select the loop and its default cel. |
0x2c | object, loop variable | Select the loop named by a variable. |
0x2d | object | Disable automatic direction-based loop selection. |
0x2e | object | Enable automatic direction-based loop selection. |
0x2f | object, cel | Select a cel, refresh its dimensions, clamp placement if required, and remove the one-cycle animation-start delay. |
0x30 | object, cel variable | Select the cel named by a variable with the same effects. |
0x31 | object, destination variable | Store the highest valid cel index in the selected loop. |
0x32 | object, destination variable | Store the selected cel index. |
0x33 | object, destination variable | Store the selected loop index. |
0x34 | object, destination variable | Store the selected view number. |
0x35 | object, destination variable | Store the number of loops in the selected view. |
Selecting a view, loop, or cel requires the referenced view to be loaded and the selected index to be valid for that view. Cel selection updates width and height before applying placement bounds.
In profile 2.230, action 0x31 treats the selected loop header’s low nibble as
the cel count, subtracts one, and stores the resulting highest valid index.
Other promoted profiles subtract one from the ordinary full-byte cel count.
Action opcodes: priority and update participation
| Opcode | Operands | Behavior |
|---|---|---|
0x36 | object, priority | Select fixed priority and store the supplied value. |
0x37 | object, priority variable | Select fixed priority from a variable. |
0x38 | object | Disable fixed priority so priority is derived from baseline Y. |
0x39 | object, destination variable | Store the object’s current priority value. |
0x3a | object | Move an active object to the earlier-drawn update partition. |
0x3b | object | Move an active object to the later-drawn update partition. |
0x3c | object | Flush, rebuild, draw, and refresh all object update lists. The operand does not select a narrower refresh. |
0x3d | object | Exempt the object from the horizon clamp. |
0x3e | object | Re-enable the horizon clamp. |
0x3f | baseline | Set the horizon. A nonexempt object at or above it is placed at least one row below it. |
0x40 | object | Enable the post-footprint-scan gate that rejects when final class-state flag 0 is clear. |
0x41 | object | Enable the post-footprint-scan gate that rejects when final class-state flag 0 is set. |
0x42 | object | Clear both class-2 and class-3 rejection options. |
0x43 | object | Exempt the object from object-object crossing/collision tests. |
0x44 | object | Re-enable object-object crossing/collision tests. |
0x45 | object A, object B, destination variable | Store center-X plus baseline-Y Manhattan distance, capped at 254; store 255 if either object is inactive. |
Objects in the earlier partition are drawn before all objects in the later partition. Within a partition, objects are ordered by their drawing key; equal keys retain object-number order.
Action opcodes: animation
| Opcode | Operands | Behavior |
|---|---|---|
0x46 | object | Disable automatic cel cycling. |
0x47 | object | Enable automatic cel cycling. |
0x48 | object | Cycle forward, wrapping from the last cel to cel 0. |
0x49 | object, completion flag | Begin forward cycling toward the last cel; clear the flag now, then set it and stop cycling on completion. |
0x4a | object | Cycle backward, wrapping from cel 0 to the last cel. |
0x4b | object, completion flag | Begin backward cycling toward cel 0; clear the flag now, then set it and stop cycling on completion. |
0x4c | object, interval variable | Set both the cel-cycle reload interval and current countdown from a variable. |
Cycling is countdown-driven. When enabled, a nonzero countdown is decremented
once per eligible object-update cycle. Reaching zero advances according to the
selected mode and reloads the interval. Modes 0x49 and 0x4b have a
one-callback startup delay before their first cel change. Their completion path
also clears object direction and returns the animation mode to forward wrap.
Action opcodes: movement
| Opcode | Operands | Behavior |
|---|---|---|
0x4d | object | Clear direction and autonomous motion. For object 0, clear v6 and select object-to-v6 direction coupling. |
0x4e | object | Clear autonomous motion without clearing object direction. For object 0, clear v6 and select v6-to-object direction coupling. |
0x4f | object, step variable | Set movement step size from a variable. |
0x50 | object, cadence variable | Set the object’s movement/update cadence countdown from a variable and restart its cadence phase. |
0x51 | object, target X, target Y, step override, completion flag | Begin movement toward an immediate target. |
0x52 | object, X variable, Y variable, step variable, completion flag | Begin movement toward a variable-selected target. |
0x53 | object, near threshold, completion flag | Move autonomously toward object 0 until within the threshold. |
0x54 | object | Begin autonomous random-direction motion. |
0x55 | object | Stop the autonomous motion mode without clearing current direction. |
0x56 | object, direction variable | Set direction from a variable. |
0x57 | object, destination variable | Store current direction. |
0x58 | object | Ignore configured movement-rectangle transitions and permit control value 1 in the footprint scan. |
0x59 | object | Enforce configured movement-rectangle transitions and reject control value 1 in the footprint scan. |
0x5a | left, top, right, bottom | Enable the global movement rectangle. Inside membership uses strict comparisons against all four bounds. |
0x5b | none | Disable the global movement rectangle. |
Targeted movement stores the original step size, clears the completion flag, and uses the nonzero step override temporarily. A zero override preserves the existing step size. Completion occurs when both signed target deltas are strictly between negative step and positive step, or when placement reaches the corresponding reachable screen boundary. Completion restores the original step, stops the motion mode, and sets the flag.
Approach motion uses object centers horizontally and baselines vertically. It sets its completion flag and stops when the target-direction calculation reports that the objects are near enough. If movement is blocked, it may choose a temporary random nonzero direction and retry after a delay.
Direction values are:
| Value | Direction |
|---|---|
0 | Stationary. |
1 | Up. |
2 | Up-right. |
3 | Right. |
4 | Down-right. |
5 | Down. |
6 | Down-left. |
7 | Left. |
8 | Up-left. |
Object 0 and v6 have a cycle-level direction coupling selected by actions
0x83 and 0x84. In object-to-variable mode, the pre-logic stage copies the
object direction to v6. In variable-to-object mode, it copies v6 to the
object. After logic, object 0 direction is restored from v6 before movement.
Action opcodes: inventory locations
| Opcode | Operands | Behavior |
|---|---|---|
0x5c | item | Set the item’s location to 0xff (carried). |
0x5d | item variable | Set the variable-selected item’s location to 0xff. |
0x5e | item | Set the item’s location to 0. |
0x5f | item, location variable | Set the immediate item’s location from a variable. |
0x60 | item variable, location variable | Set a variable-selected item’s location from a variable. |
0x61 | item variable, destination variable | Store a variable-selected item’s location. |
Action opcodes: sound
| Opcode | Operands | Behavior |
|---|---|---|
0x62 | sound resource | Load the sound. |
0x63 | sound resource, completion flag | Stop prior playback, clear the supplied flag, and start the loaded sound. |
0x64 | none | Stop active sound and set its remembered completion flag. |
The complete resource and scheduling contract is in Sound Resources and Playback.
Action opcodes: text and input
| Opcode | Operands | Behavior |
|---|---|---|
0x65 | message | Display the current-logic message modally and return after acknowledgement. |
0x66 | message variable | Display the variable-selected message. |
0x67 | row, column, message | Expand substitutions in the message and display it at the configured text position. |
0x68 | row variable, column variable, message variable | Variable-selected form of 0x67. |
0x69 | top row, bottom row, attribute | Clear full-width text rows inclusively with the selected text attribute. |
0x6a | none | Enter the alternate full-screen text-attribute mode and clear/fill its visible surface. Object graphics updates are suppressed while this mode is active. |
0x6b | none | Leave alternate text mode, restore normal graphics updates, and redraw status/input areas. |
0x6c | message | Use the first byte of the message as the input prompt marker; an empty message suppresses the marker. |
0x6d | foreground, background | Set the text color/attribute pair used by text windows and alternate text mode. |
0x6e | count | Shake the visible display for the requested count, with pacing between offsets. |
0x6f | display base row, input row, status row | Configure text/input/status row placement. |
0x70 | none | Enable and redraw the status line at its configured row. |
0x71 | none | Disable and clear the status line at its configured row. |
0x72 | string slot, message | Copy the message into the 40-byte string slot. |
0x73 | string slot, prompt message, row, column, maximum length | Clear the slot, display the prompt, edit input at the optional position, and store the accepted zero-terminated text. Accepted storage is limited to min(maximum length + 1, 40) bytes. |
0x74 | string slot, parsed-word index | Copy the selected normalized parsed-word text into the string slot. |
0x75 | string slot | Clear prior parser-match state and parse the slot into dictionary word identifiers. Slot numbers outside 0..11 produce no parse. |
0x76 | prompt message, destination variable | Prompt for up to four decimal characters, parse the accepted number, and store its low 8 bits. |
0x77 | none | Disable and clear the input line. |
0x78 | none | Enable and redraw the input line. |
0x79 | key low byte, key high byte, status number | Add a key mapping for the little-endian key word. A matching raw key becomes a mapped event carrying the status number. |
Profiles 2.411, 2.440, 2.917, 2.936, 3.002.086, and 3.002.102 accept at most
39 key-map entries. Mapped events set the status observed by condition 0x0c.
The 3.002.149 profile accepts 49 entries.
Action opcodes: transient views and inventory UI
| Opcode | Operands | Behavior |
|---|---|---|
0x7a | view, loop, cel, X, baseline Y, priority, control/margin | Draw the selected loaded cel into persistent picture state at the supplied position and record enough state for restore replay. |
0x7b | seven variables | Variable-selected form of 0x7a. |
0x7c | none | Display carried inventory items. In interactive mode, store the selected item in v25; store 0xff on cancel. |
Inventory selection includes only items whose location is 0xff. Its display
uses a two-column list when needed. When the profile’s inventory-interaction
flag disables selection, the list is acknowledgement-only and does not produce
a chosen-item result. Profiles 2.089, 2.230, and 2.272 always use that
acknowledgement-only form, regardless of the flag.
Action opcodes: persistence and session control
| Opcode | Operands | Behavior |
|---|---|---|
0x7d | none | Enter the modal save selector. Cancel or a handled write failure returns to following bytecode; success writes the current game state and then returns. |
0x7e | none | Enter the modal restore selector. Cancel or open failure returns to following bytecode. Successful restore replaces runtime state, rebuilds resources, and aborts the current continuation so execution resumes from restored state. |
0x7f | none | No operation. |
0x80 | none | Request in-engine restart. Confirmation may be skipped by the restart-without-prompt flag. Cancel continues; acceptance resets game state, sets the restarted flag, and aborts current logic flow. |
0x81 | view resource | Temporarily load if needed, preview the view and its associated text, wait for acknowledgement, then discard it if this action loaded it. Do not add the temporary work to restore replay. |
0x82 | low, high, destination variable | Store a pseudorandom value in the inclusive range low..high. |
0x83 | none | Select object-0-to-v6 direction coupling. |
0x84 | none | Select v6-to-object-0 direction coupling and stop object 0 autonomous motion. |
0x85 | object variable | Display object number, X, baseline Y, width, height, priority, and step size as a modal diagnostic. |
0x86 | profile-dependent | In 2.089 and 2.230, consume no operand and terminate immediately. In other promoted profiles, consume a selector: 1 terminates immediately; other values request confirmation and terminate only when accepted. |
0x87 | none | Display heap/resource diagnostic values modally. These values are diagnostic and do not define a required internal allocator layout. |
0x88 | none | Stop sound, display the fixed pause message, wait for acknowledgement, and resume. |
0x89 | none | Redraw the enabled input line from the accepted input buffer. |
0x8a | none | Erase all currently visible input-line characters. |
0x8b | none | Run the interactive joystick-centering and range-calibration sequence when a joystick is available. |
0x8c | none | Toggle the supported alternate display mode, rebuild display state from recorded resource events, and preserve only events still inside the active replay count. This path is outside the current full-EGA target. |
0x8d | none | Display the interpreter name and version string modally. |
0x8e | replay-pair capacity | Set resource replay capacity, allocate/reset its pair sequence, and refresh object update lists. |
0x8f | message | Copy up to seven message bytes into the game signature and terminate on mismatch with the profile’s expected signature. The 2.936 profile expects SQ2; 3.002.149 accepts GR. |
0x90 | message | Append current room, current input line, and the expanded message to LOGFILE. Failure to open the log returns without terminating the game. |
0x91 | none | Save the current following-bytecode position as this logic’s future resume position. |
0x92 | none | Reset this logic’s future resume position to its bytecode entry. Current execution continues normally. |
In profile 2.411, action 0x80 always displays the restart confirmation even
when f16 is set. Other promoted profiles accept restart immediately while
f16 is set.
Profiles 2.411 and 2.440 display three heap/resource diagnostic lines for
action 0x87: heap size; current and maximum use; and maximum script use.
Later profiles also display an rm.0, etc. diagnostic line. These values do
not define a required allocation strategy.
| 0x93 | object, X, Y | Set current position, mark it newly positioned, and run placement without replacing the previous-position snapshot directly. |
| 0x94 | object, X variable, Y variable | Variable-selected form of 0x93. |
The save-file envelope, replay sequence, and restore/restart transitions are in Rooms, Replay, and Persistence.
Action opcodes: trace and configured text
| Opcode | Operands | Behavior |
|---|---|---|
0x95 | normally none | When action tracing is inactive and f10 is set, enable and draw the trace window. If tracing is already active, consume one additional byte after the opcode and otherwise return. |
0x96 | trace logic, row offset, height | Configure trace formatting and window placement; clamp height to at least 2. |
0x97 | message, row, column, width | Temporarily configure the modal message window, display the immediate message, then reset the temporary configuration. Row and column override the default centered placement; width controls message formatting and defaults to 30 when zero. |
0x98 | message variable, row, column, width | Variable-selected message form of 0x97. |
0x99 | view variable | Discard the loaded variable-selected view and every view retained after it. |
0x9a | top row, left column, bottom row, right column, attribute | Clear the inclusive text-cell rectangle with the selected attribute. Text cells are four logical pixels wide and eight logical pixels high in the EGA target. |
0x9b | two ignored bytes | Consume both bytes and otherwise do nothing. |
The temporary configuration exists only for that display action. After the message window has been opened, the row, column, and width overrides are reset to the ordinary default/centered behavior.
Profiles 2.411 and 2.440 use the same four-byte encoding listed above. The message selector is followed by row, column, and width.
Action opcodes: menus
| Opcode | Operands | Behavior |
|---|---|---|
0x9c | heading message | Append a top-level menu heading using the message text. Ignored after finalization. |
0x9d | item message, item ID | Append an enabled item to the current heading. Ignored after finalization. |
0x9e | none | Finalize menu construction and establish the initial heading/item selection. |
0x9f | item ID | Enable every menu item with the matching ID. |
0xa0 | item ID | Disable every menu item with the matching ID. |
0xa1 | none | If f14 is set, request modal menu interaction on the input cycle. |
Profile 2.272 accepts the menu action encodings 0x9c..0xa0, consumes the
operands listed above, and performs no menu operation. Profiles 2.089 and
2.230 do not accept those action slots.
Selecting an enabled item enqueues a mapped status event carrying its item ID. Escape exits without selection. Disabled items cannot produce selection events. Heading and item navigation wrap through enabled entries.
Action opcodes: remaining shared operations
| Opcode | Operands | Behavior |
|---|---|---|
0xa2 | view variable | Variable-selected form of 0x81. |
0xa3 | none | Enable the fixed input-width override, using a 36-character starting cap in profiles that support it. |
0xa4 | none | Disable the fixed input-width override and return to width derived from normal input state. |
0xa5 | variable, immediate | Multiply and retain the low 8 bits. |
0xa6 | destination variable, source variable | Multiply and retain the low 8 bits. |
0xa7 | variable, immediate | Store the unsigned quotient. Division by zero is outside valid bytecode. |
0xa8 | destination variable, divisor variable | Store the unsigned quotient. Division by zero is outside valid bytecode. |
0xa9 | none | Close/restore an active text window if present and clear the fixed input-width override even when no window is active. |
0xaa | string slot | Copy up to 31 bytes of the current save-selector description/path buffer into the slot. |
0xab | none | Save the current resource replay-pair count as a rollback point. |
0xac | none | Restore the saved replay-pair count and place the next write after the restored final pair. |
0xad | none | Increment the key-release event gate modulo 256 in the v2 and 3.002.086 profiles. A nonzero gate permits selected tracked-key releases to enqueue a movement-type zero event. |
0xae | horizon row | Rebuild the baseline-to-priority table: rows below the argument map to 4; rows from it upward rise from 5 toward 15 across the remaining 168-row picture height. |
0xaf | none at runtime | Do nothing and consume no following byte during execution, despite this opcode’s one-byte scanner length metadata. |
Version 3 extension actions
Profile 3.002.086 adds:
| Opcode | Operands | Behavior |
|---|---|---|
0xb0 | one ignored byte | Consume the byte and otherwise do nothing. |
0xb1 | gate value | Set the menu-interaction gate; zero blocks modal entry and nonzero permits it. |
Profiles 3.002.102 and 3.002.149 extend this range through 0xb5 with these
contracts:
| Opcode | Operands | Behavior |
|---|---|---|
0xb0 | none | No operation. |
0xb1 | gate value | Set the menu-interaction gate; zero blocks modal entry and nonzero permits it. |
0xb2 | none | No operation. |
0xb3 | four ignored bytes | Consume four bytes and otherwise do nothing. |
0xb4 | two variable operands | Consume two variable-index operands and otherwise do nothing. |
0xb5 | none | Clear the key-release event gate. |
In the two later profiles shared action 0xad sets the key-release gate to one rather
than incrementing it. In 3.002.149, shared actions 0xa3 and 0xa4 do
nothing; 3.002.102 retains their v2 effects. Other shared differences are
listed in Version Profiles.
Catalog completeness
Every action opcode accepted by the 2.936 profile (0x00..0xaf) now has an
operand and behavioral entry in this chapter. The 2.917 range is its
0x00..0xad subset. The version-3 extension ranges 0xb0..0xb1 and
0xb0..0xb5 are also listed. Subsystem chapters may refine these summaries;
when they do, the more detailed subsystem contract controls.
Picture Resources and Rendering
This chapter defines the full-EGA picture command stream and its effect on the logical graphics state. It applies after any container-level expansion described in Resource Containers. The resulting bytes are interpreted according to the selected profile.
Picture lifecycle
Loading, preparing, overlaying, and showing a picture are distinct operations:
- Loading makes the picture payload available but does not decode or display it.
- Preparing resets every logical cell to visual color
15and priority/control value4, resets the picture-command state, and decodes the selected payload. - Overlaying preserves the existing logical cells, resets the picture-command state, and decodes the selected payload over them.
- Showing presents the current logical visual surface. Preparing or overlaying does not, by itself, require the visible display to change.
At the start of either decoding operation, both drawing channels are disabled and the pattern mode is zero.
Command stream
The decoder processes bytes until 0xff. At command boundaries, bytes below
0xf0 are ignored. Profiles 2.089, 2.230, and 2.272 dispatch commands 0xf0..0xf8;
bytes 0xf9..0xfe are ignored at command boundaries. Later profiles dispatch
commands 0xf0..0xfa. A valid later stream does not use 0xfb..0xfe at a
command boundary.
Most command data is read by a guarded data reader. It accepts only bytes
0x00..0xef. A byte 0xf0 or greater terminates the current command without
being consumed, so the main scanner processes that byte next.
There are three exceptions in profiles that support pattern commands. The
operand immediately following 0xf0, 0xf2, or 0xf9 is a raw byte: it is
consumed even when its value is 0xf0 or greater. Profiles 2.089, 2.230, and 2.272
have only the 0xf0 and 0xf2 exceptions because they do not dispatch
0xf9.
A coordinate pair consists of guarded X and Y bytes. X is clamped to 159 and
Y is clamped to 167. If X is accepted but Y is a command byte, the partial
pair produces no drawing and the command byte remains pending.
Drawing channels
Each logical cell contains a visual nibble and a priority/control nibble. Commands independently enable or disable writes to those channels:
| Command | Operands | Effect |
|---|---|---|
0xf0 | raw color byte | Enable visual drawing and select the operand’s low nibble as the visual color. |
0xf1 | none | Disable visual drawing. |
0xf2 | raw value byte | Enable priority/control drawing and select the operand’s low nibble as the priority/control value. |
0xf3 | none | Disable priority/control drawing. |
A pixel write replaces every enabled channel with its selected value and preserves every disabled channel. If neither channel is enabled, drawing commands leave the logical cells unchanged.
Path commands
All line segments include both endpoints. Horizontal and vertical segments visit every coordinate between their endpoints regardless of endpoint order.
| Command | Data sequence | Effect |
|---|---|---|
0xf4 | initial X,Y; then Y,X,Y,X,… | Plot the initial point, then draw alternating vertical and horizontal segments. |
0xf5 | initial X,Y; then X,Y,X,Y,… | Plot the initial point, then draw alternating horizontal and vertical segments. |
0xf6 | initial X,Y; then zero or more X,Y pairs | Plot the initial point, then connect each previous point to the next absolute point. |
0xf7 | initial X,Y; then zero or more packed deltas | Plot the initial point, then connect each previous point to the endpoint encoded by the next delta byte. |
For a packed relative delta byte b:
- X magnitude is
(b & 0x70) >> 4; bit0x80selects subtraction and a clear bit selects addition. - Y magnitude is
b & 0x07; bit0x08selects subtraction and a clear bit selects addition. - Each addition or subtraction is performed modulo 256. The resulting X is
then clamped down to
159if it exceeds159; Y is similarly clamped down to167.
This is an upper clamp, not a signed lower clamp. For example, subtracting one
from X 0 first produces 255, which is then clamped to X 159.
General line rasterization
Diagonal segments use the following exact integer algorithm. It is normative because other common line algorithms choose different cells.
Let dx and dy be the absolute coordinate differences, and let xstep and
ystep be +1 or -1 toward the endpoint. The caller has already plotted
the start point.
- If
dx >= dy, setmajor = dx,xerror = 0, andyerror = floor(dx / 2). - Otherwise set
major = dy,xerror = floor(dy / 2), andyerror = 0. - Repeat
majortimes:- Set
yerror = (yerror + dy) modulo 256. Ifyerror >= major, subtractmajormodulo 256 and advance Y byystep. - Set
xerror = (xerror + dx) modulo 256. Ifxerror >= major, subtractmajormodulo 256 and advance X byxstep. - Plot the resulting point.
- Set
For example, the segment from (0,0) to (3,1) plots (0,0), (1,0),
(2,1), and (3,1). The modulo-256 accumulators also affect long edge-to-edge
segments; they must not be replaced with wider arithmetic.
Seed fill
Command 0xf8 reads zero or more coordinate-pair seeds. Each seed performs a
four-connected fill under these rules:
- If visual drawing is enabled, connectivity is determined solely by the
visual channel and the target value is visual color
15. - Otherwise, if priority/control drawing is enabled, connectivity is
determined by that channel and the target value is
4. - If neither channel is enabled, the seed has no effect.
- A visual fill whose selected visual value is
15, or a priority/control fill whose selected value is4, has no effect. - The seed must have the target value in the selected connectivity channel. Otherwise it has no effect.
- Every cell in the seed’s four-connected target-valued region is written using the normal drawing-channel rule.
When both channels are enabled, visual values determine connectivity but both channels are written. The traversal order and temporary storage strategy are not observable for valid finite pictures; the final connected region is the required result.
Pattern mode and plots
Profiles 2.089, 2.230, and 2.272 have no pattern-mode or pattern-plot commands. Their
valid picture command vocabulary ends with seed fill at 0xf8.
Profile 2.411 uses an early point-plot variant:
0xf9consumes one raw byte and otherwise has no effect.0xfarepeatedly consumes X,Y coordinate pairs with no seed bytes and performs one ordinary pixel write for each complete pair.- Shape, radius, and stipple fields do not apply in this profile.
Profiles 2.439/2.440, 2.915/2.917, 2.936, and 3.002 use the shaped and stippled pattern behavior below. The v2 and v3 families differ in the radius-one shape and horizontal edge limit, as stated under plot geometry.
Command 0xf9 consumes one raw mode byte. Its fields are:
- bits
0..2: radiusrin0..7; - bit
0x10: bypass the geometric pattern mask when set; and - bit
0x20: before each plotted coordinate, consume one guarded seed byte used for stippling.
Command 0xfa repeatedly consumes an optional seed byte followed by an X,Y
pair. The seed is required for every pair when mode bit 0x20 is set. Each
complete pair performs one pattern plot.
Plot geometry
For radius r, a plot examines 2r + 1 rows and r + 1 columns. Let
horizontal_limit be 320 for shaped-brush v2 profiles and 318 for v3
profiles. Its starting coordinates are:
doubled_x = clamp(2 * x - r, 0, horizontal_limit - 2 * r)
start_x = floor(doubled_x / 2)
start_y = clamp(y - r, 0, 167 - 2 * r)
Rows are visited from top to bottom and columns from left to right. Unless mode
bit 0x10 is set, a candidate exists only when the current row word has at
least one bit in common with the current column mask.
The column masks for columns 0..7 are:
8000 2000 0800 0200 0080 0020 0008 0002
The row words for each radius are shown below. Radius 1 is the only shape that differs between the observed shaped-brush v2 and v3 profiles.
| Radius | Shaped-brush v2 | v3 |
|---|---|---|
| 0 | 8000 | Same as v2 |
| 1 | e000 e000 e000 | 4000 e000 4000 |
| 2 | 7000 f800 f800 f800 7000 | Same as v2 |
| 3 | 3800 7c00 fe00 fe00 fe00 7c00 3800 | Same as v2 |
| 4 | 1c00 7f00 ff80 ff80 ff80 ff80 ff80 7f00 1c00 | Same as v2 |
| 5 | 0e00 3f80 7fc0 7fc0 ffe0 ffe0 ffe0 7fc0 7fc0 3f80 1f00 | Same as v2 |
| 6 | 0f80 3fe0 7ff0 7ff0 fff8 fff8 fff8 fff8 fff8 7ff0 7ff0 3fe0 0f80 | Same as v2 |
| 7 | 07c0 1ff0 3ff8 7ffc 7ffc fffe fffe fffe fffe fffe 7ffc 7ffc 3ff8 1ff0 07c0 | Same as v2 |
With the geometric mask enabled, the v2 radius-one rows admit both examined
columns on all three rows, producing a 2 by 3 logical-pixel block. In v3, the
top and bottom 4000 rows overlap neither examined column mask, so only the
two adjacent logical pixels in the center row are candidates. Mode bit 0x10
bypasses these row/column tests in either family.
Stipple sequence
When mode bit 0x20 is set, initialize an eight-bit state to seed | 1 at the
start of each plot. For every candidate that passes the geometric mask, update
the state before deciding whether to write:
- Save bit 0 as
carryand shift the state right once. - If
carrywas set, XOR the state with0xb8. - Write the candidate only when state bit 0 is clear and state bit 1 is set.
The sequence is restarted from that plot’s seed for every coordinate pair.
Linear right-edge behavior
In full-brush profiles, pattern candidates are addressed as a linear array
index row * 160 + column, after adding the calculated starts. In shaped-brush
v2 profiles, a candidate whose calculated X is 160 therefore writes logical
X 0 of the following row when that linear index remains within the 160 by 168
surface. A candidate beyond the end of the entire surface has no visible
effect. Implementations using a two-dimensional pixel API must reproduce this
result rather than clipping each pattern candidate at X 159.
The v3 horizontal limit prevents valid pattern geometry from producing X
160, so this wrap is not reachable from a valid v3 pattern plot.
Every accepted pattern candidate uses the normal drawing-channel write rule.
Valid-data boundary
This chapter specifies complete command streams ending in 0xff, with complete
raw operands, coordinate pairs, and pattern seed/coordinate groups. Resource
truncation and unsupported command bytes 0xfb..0xfe are malformed-data
behavior and are outside the conformance target.
View Resources and Cel Drawing
Views contain the animated images used by drawable objects and by transient preview operations. This chapter defines the payload structure, cel pixels, orientation behavior, and composition into the logical picture surface.
Payload structure
All multi-byte offsets are unsigned little-endian values.
| Payload location | Meaning |
|---|---|
| bytes 0 and 1 | Reserved by the promoted profiles. |
| byte 2 | Number of loops. |
| bytes 3 and 4 | Offset of an embedded zero-terminated display string, relative to the payload start. |
| byte 5 onward | One two-byte loop offset per loop, relative to the payload start. |
Except in profile 2.230, each loop begins with a one-byte cel count followed by one two-byte cel offset per cel. A cel offset is relative to the start of its loop, not to the payload start.
Profile 2.230 packs cel count and mutable orientation state into the loop’s first byte:
| Bits | Meaning |
|---|---|
0x0f | Cel count. |
0x30 | The loop number whose orientation the current shared row streams represent. |
0x40 | Mirror every cel row when the stored orientation changes. |
0x80 | Enable mutable loop-header orientation handling. |
The packed form is followed by one two-byte cel offset per low_nibble cels.
For action 0x31 and all loop/cel selection bounds, profile 2.230 likewise
uses only the low nibble as the cel count.
Each cel has this structure:
| Cel location | Meaning |
|---|---|
| byte 0 | Width in logical pixels. |
| byte 1 | Height in logical pixels. |
| byte 2 | Control byte: transparent color and orientation state. |
| byte 3 onward | Row-terminated run data. |
The low nibble of the control byte is the transparent color. Bit 0x80 marks
a cel whose row stream can be mirrored. Bits 0x70 hold the loop number whose
orientation the current row stream represents.
Loop and cel selection is zero-based. Selecting a loop or cel updates the object’s selected dimensions and decoded image state before it can be drawn.
Row decoding
The cel contains exactly height encoded rows. Each row ends with byte 0.
Every nonzero byte is one run:
- the high nibble is a color in
0..15; and - the low nibble is the run length in logical pixels.
Decoding starts at X 0 for every row. A run advances X by its length. If its
color is not the transparent color, its pixels receive that color; otherwise
the run only advances X. Unwritten pixels through the declared width are
transparent, including any implicit space after the encoded runs.
Pixels generated beyond the declared width are not part of the decoded cel. The next source byte is still interpreted as part of the same row until the zero terminator appears.
Stateful mirroring
There are two profile-selected encodings for the same externally visible orientation behavior.
Per-cel orientation
In every promoted profile except 2.230, when control bit 0x80 is clear,
selecting the cel does not change its row stream. When it is set, compare
control bits 0x70 with the selected loop number masked to three bits:
- If they match, the row stream already has the requested orientation.
- If they differ, mirror every row and replace bits
0x70with the selected loop number.
The orientation field and mirrored row stream are mutable loaded-resource state. This matters when loop entries share the same cel data: a later selection compares against the cel’s current orientation, not necessarily its original file bytes.
Profile 2.230 loop orientation
When profile 2.230 selects a loop whose header bit 0x80 is set, it compares
header bits 0x30 with the selected loop number. If they differ, it mirrors
every cel’s row stream when bit 0x40 is also set, then replaces bits 0x30
with the selected loop number. Cel control bytes retain only their ordinary
transparent-color state; they do not carry the profile’s orientation marker.
The loop header and every mirrored row stream are mutable loaded-resource
state. Loop entries may point to the same loop structure. Selecting one such
entry can therefore mirror the shared cels and change the orientation later
observed through another entry. Packed mutable-orientation loops in valid
profile-2.230 data use selected loop numbers 0..3.
For each row, mirroring operates on runs as follows:
- Find the first run whose color is not transparent.
- If no such run exists, encode the mirrored row as only its zero terminator.
- Discard explicit transparent runs before that first visible run, but include their lengths when calculating the row’s total encoded width.
- Compute the implicit trailing transparent width as
cel width - total encoded run length. - Emit that implicit width as transparent runs before the mirrored visible data, splitting it into runs no longer than 15 pixels.
- Emit every original run from the first visible run onward in reverse byte order, preserving each run byte unchanged.
- Emit the zero row terminator.
Thus original leading transparent pixels become implicit trailing transparency, while original implicit trailing transparency becomes explicit leading runs. Explicit transparent runs after the first visible run participate in the reversed sequence.
Baseline placement
A cel is positioned by a left X coordinate and a baseline Y coordinate. Its initial top row is:
top = baseline_y - height + 1
The low-level composition operation applies these adjustments in order:
- If
top < 0, addtopto left X, subtracttopfrom baseline Y, and set top to zero. For example, top-1shifts left X one pixel left and baseline Y one pixel down; this is a position adjustment, not simple top clipping. - If
left + width > 160, set left to160 - width. - During pixel composition, skip destination coordinates outside the 160 by 168 logical surface. This clips a negative left edge and rows below the bottom edge.
Higher-level object placement normally searches for an in-bounds position before invoking this operation. That search is specified with object behavior; the rules above define the composition primitive itself.
Transparency and priority composition
Transparent source pixels never modify the destination. Every nontransparent source pixel is independently tested against the logical priority/control surface. Rejection of one pixel does not reject the remainder of its run.
Let p be the drawing object’s priority in 0..15, and let q be the
destination cell’s priority/control value:
- If
qis greater than2, use it directly as the comparison value. - If
qis0,1, or2, scan downward in the same logical X column, starting one row below the destination. Use the first value greater than2. - If the scan reaches the bottom without finding such a value, use comparison
value
0. - Draw the pixel when the comparison value is less than or equal to
p. Otherwise leave the destination unchanged.
An accepted pixel replaces the destination visual color with the cel color and
replaces its priority/control value with p. Equal priorities therefore allow
drawing. Even priority 0 can draw when a downward scan finds no value above
2.
Embedded display string
View-preview operations use the header offset at bytes 3 and 4 as a pointer to
a zero-terminated string within the payload. Such an operation temporarily
shows loop 0, cel 0 centered at the bottom of the logical picture area at
priority 15, displays the embedded string modally, and then restores the
previous picture contents. Whether the view remains loaded afterward depends
on whether it was already loaded before the preview.
Profile applicability
All promoted profiles use expanded view payloads with the same header offsets, cel structure, row coding, placement, and composition. Profile 2.230 selects the packed loop-header count/orientation form; every other promoted profile selects the ordinary full-byte cel count and per-cel orientation form. Container storage may differ by profile, but container decoding finishes before this chapter’s rules are applied.
Valid-data boundary
The conformance target requires offsets to select complete structures inside the expanded payload, all declared rows to have terminators, and encoded widths to permit the mirror calculation above. Out-of-range offsets, truncated rows, and arithmetic underflow from an encoded row wider than its declared cel are malformed-data behavior and are outside this specification.
Object Behavior
This chapter defines persistent drawable-object lifecycle, placement, animation, movement, collision, control acceptance, and drawing order. Cel pixels and per-pixel priority composition are defined in View Resources and Cel Drawing.
Object lifecycle
Each object has independent active, update-eligible, automatic-cycling, and update-partition state.
Resetting an object for animation has no effect if it is already update-eligible. Otherwise it:
- enables update eligibility and automatic cel cycling;
- selects the later-drawn update partition; and
- clears direction, autonomous motion mode, and cel-cycling mode.
Clearing all objects clears active and update-eligible state from every object.
Activation requires a selected cel. It places the object at an acceptable position, snapshots its current cel and position, marks it active in the later-drawn partition, rebuilds the affected drawing state, and refreshes its visible area. Activating an already active object has no additional effect.
Deactivation restores/removes the object’s drawn contribution, clears active state, rebuilds the affected drawing state, and refreshes the exposed area. Deactivating an inactive object has no additional effect.
Priority and horizon
Unless fixed priority is enabled, an object’s drawing priority is derived from its baseline Y through a 168-entry table. The default table is:
| Baseline rows | Priority |
|---|---|
| 0..47 | 4 |
| 48..59 | 5 |
| 60..71 | 6 |
| 72..83 | 7 |
| 84..95 | 8 |
| 96..107 | 9 |
| 108..119 | 10 |
| 120..131 | 11 |
| 132..143 | 12 |
| 144..155 | 13 |
| 156..167 | 14 |
The priority-table rebuild action replaces this mapping as described in the
logic action catalog. Fixed priority bypasses baseline derivation for drawing
and priority acceptance. A fixed value of 15 also bypasses the movement
control scan described below.
The default horizon is baseline Y 36. A nonexempt object must have a baseline
strictly greater than the horizon. Placement first changes an initial baseline
at or above the horizon to horizon + 1. Horizon-exempt objects do not receive
that restriction.
Placement search
A position is within geometric bounds when all of these are true:
left >= 0
left + cel_width <= 160
baseline_y - cel_height >= -1
baseline_y <= 167
baseline_y > horizon, unless horizon-exempt
The third condition is equivalent to the cel’s top row being at least zero.
Placement tests the requested position first. A candidate is accepted only if it is geometrically valid, passes object-object collision, and passes the priority/control footprint scan. If rejected, candidates are visited in this widening spiral from the requested position:
left 1, down 1, right 2, up 2,
left 3, down 3, right 4, up 4, ...
The first accepted candidate becomes the object’s position. Valid game state is expected to provide an acceptable candidate.
Cycle ordering and cadence
Object work occurs in two phases around logic execution:
- Before logic 0, eligible objects whose movement-cadence countdown is
exactly
1update their autonomous motion direction. The configured movement-rectangle transition check also runs in this phase. - After logic 0, eligible objects perform automatic loop selection and cel timing, then apply movement, redraw, and visible refresh.
The movement pass processes active, update-eligible objects in the later-drawn partition. If an object’s cadence countdown is nonzero, decrement it. Skip the movement unless that decrement reaches zero. When movement is due, reload the countdown from its cadence interval before applying the proposed step. A zero countdown is already due.
A newly positioned object suppresses its direction delta for one due movement pass and then clears the newly-positioned state.
Direction and automatic loop selection
Direction values and coordinate deltas are:
| Value | Direction | X delta per step | Baseline-Y delta per step |
|---|---|---|---|
| 0 | stationary | 0 | 0 |
| 1 | up | 0 | -step |
| 2 | up-right | +step | -step |
| 3 | right | +step | 0 |
| 4 | down-right | +step | +step |
| 5 | down | 0 | +step |
| 6 | down-left | -step | +step |
| 7 | left | -step | 0 |
| 8 | up-left | -step | -step |
When automatic loop selection is enabled, the direction may select a loop
before cel timing. Profiles 2.089, 2.230, and 2.272 perform this selection on every
eligible post-logic object pass, independently of the movement-cadence
countdown. Profiles 2.411 and later perform it only when that countdown is
1.
For views with two or three loops:
| Direction | Selected loop |
|---|---|
| 2, 3, 4 | 0 |
| 6, 7, 8 | 1 |
| 0, 1, 5 | no change |
For views with four or more loops:
| Direction | Selected loop |
|---|---|
| 2, 3, 4 | 0 |
| 6, 7, 8 | 1 |
| 5 | 2 |
| 1 | 3 |
| 0 | no change |
Profiles 2.089, 2.230, 2.272, 2.411, 2.440, and 2.917 use the second table only for
exactly four loops; a view with more loops does not change automatically. Profiles 2.936
and 3.002.086 use the table for every view with four or more loops. In profiles
3.002.102 and 3.002.149, exactly-four-loop views always use it, while views
with more than four loops use it only while f20 is set.
Cel cycling
Automatic cel cycling has an independent interval and countdown. When cycling is enabled and its countdown is nonzero, decrement it during the post-logic object phase. When it reaches zero, perform the selected cycling mode and reload the countdown from the interval.
| Mode | Transition |
|---|---|
| forward wrap | Increase the cel index; wrap after the last cel to cel 0. |
| forward completion | Increase toward the last cel. On reaching it, set the completion flag, disable cycling, clear direction, and return the mode to forward wrap. |
| backward wrap | Decrease the cel index; wrap before cel 0 to the last cel. |
| backward completion | Decrease toward cel 0. On reaching it, or when already at cel 0, set the completion flag, disable cycling, clear direction, and return the mode to forward wrap. |
Starting either completion mode installs a one-callback delay. The first otherwise-due cycling callback clears that delay without changing the cel. Explicitly selecting a cel removes the delay.
Movement proposal and screen boundaries
When movement is due, add the direction delta to the current position, then clamp the proposed position in this order-independent set of bounds:
| Boundary | Clamped coordinate | Code |
|---|---|---|
| top | baseline_y = cel_height - 1 | 1 |
| horizon, when nonexempt | baseline_y = horizon + 1 | 1 |
| right | left = 160 - cel_width | 2 |
| bottom | baseline_y = 167 | 3 |
| left | left = 0 | 4 |
In profile 3.002.086, an exact proposed left = 0 also reports boundary code
4. Other promoted profiles report that code only when the proposal is below
zero; exact zero remains a valid non-boundary position. In every profile, a
negative proposal is clamped to zero and reports code 4.
The clamped proposal is then tested for object collision and footprint control acceptance. If either rejects it, restore the prior coordinates, discard the boundary code, and run placement search from the restored position.
If a boundary code survives for an object whose event identifier is zero,
store it in v2. Otherwise store the object’s event identifier in v4 and the
boundary code in v5. A target-motion object reaching a surviving screen
boundary completes its target motion.
Object-object collision
Collision is bypassed when the moving object has collision exemption enabled. Otherwise compare it with every active, update-eligible object that is not collision-exempt and has a different event identifier.
The horizontal spans overlap unless either of these is true:
moving_left + moving_width < other_left
moving_left > other_left + other_width
Equality therefore counts as overlap: objects are rejected before their edges
would touch under the conventional left + width - 1 interpretation.
For horizontally overlapping objects, a collision occurs when any of these is true:
- their current baselines are equal;
- the moving baseline is now greater than the other baseline and its saved baseline was strictly less than the other’s saved baseline; or
- the moving baseline is now less than the other baseline and its saved baseline was strictly greater than the other’s saved baseline.
Thus strict vertical-order reversal counts as crossing. Merely sharing a saved baseline and then moving apart does not satisfy the crossing test.
Footprint control acceptance
Unless priority is fixed, first derive it from the current baseline. Priority
15 bypasses this entire scan and accepts the footprint.
Otherwise scan the logical priority/control values at the object’s baseline from left to right for exactly the cel width. Each cell replaces the current classification state; classes encountered earlier are not latched.
| Cell value | Immediate effect and resulting state |
|---|---|
| 0 | Reject immediately. |
| 1 | Reject immediately unless the object ignores movement-rectangle restrictions; when allowed, continue with both class-state flags clear. |
| 2 | Continue with class-state flag 3 set and flag 0 clear. |
| 3 | Continue with class-state flag 3 clear and flag 0 set. |
| 4..15 | Continue with both class-state flags clear. |
After the complete scan:
- the first optional post-scan gate rejects when class-state flag 0 is clear;
- the second optional post-scan gate rejects when class-state flag 0 is set;
- enabling both gates therefore rejects every non-bypassed complete scan; and
- for event-identifier-zero objects, script flags
f3andf0receive the final class-state values.
The logic actions historically associated with control classes 2 and 3 enable the first and second post-scan gates respectively. Their exact behavior is the final-cell state rule above, not an encountered-anywhere test.
Configured movement rectangle
The global movement rectangle classifies a baseline point (x,y) as inside
only when all comparisons are strict:
left < x < right
top < y < bottom
During the pre-logic phase, an object with nonzero direction and rectangle enforcement enabled compares its current baseline point with the next point computed from direction and step size. If exactly one is inside, the object:
- records a rectangle-transition state;
- clears its direction; and
- if it is object 0, clears
v6.
An object configured to ignore movement-rectangle restrictions skips this transition check. The same option permits control value 1 during footprint acceptance.
Target motion
Starting target motion stores the target, saves the current step size, clears the completion flag, and temporarily adopts a nonzero step override. A zero override preserves the current step size.
Profiles 2.089, 2.230, and 2.272 stop after those setup changes. They retain the object’s previous direction until the next eligible target-motion update. If the object already satisfies the target, restoration of its saved step, clearing of target-motion mode, and setting of the completion flag are likewise deferred until that update. Profile 2.411 and later promoted profiles perform one target-direction calculation as part of the start action.
Each target-direction calculation uses signed delta
target_coordinate - current_coordinate. For band s, classify a delta as
negative when delta <= -s, near only when -s < delta < s, and positive
when delta >= s. Exact equality with either band edge is therefore not near.
The X and Y classes select a direction from this grid:
| Target relation | left | within X step | right |
|---|---|---|---|
| above | 8 | 1 | 2 |
| within Y step | 7 | 0 | 3 |
| below | 6 | 5 | 4 |
For target motion, s is the current step size. Direction zero completes the
mode immediately: restore the saved step size,
set the completion flag, clear the autonomous mode, and leave direction zero.
Movement need not land on the exact target when the remaining distance is
within one step.
Except in profiles 2.089, 2.230, and 2.272, the start action performs a target-direction
calculation immediately. Subsequent automatic recalculation occurs in the
pre-logic phase only when the object’s movement-cadence countdown equals 1.
Without that cadence state, a one-shot call leaves the initial direction active
and the object can continue past an internal target until another event,
commonly a screen boundary, stops it. Reissuing the start action while the
completion flag is clear also recalculates direction each script cycle.
Approach and random motion
The rules below use random_word() as the profile’s next unsigned random
value. The seed and generator timing are nondeterministic conformance inputs;
all reductions and rejection loops after obtaining a value are normative.
Approach motion aims the object’s horizontal center and baseline toward object
0’s center and baseline. It uses the action’s near threshold as band s in
the strict target-direction calculation above. Direction zero clears the
motion mode and sets the completion flag.
The approach retry-delay byte starts at 255. On the first noncomplete
direction update, replace 255 with zero and use the direct direction. On
later updates:
- If the previous refresh marked the object stationary, repeatedly calculate
random_word() modulo 9until the result is nonzero and use that as a temporary direction. - Calculate
distance = floor((abs(center_x_delta) + abs(baseline_y_delta)) / 2) + 1. - If
distance <= step, set retry delay tostep. Otherwise repeatedly calculaterandom_word() modulo distanceuntil the result is at leaststep, then use that result as the delay. - A stationary-recovery update returns with the temporary direction and new delay; it does not decrement the delay immediately.
- On a later nonstationary update with nonzero delay, subtract the step as an eight-bit value. Keep the wrapped result when the signed eight-bit subtraction is greater than or equal to zero; otherwise replace it with zero. Keep the temporary direction for that update.
- On a nonstationary update whose delay was already zero, replace the temporary direction with the newly calculated direct direction.
Random motion has its own byte countdown. On every eligible direction update, save the old countdown and decrement it modulo 256. If the old value was nonzero and the object was not marked stationary, retain the current direction and decremented countdown. Otherwise:
- Set direction to
random_word() modulo 9, allowing stationary direction zero. - Repeatedly calculate
random_word() modulo 51until the result is at least6, then store that value as the new countdown.
Both modes remain subject to cadence, screen bounds, collision, footprint acceptance, and drawing rules elsewhere in this chapter.
Drawing order and refresh
All objects in the earlier partition draw before every object in the later partition. Within one partition, objects draw in ascending drawing-key order, with object number breaking ties in ascending order. Later draws may cover earlier equal-priority pixels under the cel composition rules.
Profile 2.089 is the exception for ordering within the earlier partition. It draws that partition in ascending object-number order without sorting it by drawing key. It still sorts the later partition by drawing key and uses object number to break equal-key ties. Profile 2.230 and every promoted profile from 2.272 onward sort both partitions by drawing key as described above.
For baseline-priority objects, the drawing key is baseline Y. In the promoted 2.936 table mode, a fixed priority of zero maps before row 0 and a positive fixed priority maps to key 168, after ordinary baseline rows. No observed 2.936 state enables the alternate direct fixed-priority formula.
Before rebuilding a frame, prior object rectangles are restored. Objects are then drawn in the order above. Visible refresh covers the union of each object’s previous and current cel rectangles, so changing position, dimensions, or cel exposes the old area and presents the new one in the same update.
Input, Text, Menus, and Inventory
This chapter defines string parsing, keyboard/event handling, text-surface state, menu interaction, and inventory selection. These systems share input events but retain distinct script-visible state.
Dictionary file
The dictionary file begins with 26 unsigned big-endian offsets, one for each
lowercase initial letter a through z. An offset of zero means that initial
has no entries.
Entries in each initial-letter sequence are prefix-compressed:
prefix_length:u8
encoded_suffix:u8[]
word_id:u16be
prefix_length characters are copied from the preceding decoded word. For
each suffix byte, (byte & 0x7f) ^ 0x7f is the lowercase character. Bit 0x80
marks the final suffix byte. The following two bytes are the word identifier in
big-endian order.
Dictionary lookup is case-insensitive for ASCII letters.
String slots
Profiles 2.089, 2.230, and 2.272 expose six 40-byte string slots s0..s5. Later
promoted profiles expose twelve slots s0..s11. Slot operations use
zero-terminated byte strings within that fixed capacity.
- Copying a logic message or normalized parsed word copies no more than 40 bytes into the destination slot.
- Prompted editing clears the destination first. Its storage limit is
min(requested_maximum + 1, 40)bytes including the terminator. - Parsing ignores slot numbers outside the selected profile’s slot range.
- Numeric prompting accepts at most four decimal characters and stores the low eight bits of the resulting unsigned value.
Direct string-slot comparison has its own normalization. It removes space,
tab, ., ,, ;, :, apostrophe, !, and -; lowercases ASCII A..Z;
and then requires exact equality through both zero terminators. It does not use
the dictionary or parsed-word state.
Parser normalization
Parsing begins by clearing f2 (parsed input ready), f4 (parsed input already
matched), the parsed word identifiers, and their normalized-token references.
The source slot is normalized into a temporary zero-terminated buffer:
- space and
, . ? ! ( ) ; : [ ] { }are separators; - apostrophe, backtick, hyphen, and double quote are dropped without creating a separator;
- separator runs collapse to one space;
- a trailing space is removed; and
- ASCII dictionary matching ignores letter case.
The normalized buffer is split into tokens and looked up in the dictionary. At most ten nonzero word identifiers are retained.
Parser results
Recognized dictionary entries with word ID zero are ignored. They do not occupy a parsed-word slot or increase the reported parsed position.
Recognized nonzero identifiers are appended in token order. Their normalized token text remains available for the action that copies a parsed word into a string slot.
At the first unknown token:
- Store its normalized-token reference in the next output position.
- Set the parser count/error position to
retained_nonzero_identifier_count + 1. - Store the same one-based position in
v9. - Set
f2and stop parsing later tokens.
If parsing reaches the end with at least one retained nonzero identifier, set
the parser count to that number and set f2. If the input contains only
recognized zero-ID words, leave f2 clear.
Parsed-word matching
The variable-length word-sequence condition can run only when f2 is set, the
parser count/error position is nonzero, and f4 is clear.
Pattern identifiers are compared with retained parsed identifiers in order:
0x0001matches exactly one parsed identifier;0x270fterminates the pattern successfully without requiring all remaining parsed identifiers to be consumed; and- every other value requires exact equality.
A full exact match also succeeds. Success sets f4, preventing another
word-sequence condition from matching the same parsed input. Failure leaves
f4 clear.
A terminator-only pattern can succeed after an unknown-token parse because the nonzero parser error position satisfies the input-presence check even when no dictionary identifier precedes the unknown token.
Profile 2.089 does not recognize 0x270f as a tail terminator. It first
requires the pattern count to equal the parser count/error position, then
compares every identifier using exact equality or the 0x0001 wildcard.
Profiles 2.230, 2.272, and later use the tail-terminator behavior above.
Event queue
Input consumers share a circular queue with 20 physical event slots. Enqueueing fails when advancing the write position would equal the read position, so at most 19 events can be pending at once.
Each event has a type and a 16-bit value:
| Type | Meaning |
|---|---|
| 1 | Raw key word. |
| 2 | Movement/navigation value. |
| 3 | Script-mapped status number. |
For a key whose ASCII byte is nonzero, the raw key word contains that byte as its value. For an extended key whose ASCII byte is zero, the scan-code word is retained.
The following extended key words are converted directly to type-2 direction events:
| Key word | Value |
|---|---|
0x4800 | 1 |
0x4900 | 2 |
0x4d00 | 3 |
0x5100 | 4 |
0x5000 | 5 |
0x4f00 | 6 |
0x4b00 | 7 |
0x4700 | 8 |
Some input paths normalize key words 0x0101 and 0x0301 to Enter
0x000d, and 0x0201 and 0x0401 to Escape 0x001b.
Profiles 2.411, 2.440, 2.917, 2.936, 3.002.086, and 3.002.102 hold 39
(raw_key_word, status_number) entries; profile 3.002.149 holds 49. A matching
type-1 event becomes type 3 with the configured status number. Processing that
event makes the matching status condition true.
Raw-key condition
The raw-key condition first checks v19. If it is nonzero, the condition is
true without consuming another event.
Otherwise it dequeues until one of these occurs:
- the queue is empty, producing false;
- a type-1 event with nonzero value is found, storing its low byte in
v19and producing true; - a type-1 event with zero value is found, producing false; or
- a non-type-1 event is discarded, after which polling continues.
Mapped status and movement events are therefore not returned by the raw-key condition.
Tracked key release
Selected extended keys have a pressed latch. A corresponding release clears the latch. When the profile’s release-event gate is nonzero, that release also enqueues type-2 value zero.
In profiles 2.917, 2.936, and 3.002.086, action 0xad increments the byte gate
modulo 256. Profiles 2.411 and 2.440 do not expose that action. In profiles
3.002.102 and 3.002.149, 0xad sets the gate to one and 0xb5 clears it.
Text geometry and surfaces
The full-EGA text grid has 40 columns and 25 rows. One text column spans four logical picture pixels; one text row spans eight logical picture rows.
Text rectangle operations use inclusive top/bottom and left/right text-cell bounds. Full-width row clearing covers columns 0 through 39. These operations overwrite the visible text surface; they do not alter a loaded picture payload or re-decode picture commands.
The input/status configuration selects a display base row, input row, and
status row. In the normal EGA presentation, display base value n adds
8 * n logical rows to later picture/object presentation. The input and status
rows independently choose the eight-logical-row bands used by those text
surfaces.
Disabling the input line clears its configured row and prevents ordinary input redraw. Enabling it clears/redraws that row, then draws the prompt marker and input text when present. The prompt marker is the first byte of its selected message; a zero byte suppresses it. Erasing input sends repeated backspace behavior until the visible edit length reaches zero. Refreshing the input line uses the most recently accepted input, not unaccepted live-edit characters.
The status line similarly has enabled and disabled states. Enabling redraws its configured row; disabling clears that row.
Modal text and alternate text mode
A modal message saves the covered visible rectangle, draws and formats its text window, waits for acknowledgement, and restores the prior rectangle when closed. Opening another saved window first closes the current one.
Configured modal-message actions can supply temporary row, column, and width overrides. The width is the maximum formatted text width; a zero width means 30. Row and column override the default centered placement for that single message. Without an override, row is centered within the current display text area and column is centered within the 40-column text surface from the formatted line width.
Entering alternate text-attribute mode suppresses normal object graphics updates and clears/fills the visible full-screen surface using the configured text foreground/background pair. Leaving restores normal graphics presentation and redraws configured status/input areas.
The exact 8-by-8 glyph bitmaps are platform-font inputs, not a portable core interpreter requirement. Text placement, row/column geometry, modal blocking, and save/restore behavior remain normative.
Inventory selection
Each inventory item has a display name and one-byte location. Location 0xff
means carried. The inventory action lists only carried items, in item-number
order, using two columns when necessary.
When inventory interaction is enabled, Enter stores the selected item number
in v25 and Escape stores 0xff. When interaction is disabled, the same list
is acknowledgement-only and does not produce a selection result. With no
carried items, the UI displays its empty-inventory text.
Profiles 2.089, 2.230, and 2.272 have no interactive inventory variant. They always
display the acknowledgement-only carried-item list, do not consult the
inventory-interaction flag, and leave v25 unchanged.
Menu construction
Menu setup creates ordered headings and ordered items under the current heading. Each item has display text, a script-visible status number, and an enabled state.
Setup remains mutable until finalization. Finalization disables headings with no items, chooses the root heading and its first item as the current selection, and prevents later heading/item additions from changing the established menu. Enable and disable actions can still change an existing item by status number.
Menu state remembers the current item separately for each heading. A later menu opening resumes from the remembered heading/item state.
Menu interaction
The menu-request action requests interaction only while f14 is set. The v3
profiles add a separate menu interaction gate: a zero value set by 0xb1
prevents a pending request from opening the modal menu; a nonzero value permits
it.
While open, raw Enter selects only an enabled current item. Selection enqueues a type-3 event carrying that item’s status number. Enter on a disabled item does nothing and continues waiting. Escape closes without selection.
Type-2 navigation values have these meanings:
| Value | Menu movement |
|---|---|
| 1 | Previous item. |
| 2 | First item. |
| 3 | Next enabled heading. |
| 4 | Last item. |
| 5 | Next item. |
| 6 | Last heading. |
| 7 | Previous enabled heading. |
| 8 | Root heading. |
Item movement follows the ordered circular item list and does not skip disabled items. Heading-left/right movement skips disabled headings. Leaving a heading stores its current item. Exit restores the saved visible menu rectangle, redraws or clears the status row as configured, and clears the interaction request.
Font boundary
Message substitution and ordinary window layout are specified by their action effects and observed modal geometry. Exact glyph shapes remain a platform-font concern, so full bitmap-identical text-presentation claims must provide a font input. This does not make parser, event, menu, or inventory state provisional.
Rooms, Replay, and Persistence
This chapter defines room transitions, the resource replay sequence, restart, save/restore selection, and the known save-file envelope. Save-state semantics are normative where mapped. Reserved serialized portions are identified explicitly and have defined initialization and preservation rules.
Room transition
An immediate or variable-selected room switch performs this sequence:
- Stop active sound.
- Reset transient display/update work, parsed-input state, persistent-object participation, room-scoped resources, and the resource replay sequence.
- Preserve the global logic needed by the next top-level cycle.
- Reset the horizon to
36, disable the configured movement rectangle, and reset ordinary object cadence, step, and cel-timer defaults to1. - Copy old
v0tov1, store the destination inv0, and store object 0’s selected view number inv16. - Clear
v4andv5, then load the destination logic resource. - Apply the entry-boundary selector in
v2to object 0 and clearv2. - Set new-room flag
f5and redraw normal status/input presentation. - Abort the current logic continuation.
The next top-level pass starts logic 0 again. The room-switch operation does not implicitly execute the destination room logic before that pass.
Entry-boundary values are:
v2 | Object 0 placement |
|---|---|
| 1 | Baseline Y becomes 167. |
| 2 | Left X becomes 0. |
| 3 | Baseline Y becomes 37. |
| 4 | Left X becomes 160 - cel width. |
Profile 3.002.149 first maps immediate destinations 0x7e, 0x7f, and
0x80 to room 0x49. Other destinations and the common transition sequence
are unchanged.
Resource replay sequence
The engine maintains an ordered sequence of two-byte (kind, value) pairs.
It records only operations needed to reconstruct room resource/display state;
it is not a general execution log.
Recording appends only while f7 is clear and the internal recording gate is
enabled. Capacity is configured in pairs. Exceeding capacity is an engine
error; valid game execution must configure enough space.
The pair kinds are:
| Kind | Value | Replay operation |
|---|---|---|
| 0 | logic number | Load and retain the logic, then restore its saved resume metadata. |
| 1 | view number | Load or refresh the view. |
| 2 | picture number | Load the picture. |
| 3 | sound number | Load the sound. |
| 4 | picture number | Prepare/decode the already loaded picture after clearing logical picture state. |
| 5 | zero | Consume the next three pairs as transient-view parameters and reproduce the transient cel draw. |
| 6 | picture number | Discard the picture. |
| 7 | view number | Discard the view. |
| 8 | picture number | Overlay/decode the already loaded picture without clearing logical picture state. |
Kind 5 is a four-pair packet. After (5,0), the next three pairs carry these
seven bytes in order:
(view, loop)
(cel, left_x)
(baseline_y, packed_priority_control)
The final byte contains the staged priority/control nibbles used by transient composition.
Replay stops sound, resets room resource caches, disables recording, executes the sequence in order, and then re-enables recording before object view bindings and normal display/input state are refreshed. Replayed operations therefore do not append duplicates.
Replay kinds 6 and 7 use the ordinary ordered-discard rule. They remove the named picture or view and every resource retained later in that same family. Subsequent replay pairs may load those resources again, establishing a new retention order before object bindings are restored.
Temporary view preview actions disable recording around their internal load/display/discard work, so they never become persistent replay events.
Replay checkpoints
The replay sequence tracks an active pair count. The checkpoint action saves that count. The rollback action restores it and moves the append position to the end of the restored prefix. Pairs after the checkpoint remain outside the active sequence and are neither replayed nor included as active state.
Save selector
Save and restore use a modal selector with up to 12 numbered slots.
On entry, the selector remembers whether the normal prompt marker was visible, erases it, saves text/window state, stops sound, and switches to selector text attributes. Every nonfatal exit restores text state and redraws the prompt marker only when required by the profile and entry state.
If no save directory is already available, the selector prompts for one. Path normalization:
- skips leading spaces;
- substitutes the current directory for an accepted empty string;
- removes one trailing slash or backslash from strings longer than one byte;
- accepts a single slash or backslash;
- checks two-character drive paths such as
A:for drive availability; and - otherwise requires the path to resolve as a directory.
Invalid/unavailable paths display the path or disk prompt and may repeat. Escape cancels without file I/O.
The selector scans slots 1 through 12. Restore lists only files whose header and signature prefix pass validation. Save may select an empty slot; doing so opens a second editor for a description of at most 31 bytes. Enter accepts the current row, Escape cancels, and movement values 1 and 5 move up/down with wrap.
Save names and signatures
The game signature action copies up to seven message bytes into the runtime signature and verifies the profile’s expected game identifier. The signature is used in save filenames and in restore candidate validation.
The observed filename stem is the signature followed by SG. and the slot
number. For example, signatures SQ2 and GR produce SQ2SG.1 and GRSG.1.
An empty signature produces SG.1.
Restore candidate scanning reads the 31-byte header, skips the first block length, and compares the first seven state bytes with the active signature area before listing the slot.
Save-file envelope
A save file has this framing:
description_header[31]
repeat profile_block_count times:
block_length:u16le
block_data[block_length]
The displayed description is the zero-terminated prefix of the 31-byte header.
Profiles 2.230, 2.272, and later profiles use five conceptual blocks:
- global scalar, signature, string, parser, display, and session state;
- persistent drawable-object state;
- inventory and related object metadata;
- the configured resource replay-pair storage;
- variable-sized loaded-logic/cache resume state.
Profile 2.089 uses only the first four blocks. It does not write or read a loaded-logic/cache resume block. For the selected SQ1 data, its dimensions are:
| Block | Length |
|---|---|
| 1 | 987 (0x03db) |
| 2 | 731 (0x02db), containing 17 object records |
| 3 | 339 (0x0153) |
| 4 | Twice the configured replay-pair capacity |
For profile 2.272 and the selected XMAS data:
| Block | Length |
|---|---|
| 1 | 987 (0x03db) |
| 2 | 774 (0x0306), containing 18 object records |
| 3 | 15 (0x000f) |
| 4 | Twice the configured replay-pair capacity |
| 5 | Variable |
For profile 2.230 and the selected XMAS.230 data:
| Block | Length |
|---|---|
| 1 | 987 (0x03db) |
| 2 | 774 (0x0306), containing 18 object records |
| 3 | 15 (0x000f) |
| 4 | 400 (0x0190), containing 200 replay pairs |
| 5 | Variable |
The source-backed 2.089/2.230/2.272 block-1 partition is:
| Offset | Size | Meaning |
|---|---|---|
0x0000 | 7 | Game/save signature area. |
0x0007 | 0x0100 | Variables v0..v255. |
0x0107 | 0x0020 | Packed flags f0..f255. |
0x0127 | 4 | Timer tick counter. |
0x012b | 2 | Selected display-mode value. |
0x012d | 2 | Object horizon baseline. |
0x012f | 2 | Previous navigation-event value. |
0x0131 | 2 | Movement rectangle left bound. |
0x0133 | 2 | Movement rectangle top bound. |
0x0135 | 2 | Movement rectangle right bound. |
0x0137 | 2 | Movement rectangle bottom bound. |
0x0139 | 2 | Object-0 direction-coupling selector. |
0x013b | 2 | Most recently prepared picture number. |
0x013d | 2 | Direction selected by navigation input. |
0x013f | 2 | Movement rectangle enabled state. |
0x0141 | 2 | Reserved startup count; preserve on round trip. |
0x0143 | 2 | Configured replay-pair capacity. |
0x0145 | 2 | Active replay-pair count. |
0x0147 | 0x009c | Thirty-nine four-byte raw-key/status mappings. |
0x01e3 | 4 | Reserved padding before strings. |
0x01e7 | 0x00f0 | Six 40-byte script string slots. |
0x02d7 | 0x00f0 | Six reserved 40-byte string records. |
0x03c7 | 2 | Derived foreground text attribute. |
0x03c9 | 2 | Derived background text attribute. |
0x03cb | 2 | Packed current text/window attribute. |
0x03cd | 2 | Input-line enabled state. |
0x03cf | 2 | Configured input row. |
0x03d1 | 1 | Prompt marker byte. |
0x03d2 | 1 | Reserved alignment byte. |
0x03d3 | 2 | Status-line enabled state. |
0x03d5 | 2 | Configured status row. |
0x03d7 | 2 | Display base row. |
0x03d9 | 2 | Display base row plus 21. |
These offsets define the semantic partition and round-trip preservation contract. Canonical initial bytes for reserved and selected-game-dependent fields have not been established, so constructing a byte-identical new save without an existing state remains outside current binary interchange claims.
For profile 2.411, the observed KQ2 state uses lengths:
| Block | Length |
|---|---|
| 1 | 1503 (0x05df) |
| 2 | 731 (0x02db) |
| 3 | 598 (0x0256) |
| 4 | 120 (0x0078) |
| 5 | Variable. |
For profile 2.440, the observed LSL1 state uses lengths:
| Block | Length |
|---|---|
| 1 | 1503 (0x05df) |
| 2 | 731 (0x02db) |
| 3 | 308 (0x0134) |
| 4 | 288 (0x0120) |
| 5 | Variable. |
The observed BC data selects the mapped-equivalent 2.439/2.440 behavioral rules with these separate dimensions:
| Block | Length |
|---|---|
| 1 | 1503 (0x05df) |
| 2 | 731 (0x02db) |
| 3 | 309 (0x0135) |
| 4 | 254 (0x00fe) |
| 5 | Variable. |
For the observed profile 2.936 game data, block lengths are:
| Block | Length |
|---|---|
| 1 | 1505 (0x05e1) |
| 2 | 903 (0x0387) |
| 3 | 328 (0x0148) |
| 4 | 200 (0x00c8) |
| 5 | Variable. |
For profile 2.917, the observed KQ1 state uses lengths:
| Block | Length |
|---|---|
| 1 | 1505 (0x05e1) |
| 2 | 774 (0x0306) |
| 3 | 328 (0x0148) |
| 4 | 200 (0x00c8) |
| 5 | Variable. |
For profile 2.917, the observed PQ1 state uses lengths:
| Block | Length |
|---|---|
| 1 | 1505 (0x05e1) |
| 2 | 860 (0x035c) |
| 3 | 366 (0x016e) |
| 4 | 500 (0x01f4) |
| 5 | Variable. |
The selected MG directory does not currently define a binary-save interchange
contract. Its bundled interpreter and metadata derive block lengths
0x05e1, 0x0f49, and 0x0005 for the first three blocks. Its bundled save
uses incompatible lengths 0x05df, 0x0387, and 0x0005, so that save must
not be treated as an output of the selected interpreter/data combination.
The observed SQ1.22 data uses profile 2.917 with lengths 0x05e1, 0x0306,
0x0148, 0x0064, and a variable fifth block.
For profile 2.936, the observed KQ3 state uses lengths:
| Block | Length |
|---|---|
| 1 | 1505 (0x05e1) |
| 2 | 731 (0x02db) |
| 3 | 775 (0x0307) |
| 4 | 254 (0x00fe) |
| 5 | Variable. |
For profile 3.002.086, the observed full KQ4 state uses lengths:
| Block | Length |
|---|---|
| 1 | 1505 (0x05e1) |
| 2 | 1118 (0x045e) |
| 3 | 710 (0x02c6) |
| 4 | 500 (0x01f4) |
| 5 | Variable. |
For profile 3.002.149, the observed Gold Rush state uses lengths:
| Block | Length |
|---|---|
| 1 | 1028 (0x0404) |
| 2 | 989 (0x03dd) |
| 3 | 1811 (0x0713) |
| 4 | 100 (0x0064) |
| 5 | Observed initial saves use 12 (0x000c); the record grammar is variable. |
The envelope, lengths, signature prefix, and mapped subsystem effects are normative. All five blocks in the observed profile 2.936 game data are mapped below. Profile-specific reserved bytes remain explicitly identified.
All positions in the following tables are relative to the start of that block. All multi-byte integers are little-endian. Reserved ranges are part of the file contract even though valid game operations do not address them. A newly initialized state uses the canonical bytes listed below. A save loaded for binary interchange preserves the bytes it supplied and emits them unchanged.
Profiles 2.411 and 2.440 observed early blocks
Both early profiles use the first 0x05df bytes of the profile 2.936 block-1
partition. They include all fields through display_bottom_row and omit the
later two-byte saved replay-checkpoint count. Reserved ranges within that
prefix use the same canonical initialization and byte-preservation rules.
The selected KQ2 data uses 17 consecutive 0x2b-byte object records in block
2. Its 0x0256-byte block 3 contains 85 three-byte inventory entries followed
by a 343-byte zero-terminated display-name pool. It configures 60 replay pairs,
so block 4 is 0x0078 bytes.
The selected LSL1 data also uses 17 object records. Its 0x0134-byte block 3
contains 21 three-byte inventory entries followed by a 245-byte display-name
pool. It configures 144 replay pairs, so block 4 is 0x0120 bytes.
Both profiles store block 3 directly without the v3 transform. Block 5 uses the common variable-length logic-resume grammar.
Profile 2.917 observed KQ1 blocks
The selected KQ1 data uses the profile 2.936 block-1 partition and reserved
state rules exactly. Block 1 is 0x05e1 bytes.
Block 2 contains 18 consecutive 0x2b-byte object records with the record
partition specified below for profile 2.936. The decoded inventory metadata
header’s maximum object index is 17, establishing that count.
Block 3 is 0x0148 bytes. Its first 81 bytes contain 27 three-byte inventory
entries and its remaining 247 bytes are the zero-terminated display-name pool.
The block is stored directly, without the v3 transform.
The selected game configures 100 replay-pair slots, so block 4 is 0x00c8
bytes. Block 5 uses the common variable-length logic-resume grammar.
Profile 2.917 observed PQ1 blocks
PQ1 uses the same 0x05e1 block-1 partition. Block 2 contains 20 consecutive
object records. Block 3 is 0x016e bytes: 25 three-byte inventory entries
followed by a 291-byte display-name pool. The selected game configures 250
replay pairs, so block 4 is 0x01f4 bytes. Block 5 uses the common grammar.
Profile 2.936 observed KQ3 blocks
KQ3 uses the same 0x05e1 block-1 partition. Block 2 contains 17 consecutive
object records. Block 3 is 0x0307 bytes: 55 three-byte inventory entries
followed by a 610-byte display-name pool. The selected game configures 127
replay pairs, so block 4 is 0x00fe bytes. Block 5 uses the common grammar.
Profile 3.002.086 observed full KQ4 blocks
The selected full KQ4 data uses the profile 2.936 block-1 partition and
reserved-state rules exactly. Block 1 is 0x05e1 bytes. The profile’s menu
interaction gate and incrementing key-release gate are not fields in this
serialized block.
Block 2 contains 26 consecutive 0x2b-byte object records. The decoded
inventory metadata header’s maximum object index is 25, establishing that
count.
Block 3 is XOR-transformed on disk. Its decoded 0x02c6-byte payload contains:
| Position | Size | Portable state |
|---|---|---|
0x0000 | 135 | Forty-five three-byte inventory entries. |
0x0087 | 575 | Zero-terminated inventory display-name pool. |
The selected game configures 250 replay-pair slots, so block 4 is 0x01f4
bytes. Block 5 uses the common variable-length logic-resume grammar.
Profile 2.936 block 1
Block 1 is exactly 0x05e1 bytes. Its complete partition is:
| Position | Size | Portable state |
|---|---|---|
0x0000 | 7 | Game/save signature area. |
0x0007 | 256 | Variables v0 through v255. |
0x0107 | 32 | Packed flags f0 through f255. |
0x0127 | 4 | Unsigned 32-bit timer tick count. |
0x012b | 2 | Horizon baseline. |
0x012d | 2 | Reserved word; canonical bytes are 00 00. |
0x012f | 2 | Movement rectangle left bound. |
0x0131 | 2 | Movement rectangle top bound. |
0x0133 | 2 | Movement rectangle right bound. |
0x0135 | 2 | Movement rectangle bottom bound. |
0x0137 | 2 | Object-0/global-direction coupling selector. |
0x0139 | 2 | Most recently prepared picture number. |
0x013b | 2 | Movement rectangle enable value. |
0x013d | 2 | Reserved word; canonical bytes are 0f 00. |
0x013f | 2 | Replay-pair capacity. |
0x0141 | 2 | Active replay-pair count. |
0x0143 | 156 | Thirty-nine key mappings, each raw_key:u16le, status:u16le. |
0x01df | 40 | Ten inactive key-map records outside this profile’s 39-entry capacity; canonical contents are all zero. |
0x0207 | 4 | Reserved pre-string padding; canonical contents are all zero. |
0x020b | 480 | Twelve script string slots of 40 bytes each. |
0x03eb | 480 | Twelve reserved 40-byte records outside the valid string-slot range; canonical contents are all zero. |
0x05cb | 2 | Derived foreground text attribute. |
0x05cd | 2 | Derived background text attribute. |
0x05cf | 2 | Packed current text/window attribute. |
0x05d1 | 2 | Input-line enabled value. |
0x05d3 | 2 | Input text row. |
0x05d5 | 1 | Prompt-marker character. |
0x05d6 | 1 | Reserved byte before following word state; canonical value is zero. |
0x05d7 | 2 | Status-line enabled value. |
0x05d9 | 2 | Status text row. |
0x05db | 2 | Display base row. |
0x05dd | 2 | Display bottom row. |
0x05df | 2 | Replay checkpoint count. |
The string region contains twelve addressable 40-byte slots. The following 480-byte reserved bank is not an additional set of script-visible slots.
Profile 2.936 block 2
Block 2 is exactly 21 consecutive object records of 0x2b bytes each. Object
index n occupies block positions n * 0x2b through
n * 0x2b + 0x2a. Each record has this complete partition:
| Record position | Size | Portable state |
|---|---|---|
0x00 | 1 | Movement-cadence interval. |
0x01 | 1 | Movement-cadence countdown. |
0x02 | 1 | Boundary/collision event identifier. |
0x03 | 2 | Current left X coordinate. |
0x05 | 2 | Current baseline Y coordinate. |
0x07 | 1 | Selected view number. |
0x08 | 2 | Serialized view-reference token. |
0x0a | 1 | Selected loop number. |
0x0b | 1 | Loop count in the selected view. |
0x0c | 2 | Serialized selected-loop-reference token. |
0x0e | 1 | Selected cel number. |
0x0f | 1 | Cel count in the selected loop. |
0x10 | 2 | Serialized selected-cel-reference token. |
0x12 | 2 | Serialized previous-cel-reference token. |
0x14 | 2 | Serialized render-list-reference token. |
0x16 | 2 | Previous or saved left X coordinate. |
0x18 | 2 | Previous or saved baseline Y coordinate. |
0x1a | 2 | Selected cel width. |
0x1c | 2 | Selected cel height. |
0x1e | 1 | Movement step size. |
0x1f | 1 | Cel-cycling interval. |
0x20 | 1 | Cel-cycling countdown. |
0x21 | 1 | Movement direction. |
0x22 | 1 | Autonomous motion mode. |
0x23 | 1 | Cel-cycling mode. |
0x24 | 1 | Priority/control byte. |
0x25 | 2 | Object state flags. |
0x27 | 4 | Mode-dependent motion parameters. |
The five reference tokens are serialized profile data, not portable object identity. Successful restore keeps the selected view, loop, and cel numbers, then reconstructs their references, loop/cel counts, and cel dimensions from the loaded view resource. It reconstructs drawing-list participation from the saved flags and then restores the saved flag word. The event identifier is normalized to the object’s table index. A clean runtime may organize these associations differently; it must reproduce that rebuilt state and subsequent behavior rather than expose the token values.
Profile 2.936 block 3
Block 3 is the runtime_inventory_data from the game’s decoded inventory
metadata file. Its length, item count, and name-pool boundary are therefore
game-data properties rather than universal constants of the interpreter
profile.
For the observed game data it is exactly 0x0148 bytes:
| Position | Size | Portable state |
|---|---|---|
0x0000 | 120 | Forty three-byte inventory entries. |
0x0078 | 208 | Zero-terminated inventory display-name pool. |
Each three-byte entry contains name_offset:u16le, location:u8. The name offset
is relative to block 3 and must select a zero-terminated name in the name pool.
Multiple entries may share a name offset. Item number is the entry index.
The location byte is the mutable inventory state used by logic actions; 0xff
means carried. The name offsets and name pool originate in the game metadata
and remain part of the serialized block. The block length is the decoded
inventory metadata file length minus its three-byte header.
Profile 2.936 block 4
Block 4 is exactly 100 consecutive two-byte (kind, value) replay-pair slots.
The active count in block 1 selects the prefix that participates in replay.
Slots after that prefix are inactive capacity and must not be executed. The
checkpoint count in block 1 is also measured in pairs and identifies an earlier
active-prefix length.
The capacity value in block 1 and the block-4 byte length must agree for valid
profile state: block_4_length = replay_capacity * 2.
Profile 2.936 block 5
Block 5 is a variable-length sequence of four-byte logic-resume records:
logic_number:u16le
resume_offset:u16le
The block contains:
- A leading cache-head record, observed as
(0, 0). - One record for each cached logic, in cache order.
- A final record whose logic number is
0xffff.
The terminator’s second word is ignored and need not be zero. Block length is
therefore (cached_logic_count + 2) * 4 bytes. Nonterminal logic numbers are
zero-extended 8-bit resource numbers. Resume offset is measured from the first
byte of that logic’s bytecode, not from a process-specific reference.
This block does not decide which logic resources are restored. The replay sequence does. Whenever replay loads a logic, restoration scans block 5 from the beginning and uses the first record with that logic number. The resulting resume position is:
loaded_logic_bytecode_start + resume_offset
No matching record leaves the newly loaded logic at its normal bytecode entry.
Records for logics not loaded by replay have no effect. Duplicate logic numbers
are permitted; only the first match is effective. Consequently the observed
leading (0, 0) record would take precedence over a later cached-logic-0 record
if logic 0 were replay-loaded.
Profile 3.002.102 observed KQ4D demo blocks
The selected KQ4D demo uses the same five-block envelope and the same block-1
positions as profile 2.936 through position 0x05e0. It appends two v3 fields:
| Position | Size | Portable state |
|---|---|---|
0x05e1 | 2 | Menu interaction gate. |
0x05e3 | 1 | Key-release enqueue gate. |
Block 1 is therefore 0x05e4 bytes. Its 39-entry key map, reserved key-map
tail, twelve valid string slots, reserved string bank, text fields, and replay
checkpoint use the profile 2.936 layout and reserved-state rules.
Block 2 is 16 consecutive object records of 0x2b bytes each. Block 3 is
XOR-transformed on disk as described below. Its decoded five-byte payload is:
| Position | Size | Portable state |
|---|---|---|
0x0000 | 3 | One inventory entry: name_offset:u16le, location:u8. |
0x0003 | 2 | Zero-terminated display-name pool containing ?. |
The decoded inventory metadata header’s maximum object index is 15, establishing
the 16 block-2 records. The selected demo sets replay-pair capacity to one, so
block 4 contains one two-byte (kind, value) slot. Block 5 uses the common
variable-length logic-resume grammar.
Profile 3.002.149 observed Gold Rush blocks
Profile 3.002.149 uses the same five-block envelope and conceptual block roles. The observed Gold Rush data changes capacities and applies the block-3 transform described below.
Block 1 is 0x0404 bytes in the observed Gold Rush saves. Its complete
partition is:
| Position | Size | Portable state |
|---|---|---|
0x0000 | 7 | Game/save signature area; a valid signed save begins with GR\0. |
0x0007 | 256 | Variables v0 through v255. |
0x0107 | 32 | Packed flags f0 through f255. |
0x0127 | 4 | Unsigned 32-bit timer tick count. |
0x012b | 2 | Horizon baseline. |
0x012d | 2 | Reserved word; canonical bytes are 00 00. |
0x012f | 2 | Movement rectangle left bound. |
0x0131 | 2 | Movement rectangle top bound. |
0x0133 | 2 | Movement rectangle right bound. |
0x0135 | 2 | Movement rectangle bottom bound. |
0x0137 | 2 | Object-0/global-direction coupling selector. |
0x0139 | 2 | Most recently prepared picture number. |
0x013b | 2 | Movement rectangle enable value. |
0x013d | 2 | Reserved word; canonical bytes are 0f 00. |
0x013f | 2 | Replay-pair capacity; observed value is 50. |
0x0141 | 2 | Active replay-pair count. |
0x0143 | 196 | Forty-nine key mappings, each raw_key:u16le, status:u16le. |
0x0207 | 4 | Reserved pre-string padding; canonical contents are all zero. |
0x020b | 480 | Twelve script string slots of 40 bytes each. |
0x03eb | 2 | Derived foreground text attribute. |
0x03ed | 2 | Derived background text attribute. |
0x03ef | 2 | Packed current text/window attribute. |
0x03f1 | 2 | Input-line enabled value. |
0x03f3 | 2 | Input text row. |
0x03f5 | 1 | Prompt-marker character. |
0x03f6 | 1 | Reserved byte before following word state; canonical value is zero. |
0x03f7 | 2 | Status-line enabled value. |
0x03f9 | 2 | Status text row. |
0x03fb | 2 | Display base row. |
0x03fd | 2 | Display bottom row. |
0x03ff | 2 | Replay checkpoint count. |
0x0401 | 2 | Menu interaction gate. |
0x0403 | 1 | Key-release enqueue gate. |
This profile keeps the same twelve string slots as profile 2.936. The expanded
49-slot key map consumes the ten inactive key-map records serialized by the
2.936 profile. The four reserved bytes at 0x0207..0x020a remain before the
string slots.
Block 2 is 23 consecutive object records of 0x2b bytes each. Each record uses
the same record layout specified for profile 2.936 block 2. The record count is
derived from the decoded Gold Rush object metadata header: maximum drawable
object index 22 means records for objects 0..22.
Block 3 is transformed on disk. After applying the profile 3.002.149 transform,
the decoded block is exactly the runtime inventory payload from the decoded
Gold Rush OBJECT metadata file. Its decoded length is 0x0713 bytes:
| Position | Size | Portable state |
|---|---|---|
0x0000 | 393 | One hundred thirty-one three-byte inventory entries. |
0x0189 | 1418 | Zero-terminated inventory display-name pool. |
Each three-byte entry has the same name_offset:u16le, location:u8 format as
profile 2.936 block 3. Name offsets are relative to the decoded block.
Block 4 is 50 consecutive two-byte (kind, value) replay-pair slots. The
active count in block 1 selects the prefix that participates in replay, as in
profile 2.936.
Block 5 uses the same four-byte logic-resume record grammar as profile 2.936.
The observed initial Gold Rush saves contain three records: leading (0, 0),
cached logic-0 (0, 0), and terminator (0xffff, 0). As with profile 2.936,
the replay-pair sequence decides which logic resources are loaded; block 5 is
only a resume-offset lookup consulted during replayed logic loads.
V3 block-3 transform
Profiles 3.002.086, 3.002.102, and 3.002.149 XOR-transform block 3 on disk with this repeating ASCII key:
Avis Durgan
For byte index i within block 3:
stored[i] = runtime[i] XOR key[i modulo 11]
Saving applies the transform for output and restores the in-memory bytes before returning. Restoring applies the same transform after reading block 3. Applying the operation twice returns the original data.
Save action outcomes
After successful selection, save displays its confirmation state, creates the slot file, writes the header and all five length-prefixed blocks, closes the file, restores modal state, and continues after the save action.
Create failure is recoverable: display the directory-full/write-protected message, restore modal state, and continue. A short write is also recoverable: close and delete the partial file, display the disk-full message, restore modal state, and continue.
The last selected/entered save-description buffer can be copied into a logic string slot. That copy uses at most 31 bytes.
Restore action outcomes
Cancel and file-open failure are recoverable and continue after the restore action. A failure while reading any selected save block is fatal after the restore-error dialog; it does not return to bytecode.
Successful restore:
- Replaces scalar, parser, object, inventory, replay, logic-resume, display, and session state with saved values.
- Resets transient caches and replays the saved resource sequence with recording disabled.
- Rebinds object views and refreshes picture, objects, menu, status, and input presentation.
- Aborts the current continuation.
Execution therefore resumes through restored logic state rather than from the instruction following the restore action.
Restart
Profile 2.411 always requests confirmation. In every other promoted profile,
f16 skips the prompt and accepts restart immediately. Cancellation continues
after the action; sound has already stopped and normal prompt/input presentation
is restored.
Accepted restart:
- stops sound and erases active input;
- preserves the prior value of
f9across reset; - clears transient allocation, resource, replay, menu, object, parser, and display state to startup-compatible values;
- reruns initial object/inventory setup;
- sets restarted flag
f6; - clears the engine’s two timing accumulators;
- reloads configured trace logic when present; and
- aborts the current logic continuation.
Profiles 3.002.086, 3.002.102, and 3.002.149 remember whether the prompt marker was visible before confirmation. They redraw the marker after accepted restart and after canceled restart only when it had been visible on entry.
Process termination
Immediate exit terminates without confirmation. Confirmed exit, game-signature failure, unrecoverable allocation failure, and restore read failure share the cleanup path: close the log if open, restore input/timer hooks and the prior display mode, and terminate with process exit code zero.
Reserved-state rule
Every byte position in the observed 2.089, 2.230, 2.272, 2.411, 2.440, 2.917, 2.936, 3.002.086, 3.002.102, and 3.002.149 save blocks has a portable field or a reserved-state assignment. Valid operations do not read or modify the reserved records and padding as game state. Restoring and re-saving an existing save preserves its supplied reserved bytes. A newly synthesized save for profiles 2.411 and later uses the canonical values stated by its selected profile. Profiles 2.089, 2.230, and 2.272 require a caller-supplied initialization policy for reserved bytes because their canonical pristine values have not been established. Other interpreter/game profiles require independent byte maps before binary interchange can be claimed.
Sound Resources and Playback
This chapter defines the sound behavior shared by the promoted profiles. Version 3 containers may compress the payload before loading; after expansion, the sound payload has the format below.
Payload format
A sound payload begins with four little-endian 16-bit offsets. Each offset is relative to the beginning of the payload and identifies one channel stream.
| Bytes | Channel |
|---|---|
0..1 | Channel 0. |
2..3 | Channel 1. |
4..5 | Channel 2. |
6..7 | Channel 3. |
Each channel is an independent sequence of events:
| Field | Size | Meaning |
|---|---|---|
| Duration | 2 bytes | Unsigned little-endian countdown until the next record. |
| Tone | 2 bytes | Device-profile tone value. |
| Control | 1 byte | Its low nibble is attenuation; 0x0f is silent. |
Duration 0xffff terminates the channel and has no following tone or control
field. A valid stream reaches a terminator without reading outside the payload.
Logic operations
Sound is controlled by three action opcodes:
| Opcode | Operands | Behavior |
|---|---|---|
0x62 | sound number | Load the sound resource. |
0x63 | sound number, completion flag | Stop any active sound, select the loaded sound, remember and clear the supplied flag, then begin playback. |
0x64 | none | Stop active playback. If a sound was active, silence it and set its remembered completion flag. |
Starting a sound requires that resource to have been loaded. Starting a new sound first completes the stop behavior for the old active sound, including its old completion flag, before installing and clearing the new completion flag.
Tick schedule
Playback advances in sound ticks. Starting a sound gives every participating
channel a countdown of 1, so its first event or terminator is consumed on
tick 1.
When an event is consumed, its duration becomes that channel’s next 16-bit
countdown. The countdown decreases once per sound tick. The next record is
consumed when the countdown reaches its event-read point. Equivalently, if an
event is read at tick T, the following record is read at:
T + duration when duration is nonzero
T + 65536 when duration is zero
The zero case follows 16-bit countdown wraparound.
Playback completes naturally when every participating channel reaches its terminator. Completion silences the selected sound profile, marks playback inactive, and sets the remembered completion flag.
Flag f9 is a playback gate. If it is clear at the beginning of an active
sound tick, playback stops and completes on that tick without consuming more
channel events.
Channel profiles
The single-channel sound profile advances channel 0 only. The four-channel profile advances channels 0 through 3 and completes at the latest terminator among them. Channels that terminate earlier remain silent while the others continue.
In profiles 2.089, 2.230, 2.272, 2.411, and 2.440, device selector 0 selects
the single-channel profile and every nonzero value selects all four channels. In profiles 2.917,
2.936, 3.002.086, 3.002.102, and 3.002.149, selectors 0 and 8 select the
single-channel profile; other values select all four channels.
PC-speaker output
The single-channel PC-speaker profile uses the event’s attenuation low nibble
as its gate. Attenuation 0x0f is silence. Any other value enables the tone
whose integer divisor is:
12 * (((tone & 0x003f) << 4) + ((tone >> 8) & 0x000f))
Stopping or completing the sound disables the speaker. A conforming backend does not need to reproduce the original hardware operations, but its note and silence sequence must follow the same divisors and tick schedule.
Four-channel command output
For the four-channel profile, a tone event emits the high byte and low byte of the tone word in profiles 2.089, 2.230, 2.272, and 2.411. Every other promoted profile emits the low byte unless the high byte has its top three bits set; in that case, only the high byte is emitted.
Stopping or completing playback emits this silence sequence:
9f bf df ff
Attenuation commands combine a channel selector with a low-nibble attenuation:
| Channel | Selector |
|---|---|
| 0 | 0x90 |
| 1 | 0xb0 |
| 2 | 0xd0 |
| 3 | 0xf0 |
Profiles 2.089 and 2.230
These profiles have no attenuation-envelope state. For device selector 2, a
control byte whose low nibble is below 8 has that nibble increased by 3. It
then emits the resulting control byte unchanged. The runtime global
attenuation adjustment does not affect this output.
Profile 2.272
This profile also has no attenuation-envelope state. It first applies the same
device-selector-2 low-nibble increase by 3. It then adds the unsigned 8-bit
global adjustment to the entire control byte modulo 256. It treats the result
as signed: a result greater than signed 15 is replaced by 0x0f; every
other result is emitted unchanged. Consequently the global-adjustment step
does not independently preserve the high channel-selector nibble.
Profiles 2.411 and 2.440
The early profiles have no attenuation-envelope state. They emit an attenuation command when consuming an event or channel terminator, but do not change attenuation on intervening countdown ticks.
For a consumed event, add the runtime global attenuation adjustment to the
control byte’s low nibble and clamp the result to 0x0f. Preserve the control
byte’s high channel-selector nibble and combine it with that result. There is no
additional device-2 adjustment. A terminator emits attenuation 0x0f for its
channel.
Profiles 2.917 and later
Each participating channel has three attenuation-envelope state fields:
| Field | Initial value when playback starts | Meaning |
|---|---|---|
| Base attenuation | Event-defined | The low nibble from the most recently consumed event, or 0x0f after channel termination. |
| Envelope index | Disabled | The current position in the envelope table. |
| Envelope value | Unspecified until first envelope step | The last clamped envelope result. |
Playback start disables the envelope index for every channel. When a channel consumes a new event, channels 0, 1, and 2 reset their envelope index to zero before storing the event’s base attenuation. Channel 3 preserves its current envelope index across event boundaries. This channel-3 persistence is part of the 2.936 profile.
On each attenuation output for a non-silent base attenuation, an enabled envelope index consumes one byte from the default envelope table:
fe fd fe ff 00 00 01 01 01 01 02 02 02 02 02 02
02 02 03 03 03 03 03 03 03 04 04 04 04 05 05 05
05 06 06 06 06 06 07 07 07 07 08 08 08 08 09 09
09 09 0a 0a 0a 0a 0b 0b 0b 0b 0b 0b 0c 0c 0c 0c
0c 0c 0d 80
Table byte 0x80 disables the envelope and copies the previous envelope value
into the base attenuation. Any other table byte is treated as an 8-bit signed
delta from the current event’s base attenuation, not from the previous
envelope output. The result is clamped to 0..0x0f, stored as the new envelope
value, and then used as the emitted attenuation for that output.
After envelope processing, the runtime global attenuation adjustment is added
and clamped to 0x0f. The four-channel profile selected by device value 2
then increases any non-silent attenuation below 8 by 2. Finally, the
channel selector byte is combined with the low-nibble attenuation and emitted.
If the base attenuation is already 0x0f, no envelope step, global
adjustment, or device-2 adjustment occurs; the channel emits its selector byte
combined with 0x0f.
Output boundary
Exact analog waveform synthesis is outside the specification. Compatibility is defined by resource interpretation, participating channels, event order, timing, tone values or divisors, attenuation command bytes, silence transitions, active state, and completion flags.