Analysis model: gpt-5.5 xhigh
Cardiac by Infiny - Technical Dissection
Scope
This is a static and limited runtime dissection of Cardiac by Infiny, the
third-place PC demo at The Party 1993. The demo is interesting because it is not
one monolithic executable. The released CARDIAC.EXE is an LZEXE-packed outer
program with a large overlay. Inside that overlay is a bank of independently
packed MZ executables: picture/animation parts, a flat-real memory manager,
Kroc's 3D/vector-ball code, KTrak music support, and the final information
part.
Public references:
- Pouet production page: https://www.pouet.net/prod.php?which=4044
- Demozoo production page: https://demozoo.org/productions/6125/
- Scene.org archive: https://files.scene.org/get/parties/1993/theparty93/demo/cardiac.zip
- The Party 1993 result file: https://files.scene.org/get/parties/1993/theparty93/info/results.txt
This is not a source reconstruction. The module scheduler, every data structure field, and every generated object table are not fully named. What is covered in detail is the packaging model, the outer LZEXE depacker, the command-line part selector, the embedded executable bank, the 386/XMS/flat-real setup, the raster-synchronized palette loop, Kroc's vector-ball object path, the planar Mode X span writer, page flipping, palette fading, picture/PBM helpers, and the main color/rectangle loops that could be identified from the unpacked code.
Offset notation:
CARDIAC.EXE+0x...means a file offset in the released packed executable.outer+0x...means a file offset in the outer executable after LZEXE unpacking.MODnn+0x...means a file offset in an unpacked embedded module.- Port and register names are the original 16-bit x86/VGA names unless noted.
Examined Files
The archive contains:
CARDIAC.EXE 3,706,263 bytes
CONFIG.NFO 1,431 bytes
DISTSITE.NFO 1,830 bytes
FILE_ID.DIZ 490 bytes
Hashes:
4aaeb05146d7a006865bdf9fc73e297136cefc1ec14e6094f9415328d74d1763 cardiac.zip
695ffd16b40d295817476b03c658522f3ec4c49313c1e7f9f3d4618b4e536be5 CARDIAC.EXE
a12cd474065e4147340842a1865fed87cc03c78298f7a710de9364d602e71094 CONFIG.NFO
5ccb3ccc8f9ad795e210f47df2f38db23ef46d378ca01e0bc26138e04aae02b8 DISTSITE.NFO
c21eec1a814c69b1c22392ffc980322850aede101dabe0a0d1716be16689212a FILE_ID.DIZ
FILE_ID.DIZ identifies the production as Cardiac By INFINY, with VGA,
Sound Blaster, and Gravis Ultrasound support. It says the demo was released on
10 April 1994 and placed third at The Party III. Demozoo's page records the
same practical release story: made for The Party 1993, presented there, and
not really released until 10 April 1994.
The official result file has the PC demo third-place line with title/group effectively swapped:
The Party 1993 PC Demo
Rank Score Title Coder/Group
1 2492 UNTITLED Dust
2 2464 The good, the bad, the ugly S!P
3 1584 Infiny Cardiac
Pouet and Demozoo both identify the production as Cardiac by Infiny, which
is also what the archive metadata says.
Runtime Requirements
CONFIG.NFO is unusually explicit about what the executable expects:
- i386 or better.
- Standard VGA.
- 960 KB of XMS.
- About 600,000 bytes of free conventional memory.
- No TSR or memory manager that leaves the processor in V86 mode.
- A simple
HIMEM.SYSsetup, optionally withDOS=HIGH,UMB. - No
EMM386.
That matches the code. The demo uses 386 instructions, probes XMS through
int 2f, and includes flat-real/protected-mode setup code. A V86 monitor would
break assumptions about switching CR0 and loading descriptor tables.
A short DOSBox-X run entered graphics mode. The run did not extract temporary
files, which supports the static finding: CARDIAC.EXE contains the parts and
feeds them from its own overlay rather than acting like a plain self-extracting
archive.
Outer Executable Layout
The released executable is an MZ file with a very small DOS load image and a large overlay:
file size: 3,706,263 bytes
MZ extent: 22,962 bytes
header: 32 bytes
load image: 22,930 bytes
overlay: 3,683,301 bytes
relocations: 0
CS:IP: 0565:000e
SS:SP: 0b38:0080
min/max alloc: 074a/078a paragraphs
entry file off: 0x567e
UNP identifies the outer program as LZEXE 0.91/1.00a. After unpacking:
outer unpacked file size: 3,729,941 bytes
MZ extent: 46,640 bytes
header: 1,824 bytes
load image: 44,816 bytes
overlay: 3,683,301 bytes
relocations: 449
CS:IP: 0000:2ff5
SS:SP: 0c1f:0400
entry file off: 0x3715
The important point is that the overlay size is unchanged. LZEXE only expands the outer loader image. The big bank of embedded parts stays in the overlay.
Outer LZEXE Depacker
The original entry at CARDIAC.EXE+0x567e is the LZEXE bootstrap. It starts by
copying the packed stub upward, using std so the overlapping move is safe:
CARDIAC.EXE+0x567e:
push es
push cs
pop ds
mov cx,[000ch] ; byte count / packed-stub extent
mov si,cx
dec si
mov di,si
mov bx,ds
add bx,[000ah] ; destination segment above current image
mov es,bx
std
rep movsb
push bx
mov ax,002bh
push ax
retf ; continue in relocated depacker
After the far return, the main bit loop uses a 16-bit reservoir. BP holds
bits and DX counts how many are left. A clear bit copies a literal. A set bit
decodes a match or a long-control token:
CARDIAC.EXE+0x56d3:
mov dx,0010h
next_bit:
lodsw
mov bp,ax
bit_loop:
shr bp,1
dec dx
jne have_bit
lodsw
mov bp,ax
mov dl,10h
have_bit:
jae match_or_long
movsb ; literal: input byte to output byte
jmp bit_loop
The short match copy reads from the already expanded output. That means overlap is intentional and required: a run can be extended by bytes it is itself writing.
CARDIAC.EXE+0x572b:
mov al,es:[bx+di] ; bx is negative/backward match base
stosb
loop 572bh
For long files the depacker cannot pretend the whole stream fits in one 64 KB
segment. The continuation path around CARDIAC.EXE+0x5741 adjusts DS, ES,
SI, and DI around paragraph boundaries so the bitstream can keep expanding
across segment windows. This is why the code looks stranger than a tiny COM
depacker: it has to relocate and stream a proper MZ image, not just inflate a
small flat blob.
After decompression, LZEXE applies the relocation table. The loop reads compressed relocation deltas, adds the actual load segment, and patches words inside the new image:
CARDIAC.EXE+0x5771:
pop bx
add bx,0010h ; actual image base
xor di,di
reloc_loop:
lodsb
or al,al
je reloc_done_or_escape
add di,ax
add es:[di],bx
jmp reloc_loop
The final transfer restores the recovered program's stack and jumps to its real entry:
CARDIAC.EXE+0x57a6:
mov ax,bx
mov di,[0004h] ; recovered SP
mov si,[0006h] ; recovered SS
add si,ax
add [0002h],ax ; recovered CS relocated by load base
xor bx,bx
cli
mov ss,si
mov sp,di
ljmp cs:[bx] ; relocated CS:IP
At this point execution is in the unpacked outer loader at outer+0x3715.
Main Loader and Part Selection
The outer loader contains the visible configuration/usage text:
INFINY 1994 - CARDIAC DEMO
- Choose configuration -
Devices
Gravis Ultrasound
Sound Blaster
No sound
Sound quality
Good quality !
Medium quality !
The sound is poor
Save configuration
Quit without save
KTrak v2.99 - coded by KROC/Infiny
INFINY 1994 - CARDIAC v1.4
CARDIAC %(-) where % is a number in '1,2,3,4,5'.
or /L to loop the demo until reboot !
1. To run the demo from the INTRO.
2. To run the demo from the 3D PART.
3. To run the demo from the VECTOR BALLS.
4. To run the demo from the COLORS PART.
5. To run the demo from the END.
- after the number allows you to see only the checked part...
187 Secret Mode Activated...
The entry around outer+0x3715 is mostly a coordinator. It calls far runtime
services, parses the PSP command tail, compares against 1, 2, 3, 4,
5, /L, and the hidden 187, then chooses where the demo script starts or
whether it should stop after the selected part. The loader also owns the text
mode fallback and configuration menu.
The useful architectural conclusion is that the release executable is a small script engine plus a bank of separately packed code modules. The command-line selector is not a DOS batch trick; it is part of the loader.
Embedded Executable Bank
After unpacking the outer LZEXE image, nine additional MZ executables can be carved from the overlay. The packed module offsets below are in the outer unpacked file:
module offset packed size
MOD01 0x34c3bd 44,884
MOD02 0x357311 68,524
MOD03 0x367ebd 31,172
MOD04 0x36f881 31,091
MOD05 0x3771f4 6,111
MOD06 0x3789d3 34,036
MOD07 0x380ec7 32,785
MOD08 0x388ed8 13,039
MOD09 0x38c1c7 10,318
Each is itself LZEXE-packed. After unpacking:
module unpacked size header relocations entry file off
MOD01 84,144 464 109 0x069a
MOD02 398,704 80 13 0x26e0
MOD03 60,160 2,032 499 0x75c0
MOD04 52,496 352 80 0x05fd
MOD05 12,800 288 62 0x0120
MOD06 176,442 112 18 0xc380
MOD07 63,648 1,264 308 0x4874
MOD08 27,680 720 172 0x1451
MOD09 19,056 1,216 295 0x38e2
The string map gives good part identities:
MOD01 Barti DEPAC-LBM/PBM picture helper, palette and Mode X support.
MOD02 Kroc flat-real library, Infiny32 memory manager, TinyFli, KTrak.
MOD03 color/rectangle visual part with direct A000 copy/fill loops.
MOD04 another Barti DEPAC-LBM/PBM picture module.
MOD05 LCA 32-bit memory manager and ANIM_X bridge; raster DAC loop.
MOD06 Kroc 3D/vector-ball code and VGA span/page/palette routines.
MOD07 Barti DEPAC-LBM/PBM picture/transition module.
MOD08 credits/end picture module with Barti/Kroc/Karl/Legend/Friends strings.
MOD09 information/end text module and production credits.
MOD09 is useful as a sanity check on authorship. It names Barti for code and
graphics, Kroc for code, Karl for code, LCA for code, Moog and Shad for music,
and Zeb/AJT for graphics. It also contains a note about the hidden part. The
static code map aligns with those roles: Barti code clusters around picture
unpacking and PBM/LBM display; Kroc appears in the flat-real/audio and 3D
modules; LCA appears in the 32-bit memory manager / animation bridge.
Flat Real Mode and XMS Runtime
MOD02 and MOD05 explain the strict DOS setup in CONFIG.NFO.
MOD02 contains:
Flat Real Mode library v0.2
Infiny32 : Flat Real Mode Memory Manager
TinyFli v0.01
KTrak v2.99
The module checks for a 386, checks that the machine is not already in protected mode, probes HIMEM/XMS, and manages high memory. This is the infrastructure that lets the demo keep conventional memory free while still using large animation, music, and object data.
MOD05 is smaller but very revealing. Its entry at MOD05+0x120 shrinks the
DOS allocation, masks interrupts around sensitive sections, sets up FS, and
calls a 32-bit memory-manager service. The protected-mode hop is conventional
but direct:
MOD05+0x633:
pushad
...
lgdt [054ch] ; GDT for temporary protected-mode descriptors
mov eax,cr0
or al,1
mov cr0,eax
jmp 08h:00000183h ; flush prefetch, enter protected side
protected_side:
...
mov ax,0
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov eax,cr0
and eax,0fffffffeh
mov cr0,eax
jmp 0000h:000001aeh
The code is not trying to stay in protected mode as a DPMI client. It uses the protected-mode transition to load descriptors and arrange large addressability, then returns to real-mode style execution with 386 segment semantics. That is the classic "flat real" pattern: use protected mode briefly as a setup tool, then run most of the demo with BIOS/VGA friendliness and large-memory access.
The XMS detection path starts with the standard multiplex API:
MOD05+0x5c7:
mov ax,4300h
int 2fh ; XMS installed?
cmp al,80h
jne no_xms
mov ax,4310h
int 2fh ; ES:BX = XMS entry point
mov cs:[05a0h],bx
mov cs:[05a2h],es
So the NFO's "HIMEM yes, EMM386 no" instruction is not superstition. The code really wants XMS services while retaining control over the CPU mode.
Raster-Synchronized DAC Loop in MOD05
MOD05 also contains a small VGA trick that is easy to miss. It programs a
tweaked VGA mode, sets CRTC/attribute registers, and then updates palette
components synchronized to display-enable transitions on port 3DAh.
The core loop:
MOD05+0x26f:
mov dx,3c8h
xor al,al
out dx,al ; DAC write index = 0
inc dx ; dx = 3c9h
palette_loop:
outsb ; red or first component
outsb ; green or second component
mov dx,3dah
wait_display_enable:
in al,dx
test al,1
je wait_display_enable
mov dx,3c9h
outsb ; third component
mov dx,3dah
wait_display_disable:
in al,dx
test al,1
jne wait_display_disable
The significant bit is test al,1, not test al,8. Bit 3 is vertical
retrace. Bit 0 is display enable. This code is not merely waiting for vblank;
it is using the fine-grained active-display/blanking state to time when a DAC
component is written. On a CRT-era VGA this can produce raster-colored bands or
stabilize a mid-screen palette effect by slipping one component write into a
specific display-enable phase.
In practical terms:
- Two DAC component bytes are emitted immediately.
- The code waits until active display is asserted.
- The third component is emitted.
- The code waits until display enable clears again.
- The next palette entry or raster step can be advanced.
This is exactly the sort of loop that makes the demo sensitive to emulators and VGA clones. It depends on I/O timing and the semantics of the input-status register, not just on memory writes.
MOD06: Kroc's 3D / Vector-Ball Part
MOD06 has the strongest identifiable 3D code. Its strings include:
Credits for this part : code by Kroc
3D routines coded by Kroc/Infiny (1992/1993)
Vector-Baballes v0.84 - INFINY 1993
The entry at MOD06+0xc380 is after a large block of data. It sets its own
stack, calls runtime services through int 6b, initializes VGA/audio/support
state, installs a keyboard interrupt, waits for vertical blank, and then enters
the vector-ball sequence:
MOD06+0xc380:
mov ax,2a77h
mov ss,ax
mov sp,0640h
...
int 6bh ; runtime service calls
call 0bb05h
call 0bc7dh
call 0c2e8h
call 0b7eeh
call 294eh
call 0c343h ; install keyboard IRQ
call 0c186h ; wait one retrace phase
call 00f7h
...
call 5b3ah ; main vector-ball sequence
The keyboard hook reads port 60h and sets a flag when it sees the relevant
escape scan code, then sends EOI to the PIC:
MOD06+0xc32a:
in al,60h
cmp al,81h
jne chain_or_exit
mov byte ptr cs:[0c303h],1
mov al,20h
out 20h,al
The VGA setup is a Mode 13h to unchained/tweaked planar setup. It clears A000h with all planes enabled:
int 10h
call 0bcefh
mov dx,3c4h
mov ax,0604h
out dx,ax ; Sequencer Memory Mode
mov dx,3d4h
mov ax,0014h
out dx,ax ; CRTC underline/location tweak
mov ax,0e317h
out dx,ax ; CRTC mode control tweak
mov dx,3c4h
mov ax,0f02h
out dx,ax ; map mask = all four planes
mov es,0a000h
xor di,di
mov eax,51515151h
mov cx,4000h
rep stosd
The normal vblank wait uses bit 3 of input-status register 1:
MOD06+0xc186:
mov dx,3dah
wait_on:
in al,dx
test al,8
je wait_on
wait_off:
in al,dx
test al,8
jne wait_off
ret
That is separate from the MOD05 display-enable DAC loop. MOD06 uses vblank
for frame pacing; MOD05 uses display-enable timing for raster palette work.
Object Slot Allocator
The vector-ball part keeps a small table of active object slots. The table at
0640h has 50 word entries; 076ch holds the active count. Clearing the table
is a compact rep stosw:
MOD06+0x29184:
pusha
mov di,0640h
mov cx,0032h
mov ax,0ffffh
rep stosw ; 50 free slots
mov word ptr [076ch],0
popa
ret
Freeing an object trusts the index stored in the object record at [bx+16].
If the index is valid and the table entry is live, it marks both sides free:
MOD06+0x29198:
mov ax,0ffffh
push di
mov di,0640h
cmp word ptr [bx+16],0ffffh
je done
mov si,[bx+16]
shl si,1
add di,si
cmp word ptr [di],0ffffh
je done
dec word ptr [076ch]
mov word ptr [di],0ffffh
mov word ptr [bx+16],0ffffh
xor ax,ax
done:
pop di
ret
Allocating scans linearly for the first free slot:
MOD06+0x291c1:
cmp word ptr [076ch],0032h
jae full
mov si,0640h
scan:
lodsw
cmp ax,0ffffh
jne scan
mov [si-2],bx ; slot points to object record
inc word ptr [076ch]
sub si,0640h
shr si,1
dec si
mov [bx+16],si ; object remembers slot index
xor ax,ax
ret
full:
mov ax,0ffffh
ret
There is no heap sophistication here because the object count is small and fixed. The important feature is determinism: frame code can walk at most 50 slots, and allocation cost is bounded.
Projection and Visible-Object Sorting
The object walk at MOD06+0x291ea computes camera-relative deltas from object
coordinates and tests a squared-distance threshold. The camera position appears
at [079c], [07a0], and [07a4]; temporary deltas go to [07b0],
[07b2], and [07b4].
The inner arithmetic is 386-style 32-bit multiply inside 16-bit code:
mov eax,[bp+00h] ; object x, fixed/integer long
sub eax,[079ch] ; camera x
mov [07b0h],ax
imul eax ; edx:eax = dx * dx
mov ecx,eax
mov ebx,edx
mov eax,[bp+04h] ; object y
sub eax,[07a0h]
mov [07b2h],ax
imul eax
add ecx,eax
adc ebx,edx
mov eax,[bp+08h] ; object z
sub eax,[07a4h]
mov [07b4h],ax
imul eax
add ecx,eax
adc ebx,edx
shld ebx,ecx,16
cmp ebx,35a4h
jge skip_object
That is a distance cull. The exact unit scale is data-dependent, but the shape is clear: square X/Y/Z deltas, accumulate a high-range distance measure, reject objects beyond a threshold.
For visible objects it then projects/transforms the saved deltas and builds a
sort list at 06a4h:
movsx eax,word ptr [07b0h]
movsx ebx,word ptr [07b2h]
movsx ecx,word ptr [07b4h]
call 2947ch ; transform/project to screen/depth
neg ax
mov [di],ax ; sort key
mov [di+2],bp ; object pointer
add di,4
inc word ptr [076eh]
If more than one object survives, it calls a sort routine at 0x293bf, then
draws in sorted order:
cmp word ptr [076eh],1
jbe no_sort
call 293bfh
draw_sorted:
mov cx,[076eh]
mov si,06a4h
next_object:
mov bp,[si+2]
add si,4
call 292bch ; draw this object
loop next_object
Inside the object draw routine, a bounds routine at 0x295e7 rejects objects
that would fall outside the visible screen rectangle. Surviving polygon/ball
records are converted into a secondary draw list at 04b0h, sorted again, then
sent to the rasterizer. That two-level ordering is typical for this kind of
1993 engine: first sort whole objects, then sort faces or ball sprites inside
an object.
Planar Span Filler Inner Loop
The most important low-level loop in MOD06 is the horizontal span writer
around MOD06+0x2a268. It draws clipped spans in a planar VGA layout. In this
mode, four adjacent pixels share one byte address, selected by the Sequencer
map-mask register at port 3C4h/3C5h.
Inputs visible in the code:
[0da6] first scanline / y0
[0da8] last scanline / y1
[0da0] fill color
[0a1c] left-edge x table, one word per scanline
[0bac] right-edge x table, one word per scanline
[5102] current page segment, usually A000h or A400h
Setup:
MOD06+0x2a268:
mov cx,[0da8h]
mov si,[0da6h]
sub cx,si
inc cx ; number of scanlines
shl si,1
mov bp,si
add si,0a1ch ; SI -> left-edge table row
; compute initial row base in BP, effectively y * 80
; after shifts/adds, next rows add 50h bytes.
mov es,[5102h] ; active draw page
mov dx,3c4h
mov al,2
out dx,al
inc dx ; dx = 3c5h, sequencer map mask data port
mov ah,[0da0h] ; AH = color
Per scanline it loads the two edge X values, orders them, clips them, converts the first X to a byte offset and starting plane, and writes three possible regions: leading partial byte, full middle bytes, trailing partial byte.
The ordered/clipped part:
span_line:
push cx
mov cx,[si] ; x0
mov bx,[si+0190h] ; x1, second edge table
cmp cx,bx
jle ordered
xchg cx,bx
ordered:
cmp bx,0
jl next_line ; fully left of screen
cmp cx,319
jg next_line ; fully right of screen
cmp cx,0
jge left_ok
xor cx,cx
left_ok:
cmp bx,319
jle right_ok
mov bx,319
right_ok:
sub bx,cx
inc bx ; BX = pixel count
Address and plane setup:
mov di,cx
shr di,2 ; byte column = x / 4
add di,bp ; add row base
and cx,3 ; starting plane = x & 3
je middle
If the span does not begin on plane 0, a leading partial byte is needed. For
example, starting at plane 2 means only planes 2 and 3 of the first byte should
change. The map-mask byte is built from 0x0f << starting_plane:
leading_partial:
add bx,cx
sub bx,4
js one_byte_span
mov al,0fh
shl al,cl
out dx,al ; enable planes from start plane through plane 3
mov al,ah
stosb ; one byte address, selected planes only
Then come the full bytes. These represent groups of four pixels, so all four
planes are enabled and rep stosb is enough:
middle:
mov al,0fh
out dx,al ; all planes enabled
mov al,ah
mov cx,bx
and bx,3 ; BX = tail pixels after full bytes
shr cx,2 ; CX = number of full byte columns
rep stosb
The trailing partial byte is the mirror image of the leading partial. If one,
two, or three pixels remain, it shifts 0x0f right to keep only the low planes:
tail:
mov cx,4
sub cx,bx
mov al,0fh
shr al,cl ; mask for low tail planes
je next_line
out dx,al
mov al,ah
mov es:[di],al
Finally it advances to the next scanline:
next_line:
add bp,0050h ; 80 bytes per 320-pixel planar row
add si,2
pop cx
loop span_line
What this loop is doing, in plain terms:
- A filled polygon or ball silhouette has already generated left and right X edges for each scanline.
- The span writer clips each horizontal line to 0..319.
- Because unchained VGA stores pixels by plane, it cannot simply write a byte per pixel.
- It writes a first partial byte if the span starts mid-byte.
- It writes as many full four-pixel byte columns as possible.
- It writes a final partial byte if the span ends mid-byte.
This is a classic VGA performance compromise. The CPU avoids per-pixel branches
inside the middle of the span. The expensive out dx,al map-mask changes only
happen at the edges and at the transition to the full middle run.
There is a related unclipped path around MOD06+0x2a324. It has the same
planar-mask idea but assumes the caller already produced safe X ranges.
Page Flipping and Clears
The vector-ball code flips between two 64 KB-ish page windows by changing both
the CRTC start address and the segment it uses for drawing. The state byte at
[510c] selects which page is visible/drawn:
MOD06+0x2a443:
cmp byte ptr [510ch],0
jne page_b
page_a:
mov word ptr [5102h],0a000h
mov dx,3d4h
mov ax,000dh
out dx,ax
mov ax,400ch
out dx,ax
mov byte ptr [510ch],1
ret
page_b:
mov word ptr [5102h],0a400h
mov dx,3d4h
mov ax,000dh
out dx,ax
mov ax,000ch
out dx,ax
mov byte ptr [510ch],0
ret
The clear routine writes both pages with all four planes enabled:
MOD06+0x2a4af:
mov dx,3c4h
mov ax,0f02h
out dx,ax
mov es,0a000h
xor di,di
xor ax,ax
mov cx,1f40h
rep stosw
mov es,0a400h
xor di,di
xor ax,ax
mov cx,1f40h
rep stosw
1f40h words is 16,000 bytes. With all four planes enabled, that covers
64,000 pixels, i.e. a 320x200 display. Again, the code is exploiting planar VGA
instead of treating A000h as a simple chunky framebuffer.
The full mode initializer first sets BIOS mode 13h, then reprograms the VGA into the unchained layout the span code expects:
MOD06+0x2a4e5:
call black_palette
mov ax,0013h
int 10h
mov dx,3c4h
mov ax,0604h
out dx,ax
mov dx,3d4h
mov ax,0014h
out dx,ax
mov ax,0e317h
out dx,ax
mov dx,3c2h
mov al,83h
out dx,al
mov dx,3c4h
mov ax,0f02h
out dx,ax
mov es,0a000h
xor di,di
xor ax,ax
mov cx,8000h
rep stosw
The apparent mismatch between "Mode 13h" and the planar span code is resolved by those sequencer/CRTC writes. BIOS mode 13h provides a known VGA baseline; the demo then turns it into the tweaked planar mode it actually uses.
Palette Fades
MOD06 does not just upload a fixed palette. It has small integer fade loops
that scale or bias 256 RGB triples before writing them to the VGA DAC.
The fade-in loop uses [3e6c] as a factor, increments it by 3, and writes the
high byte of each 8x8 multiply as the scaled VGA component:
MOD06+0x2a553:
mov bl,[3e6ch]
add byte ptr [3e6ch],3
mov es,2adch ; palette table segment
mov si,000ah
mov dx,3c8h
xor al,al
out dx,al
inc dx ; 3c9h
mov cx,0100h
fade_entry:
mov al,es:[si+0]
mul bl
mov al,ah
out dx,al
mov al,es:[si+1]
mul bl
mov al,ah
out dx,al
mov al,es:[si+2]
mul bl
mov al,ah
out dx,al
add si,3
loop fade_entry
The fade-to-white path starts with a small factor and moves each component
toward 0x3f, the VGA DAC maximum:
mov al,3fh
sub al,es:[si+n] ; distance from current component to white
mul bl
add al,es:[si+n] ; current + scaled distance
out 3c9h,al
This is cheap and visually useful. It avoids divisions, uses only 8-bit multiplies, and lets the script fade the whole vector-ball palette in, through white, or down without recomputing palette tables offline.
MOD03 Color and Rectangle Loops
MOD03 appears to be one of the color/visual modules. Two inner loops are
especially clear.
The first copies a 120-line rectangle into A000h. One path uses per-line source offset tables, so the image can be warped or scrolled by varying source and destination offsets per scanline:
MOD03+0x13d1:
; for y = 0..119
; DS = source segment [555e]
; ES = A000h
mov si,table_value
add si,005ah
mov di,311ah
add di,x_or_phase_offset
add di,row_offset
mov cx,0046h
rep movsw ; 70 words = 140 bytes
The alternate path is a fixed rectangle copy:
mov es,0a000h
mov di,311ah
add di,phase_offset
mov si,005ah
mov dl,78h ; 120 lines
row:
mov cx,0046h ; 140 bytes
rep movsw
add di,00b4h ; 320 - 140 bytes to next destination line
dec dl
jne row
The second clear/fill loop at MOD03+0x2a50 is a planar-gradient or box-fill
path. It enables all planes, chooses an A000h address, then writes rows while
changing the fill byte:
MOD03+0x2a50:
mov dx,3c4h
mov ax,0f02h
out dx,ax ; all planes enabled
mov es,0a000h
mov di,[6956h]
add di,0059h
mov ah,0bfh
mov al,0bfh
mov dl,3eh
upper:
dec ah
dec al
push di
mov cx,001fh
rep stosw ; 62 bytes, four planes at a time
pop di
add di,0050h ; next planar scanline
dec dl
jne upper
Because all four VGA planes are selected, each byte/word store affects multiple pixels in the planar image. The loop is short, branch-light, and exactly shaped for a rectangular band: write a run, step by 80 bytes, repeat.
Barti PBM/LBM Picture Helpers
MOD01, MOD04, MOD07, and MOD08 share Barti picture/unpacking code.
They contain strings such as:
BARTI! DEPAC-LBM BARTI! V92.235
FORM
PBM
BMHD
CMAP
BODY
TINY
CRNG
The code is an IFF/PBM/LBM-style image loader and display helper. It parses chunks, uploads palettes, sets the VGA mode, clears display memory, and waits for vblank.
The palette upload in MOD01 is the straight DAC path:
MOD01+0x130c3:
push ds
mov si,[bp+6]
mov ds,[bp+8]
mov dx,3c8h
mov cx,0300h ; 256 * 3 RGB bytes
xor al,al
out dx,al ; DAC index 0
inc dx ; DAC data
rep outsb
pop ds
retf 4
The mode tweak is the same broad family as the vector-ball code:
MOD01+0x13159:
mov dx,3c4h
mov ax,0604h
out dx,ax
mov dx,3d4h
mov ax,0014h
out dx,ax
mov ax,0e317h
out dx,ax
The clear loop:
MOD01+0x1316f:
mov es,0a000h
mov cx,7d00h
xor di,di
xor ax,ax
rep stosw
And the simple vblank wait:
MOD01+0x1317f:
mov dx,3dah
wait_on:
in al,dx
and al,8
je wait_on
wait_off:
in al,dx
and al,8
jne wait_off
retf
There are also Pascal-style string and memory helpers in MOD01. A
length-prefixed string copy is literally:
lodsb ; AL = length
stosb ; store length
mov cl,al
rep movsb ; copy payload
The overlap-safe memory move at MOD01+0x14813 chooses backward copying when
the destination is inside the source range:
cmp si,di
jae forward
add si,cx
add di,cx
dec si
dec di
std
rep movsb
cld
ret
forward:
rep movsb
That kind of support code is boring only until you remember that this demo is loading and animating many packed image chunks in a 16-bit environment. Robust chunk parsing and overlap-safe moves matter.
What the Parts Add Up To
Cardiac is structured like a small demo operating system:
- The released MZ starts as a compact LZEXE-packed loader.
- The unpacked outer program handles configuration, command-line part selection, looping, and hidden mode dispatch.
- The overlay contains multiple packed MZ modules rather than one flat asset blob.
- The memory system uses XMS and flat-real/protected-mode setup to escape the normal conventional-memory ceiling.
- Picture parts use Barti's PBM/LBM depacker and familiar VGA palette/mode helpers.
- Kroc's 3D part uses a fixed active-object table, distance culling, sorted draw lists, and a real planar Mode X span filler.
- The raster and palette code uses both vertical-retrace waits and the finer display-enable bit, depending on the effect.
The planar span filler is the most "classic inner loop" recovered here. It is
not abstract polygon code; it is hand-shaped for 320-pixel VGA: convert X to
x >> 2, choose planes with x & 3, do at most two partial-byte map-mask
writes, and blast the middle with rep stosb. That is the exact kind of code
that made early PC demos feel closer to hardware programming than to a graphics
library.
Remaining Unknowns
The main uncertainties are in orchestration and data naming:
- The outer loader's module-call protocol is clear in broad shape, but not fully named field-by-field.
- The generated or precomputed object data for the larger 3D scene is not fully decoded here.
- The animation formats behind
TinyFliandANIM_Xare identified by strings and surrounding code, but their complete file/chunk structures were not reconstructed. - The hidden
187mode is identified from the loader text and dispatch logic, but this pass did not exhaustively trace its full media path.
Those gaps do not change the main technical picture: Cardiac is a multi-module 386 VGA demo with a serious loader, high-memory runtime, image/animation subsystems, and a hand-written planar 3D rasterizer.