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:
- Demozoo production: https://demozoo.org/productions/6705/
- Pouet production for Debut CD Version: https://www.pouet.net/prod.php?which=5055
- Pouet NFO: https://www.pouet.net/prod_nfo.php?which=5055
- Assembly Archive page: https://archive.assembly.org/1993/pc-intro/debut-by-darkzone
- Original Assembly archive: https://archive.scene.org/pub/parties/1993/assembly93/100k/debut_dz.zip
- CD/bugfix archive: https://archive.scene.org/pub/parties/1993/assembly93/100k/dzdebcde.zip
- Hornet mirror path for CD/bugfix archive: https://files.scene.org/view/mirrors/hornet/demos/1993/d/dzdebcde.zip
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:
- Kingpin: 3600-dot morphing vectors, 320x400 shade effect, pixel tunnel, timing, assembly, lady picture, and DEBUT logo.
- P-Nut: GlenzCubes and filled XOR delay circles.
- Cascada: GUS play routine, slightly modified.
- Outzider: music and some timing.
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:
- Original: tiny RNC-packed MZ executable plus
OBJECTS.DATandDARKZONE.MOD. - CD version: large relocated MZ executable plus
DZDEBUT.DATandDZDEBUT.MOD. - Original expanded image and CD executable share the same core strings, GUS player marker strings, VGA hardware patterns, and effect loops.
- The DAT files have the same size but differ in the data region, consistent with bugfix/content timing changes rather than a new engine.
- The CD version has explicit text saying it works on Kingpin's 386-25 and 486-66, including with QEMM installed, which is a useful contrast with many 1993 intros that reject memory managers.
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
- The Cascada-derived GUS player at relocated segment
8322hwas not decoded as a standalone sound engine. Its timing-marker interface is clear enough for the visual timeline. - The final Mode 13h tunnel's table math is only partially resolved here.
- Some effect names are inferred from the CD NFO and matching code shape. The underlying loops and hardware accesses are directly observed.