Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

OpcodeOperandsBehavior
0xb1one immediate byteSet the menu interaction gate. Zero prevents a pending menu request from entering modal menu interaction; nonzero permits it.
0xb5noneClear 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:

ClassificationMeaning
CommonThe stated contract applies to all promoted profiles.
Profile variantThe profile selects a stated alternative.
Game dataThe format and interpretation are fixed, but counts, names, sizes, or values come from the selected game.
PartialSome observable behavior is specified, but the listed gap prevents a complete claim for that domain.
Outside targetNo 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:

DomainContract
Logic payloadsCode-length framing, message table, message XOR decoding, control-flow markers, conditions 0x00..0x12, and shared action semantics.
Runtime valuesUnsigned byte variables, packed flags, 40-byte strings, parsed words, inventory locations, object selectors, and cycle-visible state transitions.
PicturesExpanded command stream, prepare/overlay/show lifecycle, visual and priority/control channels, line/fill/pattern raster rules, and terminator behavior.
ViewsExpanded view/loop/cel structure, row-run decoding, transparency, mirroring, baseline placement, and priority composition.
ObjectsLifecycle, placement, cadence, movement, collision, priority/control acceptance, cel cycling, draw partitions, and refresh ordering.
Input and textEvent records, parser normalization and matching, mapped keys, modal input, text geometry, font-input boundary, inventory selection, and menu construction.
Rooms and replayRoom-switch state changes, top-level re-entry, replay-pair language, checkpoints, and replay-without-rerecording.
SoundResource event structure, per-channel countdown scheduling, terminators, tone/divisor output, attenuation command output, completion flags, and stop behavior.

Profile variants

Resource and bytecode variants

Domain2.9363.002.149
Resource directoryFour 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 recordFive-byte header; payload stored directly.Seven-byte header; direct, dictionary-expanded, or picture-nibble-expanded payload.
Action range0x00..0xaf.0x00..0xb5.
Extra actionsNone.0xb1 sets menu gate; 0xb5 clears release gate; 0xb0, 0xb2, 0xb3, and 0xb4 consume operands without another effect.
Condition range0x00..0x12.0x00..0x12.

Runtime and presentation variants

Domain2.9363.002.149
Script key-map capacity39 entries.49 entries.
Release-event gate action 0xadIncrement modulo 256.Set to one.
Menu interaction gateNo separate gate.Action 0xb1 selects whether a pending request may enter modal interaction.
Input-width actions 0xa3/0xa4Enable and clear the fixed width override.No effect.
Immediate room aliasesNone.None in the base MH2 build; the Gold Rush build maps 0x7e, 0x7f, and 0x80 to 0x49.
Direction-selected loopsFour-or-more-loop table applies without f20.Exactly four loops always use the table; more than four require f20.

Persistence variants

Domain2.9363.002.149
Save envelope31-byte description plus five u16le length-prefixed blocks.Same envelope.
Block layoutAll five observed game blocks are mapped in the persistence chapter.All five observed Gold Rush blocks are mapped in the persistence chapter.
Block transformNo transform for block 3.Block 3 is XOR-transformed with the specified repeating Avis Durgan key.
Binary save interchange statusDefined 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

Domain2.089 behavior
Resource containerSplit direct resources; inventory metadata is stored expanded.
Action and condition rangesActions 0x00..0x9a; conditions 0x00..0x12.
Exit and menu actions0x86 is an unconditional zero-operand exit; 0x9b and later are unavailable.
Position and compositionPosition 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 commandsDispatch 0xf0..0xf8; no pattern-command slots.
Show picturePresent and mark shown without clearing f15 or closing an active text window.
Direction-selected loopsEvaluate every eligible post-logic pass, without a cadence gate; exactly four loops use the four-loop table.
String and parser profileSix string slots; exact-count word matching without the 0x270f tail terminator.
Object distance and target motionStore wrapped low-byte distance; defer the first target direction/completion calculation.
Movement-clear actions0x4d clears direction but not autonomous mode; 0x4e leaves both unchanged and only applies object-0 coupling/navigation effects.
Inventory displayAlways acknowledgement-only; never writes a selected item to v25.
Sound outputSelector 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 envelopeFour blocks; no logic-resume block. Block 1 has a complete semantic partition.
Binary save interchange statusExisting 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

Domain2.230 behavior
Resource containerSplit direct resources; inventory metadata is stored expanded.
Action and condition rangesActions 0x00..0x9a; conditions 0x00..0x12.
Exit and menu actions0x86 is an unconditional zero-operand exit; 0x9b and later are unavailable.
Position and compositionPosition actions update current/saved coordinates together. Both draw partitions use drawing-key order.
View loop encodingThe 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 commandsDispatch 0xf0..0xf8; no pattern-command slots.
Show pictureSame retained-f15 and retained-window behavior as 2.089.
Direction-selected loopsSame cadence-independent, exact-four behavior as 2.089.
String and parser profileSix string slots; 0x270f has tail-terminator meaning.
Object distance and target motionSame wrapped distance and deferred first target update as 2.089.
Movement-clear actionsSame as 2.089: 0x4d clears direction but not autonomous mode; 0x4e leaves both unchanged and only applies object-0 coupling/navigation effects.
Inventory displayAlways acknowledgement-only; never writes a selected item to v25.
Sound outputSame 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 envelopeFive blocks with a semantically partitioned 0x03db block 1.
Binary save interchange statusExisting state bytes can be parsed and preserved; pristine synthesis requires canonical initial reserved bytes that are not currently specified.

2.272 variant selection

Domain2.272 behavior
Resource containerSplit direct resources; inventory metadata is stored expanded.
Action and condition rangesActions 0x00..0xa0; conditions 0x00..0x12.
Exit and menu actions0x86 consumes its selector; 0x9b consumes two bytes; 0x9c..0xa0 consume their operands without constructing menu state.
Position and compositionPosition actions update current/saved coordinates together. Both draw partitions use drawing-key order.
Picture commandsDispatch 0xf0..0xf8; no pattern-command slots.
Show pictureSame retained-f15 and retained-window behavior as 2.089.
Direction-selected loopsSame cadence-independent, exact-four behavior as 2.089.
String and parser profileSix string slots; 0x270f has tail-terminator meaning.
Object distance and target motionSame wrapped distance and deferred first target update as 2.089.
Movement-clear actions0x4d clears direction and autonomous mode; 0x4e clears autonomous mode while retaining direction.
Inventory displayAlways acknowledgement-only; never writes a selected item to v25.
Sound outputEarly channel selection; emit both tone bytes; apply the device-2 adjustment, then adjust and signed-clamp the entire control byte.
Save envelopeFive blocks with a semantically partitioned 0x03db block 1.
Binary save interchange statusExisting 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

Domain2.411 behavior
Resource containerFour split family directories and five-byte direct volume records.
Action and condition rangesActions 0x00..0xa9; conditions 0x00..0x12.
Configured messages0x97 and 0x98 consume message, row, column, and width.
Restart confirmationAlways display the confirmation prompt; f16 does not bypass it.
Heap diagnosticOmit the rm.0, etc. line.
Pattern commands0xf9 consumes and ignores one byte; 0xfa writes one pixel per X,Y pair without seeds.
Direction-selected loopsExactly four loops use the four-loop table; larger counts do not receive automatic selection.
Sound channelsSelector 0 uses channel 0; every nonzero selector uses all four channels.
Sound command outputNo attenuation envelopes or device-2 adjustment; always emit both non-PC tone bytes.
Save envelopeFive blocks with a 0x05df block 1 and no block-3 transform.
Binary save interchange statusDefined for the mapped KQ2 dimensions.

2.440 variant selection

Domain2.440 behavior
Resource, opcode, configured-message, loop, and save profileSame early boundaries as 2.411.
Restart confirmationf16 bypasses the prompt and accepts restart immediately.
Heap diagnosticOmit the rm.0, etc. line.
Pattern commandsUse the common shaped/stippled 0xf9/0xfa behavior.
Sound channels and attenuationEarly selector rule and no attenuation envelopes or device-2 adjustment.
Tone-byte outputSuppress the low byte when the high byte’s top three bits are all set.
Binary save interchange statusDefined for the mapped LSL1 dimensions.

2.917 variant selection

Domain2.917 behavior
Resource containerFour split family directories and five-byte direct volume records.
Action range0x00..0xad; 0xae and 0xaf are not valid actions.
Condition range0x00..0x12.
Script key-map capacity39 entries.
Release-event gate action 0xadIncrement modulo 256.
Input-width actions0xa3 and 0xa4 retain their common effects.
Immediate room aliasesNone.
Direction-selected loopsExactly four loops use the four-loop table; more than four do not receive automatic selection.
Pictures, views, and objectsThe full-EGA command, raster, composition, movement, collision, animation, update-list, and refresh contracts are common.
Save envelopeFive length-prefixed blocks with no block-3 transform.
Binary save interchange statusDefined for the mapped KQ1 dimensions; reserved bytes use canonical initialization and byte preservation.

3.002.086 variant selection

Domain3.002.086 behavior
Resource containerCombined v3 directory and seven-byte volume records with direct, dictionary-expanded, or picture-nibble-expanded payloads.
Action range and extra slots0x00..0xb1; 0xb0 consumes one ignored byte, and 0xb1 sets the menu gate.
Condition range0x00..0x12.
Script key-map capacity39 entries.
Release-event gate action 0xadIncrement modulo 256.
Input-width actions0xa3 and 0xa4 retain their v2 effects.
Immediate room aliasesNone.
Direction-selected loopsFour-or-more-loop table applies without f20.
Exact-zero left proposalClamp to zero and report left-boundary code 4.
Pictures, views, and objectsOther full-EGA command, raster, composition, movement, collision, animation, update-list, and refresh contracts are common.
Save envelope and transformFive length-prefixed blocks; block 3 uses the v3 repeating-key XOR transform.
Binary save interchange statusDefined for the mapped full KQ4 dimensions; block 1 uses the 2.936 partition and reserved-state rules.

3.002.102 variant selection

Domain3.002.102 behavior
Resource containerCombined v3 directory and seven-byte volume records with direct, dictionary-expanded, or picture-nibble-expanded payloads.
Action range and extra slots0x00..0xb5; extra slots have the 3.002.149 contracts and effects.
Script key-map capacity39 entries.
Release and menu gates0xad sets the release gate to one; 0xb5 clears it; 0xb1 sets the menu interaction gate.
Input-width actions0xa3 and 0xa4 retain their 2.936 effects; closing window state clears the override.
Immediate room aliasesNone.
Direction-selected loopsExactly four loops always use the table; more than four require f20.
Pictures, views, and objectsThe full-EGA command, raster, composition, movement, collision, animation, update-list, and refresh contracts are common.
Save envelope and transformFive length-prefixed blocks; block 3 uses the v3 repeating-key XOR transform.
Binary save interchange statusDefined for the mapped KQ4D demo dimensions; reserved bytes use canonical initialization and byte preservation.

Game-data dimensions

These values are not universal interpreter constants:

DimensionSource and effect
Resource presence and countDirectory entries determine which numbered logic, picture, view, and sound resources exist.
Resource bytesLogic behavior, pictures, cels, sounds, messages, and dictionary words come from the selected game.
Combined-container prefixThe selected game supplies the v3 directory and volume filename prefix.
Inventory item count and namesThe decoded inventory metadata header and item/name region define them.
Drawable-object countThe 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 lengthsObject count and decoded inventory metadata length determine them.
Replay storage lengthThe configured replay-pair capacity determines block 4 length.
Logic-resume block lengthThe cache population at save time determines block 5 length.
Signature and save stemThe accepted game signature establishes candidate validation and filename stem.
Room and object initial stateLogic 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

DomainSpecified portionRemaining limitation
Additional interpreter versionsThey 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:

FieldMeaning
formatThe literal agi-clean-room-conformance-results.
format_versionInteger 1 for this format.
suite_idStable identifier for the selected case set.
profileInterpreter behavior profile claimed by the producer.
producerHuman-readable identifier for the producing engine or oracle.
casesArray 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:

FieldRequired value
width160
height168
pixel_formatega16-indexed-row-major
sha256Lowercase hexadecimal SHA-256 digest of the canonical bytes.
artifactOptional 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:

FamilyPurpose
LogicExecutable game bytecode and its messages.
PictureCommands that construct the room’s visual and priority/control surfaces.
ViewAnimated cel images used for actors, objects, and previews.
SoundTimed 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:

FamilyDirectory file
LogicLOGDIR
PicturePICDIR
ViewVIEWDIR
SoundSNDDIR

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:

ByteMeaning
0Magic byte 0x12.
1Magic byte 0x34.
2Volume number; must match the directory entry.
3..4Payload 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:

BytesSection
0..1Logic entries.
2..3Picture entries.
4..5View entries.
6..7Sound 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:

ByteMeaning
0Magic byte 0x12.
1Magic byte 0x34.
2Volume metadata. Bit 0x80 selects picture-nibble expansion; the low nibble identifies the volume when that bit is set.
3..4Expanded payload length.
5..6Stored 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:

  1. If metadata bit 0x80 is set, apply picture-nibble expansion.
  2. Otherwise, if stored and expanded lengths are equal, use the bytes directly.
  3. 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.

CodeMeaning
0x000..0x0ffOne literal byte.
0x100Reset the dictionary and code width.
0x101End the expanded stream.
0x102 and aboveDictionary 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:

  1. 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.
  2. Emit the expanded string.
  3. Add previous string + first byte of current string at the next dictionary index.
  4. 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:

StateMeaning
UnloadedThe resource has no payload available to consumers.
LoadedDirectory lookup, volume reading, and expansion have produced the typed resource payload.
SelectedA logic activation, picture operation, object, or sound operation is using the loaded resource.
DiscardedThe 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:

  1. Wait until the pacing counter reaches v10, then clear that counter for the next accumulation period.
  2. Clear all transient mapped-event status values and clear f2 and f4.
  3. Update due timed input sources, then process pending input and any requested modal menu interaction. This input phase clears the previous raw-key byte v19 and parser count/error byte v9 before consuming events. Mapped status events produced here remain visible to logic for the rest of the cycle.
  4. Apply the configured object-0/global-direction mirror.
  5. Perform the pre-logic autonomous-motion and configured-boundary pass for eligible objects.
  6. Remember the status-line values v3 and f9, then execute logic 0, including nested logic calls made by that bytecode.
  7. If logic execution requests immediate re-entry, clear v9, v4, v5, and f2, update the remembered v3 value, and invoke logic 0 again without repeating pacing, input, direction mirroring, or pre-logic motion. Repeat this rule until logic 0 completes normally.
  8. Restore object 0’s direction from the global direction byte. If v3 or f9 changed during logic execution and the status line is enabled, redraw the status line.
  9. Clear object event bytes v4 and v5 and cycle flags f5, f6, and f12. These values remain available to logic until this point.
  10. 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.

NotationMeaning
immThe operand byte itself.
vNVariable whose index is the operand byte.
fNFlag whose index is the operand byte.
itemInventory-item number.
objectPersistent-object number.
resourceLogic, picture, view, or sound resource number.
messageMessage 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:

ByteEncodingBehavior
0x0000End the current logic invocation and return normally to its caller, if any.
0xfefe delta:s16leAdd the signed displacement to the position immediately after the displacement.
0xffff conditions ff delta:s16leEvaluate 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:

ByteMeaning
0xfcBegin or end an OR group.
0xfdInvert the result of the next predicate.
0xffEnd 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

OpcodeOperandsTrue when
0x00noneNever.
0x01variable, immediateThe variable equals the immediate byte.
0x02variable A, variable BA equals B.
0x03variable, immediateThe variable is less than the immediate byte.
0x04variable A, variable BA is less than B.
0x05variable, immediateThe variable is greater than the immediate byte.
0x06variable A, variable BA is greater than B.
0x07flagThe selected flag is set.
0x08variableThe flag whose number is stored in the variable is set.

Inventory and event conditions

OpcodeOperandsTrue when
0x09itemThe item’s location/state byte is 0xff.
0x0aitem, variableThe item’s location/state byte equals the variable value.
0x0cstatus numberThe selected mapped-event status byte is nonzero.
0x0dnoneA 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.

OpcodeHorizontal point or spanTrue when
0x0bLeft Xleft <= x <= right.
0x10Full span from X through x + width - 1Both ends lie within the horizontal bounds.
0x11x + floor(width / 2)The center lies within the horizontal bounds.
0x12x + width - 1The 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

OpcodeOperandsBehavior
0x01variableIncrement, saturating at 255.
0x02variableDecrement, saturating at 0.
0x03variable, immediateAssign the immediate byte.
0x04destination variable, source variableCopy the source value.
0x05variable, immediateAdd and retain the low 8 bits.
0x06destination variable, source variableAdd the source and retain the low 8 bits.
0x07variable, immediateSubtract and retain the low 8 bits.
0x08destination variable, source variableSubtract the source and retain the low 8 bits.
0x09index variable, source variableStore the source value in the variable whose index is held by the index variable.
0x0adestination variable, index variableRead the variable whose index is held by the index variable into the destination.
0x0bindex variable, immediateStore the immediate byte in the variable whose index is held by the index variable.

Action opcodes: flags

OpcodeOperandsBehavior
0x0cflagSet the flag.
0x0dflagClear the flag.
0x0eflagToggle the flag.
0x0fvariableSet the flag whose number is stored in the variable.
0x10variableClear the flag whose number is stored in the variable.
0x11variableToggle the flag whose number is stored in the variable.

Action opcodes: room and logic control

OpcodeOperandsBehavior
0x12destination logic/roomSwitch immediately to the selected room state.
0x13variableSwitch to the room whose number is stored in the variable.
0x14logic resourceLoad and retain the logic resource.
0x15variableLoad and retain the logic resource whose number is stored in the variable.
0x16logic resourceInvoke the selected logic.
0x17variableInvoke 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:

  1. Stop active sound and reset transient update, parser, object, and room resource state while retaining the global logic needed for the next cycle.
  2. Copy old v0 to v1, store the destination in v0, and preserve object 0’s selected view number in v16.
  3. Load the destination logic resource.
  4. Consume entry-boundary selector v2, repositioning object 0 when selected, then clear v2.
  5. Set the new-room flag f5 and refresh normal status/input display state.
  6. 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:

v2Object 0 placement
1Baseline Y becomes 167.
2X becomes 0.
3Baseline Y becomes 37.
4X 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

OpcodeOperandsBehavior
0x18variableLoad the picture whose number is stored in the variable.
0x19variableClear/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.
0x1anonePresent the prepared logical visual surface, clear f15, and mark the picture as shown.
0x1bvariableDiscard the loaded picture selected by the variable and every picture retained after it.
0x1cvariableDecode the already loaded picture selected by the variable over the existing logical surfaces without the prepare-time clear. Visibility still requires a show action.
0x1dnoneTemporarily display priority/control values, wait for an input event, then restore the normal visual display.
0x1eview resourceLoad the selected view resource.
0x1fvariableLoad the view whose number is stored in the variable.
0x20view resourceDiscard 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

OpcodeOperandsBehavior
0x21objectIf 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.
0x22noneClear active and update-eligible state from every persistent object.
0x23objectActivate an object that has a selected cel, snapshot its current cel and position, and add it to update/draw processing.
0x24objectDeactivate the object and remove it from update/draw processing.
0x25object, X, YSet both current and previous position to the immediate coordinates.
0x26object, X variable, Y variableSet both current and previous position from variables.
0x27object, X destination, Y destinationStore current X and baseline Y in the destination variables.
0x28object, X-delta variable, Y-delta variableAdd signed 8-bit deltas to current position, clamp negative underflow to zero, mark the object newly positioned, and run placement.
0x29object, viewBind the loaded view, then select its default loop and cel.
0x2aobject, view variableBind the loaded view selected by a variable.
0x2bobject, loopSelect the loop and its default cel.
0x2cobject, loop variableSelect the loop named by a variable.
0x2dobjectDisable automatic direction-based loop selection.
0x2eobjectEnable automatic direction-based loop selection.
0x2fobject, celSelect a cel, refresh its dimensions, clamp placement if required, and remove the one-cycle animation-start delay.
0x30object, cel variableSelect the cel named by a variable with the same effects.
0x31object, destination variableStore the highest valid cel index in the selected loop.
0x32object, destination variableStore the selected cel index.
0x33object, destination variableStore the selected loop index.
0x34object, destination variableStore the selected view number.
0x35object, destination variableStore 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

OpcodeOperandsBehavior
0x36object, prioritySelect fixed priority and store the supplied value.
0x37object, priority variableSelect fixed priority from a variable.
0x38objectDisable fixed priority so priority is derived from baseline Y.
0x39object, destination variableStore the object’s current priority value.
0x3aobjectMove an active object to the earlier-drawn update partition.
0x3bobjectMove an active object to the later-drawn update partition.
0x3cobjectFlush, rebuild, draw, and refresh all object update lists. The operand does not select a narrower refresh.
0x3dobjectExempt the object from the horizon clamp.
0x3eobjectRe-enable the horizon clamp.
0x3fbaselineSet the horizon. A nonexempt object at or above it is placed at least one row below it.
0x40objectEnable the post-footprint-scan gate that rejects when final class-state flag 0 is clear.
0x41objectEnable the post-footprint-scan gate that rejects when final class-state flag 0 is set.
0x42objectClear both class-2 and class-3 rejection options.
0x43objectExempt the object from object-object crossing/collision tests.
0x44objectRe-enable object-object crossing/collision tests.
0x45object A, object B, destination variableStore 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

OpcodeOperandsBehavior
0x46objectDisable automatic cel cycling.
0x47objectEnable automatic cel cycling.
0x48objectCycle forward, wrapping from the last cel to cel 0.
0x49object, completion flagBegin forward cycling toward the last cel; clear the flag now, then set it and stop cycling on completion.
0x4aobjectCycle backward, wrapping from cel 0 to the last cel.
0x4bobject, completion flagBegin backward cycling toward cel 0; clear the flag now, then set it and stop cycling on completion.
0x4cobject, interval variableSet 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

OpcodeOperandsBehavior
0x4dobjectClear direction and autonomous motion. For object 0, clear v6 and select object-to-v6 direction coupling.
0x4eobjectClear autonomous motion without clearing object direction. For object 0, clear v6 and select v6-to-object direction coupling.
0x4fobject, step variableSet movement step size from a variable.
0x50object, cadence variableSet the object’s movement/update cadence countdown from a variable and restart its cadence phase.
0x51object, target X, target Y, step override, completion flagBegin movement toward an immediate target.
0x52object, X variable, Y variable, step variable, completion flagBegin movement toward a variable-selected target.
0x53object, near threshold, completion flagMove autonomously toward object 0 until within the threshold.
0x54objectBegin autonomous random-direction motion.
0x55objectStop the autonomous motion mode without clearing current direction.
0x56object, direction variableSet direction from a variable.
0x57object, destination variableStore current direction.
0x58objectIgnore configured movement-rectangle transitions and permit control value 1 in the footprint scan.
0x59objectEnforce configured movement-rectangle transitions and reject control value 1 in the footprint scan.
0x5aleft, top, right, bottomEnable the global movement rectangle. Inside membership uses strict comparisons against all four bounds.
0x5bnoneDisable 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:

ValueDirection
0Stationary.
1Up.
2Up-right.
3Right.
4Down-right.
5Down.
6Down-left.
7Left.
8Up-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

OpcodeOperandsBehavior
0x5citemSet the item’s location to 0xff (carried).
0x5ditem variableSet the variable-selected item’s location to 0xff.
0x5eitemSet the item’s location to 0.
0x5fitem, location variableSet the immediate item’s location from a variable.
0x60item variable, location variableSet a variable-selected item’s location from a variable.
0x61item variable, destination variableStore a variable-selected item’s location.

Action opcodes: sound

OpcodeOperandsBehavior
0x62sound resourceLoad the sound.
0x63sound resource, completion flagStop prior playback, clear the supplied flag, and start the loaded sound.
0x64noneStop 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

OpcodeOperandsBehavior
0x65messageDisplay the current-logic message modally and return after acknowledgement.
0x66message variableDisplay the variable-selected message.
0x67row, column, messageExpand substitutions in the message and display it at the configured text position.
0x68row variable, column variable, message variableVariable-selected form of 0x67.
0x69top row, bottom row, attributeClear full-width text rows inclusively with the selected text attribute.
0x6anoneEnter the alternate full-screen text-attribute mode and clear/fill its visible surface. Object graphics updates are suppressed while this mode is active.
0x6bnoneLeave alternate text mode, restore normal graphics updates, and redraw status/input areas.
0x6cmessageUse the first byte of the message as the input prompt marker; an empty message suppresses the marker.
0x6dforeground, backgroundSet the text color/attribute pair used by text windows and alternate text mode.
0x6ecountShake the visible display for the requested count, with pacing between offsets.
0x6fdisplay base row, input row, status rowConfigure text/input/status row placement.
0x70noneEnable and redraw the status line at its configured row.
0x71noneDisable and clear the status line at its configured row.
0x72string slot, messageCopy the message into the 40-byte string slot.
0x73string slot, prompt message, row, column, maximum lengthClear 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.
0x74string slot, parsed-word indexCopy the selected normalized parsed-word text into the string slot.
0x75string slotClear prior parser-match state and parse the slot into dictionary word identifiers. Slot numbers outside 0..11 produce no parse.
0x76prompt message, destination variablePrompt for up to four decimal characters, parse the accepted number, and store its low 8 bits.
0x77noneDisable and clear the input line.
0x78noneEnable and redraw the input line.
0x79key low byte, key high byte, status numberAdd 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

OpcodeOperandsBehavior
0x7aview, loop, cel, X, baseline Y, priority, control/marginDraw the selected loaded cel into persistent picture state at the supplied position and record enough state for restore replay.
0x7bseven variablesVariable-selected form of 0x7a.
0x7cnoneDisplay 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

OpcodeOperandsBehavior
0x7dnoneEnter the modal save selector. Cancel or a handled write failure returns to following bytecode; success writes the current game state and then returns.
0x7enoneEnter 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.
0x7fnoneNo operation.
0x80noneRequest 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.
0x81view resourceTemporarily 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.
0x82low, high, destination variableStore a pseudorandom value in the inclusive range low..high.
0x83noneSelect object-0-to-v6 direction coupling.
0x84noneSelect v6-to-object-0 direction coupling and stop object 0 autonomous motion.
0x85object variableDisplay object number, X, baseline Y, width, height, priority, and step size as a modal diagnostic.
0x86profile-dependentIn 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.
0x87noneDisplay heap/resource diagnostic values modally. These values are diagnostic and do not define a required internal allocator layout.
0x88noneStop sound, display the fixed pause message, wait for acknowledgement, and resume.
0x89noneRedraw the enabled input line from the accepted input buffer.
0x8anoneErase all currently visible input-line characters.
0x8bnoneRun the interactive joystick-centering and range-calibration sequence when a joystick is available.
0x8cnoneToggle 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.
0x8dnoneDisplay the interpreter name and version string modally.
0x8ereplay-pair capacitySet resource replay capacity, allocate/reset its pair sequence, and refresh object update lists.
0x8fmessageCopy 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.
0x90messageAppend current room, current input line, and the expanded message to LOGFILE. Failure to open the log returns without terminating the game.
0x91noneSave the current following-bytecode position as this logic’s future resume position.
0x92noneReset 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

OpcodeOperandsBehavior
0x95normally noneWhen 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.
0x96trace logic, row offset, heightConfigure trace formatting and window placement; clamp height to at least 2.
0x97message, row, column, widthTemporarily 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.
0x98message variable, row, column, widthVariable-selected message form of 0x97.
0x99view variableDiscard the loaded variable-selected view and every view retained after it.
0x9atop row, left column, bottom row, right column, attributeClear 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.
0x9btwo ignored bytesConsume 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

OpcodeOperandsBehavior
0x9cheading messageAppend a top-level menu heading using the message text. Ignored after finalization.
0x9ditem message, item IDAppend an enabled item to the current heading. Ignored after finalization.
0x9enoneFinalize menu construction and establish the initial heading/item selection.
0x9fitem IDEnable every menu item with the matching ID.
0xa0item IDDisable every menu item with the matching ID.
0xa1noneIf 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

OpcodeOperandsBehavior
0xa2view variableVariable-selected form of 0x81.
0xa3noneEnable the fixed input-width override, using a 36-character starting cap in profiles that support it.
0xa4noneDisable the fixed input-width override and return to width derived from normal input state.
0xa5variable, immediateMultiply and retain the low 8 bits.
0xa6destination variable, source variableMultiply and retain the low 8 bits.
0xa7variable, immediateStore the unsigned quotient. Division by zero is outside valid bytecode.
0xa8destination variable, divisor variableStore the unsigned quotient. Division by zero is outside valid bytecode.
0xa9noneClose/restore an active text window if present and clear the fixed input-width override even when no window is active.
0xaastring slotCopy up to 31 bytes of the current save-selector description/path buffer into the slot.
0xabnoneSave the current resource replay-pair count as a rollback point.
0xacnoneRestore the saved replay-pair count and place the next write after the restored final pair.
0xadnoneIncrement 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.
0xaehorizon rowRebuild 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.
0xafnone at runtimeDo 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:

OpcodeOperandsBehavior
0xb0one ignored byteConsume the byte and otherwise do nothing.
0xb1gate valueSet 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:

OpcodeOperandsBehavior
0xb0noneNo operation.
0xb1gate valueSet the menu-interaction gate; zero blocks modal entry and nonzero permits it.
0xb2noneNo operation.
0xb3four ignored bytesConsume four bytes and otherwise do nothing.
0xb4two variable operandsConsume two variable-index operands and otherwise do nothing.
0xb5noneClear 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:

  1. Loading makes the picture payload available but does not decode or display it.
  2. Preparing resets every logical cell to visual color 15 and priority/control value 4, resets the picture-command state, and decodes the selected payload.
  3. Overlaying preserves the existing logical cells, resets the picture-command state, and decodes the selected payload over them.
  4. 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:

CommandOperandsEffect
0xf0raw color byteEnable visual drawing and select the operand’s low nibble as the visual color.
0xf1noneDisable visual drawing.
0xf2raw value byteEnable priority/control drawing and select the operand’s low nibble as the priority/control value.
0xf3noneDisable 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.

CommandData sequenceEffect
0xf4initial X,Y; then Y,X,Y,X,…Plot the initial point, then draw alternating vertical and horizontal segments.
0xf5initial X,Y; then X,Y,X,Y,…Plot the initial point, then draw alternating horizontal and vertical segments.
0xf6initial X,Y; then zero or more X,Y pairsPlot the initial point, then connect each previous point to the next absolute point.
0xf7initial X,Y; then zero or more packed deltasPlot 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; bit 0x80 selects subtraction and a clear bit selects addition.
  • Y magnitude is b & 0x07; bit 0x08 selects subtraction and a clear bit selects addition.
  • Each addition or subtraction is performed modulo 256. The resulting X is then clamped down to 159 if it exceeds 159; Y is similarly clamped down to 167.

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, set major = dx, xerror = 0, and yerror = floor(dx / 2).
  • Otherwise set major = dy, xerror = floor(dy / 2), and yerror = 0.
  • Repeat major times:
    1. Set yerror = (yerror + dy) modulo 256. If yerror >= major, subtract major modulo 256 and advance Y by ystep.
    2. Set xerror = (xerror + dx) modulo 256. If xerror >= major, subtract major modulo 256 and advance X by xstep.
    3. Plot the resulting point.

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:

  1. If visual drawing is enabled, connectivity is determined solely by the visual channel and the target value is visual color 15.
  2. Otherwise, if priority/control drawing is enabled, connectivity is determined by that channel and the target value is 4.
  3. If neither channel is enabled, the seed has no effect.
  4. A visual fill whose selected visual value is 15, or a priority/control fill whose selected value is 4, has no effect.
  5. The seed must have the target value in the selected connectivity channel. Otherwise it has no effect.
  6. 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:

  • 0xf9 consumes one raw byte and otherwise has no effect.
  • 0xfa repeatedly 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: radius r in 0..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.

RadiusShaped-brush v2v3
08000Same as v2
1e000 e000 e0004000 e000 4000
27000 f800 f800 f800 7000Same as v2
33800 7c00 fe00 fe00 fe00 7c00 3800Same as v2
41c00 7f00 ff80 ff80 ff80 ff80 ff80 7f00 1c00Same as v2
50e00 3f80 7fc0 7fc0 ffe0 ffe0 ffe0 7fc0 7fc0 3f80 1f00Same as v2
60f80 3fe0 7ff0 7ff0 fff8 fff8 fff8 fff8 fff8 7ff0 7ff0 3fe0 0f80Same as v2
707c0 1ff0 3ff8 7ffc 7ffc fffe fffe fffe fffe fffe 7ffc 7ffc 3ff8 1ff0 07c0Same 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:

  1. Save bit 0 as carry and shift the state right once.
  2. If carry was set, XOR the state with 0xb8.
  3. 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 locationMeaning
bytes 0 and 1Reserved by the promoted profiles.
byte 2Number of loops.
bytes 3 and 4Offset of an embedded zero-terminated display string, relative to the payload start.
byte 5 onwardOne 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:

BitsMeaning
0x0fCel count.
0x30The loop number whose orientation the current shared row streams represent.
0x40Mirror every cel row when the stored orientation changes.
0x80Enable 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 locationMeaning
byte 0Width in logical pixels.
byte 1Height in logical pixels.
byte 2Control byte: transparent color and orientation state.
byte 3 onwardRow-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 0x70 with 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:

  1. Find the first run whose color is not transparent.
  2. If no such run exists, encode the mirrored row as only its zero terminator.
  3. Discard explicit transparent runs before that first visible run, but include their lengths when calculating the row’s total encoded width.
  4. Compute the implicit trailing transparent width as cel width - total encoded run length.
  5. Emit that implicit width as transparent runs before the mirrored visible data, splitting it into runs no longer than 15 pixels.
  6. Emit every original run from the first visible run onward in reverse byte order, preserving each run byte unchanged.
  7. 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:

  1. If top < 0, add top to left X, subtract top from baseline Y, and set top to zero. For example, top -1 shifts left X one pixel left and baseline Y one pixel down; this is a position adjustment, not simple top clipping.
  2. If left + width > 160, set left to 160 - width.
  3. 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:

  1. If q is greater than 2, use it directly as the comparison value.
  2. If q is 0, 1, or 2, scan downward in the same logical X column, starting one row below the destination. Use the first value greater than 2.
  3. If the scan reaches the bottom without finding such a value, use comparison value 0.
  4. 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 rowsPriority
0..474
48..595
60..716
72..837
84..958
96..1079
108..11910
120..13111
132..14312
144..15513
156..16714

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.

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:

  1. Before logic 0, eligible objects whose movement-cadence countdown is exactly 1 update their autonomous motion direction. The configured movement-rectangle transition check also runs in this phase.
  2. 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:

ValueDirectionX delta per stepBaseline-Y delta per step
0stationary00
1up0-step
2up-right+step-step
3right+step0
4down-right+step+step
5down0+step
6down-left-step+step
7left-step0
8up-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:

DirectionSelected loop
2, 3, 40
6, 7, 81
0, 1, 5no change

For views with four or more loops:

DirectionSelected loop
2, 3, 40
6, 7, 81
52
13
0no 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.

ModeTransition
forward wrapIncrease the cel index; wrap after the last cel to cel 0.
forward completionIncrease toward the last cel. On reaching it, set the completion flag, disable cycling, clear direction, and return the mode to forward wrap.
backward wrapDecrease the cel index; wrap before cel 0 to the last cel.
backward completionDecrease 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:

BoundaryClamped coordinateCode
topbaseline_y = cel_height - 11
horizon, when nonexemptbaseline_y = horizon + 11
rightleft = 160 - cel_width2
bottombaseline_y = 1673
leftleft = 04

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 valueImmediate effect and resulting state
0Reject immediately.
1Reject immediately unless the object ignores movement-rectangle restrictions; when allowed, continue with both class-state flags clear.
2Continue with class-state flag 3 set and flag 0 clear.
3Continue with class-state flag 3 clear and flag 0 set.
4..15Continue 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 f3 and f0 receive 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 relationleftwithin X stepright
above812
within Y step703
below654

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:

  1. If the previous refresh marked the object stationary, repeatedly calculate random_word() modulo 9 until the result is nonzero and use that as a temporary direction.
  2. Calculate distance = floor((abs(center_x_delta) + abs(baseline_y_delta)) / 2) + 1.
  3. If distance <= step, set retry delay to step. Otherwise repeatedly calculate random_word() modulo distance until the result is at least step, then use that result as the delay.
  4. A stationary-recovery update returns with the temporary direction and new delay; it does not decrement the delay immediately.
  5. 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.
  6. 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:

  1. Set direction to random_word() modulo 9, allowing stationary direction zero.
  2. Repeatedly calculate random_word() modulo 51 until the result is at least 6, 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:

  1. Store its normalized-token reference in the next output position.
  2. Set the parser count/error position to retained_nonzero_identifier_count + 1.
  3. Store the same one-based position in v9.
  4. Set f2 and 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:

  • 0x0001 matches exactly one parsed identifier;
  • 0x270f terminates 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:

TypeMeaning
1Raw key word.
2Movement/navigation value.
3Script-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 wordValue
0x48001
0x49002
0x4d003
0x51004
0x50005
0x4f006
0x4b007
0x47008

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 v19 and 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.

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 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.

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:

ValueMenu movement
1Previous item.
2First item.
3Next enabled heading.
4Last item.
5Next item.
6Last heading.
7Previous enabled heading.
8Root 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:

  1. Stop active sound.
  2. Reset transient display/update work, parsed-input state, persistent-object participation, room-scoped resources, and the resource replay sequence.
  3. Preserve the global logic needed by the next top-level cycle.
  4. Reset the horizon to 36, disable the configured movement rectangle, and reset ordinary object cadence, step, and cel-timer defaults to 1.
  5. Copy old v0 to v1, store the destination in v0, and store object 0’s selected view number in v16.
  6. Clear v4 and v5, then load the destination logic resource.
  7. Apply the entry-boundary selector in v2 to object 0 and clear v2.
  8. Set new-room flag f5 and redraw normal status/input presentation.
  9. 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:

v2Object 0 placement
1Baseline Y becomes 167.
2Left X becomes 0.
3Baseline Y becomes 37.
4Left 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:

KindValueReplay operation
0logic numberLoad and retain the logic, then restore its saved resume metadata.
1view numberLoad or refresh the view.
2picture numberLoad the picture.
3sound numberLoad the sound.
4picture numberPrepare/decode the already loaded picture after clearing logical picture state.
5zeroConsume the next three pairs as transient-view parameters and reproduce the transient cel draw.
6picture numberDiscard the picture.
7view numberDiscard the view.
8picture numberOverlay/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:

  1. global scalar, signature, string, parser, display, and session state;
  2. persistent drawable-object state;
  3. inventory and related object metadata;
  4. the configured resource replay-pair storage;
  5. 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:

BlockLength
1987 (0x03db)
2731 (0x02db), containing 17 object records
3339 (0x0153)
4Twice the configured replay-pair capacity

For profile 2.272 and the selected XMAS data:

BlockLength
1987 (0x03db)
2774 (0x0306), containing 18 object records
315 (0x000f)
4Twice the configured replay-pair capacity
5Variable

For profile 2.230 and the selected XMAS.230 data:

BlockLength
1987 (0x03db)
2774 (0x0306), containing 18 object records
315 (0x000f)
4400 (0x0190), containing 200 replay pairs
5Variable

The source-backed 2.089/2.230/2.272 block-1 partition is:

OffsetSizeMeaning
0x00007Game/save signature area.
0x00070x0100Variables v0..v255.
0x01070x0020Packed flags f0..f255.
0x01274Timer tick counter.
0x012b2Selected display-mode value.
0x012d2Object horizon baseline.
0x012f2Previous navigation-event value.
0x01312Movement rectangle left bound.
0x01332Movement rectangle top bound.
0x01352Movement rectangle right bound.
0x01372Movement rectangle bottom bound.
0x01392Object-0 direction-coupling selector.
0x013b2Most recently prepared picture number.
0x013d2Direction selected by navigation input.
0x013f2Movement rectangle enabled state.
0x01412Reserved startup count; preserve on round trip.
0x01432Configured replay-pair capacity.
0x01452Active replay-pair count.
0x01470x009cThirty-nine four-byte raw-key/status mappings.
0x01e34Reserved padding before strings.
0x01e70x00f0Six 40-byte script string slots.
0x02d70x00f0Six reserved 40-byte string records.
0x03c72Derived foreground text attribute.
0x03c92Derived background text attribute.
0x03cb2Packed current text/window attribute.
0x03cd2Input-line enabled state.
0x03cf2Configured input row.
0x03d11Prompt marker byte.
0x03d21Reserved alignment byte.
0x03d32Status-line enabled state.
0x03d52Configured status row.
0x03d72Display base row.
0x03d92Display 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:

BlockLength
11503 (0x05df)
2731 (0x02db)
3598 (0x0256)
4120 (0x0078)
5Variable.

For profile 2.440, the observed LSL1 state uses lengths:

BlockLength
11503 (0x05df)
2731 (0x02db)
3308 (0x0134)
4288 (0x0120)
5Variable.

The observed BC data selects the mapped-equivalent 2.439/2.440 behavioral rules with these separate dimensions:

BlockLength
11503 (0x05df)
2731 (0x02db)
3309 (0x0135)
4254 (0x00fe)
5Variable.

For the observed profile 2.936 game data, block lengths are:

BlockLength
11505 (0x05e1)
2903 (0x0387)
3328 (0x0148)
4200 (0x00c8)
5Variable.

For profile 2.917, the observed KQ1 state uses lengths:

BlockLength
11505 (0x05e1)
2774 (0x0306)
3328 (0x0148)
4200 (0x00c8)
5Variable.

For profile 2.917, the observed PQ1 state uses lengths:

BlockLength
11505 (0x05e1)
2860 (0x035c)
3366 (0x016e)
4500 (0x01f4)
5Variable.

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:

BlockLength
11505 (0x05e1)
2731 (0x02db)
3775 (0x0307)
4254 (0x00fe)
5Variable.

For profile 3.002.086, the observed full KQ4 state uses lengths:

BlockLength
11505 (0x05e1)
21118 (0x045e)
3710 (0x02c6)
4500 (0x01f4)
5Variable.

For profile 3.002.149, the observed Gold Rush state uses lengths:

BlockLength
11028 (0x0404)
2989 (0x03dd)
31811 (0x0713)
4100 (0x0064)
5Observed 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:

PositionSizePortable state
0x0000135Forty-five three-byte inventory entries.
0x0087575Zero-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:

PositionSizePortable state
0x00007Game/save signature area.
0x0007256Variables v0 through v255.
0x010732Packed flags f0 through f255.
0x01274Unsigned 32-bit timer tick count.
0x012b2Horizon baseline.
0x012d2Reserved word; canonical bytes are 00 00.
0x012f2Movement rectangle left bound.
0x01312Movement rectangle top bound.
0x01332Movement rectangle right bound.
0x01352Movement rectangle bottom bound.
0x01372Object-0/global-direction coupling selector.
0x01392Most recently prepared picture number.
0x013b2Movement rectangle enable value.
0x013d2Reserved word; canonical bytes are 0f 00.
0x013f2Replay-pair capacity.
0x01412Active replay-pair count.
0x0143156Thirty-nine key mappings, each raw_key:u16le, status:u16le.
0x01df40Ten inactive key-map records outside this profile’s 39-entry capacity; canonical contents are all zero.
0x02074Reserved pre-string padding; canonical contents are all zero.
0x020b480Twelve script string slots of 40 bytes each.
0x03eb480Twelve reserved 40-byte records outside the valid string-slot range; canonical contents are all zero.
0x05cb2Derived foreground text attribute.
0x05cd2Derived background text attribute.
0x05cf2Packed current text/window attribute.
0x05d12Input-line enabled value.
0x05d32Input text row.
0x05d51Prompt-marker character.
0x05d61Reserved byte before following word state; canonical value is zero.
0x05d72Status-line enabled value.
0x05d92Status text row.
0x05db2Display base row.
0x05dd2Display bottom row.
0x05df2Replay 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 positionSizePortable state
0x001Movement-cadence interval.
0x011Movement-cadence countdown.
0x021Boundary/collision event identifier.
0x032Current left X coordinate.
0x052Current baseline Y coordinate.
0x071Selected view number.
0x082Serialized view-reference token.
0x0a1Selected loop number.
0x0b1Loop count in the selected view.
0x0c2Serialized selected-loop-reference token.
0x0e1Selected cel number.
0x0f1Cel count in the selected loop.
0x102Serialized selected-cel-reference token.
0x122Serialized previous-cel-reference token.
0x142Serialized render-list-reference token.
0x162Previous or saved left X coordinate.
0x182Previous or saved baseline Y coordinate.
0x1a2Selected cel width.
0x1c2Selected cel height.
0x1e1Movement step size.
0x1f1Cel-cycling interval.
0x201Cel-cycling countdown.
0x211Movement direction.
0x221Autonomous motion mode.
0x231Cel-cycling mode.
0x241Priority/control byte.
0x252Object state flags.
0x274Mode-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:

PositionSizePortable state
0x0000120Forty three-byte inventory entries.
0x0078208Zero-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:

  1. A leading cache-head record, observed as (0, 0).
  2. One record for each cached logic, in cache order.
  3. 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:

PositionSizePortable state
0x05e12Menu interaction gate.
0x05e31Key-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:

PositionSizePortable state
0x00003One inventory entry: name_offset:u16le, location:u8.
0x00032Zero-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:

PositionSizePortable state
0x00007Game/save signature area; a valid signed save begins with GR\0.
0x0007256Variables v0 through v255.
0x010732Packed flags f0 through f255.
0x01274Unsigned 32-bit timer tick count.
0x012b2Horizon baseline.
0x012d2Reserved word; canonical bytes are 00 00.
0x012f2Movement rectangle left bound.
0x01312Movement rectangle top bound.
0x01332Movement rectangle right bound.
0x01352Movement rectangle bottom bound.
0x01372Object-0/global-direction coupling selector.
0x01392Most recently prepared picture number.
0x013b2Movement rectangle enable value.
0x013d2Reserved word; canonical bytes are 0f 00.
0x013f2Replay-pair capacity; observed value is 50.
0x01412Active replay-pair count.
0x0143196Forty-nine key mappings, each raw_key:u16le, status:u16le.
0x02074Reserved pre-string padding; canonical contents are all zero.
0x020b480Twelve script string slots of 40 bytes each.
0x03eb2Derived foreground text attribute.
0x03ed2Derived background text attribute.
0x03ef2Packed current text/window attribute.
0x03f12Input-line enabled value.
0x03f32Input text row.
0x03f51Prompt-marker character.
0x03f61Reserved byte before following word state; canonical value is zero.
0x03f72Status-line enabled value.
0x03f92Status text row.
0x03fb2Display base row.
0x03fd2Display bottom row.
0x03ff2Replay checkpoint count.
0x04012Menu interaction gate.
0x04031Key-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:

PositionSizePortable state
0x0000393One hundred thirty-one three-byte inventory entries.
0x01891418Zero-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:

  1. Replaces scalar, parser, object, inventory, replay, logic-resume, display, and session state with saved values.
  2. Resets transient caches and replays the saved resource sequence with recording disabled.
  3. Rebinds object views and refreshes picture, objects, menu, status, and input presentation.
  4. 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 f9 across 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.

BytesChannel
0..1Channel 0.
2..3Channel 1.
4..5Channel 2.
6..7Channel 3.

Each channel is an independent sequence of events:

FieldSizeMeaning
Duration2 bytesUnsigned little-endian countdown until the next record.
Tone2 bytesDevice-profile tone value.
Control1 byteIts 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:

OpcodeOperandsBehavior
0x62sound numberLoad the sound resource.
0x63sound number, completion flagStop any active sound, select the loaded sound, remember and clear the supplied flag, then begin playback.
0x64noneStop 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:

ChannelSelector
00x90
10xb0
20xd0
30xf0

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:

FieldInitial value when playback startsMeaning
Base attenuationEvent-definedThe low nibble from the most recently consumed event, or 0x0f after channel termination.
Envelope indexDisabledThe current position in the envelope table.
Envelope valueUnspecified until first envelope stepThe 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.