Skip to content

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's bytes: the header's fields named, three planes beside their bits, the smoke with one pixel framed.

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.

A palm and a carrier's piece over two grounds, with and without the two bytes.

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.

Ten grey hulls with their names, blue sky in their corners.

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 three words: the box from 37 to 68, the clip from 48, the pixels 48 to 68 written.

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 as stored and mirrored, its hotspot framed.

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.

Twelve shapes with their names: the carrier's pieces and flags.

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

Outside the repository: the Amiga Hardware Reference Manual, its chapter "Blitter Hardware", for the minterms, the shifters and the word masks.