# Pixel Tricks — Copper Chunky, HAM Art, Scroll Register Abuse, and Modulo Wrapping
## Overview
The Amiga's display hardware was designed for planar bitplane graphics — a memory-efficient scheme that matched the DMA streaming pattern perfectly. But the demoscene never accepted "designed for" as a limitation. From 1989 onward, demoscene coders systematically abused every display register to create effects that Commodore's engineers never imagined: **copper chunky** (no bitplanes at all, just color register writes), **HAM art** (photorealistic images in 4096 colors), **scroll-register distortion** (sine-wave text), and **modulo wrapping** (infinite scrolling surfaces).
These techniques share a common thread: they treat the display registers themselves as the primary rendering surface, not the bitplane memory. The Copper, scroll registers, and modulo values become the "pixels" — a fundamental inversion of the intended programming model.
The most extreme display hack on the Amiga. Copper chunky creates a pixel display **without any bitplanes at all**. The Copper writes to a single color register (`COLOR01`) at every pixel position across each scanline. The result is a "chunky pixel" display where each pixel's color is set directly by the Copper — no bitplane memory, no Blitter, no CPU rendering.
### Why It Works
```mermaid
sequenceDiagram
participant Copper
participant COLOR01 as COLOR01 $DFF182
participant Denise as Denise DAC
Note over Copper: Scanline Y=100, pixel positions:
Copper->>COLOR01: MOVE #$0F00 (blue) at x=0
COLOR01->>Denise: Output blue pixel
Copper->>COLOR01: MOVE #$00F0 (green) at x=2
COLOR01->>Denise: Output green pixel
Copper->>COLOR01: MOVE #$0FFF (white) at x=4
COLOR01->>Denise: Output white pixel
Copper->>COLOR01: MOVE #$0F00 (blue) at x=6
COLOR01->>Denise: Output blue pixel
Note over Copper: ...continues across entire line
```
### Resolution and Bandwidth
Copper chunky resolution is limited by how many MOVE instructions fit per scanline:
> Copper chunky requires **disabling all bitplane DMA** — you cannot display bitplane graphics alongside copper chunky pixels. The technique is mutually exclusive with normal display rendering.
---
## Technique 2: HAM Art
Hold-And-Modify (HAM) mode gives the Amiga 4,096 on-screen colors from only 6 bitplanes. Instead of direct color lookup, HAM encodes most pixels as **deltas** — modifications to the previous pixel's color. This makes HAM ideal for photorealistic images but nearly useless for animation.
### HAM-6 Encoding (OCS/ECS)
| Bit 5 | Bit 4 | Bits 3-0 | Meaning |
|-------|-------|----------|---------|
| 0 | 0 | color index | Use palette[color index] (64 palette entries) |
| 0 | 1 | blue delta | Modify blue component of previous pixel |
| 1 | 0 | red delta | Modify red component of previous pixel |
| 1 | 1 | green delta | Modify green component of previous pixel |
### HAM-8 Encoding (AGA)
| Bits 7-6 | Bits 5-0 | Meaning |
|-----------|----------|---------|
| 00 | palette index | Use palette[index] (64 entries) |
| 01 | blue value | Set blue to 6-bit value, keep R,G |
| 10 | red value | Set red to 6-bit value, keep G,B |
| 11 | green value | Set green to 6-bit value, keep R,B |
### HAM Art Technique
HAM art for demos works by pre-rendering images in HAM mode, then displaying them as static or slowly-animated screens. The trick is managing the delta encoding to minimize color fringing:
```c
/* ham_render.c — Render a pre-calculated image to HAM-6 bitmap */
| **Display artifacts** | Vertical color bleeding from delta chains | Reset color at start of each scanline |
---
## Technique 3: Scroll Register Distortion
`BPLCON1` ($DFF102) controls horizontal scroll offset for the playfield. By changing it per scanline via the Copper, you create wave distortion effects — the classic "sine scrolling text" that defined the Amiga demo aesthetic.
`BPL1MOD` and `BPL2MOD` ($DFF108/$DFF10A) define the byte offset added to each bitplane's data pointer at the end of a scanline. Normally this compensates for interleaving. By setting the modulo to unusual values, you create **wrapping effects** — the bitmap appears to fold, repeat, or scroll infinitely.
### How Modulo Wrapping Works
```
Normal display (320px wide, 40 bytes/line):
Line 0: address 0
Line 1: address 40 (modulo = 40)
Line 2: address 80
With modulo = -40 (wraps back 40 bytes per line):
Line 0: address 0 → displays data[0..39]
Line 1: address -40 → wraps to end of bitmap!
Line 2: address -80 → wraps further back!
With modulo = 20 (half-width):
Line 0: address 0 → displays data[0..39]
Line 1: address 20 → offset by 20 bytes = half-line shift
Line 2: address 40
```
### Tunnel Effect via Modulo
```c
/* modulo_tunnel.c — Create a tunnel effect using modulo wrapping */
void setup_modulo_tunnel(ULONG frame) {
int y;
UWORD *cop = copper_list;
for (y = 0; y <200;y++){
/* Distance from center creates tunnel perspective */
int dist = 100 - abs(y - 100); /* 0 at top/bottom, 100 at center */
int modulo = dist * 2; /* Tighter wrapping at center */
Plasma is a classic demoscene effect created by overlapping sine waves mapped to a color palette. On the Amiga, plasma is implemented by pre-calculating a color lookup table and using the Copper to write it per scanline (or per block).
### Plasma Generation
```c
/* plasma.c — Generate plasma color values */
#define PLASMA_WIDTH 40 /* One color per 8 pixels (320/8) */
#define PLASMA_HEIGHT 25 /* One color per 8 scanlines (200/8) */
int r = (int)(8.0 + 7.0 * sin(phase * 3.14159 / 180.0));
int g = (int)(8.0 + 7.0 * sin((phase + 120) * 3.14159 / 180.0));
int b = (int)(8.0 + 7.0 * sin((phase + 240) * 3.14159 / 180.0));
plasma_palette[i] = (r <<8)|(g<<4)|b;
}
}
/* Calculate plasma value at (x,y) with time offset */
UBYTE plasma_value(int x, int y, ULONG t) {
int v = 0;
v += sine_table[(x * 4 + t) & 0xFF];
v += sine_table[(y * 6 + t * 2) & 0xFF];
v += sine_table[((x + y) * 3 + t) & 0xFF];
v += sine_table[((x * 2 - y + t * 3) & 0xFF)];
return (v >> 2) & 0x3F; /* 0-63 → palette index */
}
```
---
## Antipatterns
### 1. The Copper Chunky Bandwidth Wall
Attempting copper chunky at too high a resolution. With 5+ bitplanes enabled, there aren't enough DMA slots for per-pixel color writes. The display will flicker, show garbage, or skip pixels.
**Broken:**
```asm
; Trying 112-pixel copper chunky with 4 bitplanes active
; → DMA starvation, half the pixels never get written
dc.w $0100,$$4200 ; BPLCON0: 4 bitplanes enabled
; ... then try 112 MOVE instructions per line → FAIL
```
**Fixed:**
```asm
; Disable ALL bitplanes for copper chunky
dc.w $0100,$0200 ; BPLCON0: 0 bitplanes, color on
; Now full DMA bandwidth available for Copper writes
```
### 2. The HAM Fringe
Rendering sharp edges in HAM mode without resetting the color state. Adjacent pixels of very different colors create visible "fringing" as the delta encoding struggles to transition.
**Broken:**
```c
/* Drawing a red square on a blue background in HAM */
/* Each pixel transition goes: blue→red→blue→red → massive fringing */
for (y = 0; y <height;y++){
for (x = 0; x <width;x++){
/* Each transition needs 2-3 pixels to change color */
set_ham_pixel(x, y, is_square ? RED : BLUE); /* Fringes! */
}
}
```
**Fixed:**
```c
/* Use palette entries for the square colors, and place the
square on scanline boundaries where HAM state resets */
for (y = 0; y <height;y++){
/* At start of each line, set base color from palette */
set_ham_pixel(0, y, is_square ? RED_PAL_ENTRY : BLUE_PAL_ENTRY);
/* Now deltas work from a known state */
for (x = 1; x <width;x++){
/* Only use SET mode for sharp transitions */
if (is_border_pixel(x, y)) {
set_ham_pixel(x, y, target_palette_entry);
}
}
}
```
### 3. The Modulo Overflow
Setting a modulo value that causes bitplane addresses to wrap into non-bitplane memory or cross allocation boundaries. The result is visible garbage, DMA conflicts, or even system crashes.
**Broken:**
```c
/* Modulo too large → bitplane addresses go past allocated memory */
custom.bpl1mod = 0x7FFF; /* Almost 32KB jump per line! */
/* → Reads from random memory, shows garbage, may crash */
```
**Fixed:**
```c
/* Calculate modulo based on actual bitmap dimensions */
Forgetting that `BPLCON1` scroll applies from the Copper write position to the end of the frame. If you set scroll per-line but forget to reset it, the last value persists for all remaining lines.
**Broken:**
```asm
; Sine scroll lines 50-150, but no reset after line 150
dc.w $8060,$FFFE ; WAIT line 96
dc.w $0102,$0070 ; Scroll = 7
; ... more lines ...
dc.w $8096,$FFFE ; WAIT line 150 (last sine line)
dc.w $0102,$0003 ; Scroll = 3
; BUG: Lines 151+ still have scroll=3!
```
**Fixed:**
```asm
; After the sine section, reset scroll to 0
dc.w $8097,$FFFE ; WAIT line 151
dc.w $0102,$0000 ; Reset scroll to 0
```
### 5. The Palette Stride
Assuming HAM palette entries are contiguous. HAM-6 uses only 16 palette entries (0–15) for SET mode — the upper 48 entries of the 64-entry palette are not directly accessible via HAM SET codes.
**Broken:**
```c
/* Setting HAM palette entry 32 — NOT accessible via HAM SET! */
ham_palette[32] = 0x0FFF;
/* HAM SET mode only uses bits 3-0 → entries 0-15 */
```
**Fixed:**
```c
/* Only entries 0-15 are usable with HAM SET (00xxxx) */
/* Place your most important anchor colors in entries 0-15 */
| Color gradient animation | Per-line color writes | Copper bar variants |
---
## FPGA / Emulation Impact
| Concern | Impact | Notes |
|---------|--------|-------|
| **Copper chunky timing** | Exact per-cycle color register writes | Denise must latch COLORxx at precise pixel positions |
| **HAM delta decoding** | Per-pixel color state machine | Must track running R/G/B across entire scanline |
| **Scroll register latency** | BPLCON1 changes take effect next line | Must match hardware pipeline delay |
| **Modulo arithmetic** | Signed 16-bit addition to address | Address wrapping must handle 24-bit space correctly |
| **BPLCON0 bitplane disable** | 0-bitplane mode must suppress all BPL DMA | Copper chunky depends on zero bitplane DMA |
| **HAM-8 AGA extension** | 8-bitplane HAM mode with 6-bit deltas | AGA Lisa chip HAM decoder must be implemented |
---
## FAQ
**Q: Can copper chunky animate at full frame rate?**
A: Rarely. A 56×200 copper chunky frame requires generating ~11,200 copper instructions per frame. On a 7 MHz 68000, that takes most of the frame time. Most copper chunky demos animate at reduced resolution or partial updates.
**Q: Why not always use HAM for more colors?**
A: HAM's delta encoding makes it nearly impossible to render sharp-edited graphics or animations. Any pixel transition that requires changing more than one color component creates visible fringing. HAM is excellent for pre-rendered images but terrible for real-time rendering.
**Q: What's the difference between plasma and copper bars?**
A: Copper bars are horizontal bands of uniform color (one color per scanline or per group of scanlines). Plasma varies color both horizontally and vertically using sine-based lookup tables, creating a 2D color field. Plasma typically renders to bitplanes (pre-calculated pixel data) while copper bars use register writes only.
**Q: Can I combine copper chunky with normal bitplane graphics?**
A: Not simultaneously — copper chunky requires disabling all bitplane DMA. However, you can use copper chunky on some scanlines and bitplane graphics on others, switching `BPLCON0` mid-frame via the Copper. This is rarely done because the copper list size doubles.
**Q: How does modulo wrapping differ from scroll wrapping?**
A: Scroll (`BPLCON1`) shifts pixel data horizontally within a scanline by 0–15 pixels. Modulo (`BPL1MOD`) changes the byte offset between consecutive scanlines in memory — it affects which memory addresses are read for each line, not how the bits are shifted. They produce different visual effects: scroll creates smooth horizontal displacement, modulo creates discontinuous "jumps" in the displayed data.
**Q: What is EHB (Extra Half-Brite) and how does it relate?**
A: EHB is a 6-bitplane mode where the 32nd palette entry is automatically generated as a half-brightness copy of entries 0–31, giving 64 colors. It's not a "pixel trick" per se — it's a hardware feature of ECS+. It can be combined with copper effects for interesting results but is documented separately in [HAM & EHB Modes](../08_graphics/ham_ehb_modes.md).
---
## References
### Related Knowledge Base Articles
- [Pixel Conversion](../08_graphics/pixel_conversion.md) — C2P algorithms, copper chunky theory