Skip to content

Chapter 15

Weapons, targets and soldiers

Chapter 14 flew the aircraft; this chapter is about what it fights with and against. By its end you will know where the game keeps a weapon while it flies, and why a shot can vanish; how the weapons move and where they come down; what a hit does to an island's targets; how the soldiers come out, run and die, and why their deaths decide when an island is beaten; the guns and the targets' fire; and the pools of smoke, splashes and balloons. The port and its instruments come at the end.

What the player fights with

Besides its guns, the Hellcat carries one kind of weapon at a time, chosen in the hold's weapon menu before take-off: rockets, bombs or the torpedo (manual, pages 4 and 7). The game numbers them in the menu's order, 0, 1 and 2, the weapon's type; a reset in the hold sets bombs, and a load is 15 rockets, 30 bombs or one torpedo, as the manual counts them. The guns fire while the button is held, and a tap drops the other weapon (pages 6 and 7): chapter 7's two fire bits of the input byte. The dashboard's weapon counter shows the load on two drums.

Fifteen records and a byte

A weapon in flight needs a place for its position and speeds; the game keeps it in an object record: one of fifteen records of 42 bytes in a fixed table, with a sixteenth elsewhere in the data for an enemy torpedo bomber's torpedo (chapter 16). There is no allocator and no list of free records. An object record's byte at +0x20, which the notes call its kind, says where the record stands: 0xFF while it flies, 0 when it is free, and 8 while it goes out, showing six frames of an explosion or a splash, one a pass, before the pass frees it. Its word at +0x22, its type, says which weapon it is.

A tap drops the weapon unless the aircraft is in the middle of a turn, its attitude from 6 to 16, and the drop looks for a free object record the only way the table allows: from the first, one by one. On the left, look at moveq #$e,d0, 14, because dbra runs once more than its count, and at tst.b $20(a0) with beq.w, which leaves for the launch at the first kind byte that is zero. The port's C on the right does the same with a for.

; re/Wings.lst 0x0107F2-0x01081E: object_draw_first [asm]
object_draw_first:
0107f2  48e70700             movem.l    d5-d7, -(a7)
0107f6  397c0014bf8c         move.w     #$14, -$4074(a4)                       ; drop_debug
0107fc  4a2ca36f             tst.b      -$5c91(a4)                             ; weapon_count
010800  67000018             beq.w      $1081a
010804  41ec9cb0             lea.l      -$6350(a4), a0                         ; object_records
010808  700e                 moveq      #$e, d0
loc_01080a:
01080a  4a280020             tst.b      $20(a0)
01080e  6700007e             beq.w      $1088e
010812  d0fc002a             adda.w     #$2a, a0
010816  51c8fff2             dbra       d0, $1080a
loc_01081a:
01081a  4cdf00e0             movem.l    (a7)+, d5-d7
01081e  4e75                 rts
/* src/objects.c, lines 140-154 */
/* orig 0x0107F2 object_draw_first - the other weapon, if any is left: 0x026F8A 0x14 for the
 * debug view behind 0x02536D, then the first free object record (of the fifteen, not the
 * extra one) goes to the launch at 0x01088E; with none free nothing is dropped. */
static void object_draw_first(void)
{
    wof_g.g_026f8a = 0x14;
    if (wof_g.weapon_count == 0)
        return;
    for (int i = 0; i < 15; i++) {
        if (wof_m.object_records[i].kind == 0) {
            launch(&wof_m.object_records[i]);
            return;
        }
    }
}

With all fifteen in use the walk returns: the game silently loses the shot, and since the executable's code takes the weapon off the count only in the launch, the lost shot costs nothing. No script filled all fifteen; the drop's test does, below. The launch takes the aircraft's two speeds, its x and its height as the pass last drew it, one of chapter 7's couplings, plus 0x0B, which the drawing of an object takes off again: the weapon starts where the aircraft is drawn. object_spawn makes the explosions of a crash or of a wreck at rest in the same object records (chapter 14), with kind 8 and type 1. The tick moves the object records and the pass draws them, freeing one that has gone out for the next tick's drop.

A weapon in flight

Once a tick, objects_step hands every object record whose kind byte is not zero to object_step, and what the record does is decided by a chain of tests, not by a table of routines or a switch: a kind byte of 8, going out, has nothing left to do; a type of 0 is a rocket; a type of 2 with a frame byte of 0x0A, the frame the pass draws, here the running torpedo's, drawn as nothing, is a torpedo running in the sea; everything else falls like a bomb.

A record's speeds are longs, the whole pixels in the upper word and the fraction below them, as chapter 5's bounce showed. A bomb loses a tenth of the whole part of its horizontal speed each tick, a division of whole numbers, so below ten pixels a tick it loses nothing more; gravity takes 0x6000, three-eighths of a pixel, off its vertical speed each tick. The height moves by that speed's whole part, rounded down, and keeps no fraction, so the speed's fraction never carries. As it falls, a bomb tumbles: its frame steps on every other pass, round twelve frames.

A bomb's curve from the aircraft to the ground, a dot a tick, the aircraft flying on; a rocket fired level that drops a few pixels and races on level, and one fired in a dive that follows the dive down.

A bomb dropped in level flight at 14 pixels a tick, starting where the aircraft is drawn at a height of 150, and two rockets that drop for 8 ticks before their thrust starts, fired level and in a dive of 20 degrees: computed from the arithmetic of object_step, a dot a tick, the heights drawn 1.6 times as tall as the x.

A rocket takes more at the launch. The bearing is the aircraft's pitch as an angle of 0x800 steps a turn, worked out each tick for whatever the aircraft shoots or drops next, and halved for the sine table's 0x400 steps. The thrust is the bearing's sine and cosine, at most a pixel a tick. The fall is the ticks the rocket drops before its thrust starts: two bits of a draw of the beam, 0, 4, 8 or 12, with 0 counting as 8. While it counts, the rocket moves by the aircraft's speeds and its vertical thrust, and a pixel back and a pixel down besides: a fall of 8 gives seven such ticks and the aim on the eighth. From then on the thrust is added to its speeds every tick. Fired level, it drops a few pixels and races on level, never coming down: the sine of 0 is 0, so it gets no downward thrust. Beyond 0x500 pixels from the aircraft, 1,280, four screens' width, its object record is freed.

The aim happens once, as the fall ends, and only for a rocket pointing down: the game takes the ground its bearing reaches and that of two bearings a little either side, and turns the rocket's speeds straight at a ship's gun or a standing pillbox between them; there is no homing after. No script's rocket found one, those fired at map c's pillboxes hitting them straight, so the oracle alone holds the aim.

Here is the head of the chain, and the rocket's fall and aim. Look at cmpi.b #$8,$20(a2), the kind, and cmpi.w #$0,$22(a2), the type; then at subq.w #$1,$24(a2), the fall's count, and at bsr.w $1099a, the aim, when it reaches zero.

; re/Wings.lst 0x010AA6-0x010AF8: object_step [asm], a part of 0x010AA6-0x010DA5
object_step:
010aa6  48e76420             movem.l    d1-d2/d5/a2, -(a7)
010aaa  2448                 movea.l    a0, a2
010aac  0c2a00080020         cmpi.b     #$8, $20(a2)
010ab2  67000246             beq.w      $10cfa
010ab6  0c6a00000022         cmpi.w     #$0, $22(a2)
010abc  66000086             bne.w      $10b44
010ac0  4a6a0024             tst.w      $24(a2)
010ac4  67000026             beq.w      $10aec
010ac8  536a0024             subq.w     #$1, $24(a2)
010acc  67000018             beq.w      $10ae6
010ad0  7001                 moveq      #$1, d0
010ad2  4a6ca08e             tst.w      -$5f72(a4)                             ; player_facing
010ad6  6a000004             bpl.w      $10adc
010ada  4440                 neg.w      d0
loc_010adc:
010adc  9152                 sub.w      d0, (a2)
010ade  536a0004             subq.w     #$1, $4(a2)
010ae2  60000018             bra.w      $10afc
loc_010ae6:
010ae6  204a                 movea.l    a2, a0
010ae8  6100feb0             bsr.w      $1099a
loc_010aec:
010aec  202a001a             move.l     $1a(a2), d0
010af0  d1aa0012             add.l      d0, $12(a2)
010af4  202a0016             move.l     $16(a2), d0
010af8  d1aa000e             add.l      d0, $e(a2)

The torpedo falls like a bomb, in one of its two frames. If it meets the sea no faster than 5 pixels a tick, it runs in it, 4.3 pixels a tick for 200 ticks, drawn as nothing: the player sees the splash it leaves every tick. It hits the first map record under it that is not sea, and goes out undrawn, so the pass never counts its frames and its object record stays in use until the hold clears them all. Faster, it goes out where it fell. The manual asks for a low drop with the ship in sight (page 7).

Twelve bomb frames, twenty rocket frames and a torpedo's two, tiny, with their names.

The weapons' frames of torpedo.shp: a bomb's twelve; a rocket's ten by its bearing while it falls, and the torpedo's two, by its facing, shown while it falls; the rocket's ten once its thrust starts.

Where a weapon comes down

Over an airfield a rocket that comes down to a height of 30 is freed at once, with no explosion, and anything else bounces on the runway (chapter 5); the first three maps have no airfield. Elsewhere the ground is 0x0C, 12 pixels above the water line, over the sea, the land and the targets alike, and a ship's deck over a ship. The executable's code does compare the map record's low bits with the slots of the dug-out and the barracks, 3 and 4, but on land the low bits are 2, never a slot's number: the test cannot hold, and a weapon comes down at the one height over those targets too, whatever chapter 13 gave as theirs. The port gives them that one height, its comment naming the test that cannot hold.

At the ground the object record keeps the low bits, which choose an explosion over land or a deck and a splash over the sea; makes the impact's sound (chapter 18); calls weapon_hit, two sections on; and goes out. The first running soldier within 16 pixels starts dying, as the soldiers' section tells.

The targets

An island carries three kinds of target, shapes standing in the map's records. A dug-out holds soldiers and fires at the aircraft while it holds any; a barracks holds soldiers and burns when hit; a pillbox, the notes' name for the large gun the manual gives the rockets for (pages 7 and 10), fires, and only a rocket destroys it.

Slot Shape The target Hit
3 dugo a dug-out, five soldiers inside at the start 200 points while it holds soldiers, who are let out
4 huta a barracks, five soldiers inside at the start 150 points; it burns, slot 5, hutb, and its soldiers are let out
0x0F to 0x1E pila to pilp a pillbox by a rocket only: 200 points, destroyed

A target is four map records wide, the third carrying the draw flag, and has an entry in a table the loader built (chapter 13): its place, its island, the soldiers inside and its counters.

A dug-out, a barracks and a burnt barracks; below, sixteen pictures of a grey pillbox, from whole to marked in every quarter.

The targets of world.shp: the dug-out, the barracks and the burnt barracks a hit leaves; the pillbox's sixteen pictures, pila whole to pilp, by the quarters a rocket has hit.

Each island keeps two counts: its soldiers alive, five for every dug-out and barracks, and its pillboxes standing. An island whose two counts have both fallen to zero is a neutralised island. No count of the island holds its dug-outs and barracks, yet the two counts make the manual's rule hold, that an island falls only when every barracks, soldier and gun on it is gone (page 10), and they do it through the soldiers. A soldier inside a target counts until he is dead. A dug-out's soldiers come out only when it is hit. A barracks gives a soldier to an empty dug-out that calls one over, the refill of the soldiers' section, only while it holds two or more, so its last comes out only when it is hit too.

Neutralised, the island pays a bonus from a table by map and island and puts its message on the ticker; the map's last island, with no enemy ship left, ends the mission, chapter 17's subject.

What a hit does

weapon_hit is what an impact does to the map record under the object. On the left, beq.w $14986, twice, sends the sea and a ship to a branch of their own; for a dug-out, tst.b $8(a0), the soldiers inside, decides: #$c8, 200, goes to the dug-out's 200-pass timer and to the score, and target_release lets the soldiers out. The C on the right begins after the branch to the sea.

; re/Wings.lst 0x0146DC-0x014764: weapon_hit [asm], a part of 0x0146DC-0x014A4D
weapon_hit:
0146dc  48e7fffe             movem.l    d0-d7/a0-a6, -(a7)
0146e0  3010                 move.w     (a0), d0
0146e2  2c48                 movea.l    a0, a6
0146e4  c0bc0000ffff         and.l      #$ffff, d0
0146ea  2400                 move.l     d0, d2
0146ec  4eac80f8             jsr        -$7f08(a4)                             ; -> map_slot_at
0146f0  b03c0000             cmp.b      #$0, d0
0146f4  67000290             beq.w      $14986
0146f8  c27c1fff             and.w      #$1fff, d1
0146fc  b03c0001             cmp.b      #$1, d0
014700  67000284             beq.w      $14986
014704  0c6e00000022         cmpi.w     #$0, $22(a6)
01470a  6600000e             bne.w      $1471a
01470e  397c0005a418         move.w     #$5, -$5be8(a4)                        ; flash_count
014714  397c0fffa41a         move.w     #$fff, -$5be6(a4)                      ; flash_colour
loc_01471a:
01471a  b27c0113             cmp.w      #$113, d1
01471e  66000006             bne.w      $14726
014722  60000324             bra.w      $14a48
loc_014726:
014726  b27c0003             cmp.w      #$3, d1
01472a  6600003c             bne.w      $14768
01472e  2002                 move.l     d2, d0
014730  4eba0422             jsr        $14b54(pc)                             ; target_of
014734  4a00                 tst.b      d0
014736  6b000310             bmi.w      $14a48
01473a  0c6e00000022         cmpi.w     #$0, $22(a6)
014740  66000008             bne.w      $1474a
014744  397c0f00a41a         move.w     #$f00, -$5be6(a4)                      ; flash_colour
loc_01474a:
01474a  4a280008             tst.b      $8(a0)
01474e  670002f8             beq.w      $14a48
014752  317c00c8000e         move.w     #$c8, $e(a0)
014758  06ac000000c8a34e     addi.l     #$c8, -$5cb2(a4)                       ; player_score
014760  4eba03de             jsr        $14b40(pc)                             ; target_release
014764  600002e2             bra.w      $14a48
/* src/targets.c, lines 386-404, a part of wof_weapon_hit (lines 325-456) */
slot &= 0x1FFFu;
if (o->type == 0) {
    wof_g.flash_count = 5;
    wof_g.flash_colour = 0x0FFF;
}
if (slot == 0x113)
    return;
if (slot == 3) {                                              /* 0x014726 */
    wof_gtarget_t *t = wof_target_of(x);

    if (!t)
        return;
    if (o->type == 0)
        wof_g.flash_colour = 0x0F00;
    if (t->state == 0)
        return;
    t->w0e = 0xC8;
    wof_g.player_score += 0xC8;
    target_release(t);

A dug-out hit is not destroyed: its soldiers join those still to come out, and a timer starts. Two rockets in one tick score once: the first empties the dug-out, and the second finds no soldier inside. A barracks burns whatever it holds: its four map records become slot 5, each keeping its draw flag, as chapter 13 told, and the burnt barracks smokes. A rocket at a pillbox adds to the slot of all four of its map records the bit of the one it hit, bit 3 for the first and bit 0 for the fourth: a bit for each quarter hit, the figure's sixteen pictures. The first rocket destroys the pillbox; later ones only mark quarters and score nothing, and a bomb does nothing to it.

On land a rocket flashes the sky, chapter 11's sky flash: a count of five passes, white, or red when the rocket hits a target, the colour showing on three of them. A bomb never flashes. Here are two rockets of a dive, down on a dug-out in the same tick:

The play screen with a red sky: the aircraft low, flying west, just past two explosions beside a dug-out, smoke drifting up behind it; 13 rockets and 200 points on the dashboard.

The rockets run, a replay of the script rockets_a, about 47 seconds into the mission: two rockets down on a dug-out of map a, the sky red. The run checks the score, 200 for one hit, the flash's colour, 13 rockets left and the aircraft's height, 41.

In the sea nothing happens. On a ship, chapter 16's subject, a bomb does nothing, a running torpedo takes one of the ship's hits, and anything else, a rocket or a torpedo that falls onto the deck, destroys the ship's first standing gun within 16 pixels, for 200 points. A crash on land hits as a rocket would, as chapter 14 told, and the first running soldier within 8 pixels of the wreck starts dying.

The soldiers

A soldier is a record of eight bytes: his x, a direction, a frame, a timer, his island and his state, 0 free, 1 running, 2 dying, 3 dead. The loader sizes the soldiers' table at five for every dug-out and barracks.

A hit lets a target's soldiers out one at a time, by a timer the tick runs: the first after 60 ticks, each next one after up to 31. That number is not chance but the clock, bits 4 to 8 of the count of VBlanks since the program's start, so a run that spends more VBlanks before the mission, as the music's fades do, lets its soldiers out at other ticks; three scripts set the count back at a fixed point.

He comes out at one end of his target and runs, in the pass, three pixels a pass, turning round at the water. Running into a dug-out he goes in, and a dug-out that was empty shows its gun again only 360 passes later. The refill: an empty dug-out calls a soldier over every 200 passes from the nearest barracks of its island that holds two or more, and he runs across. This refill is where chapter 9's upper word came from. Since the soldiers live in the pass, how long a pass takes sets how fast they run (chapter 7).

In the listing, sub.w d1,d0 and add.w d1,d1 turn the x and the half width into a span's start and width, so that one compare tests both ends: a soldier west of the span gives a negative difference, which bhi.w, reading it unsigned, takes for a huge one. A hit soldier gets state 2, frame 5 and a timer of 2, and screams.

; re/Wings.lst 0x011A8C-0x011AD8: soldiers_hit [asm]
; soldiers_hit   [asm]
;   asm. D0 = world x, D1 = half width: running soldiers there start dying
;   callers: object_step, gun_splashes
soldiers_hit:
011a8c  48e7f080             movem.l    d0-d3/a0, -(a7)
011a90  9041                 sub.w      d1, d0
011a92  d241                 add.w      d1, d1
011a94  206ca502             movea.l    -$5afe(a4), a0                         ; soldier_records
011a98  362ca3c6             move.w     -$5c3a(a4), d3                         ; soldier_count
011a9c  6000002e             bra.w      $11acc
loc_011aa0:
011aa0  0c6800010006         cmpi.w     #$1, $6(a0)
011aa6  66000022             bne.w      $11aca
011aaa  3410                 move.w     (a0), d2
011aac  9440                 sub.w      d0, d2
011aae  b441                 cmp.w      d1, d2
011ab0  62000018             bhi.w      $11aca
011ab4  317c00020006         move.w     #$2, $6(a0)
011aba  117c00050003         move.b     #$5, $3(a0)
011ac0  117c00020004         move.b     #$2, $4(a0)
011ac6  4eba08e4             jsr        $123ac(pc)                             ; sound_scream
loc_011aca:
011aca  5048                 addq.w     #$8, a0
loc_011acc:
011acc  51cbffd2             dbra       d3, $11aa0
011ad0  4eba0010             jsr        $11ae2(pc)                             ; torpedoes_hit
011ad4  4cdf010f             movem.l    (a7)+, d0-d3/a0
011ad8  4e75                 rts
/* src/tick.c, lines 353-375 */
/* orig 0x011A8C soldiers_hit - the running soldiers from x - w to x + w start dying (state 2,
 * frame 5, timer 2) with a scream; then 0x011AE2 over the same span.  The scream's sound
 * code (0x0123AC) leaves its own values in D0 and D1 and the walk goes on with them, so after
 * the first soldier hit the span is the scream's (src/sound.c). */
void wof_soldiers_hit(int16_t x, int16_t w)
{
    uint16_t d0 = (uint16_t)(x - w);
    uint16_t d1 = (uint16_t)(w + w);

    for (uint16_t n = 0; n < wof_g.soldier_count && n < 160; n++) {
        wof_soldier_t *s = &wof_m.soldier_records[n];

        if (s->state != 1)
            continue;
        if ((uint16_t)(s->x - d0) > d1)
            continue;
        s->state = 2;
        s->frame = 5;
        s->timer = 2;
        wof_sound_scream(&d0, &d1);                            /* 0x0123AC */
    }
    wof_torpedoes_hit(d0, d1);
}

The scream's sound routine returns with its own values in D0 and D1, which held the span, and the walk goes on with them: after the first soldier it hits, the span lies near the map's west end, so one impact sets one running soldier dying, unless others stand near world x 0. The original behaves so, as its code shows and the oracle holds; the port keeps it, and chapter 20 collects such quirks.

Dying, a soldier's frame steps every third pass, and after frame 7 he is dead, frame 8: 25 points, scored in the pass, and one soldier fewer on his island. A dead soldier stays where he fell.

A soldier's nine frames, running, falling, lying; below, a gun at seven angles, and the seven firing.

A soldier facing east, guy0 to guy8: running, dying, dead; nine more face west. Below, a target's gun by the aircraft's distance and height, gun0 to gun6, and firing, gnf0 to gnf6.

The search for a free soldier's record has no end and would run past the table if all were in use. That cannot happen: the soldiers inside, those still to come out and the soldiers' records in use, running, dying or dead, always add up to the table's size. The port marks the case as a stand-in, and a test holds the balance at more than 20,000 steps of the scripts.

The guns, and the targets' fire

The guns are no object at all. While the button is held and the aircraft flies straight, the tick works out where the bullets reach the ground ahead: the aircraft's height over the tangent of the bearing. They reach it only while the aircraft is sinking, below a height of 0xA0, 160, and not stalling, which is why the autopilot fires them in a shallow descent. Each tick a splash or a puff of dust is made there, and the first running soldier within 16 pixels starts dying. The rounds are a word set to 0x600 in the hold that nothing counts down: the guns never run out. The muzzle flash is drawn by chapter 12's exclusive-or blit.

The targets fire back. In each pass a dug-out with soldiers and a standing pillbox show one of seven frames of their gun, from a table by the aircraft's height and distance, or the firing frame when a draw of the beam allows; the index can leave the table's 48 bytes, so the port reads it by its address, as chapter 14 told of the wheel table. Then the gun may hit the aircraft on two draws of the beam: the first is held against the distance and the height, so the nearer and lower the aircraft, the likelier the hit, and the second lets about one value in five through. A hit on the aircraft puts smoke at the engine and counts down the hit count, a word of the player's record drawn from 6 to 9 at a reset; at zero the oil falls by one and the fuel by up to three, chapter 14's oil, and a new draw starts the count again at 6 to 13. The enemy's fighters count it down too (chapter 16).

The figures

The targets' fire
A gun shown within 512 pixels, 0x200
A gun that may hit within 448 pixels, 0x1C0
The first draw, below 512 must reach the larger of distance and height plus a quarter of the smaller
The second draw, below 2,048 must be at most 409, 0x199
The hit count 6 to 9 at a reset, then 6 to 13

The pools

Beside the object records the game keeps four pools, tables of small records allocated once at the start, their counts the allocations' sizes over the records'. Each of a pool's records says in a byte or a word whether it is in use, and a claim takes the first free one or, with none free, nothing; since no list says which are in use, every walker steps through its whole table, every pass or every tick.

Pool Records Each Free again
Smoke 40 a puff from the engine, a burnt barracks, a destroyed pillbox or a wreck, drifting one to two pixels a pass east and up when it has shrunk through its five frames, six for a hit on the aircraft, one every seventh pass
Splashes 20 a splash in the sea, of a bullet, a running torpedo, a ship's shell or a wreck, or a bullet's dust on land after 6 passes, 4 on land
Balloons 20 a balloon rising from the carrier after a promotion at a height of 170
Ricochet 20 never filled: its one writer is a routine nothing calls

The balloons rise only after the promotion at a rank's last mission, for the flight back to the carrier, each free balloon record going up again with a colour and a drift from the beam. The port keeps the Ricochet pool for the layout.

What the port made of it

The port keeps the same object records, pools and soldiers' records, bytes and chains of tests, in src/objects.c, src/targets.c, src/pools.c and src/tick.c, each routine with its orig comment and the original's widths. Where a register of these hand-written routines crosses a call, the C passes it as an argument and names it: chapter 9's upper word goes from one walk of the targets to the next, and the scream's two registers come back from the sound routine. The address of the map's records, which a wreck's explosion takes for an x, is an input of the port (chapter 20).

What the scripts reached was ported. What they did not reach was ported from reading the listing of chapter 4 and is held by the oracle, region by region; what belonged to the enemy was a stand-in until its milestone. One marker is left, the full table of soldiers the balance rules out.

The eighteen mission scripts of the weapons fly bombs over the first three maps, rockets over a and c and the torpedo off map a, the targets' fire until the engine seizes, a crash, the island cleared, the balloons, and the pause, the flip, the restart and the cheat while a bomb falls; maps b and c and the balloons are reached by a poke. The autopilot of chapter 8 flew them by plans: a rocket from a dive, the guns in a shallow descent, and a bomb where its own copy of object_step's arithmetic, run ahead, says the bomb comes down on a target. The prediction falls 14 pixels short, the tick or so between the tap and the drop, which the plans add. Look at the loop: a tenth off the horizontal speed, gravity off the vertical, the height's whole part, until the height of 12.

# tools/m5_autopilot.py, lines 116-138
def fall(m, s):
    """Where a bomb dropped now comes down, as object_step moves a type-1 record (0x010B58):
    the horizontal speed loses a tenth of its whole part every tick, the vertical long loses
    g_025350, and the height is the whole part of the vertical long plus the height with its
    old fraction.  It explodes at height 0x0C over land or sea (0x010C04, 0x010C1A).  The
    fraction the record keeps from its last use is taken as 0 here; the plan's `lead`
    absorbs the rest."""
    g = s32(m.o.r32(GRAVITY))
    x = s['x'] << 16
    dx = s['sx'] << 16
    if s['face'] < 0:
        dx = -dx
    vy = s['sy'] << 16
    y = (s['y'] + 0x0B) << 16
    for _ in range(400):
        q = int((dx >> 16) / 10)
        dx -= q << 16
        x += dx
        vy -= g
        y = ((vy + y) >> 16) << 16
        if (y >> 16) <= 0x0C:
            break
    return x >> 16

Every script runs in both loops of chapter 8, the closed loop and the open loop, every tick and pass compared, two also at one and three VBlanks a pass, and the completeness list accounts for every address they write. Under the oracle, twelve tests hold the routines over random settings of the registered state and three over every case there is. The drop's test fills all fifteen object records in every fifth case:

# tests/test_oracle_m5.py, lines 707-727
def test_the_drop_matches_the_original(tick):
    """0x01107C with 0x0107F2 and the launch at 0x01088E: every weapon type, counts from none
    to unlimited, bearings up and down, both facings, with free records and none."""
    d = tick
    o = d.o
    rng = random.Random(0x1107)
    for n in range(1500):
        d.randomise_tick(rng)
        w(o, 0x0253A4, rng.choice([0, 1, 2]))                          # weapon_type
        o.write(0x02536D, bytes([rng.choice([0, 1, 5, 30, 0xFF])]))    # weapon_count
        w(o, 0x025404, rng.randrange(-0x400, 0x400))
        w(o, 0x025414, rng.randrange(0, 1600))                         # airspeed
        for k in (0x026E62, 0x026E66, 0x026E6A, 0x026E6E):
            o.w32(k, rng.randrange(1 << 32))
        if n % 5 == 0:
            for i in range(15):
                o.write(OBJECTS + 0x2A * i + 0x20, bytes([0xFF]))
        d.load_port()
        o.call(0x01107C)
        d.port(0x01107C)
        check(d, 'case %d' % n)

No script of the weapons reached the promotion: an autopilot's eight sorties against map c killed 35 of its 70 soldiers. The campaign's scripts reach it with a poke (chapter 17).

For the developer

The routines: weapon_drop 0x01107C, object_step 0x010AA6, weapon_hit 0x0146DC, target_fire 0x014F5C, soldier_out 0x011E82, soldiers_draw 0x013EEE, gun_splashes 0x0119BC. The scripts are tools/m5_scripts.py, the upper word's test is in tests/test_oracle_m7.py.

What comes next

The chapter in one sentence: the weapons fly in fifteen object records with a byte that says in use, moved by a chain of tests and brought down at the ground's one height, the low bits saying what the hit does; a hit rewrites the map and lets soldiers out, and their deaths, with the pillboxes, neutralise an island. Chapter 16 takes up the enemy: the aircraft that come when the countdown runs out, the ships and their guns, the torpedo run, the airfields.

Further reading