Analysis model: gpt-5.5 xhigh
Show by Majic 12 - Technical Dissection
Analysis model: gpt-5.5 xhigh
Scope
This is a static dissection of Show by Majic 12, released at Hammering 1994.
The public pages and release text credit the demo to Majic 12; the coder is
credited as Maxwood. That is the name used in the production metadata and in
the demo's own embedded text, so I treat the user's "Maxwell" reference as this
Maxwood/Majic 12 production.
Sources:
- Pouet prod page: https://www.pouet.net/prod.php?which=1158
- Demozoo production page: https://demozoo.org/productions/9715/
- Scene.org archive: https://files.scene.org/get/demos/groups/majic12/show_fix.zip
The analysis target is the fixed archive:
102903192fae04f730b96cb111b2c9e35aced8f1dd462ecf35cf72df838a2571 show_fix.zip
1f2470abef4600ac176b5263f2a4f0a384bdb074e0cb51eb3d773876adc489e8 SHOW.EXE
9c0cf703a3f2f3502dae0cf56e8fc010f081978b9ce322509ec9acf66d3fb174 SHOW.TXT
394b82bb93bc21015894002d6e352dc970b2ae6c7c8294e94cb6346a007501ad FILE_ID.DIZ
FILE_ID.DIZ calls this "the final bugfree version" and lists the requirements
as GUS,386. SHOW.TXT gives the fuller requirement list: 386, VGA-compatible
card, Gravis UltraSound with 256 KB, and about 570 KB of conventional memory.
It also states that the music replay uses Cascada's GUS routines.
The important caveat: I did not reconstruct source code, and I did not produce a screenshot timeline for every part. This is a binary-level behavioral map of the loader, the resource layout, and the most important render loops. For modules where the extracted file name and code agree, I use a confident visual label. For modules where the binary only proves "picture viewer", "palette cycler", or "planar blitter", I say that directly instead of pretending a frame capture was done.
Why This Demo Is Built Differently
SHOW.EXE is not a normal single executable image. It is a small real-mode DOS
loader plus a very large appended module bundle. The MZ header describes only
about 17.5 KB of executable image, while the actual file is about 1.95 MB.
The loader reads its own executable file, seeks to an internal directory near
the end, and then loads individual *.COD and *.DAT resources by name. The
visible show is therefore a script of small self-contained effect modules:
SKULL.START.QQ.ROT.CH.COPPER.16C.1LAP.PIXEL.TMAP.DO.TRON.C.DOOM.NEWG.
STOP.ART.ZZZ.INTER.CIR.FAST.RULES.CRED.NOTJUST.RAS.BIGS.CYC.BOB.COM.
HAM.END.THE.
Each part is loaded, called, allowed to own VGA for a while, and then returns to the loader. This makes the demo feel like one large hand-authored timeline, but the executable structure is closer to a tiny DOS module player.
Credits And Release Notes
The release text credits:
Program: Maxwood / Majic 12
Graphics: Rack / Majic 12
Music: Chorus & Sid
Replay: Cascada GUS routines
END.DAT also contains the demo's own end text. It says the demo was released
at Hammering 1994 in Budapest, was compiled with Turbo Assembler, and was
written on a 386DX/40 with a Tseng ET4000. That last detail matters: a lot of
the code is very explicitly 386-era real-mode VGA code. It avoids a protected
mode runtime and spends its budget on precomputed tables, self-modifying code,
and direct VGA register control.
Outer EXE Layout
The MZ header has these useful fields:
e_cblp = 0000h
e_cp = 0023h ; MZ image size = 35 * 512 = 17920 bytes
e_cparhdr = 0020h ; 512-byte header
e_crlc = 0007h ; only seven relocations
CS:IP = 001c:1446h
SS:SP = 0000:01c0h
The entry point's raw file offset is:
0x200 + 0x1c * 16 + 0x1446 = 0x1806
There are no obvious PKLITE, RNC, PMODE, DOS/4G, or LZ91 packer
markers in SHOW.EXE. The large file size is not a packed protected-mode blob;
it is mostly an appended effect and asset store.
The loader contains the show script string at file offset 0x919, followed by
user-facing error text such as Not enough memory and the Gravis prompt. The
internal resource table begins near file offset 0x1aa802.
Resource Directory
The resource directory is read from the executable itself. Each entry is 18 bytes:
00..0b zero-padded DOS-style filename, e.g. "TMAP.COD"
0c..0f 32-bit file offset, stored as high word then low word for int 21h seek
10..11 16-bit read length
The loader uses two file names for each visible part:
PART.COD executable code module
PART.DAT data, graphics, lookup tables, or text for that module
The loaded memory layout is consistent across modules:
code segment = allocated aligned segment
data segment = code segment + 1000h
The loader reads *.COD to offset zero in the code segment, reads *.DAT to
offset zero in the data segment, sets up a call contract, and far-calls the
loaded code through its module dispatch pointer.
The register contract visible at the call site is:
DS = module data segment
AX = data segment + 1000h, used by some parts as scratch or extra buffer space
CX = loader code segment
DX = loader/Cascada music control offset, observed as 2fd4h
BP = Cascada GUS code segment, observed as 01b1h
BX = Cascada GUS routine offset, observed as 22c8h
Several modules save AX immediately and use it later as a scratch segment.
PIXEL.COD, for example, loads it into GS and stores addresses of changed
screen bytes there so the next frame can erase only the dirty pixels.
Loader Inner Loop
The loader's own main loop is small but important:
; conceptual form of the part loop
show_ptr = address_of("SKULL.START.QQ...")
while *show_ptr:
name = read_until_dot(show_ptr)
cod_name = name + ".COD"
dat_name = name + ".DAT"
; Special case: a module name ending in ".COM" can be rewritten to ".KOM"
; depending on the loader's speed/timing variable.
cod_rec = find_record(cod_name)
dat_rec = find_record(dat_name)
read_exe_range(cod_rec.offset, cod_rec.size, code_segment:0000)
read_exe_range(dat_rec.offset, dat_rec.size, data_segment:0000)
call_far_module_entry()
The loader has one odd adaptive branch around the COM part. If the parsed
module name ends in COM and a loader timing/speed variable is below or equal
to 7, it rewrites the first letter to K, so COM.COD becomes KOM.COD.
Both module pairs are present in the directory. That strongly suggests COM
and KOM are alternate implementations of the same visible slot, probably
selected for machine speed or timing tolerance.
Keyboard And Sound
The loader installs its own interrupt 9 keyboard handler:
in al,60h ; read scancode
mov cs:[0625],al
in al,61h ; keyboard acknowledge toggle
or al,80h
out 61h,al
and al,7fh
out 61h,al
mov al,20h
out 20h,al ; end of interrupt
iret
That gives every part a cheap "last scancode" location without calling BIOS in the render loop. The old interrupt vector is saved and restored on exit.
For music, the startup code probes or asks for a Gravis UltraSound port, then calls the Cascada replay routines. The visual parts receive enough loader state to poll a sync/control word. In several effects the outer frame loop waits for a nonzero word at the loader-provided location before drawing the next step. That is the demo's music/timer pacing hook.
Module Inventory
The table below uses sizes from the extracted resource table and entry offsets
from the first jump in each *.COD module. The "role" column is intentionally
code-oriented. When the visual name is certain, it says so; otherwise it says
what the binary actually proves.
| Module | COD bytes | DAT bytes | Entry | Role |
|---|---|---|---|---|
SKULL |
23968 | 60736 | 0x5600 |
Opening skull/logo-style planar part; uses A000/A800, direct VGA, and large picture data. |
START |
64816 | 24343 | 0xf8ec |
Large title/start part; very near 64 KB of code, mostly asset decode and VGA presentation. |
QQ |
45674 | 42576 | 0xad93 |
Large table-driven visual part; exact caption is not obvious statically. |
ROT |
31048 | 64000 | 0x6b98 |
Rotation/object part; sizeable trigonometric/table render core and planar output. |
CH |
6142 | 128 | 0x1414 |
Small control/transition part; almost no external data, mostly code and palette state. |
COPPER |
672 | 9004 | 0x004d |
Hardware-timed raster/copper bars using retrace polling and VGA palette writes. |
16C |
1424 | 52931 | 0x01a2 |
16-color picture/palette display; compact code with large image data. |
1LAP |
608 | 2528 | 0x000b |
Short bridge/transition; VGA timing and palette writes dominate. |
PIXEL |
55607 | 30960 | 0xcbf4 |
3D pixel/vector object with classic planar Bresenham line inner loops. |
TMAP |
24288 | 65343 | 0x5602 |
Texture-mapped column/strip renderer, probably the most interesting core renderer. |
DO |
1860 | 15360 | 0x000b |
Short bridge or picture part; small code, medium data, VGA transition behavior. |
TRON |
2048 | 51954 | 0x0007 |
Tron/grid line part with planar setup and page flipping. |
C |
4736 | 23912 | 0x0336 |
Graphics-controller-heavy planar blitter/effect; many GC and sequencer writes. |
DOOM |
50734 | 21248 | 0xc123 |
Doom-inspired scanned-picture scaler with generated column-copy codelets. |
NEWG |
16338 | 10208 | 0x39c9 |
Larger graphics effect; direct VGA, palette, and buffer work. |
STOP |
752 | 11042 | 0x004e |
Compact ILBM/PBM-style picture transition; data contains FORM/ILBM markers. |
ART |
320 | 54966 | 0x0007 |
Picture viewer with PBM, BMHD, CRNG, and BODY chunks. |
ZZZ |
8437 | 65535 | 0x0d2b |
Data-heavy full-screen part; many generated/stos-style fills, likely sleep/ZZZ themed. |
INTER |
592 | 7083 | 0x000b |
Intermission picture viewer; data contains FORM/ILBM/BODY. |
CIR |
1744 | 26530 | 0x000b |
Circle/ring transition; compact code and large lookup/image data. |
FAST |
8115 | 51040 | 0x0025 |
Fast full-screen effect; data contains <Maxwood/Majic 12>. |
RULES |
6942 | 48096 | 0x1377 |
Rules/text or page sequence; moderate renderer plus large data bank. |
CRED |
896 | 42883 | 0x000b |
Credits picture pages; multiple PBM and CRNG chunks. |
NOTJUST |
880 | 14302 | 0x00ec |
Short ILBM-backed text/picture part; data contains FORM/ILBM/BODY. |
RAS |
1120 | 1530 | 0x00ae |
Raster-bar/palette split part; small data and direct VGA register writes. |
BIGS |
774 | 43632 | 0x0007 |
Big text/bitmap display; tiny code driving large image data. |
CYC |
8736 | 60474 | 0x1a96 |
Color-cycling picture engine; code and data contain FORM, PBM, BMHD, CRNG, BODY. |
BOB |
7800 | 28768 | 0x052e |
Blitter object/sprite part; "BOB" name matches the block-move style renderer. |
COM |
17913 | 51200 | 0x3297 |
One implementation of the adaptive COM slot; heavy planar drawing with many clears. |
KOM |
25932 | 51200 | 0x4197 |
Alternate implementation of the same slot, selected by loader rewrite on some timings. |
HAM |
14282 | 65535 | 0x2ee4 |
Hammering/party section or late-demo full-screen part; large data bank. |
END |
53568 | 53261 | 0xcc02 |
Long end sequence and text scroller; END.DAT contains the demo's release commentary. |
THE |
576 | 11636 | 0x000d |
Final picture/page; data contains PBM, CRNG, and BODY chunks. |
The resource table also contains BEGIN and PERS. They are not in the main
show script string I found, so they are either unused, called indirectly, or
left over from development:
| Module | COD bytes | DAT bytes | Entry | Role |
|---|---|---|---|---|
BEGIN |
752 | 7120 | 0x004a |
ILBM-backed picture/init module; not in the main show script. |
PERS |
2736 | 65196 | 0x0710 |
Perspective-related resource by name and size; not in the main show script. |
Part-By-Part Reading
SKULL opens with a large planar display part. Its data bank is almost 60 KB,
and the code hits both A000 and A800, which in this demo usually means the
part prepares one page while another page is visible. It uses direct Sequencer
and Graphics Controller programming rather than BIOS drawing. The role is most
likely the opening skull/logo visual implied by the name.
START is the largest individual code module after the loader. Its entry is at
the end of the COD file, and the preceding region is mostly helper code,
tables, and unrolled routines. That shape usually means the part jumps over
embedded routines/data into a high-offset entry point. Its role is the title or
start presentation: it is not a tiny transition, it is a full renderer.
QQ is another large, precomputed, asset-driven module. It uses palette and
VGA timing routines but has no readable caption strong enough to name the
visual from static evidence alone. The safe statement is: it is a full-screen
graphics effect, not a simple text page.
ROT is the first obviously computational part in the timeline. The name,
module size, table density, and planar screen access point to a rotation effect:
rotating bitmap, rotating object, or rotating coordinate field. The code's
shape is not a raw picture viewer; it spends significant time on table-driven
address and pixel generation before touching the visible page.
CH is a short control or transition part. Its .DAT is only 128 bytes, so it
cannot be showing a substantial image from external data. It is mostly code and
state, probably bridging between the earlier full-screen visual and the
hardware-timed COPPER part.
COPPER is the cleanest hardware trick in the demo. It sets a 16-color VGA/EGA
graphics mode, extracts palette bytes from an ILBM-style BODY image, and then
changes hardware state in sync with the raster. It polls 3DAh, writes the
Attribute Controller through 3C0h, and uploads DAC values through 3C8h/3C9h
while a fade counter changes. This is "copper" in the Amiga sense: not a real
PC copper, but a carefully timed CPU loop pretending to be one.
16C is a compact 16-color display part. The code is small and the data is
large, which is typical of "show a prepared picture with some palette or
scrolling treatment". It also writes VGA planar registers, so it is not a
linear 256-color mode part.
1LAP is a small transition. The code uses DAC writes, 3DAh retrace waits,
and CRTC/Attribute Controller writes. It likely exists to pace or wipe into the
next large effect rather than to carry its own complex renderer.
PIXEL is a major renderer. It transforms points, projects them, then draws
line or pixel structures into planar VGA memory. It tracks dirty screen bytes
in a scratch segment so the next frame can clear exactly those addresses. Its
inner loop is a classic 386-era planar Bresenham plotter, described in detail
below.
TMAP is the texture-mapping core. It builds row tables, bit-mask tables,
texture delta tables, and interpolation tables, then draws projected vertical
strips into a scratch buffer before copying to the screen. This is the most
"demo engine" looking part in Show: the renderer is not just blitting a
picture, it is using precomputed tables to make texture mapping fast enough on
a 386.
DO is a small bridge part. Its data is large enough for graphics, but the code
is far smaller than the major renderers. It likely displays or transitions a
prepared page.
TRON draws a grid-like line scene. It initializes planar masks, draws
horizontal and vertical line structures into two pages, and flips or scrolls by
changing CRTC display-start registers. The name and line-grid code agree.
C is a compact planar graphics effect. It writes heavily to the VGA Graphics
Controller (3CEh) and Sequencer (3C4h), so its visual work depends on
planar masking and read-map/write-map tricks rather than normal byte-per-pixel
output.
DOOM is the famous/fancy one in this executable. SHOW.TXT says the Doom
effect picture is scanned, and the code matches a scanned-picture scaler: it
enters mode 13h, tweaks VGA registers, generates tiny copy routines, and calls
those generated routines per column. It is not a Doom engine; it is a Doom-like
wall/picture zoomer built from vertical scalers.
NEWG is a medium-large graphics part. It has enough code to be an effect
rather than a static page, but without a stronger embedded text label I would
not assign a precise visual beyond "new graphics/effect slot".
STOP, ART, INTER, CRED, NOTJUST, and THE are picture/page modules
with Amiga-ish file markers in their data (FORM, ILBM, PBM, BMHD,
BODY, and often CRNG). Their code is tiny compared with their data. That
means they mostly decode or present prepared images, sometimes with color
cycling rather than geometric rendering.
ZZZ is data-heavy and fill-heavy. The code contains many store/fill patterns,
so it is more active than a minimal picture loader, but the static name is the
strongest visual label here. Treat it as a themed full-screen part with large
prepared assets.
CIR is a compact circle/ring effect. Its module name and data/code ratio
suggest precomputed circle or radial tables feeding a small VGA renderer.
FAST contains the literal <Maxwood/Majic 12> in its data and has a renderer
large enough to do more than show a single bitmap. This is probably one of the
author-signature or fast-moving text/bitmap sections.
RULES is likely a text/page section. It is too large to be only a palette
fade, but not shaped like the big geometric renderers. The name says "rules",
and the data size says prepared content.
RAS is the raster-bar module. It is tiny, writes the DAC and VGA registers,
and has only 1530 bytes of data. This is a timing/palette trick rather than an
asset-heavy picture part.
BIGS is a large bitmap/text presenter: tiny code, huge data. The name likely
means "big scroller" or "big letters". Static evidence cannot distinguish those
two without frame capture, but the implementation class is clear.
CYC is a color-cycling picture engine. Both code and data contain CRNG
markers, the Amiga IFF color-range cycling chunk. The module is not only
showing a picture; it is animating palette ranges.
BOB is almost certainly a sprite/blitter-object part. In Amiga terminology,
a BOB is a blitter object. On PC VGA there is no Amiga blitter, so the module
does the moving/copying itself with CPU loops and planar masks.
COM/KOM are the adaptive pair. The loader has one visible COM slot, but
it can rewrite COM.COD to KOM.COD before loading. The two implementations
share the same data size. This is a neat production detail: the timeline has
one logical part, but the binary ships more than one renderer path for it.
HAM and END are late-demo sections. END.DAT contains the release comments,
technical bragging, BBS adverts, and closing text, so END is definitely a
long text/end sequence rather than an abstract effect.
VGA Model
Most parts are planar VGA, not chunky mode 13h. A typical module writes:
mov dx,3c4h
mov ax,0202h
out dx,ax ; Sequencer index 2, map mask
mov dx,3ceh
mov ax,0005h
out dx,ax ; Graphics Controller mode
mov ax,0a000h
mov es,ax
In planar modes, one byte in A000 represents eight horizontal pixels on one
or more bitplanes. That is why many inner loops look strange if read as
byte-per-pixel drawing. They are not writing color indices directly. They are
selecting planes, rotating bit masks, and ORing prepared bit patterns into
screen bytes.
Several modules also use A800. In real mode, segment A800h addresses
A0000h + 8000h, so it is the second half of VGA memory. The demo uses that as
an alternate page or staging area, then changes CRTC display-start registers to
select what is visible.
Inner Loop 1: TMAP.COD Texture Strip Renderer
TMAP enters at 5602h. Its setup sequence does four important things:
- Saves the loader-provided scratch segment from
AX. - Builds a row-offset table where each scanline advances by
28hbytes. - Clears the scratch buffer with
rep stosw. - Builds lookup tables for planar masks, texture deltas, and projection.
The row table proves this renderer is still planar. A 320-pixel-wide planar
line is 40 bytes, which is exactly 28h.
The main frame loop toggles between visible pages. It changes CRTC registers
0Ch and 0Dh to set the display start, waits for vertical blank, copies a
prepared scratch rectangle to the active page, clears the scratch spans, and
then renders new strips.
The core strip routine is at 5a08h. In simplified form:
; Inputs have already been prepared by the edge/projection code.
; BP = texture/sample pointer
; DI = destination offset in scratch buffer
; CX = height / 4
; DX = 0028h, one planar scanline
; BX selects a precomputed mask/pattern table
strip_loop:
bl = ds:[bp] ; sample texture/source byte
bx = bx * 2
ax = table[bx] ; planar mask/pattern for this texture sample
es:[di] |= ax ; merge into scratch word
di += 28h ; next scanline
bp += next_delta() ; texture step from lodsw table
; repeated three more times, unrolled
; ...
loop strip_loop
tail:
; same operation for height & 3 remaining pixels
The real loop is more compact and uses lodsw to fetch the next texture
advance. It also uses bit rotations and a mask table so a vertical strip can
land at arbitrary bit alignment in the planar byte layout.
What this accomplishes:
- Projection code decides which vertical strips need drawing.
- A texture coordinate walks through source data using precomputed deltas.
- Each source sample is converted to a planar mask/pattern by table lookup.
- The destination is a scratch page, not the visible screen.
- The final scratch rectangle is copied to VGA during the page update.
This is exactly the kind of 386 texture mapper that survives by moving work out of the frame loop. Expensive choices are turned into tables. The runtime hot path is mostly: load sample, table lookup, OR word, add row stride.
TMAP Projection Side
The strip loop is fed by several helper routines:
5ba7h transforms vertices using sine/cosine tables
5c0fh perspective-projects coordinates with signed division
5906h chooses edge order and prepares strip parameters
5a08h draws the projected vertical strip
So the flow is:
object/texture state
-> transform
-> perspective projection
-> edge/strip setup
-> vertical strip inner loop
-> scratch-to-page copy
-> CRTC page flip
The result is not a general polygon engine in the modern sense. It is a specialized mapper shaped around the exact visual it needs to draw, with planar VGA constraints baked into the lookup tables.
Inner Loop 2: PIXEL.COD Bresenham Planar Plotter
PIXEL enters at cbf4h. It saves the loader sync pointer, stores the
loader-provided scratch segment in GS, sets ES=A000h, builds a 40-byte row
table, and initializes the VGA palette and CRTC.
The renderer has three major phases:
1. Transform 3D points through sine/cosine tables.
2. Project them into screen-space x/y coordinates.
3. Draw line lists with planar Bresenham routines.
The dirty-byte strategy is important. Every time the plotter touches a screen
byte, it stores that byte's address in GS:[bp]. On the next frame the module
can erase only those bytes instead of clearing the whole page.
One of the x-major line routines has this structure:
; x-major, positive x direction, positive y direction
di = row_table[y0] + (x0 >> 3)
cx = abs(dx) + 1
si = abs(dy)
bx = abs(dx)
err = bx >> 1
al = bit_mask_for_x0
line_loop:
es:[di] |= al ; set one planar pixel bit
gs:[bp] = di ; remember dirty byte
bp += 2
ror al,1 ; next x bit in this byte
if carry:
di += 1 ; crossed into next screen byte
err += si
if err >= bx:
err -= bx
di += 28h ; next scanline
loop line_loop
The code has four variants:
x-major, x increasing
x-major, x decreasing
y-major, x increasing
y-major, x decreasing
That avoids branches inside the pixel loop for sign and major-axis decisions. The setup routine selects the correct routine before entering the hot loop.
This is a textbook 1994 planar line renderer:
- Keep the screen row stride as
28hbytes. - Keep x movement as bit rotation inside a byte.
- Move to the next/previous byte only when the bit rotation wraps.
- Move vertically by adding or subtracting
28h. - Use an error accumulator for the minor axis.
- Record dirty bytes for cheap clearing.
PIXEL is therefore not a generic "plot pixels" label. It is a real vector
object renderer built around the weirdness of planar VGA memory.
Inner Loop 3: DOOM.COD Generated Vertical Scaler
DOOM enters at c123h. Unlike many other modules, it starts from BIOS mode
13h and then tweaks VGA:
mov ax,0013h
int 10h
; then Sequencer/CRTC changes, page setup, DAC upload
SHOW.TXT says the Doom effect picture was scanned. The data and code match
that description: this part is a picture scaler that produces a Doom-like wall
motion, not a raycaster.
The most interesting routine is the code generator at c56ch. It emits tiny
copy routines into memory. Each generated routine is a sequence of:
mov al,[si+source_displacement]
mov es:[di+dest_displacement],al
followed by retf.
Conceptually:
for each scale_level:
code_ptr = output_code_area
for each destination sample in this vertical column:
emit "mov al,[si+src]"
emit "mov es:[di+dst],al"
emit "retf"
scale_table[scale_level] = code_ptr
Then the visible frame renderer can choose a scale level and call the already generated codelet instead of interpreting a scaler loop every time.
The frame side does roughly this:
build/adjust column boundary arrays
for each visible x column:
choose VGA write mask / page
choose scale codelet
call generated far routine
clear top/bottom overdraw if needed
flip or scroll page with CRTC start
This is why the part has a large COD file and a comparatively smaller
DAT file. The image data is important, but the visual speed comes from
runtime-generated specialized column copy code.
The generated-scaler approach is especially suited to 386 real mode:
- It avoids a branch-heavy generic scaler inner loop.
- It keeps the hot path as straight-line
movpairs. - It lets each scale level have exactly the source/destination offsets it needs.
- It works well with a scanned source picture because the source is fixed.
Inner Loop 4: COPPER.COD Raster Timing
COPPER is tiny enough to understand as a complete effect. It starts by:
1. Setting graphics mode 0Dh.
2. Clearing DAC entries.
3. Setting CRTC offset register 13h to 28h.
4. Initializing BIOS/VGA palette state.
5. Finding the `BODY` marker in its ILBM-style data.
The setup routine extracts palette bytes from the data and shifts them down to VGA's 6-bit DAC range. It also initializes a rotating plane mask.
The frame loop combines music/timer sync, display-start changes, horizontal timing, Attribute Controller writes, and palette fade. The core timing part is:
mov dx,3dah
wait_vblank_or_retrace:
in al,dx
test al,08h
jz wait_vblank_or_retrace
wait_horizontal:
in al,dx
test al,01h
jz wait_horizontal
mov dx,3c0h
mov al,33h
out dx,al
mov al,low(scroll_or_phase) & 7
out dx,al
After that it streams palette values:
mov dx,3c8h
xor al,al
out dx,al ; start at DAC index 0
inc dx ; 3c9h
for i in 0..2fh:
al = palette[i] - fade
if al < 0: al = 0
out dx,al
On the Amiga, a copper list can change registers at specific raster positions without CPU involvement. On VGA, this code does it by burning CPU cycles in polling loops. The visual idea is the same: change color/scroll state while the display is being scanned.
Inner Loop 5: TRON.COD Grid And Page Flip
TRON is small and very direct. Its setup routine selects all VGA planes, then
draws a static line/grid frame into two memory regions:
A000: first page
A800: second page
The grid setup writes repeated ffffh line patterns at fixed offsets and then
places vertical markers every 28h bytes, matching the planar row stride.
The frame loop changes CRTC display start registers:
mov dx,3d4h
mov al,0ch
out dx,al
inc dx
mov al,display_start_high
out dx,al
dec dx
mov al,0dh
out dx,al
inc dx
mov al,display_start_low
out dx,al
This is a cheap way to move or flip the visible page without copying the entire screen. The expensive grid drawing is done once; the CRTC start address makes the image appear to move.
Picture And Color Cycling Parts
Several data files are recognizably Amiga-style image containers:
ART.DAT FORM / PBM / BMHD / CRNG / BODY
CRED.DAT FORM / PBM / BMHD / CRNG / BODY
CYC.COD FORM / PBM / BMHD / CRNG / BODY markers in code/data path
INTER.DAT FORM / ILBM / BMHD / BODY
NOTJUST.DAT FORM / ILBM / BMHD / BODY
STOP.DAT FORM / ILBM / BMHD / BODY
THE.DAT FORM / PBM / BMHD / CRNG / BODY
COPPER.DAT FORM / ILBM / BMHD / BODY
The presence of CRNG is a strong clue. In IFF pictures, CRNG chunks describe
color cycling ranges. A PC demo can emulate that by rotating DAC palette entries
instead of redrawing pixels. That is why some of these modules have tiny code
and very large data: the animation is mostly palette manipulation.
CYC is the most explicit color-cycle module. It has many CRNG markers and a
larger code body, so it likely parses or applies multiple color ranges rather
than just showing one page.
Why The Demo Feels Fast On A 386
The demo repeatedly uses the same performance strategy:
precompute tables
specialize the renderer to the exact effect
draw into a scratch page
copy or page-flip during retrace
avoid full clears when a dirty list is enough
let the VGA hardware handle planes, masks, starts, and palette
Examples:
TMAPprecomputes planar masks and texture deltas, then its hot loop only samples, looks up, ORs, and advances by28h.PIXELchooses one of four Bresenham variants before drawing so the hot loop does not pay sign/axis branches.DOOMgenerates straight-line scaler routines before the frame loop.COPPERuses hardware timing and palette changes instead of redrawing the whole image.CYCand picture parts animate via DAC changes when the image format allows it.
This is also why the code is difficult to read as a single "engine". It is not one engine. It is a collection of specialized inner loops that share only the loader, music, keyboard, and some VGA idioms.
The Most Important Takeaways
Show is a good example of late real-mode DOS demo engineering. It has no big
protected-mode framework and no single clean renderer abstraction. Instead it
uses a small loader to sequence many hand-built effects, each with its own
tables and VGA assumptions.
The strongest inner loops are:
TMAP table-driven planar texture strip renderer
PIXEL dirty-list Bresenham vector/pixel renderer
DOOM generated vertical scaler for scanned-picture wall motion
COPPER retrace-polled VGA palette/raster trick
TRON page-start grid movement using CRTC display start
The weaker, asset-heavy parts are still important to the pacing, but their code mostly says "decode/display/cycle this prepared image" rather than exposing a new renderer. The real technical signature of the demo is the way Maxwood combines those prepared visuals with a few very concentrated hand-optimized inner loops.