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:

```text
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:

```text
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:

```text
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:

```text
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:

```asm
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:

```asm
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:

```text
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:

```asm
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:

```asm
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:

```asm
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:

```asm
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:

```asm
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:

```text
0x0e10 = 3600
```

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

```c
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:

```c
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:

```c
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:

```asm
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:

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

Loop shape:

```c
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:

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

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

```text
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:

```asm
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:

```asm
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:

```asm
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:

```c
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:

```asm
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`:

```asm
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:

```asm
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:

```text
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`:

```asm
ax = 000dh
int 10h
```

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

```asm
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:

```c
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:

```c
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:

```c
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:

```asm
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:

```c
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:

```asm
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:

```asm
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:

```c
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:

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

The visible frame loop includes:

```asm
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:

```asm
ax = 0013h
int 10h
call setup
clear A000
```

Then it enters a frame loop:

```asm
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.DAT` and
  `DARKZONE.MOD`.
- CD version: large relocated MZ executable plus `DZDEBUT.DAT` and
  `DZDEBUT.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:

```text
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 `8322h` was 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.

