Analysis model: gpt-5.5 xhigh

Trek, the Last Generation by Official Version - Technical Dissection

Trek, the Last Generation is a July 1991 MS-DOS demo by Official Version. Pouet and Demozoo list it under that title, platform, release month, and group. The Hornet 1991 index instead lists the same ov_trek.zip archive as Official Version by The Last Generation, so older archive metadata appears to have blurred the group/title fields.

Release year: 1991

This is one of the stranger 1991 pieces in the current early-PC pass. The presentation wraps itself in a fake Amiga-emulator shell, but the program is plain real-mode DOS code with a large restored MZ image. The interesting parts are not one single core loop: it uses a mode 13h text/palette/scroller frontend, mode 0Dh and 0Eh planar picture loaders, retrace-timed block transitions, a timer interrupt that pokes the VGA attribute controller, and a separate planar pixel/line library used by later drawing routines.

Trek, the Last Generation recovered code-path map

The image above is a recovered static-analysis map, not a runtime frame. A bounded DOSBox-X capture attempt against the original packed executable did not produce usable video, so this article does not claim exact visual timestamps. When OV-TREK gets a clean capture pass, the missing effect-time annotations should be added here.

Front-page media note: the card uses the generated recovered code-path map as a static preview. I did not make a GIF for this pass because the current local evidence contains no usable runtime frames; a clean DOSBox-X capture remains a follow-up item for this demo.

Sources

Archive

The examined archive is ov_trek.zip from the Hornet mirror:

ov_trek.zip  263143 bytes
ov-trek.exe  269634 bytes  1991-07-08 01:00

Hashes:

5c473be5845ddbea80720879047bf4bdfd4986f71ab5b558fb36f9cce850f90b  ov_trek.zip
eb102b54acd8a02a168838cb48c290572b5e49fd46581910c8a3808e44b19138  ov-trek.exe
13145163ac54e0a11cb37334db2beb9dbd44e9010fa2f823347c1755c1816a12  OV-TREK.EXE after UNP

The restored binary contains its own public release text saying the demo was released on Monday, 8 July 1991. It also contains private postal contact text; that private block is deliberately omitted from this public note.

Capture Status

The current visual status is:

DOSBox-X execution        starts but did not yield a clean captured frame here
usable runtime video      no
exact observed timestamps no
image in this article     static recovered code-path map

The timing comments below therefore describe loop pacing and delay counts from the code, not stopwatch-verified visual timestamps.

MZ Layout And Address Convention

The distributed executable is packed:

packed file size       269634 bytes
MZ header                 112 bytes
MZ reported size       269634 bytes
load image             269522 bytes
relocations                 0
min alloc              3F43h paragraphs
max alloc              3F43h paragraphs
entry                  FFF0:0100
stack                  41D5:0400
relocation table       0050h

UNP restores a much larger MZ:

unpacked file size     515520 bytes
MZ header                 992 bytes = 03E0h
MZ reported size       515520 bytes
load image             514528 bytes
relocations               241
min alloc              0373h paragraphs
max alloc              0373h paragraphs
entry                  0000:0490
entry raw              0870h
stack                  7F1A:1F70
relocation table       001Ch

Offsets below are raw offsets in the restored executable. Runtime memory offset is therefore:

runtime offset = raw offset - 03E0h

For example, raw 1604h is runtime offset 1224h, and the restored entry raw 0870h is runtime 0490h.

Overall Structure

The useful visible code is split into four families:

03E0h..080Fh   mode 13h frontend, palette upload, text/scroller loop
10B0h..11CBh   retrace-paced 20x20 block copy/clear transitions
1503h..1A50h   higher-level show sequence, planar picture loaders, text ending
1A60h..1D0Fh   planar pixel read/write and line drawing primitives

That matters because a shallow reading makes the demo look like a generic chunky mode-13 program. It is not. The first text/scroller section is mode 13h, but the later images and primitive drawing use EGA/VGA planar modes and the sequencer/graphics-controller registers directly.

Mode Set Helpers

Raw 03E0h and 03E6h are trivial BIOS helpers:

03E0: mov ax,0013h
03E3: int 10h
03E5: ret

03E6: mov ax,0003h
03E9: int 10h
03EB: ret

The more important helper starts at raw 03ECh. It is a retrace-synchronized DAC uploader:

input count     [bp+4] -> CX
input start     [bp+6] -> BX, also DAC start index
input far ptr   [bp+8] -> DS:SI

The loop does this:

  1. Adds the start index to the far source pointer.
  2. Waits for vertical retrace to be clear, then set, using port 03DAh bit 3.
  3. Disables interrupts.
  4. Writes the start color index to 03C8h.
  5. For each byte in the source range, emits that byte three times to 03C9h.
  6. Re-enables interrupts and returns with ret 8.

So 03ECh treats a one-byte intensity stream as grayscale RGB triplets. It is not a full 768-byte palette copy. This is why later fade code can adjust a compact byte table and still upload RGB colors.

Palette Fades At 041Eh And 0579h

Raw 041Eh is a palette transition routine built around the 03ECh uploader. It uses two main tables:

1A66h   working intensity table
09DCh   target intensity table

The first part optionally calls the surrounding TLG runtime through far calls around segment 7B37h/7949h, then clears the fade counter at 1B6Ch. The body repeatedly fills or mutates 1A66h, calls 03ECh for palette indices starting at 14h, and walks bytes in 1A66h toward the target bytes in 09DCh.

The important inner behavior is byte convergence rather than arithmetic color ramps:

for each affected palette byte:
    if work_byte < target_byte: work_byte++
    if work_byte > target_byte: work_byte--
upload changed range through 03ECh

Raw 0579h is related but starts with a screen-data pass. It sets ES=A000h and applies:

CX = 1900h
for DI from 0000h:
    ES:[DI] &= 7Fh

That clears the high bit across 6400 bytes of display memory before continuing with the same 1A66h/09DCh palette-table machinery. It is a visual cleanup plus fade path, not only a palette routine.

The 0615h Text, Palette, And Strip Loop

Raw 0615h is the first really dense display kernel. It takes a font pointer table and a text/control stream as far arguments, then loops until its stream state says the part is done.

Initial state:

[0002h] = 0000h   text-stream cursor
[0004h] = 0000h   current glyph cursor
[0006h] = 00h     glyph word countdown
[0007h] = 01h     reload delay/state
[07F5h] = 00h     termination flag
[000Ah] = 14h     cadence counter

Stream Reader

When [0007h] reaches zero, the code reads a byte from the far text stream:

AL = stream[[0002h]]
if AL == 40h:
    [07F5h] = 1
    [0007h] = 1
    [0002h]++
    jump to the frame tail
else:
    [0002h]++
    AL -= 20h
    AX = AL * 2
    SI = font_pointer_table[AX]
    AX = first word from glyph stream
    [0004h] = SI + 2
    [0006h] = AL

40h is the @ terminator/control byte. Normal characters are shifted down by space (20h) and used as an index into a word-pointer table. That is a compact proportional glyph system, not a BIOS-font print.

If the current glyph is already active, the loop reads the next glyph word from the saved cursor at [0004h], decrements [0006h], and when that counter expires sets [0007h] to 03h so a small gap elapses before the next stream character.

320-Byte Buffer Shift

The scroller buffer begins at 1B70h. Each frame performs sixteen single-column shifts:

SI = 1B70h
DI = 1B70h
SI++
DX = 0010h

repeat 16 times:
    CX = 013Fh
    rep movsb        ; shift 319 bytes left by one
    AL = 00h
    glyph_word <<= 1
    if carry:
        AX = 007Ah + DX
    stosb            ; append generated byte
    SI++
    DX--

013Fh + 1 gives a 320-byte logical row. Sixteen rows are advanced in one outer frame, with the appended byte taken from the current glyph word. The nonzero byte value is not just FFh; it is 7Ah + row_number, giving the bottom strip a palette-index gradient.

Palette Shuffle

After the buffer shift, raw 06CAh..06DFh runs backward over the 1A66h palette work table:

SI = 1A66h + 77h
DI = SI
DX = 0005h
repeat 5 chunks:
    CX = 0013h
    AL = [SI--]
    rep movsb backward
    stosb AL

That rotates five 20-byte-ish chunks. It is preparing the palette/intensity table used immediately afterward.

Retrace And DAC Output

The loop then waits on port 03DAh bit 3. Once inside retrace, it writes DAC index 14h to 03C8h and streams 100 palette entries from 1A66h + 14h. The code uses [07F4h] as a bit-controlled channel selector:

CX = 0064h
SI = 1A66h + 14h
BL = [07F4h]

repeat CX times:
    AL = *SI++
    AH = 00h
    for R,G,B:
        rotate/test BH
        sometimes swap AL/AH
        out 03C9h, AL

The point is that the source is still one intensity byte per logical palette entry, but the output can put that byte into selected RGB channels while zeroing the others. This gives colored palette motion from a compact intensity table.

Immediately after that, the code writes DAC index 7Bh and emits 30h bytes from a table at 0ADCh + [0008h] * 3. The [0008h] index increments and wraps at 00BDh, so this is a second cyclic color strip.

Bottom-Strip Copy

The full text buffer is copied to video memory here:

ES = A000h
DI = E380h
SI = 1B70h
CX = 0A00h
rep movsw

0A00h words equals 5120 bytes, enough for a 320-byte by 16-line strip. In mode 13h, A000:E380h is near the bottom of a 64000-byte page:

E380h decimal 58240
58240 / 320 = line 182

So this is a bottom-of-screen strip copy.

Optional Sprite Strip

Every other frame, [000Bh] is rotated and a sprite/animation strip is copied:

DS:SI = far pointer [2F9Ah] + [000Ch]
DI    = CB84h
BX    = 0010h rows

repeat 16 rows:
    CX = 001Eh
    rep movsw        ; 30 words = 60 bytes
    DI += 0104h      ; 60 + 260 = 320-byte row stride

[000Ch] += 003Ch
if [000Ch] >= 1518h:
    [000Ch] = 0

That is a 60-pixel-wide, 16-line animation copied into the same 320-byte row geometry, with source frames spaced by 3Ch bytes per row slice.

Tiny Font Overlay

There is also a countdown at [0010h]. When it reaches zero, the code reads a byte from 1544h + [000Eh], maps it through an 8-byte glyph table at 0D44h, and draws an 8x8 character into the bottom area. The draw loop clears bit 7 of each destination byte and ORs in the shifted glyph bit:

8 glyph rows:
    AL = glyph_row
    8 pixels:
        ES:[DI] &= 7Fh
        AL <<= 1
        AH = carry ? 80h : 00h
        ES:[DI] |= AH
        DI++
    DI += 0138h

The row stride is again 320 bytes: eight written pixels plus 0138h equals 0140h.

20x20 Block Copy And Clear

Raw 10B0h is a compact block copier. It treats DI=FFFFh as a null destination; otherwise it copies a 20x20 rectangle:

if DI == FFFFh:
    return

BP = 0014h            ; 20 rows
repeat:
    CX = 000Ah
    rep movsw         ; 10 words = 20 bytes
    DI += 012Ch       ; 20 + 300 = 320-byte stride
    BP--
until BP == 0

Raw 10C6h is the same geometry but clears with zero words:

if DI == FFFFh:
    return

AX = 0000h
BP = 0014h
repeat:
    CX = 000Ah
    rep stosw
    DI += 012Ch
    BP--
until BP == 0

Raw 10DEh is a vertical-retrace wait using 03DAh bit 3: wait while retrace is set, then wait until it becomes set again. The transition routines call it three times per step, so their block motion is deliberately slowed to a visible cadence.

Four transition walkers reuse the same geometry:

10ECh  table 0652h, count 006Eh, source 01A2h, clears oldest slot
1124h  table 0734h, count 0028h, source 01A2h, clears oldest slot
115Ch  table 078Ah, count 0032h, source 0012h, copies four table slots
1194h  table 078Ah, count 0032h, source 01A2h, clears oldest slot

The tables contain destination offsets. Each iteration reads four consecutive word positions through BX, copies the same 20x20 source block into several positions, clears one old position, advances the table pointer by two bytes, and loops. This is a table-driven tile transition rather than a freeform sprite engine.

Keyboard/Configuration Gate At 1503h

Raw 1503h is not a graphics loop, but it controls part sequencing. It allocates 0382h bytes of stack workspace, uses TLG runtime calls to read or transform a configuration/string buffer, and loops until either a runtime check reports a key/event or a requested count has been reached.

The user-facing error path switches to text mode 03h and prints strings through runtime calls if the comparison against table bytes at 07F9h fails. In normal flow it acts as a paced gate between image parts. The top-level sequence calls it with arguments 14h and 1Eh, after delays of 1000 and 300 runtime ticks respectively.

Mode 0Dh And 0Eh Planar Image Loaders

Raw 1604h loads an image into BIOS mode 0Dh:

1604: mov ax,000Dh
1607: int 10h
1609: push ds
160A: lds si,[2F92h]
160E: mov ax,A000h
1611: mov es,ax

It then configures the VGA graphics controller and sequencer:

GC port 03CEh:
    AX = 0805h
    AX = 0007h

SEQ port 03C4h:
    start AX = 0102h    ; map mask plane 0

The copy loop runs four plane passes:

BL = 04h
AH = 01h
repeat 4 planes:
    out 03C4h, AX       ; sequencer map mask
    DI = 0000h
    CX = 0FA0h
    rep movsw           ; 8000 bytes into this plane
    AH <<= 1            ; 1,2,4,8
    BL--

0FA0h words is 8000 bytes per plane, matching 320x200 4-plane graphics at one bit per pixel per plane.

Raw 1646h is the same loader for BIOS mode 0Eh:

mode             0Eh
source pointer   [2F96h]
copy count       1F40h words = 16000 bytes per plane
plane masks      1,2,4,8

Mode 0Eh is the 640x200 16-color mode, so each plane is 16000 bytes. Both loaders restore the sequencer map mask to 0Fh and reset graphics-controller registers after the copy.

Timer ISR And Attribute Controller

Raw 1688h is an interrupt handler. It is short but hardware-specific:

push AX, DX
AH = [2F78h]
read 03DAh          ; reset attribute-controller flip-flop
out 03C0h, 09h      ; attribute register index 09h
out 03C0h, AH       ; value from [2F78h]
out 03C0h, 29h      ; index 09h with display-enable bit set
[2F7A:2F7C]--       ; dword countdown
out 20h, 20h        ; PIC EOI
pop DX, AX
iret

Raw 16B0h installs that handler on interrupt vector 08h:

ES = 0000h
old vector 08h saved at [2F7Eh]/[2F80h]
vector 08h offset set to 01A8h
vector 08h segment set to CS
PIT channel 0 programmed through ports 43h and 40h

Because the handler writes the attribute controller instead of the DAC, it is not the same kind of color engine as the mode-13 0615h loop. It changes an attribute register at timer rate while the rest of the program draws or waits.

Top-Level Show Sequence

Raw 1A18h is the clearest high-level script:

clear/runtime init through far call 7B37:027C
call 1604h                  ; mode 0Dh image
delay 1000 runtime ticks
call 1503h with 14h
call 1646h                  ; mode 0Eh image
delay 300 runtime ticks
call 1503h with 1Eh
call 1702h
call 17A4h
call 19CEh
far return

This confirms the multipart nature: picture load, delay/gate, picture load, delay/gate, then a later drawing/text path.

VRAM Scroll And Runtime Drawing At 17A4h

Raw 17A4h begins by clearing or preparing runtime state, then performs a direct video-memory backward copy:

ES = A000h
DS = A000h
CX = 3480h
SI = 347Fh
DI = 3E7Fh
STD
GC register 05h = 01h
rep movsb
GC register 05h = 00h

The copy is backward because the source and destination overlap:

source      A000:0000h..347Fh
destination A000:0A00h..3E7Fh

So this shifts/copies a large screen area down by 0A00h bytes while avoiding overwrite. In a 320-byte row layout, 0A00h is eight lines. The graphics controller write mode setting around the copy means this is not just a normal RAM move; it is being done with planar VGA write behavior active.

After that, 17A4h enters a runtime-assisted drawing loop. It calls far drawing helpers in segment 0168h, uses delays of 00FAh, polls keyboard state through TLG runtime calls around 7AD5h, and exits on enough iterations, space, or Esc. The visible effect is driven by the surrounding runtime library, but the first scroll/copy block is local and hardware-facing.

Text-Mode Intermission

Raw 1932h is a centered typed-text helper. It copies a string into a local buffer, computes a centered column from the first byte/length, positions the cursor through the runtime, then prints one character at a time with a delay of 46h ticks between characters. At the end it waits 01F4h ticks.

Raw 19CEh uses it:

clear/runtime call
BIOS mode 03h
print string at 082Eh on row 0Ah
print string at 0880h on row 0Ch
print string at 08D2h on row 0Eh
print string at 0924h on row 10h
delay 1000 runtime ticks
return

That is the text ending/intermission path after the planar graphics sequence.

Planar Pixel Set At 1A60h

Raw 1A60h is a far-callable planar pixel writer. It takes color plus y and x style arguments from the stack. The address math is the important part:

AX = y
DX = y
AX <<= 2          ; y * 4
AX += DX          ; y * 5
AX <<= 4          ; y * 80
BX = x
CL = low 8 bits of x
BX >>= 3          ; x / 8
BX += AX          ; byte offset = y*80 + x/8

Then it builds the bit mask:

CL &= 7
CL ^= 7
AH = 1 << CL
out 03CEh, AX with AL=08h     ; GC bit mask

The write uses graphics-controller write mode and data-rotate setup:

out 03CEh, 0205h
out 03CEh, 0003h
dummy read ES:[BX]
AL = color byte from [BP+6]
ES:[BX] = AL
reset bit mask to FFh
reset GC register 05h and 03h
lret 6

This is the classic EGA/VGA single-pixel write pattern: select a bit inside the byte through GC register 08h, arm the controller, do a latch read, then write the color.

Planar Pixel Read At 1ABCh

Raw 1ABCh uses the same y*80 + x/8 and 1 << (7 - (x&7)) math, then reads the selected pixel across four planes:

SI = byte offset
BL = 00h
GC read map select starts at plane 3

for planes 3 down to 0:
    out 03CEh, AX with AL=04h
    BH = ES:[SI]
    BH &= bit_mask
    BH = -BH          ; carry/sign-like collapse to 0/FF
    rol BX,1          ; collect bit into BL
    AH--

It returns the four-plane pixel value in AL. This is exactly the inverse of the planar set-pixel helper: select a plane, read the byte, test the bit, pack the result into a nibble.

Line Drawing At 1B12h And 1D06h

Raw 1B12h is the main line primitive. It starts by programming the graphics controller:

DX = 03CEh
AH = color from [BP+6]
AL = 00h
out DX, AX          ; set/reset value
out DX, 0F01h       ; enable set/reset on all planes
out DX, 0003h       ; data rotate/function setup

Then it computes deltas from the endpoint arguments:

dy = y1 - y2
if dy < 0:
    dy = -dy
    swap endpoints

dx = x1 - x2
if dx < 0:
    dx = -dx
    row_step = -80
else:
    row_step = +80

The function stores working values at 2F82h..2F88h, chooses one of two loop entries depending on whether the line is shallow or steep, and uses an indirect jump through [2F88h].

The address setup is shared with the pixel helpers:

byte_offset = y*80 + x/8
bit_mask    = 1 << (7 - (x & 7))
ES          = A000h

The vertical/general line loops repeatedly write GC bit masks to register 08h and OR a byte at ES:[DI] to trigger the latched VGA write. Error terms decide when to step to the next byte/row. The horizontal special path at 1C0Fh..1C8Bh builds start/end masks and uses rep movsb for middle byte runs, which is much faster than plotting every pixel separately.

Raw 1D06h is a second entry into the same machinery, but it initializes the color/control state differently before jumping into the shared body. It is best read as an alternate line mode, not a separate renderer.

What This Demo Is Doing

OV-TREK is a hybrid:

The lack of a clean capture means the current article is stronger on code identity than visual chronology. The next improvement should be a reliable video capture, then exact timestamp annotations for where the text strip, block transitions, planar stills, scroll/copy pass, and text intermission appear.