Analysis model: gpt-5.5 xhigh

Debut / Debut CD Version by DarkZone - Technical Dissection

Scope

This is a reverse-engineering pass over Debut by DarkZone, with the later Debut CD Version used as the main binary target. Debut was an MS-DOS/GUS intro released at Assembly 1993.

Public references:

There is the same old ranking disagreement seen with Tangle. Demozoo lists Debut as 3rd at Assembly 1993 in the PC 100K Intro competition. The Assembly Archive page lists it as 2nd. Pouet lists Debut CD Version as 3rd. The original scene.org result text puts Debut CD Version second and Tangle third. It is a top-three Assembly 1993 PC intro in every version of the metadata.

The CD version is not a 100K archive anymore. Its NFO says Kingpin did not consider it a new release: it is essentially the same demo with code bugs fixed, made for a Waite Group Press CD/book, and it requires 540 KB of conventional memory and at least a 386. Because that version is the public "bugfixed" version and Pouet points to it directly, this analysis uses DZDEBUT.EXE as the main code target, while also unpacking the original DEBUT_F!.EXE to compare layout.

Offsets below are raw file offsets inside DZDEBUT.EXE unless otherwise noted. The CD executable is a normal MZ EXE with relocations; segment constants shown by raw disassembly are pre-relocation paragraph values, not absolute runtime segments.

Examined Archives

Downloaded archives:

4b6fcf7778cec22398c8166941ee293acf23bef8f517f6d64844735bd35ee8d2  debut_dz.zip
cdc3b7479231361cb2ef59ed71f50ca6d61c72329a8fd654db865651821f684f  dzdebcde.zip

Original Assembly package:

File Size SHA-256 Notes
DEBUT_F!.EXE 20,038 9d0352560e5b76ec15d8df7d6ff73d253642585b46a6fe51615cbec75b0528d4 Small RNC-packed MZ loader/main
OBJECTS.DAT 194,336 cec568140e6cdb3abd5bf10b34e810b3aab55cc78477e852abcd8cf7c6ff3f84 Raw effect/object data
DARKZONE.MOD 240,236 da2e45232fce8712d5e7293a2d0708cd5be9e7ebe87c3016b51c0e0d1f09fa42 4-channel ProTracker module
DEBUT_F!.NFO 9,164 a109988c4183a07c6db02671634bc627b225490c54fe2f2ca9c16a34c1b670f3 Original info file
FILE_ID.DIZ 163 b7288fb70fd2444c3ab85984b96c406ac3c51a391750972233f4d3210eaeb2b8 BBS description
FILE_ID.OLD 146 a3e9b7b48e98cc266ff25f882f076fa9304ed9d7395129733fab9890a3f61c62 Older BBS description

CD/bugfix package:

File Size SHA-256 Notes
DZDEBUT.EXE 548,617 66e37653ade973604ea7e78afe557196c9b6af080a2d8d0b3a2315043a198fac Main CD/bugfix executable
DZDEBUT.DAT 194,336 6a6f69f4ff5d82790a985e16f4c810db7ce5a7f6888b4eb16b5db65e4e756727 Raw effect/object data
DZDEBUT.MOD 242,284 d18f24fef140ff1540ee377dcb48e48a49ab002a5983b62254eb91f81d316cdc 4-channel ProTracker module
DZDEBUT.NFO 6,594 8dfc62bde7028bbbb11af3f49b81bc1965a4ec951399346e1b0a7488cbe24bc0 CD edition info file
FILE_ID.DIZ 190 19258f4209a5578316510590e3ba26df87b215fc8d42e4dbc2f6ac5b31f77a6b BBS description

The module title in both package variants is:

m0rk sone2          #outzider/darkzone

The CD NFO credits:

Those credits match the code shape. The largest recovered systems are dot/vector morphing, a shadebob-like point renderer, a Mode 0Dh glenz/XOR-line section, and a final Mode 13h tunnel-style section.

Original RNC Package Versus CD Executable

The original DEBUT_F!.EXE is an MZ file with a 32-byte header and an RNC block immediately after the header:

offset 0x20: RNC method 1
compressed size: 19,073 bytes
expanded size:   72,764 bytes
expanded SHA-256:
346d2dc9ed9237ab4ebdd08e844be684fb6b413754eff89751e8038c213f5e22

The expanded original image contains the same important strings and hardware patterns as the CD version, but shifted. For example:

original expanded image: "OBJECTS.DAT", "DARKZONE.MOD"
CD executable:           "DZDEBUT.DAT", "DZDEBUT.MOD"

The CD executable is a much larger normal MZ executable:

Field Value
File size 548,617 bytes
MZ image size 548,617 bytes
Header size 0x400 bytes
Relocations 155
Entry 0d0d:2210
Entry file offset 0xf6e0
Stack 0000:0400
Min alloc 0 paragraphs
Max alloc 0xffff paragraphs

The entry bytes at file offset 0xf6e0 are:

b8 40 00     mov ax,0040h
8e d8        mov ds,ax
8e e0        mov fs,ax
fc           cld
e8 42 de     call d52dh

Because the file has relocations, 0040h is a program-relative paragraph value which DOS will relocate at load time.

OBJECTS.DAT and DZDEBUT.DAT have the same size. They are not byte-identical: the first detected differences begin around byte 64804 decimal. The overall layout is still clearly the same raw data file family, not a totally new resource format.

Entry and Dispatcher at 0xf6e0

The CD entry sequence is compact and readable:

f6e0: ds = fs = program_segment + 0040h
      cld
      call d52d          ; prompt for GUS base port
      ax = 0013h
      int 10h            ; mode 13h
      call d575          ; clear/fade/logo-ish setup

      far call 8322:2449 ; GUS/player init helper
      far call 8322:2426 ; GUS/player helper

      dx = 5f93h         ; "DZDEBUT.DAT"
      far call 8322:2598 ; likely open/check via support code
      if carry: print DAT error and exit

      far call 8322:2326
      call f435          ; DOS open DAT

      call d9fc
      call de37
      call de9f
      call e6a6
      call eb51
      call ee8c
      call f359

      wait for timing marker 16h
      far call 8322:23c4 ; player shutdown
      text mode 3
      exit

The visual sequence is therefore statically fixed:

d9fc  data/morph setup and first dot-vector sequence
de37  full-screen copy/palette stage
de9f  another copy/palette/dot stage
e6a6  shadebob/dot morph section
eb51  mode 0Dh glenz/XOR vector section
ee8c  filled XOR delay circles
f359  final Mode 13h pixel/tunnel-style section

The GUS/player code is not decoded here as a standalone sound-engine analysis, but its interface is visible. The support code exposes timing bytes that the visuals poll:

f462 wait_until_marker:
    ds = 8322h
    si = 1824h
wait:
    al = [si]
    cmp al, bl
    jne wait
    ret

f473 read_marker:
    ds = 8322h
    si = 1824h
    al = [si]
    ret

f480 clear_marker:
    ds = 8322h
    byte [1824h] = 0

The effect loops do not guess their duration purely from frame counts. They poll music/timing markers and transition when the player reaches values such as 0x03, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, and 0x14.

File Loader

The local DOS file helpers are tiny:

f435:
    ds = program_segment + 0040h
    dx = 5e8dh              ; filename, DZDEBUT.DAT
    ax = 3d00h
    int 21h                 ; open read-only
    if carry: error
    [5e99] = ax             ; file handle

f44a:
    bx = [fs:5e99]
    ah = 3fh
    int 21h                 ; read CX bytes to DS:DX
    if carry: error

f458:
    bx = [fs:5e99]
    ah = 3eh
    int 21h                 ; close

The early data staging uses fixed read sizes and fixed target segments. The helper at 0xd634 reads 0x5460 bytes from the DAT file into a caller-selected segment. Later helpers build derived morph tables from that raw data.

VGA Setup and Frame Waits

Unchained 256-Colour Setup at 0xd4d8

The main 256-colour setup starts with BIOS mode 13h and then edits VGA registers directly:

ax = 0013h
int 10h

; graphics controller mode/misc setup
select GC register 05h
clear bit 4

select GC register 06h
clear bit 1

; sequencer memory mode
select sequencer register 04h
clear bit 3
set bit 2

es = a000h
di = 0
eax = 0
cx = 3e80h
rep stosd                ; clear 64,000 bytes

; CRTC changes
register 09h: keep only bits 4..6
register 14h: clear bit 6
register 17h: set bit 6

This is a tweaked mode-13h/Mode-X family setup. The later renderers explicitly use VGA sequencer map masks and byte/plane addressing.

VBlank Wait at 0xf4a1

The frame wait helper jumps into a status-register loop:

dx = 03dah
while (in dx & 8) != 0:
    spin
while (in dx & 8) == 0:
    spin
ret

Palette fades, page-ish copies, and many draw phases call this helper before touching visible video memory.

Palette Upload at 0xf48e

The full DAC upload helper is:

ds = ax
si = 0
out 3c8h, 0
dx = 3c9h
cx = 0300h
rep outsb

Other palette routines modify the live DAC by reading from 0x3c7/0x3c9, stepping components toward a target, and writing them back through 0x3c8 and 0x3c9.

Morph Data Construction

The intro contains 3600-point morphing vector data. The count is explicit:

0x0e10 = 3600

The table builder at 0xd6d4 constructs 12-byte records:

for i in 0..3599:
    dst_x = read_word(source)
    delta_x = target_x - dst_x

    dst_y = read_word(source)
    delta_y = target_y - dst_y

    dst_z = read_word(source)
    delta_z = target_z - dst_z

    write:
        delta_x, dst_x,
        delta_y, dst_y,
        delta_z, dst_z

The interpolator at 0xd749 then evaluates:

for i in 0..3599:
    x = start_x + delta_x * t / divisor;
    y = start_y + delta_y * t / divisor;
    z = start_z + delta_z * t / divisor;

The simpler updater at 0xd79b just adds the stored delta words directly:

for i in 0..3599:
    x = start_x + delta_x;
    y = start_y + delta_y;
    z = start_z + delta_z;

The extractor at 0xd7d3 copies only the destination coordinate words. The variant at 0xd7f4 adds sine-table offsets to the z component, which gives the dot cloud a wave/depth disturbance:

for 75*48 points:
    z = point.z
    if z >= 0:
        z += sine_a >> 6
        z += sine_b >> 7
        point.z = z

Two phase pointers at 0x7c4b and 0x7c4d advance by 0x200 per call, wrapping inside a 0x4000 sine-table range.

3600-Point Projection and Z-Shaded Plotting

The projection plotter at 0xd95f is one of Debut's important inner loops. It projects 3600 3D points into a linear offscreen buffer in segment 0x4ed5.

Inputs:

DS = point table segment 0x1f38
ES = offscreen buffer segment 0x4ed5
FS = program data segment
0x6025 = depth/camera term

Loop shape:

for i in 0..3599:
    denom = depth - point.z;

    sx = project(point.x, denom);
    if sx > 159 || sx < -160:
        continue;

    sy = project(point.y, denom);
    if sy < -100 || sy > 99:
        continue;

    offset = sy * 320 + 0x7da0 + sx;
    shade = (point.z >> 4) + 0x80;

    if (offscreen[offset] <= shade)
        offscreen[offset] = shade;

The strange-looking cwtd, byte swaps through AH/AL, and idiv bx sequence is a compact signed fixed-point projection. The bounds show the intended centre:

x range: -160..159
y range: -100..99
base offset: 0x7da0

0x7da0 is exactly the centre bias for a 320x200 linear buffer:

100 * 320 + 160 = 32160 = 0x7da0

After drawing, 0xd9d8 waits for vblank and copies the offscreen buffer to video memory. The copy helper around 0xd9e0 does something important:

for 0x3e80 dwords:
    movsd source -> A000
    write zero to the source dword just copied

So the offscreen dot buffer is a one-frame accumulation buffer. Each frame is copied to VRAM and cleared in the same pass.

Shadebob Dot Renderer at 0xe2e9, 0xe3ff, and 0xe458

The section at 0xe6a6 repeatedly calls three routines:

call e2e9     ; update/project point data
call e3ff     ; convert to screen byte offsets and plane indexes
call e458     ; draw shadebob footprint into A000

It loops until the GUS/player timing marker reaches 0x0e, then repeats similar loops for markers 0x0f, 0x10, and 0x11.

Phase and Coordinate Update at 0xe2e9

This routine updates two phase indices and turns them into a moving centre:

phase = (phase + phase_step) & 0x0fff
x_centre = (sine[phase].x >> 9) + 160
y_centre = (sine[phase].y >> 8) + 200

Then it walks a point list, updates two angle indices per point, fetches two sine/cosine pairs from GS, and writes projected coordinates into a work table at segment 0x1f38, offset 0x0c00.

Screen Address Conversion at 0xe3ff

This routine converts each point to a Mode-X-style byte address and plane:

for each point:
    x = projected_x;
    y = projected_y;

    if x <= 3 || x > 0x137:
        invalid;
    if y >= 0x183:
        invalid;

    linear = y * 80 + (x >> 2);
    plane  = x & 3;

    out_record.offset = linear;
    out_record.plane  = plane;

The implementation computes y * 80 as:

dx = y
ax = y << 6
dx <<= 4
ax += dx              ; y*64 + y*16 = y*80

The plane extraction is done by rotating two bits out of BX into DX:

rcr bx,1
rcl dx,1
rcr bx,1
rcl dx,1

That gives the two low x bits after the code has prepared the byte address.

The Shadebob Inner Loop at 0xe458

The draw loop reads the byte address and plane for each point, then increments a predefined blob footprint in video memory:

for each point:
    di = byte_offset
    cl = plane

    out 3ceh, 0400h | cl       ; graphics controller read map
    out 3c4h, 0200h | (1<<cl)  ; sequencer map mask

    inc byte [es:di + row_offset_0]
    inc byte [es:di + row_offset_1]
    ...

    advance to the next plane:
        cl++
        if cl == 4:
            di++
            cl = 0

The footprint is manually unrolled. It uses row offsets in multiples of 0x50 bytes, which is 80 bytes per scanline in unchained 320-wide planar memory:

0x000, 0x050, 0x0a0, 0x0f0,
0x140, 0x190, 0x1e0, 0x230,
0x280, 0x2d0, 0x320, 0x370

The first and last footprint phases increment fewer rows; the middle phases increment more rows. That creates a vertical blob kernel without any multiply or table lookup in the hot path. The cost is VGA port writes per point/plane, but the pixel work itself is just inc byte [A000:di+offset].

This matches the NFO's "ShadeEffect (320x400)" credit: the section uses Mode-X-like addressing and tall, repeated row offsets to build soft trails from many moving dot centres.

Mode 0Dh Glenz / XOR Vector Section

The section at 0xeb51 switches to BIOS mode 0x0d:

ax = 000dh
int 10h

It writes a small palette, initializes a set of object/camera state variables, and enters a loop that:

clear local work buffer
copy polygon/edge records from object tables
transform vertices
project vertices
render lines/spans into a bitplane buffer
XOR-delay the buffer
copy two planes to A000 through sequencer map masks
swap CRTC display start offsets
advance object/timing parameters
poll player marker 12h

The transform routine at 0xea0c applies three rotations. It repeatedly fetches sine/cosine words from GS and updates vertex records:

rotate y/z around angle0;
rotate x/z around angle1;
rotate x/y around angle2;

The projection at 0xeb18 divides x and y by a z/depth term:

screen_x = project(vertex.x, depth - vertex.z) + 0x118;
screen_y = project(vertex.y, depth - vertex.z) + 0x0fa;

The line/backface block at 0xe71d first computes a signed area from three points:

cross =
    (x2 - x0) * (y3 - y0)
  - (y2 - y0) * (x3 - x0);

Then it selects one of two line paths. The line drawer at 0xe7c1 clips and draws into a 40-byte-row bitplane buffer using an XOR pixel operation:

byte = x >> 3
mask = 0x80 >> (x & 7)
address = y * 40 + byte + page_offset

buffer[address] ^= mask

The implementation has positive-slope and negative-slope paths. Both use a fixed-point error accumulator:

step = abs(dx) << 7 / dy;
for each y:
    x += step;
    xor one bit in the bitplane buffer;

The section then does an "XOR delay" pass:

for 0x7c6 dwords:
    buffer[i + 0x24] ^= buffer[i]

for another 0x7c6 dwords after a 0x28-byte row skip:
    buffer[i + 0x24] ^= buffer[i]

That is a temporal/spatial echo inside the bitplane buffer. After that, it copies two 0x7d0 dword chunks to A000:

out 3c4h, 0102h
rep movsd buffer -> A000:display_start

out 3c4h, 0202h
rep movsd next_buffer -> A000:display_start

Finally it writes the display-start word to CRTC registers 0x0c/0x0d through the helper at 0xe7b1. This section is the strongest match for the NFO's P-Nut credits: GlenzCubes and filled XOR delay circles share the same bitplane, XOR, and CRTC display-start machinery.

Filled XOR Delay Circles at 0xee8c

The later XOR-circle section starts by building a small circle table at 0xed3a from byte data at 0x8156. It writes paired offsets into a temporary table:

for 32 bytes:
    a = table[i];
    top_or_left  = a * 40;
    bottom_or_right = mirrored(a) * 40;

It then clears the local bitplane buffer, calls 0xed83 to combine two moving index streams into 45-ish circle positions, and calls 0xee62 for each position. The hot pixel primitive is again:

cl = x & 7
dx = x >> 3
call bit_xor_span_or_point

The visible frame loop includes:

buffer[i + 0x24] ^= buffer[i]      ; delay/echo
write CRTC start
update palette bands
rotate sequencer map mask
copy buffer to A000

The palette part at 0xef37 writes two 24-component DAC bands, adding a brightness value and clamping at 0x3f. This is why the XOR geometry does not just look like monochrome line art: the plane mask and palette band are both cycling.

Final Mode 13h Pixel/Tunnel-Style Section at 0xf359

The final large section starts with:

ax = 0013h
int 10h
call setup
clear A000

Then it enters a frame loop:

call f1d4
call f28d
wait vblank
call f2ec
wait vblank
call f308
marker = read_player_marker()
if marker < 14h:
    repeat

The surrounding tables and the NFO credit identify this as the pixel tunnel family. The code uses Mode 13h linear addressing rather than the earlier unchained span/bitplane model. The last phase counts down 0x40 frames and uses a different palette/fade helper before clearing the screen and restoring the opening image/logo strip.

I did not fully reconstruct the tunnel's table-generation math in this pass. The control flow and hardware behaviour are still clear: it is a Mode 13h final part, synchronized by player marker 0x14, with per-frame table-driven drawing and palette updates.

Version Differences That Matter

The CD version keeps the original effect architecture but changes packaging and stability:

What Each Major Part Does

The best compact model of Debut is:

DAT file:
    raw point clouds, logo/image/object data, palette and path tables

GUS player:
    module playback plus timing marker byte

Main code:
    load data
    initialize GUS
    run fixed effect sequence

Dot-vector parts:
    build 3600-point morph tables
    interpolate or perturb points
    project into centred 320x200 buffer
    z-shade and copy/clear every frame

Shade part:
    move point centres by sine phases
    convert x/y into Mode-X byte+plane
    draw an unrolled vertical shadebob kernel by incrementing A000 bytes

Glenz/XOR parts:
    switch to mode 0Dh
    transform/project simple objects
    XOR line bits into a 40-byte-row buffer
    delay the buffer by xoring against shifted dwords
    copy selected planes to A000 and change CRTC start

Final tunnel:
    return to mode 13h
    table-driven pixel effect and palette loop

The inner loops are small, but they are carefully chosen for the hardware: Mode-X dot effects use x >> 2 plus VGA map masks, 16-colour bitplane effects use x >> 3 plus bit masks, and the heavy 3600-dot morph uses a linear offscreen buffer so it can copy and clear with movsd/zero writes instead of fighting VGA planes per point.

Unresolved or Lower-Confidence Areas