# Sprite Techniques — Multiplexing, Sprite-Built Images, and Attached Sprites
## Overview
The Amiga has 8 hardware sprite channels, each displaying a 16-pixel-wide, arbitrarily-tall image in 3 colors (plus transparent). That sounds limiting — and it is, for games that need dozens of moving objects. But the demoscene turned this constraint into a creative engine. By repositioning sprites at different scanlines (multiplexing), building full-screen images from sprite data, and combining pairs into 15-color attached sprites, demoscene coders extracted far more from 8 channels than Commodore intended.
This article covers the specific demoscene sprite techniques that go beyond basic sprite usage. For the hardware architecture, DMA timing, and OS-level sprite API, see [Sprites](../08_graphics/sprites.md).
```mermaid
graph TB
subgraph "Multiplexing"
SMX["Vertical Multiplex<br/>Reuse sprites at different Y"]
HMX["Horizontal Reposition<br/>Move SPRxPOS per line"]
end
subgraph "Construction"
SBI["Sprite-Built Images<br/>Full graphics from sprite data"]
SBM["Sprite-Built Masks<br/>Sprites as Blitter stencil"]
end
subgraph "Enhancement"
ATT["Attached Sprites<br/>15-color sprite pairs"]
OVL["Sprite Overlay<br/>Sprites on top of playfield"]
end
SMX --> SBI
HMX --> SBM
SBI --> ATT
SBM --> OVL
```
---
## Hardware Recap
### Sprite DMA Channels
| Sprite | DMA Slots/Line | Position Reg | Data Regs | Colors | Notes |
| `SPRxCTL` | `$DFF142+8n` | Vertical stop (bits 15-8) + attach bit (bit 7) + control |
| `SPRxDATA` | `$DFF144+8n` | Data bitplane 0 (write-only) |
| `SPRxDATB` | `$DFF146+8n` | Data bitplane 1 (write-only) |
> [!NOTE]
> Sprite position encoding: The 8-bit horizontal component uses low-resolution pixel units ($00 = leftmost visible, max ~$DA). The 8-bit vertical component is the scanline number (PAL: 0–311, NTSC: 0–261).
---
## Technique 1: Sprite Multiplexing
The most important demoscene sprite technique. The Amiga's 8 sprites each persist from their start scanline to their stop scanline. By repositioning a sprite mid-frame (via the Copper writing new `SPRxPOS`/`SPRxCTL` values), the same hardware channel displays different images at different vertical positions.
### How Multiplexing Works
```mermaid
sequenceDiagram
participant Copper as Copper
participant SPR0 as Sprite 0 Channel
participant Screen as Display
Note over Screen: Scanline 50
Copper->>SPR0: SPR0POS = $3200 (start line 50, x=0)
Copper->>SPR0: SPR0CTL = $5C00 (stop line 92, attach=0)
Copper->>SPR0: SPR0DATA = image_A plane 0
Copper->>SPR0: SPR0DATB = image_A plane 1
SPR0->>Screen: Render image_A (lines 50-92)
Note over Screen: Scanline 93
Copper->>SPR0: SPR0POS = $5D28 (start line 93, x=40)
Copper->>SPR0: SPR0CTL = $8728 (stop line 135, attach=0)
Copper->>SPR0: SPR0DATA = image_B plane 0
Copper->>SPR0: SPR0DATB = image_B plane 1
SPR0->>Screen: Render image_B (lines 93-135)
```
### Multiplexed Sprite Setup
The Copper writes new position and data values for each sprite at each repositioning point:
```asm
; sprite_mux.asm — Multiplex sprite 0 at two vertical positions
SPRITE_MUX_COPPER:
; ---- First instance: lines 50-79 (30 lines) ----
dc.w $8032,$FFFE ; WAIT line 50
dc.w $0140,$3200 ; SPR0POS: start line 50, x=0
dc.w $0142,$4E00 ; SPR0CTL: stop line 78, no attach
; Sprite 0 data pointer in Copper (sets SPR0DATA/DATB via DMA)
dc.w $0180,$0000 ; COLOR00 = black (sprite uses it)
; ---- Reposition at line 80 ----
dc.w $8050,$FFFE ; WAIT line 80
dc.w $0140,$5028 ; SPR0POS: start line 80, x=40
dc.w $0142,$6A28 ; SPR0CTL: stop line 106, x=40
; ---- Reposition again at line 110 ----
dc.w $806E,$FFFE ; WAIT line 110
dc.w $0140,$6E50 ; SPR0POS: start line 110, x=80
dc.w $0142,$8C50 ; SPR0CTL: stop line 140, x=80
dc.w $FFFF,$FFFE ; End
```
### Multiplexing Limits
| Factor | Constraint | Practical Limit |
|--------|-----------|-----------------|
| **DMA bandwidth** | 2 slots per sprite per scanline (8 sprites = 16 slots) | All 8 sprites always consume 16 slots — fixed cost |
| **Copper repositioning** | 2 WAIT+MOVE pairs per reposition (8 words) | ~7 repositions per scanline in LoRes 4-plane |
| **Vertical spacing** | Must wait for sprite stop before restarting | Minimum ~1 scanline gap between instances |
| **Data storage** | Each sprite instance needs its own data in Chip RAM | Limited by Chip RAM budget |
| **Horizontal position** | Single 8-bit value, max ~$DA (218 pixels LoRes) | LoRes only; HiRes sprites need different encoding |
Instead of using sprites for moving objects, demoscene coders use sprite data to construct static images — logos, borders, or even full-screen graphics. This frees bitplane memory for the main display and lets sprites act as an overlay layer.
### How It Works
1. Arrange 8 sprites vertically (no multiplexing) to cover the full screen height
2. Each sprite is 16 pixels wide × the full display height (~256 lines)
3. Total sprite coverage: 8 × 16 = 128 pixels wide, full height
4. Combine with bitplanes for the remaining horizontal space
/* Each sprite line = 2 words = 32 bits = 16 pixels × 2 bitplanes
For a logo, we need to convert our image data to sprite format */
struct SpriteData {
UWORD pos; /* SPRxPOS value */
UWORD ctl; /* SPRxCTL value */
UWORD data[LOGO_HEIGHT]; /* Bitplane 0 */
UWORD datb[LOGO_HEIGHT]; /* Bitplane 1 */
};
/* Initialize 8 sprites to display a 128-pixel logo */
void init_sprite_logo(struct SpriteData sprites[8], int start_y) {
int i;
for (i = 0; i <8;i++){
int x = i * 16; /* Each sprite starts 16px after the previous */
/* Position: high byte = Y, low byte = X (lowres units) */
sprites[i].pos = (start_y <<8)|(x&0xFF);
/* Control: stop Y = start_y + LOGO_HEIGHT - 1 */
int stop_y = start_y + LOGO_HEIGHT - 1;
sprites[i].ctl = (stop_y <<8)|(x&0xFF);
/* Data is already in sprite format (2 words per line) */
/* Would be filled from pre-converted image data */
}
}
```
---
## Technique 3: Attached Sprites (15-Color)
Normal sprites have 3 colors. **Attached sprites** combine two sprite channels (even+odd, like SPR0+SPR1) into a single image with 15 colors. The even sprite provides bitplanes 0-1, the odd sprite provides bitplanes 2-3. Together, they form a 4-bitplane (16-color, one transparent) image.
### Attachment Encoding
The `attach` bit in `SPRxCTL` (bit 7) tells Denise to combine the sprite pair:
> The 4-bit combination gives 16 values. Value 0 is transparent, and values where both even and odd planes are all-ones map to `COLOR00`. The remaining 12 values use the sprite's own color registers (COLOR17–COLOR20 for the SPR0+SPR1 pair), giving effectively 15 distinct non-transparent colors.
### Attached Pair Assignments
| Even Sprite | Odd Sprite | Color Registers | Available |
## Technique 4: Sprite Overlay with Priority Control
`BPLCON2` (bit 6) controls whether sprites appear above or below playfield bitplanes. Demoscene effects exploit this to create overlay effects — sprites that appear in front of or behind the playfield for visual layering:
```asm
; Set sprites behind playfield (for background effects)
; Set sprites in front of playfield (default, for overlays)
dc.w $0104,$0000 ; BPLCON2: sprites in front
```
This can be changed mid-frame by the Copper, enabling sprites to appear behind bitplanes in one area and in front in another — a common technique in multi-layer parallax effects.
---
## Technique 5: Sprite-Based Color Effects
Each sprite's color registers can be changed by the Copper per scanline. This enables per-line color animation on sprite graphics:
```asm
; Animate sprite colors per scanline for rainbow effect
dc.w $8032,$FFFE ; WAIT line 50
dc.w $0188,$0F00 ; COLOR17 = blue (SPR0 color A)
dc.w $8033,$FFFE ; WAIT line 51
dc.w $0188,$0FF0 ; COLOR17 = cyan
dc.w $8034,$FFFE ; WAIT line 52
dc.w $0188,$0FFF ; COLOR17 = white
; ... continues for full rainbow
```
This technique is the basis for many demo effects where a sprite "absorbs" the current background color and appears to change color as it moves.
---
## Antipatterns
### 1. The Invisible Sprite
Forgetting to set sprite color registers. By default, all sprite colors are `$0000` (black), which is also the default background color — making sprites invisible against a black background.
**Broken:**
```c
/* Sprite is there but invisible — COLOR17/18 match background */
custom.spr[0].pos = pos_value;
custom.spr[0].ctl = ctl_value;
/* Colors never set → invisible on black background */
```
**Fixed:**
```c
custom.spr[0].pos = pos_value;
custom.spr[0].ctl = ctl_value;
/* Set sprite colors to visible values */
custom.color[17] = 0x0FFF; /* Bright white */
custom.color[18] = 0x0F00; /* Blue */
```
### 2. The Overlapping Multiplex
Repositioning a sprite before its previous instance has finished displaying. The sprite channel can only hold one position at a time — setting a new start position while the old one is still active causes visual artifacts.
**Broken:**
```asm
; Sprite 0 starts at line 50, runs to line 100
dc.w $0140,$3200 ; SPR0POS: start=50
dc.w $0142,$6400 ; SPR0CTL: stop=100
; But reposition at line 70 — before line 100 stop!
dc.w $8046,$FFFE ; WAIT line 70
dc.w $0140,$4628 ; SPR0POS: start=70 ← CONFLICT!
```
**Fixed:**
```asm
; Let first instance finish (stop line 100), then reposition
dc.w $8064,$FFFE ; WAIT line 100 (after stop)
dc.w $0140,$6428 ; SPR0POS: start=100, x=40
dc.w $0142,$8C28 ; SPR0CTL: stop=140, x=40
```
### 3. The Misaligned Attached Pair
Attached sprites require both even and odd sprites to have identical positions. Even a 1-pixel offset breaks the 15-color illusion.
**Broken:**
```asm
dc.w $0140,$3200 ; SPR0POS: line 50, x=0
dc.w $0148,$3201 ; SPR1POS: line 50, x=1 ← OFFSET!
```
**Fixed:**
```asm
dc.w $0140,$3200 ; SPR0POS: line 50, x=0
dc.w $0148,$3200 ; SPR1POS: line 50, x=0 ← SAME!
```
### 4. The Sprite DMA Starvation
Disabling sprite DMA (`DMACON` bit 3) but still writing to sprite position registers. Without DMA, no sprite data is fetched — the registers are set but nothing appears.
**Broken:**
```c
custom.dmacon = 0x8100; /* Enable DMA, but forgot sprite bit (bit 3) */
/* Sprites won't display even though positions are set */
custom.dmacon = 0x81FF; /* Enable all DMA channels */
```
### 5. The AGA Width Trap
AGA allows 32-pixel and 64-pixel wide sprites via `FMODE` settings. But the sprite data format changes — wider sprites need more words per line. Using OCS-format sprite data with AGA wide-sprite settings produces garbage.
**Broken:**
```c
/* Set 32-pixel sprite mode but provide 16-pixel data */
| With new data pointers | 8 | 8 | POS + CTL + DATA/DATB ptrs |
| Full reposition + colors | 12 | 12 | Above + 2 color register writes |
> [!TIP]
> Each sprite reposition costs 4–12 DMA slots per scanline at the reposition line. Plan multiplexing points carefully — avoid repositioning all 8 sprites on the same scanline.
| **Sprite DMA timing** | Must fetch exactly 2 words per sprite per scanline | Minimig/MiSTer implement precise DMA slot scheduler |
| **Attached sprite decoding** | Denise must combine even+odd data correctly | 4-bitplane lookup from 2 sprite channels |
| **SPRxPOS/CTL latency** | Position changes take effect next line | Must match real hardware delay |
| **CLXCON collision detection** | Hardware collision between sprites/bitplanes | Required for games like Turrican |
| **AGA FMODE sprite width** | 32/64px sprites change data format | FMODE.SPR_FMODE must be tracked |
| **Sprite data staging** | Denise latches data at end of scanline for next line | Pipeline behavior must be emulated |
---
## FAQ
**Q: How many sprites can I display on screen at once?**
A: 8 per scanline (hardware limit). With multiplexing, you can reuse each channel multiple times vertically — a common demo technique shows 30+ "sprites" using 8 channels multiplexed 4 times each.
**Q: Can sprites be wider than 16 pixels?**
A: On OCS/ECS, no — 16 pixels is the fixed hardware width. On AGA, `FMODE` bits can set 16, 32, or 64 pixel widths. For OCS, wider objects require Blitter BOBs or multiple sprites side by side.
**Q: Why does SPR0 sometimes conflict with the mouse pointer?**
A: Intuition reserves SPR0 and SPR1 for the mouse pointer. If you take over the hardware (demos), you can use all 8 sprites. If running under the OS, use only SPR2-SPR7 or use the `ExtSprite` API (V39+) which cooperates with Intuition.
**Q: What happens when sprites overlap?**
A: Lower-numbered sprites have priority — SPR0 appears on top of SPR1, which appears on top of SPR2, etc. The sprite/playfield priority is controlled by `BPLCON2`.
**Q: Can I use sprites and Blitter BOBs together?**
A: Yes. Sprites are DMA-driven and cost zero CPU time. BOBs are software-driven (Blitter copies) and cost Blitter DMA time. Many games use sprites for small, frequently-moving objects and BOBs for larger or more colorful ones.
**Q: What is CLXCON and how does collision detection work?**
A: `CLXCON` (`$DFF098`) configures which sprite and bitplane bits are included in collision detection. `CLXDAT` (`$DFF00E`) reports detected collisions. The hardware compares sprite and bitplane data in real-time and sets bits when matching pixels overlap. See [Sprites](../08_graphics/sprites.md) for full details.