Chapter 12
Shapes¶
This chapter follows a shape from chapter 3's file into chapter 11's surfaces. By its end you will know what a shape is to the game and what each field of its header is for; how the program's names become shapes, and when; what exactly the blit does, and how its clipping comes out exact to the pixel although the blitter works sixteen pixels at a time; how the game turns the player's aircraft round; and how to read the contact sheets. Each mechanism ends with what the port made of it and the instrument that holds it.
What a shape is to the game¶
The game keeps a shape container as it came from the disk. It loads the file whole into chip memory, unpacks it if it is packed in chapter 3's Rpck format, and from then on works on that image, converting and moving nothing. A shape, to the game, is a pointer to its shape record: a header of 20 bytes, then its stored planes, the bitplanes it carries, one after another. The drawing reads the fields and planes where they lie, so a pointer is all it needs.
The twelve containers hold 1,049 shapes. Most store fewer planes than the playfield's five: about a quarter store all five, and one stores none. Here is one record whole, smk3 of world.shp, one of the smoke's shapes.

The record of smk3, 68 bytes: its header, its stored planes a row to a line beside their bits, its colours and hotspot.
The header, field by field¶
The first two words give the size: the width in bytes, eight pixels to a byte, and the height in rows. The smoke is 16 by 8 pixels, and that rectangle is the shape's box.
The next two are the hotspot: the point of the shape, counted from its top left corner, that lands on the position the shape is drawn at. The routines that draw a shape from a table or a lookup subtract it from the position first, so a shape is placed by a point of its own, the smoke by the middle of its cloud, (9, 4): the code needs nothing of its size, and frames of different sizes stay in place.
The words at +8 and +10 are, to all appearances, the position the shape was cut from in the picture the artist drew it in: every x among them is a multiple of 16, and some frames of one animation share one cut position. Only the rank selection reads them, drawing its highlight at the position its records give, one row of the rank list each, with no hotspot taken off. In two containers the game overwrites the first, for the mirror.
The next two bytes name planes of the screen. A byte that names planes uses a colour number's bits as chapter 2 gave them, plane 1 worth 1 up to plane 5 worth 16: 0x10 is the fifth plane, 0x18 the fourth and the fifth, 0x04 the third. These two are the clear and set bytes: the screen planes the blit sets to 0 and those it sets to 1 wherever the shape's mask is set. Otherwise a screen plane the shape does not store keeps the background's bits, and a pixel's colour depends on what lay beneath; with the clear byte 0x10 a four-plane shape lands in colours 0 to 15 whatever the fifth plane held. On the playfield's five planes that keeps its colours; on the dashboard's four the blit takes the fifth plane out of the clear byte, the set byte and the plane masks alike, so there the same clear byte, which 207 of the dashboard's 223 shapes carry, does nothing.
The figures
| The two bytes in the 1,049 shapes | Shapes |
|---|---|
| Clear byte 0 | 512 |
Clear byte 0x10, the fifth plane |
481 |
| Clear byte of another value | 56 |
| Set byte other than 0 | 7 |
In the figure, tre1, a palm at the eighth scale, has four planes and the clear byte 0x10: over colour 17, an island's ground, whose fifth plane is set, it keeps its greens and browns, and without the byte it would turn blue and grey. jcrm, one of the Japanese carrier's shapes, stores three planes, colours 1 to 5, and its set byte 0x18 puts the fourth and fifth planes under every pixel: on the screen it shows the carrier's greys, 25 to 29.

tre1 of 8thscale.shp and jcrm of japcarrier.shp over colours 1 and 17: by the game's rule on the left, without the clear and set bytes on the right.
The header's last six bytes are the plane masks, one for each stored plane and a zero after the last: each names the screen planes that stored plane is written to. A plane mask is not the shape's mask of chapter 2, which says where the shape has colour. An ordinary five-plane shape has 0x01 0x02 0x04 0x08 0x10. A plane mask of two bits writes a stored plane into two screen planes: the smoke's third, 0x18, lands its third stored plane in planes 4 and 5, and its clear byte 0x04 clears plane 3, which it does not store, so three stored planes give the smoke its colours 25 to 27 over any background.
From names to shapes¶
The program almost always asks for a shape by a name of four characters; two places go by number. The names lie in nine name lists in the program's data, each ended by a zero. When a container is loaded, shapes_resolve looks every name of its list up in it and writes the results into a pointer table, an array of record pointers in the list's order. The code then names a shape by its slot, its fixed position in a table, and fetches it with one load. Resolving once also lets one list serve two containers, as chapter 3 said, the world's at both scales and the dashboard's by day and night.
| Name list | Names | Resolved in | Absent |
|---|---|---|---|
world_names |
184 | world.shp; 8thscale.shp |
55; 84 |
torpedo_names |
138 | Torpedo.shp |
40 |
dash_names |
117 | dash.shp; nightdash.shp |
none |
hellcat_names |
107 | hellcat.shp |
16 |
destroyer_names |
27 | destroyer.shp |
none |
battleship_names |
25 | battleship.shp |
none |
cruiseship_names |
24 | cruiseship.shp |
none |
japplane_names |
22 | japplane.shp |
none |
japcarrier_names |
13 | japcarrier.shp |
3 |
A name absent from its container gives a null pointer, and that is the normal case, not an error: 84 of the 184 world names have no shape at the eighth scale, as chapter 11 told, and even the world's own container lacks 55, lpn0 to lpnj among them; their slots stay null and draw nothing. A container may hold more shapes than its list names: the dashboard has 223 for 117 names, the rest reached by tables of names of their own.
The lookup, shape_find, walks the container's names from the first, stops at the first name not less than the one it wants, and then tests whether it is the same. That early exit is right only over names in ascending order, and all twelve containers are so, strictly; most of the nine lists are not, and need not be. In the loop on the left, cmp.l (a1)+,d0 compares four letters as one long, and dble loops back while the wanted name is still the greater and names remain; the port on the right keeps the exit.
; re/Wings.lst 0x020560-0x0205B2: shape_find [asm]
shape_find:
020560 48e74040 movem.l d1/a1, -(a7)
020564 32280004 move.w $4(a0), d1
020568 4a41 tst.w d1
02056a 6f3e ble.b $205aa
02056c 43e80006 lea.l $6(a0), a1
loc_020570:
020570 b099 cmp.l (a1)+, d0
020572 5fc9fffc dble d1, $20570
020576 5989 subq.l #$4, a1
020578 2209 move.l a1, d1
02057a 9288 sub.l a0, d1
02057c 5d81 subq.l #$6, d1
02057e e489 lsr.l #$2, d1
020580 b091 cmp.l (a1), d0
020582 6626 bne.b $205aa
020584 30280004 move.w $4(a0), d0
020588 48c0 ext.l d0
02058a e588 lsl.l #$2, d0
02058c 2248 movea.l a0, a1
02058e d3c0 adda.l d0, a1
020590 48c1 ext.l d1
020592 e589 lsl.l #$2, d1
020594 d3c1 adda.l d1, a1
020596 5c89 addq.l #$6, a1
020598 2011 move.l (a1), d0
02059a d088 add.l a0, d0
02059c 32280004 move.w $4(a0), d1
0205a0 48c1 ext.l d1
0205a2 e789 lsl.l #$3, d1
0205a4 d081 add.l d1, d0
0205a6 5c80 addq.l #$6, d0
0205a8 6002 bra.b $205ac
loc_0205aa:
0205aa 7000 moveq #$0, d0
loc_0205ac:
0205ac 2040 movea.l d0, a0
0205ae 4cdf0202 movem.l (a7)+, d1/a1
0205b2 4e75 rts
/* src/shapes.c, lines 32-48 */
/* orig 0x020560 - A0 = container, D0 = the name as a big-endian long. A linear scan that
* stops at the first stored name greater than or equal to the wanted one, so it needs the
* ascending order that all twelve containers on the disk have. The port keeps the early
* exit: the result is the same as an exact-match lookup, and the oracle test compares it
* against the original for every name of every list. */
int16_t wof_shape_find(const wof_container_t *c, uint32_t name)
{
if (!c || (int16_t)c->count <= 0)
return WOF_NO_SHAPE;
for (uint16_t i = 0; i < c->count; i++) {
if (c->names[i] >= name)
return c->names[i] == name ? (int16_t)i : WOF_NO_SHAPE;
}
/* Ran past the end: the original then re-tests the last entry, which cannot match. */
return WOF_NO_SHAPE;
}
By number go the rank selection, which takes the eight records of selectrank.shp, a container without a list, through shape_by_index, and the start-up, which walks the two mirrored containers to set their marker. Other names come from tables of the program's own: the enemy aircraft's frames, and the dashboard's variants of them, are resolved once a mission, and while the player's aircraft is drawn from the slots of hellcat_names, the routine that chooses its frame looks the frame's name up anew each time. Chapters 14 and 16 tell the frames.
Before every mission two tables are built from the others, MasterList and AthList, whose "Ath" reads as "eighth". The map's records index them: 273 slots, the world's 184, then the cruise ship's 24, the destroyer's 27, the Japanese carrier's 13 and the battleship's 25, null where the mission has no such ship; AthList holds the same names resolved in 8thscale.shp. The map's own loop fetches each record's shape there and skips a null slot. The objects, the smoke, the soldiers and the rest reach their shapes by slot through one helper, draw_world_shape. Its first lines turn the world's position into the screen's, chapter 13's subject, and by the routine's rule leave out a shape whose x lies outside −128 to 448; then add.w d2,d2 twice makes the slot an offset of four bytes a pointer, movea.l (a0,d2.w),a0 fetches the record, and the two sub.w take the hotspot off. It has no test for null; the blit rejects a null record.
; re/Wings.lst 0x015174-0x0151C0: draw_world_shape [asm]
draw_world_shape:
015174 48e7f0c0 movem.l d0-d3/a0-a1, -(a7)
015178 4441 neg.w d1
01517a d27c000b add.w #$b, d1
01517e d26c9f34 add.w -$60cc(a4), d1 ; view_y
015182 906c9f32 sub.w -$60ce(a4), d0 ; view_x
015186 4a6c9f36 tst.w -$60ca(a4) ; view_shift
01518a 67000006 beq.w $15192
01518e e640 asr.w #$3, d0
015190 e641 asr.w #$3, d1
loc_015192:
015192 b07cff80 cmp.w #$ff80, d0
015196 6d000024 blt.w $151bc
01519a b07c01c0 cmp.w #$1c0, d0
01519e 6e00001c bgt.w $151bc
0151a2 d442 add.w d2, d2
0151a4 d442 add.w d2, d2
0151a6 20702000 movea.l (a0, d2.w), a0
0151aa 93c9 suba.l a1, a1
0151ac 90680004 sub.w $4(a0), d0
0151b0 92680006 sub.w $6(a0), d1
0151b4 2c6cb93a movea.l -$46c6(a4), a6 ; custom_base
0151b8 4eac832c jsr -$7cd4(a4) ; -> shape_draw
loc_0151bc:
0151bc 4cdf030f movem.l (a7)+, d0-d3/a0-a1
0151c0 4e75 rts
The port builds its tables from the lists in the program, not from the files, because the slots are the code's interface and follow the list's order, not the container's. It keeps the slots and the early exit and holds both under the oracle of chapter 5: every name of every list and every name a container carries, looked up in all twelve containers, about seven thousand lookups, and every table entry for entry.
What is loaded when¶
| When | What | Until |
|---|---|---|
| The program's start | world.shp, hellcat.shp, Torpedo.shp, japplane.shp, 8thscale.shp |
the program's end |
| The rank selection | selectrank.shp |
the screen's end |
| Each mission | dash.shp by day, nightdash.shp by night |
the mission's end |
| Each mission, after its map | the container of each enemy ship the map holds | the mission's end |
The five that stay hold what any mission may draw: the world at both scales, the player's aircraft and its weapons, the enemy aircraft. Only the dashboard has night shapes; the world changes by its palette alone. A container that cannot be loaded ends the program, except battleship.shp, which the game treats as optional. The port keeps every container converted from its start, so neither failure can happen, but opens the files in the original's order, and chapter 8's comparisons hold the headless original's log of opened files to the port's trace.
The blit, exactly¶
As chapter 2 told, the blit draws through a mask, and shape_draw chooses it first. A shape whose one stored plane is larger than 1,040 bytes, the buffer the mask is built in, gets none (the width times the height, mulu.w, compared with MaskBuffer_size). With the stored planes counted in D1, two or more get their OR, which the processor builds in the buffer (bhi.b to its loops); one is its own mask, the record plus $14, 20, the header's size (lea.l $14(a0),a1); none gets none, A1 subtracted from itself, which zeroes it (suba.l a1,a1).
; re/Wings.lst 0x020CE2-0x020D26: shape_draw [asm], a part of 0x020CE2-0x020DE9
shape_draw:
020ce2 b2fc0000 cmpa.w #$0, a1
020ce6 6600fe24 bne.w $20b0c
020cea 3f00 move.w d0, -(a7)
020cec 3010 move.w (a0), d0
020cee c0e80002 mulu.w $2(a0), d0
020cf2 b0acc43c cmp.l -$3bc4(a4), d0 ; MaskBuffer_size
020cf6 6306 bls.b $20cfe
020cf8 301f move.w (a7)+, d0
020cfa 6000fe10 bra.w $20b0c
loc_020cfe:
020cfe 48e740b6 movem.l d1/a0/a2-a3/a5-a6, -(a7)
020d02 43e8000e lea.l $e(a0), a1
020d06 72ff moveq #$ff, d1
loc_020d08:
020d08 5241 addq.w #$1, d1
020d0a 4a19 tst.b (a1)+
020d0c 66fa bne.b $20d08
020d0e 5341 subq.w #$1, d1
020d10 6214 bhi.b $20d26
020d12 6604 bne.b $20d18
020d14 4e71 nop
020d16 4e71 nop
loc_020d18:
020d18 43e80014 lea.l $14(a0), a1
020d1c 670000a8 beq.w $20dc6
020d20 93c9 suba.l a1, a1
020d22 600000a2 bra.w $20dc6
loc_020d26:
020d26 226cc438 movea.l -$3bc8(a4), a1 ; MaskBuffer
Then come three phases, a run of the blitter for each screen plane: the clear planes, its source B held at 0; the set planes, B held at all ones; each stored plane into the screen planes of its plane mask, B the stored plane. Each run combines the mask, source A, with B and the screen plane, C, by the logic function 0xCA. The blitter's function is a byte with one bit for each of the eight minterms, the eight ways A, B and C can be set at a pixel, the bit saying whether the result is 1 there; programmers name a function by that byte, and 0xCA gives B where A is set and C where it is not. For a pixel of colour number v under the mask the phases come to v = (((v and not clear) or set) and not U) or p, U being every plane the plane masks name and p the shape's pixel; on the dashboard the clear byte, the set byte and U first lose their fifth plane. Screen planes named by none of them keep the background's bits.
A shape without a mask writes its whole box, colour 0 included: an opaque shape. Eleven are too large for the buffer, as chapter 2 told; ten are pieces of the carriers and the ships, which carry the sky's colour where the opaque blit would otherwise show a hole. One shape stores no plane at all, bchm, whose clear and set bytes would paint colour 17; no name in the program asks for it.

The ten hull pieces too large for a mask, in the day palette: no colour 0, and the sky's colour 1 around nine of them.
Clipping to the pixel¶
Every draw is limited to a clip rectangle, the part of the screen it may change, its bottom and right edges the first row and column outside it. The game sets five: the playfield, the dashboard, the dashboard's 3-D window of chapter 16, the playfield down to the carrier's water line, and the front end's screen; the sidebar gives their sizes. clip_set rounds the left and right edges down to a multiple of 16 (andi.w #$fff0): the blitter writes a plane a word at a time, 16 pixels, and rounding whatever it is given keeps its word arithmetic from meeting an edge inside a word. Every edge the game asks for is on one already.
That raises a puzzle: a shape may lie at any x and the blitter writes whole words, yet nothing outside the shape's box changes and the clip cuts the box exactly. Four things answer it, and the diagram follows one row. The blit's span starts at the word that holds the shape's left edge and, when x is not a multiple of 16, is one word wider than the shape, so it overhangs the box on both sides. The blitter's shifter, which moves source A right by up to 15 pixels, carrying what falls out into the next word, moves the mask by x's remainder and brings in zeros. Its two word masks, ANDed with the first and the last word of every row of the mask, blank the shape's columns left of the clip and the extra word on the right; when the clip's edge lies more than a word right of x, whole words are skipped first and the first word mask blanks the rest. And with the function 0xCA, a 0 in the mask leaves the screen's bit as it was. So every pixel of the shape's box inside the clip is written, and no other. A right edge of the clip inside the box is cut the same way: the blit ends at the clip's word, and the last word mask blanks the columns past the edge.
One row of a shape 32 pixels wide at x 37, the clip from pixel 48: the shift and the two word masks leave the mask set only on the box's pixels inside the clip.
So the port may clip pixel by pixel, the rectangle rounded as the original rounds it; a test below holds the claim.
A highlight that removes itself¶
A second routine, shape_draw_xor, takes no mask and ignores the clear byte: inside the clipped box it inverts the screen planes of the set byte and combines each stored plane with the screen's by exclusive or, which flips a screen bit wherever the shape's bit is set. Drawing a shape twice at one place leaves the screen as it was, which is how the rank selection moves its highlight. The player's muzzle flash and an enemy aircraft's are drawn the same way. The port's routine does the same on pixels, and a test holds it to the original's register programme on every shape of hellcat.shp.
The mirror¶
The player's aircraft turns round, and the game mirrors its frames of straight flight, climbing or diving, and those on the deck in place rather than keeping each twice; a turn's own frames are separate shapes, drawn as stored (chapter 14). At load it walks every record of hellcat.shp and Torpedo.shp and writes 2 into its word at +8. From then on that word is the mirror marker, which way the stored pixels face, 2 meaning as the file holds them. The routine that chooses the aircraft's frame compares the marker with the facing it wants and, only when they differ, writes the new value and calls shape_mirror_x, which turns the record round in place. A frame is mirrored once when the aircraft turns, not at every draw, and the way the shapes face becomes part of the game's state: chapter 8's open loop hands it over, and chapter 9's music tests once left shapes mirrored for the tests after them.
shape_mirror_x reverses every row of every stored plane, swapping bytes from both ends and reversing each byte's bits through a table, bit_reverse_table. The hotspot turns too, so that the same point of the aircraft lands on the drawing position, now counted from the other side: its x becomes 8 times the width, less 1, less the old x. On the left, look at that arithmetic, lsl.w #$3, subq.w #$1 and sub.w $4(a0), and at the inner loop's two move.b through the table; on the right the port works on pixels, where one reversal of each row is the whole mirror.
; re/Wings.lst 0x015B58-0x015BC4: shape_mirror_x [asm]
; shape_mirror_x [asm]
; asm. (record) mirrors a shape in place through bit_reverse_table, hotspot x becomes 8*w-1-x
; callers: aircraft_frame
shape_mirror_x:
015b58 206f0004 movea.l $4(a7), a0
015b5c 2008 move.l a0, d0
015b5e 67000064 beq.w $15bc4
015b62 48e70f30 movem.l d4-d7/a2-a3, -(a7)
015b66 3010 move.w (a0), d0
015b68 3400 move.w d0, d2
015b6a e748 lsl.w #$3, d0
015b6c 5340 subq.w #$1, d0
015b6e 90680004 sub.w $4(a0), d0
015b72 31400004 move.w d0, $4(a0)
015b76 47eca6b8 lea.l -$5948(a4), a3 ; bit_reverse_table
015b7a 43e80014 lea.l $14(a0), a1
015b7e 45f02014 lea.l $14(a0, d2.w), a2
015b82 3e02 move.w d2, d7
015b84 e24a lsr.w #$1, d2
015b86 3c02 move.w d2, d6
015b88 de46 add.w d6, d7
015b8a 5342 subq.w #$1, d2
015b8c 36280002 move.w $2(a0), d3
015b90 5343 subq.w #$1, d3
015b92 7000 moveq #$0, d0
015b94 7200 moveq #$0, d1
015b96 d0fc000e adda.w #$e, a0
015b9a 4a18 tst.b (a0)+
015b9c 67000022 beq.w $15bc0
loc_015ba0:
015ba0 3803 move.w d3, d4
loc_015ba2:
015ba2 3a02 move.w d2, d5
loc_015ba4:
015ba4 1011 move.b (a1), d0
015ba6 1222 move.b -(a2), d1
015ba8 14b30000 move.b (a3, d0.w), (a2)
015bac 12f31000 move.b (a3, d1.w), (a1)+
015bb0 51cdfff2 dbra d5, $15ba4
015bb4 d4c7 adda.w d7, a2
015bb6 d2c6 adda.w d6, a1
015bb8 51ccffe8 dbra d4, $15ba2
015bbc 4a18 tst.b (a0)+
015bbe 66e0 bne.b $15ba0
loc_015bc0:
015bc0 4cdf0cf0 movem.l (a7)+, d4-d7/a2-a3
loc_015bc4:
015bc4 4e75 rts
/* src/shapes.c, lines 202-230 */
/* orig 0x015B58 - mirrors a shape in place and turns its hotspot round.
*
* The original reverses the bytes of every stored plane row and the bits inside each byte
* through bit_reverse_table; on indexed pixels that is one row reversal, because a pixel
* carries the bits of all planes at once. It swaps byte pairs from both ends, so the
* middle byte of an odd byte width would stay unreversed - every width in the files is
* even, which the oracle test relies on as much as the original does. A null record
* returns at once. */
void wof_shape_mirror_x(wof_shape_t *s)
{
if (!s)
return;
s->hot_x = (int16_t)(s->wbytes * 8 - 1 - s->hot_x);
if (!s->pixels)
return;
uint32_t w = (uint32_t)s->wbytes * 8u;
for (uint32_t y = 0; y < s->height; y++) {
uint8_t *row = s->pixels + y * w;
for (uint32_t i = 0, j = w - 1; i < j; i++, j--) {
uint8_t t = row[i];
row[i] = row[j];
row[j] = t;
}
}
}

The Hellcat hc05 as hellcat.shp stores it and mirrored, its hotspot framed in both.
The routine that chooses the frame does not test its lookup: for a frame name absent from a container it reads the marker through a null pointer and may even write there. The lowest addresses hold the processor's exception vectors, ordinary memory, so the read returns some bytes and the write lands in a vector nobody uses; the port tests first, to the same visible result. Under the oracle its mirror is held to the original's on all 216 records of the two containers, each mirrored twice to come back as it was.
What the port made of it¶
The port has no planes and no blitter. It converts every shape once, at load, to the pixels of chapter 11's indexed framebuffer, a colour number a byte, and keeps the header's fields the blit still needs: the clear and set bytes, every plane the plane masks name, the number of stored planes, the size of one, the hotspot and the marker. Then it throws the file image away. A pointer table becomes an array of shape numbers, −1 standing for null. Under the oracle every one of the 1,049 conversions is held to the planes the original keeps.
The mask needs no storage. A pixel's colour number is the OR of the plane masks of the stored planes set there; since no two plane masks of a shape share a plane, the number is 0 exactly where no stored plane has its bit, which is where the mask, the OR of the stored planes, is 0, and that holds for every shape on the disk. The blit is the one place where the port cannot follow the original instruction by instruction, since the original programmes a chip the port does not have. It applies the rule per pixel, with the same clipping and the same order of drawing. Here is its heart: the clear and set bytes and the plane masks' planes folded into keep and force, a pixel of 0 skipped where the shape has a mask, and one line that writes the pixel.
/* src/draw.c, lines 192-213, a part of shape_blit (lines 157-213) */
uint8_t M = target.mask;
uint8_t clr = (uint8_t)(s->clear & M);
uint8_t st = (uint8_t)(s->set & M);
uint8_t un = (uint8_t)(s->union_mask & M);
uint8_t keep = (uint8_t)~(clr | st | un);
uint8_t force = (uint8_t)(st & ~un);
/* A shape that stores no plane has no pixels at all (8thscale's `bchm` is the one on
* the disk). It still paints its clear and set bytes over its box, opaquely. */
for (int32_t r = sy0; r < sy1; r++) {
const uint8_t *src = s->pixels ? s->pixels + (uint32_t)r * w : 0;
uint8_t *dst = target.pixels + (int32_t)(y0 + r) * target.stride + x0;
for (int32_t c = sx0; c < sx1; c++) {
uint8_t p = src ? src[c] : 0;
if (useMask && !p)
continue;
dst[c] = (uint8_t)((dst[c] & keep) | force | (p & un));
}
}
}
How the blit is held¶
Chapter 5 told how the blit is compared, through its register programme: the blitter's registers as they stand when a blit starts, its pointers, its modulos (the bytes it skips at each row's end), word masks, shifts, logic function and size. The original's shape_draw runs under the oracle with the custom chips' addresses as plain memory, every blit is captured at the register that starts it, and a model of the blitter, tests/blitter.py, replays it on a copy of the planes, to be compared with the port's pixels.
The figures
| The blit's comparison | |
|---|---|
| Shapes and positions | 1,049 x 6: on a word and shifted, inside and over every edge |
| Clip rectangles and backgrounds | 2 x 2: the whole playfield and one inside it; empty and noisy |
| Draws on the playfield's five planes | 1,049 x 6 x 2 x 2 = 25,176 |
| Draws on the dashboard's four planes | (223 + 223 + 140 + 116) x 3 = 2,106, four containers at three positions |
As chapter 5 said, that holds the clipping, the pointers, the word masks and the phases to the original's own code; the model itself is documented hardware behaviour, read the same way for the port, which an emulator exact to the cycle would check. One test leaves the port out and holds the programme to the claim of the clipping section: every seventh shape of world.shp, at four positions against both clips, drawn over all zeros and all ones; touched, the pixels either draw changed, must lie within box.
# tests/test_oracle_m1.py, lines 495-517
def test_the_blit_writes_exactly_the_shape_box(ported):
"""An independent statement about the register programme, not about the port: the
blitter's first and last word masks leave the destination alone everywhere outside
the shape's own box and outside the clip rectangle, however the blit is shifted.
This is what lets the port clip per pixel."""
reference = Reference('shapes/world.shp', 40, 162, 5)
ones = bytes([31]) * (320 * 162)
zeros = bytes(320 * 162)
for index in range(0, reference.count, 7):
width, height = reference.shape_size(index)
for x, y in ((33, 20), (48, 20), (-5, -3), (310, 155)):
for clip in (CLIP_FULL_5, CLIP_INNER_5):
lit = reference.draw(index, x, y, clip, zeros)
dark = reference.draw(index, x, y, clip, ones)
touched = {i for i in range(len(ones)) if lit[i] or dark[i] != 31}
box = {(y + r) * 320 + (x + c)
for r in range(height) for c in range(width)
if clip[0] <= y + r < clip[1] and clip[2] <= x + c < clip[3]
and 0 <= x + c < 320 and 0 <= y + r < 162}
assert touched <= box, (
'a blit of %s at (%d,%d) touched %d pixels outside its box'
% (struct.pack('>I', reference.name(index)), x, y, len(touched - box)))
These tests and those of the earlier sections make shape_find, shapes_resolve, shape_mirror_x, clip_set and the blit's routines verified. Two containers unpack to one byte more than they declare, and the port's reading found the original writing it past the end of its buffer; the port stops at the declared size, and nothing reads the byte.
The contact sheets¶
A contact sheet is a picture of every shape of a container with its name above it, as a photographer's contact sheet shows a whole film. tools/ppkc.py makes it from the planes in the day palette, colour 0 left as the ground, twice enlarged. Seven are kept in ref/sheets/, and a test holds each byte for byte to what the tool makes of the disk. Here is the Japanese carrier's container, made by the same tool.

A contact sheet of japcarrier.shp, packed, in the day palette: jcrm shows the colours its planes store.
A sheet answers what a name shows and lays an animation's frames side by side. One caution: it shows the colour numbers the planes store, not what the clear and set bytes make of them, so jcrm, red and blue here, is grey on the screen. The shape browser of this book shows every shape of every container.
For the developer
The clip rectangles, top, bottom, left and right, the bottom and the right exclusive:
- the playfield, 0, 162, 0, 320: 162 rows by 320 columns;
- the dashboard, 0, 37, 0, 640;
- its 3-D window, 7, 32, 256, 400: rows 7 to 31, columns 256 to 399;
- the water line's: the sides kept, the bottom at the carrier's water line, or 161.
The blitter library lies at 0x0209BC to 0x0215D8; aircraft_frame, which chooses the player's frame, at 0x01ABDE. The notes and SPEC.md count the planes from 0, so their "clear plane 4" is this chapter's fifth plane, 0x10. In the port: src/shapes.c, src/draw.c; the tests in tests/test_oracle_m1.py.
What comes next¶
The chapter in one sentence: a shape is a record in a container the game keeps as it came from the disk, reached through tables its names were resolved into once, and drawn by one blit whose rule, clipping included, the port keeps pixel by pixel. Chapter 13 turns to the map, whose records name these shapes by slot.
Further reading¶
re/notes/shapes.md, the whole note.re/notes/drawing.md, "Summary", "The blitter library", "Whatshape_drawdoes, exactly" and "The scene routines".re/notes/porting-m1.md, "The blit is per-pixel, and why" and "How the tests establish it".SPEC.md, section 3.5, "File formats"; 6.4, "Video model".src/shapes.candsrc/draw.c, their opening comments;tools/ppkc.pyandref/sheets/.
Outside the repository: the Amiga Hardware Reference Manual, its chapter "Blitter Hardware", for the minterms, the shifters and the word masks.