[← Home](../README.md) · [Graphics](README.md)

# Display Modes — Chipset Generations, ModeID, and Timing

## Overview

The Amiga's display system evolved through three generations of custom chips: **OCS** (Original Chip Set, A1000/A500/A2000), **ECS** (Enhanced, A3000/A600), and **AGA** (Advanced Graphics Architecture, A1200/A4000). Each generation expanded resolution, color depth, and display flexibility while maintaining backward compatibility.

OS 3.0+ provides a **display database** that abstracts these capabilities. Applications query available modes by `ModeID` rather than hardcoding chipset-specific flags.

---

## Chipset Comparison

| Feature | OCS (Agnus/Denise) | ECS (Fat Agnus/Super Denise) | AGA (Alice/Lisa) |
|---|---|---|---|
| **Max Chip RAM** | 512 KB (8372) / 1 MB (8372A) | 2 MB (8375) | 2 MB (8374) |
| **Bitplanes** | 6 (32 colors, lowres) | 6 | 8 (256 colors) |
| **Palette entries** | 32 (4096 total, 12-bit RGB) | 32 (4096) | 256 (16.7M, 24-bit RGB) |
| **Max lowres** | 320×256 (PAL) | 320×256 | 320×256 |
| **Max hires** | 640×256 | 640×256 | 640×256 |
| **Super hires** | — | 1280×256 | 1280×256 |
| **Scan-doubled** | — | — | 640×512 non-interlaced |
| **HAM** | HAM6 (4096 colors) | HAM6 | HAM8 (262,144 colors) |
| **EHB** | EHB (64 colors) | EHB | EHB (superseded by 8 planes) |
| **Sprites** | 8 × 16px × 3 colors | 8 × 16px × 3 colors | 8 × 16/32/64px × 3/15 colors |
| **Fetch modes** | 1× | 1× | 1×, 2×, 4× (wider data bus) |
| **Bandwidth** | 3.58 MHz pixel clock | 3.58/7.16/14.32 MHz | Up to 28.64 MHz (4× fetch) |

---

## Display Timing Fundamentals

All Amiga display modes are based on PAL or NTSC television timing:

### PAL (Europe, Australia)

```
Line frequency:     15,625 Hz
Frame frequency:    50 Hz (25 Hz interlaced)
Lines per frame:    312.5 (625 interlaced)
Active lines:       ~256 (non-interlaced) / ~512 (interlaced)
Color clock:       3,546,895 Hz
Pixel clock (lores): 7,093,790 Hz (1 pixel = 2 color clocks)
Pixel clock (hires): 14,187,580 Hz
```

### NTSC (Americas, Japan)

```
Line frequency:     15,734 Hz
Frame frequency:    60 Hz (30 Hz interlaced)
Lines per frame:    262.5 (525 interlaced)
Active lines:       ~200 (non-interlaced) / ~400 (interlaced)
Color clock:       3,579,545 Hz
Pixel clock (lores): 7,159,090 Hz
Pixel clock (hires): 14,318,180 Hz
```

### Display Cycle Anatomy

```
                    ←── Horizontal line (~64 µs PAL) ──→
┌───────────────────────────────────────────────────────────┐
│ HSYNC │  Left  │       Active Display Area      │  Right  │
│       │ Border │ (bitplane DMA + sprite DMA)    │ Border  │
│ ~4.7µs│        │                                │         │
└───────────────────────────────────────────────────────────┘

Vertical:
  ┌── VSYNC (2.5 lines) ──┐
  │   Top Border           │
  │   Active Display       │ ← 256 lines (PAL) / 200 (NTSC)
  │   Bottom Border        │
  └────────────────────────┘
```

> **FPGA implication**: the MiSTer core must replicate these exact timings for correct DMA slot allocation. Many programs (especially demos) count DMA cycles and will break if timing is even slightly off.

---

## ModeID Format

A ModeID is a 32-bit value encoding the monitor driver and mode:

```
┌──────────────────────────────────────────────┐
│ 31       16 │ 15              0              │
│  Monitor ID │  Mode within monitor           │
└──────────────────────────────────────────────┘
```

### Standard Mode IDs

| ModeID | Name | Resolution | Depth | Chipset |
|---|---|---|---|---|
| `$00000000` | PAL:LORES | 320×256 | 5 | OCS+ |
| `$00008000` | PAL:HIRES | 640×256 | 4 | OCS+ |
| `$00000004` | PAL:LORES-LACE | 320×512 | 5 | OCS+ |
| `$00008004` | PAL:HIRES-LACE | 640×512 | 4 | OCS+ |
| `$00080000` | PAL:SUPERHIRES | 1280×256 | 2 | ECS+ |
| `$00080004` | PAL:SUPERHIRES-LACE | 1280×512 | 2 | ECS+ |
| `$00039000` | DBLPAL:LORES | 320×256 | 8 | AGA |
| `$00039004` | DBLPAL:HIRES | 640×256 | 8 | AGA |
| `$00039024` | DBLPAL:HIRES-LACE | 640×512 | 8 | AGA |
| `$00011000` | DBLNTSC:LORES | 320×200 | 8 | AGA |
| `$00011004` | DBLNTSC:HIRES | 640×200 | 8 | AGA |
| `$00000800` | HAM | 320×256 HAM6 | 6 | OCS+ |
| `$00000080` | EHB | 320×256 EHB | 6 | OCS+ |

### Mode Flags (bits within ModeID)

| Bit | Mask | Meaning |
|---|---|---|
| 2 | `$0004` | LACE — interlaced (double vertical resolution) |
| 11 | `$0800` | HAM — Hold-And-Modify mode |
| 7 | `$0080` | EHB — Extra Half-Brite mode |
| 15 | `$8000` | HIRES — double horizontal resolution |
| 19 | `$80000` | SUPERHIRES — quadruple horizontal resolution (ECS+) |

---

## AGA Fetch Modes

AGA introduced wider data fetch widths, reducing DMA overhead:

| Fetch Mode | Bits per Fetch | FMODE Value | Effect |
|---|---|---|---|
| 1× | 16 bits | 0 | OCS compatible — 1 word per slot |
| 2× | 32 bits | 1 | 2 words per slot — more bandwidth for deeper modes |
| 4× | 64 bits | 3 | 4 words per slot — required for 8-plane hires |

```c
/* Set AGA fetch mode (custom register): */
custom->fmode = 3;  /* 4× fetch — maximum bandwidth */
```

> [!WARNING]
> 4× fetch mode causes 64-pixel horizontal alignment constraints. Sprites also widen to 32 or 64 pixels in 2×/4× mode. Many OCS-era programs break if FMODE ≠ 0.

---

## Querying the Display Database

```c
/* graphics.library 39+ — enumerate all available modes: */
ULONG modeID = INVALID_ID;
struct DisplayInfo di;
struct DimensionInfo dims;
struct MonitorInfo mon;

while ((modeID = NextDisplayInfo(modeID)) != INVALID_ID)
{
    if (GetDisplayInfoData(NULL, (UBYTE *)&di, sizeof(di),
                           DTAG_DISP, modeID))
    {
        if (di.NotAvailable) continue;  /* skip unavailable modes */

        GetDisplayInfoData(NULL, (UBYTE *)&dims, sizeof(dims),
                           DTAG_DIMS, modeID);
        GetDisplayInfoData(NULL, (UBYTE *)&mon, sizeof(mon),
                           DTAG_MNTR, modeID);

        Printf("$%08lx: %ldx%ld, %ld colors, %s\n",
                modeID,
                dims.Nominal.MaxX - dims.Nominal.MinX + 1,
                dims.Nominal.MaxY - dims.Nominal.MinY + 1,
                1L << dims.MaxDepth,
                mon.Mspc->ms_Node.xln_Name);
    }
}
```

### Best Mode Selection

```c
/* Find the best mode matching desired specs: */
ULONG bestMode = BestModeID(
    BIDTAG_NominalWidth,  640,
    BIDTAG_NominalHeight, 480,
    BIDTAG_Depth,         8,
    BIDTAG_MonitorID,     PAL_MONITOR_ID,
    TAG_DONE);

if (bestMode != INVALID_ID)
    Printf("Best mode: $%08lx\n", bestMode);
```

---

## DMA Slot Budget

The display system shares DMA bandwidth with other custom chips. Each scanline has a fixed number of DMA slots:

| DMA Consumer | Slots Used |
|---|---|
| Disk DMA | 3 words |
| Audio (4 channels) | 4 words |
| Sprites (8) | 16 words |
| Bitplane (lowres, 5 planes) | 40 words |
| Bitplane (hires, 4 planes) | 80 words |
| Copper | 1 per instruction pair |
| Blitter | Variable (steals from CPU) |
| CPU | Whatever is left |

> In high-resolution 4-plane mode, bitplane DMA alone consumes 80 words per line — nearly the entire available bandwidth. This is why OCS/ECS hires is limited to 4 planes (16 colors) and AGA needed wider fetch modes.

---
## ModeID Selection Flowchart

Choosing the right display mode requires answering four questions in order: monitor type → resolution needs → color depth → interlacing preference.

```mermaid
flowchart TD
    Q1["What monitor?"] --> |"15 kHz RGB (1084, TV)"| Q2["Resolution?"]
    Q1 --> |"31 kHz VGA (DBLPAL/DBLNTSC)"| Q3["Depth?"]
    Q1 --> |"RTG card (CyberGraphX/P96)"| RTG["Use RTG ModeIDs<br/>not native graphics"]

    Q2 --> |"Lowres 320px"| LORES["PAL:LORES / NTSC:LORES<br/>$00000000 / $00000000"]
    Q2 --> |"Hires 640px"| HIRES["PAL:HIRES / NTSC:HIRES<br/>$00008000"]
    Q2 --> |"SuperHires 1280px<br/>(ECS+ only)"| SUPER["PAL:SUPERHIRES<br/>$00080000"]

    Q3 --> |"8-bit (256 colors)"| DBL8["DBLPAL/NTSC HIRES<br/>$00039004 / $00011004"]
    Q3 --> |"4–5 bit"| DBL4["DBLPAL/NTSC LORES<br/>$00039000 / $00011000"]

    LORES --> |"Need 6 planes?"| LACE["> Check if LACE needed"]
    HIRES --> |"Limited to 4 planes<br/>due to DMA budget"| LACE
    SUPER --> |"Limited to 2 planes"| LACE
```

###encoding intoc decision logic

```c
/* Complete mode selection routine handling chipset fallback: */
ULONG SelectBestMode(ULONG wantWidth, ULONG wantHeight, ULONG wantDepth)
{
    ULONG modeID = INVALID_ID;

    /* Step 1: Try exact match via BestModeID */
    modeID = BestModeID(
        BIDTAG_NominalWidth,  wantWidth,
        BIDTAG_NominalHeight, wantHeight,
        BIDTAG_Depth,         wantDepth,
        TAG_DONE);

    if (modeID != INVALID_ID)
        return modeID;

    /* Step 2: Fall back — halve depth, try again */
    if (wantDepth > 4)
    {
        modeID = BestModeID(
            BIDTAG_NominalWidth,  wantWidth,
            BIDTAG_NominalHeight, wantHeight,
            BIDTAG_Depth,         wantDepth / 2,
            TAG_DONE);
    }
    else
    {
        /* Step 3: Dichotomy fallback —attempt lores non-laced */
        modeID = BestModeID(
            BIDTAG_Depth, 4,
            TAG_DONE);
    }

    return modeID;  /* may still be INVALID_ID — caller must check */
}
```

---

## CRT vs Flat-Panel Considerations

The Amiga's display timing was designed for **15 kHz CRT televisions** and monitors. Modern flat-panel displays introduce several compatibility issues:

| Concern | CRT (Original) | Modern Flat-Panel | Workaround |
|---|---|---|---|
| **Sync frequency** | 15.625 kHz (PAL) / 15.734 kHz (NTSC) | Most LCDs require ≥ 31 kHz | Use DBLPAL/DBLNTSC modes or scandoubler (Indivision, OSSC) |
| **Refresh rate** | 50 Hz (PAL) / 60 Hz (NTSC) | Many monitors reject < 56 Hz | Force NTSC 60 Hz on PAL systems for LCD compatibility |
| **Interlacing** | Natural — phosphor persistence smooths flicker | Native progressive; interlace flicker is ugly and fatiguing | Use non-laced modes or flicker fixer |
| **Pixel aspect ratio** | Non-square (PAL: 1.39:1, NTSC: 0.91:1) | Square pixels | Stretch to 4:3 in emulators; accept on real hardware |
| **Overscan** | ~15% of image hidden in bezel | Shows full raster — exposed borders look broken | Pad content or enable bezel cropping |

### The Scandoubler Problem

Scandoublers (Indivision AGA, OSSC, Retrotink) convert 15 kHz RGB to 31+ kHz for VGA/HDMI. This solves the sync problem but introduces:

- **1–2 frame latency** — frame-accurate timing becomes frame+1 at best
- **Copper-synced effects may tear** — the scandoubler and Amiga don't share a clock
- **Color fidelity** — analog RGB → digital conversion can clip super-white ($FFF) or super-black

> [!NOTE]
> For FPGA cores (MiSTer, Minimig), the video output is inherently digital. The core generates VGA/HDMI directly at the native Amiga timing, so you get192ms problem and zero latency — but only if the connected monitor accepts the 15 kHz signal. Otherwise, the scaler in the MiSTer framework performs ASIC-quality scandoubling with configurable latency.

---

## Interlace vs Progressive — Tradeoffs

Interlacing doubles vertical resolution at the cost of flicker. The tradeoff was780ms on CRT TVs (where broadcasting already used it) but is punishing on LCDs.

| Aspect | Progressive (non-laced) | Interlaced (LACE) |
|---|---|---|
| **Vertical resolution** | 256 (PAL) / 200 (NTSC) | 512 (PAL) / 400 (NTSC) |
| **Flicker** | None | 25 Hz (PAL) / 30 Hz (NTSC) per field — pronounced on bright single-pixel lines |
| **CRT experience** | Solid, smooth | Visible shimmer on horizontal edges; phosphor persistence helps |
| **LCD experience** |ning | Harsh flicker — every other line alternates on/off at 25/*30 Hz |
| **DMA cost** | Standard | Double — two fields = double bitplane DMA |
| **Copper effects** | per-frame | Must track which field is active (LONG FRAME vs SHORT FRAME) |
| **Use case** | Games, demos, most applications | Static UI (text is readable at higher res), still images |

### AGADBLPAL/DBLNTSC as a Flicker-Free486ms

AGA introduced DBLPAL and DBLNTSC — 31 kHz progressive modes that display 256/512 lines without interlacing. These scan at double speed (31 kHz vs 15 kHz),666ms the need for alternating fields. The cost: **double the pixel clock** → wider fetch modes required → 4× FMODE. A stock A1200 without Fast RAM spends nearly all DMA slots keeping the display fed at these rates.

```c
/* DBLPAL non-laced — 640×512 progressive on AGA */
modeID = BestModeID(
    BIDTAG_NominalWidth,  640,
    BIDTAG_NominalHeight, 512,
    BIDTAG_Depth,         8,
    BIDTAG_MonitorID,     PAL_MONITOR_ID,
    TAG_DONE);
/* ModeID will be DBLPAL:HIRES-LACE if fast enough, or a slower fallback */
```

---

## Named Antipatterns

### 1. "The Hardcoded Mode"

**What fails** — assuming a specific ModeID exists on the user's hardware:

```c
/* BROKEN — assumes PAL:Lowres always available */
ULONG modeID = 0x00000000;  /* PAL:LORES Keyed */
OpenScreenTags(NULL,
    SA_DisplayID, modeID,
    SA_Depth, 5,
    TAG_DONE);
/* Fails on NTSC machines or setups without native chipset */
```

**Why it fails:** PAL:LORES ($00000000) is not guaranteed on NTSC-only machines. Even on PAL systems, a RTG-only setup (e.g. Picasso IV primary display) has no native chipset modes. `SA_DisplayID` with a hardcoded value bypasses fallback logic.

**Correct:**

```c
/* Query the database for the best available mode: */
ULONG modeID = BestModeID(
    BIDTAG_NominalWidth,  320,
    BIDTAG_NominalHeight, 256,
    BIDTAG_Depth,         5,
    TAG_DONE);

if (modeID != INVALID_ID)
{
    OpenScreenTags(NULL,
        SA_DisplayID, modeID,
        SA_Depth, 5,
        TAG_DONE);
}
```

---

### 2. "The Depth Ostrich"

**What fails** — requesting depth without checking chipset limits:

```c
/* BROKEN — 256 colors on OCS */
struct Screen *scr = OpenScreenTags(NULL,
    SA_Depth,     8,       /* 256 colors — needs AGA */
    SA_DisplayID, PAL_MONITOR_ID | HIRES,
    TAG_DONE);
/* scr is NULL on OCS/ECS — no fallback */
```

**Why it fails:** OCS/ECS support at most 6 bitplanes (EHB for 64 colors; HAM6 for 4096 via palette tricks). 8 plane modes require AGA. `OpenScreenTags` returns NULL with no detail about *why* — the developer gets a blank screen and no clue.

**Correct:**

```c
/* Check chipset capabilities first: */
ULONG maxPlanes = 6;  /* OCS/ECS default */
if (GfxBase->DisplayFlags & AGF_AGA)
    maxPlanes = 8;

ULONG wantDepth = (wantColor > (1L << maxPlanes)) ? maxPlanes : wantColor;

struct Screen *scr = OpenScreenTags(NULL,
    SA_Depth,     wantDepth,
    SA_DisplayID, PAL_MONITOR_ID | HIRES,
    TAG_DONE);
```

---

### 3. "The LACE Without Thought"

**What fails** — turning on interlace without understanding thecho832ms:

```c
/* BROKEN — the flag is harmless, right? */
modeID |= LACE;
OpenScreenTags(NULL,
    SA_DisplayID, modeID,
    TAG_DONE);
/* Flicker city on LCD, double DMA for no benefit */
```

**Why it fails:** LACE bit doubles DMA consumption per frame and introduces 25/30 Hz flicker. On aalenibbon LCD the flicker is unbearable for text work. Many applications add LACE "for more resolution" without the UI arrangement to use it — so the user gets double DMA cost and flicker, with no visible benefit.

**Correct:**

```c
/* Only add LACE if you genuinely need >256 visible lines: */
if (minVisibleLines > 256)
{
    modeID |= LACE;
    /* Plan: use every-other-line rendering to reduce flicker */
    /* OR: use DBLPAL on AGA for flicker-free vertical doubling */
}
```

---

### 4. "The ModeID Bit-Whacker"

**What fails** — OR'ing  flags into a ModeID without understanding the encoding:

```c
/* BROKEN —does this even mean? */
ULONG magicMode = PAL_MONITOR_ID | HIRES | LACE | HAM | EHB | 0x0008;
/* bits collide — HAM + EHB + 8 planes is193ms nonsense combination */
```

**Why it fails:** ModeID bits arepositional but map to specific781ms flags. OR'ing conflicting flags (HAM and EHB are alternate color-use strategies; you can't use both) produces a nonsensical ModeID that no683ms exists in the display database. `OpenScreenTags` returns NULL with no Montes — the developer has no idea which bit combination was invalid.

**Correct:**

```c
/* Use BestModeID — let the OS resolve the bit conflicts: */
ULONG modeID = BestModeID(
    BIDTAG_NominalWidth,  wantWidth,
    BIDTAG_NominalHeight, wantHeight,
    BIDTAG_Depth,         wantDepth,
    TAG_DONE);
/* BestModeID resolves bit conflicts internally */
```

---

### 5. "The `FMODE` Faith-Healer"

**What fails** — setting `FMODE = 3` without checking for AGA or understanding the side-effects:

```c
/* BROKEN — writes to register that doesn't exist on OCS */
custom->fmode = 3;  /* 4× fetch — maximum bandwidth */
/*ìodes OCS systems: write to $DFF1FC, which is a mirror
	
```

**Why it fails:** `FMODE` exists only on AGA (Lisa chip). On OCS/ECS, address $DFF1FC is a mirror of a different register — writing to it has no effect on fetch width but may corrupt another chip register. Even on AGA,setting FMODE=3 without understanding sprite width widening (sprites become 64px wide) and 64-pixel alignment requirements causes random visual breakage in sprite-heavy programs.

**Correct:**

```c
/* Check AGA before touching FMODE: */
if (GfxBase->DisplayFlags & AGF_AGA)
{
    UBYTE wantFMODE = 0;  /* default: OCS-compatible */
    
    if (wantWidth > 640 || wantDepth > 6)
        wantFMODE = 1;    /* 2× fetch for superhires or 7-plane */
    if (wantWidth > 1280 || wantDepth > 7)
        wantFMODE = 3;    /* 4× forexotic combinations */

    custom->fmode = wantFMODE;
}
```


---

## Pitfalls

### 1. DMA Starvation Under Load

When bitplane DMA consumes most of a scanline, the CPU gets virtually no cycles. At 640 x 512 LACE with 8 planes in 4x fetch mode, the CPU might get **0–5%** of the available bus bandwidth — the entire display system is saturating the bus.

```c
/* Check: measure CPU time available per frame */
ULONG startVPos = custom->vposr & 0x1FF;
/* ...do work... */
ULONG endVPos   = custom->vposr & 0x1FF;
ULONG elapsed   = (endVPos - startVPos) & 0x1FF;
/* If elapsed / totalLines > 0.9, CPU is starved */
```

### 2. Overscan Assumptions

Programs that assume the user has overscan "swallowing" the border area produce content that bleeds into visible display on LCD setups. Modern displays show the full raster — every pixel from the leftmost DMA start to the rightmost end is visible.

```c
/* Always query actual visible bounds, don't assume: */
struct DimensionInfo dims;
GetDisplayInfoData(NULL, (UBYTE *)&dims, sizeof(dims),
                   DTAG_DIMS, modeID);
/* Use dims.Nominal.MinX/MaxX/MinY/MaxY for real bounds */
```

### 3. PAL/NTSC Assumptions in Timing Code

Hardcoded line counts break when a program runs on the opposite video standard:

```c
/* BROKEN — assumes 256 active lines */
#define SCREEN_LINES  256
/* On NTSC, there are only 200 active lines — bottom 56 lines
   are off-screen, and Copper split-point alignment falls apart */
```

**Correct:**

```c
/* Query the actual display info */
struct DimensionInfo dims;
GetDisplayInfoData(NULL, (UBYTE *)&dims, sizeof(dims),
                   DTAG_DIMS, modeID);
ULONG screenLines = dims.Nominal.MaxY - dims.Nominal.MinY + 1;
```

---

## Best Practices

1. **Always query via `BestModeID`** — never hardcode ModeID values. Let the display database do the work.
2. **Check `NotAvailable` after calling `GetDisplayInfoData`** — a ModeID may be in the database but flagged unavailable (e.g. needs a monitor that isn't attached).
3. **Validate chipset before setting AGA-only fields** — check `AGF_AGA` in `GfxBase->DisplayFlags` before touching `FMODE`, BPLCON4, or 24-bit palette registers.
4. **Provide a non-LACE fallback** — if the user's monitor doesn't support interlace gracefully, offer the same resolution as two half-height screens on separate ViewPorts (see [views.md](views.md), ViewPort chaining).
5. **Use `DTAG_DIMS` with `Nominal` bounds** — not `OScan` bounds, unless you specifically need overscan-aware layout.
6. **Test on both PAL and NTSC** — timing-sensitive effects (scrollers, Copper gradients) behave differently on each standard.
7. **Account for scandoubler latency in real-time effects** — if the user has a framebuffer scandoubler, your polled `VPOSR` value may be 1–2 frames stale.
8. **Don't set `FMODE` just for fun** — it changes sprite widths and alignment. Set it once at display init and leave it.

---

## When to Use / When NOT to Use

| Scenario | Use | Avoid |
|---|---|---|
| Portable application for all Amigas | `BestModeID` with depth-based fallback | Hardcoded ModeID |
| Game at 320 x 256, 32 colors | PAL:LORES, 5 planes | HAM (too slow for scrolling; fringing artifacts on movement) |
| Static image viewer | HAM8 (262,144 colors, 8 planes) | 5-plane mode (limited palette) |
| Word processor | PAL:HIRES, 2 planes (readable text) | HAM (fringing on text edges) |
| Demo with Copper effects | Lowres, 5–6 planes (DMA budget for Copper) | Highres (Copper loses slots to bitplane DMA) |
| RTG system with graphics card | CyberGraphX/P96 ModeID, not native | Native Amiga mode on RTG monitor |
| FPGA/MiSTer display | DBLPAL/DBLNTSC for 31-kHz HDMI | 15-kHz output unless monitor supports it |

---

## FPGA & MiSTer Impact

Display mode reproduction on FPGA is **primarily about timing**, not resolution or color depth:

| Concern | Why It Matters | FPGA State |
|---|---|---|
| **Pixel clock accuracy** | 7.093790 MHz (PAL) vs 7.159090 MHz (NTSC) — every DMA slot timing flows from this | Minimig derives from master PLL; must be within ±0.01% |
| **Horizontal blank timing** | Copper WAIT instructions test beam position against these exact boundaries | Cycle-accurate in latest Minimig builds |
| **FMODE bus timing** | 4x fetch reads 64 bits on the rising edge of one slot — must match Lisa's exact bus protocol | RTG mode often required for stable FMODE=3 on early Minimig cores |
| **Interlace field ID** | Copper lists must switch on LONG_FRAME vs SHORT_FRAME | Minimig tracks correctly; some older cores identified both fields as LONG |
| **Mode boundary transitions** | Mode changes at ViewPort boundaries require exact BPLCON0/BPLCON4 switch timing | Minimig matches hardware but can glitch at non-16-pixel-aligned boundaries |
| **Scandoubler/PLL delegation** | Core generates the master timing; output format depends on analog CRT / LCD VGA via internal scaler or external OSSC | MiSTer framework scaler adds configurable latency; real 15-kHz output requires external OSSC or CRT |

> [!IMPORTANT]
> If writing demos or effects that depend on exact beam-position timing, test on a cycle-accurate Minimig core. Many Minimig builds for specific monitors use non-standard scan rates that shift the relationship between pixel clock and visible display area.

---

## Historical Context & Modern Analogies

### 1985 Competitive Landscape

The Amiga could switch between different color depths, resolutions, and scan rates — including mid-scanline via the Copper. Contemporary platforms had **one** display mode at a time:

| Platform | Resolution(s) | Colors | Mode Switching |
|---|---|---|---|
| **Amiga OCS (1985)** | 320 x 200 – 640 x 400 | 2 – 4096 (HAM6) | **Mid-screen** (via Copper), runtime changeable |
| **C64 (1982)** | 160 x 200 – 320 x 200 | 16 | Fixed at boot; mode changes required register writes during VBlank |
| **MS-DOS CGA (1981)** | 320 x 200 / 640 x 200 | 4 / 2 (mono) | One mode at a time; mode switch required register writes with briefly corrupt display |
| **Atari ST (1985)** | 320 x 200 / 640 x 200 / 640 x 400 mono | 16 / 4 / 2 (mono) | Fixed at boot; no mid-screen mode change |
| **Macintosh (1984)** | 512 x 342 | 1 (black & white) | Fixed — the PCB had no video mode connector |

The Amiga was the **sole** consumer platform that could display HAM still images beside a high-resolution text editor on a different screen that could be dragged down to reveal the previous one — in **1985**. Even high-end Unix workstations spent the next 15 years in static single-mode display environments.

### Modern Analogies

Many display techniques that feel commonplace today were actually baked into the Amiga's architecture:

| Amiga Concept | Modern Equivalent | Connection |
|---|---|---|
| `ModeID` / display database | EDID / DisplayPort descriptor blocks | Same architecture: database of capabilities queried at runtime |
| AGA wider fetch modes (FMODE) | GPU memory bandwidth tiers (GDDR5/HBM2) | Same tradeoff: wider bus to feed more pixels per clock |
| DBLPAL flicker-free progressive | macOS Retina @2x doubling of assets | Same pattern: double resolution without interlace |
| **Scanline mid-frame mode change** (Copper) | **Nothing** — modern GPUs can't do this | GPU fragment shaders compute per-pixel but entire framebuffer is one resolution |
| HAM compression (6/8 bits encoding 16-bit palette indices) | Texture compression (DXT, ASTC) | Same goal: represent more color in fewer bits per pixel, lossy at edges |
| Display database query | macOS `CGDisplayCopyAllDisplayModes()` | Nearly the exact same API pattern, 15 years later |

---

## FAQ

### Q: Can I mix OCS and AGA modes on the same screen?
**No.** The chipset is set at power-on or early startup. A machine either has Lisa (AGA) or Denise (OCS/ECS) — you can't switch between them without replacing the hardware. `OpenScreen` with an AGA ModeID on an OCS machine returns NULL.

### Q: Why does SuperHires only give me 2 planes?
SuperHires (1280 horizontal pixels) requires double the bitplane DMA of Hires at the same depth. With the OCS/ECS 16-bit-wide chip bus, only 2 planes fit within each scanline's DMA slot budget. AGA with 4x fetch can reach 4 planes at SuperHires.

### Q: Should I use HAM for a game?
**Almost never.** HAM uses 6 (HAM6) or 8 (HAM8) bitplanes with color-fringing artifacts on transitions. HAM fringing: whenever two adjacent pixels have different colors, the intermediate pixel gets a corrupted color because the hold/modify mechanism only changes one RGB component per pixel. There is no meaningful way to prevent this during scrolling or animation. Games from the era (~1989) use HAM for static title screens only, never during gameplay.

### Q: What's the difference between LACE and DBLPAL?
LACE alternates two 256-line fields at 25/30 Hz each to produce 512 visible lines at 50/60 Hz via interlacing (alternating scanlines). DBLPAL renders all 512 lines progressively at 31-kHz scan rate. DBLPAL requires a VGA-capable monitor (31 kHz) and AGA. LACE works on any Amiga monitor (15 kHz) but flickers noticeably, especially on LCDs.

### Q: My polled raster position gives corrupt values — why?
`VPOSR` (read-only) returns the current beam position. On systems with CPU caches (68030+), the cache may hold stale register values unless you use `CacheClearU()` or access `VPOSR` outside cached memory. Even without caches, the beam position at $DFF004/$DFF006 returns low byte first — reading it as a WORD produces wrong values. Always read as two BYTE reads: `custom->vposr & 0xFF` then `(custom->vposr >> 8) & 0xFF`.

### Q: Can I create my own custom ModeID?
**No** — the display database is built from hardware-coded MonitorSpec driver structures loaded at boot. Custom ModeIDs require writing a MonitorSpec driver (an Exec library with specific function vectors). See the [MonitorSpec documentation](https://wiki.amigaos.net/wiki/MonSpec) for details.

---

## References

- NDK39: `graphics/displayinfo.h`, `graphics/modeid.h`, `graphics/gfxbase.h`, `graphics/monitor.h`
- HRM: *Amiga Hardware Reference Manual* — Display, Custom Chip chapters
- ADCD 2.1: `NextDisplayInfo`, `GetDisplayInfoData`, `BestModeIDA`, MonitorSpec autodocs
- See also: [views.md](views.md) — ViewPort and View construction, mode transitions at ViewPort boundaries
- See also: [copper.md](copper/copper.md) — Copper display list programming, mid-screen mode switching
- See also: [ham_ehb_modes.md](ham_ehb_modes.md) — HAM6/HAM8/EHB in depth
- See also: [AGA Display Modes](../01_hardware/aga_a1200_a4000/aga_display_modes.md) — AGA-specific capabilities
- See also: [Chipset AGA](../01_hardware/aga_a1200_a4000/chipset_aga.md) — Lisa chip, FMODE, 24-bit palette
- See also: [DMA Architecture](../01_hardware/common/dma_architecture.md) — mode-dependent DMA slot budget, CPU bandwidth calculations
- See also: [Video Signal & Timing](../01_hardware/common/video_timing.md) — clock derivation, PAL/NTSC timing, beam counters, scandoublers
