# Copper Effects — Bars, Raster Splits, Gradients, and Sine Cycling
## Overview
The Copper is the single most important tool in the demoscene coder's arsenal. With only three instructions — `WAIT`, `MOVE`, and `SKIP` — it can repaint the entire screen 50 times per second, changing color registers, bitplane pointers, scroll offsets, and sprite positions at exact scanline boundaries. Every iconic Amiga demo effect, from the rainbow copper bars in [Red Sector's **Megademo**](https://www.pouet.net/prod.php?which=3119) (1989) to the sinus-scrolling message waves in [**Desert Dream**](https://www.pouet.net/prod.php?which=1483) (1993, [Demozoo](https://demozoo.org/productions/142/)), traces back to someone figuring out how to make the Copper do something Commodore's engineers never intended.
This article covers the specific techniques demoscene coders developed for the Copper: classic copper bars, raster splits for multi-resolution screens, gradient shading, sine-based color cycling, and advanced tricks like copper-generated chunky pixels and mid-frame copper list swaps. For the Copper's hardware architecture and basic programming model, see [Copper](../08_graphics/copper/copper.md) and [Copper Programming](../08_graphics/copper/copper_programming.md).
The following screenshots from [Pouet.net](https://www.pouet.net) show copper effects in landmark demoscene productions. Each captures a single frame of effects that are typically animated at 50 Hz.
| `WAIT` | 2 | 2 | Stalls until beam reaches position |
| `MOVE` | 2 | 2 | Writes a value to a register |
| `SKIP` (AGA) | 2 | 2 | Conditional skip of next instruction |
| `WAIT + MOVE` | 4 | 4 | The basic unit of most effects |
> [!IMPORTANT]
> Each `WAIT` + `MOVE` pair costs **4 DMA slots per scanline**. The Copper gets ~226 available slots per LoRes scanline (after bitplane, sprite, and audio DMA). This means roughly **56 color register writes per scanline** maximum — the practical budget for copper effects.
---
## Technique 1: Copper Bars
The classic Amiga demo effect. Copper bars are horizontal bands of color created by writing different values to `COLOR00` (or any color register) at each scanline. The result is a series of colored stripes across the screen.
### How It Works
```mermaid
sequenceDiagram
participant Beam as Video Beam
participant Copper as Copper
participant CR as COLOR00 $DFF180
Note over Beam: Scanline 50
Beam->>Copper: Beam reaches (50,0)
Copper->>CR: MOVE #$0F00 → dark blue
Note over Beam: Scanline 51
Beam->>Copper: Beam reaches (51,0)
Copper->>CR: MOVE #$0FF0 → cyan
Note over Beam: Scanline 52
Beam->>Copper: Beam reaches (52,0)
Copper->>CR: MOVE #$0FFF → white
Note over Beam: Scanline 53
Beam->>Copper: Beam reaches (53,0)
Copper->>CR: MOVE #$0FF0 → cyan
Note over Beam: Scanline 54
Beam->>Copper: Beam reaches (54,0)
Copper->>CR: MOVE #$0F00 → dark blue
```
### Complete Copper Bar Example
> 
>
> *Angels' Copper Master (1990) — the definitive copper bar demonstration, showing dozens of simultaneous color bands.*
```asm
; copper_bars.asm — Classic copper bars (OCS/ECS)
; Assembles with vasm -m68k -Fbin -o copper.bars copper_bars.asm
int r = ((grad >> 8) & 0xF) * hue_r[bar] * brightness / (15 * 255);
int g = ((grad >> 4) & 0xF) * hue_g[bar] * brightness / (15 * 255);
int b = (grad & 0xF) * hue_b[bar] * brightness / (15 * 255);
/* Clamp to 0-15 */
if (r > 15) r = 15;
if (g > 15) g = 15;
if (b > 15) b = 15;
move_ptr[1] = RGB(r, g, b); /* Patch the MOVE data word */
copper_ptr += 4; /* Advance past WAIT+MOVE pair */
}
}
}
```
---
## Technique 2: Raster Splits
A raster split changes display parameters mid-frame. The most common use is a **status bar** at the top of the screen (fixed resolution) with a scrolling game area below (different bitplane pointers, scroll offset, or even resolution).
### Split-Screen Architecture
> 
>
> *Budbrain Megademo (1990) — copper bars and raster splits used to create multiple display regions.*
```mermaid
graph TB
subgraph "Frame Layout"
TOP["Lines 0-49:<br/>Title Bar (HiRes, 4 bitplanes)"]
GAME["Lines 50-249:<br/>Game Area (LoRes, 5 bitplanes, scrolling)"]
BOT["Lines 250-311:<br/>Bottom Border / VBlank"]
end
subgraph "Copper Actions"
C1["Copper at line 0:<br/>Set BPLCON0=$9200 (HiRes 4bpp)"]
C2["Copper at line 50:<br/>Set BPLCON0=$D200 (LoRes 5bpp)<br/>Set BPLCON1 scroll<br/>Swap bitplane pointers"]
C3["Copper at line 250:<br/>Kill display (DIWSTOP)"]
end
C1 --> TOP
C2 --> GAME
C3 --> BOT
```
### Split-Screen Copper List
```asm
; raster_split.asm — Status bar + scrolling game area
; Top 44 lines: HiRes 4-bitplane title bar
; Bottom 200 lines: LoRes 5-bitplane scrolling game
COPPER_SPLIT:
; ---- Top section: HiRes title bar ----
dc.w $8001,$FFFE ; WAIT line 1
dc.w $0100,$9200 ; BPLCON0: HiRes, 4 bitplanes, color on
; Set bitplane pointers for title bar image
dc.w $00E0,title_bpl1 ; BPL1PTH
dc.w $00E2,title_bpl1+2 ; BPL1PTL (word-aligned)
dc.w $00E4,title_bpl2
dc.w $00E6,title_bpl2+2
dc.w $00E8,title_bpl3
dc.w $00EA,title_bpl3+2
dc.w $00EC,title_bpl4
dc.w $00EE,title_bpl4+2
dc.w $0108,$0000 ; BPL1MOD = 0 (no modulo for HiRes)
dc.w $010A,$0000 ; BPL2MOD = 0
; ---- Split point: switch to game area ----
dc.w $802C,$FFFE ; WAIT line 44
dc.w $0100,$D200 ; BPLCON0: LoRes, 5 bitplanes, color on
; Swap bitplane pointers to game bitmap
dc.w $00E0,game_bpl1
dc.w $00E2,game_bpl1+2
dc.w $00E4,game_bpl2
dc.w $00E6,game_bpl2+2
dc.w $00E8,game_bpl3
dc.w $00EA,game_bpl3+2
dc.w $00EC,game_bpl4
dc.w $00EE,game_bpl4+2
dc.w $00F0,game_bpl5
dc.w $00F2,game_bpl5+2
dc.w $0102,$0000 ; BPLCON1: scroll = 0 (updated per frame)
dc.w $0108,$0000 ; BPL1MOD (updated per frame)
dc.w $010A,$0000 ; BPL2MOD
; ---- End of display ----
dc.w $FFFF,$FFFE ; WAIT forever
```
---
## Technique 3: Gradient Shading
Gradient shading creates smooth color transitions — the signature "Amiga sky" effect. The technique writes `COLOR01`–`COLORxx` progressively at each scanline, creating a smooth ramp from one color to another.
The demoscene rarely uses static gradients. Instead, color values are driven by sine tables with different phase offsets, creating fluid wave effects. The key insight: **use multiple sine waves with different frequencies and phases**, then combine them.
### Sine Table Generation
```c
/* sine_gen.c — Generate a 256-entry sine table (0-255 range) */
/* In practice, this is pre-computed at build time */
> *Phenomena's Enigma (1991) — multi-wave sine copper cycling creating a water/plasma effect, combined with filled-vector rendering.*
### Sine Scrolling (Scrolling Sinus)
One of the most iconic effects: a text message that scrolls across the screen in a sine wave pattern. This is achieved by changing `BPLCON1` (scroll offset) or `BPLxMOD` (modulo) per scanline:
; copper_mod_points[] points to the BPLCON1 MOVE data words
scroll_sine_text:
move.l scroll_phase,d0
addq.l #2,d0 ; Advance phase
move.l d0,scroll_phase
lea sine_table,a0
lea copper_mod_points,a1 ; Array of ptrs to MOVE data words
move.w #NUM_SCROLL_LINES-1,d1
.next_line:
move.b (a0,d0.w),d2 ; Get sine value
lsr.w #4,d2 ; Scale to 0-15 scroll range
move.w d2,(a1)+ ; Patch copper MOVE data word
addq.w #4,d0 ; Advance phase per line
dbra d1,.next_line
rts
```
---
## Technique 5: Double-Buffered Copper Lists
Advanced effects swap the active copper list mid-frame. The Copper hardware reads a `COP1LC` register to know where its list starts. By changing `COP1LC` during vertical blank, or even mid-frame, you can chain multiple copper lists together:
```mermaid
sequenceDiagram
participant VBI as VBlank Interrupt
participant CPU as 68000 CPU
participant Copper as Copper
participant COP1LC as COP1LC Register
Note over VBI: Frame N starts
VBI->>CPU: Level 3 interrupt
CPU->>COP1LC: Set to copper_list_A
CPU->>CPU: Animate list A colors
Copper->>Copper: Execute list A (top half of screen)
Note over Copper: List A ends with<br/>MOVE COP1LC → copper_list_B
Copper->>COP1LC: Self-jump to list B
CPU->>CPU: Animate list B colors
Copper->>Copper: Execute list B (bottom half)
```
### Copper List Chaining
```asm
; double_copper.asm — Chain two copper lists in one frame
LIST_A:
; First half of screen: copper bars
dc.w $8032,$FFFE ; WAIT line 50
dc.w $0180,$0F00 ; COLOR00 = blue
; ... more bars ...
dc.w $8080,$FFFE ; WAIT line 128
; Chain to LIST_B: write COP1LCH and COP1LCL
dc.w $0080,LIST_B>>16 ; COP1LCH = high word of LIST_B
dc.w $0082,LIST_B&$FFFF ; COP1LCL = low word
dc.w $0088,0 ; COPJMP1 — trigger jump (strobe)
dc.w $FFFF,$FFFE ; Safety WAIT
LIST_B:
; Second half: different effects
dc.w $8080,$FFFE ; WAIT line 128
dc.w $0180,$000F ; COLOR00 = red
; ... more effects ...
dc.w $FFFF,$FFFE ; End
```
---
## Technique 6: Self-Modifying Copper Lists
Rather than pre-building the entire copper list, the CPU patches it in real-time during vertical blank. This is how most demoscene effects work — the copper list is a template with placeholder values that get overwritten each frame:
```c
/* smc_copper.c — Self-modifying copper list for animated effects */
/* Copper list with placeholders (marked 0xDEAD) */
/* Indices of color data words (every 4th word starting at offset 3) */
#define BAR1_START 3 /* Index of first color word for bar 1 */
#define BAR2_START 13 /* Index of first color word for bar 2 */
#define BAR_LEN 5 /* Entries per bar */
void patch_copper_bars(ULONG frame) {
int i;
/* Animate bar 1 with sine wave */
for (i = 0; i <BAR_LEN;i++){
int phase = (frame * 4 + i * 20) & 0xFF;
int bright = sine_table[phase] >> 4; /* 0-15 */
copper_template[BAR1_START + i * 4] = RGB(bright, bright/2, 0);
}
/* Animate bar 2 with different phase */
for (i = 0; i <BAR_LEN;i++){
int phase = (frame * 6 + i * 25 + 128) & 0xFF;
int bright = sine_table[phase] >> 4;
copper_template[BAR2_START + i * 4] = RGB(0, bright/2, bright);
}
}
```
---
## Antipatterns
### 1. The Copper Overflow
Writing too many color registers per scanline. The Copper has limited DMA bandwidth — each `WAIT` + `MOVE` pair costs 4 slots. On a scanline with heavy bitplane DMA (6 planes HiRes), there may be fewer than 20 slots available.
**Broken:**
```asm
; 32 color register writes on one scanline — WILL FAIL
; with 5+ bitplanes active (DMA starvation)
dc.w $8032,$FFFE
dc.w $0180,$0F00 ; COLOR00
dc.w $0182,$0F00 ; COLOR01
dc.w $0184,$0F00 ; COLOR02
; ... 29 more COLOR writes ...
```
**Fixed:**
```asm
; Spread color writes across 2-3 scanlines
dc.w $8032,$FFFE
dc.w $0180,$0F00 ; COLOR00
dc.w $0182,$0F00 ; COLOR01
dc.w $0184,$0F00 ; COLOR02
; ... up to ~10 more is safe with 4 planes LoRes ...
dc.w $8034,$FFFE ; Two lines later
dc.w $0186,$0F00 ; COLOR03
dc.w $0188,$0F00 ; COLOR04
; ... continue on next scanline ...
```
### 2. The Stale Copper List
Forgetting to update the copper list pointer (`COP1LC`) after modifying the list in RAM. The Copper may have already fetched and cached the old instructions.
**Broken:**
```c
/* Modify copper list in RAM but don't tell Copper */
copper_list[offset] = new_color;
/* Copper still reads cached/stale data! */
```
**Fixed:**
```c
copper_list[offset] = new_color;
/* In VBlank: reload copper pointer to flush cache */
custom.cop1lc = (ULONG)copper_list;
/* Or use COPJMP1 strobe to force immediate reload */
```
### 3. The Over-Scanned WAIT
Setting a WAIT position beyond the visible display area. PAL has 313 scanlines (0–312), NTSC has 263 (0–262). A WAIT for line 313 on PAL wraps incorrectly; on NTSC, anything past line 262 never triggers.
**Broken:**
```asm
; Assumes PAL — breaks on NTSC machines
dc.w $8138,$FFFE ; WAIT line 312 (PAL only)
dc.w $0180,$0000 ; Clear color
```
**Fixed:**
```asm
; Use a safe VBlank wait that works on both PAL and NTSC
Writing to a register that the CPU or Blitter is also modifying in the same frame. The Copper runs asynchronously — it can clobber a value the CPU just set.
**Broken:**
```c
/* CPU sets COLOR01 for game object highlighting */
custom.color[1] = 0x0FFF;
/* But the copper list also writes COLOR01 at line 100 */
/* → Copper overwrites the CPU's value */
```
**Fixed:**
```c
/* Reserve specific color registers for CPU and others for Copper */
/* CPU uses COLOR00-COLOR03, Copper uses COLOR04-COLOR31 */
On AGA, the Copper's horizontal position resolution doubles to 8 bits (`$DFF004` BEAMCON0 changes). Using OCS-style horizontal WAIT values produces incorrect timing on AGA hardware. The `BPC` bit in `FMODE` ($DFF1FC) controls whether Copper positions are interpreted as low-res or high-res clock cycles.
**Broken:**
```asm
; OCS copper list used directly on AGA — horizontal timing off
dc.w $8007,$FFFE ; WAIT x=7 on OCS, but AGA reads x=3.5
```
**Fixed:**
```asm
; Set FMODE.BPC=0 for OCS-compatible copper timing
; before activating the copper list
dc.w $01FC,$0000 ; FMODE = 0 (OCS compatibility)
; Or double all horizontal positions for AGA-native mode
dc.w $800E,$FFFE ; WAIT x=14 (AGA: same as x=7 OCS)
```
---
## Decision Guide
```mermaid
flowchart TD
START[Need visual effect] --> Q1{Color only or<br/>structural change?}
> Each color register write requires 1 WAIT + 1 MOVE = 4 slots. The "Max Color Writes" column assumes one write per scanline with a WAIT at the start. Consecutive writes without WAIT only cost 2 slots each (MOVE only), but the Copper must WAIT at least once to synchronize.
---
## Historical Timeline
```mermaid
timeline
title Copper Effects Evolution
1987 : [Scoopex Megademo](https://www.pouet.net/prod.php?which=5832) — first copper bars demo
1988 : [Red Sector Megademo](https://www.pouet.net/prod.php?which=3119) — sine-scrolling text wave
| Metallic logo shine | Fast gradient sweep across logo | [Human Target](https://www.pouet.net/prod.php?which=3459), [Arte](https://www.pouet.net/prod.php?which=1477) |
Copper effects are among the most timing-sensitive code on the Amiga. Accurate emulation requires:
| Concern | Impact | FPGA Notes |
|---------|--------|------------|
| **Cycle-accurate WAIT** | Copper must stall until exact beam position | Minimig/MiSTer implement beam counter compare at cycle granularity |
| **DMA slot allocation** | Copper slots must be reserved correctly after bitplane/sprite DMA | Bus arbiter must interleave correctly |
| **Register write latency** | Copper writes are visible next cycle | Write buffer must not add latency |
| **COPJMP strobes** | List jumps must take effect at exact position | State machine must handle strobe timing |
| **AGA 8-bit horizontal** | FMODE.BPC changes position interpretation | Must track FMODE state at copper fetch time |
| **Self-modifying code** | CPU writes to copper list must be visible to Copper DMA | Requires cache coherency between CPU writes and DMA reads |
> [!WARNING]
> Many WHDLoad patches fix games that relied on specific Copper timing on real hardware. Emulators like WinUAE have "copper timing" settings (exact/default/fast) because some demos only work with specific timing models.
---
## FAQ
**Q: How many color changes can I do per scanline?**
A: On a stock A500 with 4 bitplanes LoRes, approximately 53 WAIT+MOVE pairs per scanline. With 6 bitplanes HiRes, it drops to ~49. Each additional consecutive MOVE (without WAIT) adds 2 slots instead of 4.
**Q: Can the Copper read registers?**
A: No. The Copper has no read capability. It can only WAIT for a beam position and MOVE (write) a value to a register. This is why self-modifying copper lists are done by the CPU writing to Chip RAM — the Copper itself cannot inspect register values.
**Q: What is copper chunky and why is it impressive?**
A: Copper chunky uses the Copper to write `COLOR01` at every pixel position across a scanline, creating a chunky-pixel display without any bitplanes at all. It requires extremely precise timing and works only at low resolution. The technique was most famously used in [Sanity's Arte](https://www.pouet.net/prod.php?which=1477) (1993):
> 
>
> *Sanity's Arte (1993) — full-screen copper chunky: every pixel is a COLOR01 write, no bitplanes used.*
See [pixel_tricks.md](pixel_tricks.md) for the full technique.
**Q: Do copper effects work on AGA?**
A: Yes, with caveats. AGA adds an 8-bit horizontal position (vs OCS 7-bit), controlled by the `BPC` bit in `FMODE`. AGA also has 256-color registers (`COLOR00`–`COLOR255`) instead of 32, allowing much more complex copper effects. However, the higher bandwidth of AGA bitplane DMA leaves fewer slots for the Copper.
A: Yes, via `UCopList` — the user copper list attached to a `ViewPort`. Intuition merges your copper instructions with its own. See [Copper Programming](../08_graphics/copper/copper_programming.md) for the OS-friendly approach. For full copper control (demos), you take over the hardware directly.
**Q: What happens if the Copper runs past the end of a scanline before finishing?**
A: The Copper simply continues executing on the next scanline. There is no error or trap. The WAIT instruction's purpose is to synchronize — if you don't WAIT, the Copper runs as fast as DMA allows. Effects that don't need per-line synchronization can skip WAITs entirely.
- **Amiga Graphics Archive** — https://amiga.lychesis.net/specials/Copper.html — Forensic analysis of copper usage in commercial games (Agony, Bio Challenge, Starray, Wings of Death)
- **Scoopex Amiga Hardware Programming** (Photon) — [YouTube playlist](https://www.youtube.com/playlist?list=PLc3ltHgmiidpK-s0eP5hTKJnjdTHz0_bW) — Video walkthroughs of copper bars, raster splits, and sine effects in 68k assembly. Companion articles: [coppershade.org](http://coppershade.org/articles/)