Analysis model: gpt-5.5 xhigh

Evolution Graffiti by Spartacus - Technical Dissection

Evolution Graffiti is a 1986 MS-DOS cracktro / graffiti screen by Spartacus. Demozoo lists it as released on 1 March 1986 for MS-DOS, with Spartacus credited for the crack code.

Release year: 1986

This is a source-limited entry. I have the public Demozoo production page, the three public 320x200 screenshots referenced by that page, and the external Defacto2 record, but I do not have a verified executable for this pass. The analysis therefore does not claim exact offsets, labels, bytes, or a recovered timeline. It describes what the frames prove, then gives bounded 8088/CGA loop models that fit those frames.

The later public screenshots contain old contact-style text. The article uses one safe title frame and one redacted later frame; unredacted contact-bearing frames were kept out of the public article assets.

Sources

Two later Demozoo carousel frames were reviewed for the redacted derivative image used below. They are not linked directly here because they contain old contact-style text.

Demozoo records:

Title:        Evolution Graffiti
Group:        Spartacus
Type:         Cracktro
Platform:     MS-Dos
Released:     1 March 1986
Credit:       Spartacus - Code (crack)

Screenshot hashes used while preparing the public-safe images:

b422944cbd23169400ec4a6725d3a03b2eae995bc07bd55e3cdffe705236e83b  images/evolution-graffiti-demozoo-01.png
158478f37daf4653ff2b07e1cec1ea79b54acbe1b875f45c4c151bc0030053e1  images/evolution-graffiti-demozoo-03-redacted.png
0cb336a6204d3dab29ef00b6d8cf7b675405e5a063338f257b71ced16ea7e811  images/evolution-graffiti-layout-map.png

Visual References

Time Image Notes
00:00.000 public still Evolution Graffiti first public Demozoo frame First public Demozoo frame. Black background, orange SPARTACUS / PRESENTS text, and a green/orange oversized EVOLUT... logo region.
00:00.000 later public still Evolution Graffiti later public Demozoo frame with contact text redacted Later public Demozoo frame with old contact-style text masked. The same composition has shifted to a grey/cyan palette and the full EVOLUTION word is visible.

Evolution Graffiti CGA bitmap and loop model

What The Frames Prove

The screenshots are 320 by 200 pixels. That is the native CGA low-resolution graphics footprint, and the composition fits a direct 320x200 bitmap rather than an 80x25 text page:

The important consequence is that the hot loops are almost certainly CGA graphics memory loops, not B800h character/attribute word loops.

The three public frames also prove a state change:

early frame:  orange/green palette, large word not fully revealed
later frame:  grey/cyan palette, full word visible, bottom line present

That can be produced in two plausible ways:

  1. Draw or copy different prebuilt 320x200 pages.
  2. Keep the same 2-bit pixel indices and change the CGA palette latch while a reveal loop fills more of the large logo.

The second model is more code-economical for 1986 and explains why the same layout survives the colour change.

Visual-To-Code Concordance

This concordance is bounded to the public-safe images and the CGA memory model. No executable was acquired for this pass, so the links below describe the visible renderer classes, not recovered function addresses.

The first Demozoo still proves a graphics-mode page, not a text-mode splash:

visible image:      evolution-graffiti-demozoo-01.png
frame size:         320x200
display model:      CGA mode 04h-style 2bpp graphics
memory target:      B800h split odd/even scanline banks
proved behavior:    black clear, centered bitmap headings, partial logo state
bounded inference:  full-page copy, span table, or clipped logo reveal

The oversized EVOLUT... region is the important clue. It has broad irregular letter strokes and pixel-level edges, so the hot path cannot be a character/attribute writer. The useful primitive is a CGA byte/span writer: build a row base for the current scanline, align to four-pixel byte groups, write 00h/55h/AAh/FFh runs for solid colour, and patch the unaligned ends with 2-bit masks.

The later redacted frame ties the same layout to a state update:

visible image:      evolution-graffiti-demozoo-03-redacted.png
same geometry:      heading region + giant logo + lower note region
changed evidence:   full word visible, different apparent palette,
                    additional bottom text region
proved behavior:    either page/state switch or reveal completion
bounded inference:  CGA palette latch write plus bottom-line draw is cheaper
                    than repainting a separate full-colour page

Because the geometry remains stable while the apparent colours change, the most economical 1986 reading is stable 2-bit pixel indices plus a CGA colour select update. The palette step is technically tiny:

out 03D9h, first_colour_select
draw/reveal logo
out 03D9h, later_colour_select
draw lower line or copy lower text band

That does not prove the exact register values, but it explains why the visible composition can change colour without any large bitmap rewrite.

The generated layout map is therefore a renderer map, not a recovered call graph:

visible image:      evolution-graffiti-layout-map.png
screen regions:     headings, giant logo, lower note/contact area
core loops:         clear/copy, CGA row-base calculation, span writer,
                    optional tile/strip reveal, palette latch, delay/key gate
not proved here:    archive layout, exact reveal order, exact timing, sound

The strongest source-limited bridge is:

public title still
  -> mode set + black clear + bitmap-font headings + partial CGA logo reveal

later redacted still
  -> same page geometry + completed/replaced logo state + palette remap
     + lower text-region writer/copy

layout map
  -> split-bank B800h addressing and span/tile loops needed by both frames

This keeps the analysis appropriately small for an early graffiti screen while still identifying the real technical content: packed 2bpp CGA addressing, wide-span writes, palette-state changes, and a simple reveal or page-state transition.

CGA Memory Model

The most likely display mode is 320x200 4-colour CGA graphics. In that mode, one byte contains four horizontal pixels, two bits per pixel:

bit pair 7..6 -> pixel x + 0
bit pair 5..4 -> pixel x + 1
bit pair 3..2 -> pixel x + 2
bit pair 1..0 -> pixel x + 3

Rows are split between two 8 KiB banks:

even scanlines: B800:0000 + (y / 2) * 80 + (x / 4)
odd  scanlines: B800:2000 + (y / 2) * 80 + (x / 4)

The address formula is:

offset = ((y & 1) * 0x2000) + ((y >> 1) * 80) + (x >> 2)
shift  = 6 - ((x & 3) * 2)
mask   = 0x03 << shift
value  = (colour & 3) << shift

That odd/even layout shapes every useful inner loop. Any full-screen operation has to either visit all even rows and then all odd rows, or recompute the bank for each scanline.

Screen Parts

The screen can be divided into four functional regions:

y  18.. 55   small centered "SPARTACUS" heading
y  57.. 78   small centered "PRESENTS" heading
y  82..166   oversized "EVOLUTION" logo
y 172..194   optional bottom note / contact-style line, redacted here

The top headings and the giant logo are probably not separate "effects" in the modern demo sense. They are display-list items on a static CGA page:

The word "effect" here is mostly the reveal and palette state, not a per-frame 3D or procedural renderer.

Mode Setup

A minimal direct-CGA setup is:

mov  ax,0004h       ; CGA 320x200 graphics, 4 colours
int  10h
mov  dx,03D9h       ; CGA colour select register
mov  al,palette
out  dx,al

Many 1986 programs use BIOS for the mode switch and direct port writes for the colour latch. A pure BIOS path is also possible:

mov  ax,0004h
int  10h
mov  ah,0Bh         ; set background / palette
mov  bh,00h
mov  bl,palette_id
int  10h

The direct port path is the better fit for a cracktro because changing the palette during a reveal is a one-byte output:

mov  dx,03D9h
mov  al,next_cga_colour_select
out  dx,al

The visible orange/green and grey/cyan states do not require repainting the logo if the 2-bit pixels are stable. Changing the CGA palette changes how those indices are interpreted.

Full-Screen Clear Loop

The displayed CGA area is 16000 bytes: 8000 bytes for even rows and 8000 bytes for odd rows. The physical banks are 8192 bytes apart, so each displayed bank also leaves a small unused tail.

The direct clear is two REP STOSW runs:

mov  ax,0B800h
mov  es,ax
xor  ax,ax
cld

xor  di,di          ; even-row bank
mov  cx,4000        ; 8000 bytes
rep  stosw

mov  di,2000h       ; odd-row bank
mov  cx,4000
rep  stosw

Expanded inner loop:

for i in 0..3999:
    B800:0000 + i*2 = 0000h

for i in 0..3999:
    B800:2000 + i*2 = 0000h

That writes colour index 0 to every pixel. The frames have a black background with no texture or dither, so a zero fill is sufficient.

Whole-Page Copy Loop

If the art was stored as a complete packed page in system memory, the display copy would be similarly simple:

mov  ax,0B800h
mov  es,ax
mov  ds,art_segment
cld

mov  si,even_page
xor  di,di
mov  cx,4000
rep  movsw

mov  si,odd_page
mov  di,2000h
mov  cx,4000
rep  movsw

The real cost is 16000 bytes copied. On an 8088 that is not free, but for a static cracktro title it is still acceptable. If the program only shows one or two still states, storing full pages is simpler than building a complicated shape renderer.

The drawback is size. Three full CGA pages would be about 48 KiB before compression. A cracktro of this age is more likely to store logo masks, spans, or simple RLE blocks unless the host file already has room.

Packed Pixel Plot

The smallest general pixel writer for CGA 2bpp graphics looks like this in logic:

offset = ((y & 1) << 13) + ((y >> 1) * 80) + (x >> 2)
shift  = 6 - 2 * (x & 3)
mask   = 3 << shift
old    = B800[offset]
new    = (old & ~mask) | ((colour & 3) << shift)
B800[offset] = new

In assembly, the multiplication by 80 is usually avoided inside the deepest loop. The caller prepares a row pointer:

; BX = row base, x advances horizontally
mov  di,bx
mov  cl,x_mod_shift
mov  al,es:[di]
and  al,not mask
or   al,colour_shifted
mov  es:[di],al

This is too slow for large filled areas if called per pixel. It is useful for edges and small glyph dots, but not for the body of the EVOLUTION logo.

Horizontal Span Inner Loop

The giant logo is made from large filled letter strokes. A span renderer is a better fit than per-pixel plotting.

For a solid horizontal run from x0 to x1 at scanline y:

colour_byte = colour * 0x55

while x <= x1 and (x & 3) != 0:
    write one masked 2-bit pixel
    x += 1

while x + 3 <= x1:
    B800[row_base + x/4] = colour_byte
    x += 4

while x <= x1:
    write one masked 2-bit pixel
    x += 1

The hot center loop is one byte store per four pixels:

; DI points to first fully aligned byte
; AL is 00h, 55h, AAh, or FFh for colour 0,1,2,3
; CX is aligned byte count
rep  stosb

That is the loop that matters for the heavy logo area. Most of the scanline is handled by REP STOSB; only the two unaligned ends need read/modify/write.

The required tables are tiny:

pair_mask[4]  = C0h, 30h, 0Ch, 03h
pair_shift[4] = 6,   4,   2,   0
solid[4]      = 00h, 55h, AAh, FFh

Logo Stroke Representation

The source data for the large logo could be stored as per-scanline spans:

record:
    y
    count
    repeated count times:
        x0
        x1
        colour

The renderer is then:

for record in logo_records:
    row_base = cga_row_base(record.y)
    for each span in record:
        draw_solid_span(row_base, span.x0, span.x1, span.colour)

For the EVOLUTION logo this representation is efficient because the letters are broad and mostly horizontal on each scanline. It also makes a reveal easy: clip every span against a moving reveal column.

visible_x1 = reveal_column
clipped_x1 = min(span.x1, visible_x1)
if clipped_x1 >= span.x0:
    draw_solid_span(row_base, span.x0, clipped_x1, span.colour)

That exactly fits the difference between a partial EVOLUT... public still and a later full EVOLUTION still, without requiring separate art for every intermediate state.

Left-To-Right Reveal Loop

A likely reveal loop is:

reveal_column = 0
while reveal_column < 320:
    wait_for_tick_or_retrace()
    draw_logo_clipped_to(reveal_column)
    reveal_column += step

A more efficient version does not redraw the whole clipped logo every time. It only draws the newly exposed vertical strip:

old_column = reveal_column
reveal_column += step
for each logo span:
    x0 = max(span.x0, old_column + 1)
    x1 = min(span.x1, reveal_column)
    if x0 <= x1:
        draw_solid_span(row_base(span.y), x0, x1, span.colour)

This turns each frame into "draw the next band" rather than "redraw everything seen so far". In 1986 terms, this matters because the 8088 bus is slow and CGA video memory writes are expensive.

If step is 4 pixels, most writes are byte-aligned. If step is 8 or 16 pixels, the inner loop becomes almost entirely REP STOSB.

Tile Reveal Variant

The public frames do not prove that the reveal is column-perfect. It could also reveal 8x8 or 8x4 tiles. A tile representation would look like:

for tile_index in reveal_order:
    source = logo_bitmap + tile_index * tile_size
    target = cga_tile_address(tile_x, tile_y)
    copy_tile(source, target)

For an 8x8 tile in 320x200 CGA, each row is 2 bytes wide:

8 pixels / 4 pixels per byte = 2 bytes per row
8 rows                       = 16 bytes per tile

The tile copy must still hop between CGA banks for odd and even scanlines:

for row in 0..7:
    y = tile_y + row
    dst = ((y & 1) << 13) + ((y >> 1) * 80) + (tile_x >> 2)
    copy 2 bytes

This is more pointer-heavy than a span reveal, but it allows a small masked bitmap logo and a custom reveal order.

Small Heading Text

The SPARTACUS and PRESENTS headings can be drawn in three ranked ways:

  1. Prebaked as part of a full-page or logo bitmap.
  2. Drawn with a small custom bitmap font.
  3. Drawn by BIOS graphics text after setting CGA mode.

The custom-font loop is the most typical cracktro compromise. A one-bit 8x8 font is expanded into 2bpp CGA pixels:

for each character in string:
    glyph = font[character]
    for gy in 0..7:
        bits = glyph[gy]
        y = base_y + gy
        row_base = cga_row_base(y)
        for gx in 0..7:
            if bits & (0x80 >> gx):
                plot_or_span_scaled_pixel(base_x + gx, row_base, colour)
    base_x += glyph_advance

If the font is unscaled, the inner loop can expand one glyph byte through a lookup table:

expanded[256][2] -> two CGA bytes for eight monochrome pixels

Then each glyph row becomes:

lodsb                 ; AL = one 8-pixel font row
xlat                  ; or table lookup to two bytes
mov  es:[di],ax       ; store two CGA bytes

With a two-byte expansion table, an eight-character word costs only 16 bytes of video writes per scanline.

Centering Text

The headings are centered, so the draw routine probably computes:

pixel_width = len(text) * glyph_advance
x = (320 - pixel_width) / 2

The 8088-friendly version avoids division by using a right shift:

mov  ax,320
sub  ax,pixel_width
shr  ax,1

For a fixed title string, the x coordinate may simply be a constant in the display list:

draw_string x=112, y=24, colour=2, "SPARTACUS"
draw_string x=124, y=57, colour=2, "PRESENTS"

The visible spacing does not prove dynamic centering. Constant coordinates are more likely if the intro only draws this one page.

Palette Remap

CGA 320x200 graphics has only 2-bit pixel indices. The colour select register chooses how those indices appear. A palette change can be done without touching video memory:

mov  dx,03D9h
mov  al,orange_green_state
out  dx,al

; later
mov  al,cyan_white_state
out  dx,al

The practical inner loop is not a loop at all; it is a single I/O write. This is why palette remapping was attractive on CGA even before VGA-style DAC cycling existed.

If the frames are from separate program states, the order could be:

draw logo using stable indices
wait
change palette latch
draw bottom line
wait

Because the public stills show the same composition with different colours, palette state is a stronger explanation than repainting the entire screen in different literal colour indices.

Retrace Wait

To keep the reveal stable, the program can wait for vertical retrace through the CGA status port:

mov  dx,03DAh

wait_out:
in   al,dx
test al,08h
jnz  wait_out

wait_in:
in   al,dx
test al,08h
jz   wait_in

That waits for a clean frame boundary:

leave any current retrace
wait until the next retrace begins
perform one reveal or palette step

The screenshots cannot prove retrace pacing, but it is the natural way to make a CGA reveal look deliberate instead of tearing through a batch of writes as fast as the CPU allows.

Keyboard Or Delay Gate

A cracktro page usually does one of three things after the title:

The keyboard-poll loop is:

mov  ah,01h
int  16h
jz   no_key
mov  ah,00h
int  16h
jmp  continue_or_exit

The BIOS tick delay is:

mov  ah,00h
int  1Ah             ; CX:DX = timer ticks since midnight
add  dx,delay_ticks

wait_tick:
mov  ah,00h
int  1Ah
cmp  dx,target_dx
jb   wait_tick

For 1986, BIOS timing is adequate. A custom PIT interrupt would be unnecessary for a single static graffiti page.

Probable Runtime Order

A compact 1986 implementation could be:

1. Save original video mode if the author cared about clean exit.
2. Set CGA 320x200 4-colour mode.
3. Select the first palette state.
4. Clear B800h graphics memory.
5. Draw the small heading strings.
6. Reveal the large `EVOLUTION` logo with clipped spans or tiles.
7. Change palette state for the later frame.
8. Draw the bottom note line.
9. Wait for key or timeout.
10. Exit, return to DOS, or continue to the cracked program.

The three public stills support steps 2 through 8. Steps 1, 9, and 10 are standard cracktro control flow but not directly visible in the screenshots.

What Is Not Proven

The public evidence does not prove:

The safe conclusion is narrower: Evolution Graffiti is an early MS-DOS CGA-bitmap cracktro page whose visible complexity is in packed 2bpp screen memory, palette state, and probably a simple reveal loop rather than in later demo-style 3D, plasma, or sound-synchronized part scheduling.