Analysis model: gpt-5.5 xhigh

X-Mas '92 by Cascada - Technical Dissection

Subject: X-Mas '92 by Cascada.

Release year: 1992.

This is Cascada's December 1992 MS-DOS Christmas demo. The interesting part is not just the visible Christmas/game wrapper, but the way the production is split into packed MZ executables: a small launcher, a first intro/control part, and a large main/game part. After UNP expansion the two sidecar files become ordinary MZ images with very different jobs:

All offsets below are offsets in the UNP-expanded DOS load image unless I say otherwise. They are not byte offsets in the original packed files.

Visual References

Local DOSBox-X capture was useful for the opener and configuration path, but the run stopped on the icicle screen after accepting the saved configuration prompt. So the exact timestamps below are only for the local opener/config/icicle state. The broader visual reference sheet comes from public Demozoo screenshots and is not presented as a local timing trace.

X-Mas '92 DOSBox-X runtime contact sheet

Local capture details:

Time Frame Notes
00:02.000 images/cascada-xmas-92-runtime-00-02-000.png Twilight/Cascada title frame. The public copy is cropped at the bottom to avoid republishing the small contact/footer text.
00:18.000 images/cascada-xmas-92-runtime-00-18-000.png Same title frame during fade-down. Also cropped for the same reason.
00:21.000 images/cascada-xmas-92-runtime-00-21-000.png Saved-configuration prompt.
00:24.000 images/cascada-xmas-92-runtime-00-24-000.png Black screen with blue icicle band; the local run stayed here.

The local run used DOSBox-X 2026.01.02, machine=svga_s3, cycles=30000, and a Sound Blaster-style environment (A220 I7 D1 T4). I repeated the run with and without sound enabled; both paths reached the same icicle stall.

Selected public still references:

X-Mas '92 selected public still references

The selected Demozoo stills show the title cards, instructions, technical information page, credit pages, the Santa game screen, and the end card. I deliberately omitted the contact/BBS-looking public screenshots from the local supporting asset set, because the analysis does not need to reprint that text.

The sequence GIF below is assembled from those same selected public Demozoo stills. It is a compact public-reference overview, not a local DOSBox-X timing trace: it walks through the title/instruction/technical/credit/game/end-card screens while preserving the contact-material omission boundary.

X-Mas '92 public reference sequence GIF showing title, instruction, technical, credit, game, and end-card screens

Pipeline map:

X-Mas '92 DOS/VGA/audio pipeline

Runtime-To-Code Concordance

The evidence is intentionally split. The local DOSBox-X frames prove the opener/config/icicle path only; the later title/instruction/game/end-card screens are selected public-reference stills. The code anchors below therefore separate .000 local-runtime behavior from .001 later-screen static analysis.

Evidence Code anchor What it explains
Local title frame at 00:02.000 and fade-down at 00:18.000 .000 entry at 0080h, BIOS mode 13h, CRTC helper 1255h, plane clear 0412h, DAC clear 042Ah, transparent planar loader 03C5h, logo expander 044Ch, palette interpolator 04FCh The opener is a real VGA/planar sequence, not a DOS text screen. The fade-down is palette interpolation over existing artwork rather than a full redraw of darker pixels.
Local saved-configuration prompt at 00:21.000 Packed launcher/config front-end plus sidecar chain model This is launcher/config state around the chain into the sidecar parts. The article does not over-claim it as part of the later .001 game/text runtime.
Local blue icicle band at 00:24.000 where DOSBox-X stalled .000 planar asset setup, CRTC start/panning state, local-capture boundary The stall leaves a valid planar title/background asset on screen. It is a runtime boundary for this capture, not proof that the demo has no later parts.
Public title/instruction/technical/credit stills in the Demozoo sequence GIF .001 entry at 0204h, keyboard hook A173h, asset allocator 9A80h, VGA init 9CE2h, planar blitter 9E6Ch, palette/display transition 9EA8h The selected public stills line up with the larger main part: allocated rectangular assets, interleaved planar copies, palette ramps, and CRTC display choreography. They are static references, not local timestamps.
Public Santa game screen .001 keyboard hook A173h, text/game setup 6A2Ah, decimal renderer 6ACEh, character renderer 6BA6h, blit/clear loops around 67F0h The game screen explains why the main part needs keyboard handling, digit drawing, small asset tables, and repeated plane-masked object blits.
Public final/end-card stills Picture fade routine A047h, full 64,000-byte pixel copy, 64-color palette scale/fade The end-card family is closer to picture-copy plus DAC fade than to the game/text renderer.
Public reference sequence GIF as a whole Selected Demozoo still set and contact-material omission boundary The GIF is a public-reference overview of screens the local run did not reach. It is not presented as a timed DOSBox-X movie.
Pipeline map Launcher plus .000 plus .001 sidecar structure, service bridge 129Bh/12C0h, file loader 1380h, audio backends 1DF0h/24E0h/2730h The generated map summarizes a real multi-executable runtime: config/launcher, intro/control part, main/game part, service segment, file loader, and LPT/PC speaker/Sound Blaster output paths.
Audio backend coverage .000 LPT DAC ISR 1E60h, PC speaker ISR 2508h, Sound Blaster setup 2730h, SB ISR 27EDh, DSP/DMA helpers 2AFFh..2C21h The three output choices share the same sequencer/mixer family but differ in final hardware delivery. This is infrastructure, not a one-off logo sound call.
Omitted contact/BBS-looking frames Public screenshot selection boundary and cropped local title/footer handling The article keeps technical evidence while avoiding republication of old contact material. That boundary applies to both local runtime frames and public still selection.

This concordance is deliberately not a single "video timeline." The local timeline ends at the icicle stall; the later analysis is a public-still and static-code bridge over the recovered .001 routines.

Sources And Archive Evidence

Primary metadata used for this pass:

Archive hashes from the copy I analyzed:

File Size SHA-256
cda_xm92.zip 278641 2a67499b416d06dc1df23dbb6ef2599bcda662d907c0cc6f1593ef35caa4355e
cda-xm92.exe 8448 fe5b330884b13a3c39ecb2ac309ebf4bd1740f54b834148943a7b3b11611823b
cda-xm92.000 70547 c9969d0e1ce344b92431693643020672043b03a8c1a08934cc741cd0225e20ac
cda-xm92.001 215844 c17107ffa87ee041c4c01d7fc7126e16f916fe7b2bc5ff481374db9910da96d6
cda-xm92.cfg 7 78dabb68c3bb348fe65e5f00cb9806a57152deacd67a903dcb4f9b5da756b56a

Original archive contents:

File Size Role
cda-xm92.exe 8448 Launcher/config executable.
cda-xm92.000 70547 Packed first executable part.
cda-xm92.001 215844 Packed second executable part.
cda-xm92.cfg 7 Tiny saved-configuration state file.

The .000 and .001 sidecars are not simple data blobs. They are MZ executables with a Twilight marker in the packed form. UNP expands them into larger MZ images:

Part Packed size Expanded size Header Relocs Entry
cda-xm92.000 70547 188368 112 bytes 20 0008:0000, load offset 0080h
cda-xm92.001 215844 478384 11200 bytes 2793 0020:0004, load offset 0204h
cda-xm92.exe 8448 unchanged by UNP 112 bytes 2 00E6:0000, load offset 0E60h

The launcher still looks packed after UNP. Static disassembly shows BIOS/DOS calls and VGA port activity, but the real body is still the packed runtime image. I therefore treat the launcher conservatively: it is the config/chain front-end, but the detailed inner-loop analysis starts with the expanded .000 and .001 sidecar images.

Runtime Shape

The demo is structured like a DOS production loader, not like one monolithic effect COM:

  1. The launcher displays/queries configuration state.
  2. It runs the first sidecar executable.
  3. The first part initializes VGA, displays the opener assets, installs or selects an audio backend, and builds a service/data segment.
  4. The first part passes control to the second sidecar.
  5. The second part installs keyboard handling, allocates a set of rectangular asset blocks, switches into its own unchained VGA layout, and runs the later text/game/transition code.

There are two important consequences:

.000 Entry Path At 0080h

The first expanded part starts by making the DOS process smaller, taking over video, and setting up an unchained VGA-style surface.

The opening shape is:

; CDA-XM92.000 load offset 0080h
mov  bx,35B7h
mov  ah,4Ah
int  21h             ; resize current MCB

in   al,21h
or   al,02h
out  21h,al          ; mask IRQ1 while the part owns the machine

mov  ax,0013h
int  10h             ; VGA mode 13h baseline

mov  ax,00AEh
call 1255h           ; CRTC line/overflow helper

mov  dx,03D4h
mov  ax,2813h
out  dx,ax           ; CRTC offset register 13h = 28h

; set a CRTC display start around 4B00h
mov  ax,4B0Ch / xx0Dh
out  dx,ax

Immediately after the mode set the code leaves plain chunky mode 13h behind:

mov  dx,03C4h
mov  ax,0604h
out  dx,ax           ; sequencer memory mode

mov  dx,03D4h
mov  ax,0014h
out  dx,ax           ; CRTC underline/location tweak

mov  ax,0E317h
out  dx,ax           ; CRTC mode control tweak

That is the usual "mode 13h as a convenient BIOS entry point, then directly reprogram VGA" approach. The later blitters assume plane masks and linear 80-byte-ish row movement, not a single chunky 64K framebuffer.

The entry code then:

The low-memory/far-call part is one of the more interesting pieces. The code loads a segment from the BIOS data/low-memory area around 0040:00F0 and 0040:00F2, patches a far target, then calls 012C:0000. That far code is paired with the service bridge at 12C0h, which creates the data/control segment used by later commands. The part stores the service segment in CS:1234, and 129Bh later uses it as a command mailbox.

.000 VGA Helpers

Plane-Clearing Helper At 0412h

This is the "reset the whole planar surface" primitive:

; ES = A000h
mov  dx,03C4h
mov  ax,0F02h
out  dx,ax           ; map mask: all four planes

xor  di,di
xor  ax,ax
mov  cx,0FFFFh
rep  stosb

It does not try to be clever. Once all planes are enabled, one byte store hits the same byte address in every selected plane.

DAC-Clearing Helper At 042Ah

The DAC reset is equally direct:

xor  al,al
mov  dx,03C8h
out  dx,al           ; start at DAC entry 0

inc  dx              ; 03C9h
mov  cx,0300h
.clear:
out  dx,al           ; write R/G/B zeroes
loop .clear

The important detail is that this resets 768 DAC component writes, i.e. all 256 RGB triplets.

Transparent Planar Loader At 03C5h

This is one of the classic inner loops in the first part. The calling code passes a source segment, destination offset, and dimensions. The helper walks four VGA planes by changing the sequencer map mask, and it treats zero source bytes as transparent:

mov  es,0A000h

mov  dx,03CEh
mov  ax,4005h
out  dx,ax           ; graphics controller mode setup

mov  dx,03C4h

for each row:
    mov  cx,0050h    ; 80 byte columns in the common call pattern

    for mask in 01h,02h,04h,08h:
        mov  ax,mask:02h
        out  dx,ax   ; select one VGA plane

        al = [ds:si]
        si++
        if al != 0:
            es:[di] = al
        di++

The byte address advances through the row while the plane mask controls which bitplane receives the write. Transparent zero handling lets the same routine overlay title/logo pieces without first preserving the background.

The hidden cost is the I/O: every plane pass does a sequencer out. On a 386 this is acceptable for logo/title material, but it is still the expensive part of the loop. The routine is built for sparse sprite/title compositing, not for full-screen streaming.

Logo/Asset Expander At 044Ch

This routine expands a larger packed/interleaved asset from a fixed source segment into A000h. The structure is:

ds = 032Fh
si = 0000h
di = B180h

for band in 0..0Ah:
    for mask in 01h,02h,04h,08h:
        out 03C4h, mask:02h

        for rows in asset_band:
            copy 29h bytes
            copy 20h bytes
            advance destination to the next planar row

    source += 0520h

The notable part is the 11-band outer loop and the fixed 0520h source stride. That makes it look like an artist/tool exported plane-interleaved bands, and the code is just repacking them into the live VGA layout.

Strip/Window Copy At 049Dh

49Dh is used by the opener transition tables. It takes an X-like phase in BX, converts it to a byte offset by BX >> 2, then uses BX & 3 as a sub-byte phase selector through a small table around 0474h.

The core behavior:

set map mask to all planes
set graphics mode to 4105h

byte_x = bx >> 2
phase  = bx & 3
di     = destination_base + byte_x + phase_adjust[phase]

for row in 0..28:
    copy dx bytes from ds:si to es:di
    si += 9
    di += 50h

The fixed 50h row stride is the giveaway: this is writing a planar 320-wide logical screen, 80 bytes per row per plane. The SI += 9 after each row means the source is not a plain rectangle; it has padding or interleaved columns.

Palette Interpolation At 04FCh

The helper at 4FCh is a frame-by-frame palette interpolator. The caller gives it a source palette-like table, destination/current working tables, count, and a signed delta. The function builds two tables:

Then for each fade frame it:

  1. waits for retrace,
  2. writes the DAC start index to 03C8h,
  3. streams RGB components to 03C9h,
  4. updates current component accumulators by a per-frame step.

The small self-modifying detail matters. The code writes either an add-style or subtract-style opcode/data byte around 0522h, depending on the sign of the fade delta. In other words, the inner update loop is not branching on every component; it patches the operation once, then runs the same tight loop for the whole fade.

High-level pseudocode:

for component in selected_palette_components {
    current[component] = start_value << fixed_shift;
    step[component] = (target_value - start_value) / frames;
}

patch_inner_update(delta_is_negative ? subtract : add);

for frame in 0..frames-1 {
    wait_vblank();
    out(0x3c8, first_color);

    for component in selected_palette_components {
        out(0x3c9, current[component] >> fixed_shift);
        current[component] = patched_add_or_sub(current[component], step[component]);
    }
}

This is exactly the kind of small optimization that keeps DOS palette fades cheap: pay setup once, keep the retrace-time body predictable.

CRTC And Retrace Helpers

1255h: CRTC Overflow/Line Helper

1255h writes CRTC register 18h and then patches overflow bits in CRTC registers 07h and 09h. The entry calls it with AX=00AEh, which means it is setting a line/compare style value and distributing high bits into the VGA overflow registers.

It is not a full mode setup by itself. It is a compact helper for one of the awkward VGA details: scanline-related values are split between low registers and overflow bits.

128Bh: Vertical Retrace Wait

The wait loop is the classic two-phase 03DAh poll:

.wait_not_retrace:
    in  al,03DAh
    test al,08h
    jnz .wait_not_retrace

.wait_retrace:
    in  al,03DAh
    test al,08h
    jz  .wait_retrace

loop requested_count

The two phases avoid entering in the middle of an existing retrace and treating it as a fresh frame. Palette updates and CRTC start-address changes use this kind of wait so the visible display sees complete register batches.

Service Bridge And DOS Loader

Command Bridge At 129Bh

129Bh is a mailbox writer into the service segment stored at CS:1234:

cli
es = cs:[1234h]
es:[675Dh] = bx
es:[675Fh] = ax
es:[675Ch] = 1
sti

That is a very small command protocol: write arguments, set a command-ready flag, and let the service side consume it. The CLI/STI pair suggests the service can also be touched from an interrupt or timing-sensitive path.

Service Entry At 12C0h

The service entry has two modes.

On first initialization:

  1. Allocate approximately 67F3h bytes worth of paragraphs with DOS AH=48h.
  2. Store the new segment in CS:2027.
  3. Copy a small control block into the new segment around offset 4100h.
  4. Optionally load a side file/string referenced by the control block.
  5. Call a chain of setup routines: 15CBh, 1846h, 14A7h, 154Ch, 16DAh, 158Ah, 1700h.
  6. Dispatch through a selector table.

On later calls:

  1. Use DS = CS:[2027].
  2. Read selector byte DS:[4104h].
  3. Dispatch through one of two tables around CS:2019h or CS:200Bh.
  4. Free/reload a previous block if the 4113h field is nonzero.

This gives the first part a resident-ish service area without requiring the whole demo to be one executable image.

File Loader At 1380h

The file loader is conventional but robust:

int 21h AH=1Ah       ; set DTA to local buffer
int 21h AH=4Eh       ; find first matching file
read file size from DTA
int 21h AH=48h       ; allocate enough paragraphs
int 21h AH=3Dh       ; open read-only

if size < 8000h:
    int 21h AH=3Fh   ; one read
else:
    while bytes remain:
        read up to 8000h bytes
        destination_segment += 0800h

The 8000h chunk size maps neatly to 0800h paragraphs. That keeps the segment arithmetic simple: after every 32 KiB chunk, add 0800h to the destination segment and keep DX near zero.

.000 Audio Backends

The first part contains three distinct hardware output paths. They share a timer/sequencer idea but differ in how the final sample reaches hardware.

LPT DAC Path: 1DF0h Setup And 1E60h ISR

The setup routine at 1DF0h installs an INT 08h timer handler:

es = 0000h
save old vector from 0000:0020 / 0000:0022
write new vector offset 0BA0h, segment CS

divisor = 34DCh / [4100h]
out 43h,34h
out 40h,low(divisor)
out 40h,high(divisor)

The ISR body at 1E60h is a four-voice phase accumulator mixer:

tick_count--;

for voice in 0..3 {
    phase[voice] += 0xF000;
    if (phase wrapped past voice_limit) {
        advance sample/index pointer;
    }
    sum += sample_table[voice][phase_high];
}

mixed = rotate_right(sum, 2);
out(0x378, mixed);
out(0x20, 0x20);     // PIC EOI
iret;

The real code stores the phase values around 5443h, 5445h, 5447h, 5449h, decrements a frame/tick counter at 5441h, and jumps into the music sequencer dispatcher when that counter reaches zero. The output port is 378h, the classic parallel-port DAC/Covox-style target.

The important inner-loop point is that mixing is integer phase stepping, not sample-rate conversion through a big multiply. Each timer tick advances the phase accumulators by a fixed step, samples tables, sums the channels, rotates the result, and writes a byte.

PC Speaker Path: 24E0h Setup And 2508h ISR

The PC speaker setup uses both PIT channel 0 and channel 2:

program PIT channel 0 for timer ISR
program PIT channel 2 through port 42h
in  al,61h
or  al,03h
out 61h,al           ; enable speaker gate/data

The ISR at 2508h mirrors the phase-accumulator structure of the LPT path, but instead of writing a byte to 378h, it maps the mixed value through a table around 635Ah and writes the result to PIT channel 2:

sum = mix_four_phase_accumulators();
speaker_reload = table_635A[sum];
out(0x42, speaker_reload);
out(0x20, 0x20);
iret;

Cleanup at 2604h restores the timer vector and clears the speaker bits in port 61h with AND 0FCh.

This is not just "beep" use of the PC speaker. It is the timer-reload approximation style: continuously alter channel 2 so the speaker carries a low-fidelity sampled signal.

Sound Blaster Path: 2730h Setup And 27EDh ISR

The Sound Blaster setup path probes and programs the DSP:

call 2B70h           ; DSP reset/probe helper
send D1h             ; speaker on
send 40h             ; time constant command
send D4h             ; continue DMA / mode command in this code path

save PIC mask
unmask configured IRQ
hook vector IRQ+8 to local ISR

convert DS:67F0 buffer to DMA page/offset
program DMA controller
send DSP command 14h, length FFFFh

The helpers around 2AFFh, 2B70h, 2B99h, and 2BB8h are the busy-wait DSP write/read/status loops. 2C21h is the DMA programmer, using the standard DMA mask/clear/mode/address/count/page port sequence.

The ISR at 27EDh feeds the same sequencer/mixer family but writes output into the Sound Blaster DMA buffer region around 67F0h/67F1h and acknowledges the interrupt/PIC path. So the three devices are not three unrelated players; they are three output backends around the same timer/music state.

.001 Entry Path At 0204h

The second expanded MZ is much larger and has a conventional "setup everything, then run kernels" shape:

; CDA-XM92.001 load offset 0204h
save sp/es
call A173h           ; keyboard vector hook
call 9A80h           ; asset/table allocator
resize MCB
call 0A12h
call 72CEh
call 9CE2h           ; VGA init
call 8623h
call 92E6h
call 6A2Ah
...

It also uses the low-memory/far-service mechanism:

load far target from 0040:00F0/00F2
lcall 0B36:0000
jc exit

The main control loop checks CS:0DFE. If it is 1, it calls one path around 04C1h; otherwise it calls a path around 0290h. On exit it restores the old keyboard vector with DOS AX=2509h and returns through DOS 4Ch.

Keyboard Hook At A173h

The keyboard setup is standard DOS interrupt-vector work:

int 21h AH=35h, AL=09h  ; get old INT 09h
store old offset/segment at CS:294Ah/294Ch

DX = 72B9h
DS = CS
int 21h AX=2509h        ; set new INT 09h

That gives the main part a direct keyboard path, needed for game/control screens and clean exit behavior. The old vector is restored at shutdown.

Asset Allocator At 9A80h

9A80h is one of the clearest signs that the main part was produced with custom tools. It repeatedly allocates a block, copies a rectangular source chunk through helper 9A20h, records the resulting segment in a table, and then shrinks the memory block to its used size.

The groups are explicit:

Source Count Width-ish Height-ish Segment table Source stride
6EC6h 10 1Ch 1Bh 37DAh 02F4h
709Fh 5 20h 0Ch 3802h 0180h
6948h 16h 28h 14h 35F2h 0320h
71DFh 9 08h 06h 370Ch 0030h
7117h 0Ah 14h 10h 3772h 0140h
6D9Ah 6 28h 14h 3650h 0320h
71FAh 6 08h 07h 366Eh 0038h

There is also a final small 8x4 block from a code-segment source around AE26h, stored at 37AAh.

This allocator matters because later drawing code often does not decode asset formats from files. It selects one of these already-expanded memory segments and copies it through VGA plane loops.

.001 VGA Init At 9CE2h

The main part sets its own video state instead of relying on .000:

call 9E04h            ; VGA/extension detector
mov  ax,0013h
int  10h

zero palette work buffer
upload black DAC through 03C8h/03C9h

out 03C4h,0604h
out 03D4h,0014h
out 03D4h,0E317h

for mask in 01h,02h,04h,08h:
    out 03C4h,mask:02h
    clear 8000h words at A000h

attribute_controller[30h] = 61h
CRTC[13h] = 2Dh
set CRTC overflow/line bits
set display start and attribute panning

The detector at 9E04h probes CRTC register 25h and port 3CDh. It sets a flag around 9C03h depending on whether the expected extended behavior is available. Later panning code uses that flag to decide whether the low pixel phase should be doubled before writing the attribute-controller panning value.

Again the pattern is: BIOS mode 13h gets the machine into VGA graphics mode, then the demo programs the real layout itself.

Main-Part Planar Copy At 9E6Ch

9E6Ch is the main part's rectangular planar blitter. It expects interleaved source data and writes one VGA plane at a time:

es = A000h
bx >>= 2                  ; byte width from pixel-ish width
ax = 0102h                ; sequencer map-mask register pair

for plane_mask in 01h,02h,04h,08h:
    out 03C4h, plane_mask:02h

    for row in rows:
        for x in 0..byte_width-1:
            movsb
            si += 3       ; skip the other plane phases
        di += 5Ah - byte_width

    rewind source to next interleave phase
    advance source by one

The SI += 3 is the central detail. The source is four-way interleaved by plane/phase, so once a plane is selected the code consumes every fourth byte. The 5Ah destination row stride is wider than the simple 50h used elsewhere, which matches a horizontally wider or overscanned logical layout in this part.

Transition And Palette Loop At 9EA8h

9EA8h combines three operations:

  1. display-start/panning changes,
  2. a strip copy from segment 2A7Ch to A000h around offset 4D58h,
  3. a 64-color palette ramp driven by a table around 9FE3h.

The frame loop resembles:

for step in 0..0x3A {
    wait_vblank();

    start = 0x2058 + display_phase(step);
    crtc_start_address(start);
    attribute_panning(low_phase_bits);

    for color in 0..63 {
        dac.r = table[color].r * ramp[step] >> 6;
        dac.g = table[color].g * ramp[step] >> 6;
        dac.b = table[color].b * ramp[step] >> 6;
    }
    upload_dac_64();
}

The routine also uses the A199h command bridge with AX=1Eh and BX=0380h, which ties the visual transition back into the service/mailbox mechanism.

Picture Fade At A047h

A047h is a clean full-picture fade routine:

set mode 13h
zero 0300h-byte palette buffer
write black DAC

ds = picture_segment
si = 0301h
es = A000h
di = 0000h
cx = 7D00h
rep movsw             ; 64000 bytes of pixels

Then it fades in by odd steps:

for dl = 1; dl < 0x40; dl += 2 {
    for color in 0..63 {
        out_r = source_r[color] * dl >> 6;
        out_g = source_g[color] * dl >> 6;
        out_b = source_b[color] * dl >> 6;
    }
    wait_vblank();
    upload_first_64_colors();
}

After a wait of roughly 014Ah retraces, it fades out with DL=3Eh down to zero by twos. The multiply/shift shape is simple and robust: keep artist palette entries in 0..63 VGA range, scale by a 6-bit fade factor, and upload.

Palette Ramps At 66BDh, 670Dh, And 675Ch

The main part has several smaller palette/display helpers:

The panning helper is the timing-sensitive one:

start = [2954h] + ([2952h] >> 2)
pan   = [2952h] & 3
if extended_flag_not_set:
    pan *= 2

wait retrace boundary through 03DAh
out 03D4h, CRTC start high/low
read 03DAh to reset attribute-controller flip-flop
out 03C0h, 33h
out 03C0h, pan

This is how the demo gets smoother horizontal movement than whole-byte planar copies would allow. The framebuffer is byte/plane based, but the VGA attribute controller still provides sub-byte panning.

Text And Game Renderers

6A2Ah: Setup For A Text/Game Screen

6A2Ah sets up A000h and starts with a plane mask pattern around 1102h. It calls a formatting/text helper at B058h with string/table pointers around 6412h, 641Dh, and 6431h, then calls 6BA6h to draw text.

It also uploads an asset from segment 30D1h to A000h around offset 1C20h. That loop is another four-plane/interleaved copy:

for plane_group in 0..3:
    for plane_mask_phase in 0..3:
        select_vga_plane(mask);

        for row in 0..5:
            for x in 0..0x29:
                *dst++ = *src;
                src += 3;
            dst += row_advance;

        rewind/advance source for next phase;

The constants are small because this is not a full-screen copy. It is uploading a shaped object or panel into the current planar page.

Decimal Renderer At 6ACEh

6ACEh draws numbers by repeated division:

do {
    digit = value % 10;
    value = value / 10;
    glyph = segment_2E14 + 10E0h + digit * 90h;
    draw_digit_glyph(glyph);
} while (value != 0);

The draw loop is plane-oriented. For each digit it selects a sequencer mask, copies short movsb runs, skips the other interleaved source phases with SI += 3, and advances destination rows with DI += 58h. At the end of a vertical block it rewinds by constants such as 2D0h and 7Fh, rotates the plane mask, and carries into the next destination byte when needed.

This is designed for small scoreboard/game text. Division by 10 is outside the inner pixel loop; the expensive per-pixel work remains byte copies and plane mask updates.

Character Renderer At 6BA6h

6BA6h is the string version:

for (;;) {
    ch = es:[bx++];
    if (ch == 0) break;

    glyph_offset = word_table_2643[(ch - 0x20) * 2];
    ds = 2E14h;
    si = glyph_offset;

    for plane in 0..3 {
        out(0x3c4, plane_mask:02h);

        for glyph_rows {
            movsb style copies;
            si += 3;       // interleaved source skip
            di += 4Fh/58h; // screen row stride variant
        }
    }

    advance destination for next character;
}

The table at 2643h decouples ASCII-ish character values from glyph storage offsets. That lets the font be packed in tool order while the runtime still draws ordinary strings.

Blit/Clear Loops Around 67F0h

The main blit slice around 67F0h, 68E6h, and nearby code uses all-plane map masks to clear or punch repeated vertical shapes:

select all planes
for each column/shape:
    stosw / stosb bursts
    di += 58h         ; next row in the wider planar layout

Then it switches to small glyph/text strips:

for row in glyph_height:
    copy 0Bh bytes
    di += 4Fh
    si += 4Fh

This is the same design language as the rest of the main part: planar surface, pre-expanded small assets, row strides larger than the copied width, and palette/display-start changes doing part of the visual motion.

What Each Visible Part Is Doing

The local opener lines up with the .000 code:

The public later stills line up with the .001 code:

Why This Feels Different From The 1990/1991 Cascada Christmas Demos

The 1990 Cascada Christmas demo is mostly about a compact VGA/sound show around raw planar frames and simple animation. The 1991 one already has a setup-state and multi-backend audio feel. X-Mas '92 goes further:

So the core is not one spectacular software-rendering loop. It is a small demo runtime: loader, service bridge, VGA plane compositor, palette/panning engine, and hardware audio abstraction.

Boundaries And Caveats

Short Reconstruction Checklist

To recreate the important machinery, implement these components in order:

  1. Start from mode 13h, then reprogram VGA sequencer/CRTC into an unchained planar layout.
  2. Build a transparent planar blitter that selects masks 01h, 02h, 04h, 08h and skips zero source bytes.
  3. Add a retrace wait that waits for bit 3 of 03DAh to clear and then set.
  4. Add DAC fade helpers that scale 6-bit RGB components and upload during retrace.
  5. Add CRTC start-address and attribute-panning updates for smooth horizontal movement.
  6. Pre-expand rectangular asset blocks into allocated segments, then render them by copying every fourth source byte per selected plane.
  7. Add timer audio backends for LPT DAC, PC speaker reload, and Sound Blaster DMA around the same phase-accumulator mixer/sequencer state.
  8. Add text renderers that table-map characters to glyph offsets and draw them through the same plane-mask loop family.

That combination gives the recognizable X-Mas '92 technical profile: not a single inner loop, but a layered DOS/VGA runtime with small, purposeful loops for each hardware surface.

References