The CD32 is an AGA machine. Dragonstone barely uses that.
Lx_DataL.exe writes FMODE ($DFF1FC) with 0. That is the AGA register
that selects 16-, 32- or 64-bit bitplane fetches; zero is the OCS/ECS-
compatible setting. Before the game has drawn a pixel it has turned the
CD32's wider display path off and put the chipset back into the mode an
A500 would use.
Nothing else on the disc writes FMODE, BPLCON3, BPLCON4 or the AGA
colour-bank registers. This is an ECS-era engine running on AGA hardware with
the AGA features switched off -- which is what you would expect from a game
that also exists on the Amiga 1200 floppy set and still knows how to ask you to
change disks (11-leftovers.md).
202,332 bytes that unpack to a straight image of chip memory, with only three non-empty regions in it:
| Offset | Length | Contents |
|---|---|---|
0x00000 |
1,536 | copper lists |
0x0D200 |
3,072 | (bitmap) |
0x0F500 |
25,856 | (bitmap) |
Everything else -- 87 % of the file -- is zeroes. The loader does not build its screen; it loads a snapshot of one, buffers and all, and points the copper at it.
tools/copper.py disassembles them. There are four, and they document the
loader's display exactly:
At 0x0000 and 0x001C -- blanking. BPLCON0 = $0200 (no bitplanes,
colour burst on), COLOR00 = $0000, a couple of waits, INTREQ = $8010 to
raise the copper interrupt, end.
At 0x0034 -- a three-bitplane strip.
MOVE DIWSTRT , $A471 display window, lines 164..172
MOVE DIWSTOP , $ACD1
MOVE DDFSTRT , $0038 20 lowres fetches = 320 pixels
MOVE DDFSTOP , $00D0
MOVE BPL1MOD , $0050 modulo 80 = (3-1) * 40
MOVE BPL2MOD , $0050
MOVE BPLCON0 , $3200 BPU = 3, eight colours
MOVE SPR0PTH..SPR7PTL all eight sprites -> $000151A8
MOVE BPL1PTH , $0001 plane 1 -> $00019A8A
MOVE BPL2PTH , $0001 plane 2 -> $00019AB2 (+0x28 = +40)
MOVE BPL3PTH , $0001 plane 3 -> $00019ADA (+0x28)
WAIT VP=$28
MOVE COLOR00..COLOR07 , $0000
The three bitplane pointers are 40 bytes apart and the modulo is
2 x 40. That is the definitive statement of the disc's bitmap format:
interleaved planar, where the rows of the N planes of one scanline sit
next to each other and BPLxMOD = (planes - 1) * bytes_per_row. Everything on
this disc that is a bitmap is stored that way.
All eight hardware sprites point at the same address, which is the standard way of parking them on a null sprite.
At 0x0234 -- a four-bitplane screen with a real palette.
BPLCON0 = $4200 BPU = 4, sixteen colours
BPLCON2 = $0024
DIWSTRT = $2881 / DIWSTOP = $10B1
BPL1MOD = BPL2MOD = $0030
COLOR00 $0000 COLOR01 $0221 COLOR02 $0443 COLOR03 $0776
COLOR04 $0800 COLOR05 $0A20 COLOR06 $0C40 COLOR07 $0430
COLOR08 $0660 COLOR09 $0880 COLOR10 $0620 COLOR11 $0740
COLOR12 $0962 COLOR13 $0B84 COLOR14 $0CCC COLOR15 $0110
COLOR16 $0222 COLOR17 $0FFF COLOR18 $0ED5 COLOR19 $0D93
COLOR20 $0A60 COLOR21 $0840 COLOR22 $0620 COLOR23..31 $0000
Twelve-bit $0RGB values: dark blue-greys, reds, golds and a white -- a
parchment palette for the loading panel. Colours 16 to 22 are written even
though BPU = 4 only addresses 0 to 15, so the second half of the palette is
being staged for something else (a sprite bank, or a later BPLCON0 change).
This list confirms the interleaved format a second time and from different
numbers. DDFSTRT = $38 with DDFSTOP = $70 is eight lowres fetches, so
16 bytes per plane row, 128 pixels wide -- and BPLxMOD = $30 = 48 = 3 x 16, again exactly (planes - 1) * bytes_per_row. Two copper lists, two
different widths and plane counts, the same rule.
The interleaved format is settled. The block geometry is not, and this is where the documentation stops being certain.
Measuring the row pitch by byte autocorrelation over each file's pixel data gives a clean answer for two of them and no answer for the rest:
| File | Strongest period | Reading |
|---|---|---|
Lx_TPage bitmap region |
16 bytes, sharply | 32 pixels wide x 4 planes interleaved |
Lx_MSprF / Lx_MSprM |
128 bytes, then 256 | a 128-byte frame or block |
L?_GSBlk |
4 bytes, decaying smoothly | no periodicity above the pixel level |
Lx_CompC |
40 bytes, weakly | 320 pixels wide, one plane |
For Lx_TPage the 16-byte peak is unambiguous: the score at 16 is 0.695, at 14
it is 0.491 and at 18 it is 0.495. Rendering that region as 32 x 32 blocks of
4 interleaved planes produces recognisable curves and shading, so the width is
right and the block height is not yet pinned down.
The tile banks are the hard case. They are certainly not compressed --
re-compressing the unpacked GSBlk files with zlib gives 0.09 to 0.50, which
is what raw pixel art looks like -- but 16 x 16 and 32 x 32 sheets at four and
five planes, interleaved and separated, all render as noise. Something between
the file offset and the pixels is still missing: a header, a per-block index,
or a block size that is not a power of two. See
12-open-questions.md.
Only Lx_ChipI carries palettes as copper MOVE COLORxx instructions. The
level palettes are not stored that way -- scanning every unpacked file for
copper-style COLOR00 writes finds nothing that looks like a 16- or 32-entry
table outside Lx_ChipI and a couple of runs in Lx_CompC. The level palettes
must live inside GSBlk or LLoad as plain word arrays, which is the other
half of the geometry problem.