More content

This commit is contained in:
Ilia Sharin 2026-04-27 12:42:42 -04:00
parent 9b0dfbcf32
commit a59d8350b3
6 changed files with 1674 additions and 19 deletions

View file

@ -24,7 +24,7 @@ The **Advanced Graphics Architecture** (AGA) is the final custom chipset develop
| [aga_display_modes.md](aga_display_modes.md) | HAM8, 256-color, doublescan, VGA |
| [aga_blitter.md](aga_blitter.md) | 64-bit blitter bus, FMODE |
| [aga_copper.md](aga_copper.md) | AGA Copper programming guide |
| [cpu_030_040.md](cpu_030_040.md) | 68030/040 on A3000/A4000: cache, MMU, FPU |
| [cpu_030_040.md](cpu_030_040.md) | 68030/040/060: accelerator catalog, benchmarks, named antipatterns (cache/DMA coherency, missing libraries, timing loops, chip bus speed), FPGA impact, FAQ |
| [akiko_cd32.md](akiko_cd32.md) | CD32 Akiko chip: C2P conversion, CD-ROM, NVRAM |
| [Gayle IDE & PCMCIA](../common/gayle_ide_pcmcia.md) | A1200 Gayle: IDE and PCMCIA (shared with A600) |

View file

@ -130,10 +130,295 @@ ULONG flags = CacheControl(0, 0); /* query without changing */
#define CACRF_CopyBack (1L<<31) /* data cache write-back mode */
```
## Real Accelerator Card Context
The A3000 and A4000 launched with Commodore's own CPU cards, but the accelerator ecosystem exploded with third-party offerings that pushed 68K performance well beyond what Commodore shipped.
### Commodore Cards
| Card | Machine | CPU | Clock | FPU | Notes |
|---|---|---|---|---|---|
| **A3630** | A3000 | 68030 | 25 MHz | 68882 socket | Standard A3000-25 CPU card, 2 SIMM slots for Fast RAM |
| **A3640** | A4000 | 68040 | 25 MHz | Integrated (partial) | Standard A4000-040 card; soldered 68040, no socket. Fast RAM on card via 4 SIMM slots |
| **A3660** | A4000 | 68060 | 50 MHz | Integrated (partial) | Rare — Commodore prototype; production by third parties post-Commodore |
### Third-Party Accelerators
| Card | Vendor | CPU | Clock | FPU | Memory | Key Feature |
|---|---|---|---|---|---|---|
| **Blizzard 1230-IV** | Phase5 | 68030 | 50 MHz | 68882 @ 50 MHz | Up to 256 MB Fast RAM (1 x 72-pin SIMM) | Defacto standard A1200 accelerator; MMU for virtual memory, 50 MHz gives ~3.5x A1200 performance |
| **Blizzard 1260** | Phase5 | 68060 | 50-66 MHz | Integrated (partial) | Up to 256 MB | End-game A1200 accelerator; the engine for high-end demos and Doom/Quake ports |
| **Blizzard 2060** | Phase5 | 68060 | 50 MHz | Integrated | Up to 128 MB | A2000 version; SCSI-II controller on-card |
| **CyberStorm MK-III** | Phase5 | 68060 | 50-66 MHz | Integrated | Up to 128 MB | A3000/A4000 flagship; Ultra-wide SCSI on-card |
| **CyberStorm PPC** | Phase5 | 68060 + PowerPC 604e | 50/60 MHz + 200-233 MHz | Integrated | Up to 128 MB | Hybrid 68K+PowerPC — AmigaOS 4.0 target; 68K handles legacy, PPC for new code |
| **WarpEngine 4040** | MacroSystem | 68040 | 40 MHz | Integrated | Up to 64 MB | A3000/A4000; 40 MHz 040 comfortably outperforms stock A3640-25 |
| **GVP A530** | GVP | 68030 + 68882 | 40 MHz | Socketed 68882 | Up to 8 MB | A500 sidecar accelerator — brings 32-bit CPU to the A500 trapdoor slot |
| **Apollo 1240** | ACT | 68040 | 25-40 MHz | Integrated | Up to 64 MB | Budget A1200 accelerator; needs 68040.library |
> **Rule for all 040/060 cards**: `68040.library` or `68060.library` must be present in `LIBS:` or the system will crash on the first missing FPU instruction. Install 68040.library/68060.library at `LIBS:` root, not in a subdirectory.
---
## Performance Benchmarks
Real-world performance from SysInfo, AIBB, and actual workloads rather than misleading MIPS numbers:
| CPU | Clock | SysSpeed MIPS | Dhrystones/sec | Memory BW (MB/s) | Real-World Factor vs A500 |
|---|---|---|---|---|---|
| 68000 (A500) | 7.14 MHz | ~1.0 | ~1,100 | ~1.5 (Chip only) | 1.0x (baseline) |
| 68020 (A1200 stock) | 14.3 MHz | ~3.0 | ~4,500 | ~3.5 | ~2.5x |
| 68030/50 (Blizzard 1230) | 50 MHz | ~9.5 | ~17,000 | ~12.0 | ~8x |
| 68040/25 (A3640) | 25 MHz | ~15.0 | ~28,000 | ~18.0 | ~12x |
| 68040/40 (WarpEngine) | 40 MHz | ~24.0 | ~45,000 | ~25.0 | ~19x |
| 68060/50 (Blizzard 1260) | 50 MHz | ~53.0 | ~95,000 | ~38.0 | ~38x |
| 68060/66 | 66 MHz | ~70.0 | ~125,000 | ~40.0 | ~50x |
| 68080 (Vampire V4) | ~90 MHz equiv | ~150+ | ~300,000+ | ~200+ (Fast + RTG) | ~130x |
### Important Caveats
- **Chip RAM bandwidth is fixed at ~3.5 MB/s** regardless of CPU speed. The CPU accelerator only speeds up Fast RAM access.
- **AGA bandwidth** (4x fetch) helps but only for Chip RAM reads by the display DMA, not CPU-to-Chip-memory traffic.
- **Bus contention**: on A1200 accelerators, the trapdoor connector limits bandwidth between CPU and Amiga chip bus to ~4-5 MB/s maximum.
---
## Named Antipatterns
### 1. "The Ghost Cache"
**What fails** — writing to a buffer for DMA but not flushing the data cache:
```c
/* BROKEN — data cache holds dirty data */
PrepareCopperList(copperBuffer); /* CPU writes to buffer */
/* copperBuffer is in CPU data cache, not yet in chip RAM */
WaitTOF(); /* switch copper lists */
/* Copper reads stale MAIN MEMORY, not the CPU's dirty cache lines */
```
**Why it fails:** On 68030+ CPUs, data writes stay in the data cache. The cache writes back to memory lazily — when the cache line is evicted, not when the write happens. DMA engines (Copper, Blitter, audio, Paula) read from main memory via the chip bus and see stale data because they bypass the CPU cache entirely.
**Correct:**
```c
PrepareCopperList(copperBuffer);
CacheClearE(copperBuffer, sizeof(copperBuffer), CACRF_ClearD);
WaitTOF();
/* Now Copper reads correct data */
```
---
### 2. "The Missing Library"
**What fails** — running 68040/68060 software without `68040.library` / `68060.library`:
```c
/* BROKEN — assumes full FPU */
double x = sin(angle); /* FSIN is not in 68040/68060 silicon */
/* Line-F exception -> system crash if 68040.library not installed */
```
**Why it fails:** The 68040 and 68060 omit transcendental FPU instructions (`FSIN`, `FCOS`, `FTAN`, `FLOG10`, etc.) that were present in the 68881/68882. When the CPU encounters these opcodes, it raises a Line-F exception. 68040.library and 68060.library install exception handlers that intercept Line-F exceptions and emulate the missing instructions in software — orders of magnitude slower than hardware but at least they complete. Without these libraries loaded, the exception has no handler and the system crashes immediately.
**Correct:**
```c
/* At startup, verify the appropriate library is open: */
if (SysBase->AttnFlags & AFF_68040)
{
struct Library *Math040 = OpenLibrary("68040.library", 37);
if (!Math040) { /* FATAL: install the library */ }
}
else if (SysBase->AttnFlags & AFF_68060)
{
struct Library *Math060 = OpenLibrary("68060.library", 37);
if (!Math060) { /* FATAL: install the library */ }
}
/* Then using FPU is safe: */
double x = sin(angle);
```
---
### 3. "The Calibrated Loop"
**What fails** — delay loops calibrated for 7 MHz 68000:
```c
/* BROKEN — spin-loop delay assumes 68000 timing */
void DelayCycles(ULONG n)
{
while (n--) /* assumes ~4 cycles per iteration at 7 MHz */
{
asm("nop"); /* pretend delay */
}
}
/* On 68060 at 50 MHz with superscalar, this is 30x faster and produces no delay at all */
```
**Why it fails:** 68060 has dual pipelining, branch prediction, and static + dynamic speculation. A "calibrated delay loop" that produces a 1-second delay on a 68000 may produce a 30 millisecond delay on a 68060. The function derives timing from loop geometry that changes with every CPU generation and clock speed.
**Correct:**
```c
/* Use ReadEClock() for CPU-independent timing: */
ULONG ReadEClockFreq(struct EClockVal *dest)
{
/* Stores 64-bit E-clock tick count and returns frequency */
/* Must open timer.device first */
}
struct EClockVal start, end;
ULONG efreq = ReadEClock(&start);
/* ... do work ... */
ReadEClock(&end);
ULONG elapsed_us = (ULONG)(((UQUAD)(end.ev_lo - start.ev_lo) * 1000000ULL) / efreq);
```
---
### 4. "The Speeding on the Chip Bus"
**What fails** — 68040/68060 code writing to custom chip registers at full CPU speed:
```c
/* BROKEN — 68060 writes to custom registers at 50 MHz */
custom->bltcon0 = 0x09F0; /* set up blitter parameters */
custom->bltsize = 0x0040; /* start the blit — too fast! */
/* The chip bus runs at 3.58 MHz and may miss the second write */
```
**Why it fails:** The Amiga custom chip bus runs at 3.58 MHz (color clock speed). The 68060 at 50 MHz can issue writes at a rate that overwhelms the chip bus's buffering. The writes go directly to custom registers whose cycle timing is far slower than the CPU's. Writing without sufficient delay between register accesses causes the second write to arrive before the first has settled, producing random chip states that are not replicable even with identical timing.
**Correct:**
```c
/* Ensure sufficient delay between custom register writes */
custom->bltcon0 = 0x09F0;
/* Wait for at least one color clock (~280 ns) */
asm volatile ("nop; nop; nop"); /* ~180 ns at 50 MHz */
custom->bltsize = 0x0040;
/* Better approach: use Blitter-done interrupt for synchronization */
```
---
## Pitfalls
### 1. Cache Coherency with CIA Chips
The 8520 CIA chips are on the custom chip bus. Reading CIA registers from cached memory returns stale values. Always access CIA registers through non-cached memory regions or use `CacheClearU()` before reading. This affects keyboard handshaking, serial port timing, and disk change detection.
### 2. Address Alignment with Hardware Registers
68020+ CPUs support unaligned (odd address) accesses, but Amiga custom chips require WORD-even and LONG-even aligned addresses. An unaligned write to custom chip registers causes a bus error on 68020+. Always align pointers to 16-bit or 32-bit boundaries when pointing to hardware registers.
### 3. Chip RAM Speed Never Changes
Regardless of CPU speed, **Chip RAM always accesses at 7.09 MHz (PAL) / 7.16 MHz (NTSC)** color clock. A 68060 at 50 MHz reads Chip RAM at the same bandwidth as the 68000 at 7 MHz. The only way to increase Chip RAM bandwidth is through wider fetch modes on AGA (FMODE), and even that only helps display DMA, not CPU access to Chip RAM.
---
## Best Practices
1. **Always call `CacheClearE` before handing a CPU-written buffer to DMA** — this includes Copper lists, blitter sources, audio samples, disk buffers, and sprite data.
2. **Check `SysBase->AttnFlags` for AFF_68040 / AFF_68060** — load the corresponding math libraries at startup and abort if missing.
3. **Use `ReadEClock()` for timing, not instruction-counting loops** — this works across all CPU generations.
4. **Do NOT write to custom chip registers in tight loops without NOP delays** — give the chip bus hundreds of nanoseconds between successive writes.
5. **Align memory pointers to hardware registers** — unaligned accesses cause bus errors on 68020+.
6. **Only touch CacheControl bits you understand** — CACRF_CopyBack (bit 31) writes dirty data back, affecting performance dramatically. Do not experiment in production.
7. **Cache-pure code in Fast RAM** — 68020+ CPUs fetch instructions from Fast RAM at full CPU speed, but data that lives in Chip RAM is access-penalized at ~3.5 MB/s regardless of CPU speed.
---
## When to Use / When NOT to Use
| Scenario | Recommended CPU | Why |
|---|---|---|
| Safe baseline for any Amiga | 68000 | Every Amiga has at least this |
|lection A500/A600/A1200 original | Their stock CPU | 68000/68020 respectively |
| Compatibility testing | 68020+ with cache off | Test both cached and uncached |
| Daily Amiga workstation | 68030/50 or 68040/40 | 8-19x performance, smooth multitasking |
| Doom / Quake / 3D gaming | 68060/50 minimum | Triple-digit MIPS needed; FPU for rendering |
| Vampire / FPGA | Apollo 68080 | Maximum performance on any Amiga; 130x real-world factor |
---
## FPGA & MiSTer Impact
68020+ and 68040+ live differently on FPGA:
| Topic | Real Hardware | FPGA Concern |
|---|---|---|
| **Data cache on TG68K** | TG68K is a cycle-accurate 68000 behavior target; no data cache, no MMU | Cache advice in this article does not apply to TG68K-based cores |
| **Minimig 68020** | Minimig supports 68020 with instruction cache only |alan data cache — needed for 68020 timing benchmarks |
| **Apollo 68080** | Integrates caches, superscalar execution, and pipelining; correctly decodes all FPU instructions | Needs 68080.library or equivalent; behavior matches but is not cycle-accurate to any historical 68K CPU |
| **CacheClearE/CacheClearU** | Needed on real 68030+ | Must NOP on TG68K (no cache) but MUST work on Apollo; test both paths |
---
## Historical Context & Modern Analogies
### CPU Evolution 1985-1994
The Amiga's CPU story mirrors the broader 68K family evolution:
| Year | CPU | Amiga | Significance |
|---|---|---|---|
| 1979 | 68000 | A1000/A500/A2000 | 16/32-bit hybrid; 68,000 transistors |
| 1984 | 68020 | A1200 (1992) | Full 32-bit; 256-byte I-cache; 190,000 transistors |
| 1987 | 68030 | A3000, accelerators | Integrated MMU; 256-byte I+D caches; 273,000 transistors |
| 1990 | 68040 | A4000, accelerators | Integrated FPU (partial); 4 KB+4 KB caches; 1.2M transistors |
| 1994 | 68060 | Accelerators only | Superscalar; 8 KB+8 KB caches; 2.5M transistors |
Commodore's collapse in 1994 meant the 68060 never shipped in a stock Amiga. Every 68060 Amiga is an aftermarket upgrade.
### Modern Analogies
| Amiga Concept | Modern Equivalent | Connection |
|---|---|---|
| 68030 data cache + DMA coherency problem | Non-cache-coherent DMA in embedded ARM SoCs | Same pattern: software must explicitly flush before DMA |
| 68040/68060 missing FPU instructions | x86 microcode for complex instructions (FSIN etc.) | Same approach: hardware traps to software emulation for rare operations |
| CacheControl(CACRF_CopyBack) | x86 MTRR / PAT (Memory Type Range Registers) | Same goal: mark memory regions with different caching policies |
| 68060 superscalar dual-pipeline | In-order superscalar ARM Cortex-A8 | Same architecture: two issue slots, in-order dispatch, no OOO |
| Trapdoor accelerator slot | PCIe add-in card (modern GPU) | Same pattern: aftermarket CPU upgrade via a bus connector |
---
## FAQ
### Q: When do I need 68040.library?
If `SysBase->AttnFlags & AFF_68040` is set, the system has a 68040 CPU. 68040.library MUST be installed at `LIBS:68040.library` or the system crashes on first missing FPU instruction. The library is not optional. Similarly for 68060.library when AFF_68060 is set.
### Q: Can I turn off caches for compatibility?
Yes — `CacheControl(0, CACRF_EnableI | CACRF_EnableD)` disables both caches. Some legacy OCS software written with 68000 timing assumptions behaves correctly only with caches off. Disabling caches reduces 68040 performance to roughly 68000 levels.
### Q: Is a 68040/25 faster than a 68030/50?
In raw integer math, the 68040/25 is roughly 50% faster (15 MIPS vs 9.5 MIPS). But the 68030/50 has a socketed 68882 running at 50 MHz, giving it faster FPU performance for math-heavy workloads. For most Amiga software (integer-bound), the 68040/25 wins. For FPU-bound tasks (raytracing, 3D), the 68030/50+68882 combo is faster.
### Q: Why does my 68060 system run some demos than a 68030?
The 68060 achieves its speed via caches, pipelining, and branch prediction. Demos that use self-modifying code, precise cycle-counted Copper/CPU sync, or tight write-read sequences to custom registers break these optimizations. The 68060 must frequently flush pipelines and invalidate caches, reducing effective throughput below 68030 levels for certain pathological code patterns.
### Q: Can I upgrade an A3640 to 68060?
Not directly. The A3640 is a 68040 card with a soldered CPU. Third-party adapters (CyberStorm, WarpEngine) replace the entire CPU card. Some prototypes (A3660) exist but are extremely rare. Most 68060 upgrades use Phase5 CyberStorm boards.
---
## References
- NDK39: `exec/execbase.h`, `proto/exec.h` — CacheControl, CacheClearE, AttnFlags
- Motorola 68030 User's Manual (M68030UM/AD)
- Motorola 68040 User's Manual (M68040UM/AD)
- NDK39: `exec/execbase.h`, `proto/exec.h` — CacheControl, CacheClearE
- Motorola 68060 User's Manual (M68060UM/AD)
- ADCD 2.1: `Libraries_Manual_guide/` — exec CacheControl autodoc
- Commodore A3000/A4000 Technical Reference Manuals
- See also: [68040/68060 Libraries](../../15_fpu_mmu_cache/68040_68060_libraries.md) — detailed FPU trap handler operation
- See also: [Cache Management](../../15_fpu_mmu_cache/cache_management.md) — cache control deep-dive
- See also: [MMU Management](../../15_fpu_mmu_cache/mmu_management.md) — MMU setup on 68030/040/060
- See also: [FPU Architecture](../../15_fpu_mmu_cache/fpu_architecture.md) — 68881/68882 vs 68040/060 FPU comparison
- See also: [AGA Chipset](chipset_aga.md) — Alice/Lisa co-processors, FMODE

View file

@ -12,7 +12,7 @@
| [pattern_matching.md](pattern_matching.md) | ParsePattern, MatchPattern, AmigaDOS wildcard syntax |
| [process_management.md](process_management.md) | CreateNewProc, SystemTagList, Execute |
| [packet_system.md](packet_system.md) | DosPacket wire format, ACTION_* codes, handler protocol, BSTR encoding |
| [filesystem.md](filesystem.md) | FFS/OFS block layout, root/file/data blocks, bitmap, hash function, ADF reader |
| [filesystem.md](filesystem.md) | FFS/OFS block layout, root/file/data blocks, bitmap, hash function; capabilities matrix (max sizes, directory limits, nesting, floppy/HDD); third-party deep-dive (PFS3, SFS, FFS2 — atomic commits vs journaling vs validation); **fragmentation & defragmentation** (bitmap scatter, defrag tool catalog — ReOrg/DiskSafe/Quarterback/SFSdefrag, CF/SSD rationale, best practices); **partition IDs & on-disk layout** (two-layer RDB/signature system, complete DosType hex tables, ASCII diagrams for OFS/FFS/PFS3/SFS metadata placement); **RDB partitioning** (RigidDiskBlock, PartitionBlock linked list, FS headers, partition limits); **Kickstart boot process** (Strap, BootPri selection, Early Startup Menu, Mermaid flowchart); **multi-boot configurations** (dual-OS, recovery, games); partition layout strategy table; ADF reader (Python on modern machine) |
| [environment.md](environment.md) | GetVar/SetVar, local/global/persistent env variables |
| [error_handling.md](error_handling.md) | IoErr, Fault, PrintFault, complete error code table |
| [cli_shell.md](cli_shell.md) | CLI/Shell: pipes, redirection, scripts, ReadArgs template parsing |

View file

@ -4,7 +4,48 @@
## Overview
AmigaOS supports two native filesystem types: **OFS** (Old File System, OS 1.x) and **FFS** (Fast File System, OS 2.0+). Both use a block-based layout with 512-byte blocks. FFS differs by storing data blocks without headers, improving throughput. Understanding the on-disk layout is essential for FPGA core developers implementing virtual filesystems, HDF support, and ADF image handling.
AmigaOS supports two native filesystem types: **OFS** (Old File System, OS 1.x) and **FFS** (Fast File System, OS 2.0+). Both use a block-based layout with 512-byte blocks and work on floppies and hard disks alike. FFS improves throughput by stripping per-block headers from data blocks. Both are limited to 4 GB partitions (32-bit signed arithmetic), 30-character filenames (107 with DOS\6/\7 on OS 3.5+), and offer no journaling — a crash forces a full validation pass that rebuilds the bitmap, sometimes losing files. Third-party replacements (**PFS3**, **SFS**, **FFS2** in OS 3.5+) address these limits with atomic commits, >100 GB volumes, and extent-based allocation. Understanding the on-disk layout is essential for FPGA core developers implementing virtual filesystems, HDF support, and ADF image handling.
---
## Filesystem Capabilities & Limits
### Native Filesystems — OFS & FFS
| Capability | OFS (DOS\0) | FFS (DOS\1) | Notes |
|---|---|---|---|
| **Max partition size** | 4 GB | 4 GB (2 GB on KS 1.3) | 32-bit signed block/byte arithmetic; FFS2 in OS 3.5+ raises this with NSD/TD64 |
| **Max file size** | ~4 GB | ~4 GB | 32-bit `byte_size` field in file header |
| **Max files per directory** | Unlimited¹ | Unlimited¹ | Hash chains grow without bound; practical limit is volume block exhaustion |
| **Max filename length** | 30 chars | 30 chars (107 with DOS\6/\7) | BSTR length-prefixed; OS 3.5+ adds long filename support |
| **Max path length** | ~256 chars | ~256 chars | Limited by DOS packet buffer; no explicit OS-enforced limit |
| **Max directory nesting** | No hard limit | No hard limit | Practical limit: ~50 levels before path buffer overflows |
| **Block size** | 512 bytes (fixed) | 512 bytes (fixed) | Hardcoded; block size is never configurable |
| **Media support** | Floppy + HDD | Floppy + HDD | Both work identically on any block device |
| **International mode** | DOS\2 | DOS\3 | Case-insensitive filename comparison for non-ASCII locales |
| **Directory caching** | No | DOS\4 | Caches directory block lookups in RAM |
| **Crash resilience** | None | None | Both require full validation after unclean shutdown; no journaling, no atomic writes |
| **Metadata checksums** | Yes | Yes | Every block (root, file header, directory, data) has a checksum at offset $014 |
| **Self-describing blocks** | Yes | No | OFS data blocks contain back-pointers to file header; FFS data blocks are raw — unrecoverable if lost |
¹ **Directory file count in practice:** A directory's hash table has 72 slots (DD floppy) or 128+ (hard disk partitions). Each slot starts a collision chain. With a good hash distribution, 72-entry tables comfortably handle ~500 files before chain-walking overhead becomes noticeable. The theoretical limit is the number of free blocks on the volume — every file/directory entry consumes at least one block.
### Third-Party Filesystems
| Capability | PFS3 (TD64) | PFS3 (DirectSCSI) | SFS | FFS2 (OS 3.5+) |
|---|---|---|---|---|
| **Max volume size** | 104 GB | 104 GB | 127 GB | >4 GB (NSD/64-bit) |
| **Max file size** | ~4 GB | ~4 GB | 4 GB | >4 GB (64-bit offsets) |
| **Max filename** | 107 chars | 107 chars | 107 chars | 107 chars |
| **Block sizes** | 512, 1024, 2048, 4096 | 512, 1024, 2048, 4096 | 512 to 32768 | 512 (fixed) |
| **Media support** | **HDD only** | **HDD only** | **HDD only** | Floppy + HDD |
| **Atomic commits** | ✅ Yes | ✅ Yes | ✅ (journaling) | ❌ No |
| **Crash recovery** | Instant — always consistent | Instant — always consistent | Replays journal (~seconds) | Full validation (minutes) |
| **Delayed writes** | Yes (safe — atomic) | Yes (safe — atomic) | Yes | Dangerous — can corrupt bitmap |
| **Extent-based allocation** | ✅ | ✅ | ✅ | ❌ (bitmap) |
| **Multi-user support** | MuFS variant available | MuFS variant available | ❌ | ❌ |
| **Min CPU requirement** | 68000 | 68000 | 68020+ | 68020+ (OS 3.5 req.) |
| **KS version** | 2.0+ | 2.0+ | 2.0+ | 3.5+ |
---
@ -21,13 +62,197 @@ Block 880: Root block (always at disk midpoint)
### HDF (Hard Disk File) — Hard Drive
Hard disks use the **RDB (Rigid Disk Block)** partitioning scheme — a flexible linked-list structure far more capable than the PC MBR of the same era. There is no distinction between "primary" and "logical" partitions; all partitions are peers in a singly-linked list.
```
Block 0: Reserved (or RDB — Rigid Disk Block)
Block 1+: RDB partition table (if present)
Partition: Starts at first block of partition
Root block at partition midpoint
Block 0: RDB (Rigid Disk Block) — "RDSK" signature
Block 1N: Partition blocks (linked list, one per partition)
├─ Partition 0: DH0 (Workbench)
├─ Partition 1: DH1 (Work)
├─ Partition 2: DH2 (Games)
└─ Partition N: ...
Block N+1: First partition data begins
(may be offset for cylinder alignment)
```
### RDB (Rigid Disk Block) — Block 0
The RDB is the master header for the entire physical disk. Its 4-byte signature is `RDSK` ($5244534B):
```
Offset Size Field Description
────── ────── ─────────────────── ──────────────────────────────────
$00 4 rdb_ID "RDSK" signature
$04 4 rdb_SummedLongs Size of checksummed area (usually 64)
$08 4 rdb_ChkSum Checksum
$0C 4 rdb_HostID Host SCSI ID (for multi-initiator buses)
$10 4 rdb_BlockBytes Block/sector size (usually 512)
$14 4 rdb_Flags Disk flags
$18 4 rdb_BadBlockList Pointer to bad-block list
$1C 4 rdb_PartitionList ➜ First PartitionBlock in linked list
$20 4 rdb_FileSysHdrList ➜ Filesystem header list
$24 4 rdb_DriveInit Drive initialization code pointer
$2C 4 rdb_Cylinders Physical cylinders
$30 4 rdb_Sectors Sectors per track
$34 4 rdb_Heads Heads (surfaces)
$38 4 rdb_Interleave Interleave factor
$3C 4 rdb_ParkingZone Head parking cylinder
$44 4 rdb_LoCyl Low cylinder (start of partitionable area)
$48 4 rdb_HiCyl High cylinder (end of partitionable area)
$4C 4 rdb_CylBlocks Blocks per cylinder = Heads × Sectors
$50 4 rdb_AutoParkSeconds Auto-park timeout
$54 4 rdb_HighRSVDBlock Highest reserved block for RDB area
```
> [!NOTE]
> Solaris fields `rdb_LoCyl` and `rdb_HiCyl` define the partitionable region. The RDB and partition blocks occupy the reserved area *before* `rdb_LoCyl`. Partition data starts at `rdb_LoCyl`.
### PartitionBlock — Linked List
Each partition is described by a PartitionBlock chained via `de_Next` pointers. TheRDB's `rdb_PartitionList` field points to the first one:
```
Offset Size Field Description
────── ────── ───────────────────── ──────────────────────────────────
$00 4 de_ID "PART" signature ($50415254)
$04 4 de_SummedLongs Checksum range size
$08 4 de_ChkSum Partition block checksum
$10 4 de_DosType Filesystem identifier (e.g., DOS\1 for FFS)
$18 4 de_Flags Partition flags (see below)
$1C 4 de_BootPri Boot priority (-127 to 127)
$20 4 de_LowCyl Start cylinder
$24 4 de_HighCyl End cylinder
$28 4 de_NumBuffers Suggested buffer count
$2C 4 de_BufMemType Memory type for buffers (MEMF_PUBLIC)
$30 4 de_MaxTransfer Max transfer size per I/O operation
$34 4 de_Mask Address mask for DMA alignment
$38 4 de_BootBlocks Boot block range (for KS 1.3: first block)
$3C 4 de_Surfaces Heads/surfaces for this partition
$40 4 de_SectorsPerTrack Sectors per track (usually 1 for modern)
$44 4 de_ReservedBlocks Reserved boot area at partition start
$48 4 de_PreAlloc Pre-allocated blocks (special use)
$4C 4 de_Interleave Interleave
$50 4 de_Next ➜ Next PartitionBlock in linked list (0 = end)
$54 4 de_SizeBlock Block size in bytes (usually 512, PFS3 can use 1024+)
$58 4 de_FileSysHdr ➜ Filesystem header for this partition (0 = use ROM FS)
```
### Partition Flags (de_Flags)
| Bit | Constant | Meaning |
|---|---|---|
| 0 | `PBF_BOOTABLE` | Partition can be selected for boot |
| 1 | `PBF_AUTOMOUNT` | Automatically mount on boot (off = not mounted) |
> [!WARNING]
> A partition with `PBF_AUTOMOUNT`=0 still exists on disk but will **not appear** as a DOS device until manually mounted. The Kickstart ignores non-automount partitions during the boot device scan. This is how "hidden" partitions for dual-boot are configured.
### Filesystem Header in RDB
The RDB can store **complete filesystem binaries** inline — this is how PFS3 and SFS work without patches to the Kickstart ROM. The `rdb_FileSysHdrList` field chains FileSysHdr blocks, each containing:
```
Offset Size Field Description
────── ────── ────────────── ──────────────────────────────────
$00 4 fh_ID "FSHD" signature
$04 4 fh_SummedLongs Checksum range
$08 4 fh_ChkSum Checksum
$0C 4 fh_DosType Filesystem DosType this binary handles
$10 4 fh_Version Filesystem version
$14 4 fh_Size Binary code size in bytes
$18 4 fh_Next ➜ Next FileSysHdr in linked list
$1C ... fh_Code[] Filesystem binary code
```
Each PartitionBlock's `de_DosType` encoding matches a FileSysHdr's `fh_DosType`. When the Kickstart boots, it looks up the filesystem code from the RDB, loads it into RAM, and uses it instead of (or alongside) the ROM filesystem.
### Partition Limits
| Limit | Value | Notes |
|---|---|---|
| **Max partitions** | Unlimited (RDB space limited) | Practical: 816 typical; ~30+ on large disks with expanded RDB area |
| **Min partition size** | 1 cylinder (~18 MB typical) | Depends on geometry; smaller is possible with logical sizes |
| **Max partition size** | ~2^73 bytes (8 ZiB) theoretical | Practical: 104 GB (PFS3), 127 GB (SFS), ~128 GB (FFS2 NSD) |
| **Bootable partitions** | Any number can be marked bootable | Kickstart selects highest BootPri; tie = first in list |
| **Filesystems per disk** | Multiple — each partition can use a different FS | OFS on DH0, PFS3 on DH1, SFS on DH2 — all valid |
| **KB included for RDB area** | Configurable via `rdb_HighRSVDBlock` | Default: ~28 MB; increase for many partitions or large FS binaries |
---
## Kickstart Boot Process — From Power-On to Bootable Partition
### Phase 1: ROM Initialization
1. **Early Startup** — Kickstart initializes exec, expansion.library scans for boot ROMs (Zorro cards, accelerator auto-boot ROMs)
2. **Device enumeration** — All block device drivers (scsi.device, trackdisk.device, expansion card drivers) register themselves with exec
3. **Strap module** — exec passes control to the Strap module, which builds a list of bootable devices
### Phase 2: Boot Device Selection
Strap enumerates every block device that reports a bootable partition and sorts by **boot priority** (highest first):
```
Device BootPri Partition Filesystem
──────────────────────────────────────────────────
DH0 (HDD) 5 Workbench FFS (DOS\1)
DF0 (Floppy) 3 — OFS or custom
CC0 (CD-ROM) 1 CD-ROM ISO 9660
```
- **Highest BootPri wins** — If DH0 has BootPri=5 and DF0 has BootPri=3, the hard disk boots first
- **Floppy override at ≥ BootPri 15**: Setting a hard disk partition's BootPri ≥ 15 overrides the floppy even when a floppy is inserted (normal behavior = floppy boots before HDD at default priorities)
- **Tie-breaking**: If two partitions have the same BootPri, the **first** in the RDB partition linked list wins
- **Custom boot blocks**: On floppies, the boot block can contain custom code that takes over before the normal DOS boot. Hard disks ignore boot blocks — they always use the RDB-internal boot path
### Phase 3: Loading the Filesystem
For the chosen partition:
1. Follow `de_FileSysHdr` pointer to the FileSysHdr block
2. If non-zero: copy the filesystem binary into RAM and jump to its Init routine (this is how PFS3/SFS load from RDB without being in Kickstart ROM)
3. If zero: use the built-in ROM filesystem (FFS at `L:FastFileSystem` from Kickstart)
4. The filesystem mounts the volume, reads the root block, and locates `S:Startup-Sequence`
```mermaid
graph TD
A[Power On] --> B[Kickstart Init]
B --> C[expansion.library scans for boot ROMs]
C --> D[Strap enumerates bootable devices]
D --> E{Bootable device found?}
E -->|Yes| F[Select highest BootPri partition]
E -->|No| G["Show 'Insert Workbench' screen"]
F --> H{de_FileSysHdr != 0?}
H -->|Yes| I[Load filesystem from RDB]
H -->|No| J[Use ROM FastFileSystem]
I --> K[Mount volume, read root block]
J --> K
K --> L[Execute S:Startup-Sequence]
```
### Phase 4: Early Startup Menu
Holding both mouse buttons at power-on interrupts the boot and displays the **Early Startup Menu**. This lets the user:
- Select a specific partition to boot (overrides BootPri)
- Disable individual partitions (useful for incompatible setups or RAM conservation)
- Toggle CPU caches on/off
- Display expansion board diagnostics
This is the primary way to dual-boot: you install multiple OS components on different partitions, set one as default (high BootPri), and use Early Startup to select alternatives. No bootloader software needed.
### Valid Multi-Boot Configurations
| Scenario | Setup | How to Boot |
|---|---|---|
| **Dual OS (3.1 + 3.9)** | Two bootable partitions: DH0=3.1 (BootPri 5), DH1=3.9 (BootPri 4) | Default boots 3.1; Early Startup selects DH1 for 3.9 |
| **OS 4.x Classic + 3.x** | Requires separate Kickstart ROMs (OS4 uses its own) | Typically hardware Kickstart switcher + Early Startup |
| **MorphOS + AmigaOS** | Separate partitions; MorphOS uses SFS-based fixing | Early Startup or MorphOS boot loader |
| **Recovery + Main** | Small emergency partition (OFS/FFS, BootPri 10) + Main partition (PFS3, BootPri 5) | Recovery auto-boots if main FS absent; Early Startup overrides |
| **Games-only + Workbench** | Games partition (BootPri 0, or Automount off) + Workbench partition (BootPri 5) | Early Startup to select games; otherwise invisible |
> [!NOTE]
> There is **no bootloader discovery mechanism** on classic Amigas — each OS install is independent. You can't chain-load from one partition to another (like GRUB). The Early Startup Menu or BootPri remapping tools (`BootPri`, `ChangeBootPri`) are the standard methods.
---
## Block Types
@ -257,8 +482,269 @@ Each extension block adds 72 more data block pointers. With FFS (512 bytes/block
---
## Third-Party Filesystems — Deep Dive
### Why Replace FFS?
OFS and FFS were designed for floppy-sized media in the mid-1980s. By the mid-1990s, hard drives exceeded 4 GB routinely, and FFS's lack of crash resilience made it a liability. Three major replacements emerged, each tackling different pain points.
### PFS3 (Professional File System 3)
Developed by Michiel Pelt (Greed Developments), PFS3 is the gold standard for modern Amiga setups. Originally commercial (GBP 36), now open-source and freely available on Aminet (`PFS3_53.lha`).
**How it works:** PFS3 uses **atomic commits** — metadata changes are written to a new location first, then a single pointer update makes them visible. If power fails mid-write, the old state remains intact. The filesystem is always consistent; there is no validation pass, ever.
**Key features:**
- **Delayed writes are safe** — dirty buffers commit atomically; unlike FFS, where a crash during delayed writes corrupts the bitmap
- **Extent-based allocation** — contiguous runs of blocks reduce fragmentation and improve sequential read speed
- **Configurable block sizes** — 512, 1024, 2048, or 4096 bytes; larger blocks reduce metadata overhead for big files
- **Directory indexing** — internal B-tree-style structure ("anode" tree) eliminates hash chain walking for large directories
- **Long filenames** — 107 characters, same as DOS\6/\7
- **Multi-user variant** — MuFS version integrates with Geert Uytterhoeven's multi-user filesystem for UNIX-style permissions
**Two variants:**
| Variant | Driver Interface | When to Use |
|---|---|---|
| **TD64** | TrackDisk64 commands | Modern IDE/SCSI controllers that support TD64 (PowerFlyer, FastATA, etc.) |
| **DirectSCSI** | Raw SCSI commands, bypasses device layer | A1200/A4000 internal IDE port; older controllers without TD64 support |
> [!WARNING]
> PFS3 is **hard disk only**. It cannot be used on floppy disks — its metadata structures require more space than an 880 KB floppy provides.
**Pros:**
- Zero validation time — reboot is instant after any crash
- 25× faster than FFS on accelerated machines (68030+), especially with large directories
- No fragmentation problems with extent-based allocation
- Open source, actively maintained (Aminet `PFS3_53`)
- Block size tuning for workload (512 for many small files, 4096 for large media files)
**Cons:**
- No floppy support — floppy-only workflows must stay on OFS/FFS
- Manual installation — must be copied into RDB and each partition reconfigured
- Version confusion — PFS2 CD + PFS3 update disk was the original distribution; modern users should get PFS3_53 directly from Aminet
- Older-era sizing quirks — 68000 version exists but is slow; real benefits only on 68020+
### SFS (Smart File System)
Developed by John Hendrikx, SFS takes a journaling approach rather than PFS3's atomic-commit model. It records pending changes to a journal before applying them; after a crash, the journal is replayed.
**Key features:**
- **Journaling** —changes logged before application; replay takes seconds, not minutes
- **Large block support** — up to 32 KB blocks, reducing metadata overhead for media files
- **Extent-based allocation** — similar to PFS3; contiguous runs reduce fragmentation
- **Defragmentation tools** — SFS includes built-in defrag utilities (PFS3 largely doesn't need them)
- **127 GB max volume** — slightly larger than PFS3's 104 GB limit
**Pros:**
- Larger max volume than PFS3 (127 GB vs 104 GB)
- Journal replay is fast (seconds vs FFS minutes)
- Large block support good for media-heavy workloads
- Built-in defragmentation
**Cons:**
- Requires 68020+ CPU minimum (no 68000 version)
- Journal replay still takes time (PFS3 is instant)
- 4 GB max file size (same as FFS)
- Less widely adopted than PFS3 in the modern Amiga community
- HDD only — same as PFS3
### FFS2 — Fast File System 2 (OS 3.5 / 3.9)
AmigaOS 3.5 and 3.9 ship an updated FFS that adds 64-bit support via NSD (New Style Device) or TD64. This is the "official" large-disk solution, but carries forward FFS's fundamental validation problem.
**Key features:**
- **64-bit arithmetic** — partitions and files >4 GB (requires NSD patching at boot via `SetPatch`)
- **Long filenames** — 107 characters (DOS\6/\7)
- **Backward compatible** — still works on floppies; still uses bitmap allocation
- **NO journaling or atomic commits** — crashes still trigger full validation
**Pros:**
- Official Commodore/Haage & Partner solution
- Works on floppies (unlike PFS3/SFS)
- Backward compatible with all old tools
- No third-party installation needed on OS 3.5+
**Cons:**
- Still validates after every crash — can take minutes on large drives and sometimes fails
- NSD requires boot-time patching (`NSDPatch.cfg` in `DEVS:`)
- Old disk tools (DiskSalv, Quarterback, ReOrg) break on >4 GB partitions
- Some older buffered IDE interfaces incompatible with NSD
- Still 512-byte blocks, still bitmap allocation, still no atomicity
### Which Filesystem Should I Use?
```mermaid
graph TD
A[Need floppy support?] -->|Yes| B[Use OFS or FFS]
A -->|No| C[What CPU?]
C -->|68000| D[PFS3 \n68000 version]
C -->|68020+| E[PFS3 \nor SFS]
E --> F[Priority: instant crash recovery?]
F -->|Yes| G[PFS3 \natomic commits]
F -->|No| H[Need >104 GB?]
H -->|Yes| I[SFS \n127 GB max]
H -->|No| G
```
---
## Crash Resilience — Validation vs Atomic Commits vs Journaling
### FFS/OFS: Validation
When an Amiga with FFS suffers an unclean shutdown (power loss, guru meditation, reset), the bitmap — which tracks free vs allocated blocks — may be out of sync with actual block usage. On next boot, DOS triggers **validation**:
1. Scan every block on the partition
2. Rebuild the bitmap from scratch by checking which blocks are referenced
3. Mark all unreferenced blocks as free
4. DOS generates "checksum error on block N" for any corrupt metadata
**Cost:**
| Drive Size | Typical Validation Time (68030/50) |
|---|---|
| 100 MB | ~1015 seconds |
| 500 MB | ~4590 seconds |
| 2 GB | ~36 minutes |
| 4 GB | ~612 minutes |
If validation fails (corrupt root block, broken hash chain), the volume remains inaccessible until DiskSalv or Quarterback repairs it — and files are often lost in the process.
### PFS3: Atomic Commits
PFS3 never validates. Every metadata operation follows a write-twice protocol:
1. Write new metadata to a free block (the "shadow copy")
2. Update a single root pointer to point to the new metadata
3. Mark the old block as free
If power fails at step 1 — old pointer still points to valid old data. If power fails at step 2 — the pointer update either completes (new state) or doesn't (old state). There is no intermediate corrupt state. The filesystem is **always consistent**.
### SFS: Journaling
SFS writes pending changes to a journal (a reserved area on disk) before modifying the main structures. After a crash:
1. On mount, check journal for uncommitted entries
2. Replay them in order (redo log)
3. Mark journal clean
Replay is fast (seconds) but not instant like PFS3. The journal itself can be corrupted by a crash during journal writes — a well-known journaling vulnerability that PFS3's atomic-pointer approach avoids entirely.
### Comparison
| Crash Scenario | OFS/FFS | FFS2 (OS 3.5+) | PFS3 | SFS |
|---|---|---|---|---|
| Power loss during file write | Validation required | Validation required | Instant recovery | Journal replay (~seconds) |
| Power loss during directory create | Validation required | Validation required | Instant recovery | Journal replay |
| Power loss during rename | Validation required | Validation required | Instant recovery | Journal replay |
| Power loss during format | Volume likely lost | Volume likely lost | Volume consistent (atomic) | Journal replay |
| Risk of file loss | Moderate to high | Moderate to high | **Zero** (by design) | Very low |
| Best for... | Floppy-only, 68000 | OS 3.5+ with floppy needs | **Any hard disk Amiga** | Large volumes >104 GB |
> [!WARNING]
> FFS "delayed writes" (buffered writes that flush later) are dangerous. If a crash occurs before the flush, the bitmap is corrupt. PFS3's delayed writes are safe because the atomic-commit protocol guarantees either the full write or nothing. **On FFS, always disable delayed writes** by not using `ACTION_WRITE` with buffering — or use `ACTION_FLUSH` after every critical write.
---
## Fragmentation & Defragmentation
### How Bitmap Allocation Causes Fragmentation
OFS and FFS allocate blocks from a bitmap — a flat, per-partition table where each bit represents one block (1 = free, 0 = allocated). The allocator scans the bitmap linearly and takes the first available block:
```
Allocating a 10-block file on a busy volume:
Free bitmap before: 1111011110 0001101111 1111001110 ...
^^^^ ^ ^^ ^
File blocks end up: [0][1][2][3] [4] [5][6] [7][8][9]
scattered across the bitmap
```
After files are created, modified, and deleted over weeks or months, the free blocks become a Swiss-cheese pattern of isolated gaps. A new file that needs 50 contiguous blocks will be split across dozens of non-contiguous groups — **fragmentation**.
### Quantified Impact
Fragmentation affects different media differently:
| Media | Seek Time | Fragmentation Penalty |
|---|---|---|
| **DD Floppy** (880 KB) | ~30 ms track-to-track | Negligible — small total blocks; everything is ~seeks away regardless |
| **Mechanical HDD** (1990s) | ~1520 ms avg | **Severe** — fragmented file can be 310× slower to read sequentially |
| **Mechanical HDD** (late 1990s) | ~812 ms avg | Moderate — faster seeks help, but still 24× penalty |
| **CF Card / SSD** (IDE adapter) | ~0.1 ms | **Negligible** — no mechanical seek; random access is nearly as fast as sequential |
> [!NOTE]
> On CF cards and SSDs connected via IDE adapter, fragmentation has effectively zero performance impact. The seek time is ~0.1 ms regardless of block location. This makes PFS3's anti-fragmentation features less critical on solid-state — the main reason to use PFS3 on a CF setup is **crash resilience**, not fragmentation avoidance.
### Visual: Fragmented vs Contiguous
```
Fragmented file (OFS/FFS bitmap allocation):
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ B0 │ │ │ B1 │ B2 │ │ │ B3 │ B4 │ B5 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
↑ ↑ ↑ ↑ ↑ ↑
Disk head seeks 4 times to read 6 blocks
Contiguous file (PFS3/SFS extent allocation):
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ B0 │ B1 │ B2 │ B3 │ B4 │ B5 │ │ │ │ │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
Disk head reads all 6 blocks in one sweep
```
### Defragmentation Tools
Since OFS/FFS has no built-in defragmentation, third-party tools filled the gap:
| Tool | Author | How It Works | Limitations |
|---|---|---|---|
| **ReOrg** | Dirk Stoecker | Reads all files, sorts by directory, rewrites contiguously; rebuilds bitmap | OS 3.1 only; breaks on >4 GB partitions (FFS2/NSD) |
| **DiskSafe** | — | Block-level defrag with safety checks; can resume after interruption | Slower than ReOrg; limited to FFS partitions |
| **Quarterback Tools** | — | Full suite: defrag, backup, repair, undelete | Commercial; breaks on >4 GB partitions; last update ~1997 |
| **SFS Defrag** | John Hendrikx | Built into SFS; moves extents to consolidate free space | SFS only; not needed for PFS3 |
**How ReOrg works:**
1. Scan the entire partition, building a file list sorted by directory path
2. For each file: read all data blocks into a buffer, then write them to a new contiguous region
3. Update the file header with new block pointers
4. Mark old blocks as free in a new bitmap
5. Relocate directory headers so files in the same directory are physically close
> [!WARNING]
> ReOrg and Quarterback rewrite every block on the partition. A power failure during defragmentation **destroys the filesystem** — all files become inaccessible. Always back up before defragmenting. PFS3 and SFS make this at-risk operation unnecessary.
### PFS3: Why It Rarely Fragments
PFS3 uses **extent-based allocation** instead of a bitmap. When a file grows, PFS3:
1. Reserves a contiguous run of blocks (an "extent")
2. If the file outgrows the extent, allocates a second extent — but each extent is internally contiguous
3. On delete, free extents can be merged with adjacent free space
The result: files are always stored in large contiguous chunks. Even "fragmented" PFS3 files are far less scattered than typical FFS files. In practice, PFS3 volumes rarely need defragmentation — the extent allocator's anti-fragmentation behavior is built-in.
### SFS: Extent + Defrag
SFS also uses extent-based allocation (like PFS3) but includes a user-invoked defragmenter. After heavy create/delete cycles, SFS volumes can develop free-space fragmentation (many small free extents rather than few large ones). Running `SFSdefrag` consolidates free space into fewer, larger extents.
### Best Practices to Avoid Fragmentation
1. **Use PFS3 or SFS** — extent-based allocation eliminates the bitmap scatter problem entirely
2. **Keep Workbench on a dedicated partition** — the boot partition sees frequent small writes (prefs files, env variables); isolating it prevents fragmenting your data partition
3. **Avoid filling partitions past 85%** — near-full volumes force the allocator to use every scattered free block
4. **On FFS: defragment quarterly** if using a mechanical hard drive; never defragment without a backup
5. **Large files first** — when creating a new FFS volume, copy large static files (archives, disk images) first so they get the big contiguous runs
6. **On CF/SD/SSD: don't bother defragmenting** — the seek time advantage is zero; the write-cycle cost of rewriting every block outweighs any benefit
---
## Practical: Reading an ADF Image
The following exercise runs on a modern computer (macOS, Linux, or Windows) using standard Python 3 with zero dependencies. We open a raw `.adf` floppy disk image — the bit-exact dump of an Amiga DD disk — and walk its on-disk structures from first principles: identify the filesystem type from the boot block signature, locate the root block at the partition midpoint, extract the volume name from its BSTR field, and enumerate every file and directory by traversing the hash table. By the end, you'll have a functional filesystem reader that understands OFS/FFS block layout without any Amiga runtime or emulator.
```python
import struct
@ -295,10 +781,466 @@ def read_adf(filename):
---
## Partition IDs, Signatures & On-Disk Data Layout
### Two-Layer Identification: RDB DosType vs On-Disk Boot Block
Understanding what you see in a disk editor requires knowing the **two-layer identification system**:
1. **RDB DosType** (`de_DosType` in PartitionBlock): Tells the Kickstart **which filesystem handler** to load from the RDB or ROM. This is the identifier you configure in HDToolBox when you "Change Filesystem" on a partition.
2. **On-Disk Boot Block Signature** (first 4 bytes of block 0/1 within the partition): The filesystem handler verifies this at **mount time** to confirm the partition was actually formatted with this filesystem and hasn't been corrupted.
> [!NOTE]
> For OFS and FFS, these two identifiers are **identical** — the RDB DosType IS the on-disk signature. For PFS3, **they are different** — the RDB uses `PFS\3` but the on-disk boot block uses `PFS\1` or `PFS\2`. This distinction is critical when analyzing raw disk images: the hex you see at the partition's first block may not match the hex you expect from HDToolBox's dropdown.
### Complete DosType & Signature Reference
#### OFS (Original File System)
| RDB DosType | Hex (BE uint32) | On-Disk Boot Block | Modes Enabled |
|---|---|---|---|
| `DOS\0` | `$444F5300` | `44 4F 53 00` | OFS (original), case-insensitive |
| `DOS\2` | `$444F5302` | `44 4F 53 02` | OFS International (case-sensitive) |
| `DOS\4` | `$444F5304` | `44 4F 53 04` | OFS Intl + Directory Cache |
| `DOS\6` | `$444F5306` | `44 4F 53 06` | OFS Intl + DirCache + Long Filenames (107 chars) |
#### FFS (Fast File System) & FFS2
| RDB DosType | Hex (BE uint32) | On-Disk Boot Block | Modes Enabled |
|---|---|---|---|
| `DOS\1` | `$444F5301` | `44 4F 53 01` | FFS (original), case-insensitive |
| `DOS\3` | `$444F5303` | `44 4F 53 03` | FFS International (case-sensitive) |
| `DOS\5` | `$444F5305` | `44 4F 53 05` | FFS Intl + Directory Cache |
| `DOS\7` | `$444F5337` | `44 4F 53 37` | FFS Intl + DirCache + Long Filenames (107 chars); = FFS2 on OS 3.5+ with NSD/TD64 64-bit support |
> [!NOTE]
> The byte at offset 3 increments with each feature tier: bit 0 = FFS (vs OFS), bit 1 = International, bit 2 = DirCache. `DOS\7` has bits 0+1+2 set (7 = 0b0111). This bitmask design means a simpler handler can read a more-capable partition's metadata, though only a matching handler can use all features.
#### PFS3 (Professional File System III)
| Use | Identifier | Hex (BE uint32) | Raw Bytes |
|---|---|---|---|
| **RDB DosType** — TD64 mode | `PFS\3` | `$50465303` | `50 46 53 03` |
| **RDB DosType** — DirectSCSI mode | `PDS\3` | `$50445303` | `50 44 53 03` |
| **RDB DosType** — Multi-User | `muFS` | `$6D754653` | `6D 75 46 53` |
| **On-disk boot block** — old format¹ | `PFS\1` | `$50465301` | `50 46 53 01` |
| **On-disk boot block** — new format² | `PFS\2` | `$50465302` | `50 46 53 02` |
¹ Reserved blocksize ≤ 1024 bytes, 512-byte sectors only.
² Reserved blocksize > 1024 bytes, or non-512-byte sectors (format introduced in PFS3 v17.4).
> [!WARNING]
> If you see `PFS\1` at block 0 of a PFS3 partition, that's **correct behavior** — it's the mount-time signature, not the RDB DosType. Do NOT change the RDB DosType expecting to match the hex boot block bytes. PFS3 intentionally separates these so that different versions can share the same RDB identifier while changing internal on-disk structures.
#### SFS (Smart File System)
| Use | Identifier | Hex (BE uint32) | Raw Bytes |
|---|---|---|---|
| **RDB DosType** — SFS v1.x | `SFS\0` | `$53465300` | `53 46 53 00` |
| **RDB DosType** — SFS v2.x | `SFS\2` | `$53465302` | `53 46 53 02` |
| **On-disk superblock** | `SFS\0` or `SFS\2` | matches RDB | matches RDB |
SFS uses the same identifier for both RDB and on-disk purposes, unlike PFS3's split system.
### Summary: What You See at Partition Block 0
When you open a raw partition in a hex editor, the first 4 bytes identify the filesystem:
| Bytes at Block 0 | Filesystem | What It Means |
|---|---|---|
| `44 4F 53 00` | OFS (DOS\0) | Original File System, suitable for floppy or hard disk |
| `44 4F 53 01` | FFS (DOS\1) | Fast File System, 512-byte data blocks only |
| `44 4F 53 03` | FFS Intl (DOS\3) | FFS with case-sensitive international filenames |
| `44 4F 53 05` | FFS Intl+DC (DOS\5) | FFS Intl with directory cache enabled |
| `44 4F 53 37` | FFS2/LNFS (DOS\7) | FFS with long filenames (and NSD/TD64 on OS 3.5+) |
| `50 46 53 01` | PFS3 (old format) | PFS3 boot block, metadata zone ≤ 1024B/block |
| `50 46 53 02` | PFS3 (new format) | PFS3 boot block, metadata zone > 1024B/block or non-512B sectors |
| `53 46 53 00` | SFS v1.x | Smart File System v1 |
| `53 46 53 02` | SFS v2.x | Smart File System v2 |
### On-Disk Data Layout — Where Does the FAT Live?
A common question from users familiar with FAT/NTFS/ext4: **"Where is the allocation table?"** The answer differs radically per filesystem, and knowing where metadata lives is essential for forensic analysis and emulator development.
#### OFS/FFS: Root Block at the Midpoint
```
OFS/FFS Partition Layout:
┌──────────────────────────────────────────────────────────────┐
│ BLOCK 0-1 │ ...data blocks scattered... │ ROOT │
│ Boot Block │ ◄── half of disk ──► │ BLOCK │
│ "DOS\x" │ (file headers, data blocks, │ ┌──────┐ │
│ │ bitmap blocks interleaved) │ │Volume│ │
│ │ │ │ Name │ │
│ │ │ │Hash │ │
│ │ │ │ Tbl │ │
│ │ │ │──────│ │
│ │ │ │Bitmap│ │
│ │ │ │ Ptrs │ │
│ │ │ └──────┘ │
│ │ │ │
│ │ ...data blocks continue... │ │
└──────────────────────────────────────────────────────────────┘
│◄───────── total_blocks / 2 ────────►│◄── total_blocks / 2 ──►│
```
**Key insight:** There is no FAT at the beginning. Metadata is at the **midpoint**:
- **Root block** at `⌈total_blocks / 2⌉` (e.g., block 880 on a DD floppy of 1760 blocks)
- **Bitmap** blocks are scattered — the root block points to them via extension fields; they track free/used blocks (1 = free, 0 = used)
- **File header blocks** are everywhere — they contain the data block pointer list (last-to-first ordering), file size, name, and protection bits
- **Data blocks** are everywhere — in FFS, 100% payload (512 bytes); in OFS, 488 bytes of payload + 24 bytes of self-describing header
This midpoint placement was inherited from early Unix filesystem design (the superblock was placed at the center to minimize average seek distance). It makes OFS/FFS distinctive: in a hex editor, the first interesting metadata after the boot block doesn't appear until halfway through the image.
#### PFS3: Metadata Zone at the Beginning
```
PFS3 Partition Layout:
┌──────────────────────────────────────────────────────────────┐
│ BLOCK BLOCK ...metadata blocks... │ │
│ 0 1N │ DATA │
│ ┌──────┬──────────────────────────────────────┐ │ BLOCKS │
│ │Boot │ RESERVED / METADATA ZONE │ │ (extent- │
│ │Blk │ ┌──────┬──────┬──────┬──────┬──────┐ │ │ based │
│ │PFS\1 │ │Root │Rsrvd │Anode │Dir │Anode │ │ │ alloc) │
│ │or │ │Block │Bitmap│Idx │Blocks│Blocks│ │ │ │
│ │PFS\2 │ │ │ │Blks │(DB) │(AB) │ │ │ ... │
│ └──────┘ └──────┴──────┴──────┴──────┴──────┘ │ │ │
│◄── reserved area (2256+ blocks) ──────────► │ │
└──────────────────────────────────────────────────────────────┘
```
**Key insight:** Unlike OFS/FFS, PFS3 stores **all metadata at the beginning**:
- **Boot block** at block 0 (512 bytes; second copy at block 1 for redundancy)
- **Root block** immediately after boot block in the reserved zone. Its `disktype` field matches the boot block (`PFS\1` or `PFS\2`)
- **Reserved bitmap** tracks free blocks within the metadata zone itself
- **Anode index blocks** — an anode is an "allocation node" that describes a contiguous extent of data blocks. Index blocks are indirect pointers for large numbers of anodes
- **Directory blocks** (signature `DB` at UWORD offset 0): contain directory entries (filenames, protection bits, anode numbers) in variable-length records
- **Anode blocks** (signature `AB`): contain the extent descriptors — `blocknr` (start), `clustersize` (length in blocks), `next` (pointer to next anode for file)
- **Data blocks** begin after the metadata zone — allocated as extents (contiguous runs), tracked by anodes, with no per-block metadata overhead
This "metadata-first" design has two advantages: (a) the root is always at a known location (block ~1), no midpoint calculation needed; (b) the metadata zone is read once at mount time and cached, so all subsequent I/O goes directly to data blocks with no metadata interleaving.
#### SFS: Combining Both Approaches
```
SFS Partition Layout:
┌─────────────────────────────────────────────────────────────────┐
│ BLK 0 BLK 1 BLK 2 ...early blocks... │ │
│ ┌──────┬──────┬─────────────────────────────────────┐ │ DATA │
│ │Super-│Reser-│ MASTER DIRECTORY BLOCK (MDB) │ │ BLOCKS │
│ │block │ved │ ┌──────┬──────┬──────┬───────────┐ │ │ (extent │
│ │SFS\0 │ │ │Obj │Extent│Dir │Free Space │ │ │ based) │
│ │or │ │ │Tree │B-Tree│B-Tree│Bitmaps │ │ │ │
│ │SFS\2 │ │ │Root │Root │Root │ │ │ │ ... │
│ └──────┴──────┘ └──────┴──────┴──────┴───────────┘ │ │ │
│◄── metadata at beginning ──────────────────────────►│ │
└─────────────────────────────────────────────────────────────────┘
```
**Key insight:** SFS, like PFS3, places metadata at the beginning but uses a different structure:
- **Superblock** at block 0 (`SFS\0` or `SFS\2`), block 1 reserved
- **Master Directory Block** at logical block 2 — the root of all filesystem metadata
- **Object tree** (B-tree): maps object IDs to file/directory metadata
- **Extent B-tree**: tracks allocated extents (similar to PFS3's anodes but organized as a B-tree for O(log n) lookup)
- **Directory B-tree**: stores directory entries, allowing O(log n) filename lookups (vs FFS's O(n) hash chain traversal)
- **Free space bitmaps** for allocation bookkeeping
- SFS supports much larger block sizes (up to 32 KB), which can reduce the metadata-to-data ratio for large files
### Quick Comparison: Where Is Everything?
| Question | OFS/FFS | PFS3 | SFS |
|---|---|---|---|
| Where is the root block? | **Midpoint** (total/2 up) | **Block ~1** (after boot) | **Block 2** (MDB) |
| Where is the free-space map? | Bitmap blocks, pointed to by root | Reserved bitmap (in metadata zone) | Free space bitmaps + Extent B-tree |
| Where are directory entries? | Hash table in root/dir header blocks → file header blocks | Directory blocks (`DB`) in metadata zone | Directory B-tree (in metadata zone) |
| Where are file data blocks? | Everywhere, linked via singly-linked lists | After metadata zone, allocated as extents | After metadata zone, allocated as extents |
| Is metadata interleaved with data? | Yes — OFS embeds headers; FFS separates but scatters | No — metadata zone is contiguous at beginning | No — metadata is at beginning, separate from data |
| What identifies a file's data blocks? | Last-to-first pointer list in file header block | Anode chain (contiguous extent descriptors) | Extent B-tree entries |
> [!NOTE]
> The "root block at midpoint" vs "metadata at beginning" distinction is the single most important difference when scanning a raw disk image. If you see meaningful structured data at block 0 and then nothing recognizable for thousands of blocks, you're looking at FFS/OFS — the root block lives at the midpoint. If you see structured metadata immediately after the boot block, you're looking at PFS3 or SFS.
### 1. "The Big-Endian Blindfold"
**What fails:** reading on-disk structures with native-endian assumptions:
```python
# BROKEN: interprets multi-byte fields in host byte order
type_id = struct.unpack('<I', block[0:4])[0] # Little-endian!
# On x86, reads byte-swapped value
```
**Why it fails:** The Amiga is Big-Endian (Motorola byte order). Every multi-byte value on disk (type, header_key, checksum, byte_size) is big-endian. Modern x86 and ARM are little-endian. Using native byte order produces swapped field values that fail checksums and point to garbage blocks.
**Correct:**
```python
# Always use big-endian unpack format:
type_id = struct.unpack('>I', block[0:4])[0]
```
### 2. "The Checksum Cheater"
**What fails:** reading a block without validating its checksum:
```c
/* BROKEN: trusts block data */
ULONG fileSize = header[0x148]; /* possibly garbage */
for (int i = 0; i < 72; i++) {
ULONG dataBlock = header[0x18 + i * 4];
ReadBlock(dataBlock, buffer);
}
```
**Why it fails:** A single bit-flip passes the type check but corrupts all subsequent fields. OFS/FFS uses a checksum (sum of 128 longs must be 0). Always verify before trusting field values.
**Correct:**
```c
LONG sum = 0;
for (int i = 0; i < 128; i++)
sum += ((ULONG *)block)[i];
if (sum != 0) return ERROR_CHECKSUM_FAIL;
ULONG fileSize = ((ULONG *)header)[0x148 / 4];
```
### 3. "The Forward Hash Walker"
**What fails:** walking data_blocks in natural index order:
```c
/* BROKEN: reads blocks in index order */
for (int i = 0; i < 72; i++) {
ReadDataBlock(fileHeader->data_blocks[i], &buf[i * 488]);
}
/* File content assembled BACKWARDS */
```
**Why it fails:** Data block pointers are stored last-to-first (BCPL heritage). Index 71 = first data block, index 0 = last. Reading in index order assembles the file backwards.
**Correct:**
```c
/* Read in REVERSE index order */
for (int i = 71; i >= 0; i--) {
if (fileHeader->data_blocks[i] == 0) continue;
ReadDataBlock(fileHeader->data_blocks[i], &buf[offset]);
offset += (isFFS ? 512 : 488);
}
```
### 4. "The BSTR Stumble"
**What fails:** treating BSTR as a null-terminated C string:
```c
/* BROKEN: assumes null-terminated */
printf("Volume: %s\n", rootBlock[0x1B0]);
/* Prints garbage past the name */
```
**Why it fails:** AmigaDOS uses BSTR format: first byte = length, followed by characters, NO null terminator. Passing to printf reads random memory until a null appears.
**Correct:**
```c
UBYTE len = rootBlock[0x1B0];
char name[32];
memcpy(name, &rootBlock[0x1B1], len);
name[len] = '\0';
printf("Volume: %s\n", name);
```
### 5. "The Partition Table Skipper"
**What fails:** assuming every image starts with a boot block:
```python
# BROKEN: checks for DOS at offset 0
fs_type = data[0:4]
if fs_type not in [b'DOS\x00', b'DOS\x01']:
raise ValueError("Bad filesystem")
# Fails on hard disk images with RDB
```
**Why it fails:** HDF images start with RDB (Rigid Disk Block, "RDSK" signature) at block 0, followed by partition table. The filesystem starts at the partition's first block, not at image offset 0.
**Correct:**
```python
sig = data[0:4]
if sig == b'RDSK':
# Parse partition table, find DOS partition
for part in parse_partitions(data):
handle_filesystem(data, part.start_block)
elif sig.startswith(b'DOS'):
handle_filesystem(data, 0) # Direct ADF
```
---
## Pitfalls
### 1. Checksum Omission
Every block has a checksum at offset $014. Failing to verify means trusting corrupt data. The parser works on clean images but silently produces garbage from degraded disks.
### 2. Endianness Errors
AmigaDOS is Big-Endian everywhere: types, block numbers, sizes, hash entries, protection bits, dates. Cross-platform tools on x86/ARM must explicitly byte-swap every multi-byte field.
### 3. BSTR Overflow Risks
BSTR fields have fixed max sizes, but the length byte controls content. A corrupt length byte exceeding the field size causes buffer over-reads. Always clamp: min(length_byte, MAX_FIELD_SIZE).
### 4. Root Block Hardcoding
Root block is at total_blocks/2, not a hardcoded 880. Code assuming 880 fails on HD floppies (root at 1216) and every hard disk partition.
---
## Best Practices
1. **Always validate checksums before trusting any block field.**
2. **Use big-endian byte order for ALL multi-byte reads/writes.**
3. **Compute root_block as total_blocks/2, never hardcode 880.**
4. **Clamp BSTR length to the field maximum size.**
5. **Read data_blocks in REVERSE index order (71 to 0).**
6. **Check for RDB signature before assuming direct FS.**
7. **Use hash_chain for collision resolution, not linear scan.**
8. **Write tools that handle both OFS and FFS paths separately.**
---
## When to Use / When NOT to Use
### Filesystem Selection
| Scenario | Use | Avoid | Why |
|---|---|---|---|
| Bootable floppy for any Amiga | OFS (DOS\0) — universal boot | FFS needs KS 2.0+ | OFS boots on 68000 through 68060, all Kickstart versions |
| Daily-use hard disk (stock) | FFS + Intl + DirCache (DOS\5) | OFS loses 5% to headers | DirCache reduces directory scan overhead on mechanical drives |
| Daily-use hard disk (expanded) | **PFS3** — atomic, fast, zero validation | FFS — validation on every crash | 68030+ with PFS3 is 25× faster; instant recovery after power loss |
| Large media files (>50 MB) | PFS3 (block=4096) or SFS (block=32768) | FFS (block=512 fixed) | Larger blocks reduce metadata overhead and fragmentation for big files |
| Data recovery / forensics | OFS — self-describing blocks | FFS — no per-block back-pointers | OFS data blocks point back to file headers; FFS data blocks are anonymous |
| FPGA core implementation | OFS — easier to bootstrap | FFS — harder to test edge cases | OFS block headers provide validation points; FFS needs external context |
| OS 3.5+ with floppy needs | OFS/FFS DOS\6/\7 (long filenames) | PFS3/SFS (no floppy support) | Long filenames on floppies require DOS\6 (OFS) or DOS\7 (FFS) |
### Partition Layout Strategy
| Goal | Partition Layout | Best Practices |
|---|---|---|
| Single OS, single user | DH0=Workbench (12 GB), DH1=Work (rest of disk) | Separate Work keeps fragmentation off boot partition |
| Single OS, multi-purpose | DH0=Workbench (1 GB), DH1=Work, DH2=Games, DH3=Media | Each workload gets its own volume; failure of one partition leaves others intact |
| Dual-boot (3.1 + 3.9) | DH0=3.1 (BootPri 5), DH1=3.9 (BootPri 4) | Both bootable; Early Startup switches; share DH2 for data |
| Game archive (WHDLoad) | DH0=Workbench (small), DH1=Games (large, PFS3) | PFS3 block=1024 good balance for games; Automount games partition off for fastest Workbench boot |
| Development system | DH0=Workbench, DH1=Source, DH2=Build artifacts | Separates OS crashes from source data; builds benefit from PFS3's extent alloc |
| FPGA development target | DH0=Boot (FFS, 500 MB), DH1=Transfer/Data (FFS, small) | Keep boot partition FFS for ROM compatibility; small sizes for fast validation if needed |
| Large modern SSD | DH0=System (2 GB PFS3 blk=1024), DH1=Data (rest, PFS3 blk=4096) | Boot partition <4 GB for KS ROM compatibility; large block size on data for media throughput |
---
## FPGA & MiSTer Impact
| Topic | Real Hardware | FPGA Concern |
|---|---|---|
| Track I/O layer | trackdisk.device reads sectors; filesystem interprets blocks | FPGA cores need trackdisk emulation first, then filesystem |
| Checksum validation | CPU computes sequentially per block | FPGA can parallel-sum 128 longs using DSP blocks |
| BSTR parsing | 68000 handles byte-by-byte copies | FPGA can DMA length-prefixed data directly |
| Hash function | Called once per file during directory read | Can hash while reading raw block data from flash |
| Image format handling | Amiga uses TrackDisk; block processing in software | Modern FPGAs can read directly from SD card FAT/exFAT |
| OFS recovery | Recovery utils use per-block back-pointers | On raw block access, OFS can reconstruct files from independent blocks |
---
## Historical Context & Modern Analogies
### 1985-1994 Filesystem Landscape
**Native Amiga filesystems vs contemporary competitors and third-party successors:**
| System | Type | Max Partition | Max File | Filename | Journaling | Checksums | Floppy | Notes |
|---|---|---|---|---|---|---|---|---|
| **Amiga OFS** (1985) | Native | 4 GB | ~4 GB | 30 | ❌ | ✅ Block-level | ✅ | BCPL metadata; hash directories; self-describing data blocks for recovery |
| **Amiga FFS** (1990) | Native | 4 GB | ~4 GB | 30 / 107¹ | ❌ | ✅ Block-level | ✅ | No per-block header overhead; international mode; directory cache options |
| **MS-DOS FAT16** (1984) | Contemporary | 2 GB | 2 GB | 8.3 | ❌ | ❌ | ✅ | Simplest design; no checksums; no fragmentation resistance; widely compatible |
| **Mac HFS** (1986) | Contemporary | 2 GB | 2 GB | 31 | ❌ | ❌ | ✅ | B-tree catalog for fast lookup; resource forks; Creator/Type metadata |
| **BSD FFS** (1984) | Contemporary | 2 GB | 2 GB | 255 | ❌ | ❌ | ❌ | Cylinder groups minimize seek; symlinks; soft updates (later); UNIX permissions |
| **PFS3** (1995) | 3rd-party Amiga | **104 GB** | ~4 GB | 107 | **Atomic** | ✅ Atomic | ❌ | Instant recovery; extent allocation; configurable block sizes; open source now |
| **SFS** (1998) | 3rd-party Amiga | **127 GB** | 4 GB | 107 | **Journal** | ✅ Journal | ❌ | Journal replay in seconds; huge block sizes up to 32 KB; built-in defrag |
| **FFS2** (1999) | Official (OS 3.5+) | >4 GB² | >4 GB² | 107 | ❌ | ✅ Block | ✅ | 64-bit via NSD/TD64; still validates on crash; floppy-compatible |
¹ 107-character filenames with DOS\6 (OFS) or DOS\7 (FFS), requires OS 3.5+.
² Requires NSD (New Style Device) or TD64 driver support; old tools break on >4 GB partitions.
**Key Amiga innovations:** Checksummed metadata (neither FAT nor HFS had it), hash-based O(1) directory lookup, BSTR format immune to null-byte injection. Commodore anticipated validation as a safety measure for a primarily floppy-based world — it became a liability when hard drives grew.
### Modern Analogies
| Amiga Concept | Modern Equivalent |
|---|---|
| Block checksums | ZFS / Btrfs metadata checksums |
| Hash-based directories | Ext4 HTree / NTFS B-tree |
| BSTR format | Protocol Buffers / wire format with length prefix |
| Self-describing OFS blocks | PostgreSQL WAL blocks |
| Extension block chains | Ext4 extent trees / NTFS run lists |
| RDB partition table | GPT / MBR partition schemes |
---
## FAQ
### Q: Can FFS and OFS coexist on the same disk?
No. A partition is either OFS or FFS. Multiple partitions on one disk can use different types, but each partition is one and only one format.
### Q: Why is the root block at the midpoint?
Minimizes average seek distance on mechanical drives. Half the data is before the root, half after. Inherited from early Unix superblock placement.
### Q: Does AmigaOS suffer from fragmentation?
Yes. OFS/FFS allocates from the bitmap and never defragments. Impact is minimal on floppies but noticeable on hard drives over time. Third-party tools (ReOrg, DiskSafe) existed.
### Q: Can I convert OFS to FFS in-place?
Not safely. Requires backup-format-restore cycle. In-place tools existed but were fragile and not recommended.
### Q: What happens when a checksum fails?
The filesystem handler rejects the block with ERROR_DISK_NOT_VALIDATED. The operation fails with no attempted recovery. Self-healing was not designed into AmigaOS.
### Q: How do PFS3, SFS, and FFS2 differ from FFS?
See the [Third-Party Filesystems](#third-party-filesystems--deep-dive) section above for detailed pros/cons. In brief: PFS3 uses atomic commits (instant recovery, always consistent), SFS uses journaling (replay takes seconds), and FFS2 adds 64-bit support but still requires full validation after crashes. PFS3 is the recommended choice for any hard disk Amiga today.
### Q: What is the maximum hard disk size AmigaOS can handle?
Depends on the filesystem: stock FFS = 4 GB per partition, FFS2 (OS 3.5+) = >4 GB with NSD/TD64, PFS3 = 104 GB, SFS = 127 GB. The boot partition must be entirely within the first 4 GB of the drive regardless of filesystem (a Kickstart ROM limitation, not a filesystem one).
### Q: How many files can a single directory hold?
No hard limit — directory entries are chained through hash collision lists. With a 72-slot hash table (DD floppy), ~500 files is comfortable; with 128+ slots (hard disk partitions), thousands work fine. The real limit is block exhaustion on the volume. PFS3's B-tree-style anode tree handles very large directories (10,000+ files) far better than FFS's linear hash chains.
### Q: Does a crash always require disk validation?
Only on OFS/FFS/FFS2. PFS3 uses atomic commits and is always consistent — zero validation time after any crash. SFS replays its journal in seconds. This is one of the strongest reasons to move off FFS: on a 4 GB partition, FFS validation takes 612 minutes on a 68030; PFS3 takes zero seconds.
### Q: Do PFS3 or SFS work on floppy disks?
No. Both require hard disk storage. PFS3's atomic commit structures alone exceed the 880 KB capacity of a DD floppy. For floppy-only workflows, use OFS (universal boot) or FFS (KS 2.0+). For cross-media workflows, use FFS on floppies and PFS3 on hard disk partitions.
### Q: How many partitions can I create on one disk?
There is no hard limit — all partitions are peers in a singly-linked list within the RDB. The only practical limit is the reserved RDB area size (`rdb_HighRSVDBlock`). Typical configurations support 816 partitions comfortably; expanding the RDB reserved area allows 30+. Unlike MBR, there are no "primary" vs "logical" distinctions.
### Q: Can I mix different filesystems on one disk?
Yes. Each partition independently specifies its filesystem via `de_DosType`. A common setup: DH0=FFS (bootable, for ROM compatibility), DH1=PFS3 (Work, fast recovery), DH2=SFS (Games, large blocks). The Kickstart loads the correct filesystem binary from the RDB for each partition at mount time.
### Q: How does dual-boot work without a bootloader?
The Amiga's **Early Startup Menu** (hold both mouse buttons at power-on) lets you select any bootable partition regardless of its BootPri. To set up dual-boot: create two bootable partitions with different OS installs, assign the preferred default a higher BootPri, and use Early Startup to select the alternative when needed. Toolbox tools like `ChangeBootPri` can also remap BootPri from software.
### Q: Does the Kickstart ROM limit which filesystem I can boot from?
For stock Kickstart 3.1: the boot partition must use a filesystem the ROM knows how to read — FFS (DOS\1) or OFS (DOS\0). However, if the filesystem binary is stored in the RDB (the standard for PFS3/SFS), the Kickstart loads it before mounting, so the boot partition can use PFS3 or SFS even on KS 3.1. The real constraint: the **boot partition must be within the first 4 GB of the drive** (a Kickstart scsi.device limitation, not a filesystem limitation).
---
## References
- NDK39: `dos/filehandler.h`
- Ralph Babel: *The AmigaDOS Manual* (3rd edition) — definitive FFS reference
- Laurent Clévy: *The Amiga Filesystem* — http://lclevy.free.fr/adflib/
- Laurent Cleavy: *The Amiga Filesystem* — http://lclevy.free.fr/adflib/
- See also: [packet_system.md](packet_system.md) — filesystem handler protocol
- See also: [locks_examine.md](locks_examine.md) — lock/examine API layer
- See also: [file_io.md](file_io.md) — Read/Write buffering on OFS/FFS backends
- See also: [dos_base.md](dos_base.md) — DOS library structure, handler startup

View file

@ -10,7 +10,7 @@ The Amiga graphics system is built on custom DMA-driven hardware (Agnus/Alice +
|---|---|
| [gfx_base.md](gfx_base.md) | GfxBase structure, chipset detection (OCS/ECS/AGA), PAL/NTSC, display pipeline (MakeVPort/MrgCop/LoadView), blitter queue |
| [bitmap.md](bitmap.md) | BitMap structure, planar layout, allocation |
| [display_modes.md](display_modes.md) | Chipset comparison (OCS/ECS/AGA), ModeID system, PAL/NTSC timing, DMA slot budget |
| [display_modes.md](display_modes.md) | Full chipset comparison, ModeID selection flowchart, CRT vs flat-panel, interlace/progressive tradeoffs, named antipatterns, FPGA/MiSTer impact, historical context, modern analogies, FAQ |
| [ham_ehb_modes.md](ham_ehb_modes.md) | HAM6/HAM8 encoding pipeline, EHB half-brite, fringing, palette programming, FPGA decoder logic |
| [copper.md](copper.md) | Copper coprocessor, instruction format, UCopList |
| [copper_programming.md](copper_programming.md) | Copper deep dive: architecture, copper list construction, gradient and raster effects |

View file

@ -62,9 +62,9 @@ Pixel clock (hires): 14,318,180 Hz
```
←── Horizontal line (~64 µs PAL) ──→
┌───────────────────────────────────────────────────────────┐
│ HSYNC │ Left │ Active Display Area │ Right │
│ │Border │ (bitplane DMA + sprite DMA) │Border │
│ ~4.7µs│ │
│ HSYNC │ Left │ Active Display Area │ Right
│ │ Border │ (bitplane DMA + sprite DMA) │ Border
│ ~4.7µs│
└───────────────────────────────────────────────────────────┘
Vertical:
@ -205,13 +205,441 @@ The display system shares DMA bandwidth with other custom chips. Each scanline h
> 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 --> |"45 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:
- **12 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 **05%** 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 12 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, 56 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`
- HRM: *Amiga Hardware Reference Manual* — Display chapters
- ADCD 2.1: `NextDisplayInfo`, `GetDisplayInfoData`, `BestModeIDA`
- See also: [views.md](views.md) — ViewPort and View construction
- See also: [copper.md](copper.md) — Copper display list programming
- See also: [ham_ehb_modes.md](ham_ehb_modes.md) — special display modes
- 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.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