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:
cda-xm92.exe: launcher and configuration front-end, still mostly inside a Twilight-packed static image.cda-xm92.000: intro/control part, VGA setup, first logo/transition work, custom service loader, and timer audio backends.cda-xm92.001: main/game part, asset allocator, keyboard hook, VGA panning, palette engines, picture fades, text/game renderers, and later effect loops.
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.

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:

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.

Pipeline map:

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:
- Scene.org/Hornet 1992 index:
cda_xm92.zip,278641bytes,X-Mas '92 by Cascada. - Scene.org archive preview:
cda-xm92.000,cda-xm92.001,cda-xm92.cfg,cda-xm92.exe. - Demozoo production page:
X-Mas '92, Cascada, December 1992, MS-DOS. - Pouet production page:
X-mas 92 by Cascada, MS-DOS demo, December 1992.
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:
- The launcher displays/queries configuration state.
- It runs the first sidecar executable.
- The first part initializes VGA, displays the opener assets, installs or selects an audio backend, and builds a service/data segment.
- The first part passes control to the second sidecar.
- 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:
- The audio/player code in
.000is infrastructure shared by more than one visible sequence. It is not just a one-off logo sound routine. - The
.001code spends a lot of time moving small interleaved asset blocks into planar VGA, so many "effect" loops are actually asset/palette/display start choreography around precomputed material.
.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:
- clears the DAC through
42Ah, - clears all VGA planes through
412h, - expands logo/asset blocks with
3C5hand44Ch, - uses
4FChfor palette fades, - waits retrace with
128Bh, - uses movement/copy tables around
1133hand1157h, and - calls a far service entry through a segment value recovered from low memory.
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:
- current component values around
CS:0533, - step/delta component values around
CS:0B33.
Then for each fade frame it:
- waits for retrace,
- writes the DAC start index to
03C8h, - streams RGB components to
03C9h, - 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:
- Allocate approximately
67F3hbytes worth of paragraphs with DOSAH=48h. - Store the new segment in
CS:2027. - Copy a small control block into the new segment around offset
4100h. - Optionally load a side file/string referenced by the control block.
- Call a chain of setup routines:
15CBh,1846h,14A7h,154Ch,16DAh,158Ah,1700h. - Dispatch through a selector table.
On later calls:
- Use
DS = CS:[2027]. - Read selector byte
DS:[4104h]. - Dispatch through one of two tables around
CS:2019horCS:200Bh. - Free/reload a previous block if the
4113hfield 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:
- display-start/panning changes,
- a strip copy from segment
2A7Chto A000h around offset4D58h, - 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:
66BDhusesDL = 2 * [35DAh] + 1to fade a 64-color table fromAB26hinto a live palette buffer aroundA826h.670Dhinterpolates toward a highlight target of3Fh, usingDL = (10h - [7107h]) * 4 + 3.675Chupdates the CRTC display start and attribute-controller panning from scroll/phase values at2954hand2952h, then uploads either 64 or 256 DAC entries depending on flags.
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 title/logo frame is built by the first part's VGA setup, plane clears, transparent planar asset loader, larger logo expander, and palette fades.
- The fade-down at
00:18.000matches the palette interpolation machinery, not a redraw of darker pixels. - The saved configuration prompt is launcher/config state, before or around the chain into the sidecar parts.
- The icicle band is a title/background asset left on the planar surface; in local DOSBox-X it becomes the point where progress stops.
The public later stills line up with the .001 code:
- Title/instruction/credit pages are consistent with table-driven font/glyph drawing and palette ramps.
- The Santa game screen explains the keyboard hook, decimal renderer, small asset allocator, and repeated planar object blits.
- The final card matches the picture-copy/fade family: full 64000-byte pixel copy followed by 64-color RGB scaling.
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:
- multiple packed MZ parts instead of a single restored body,
- an explicit service/control segment,
- loader code that can allocate and chunk-read large resources,
- three output backends around one timer/music state,
- a second-part asset allocator that pre-expands many rectangular resources,
- CRTC start and attribute panning in the later visual loops, and
- game/text support routines, including keyboard hook and decimal renderer.
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
- The launcher executable remained packed enough that I did not treat its
static disassembly as a full truth source. The detailed code statements come
from the UNP-expanded
.000and.001sidecar images. - Local DOSBox-X capture did not reach the later public stills. The exact timestamps in this article stop at the icicle screen.
- Public screenshot selection intentionally omits contact/BBS-looking frames. The technical analysis paraphrases loader and runtime behavior rather than reproducing old private-contact text.
- Some routines are named by behavior and load offset, not by original symbols. No original source or map file was available in this pass.
Short Reconstruction Checklist
To recreate the important machinery, implement these components in order:
- Start from mode
13h, then reprogram VGA sequencer/CRTC into an unchained planar layout. - Build a transparent planar blitter that selects masks
01h,02h,04h,08hand skips zero source bytes. - Add a retrace wait that waits for bit 3 of
03DAhto clear and then set. - Add DAC fade helpers that scale 6-bit RGB components and upload during retrace.
- Add CRTC start-address and attribute-panning updates for smooth horizontal movement.
- Pre-expand rectangular asset blocks into allocated segments, then render them by copying every fourth source byte per selected plane.
- Add timer audio backends for LPT DAC, PC speaker reload, and Sound Blaster DMA around the same phase-accumulator mixer/sequencer state.
- 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.