{ ============================================================================
  ROUND 42  —  clean-room LOGIC reconstruction of the lost Turbo Pascal 3.0
              source for the 1986 DOS game ROUND42.EXE.

  (C) 1986 Elven Software / Mike Pooler.

  HOW THIS FILE WAS PRODUCED
  --------------------------
  The original Turbo Pascal 3.0 source is LOST. This file is a faithful
  reconstruction of the program LOGIC, recovered by disassembling the shipped
  binary ROUND42.EXE (a flat .COM image, load base 0x100) with Capstone via
  re/disrange.py, and decoding each subsystem independently. The TP3 compiler
  discarded all identifiers, so every name here is INVENTED for readability —
  but every statement is justified by the actual machine code, and each routine
  is prefixed with a {comment citing its EXE address}. The Borland TP3 runtime
  library (heap, CRT, the STACKCHECK stub @ 0x0FB2, the calibrated Delay loop)
  is intentionally OMITTED; only the game's own code is reconstructed.

  Points where the machine code was ambiguous are flagged with {?: ...} notes.

  Target: Turbo Pascal 3.0, real-mode DOS, CGA 160x100x16 (the "lores"
  composite/RGB 16-colour text-block mode, framebuffer segment B800).
  ============================================================================ }

program Round42;

{ ==================================== CONSTANTS =========================== }
const
  RowBytes = $A0;        { B800 bytes per scan-row (80 cells x 2)            }
  Bottom   = 95;         { floor cell, global [0xC35]                        }

  { hardware ports }
  SpkPort   = $61;       { PC speaker / PIT gate                             }
  PitMode   = $43;       { 8253 mode register                               }
  PitCh2    = $42;       { 8253 channel 2 (speaker)                          }
  CgaStatus = $3DA;      { CGA status register (bit0 = vertical retrace)     }

  { wall / floor limits used by the enemy movers }
  LeftWall  = 2;         { X clamp low                                       }
  RightWall = $4B;       { X clamp high (75)                                 }
  TopRow    = 7;         { spawn / top row                                   }

  { warp-round (Mode 6) geometry }
  WallStripeCell = $BB;  { LMAG-on-black stripe cell, B800 byte @ +1         }
  WallRow        = $4E;  { scan-row where each new wall row is laid          }
  RiseTop        = 2;    { [0xC4B] floor: round cleared when it hits 2        }
  RiseStart      = 12;   { [0xC4B] initial rise countdown (12 -> 2)           }

  { sprite/icon frame-buffer RAM bases (DS-relative offsets) }
  Frame0Buf = $9E6;      { frame 0 word buffer                               }
  Frame1Buf = $A60;      { frame 1 word buffer                               }
  Frame2Buf = $ADA;      { frame 2 word buffer                               }
  Frame3Buf = $B54;      { frame 3 word buffer                               }
  HeightBuf = $BCE;      { per-column byte height table (bird/spinner family)}

  { CS-relative source tables for BuildBird's four frames }
  BirdSrc0 = $2D27;  BirdSrc1 = $2D47;  BirdSrc2 = $2D67;  BirdSrc3 = $2D87;
  BirdHgtSrc = $2DA7;    { per-column height source for bird family          }

{ ====================================== TYPES ============================= }
type
  TEnemy = record
    X, Y          : byte;     { +0 +1  logical cell position                 }
    DrawnX,DrawnY : byte;     { +2 +3  last-drawn pos (for XOR-erase)         }
    DX, DY        : integer;  { +4 +6  velocity                              }
    Frame         : byte;     { +8     animation frame index                 }
  end;

{ =============================== GLOBAL VARS ============================== }
{ The shared contract. EXE offsets are DS-relative bytes/words. Where a global
  is reachable by name from many subsystems it is declared once here. }
var
  Round, Mode, EnemyCount, Cadence : byte;   { [0xC2D] [0xC19] [0xC18] [0xC29] }
  Wave        : array[0..45] of TEnemy;       { @ DS:0x260                   }
  PlayerX, PlayerY : byte;                     { [0xC1E] [0xC1F]             }
  Playing     : boolean;                       { [0xC5D]                     }
  MuteMask    : word;                          { [0xC57]                     }
  FrameSeg, FrameOfs : array[0..3] of word;    { [0x9DC] [0x9D4] icon ptrs   }

  { ---- music-engine state cells ---- }
  CurFreq      : word    absolute $0C4D;   { last frequency handed to Sound  }
  NoteIndex    : byte    absolute $0C39;   { current melody slot, 1..32      }
  SlidePos     : integer absolute $0C4F;   { pitch-slide accumulator         }
  SlideStep    : integer absolute $0C51;   { slide increment (bounces sign)  }
  FlashOn      : byte    absolute $0C5E;   { nonzero => border-flash on beat }

  { ---- movement / cadence private globals ---- }
  CadenceCtr   : byte    absolute $C28;   { counts up to Cadence then fires  }
  RRIndex      : byte    absolute $C2A;   { round-robin enemy index          }
  ActiveFlag   : byte    absolute $C5F;   { cleared when round-robin wraps   }
  InputCtr     : byte    absolute $C22;   { counts up to InputPeriod         }
  InputPeriod  : byte    absolute $C23;   { MovePlayer cadence               }
  HitFlash     : byte    absolute $C34;   { player-hit flash countdown       }
  BounceFrames : byte    absolute $C32;   { last anim frame for MoveBounce   }
  WrapFrames   : byte    absolute $C38;   { last anim frame for MoveWrap     }
  StarCtr      : byte    absolute $C2F;   { background star animation counter}
  StarPeriod   : byte    absolute $C26;
  WaveStep     : byte    absolute $C3A;   { wraps mod $23 -> drives wave adv }

  { input/mover behaviour gates (set per round) }
  WrapFireGate : boolean absolute $C68;   { MoveWrap: enable vertical step   }
  WrapJitterX  : boolean absolute $C71;   { MoveWrap: random +-X jitter      }
  WrapResetGt  : boolean absolute $C6A;   { MoveWrap: random reposition @F=$B}
  ChaosFire    : boolean absolute $C67;   { MoveChaos: emit bullet & 4-frame }
  FallDriftGt  : boolean absolute $C72;   { MoveFall: enable horizontal drift}
  FallSwoopGt  : boolean absolute $C63;   { MoveFall: enable Frame=1 swoop   }

  { sub-game flags polled by the play loops }
  GameOverReq  : boolean absolute $C59;
  RoundDoneReq : boolean absolute $C5C;
  PlayerDead   : boolean absolute $C62;

  { MoveWrap velocity / remap tables }
  WrapXVel : array[0..15] of integer absolute $BD5;
  WrapYVel : array[0..15] of integer absolute $BEF;
  WrapIcon : array[0..15] of byte    absolute $C09;
  { MoveBounce per-frame X-delta column table }
  BounceDX : array[0..15] of byte    absolute $BCE;
  { MoveBounce per-frame screen-row table lives read-only in CODE @ CS:0x2DAC }
  { RowTab[Frame] is accessed as a typed-const there. {?: original const}     }

  { ---- player / spawn private globals ---- }
  GoLeft, GoRight, GoUp, GoDown : boolean;     { [0xC5A][0xC5B][0xC66][0xC65] }
  PrevScan      : byte;                         { [0xC2C] last scancode seen  }
  CurScan       : byte;                         { [0xC2E] copy of INT9 latch  }
  ShipNewX, ShipNewY : byte;                    { [0xC20][0xC21] proposed cell}
  FireHeld      : boolean;                      { [0xC5C] F1 fire held        }
  HitTopFlag    : byte    absolute $C22;        { vert-clamp scratch          }
  SpawnScaleHi  : byte    absolute $C67;        { enable DY*Rnd(4)            }
  SpawnZeroVel  : byte    absolute $C68;        { zero-then-reseed velocity   }
  FrameMax      : byte    absolute $C32;         { Rnd ceiling for Frame      }
  PlayfieldLeft : byte    absolute $C3C;         { left X clamp               }
  AbortFlag     : boolean;                       { [0xC6F] {?: name}          }

  { ---- warp-round (Mode 6) private globals ---- }
  CadCount  : byte    absolute $C22;   { cadence accumulator for horiz move   }
  CadLimit  : byte    absolute $C23;   { CadCount hits CadLimit*2 => step      }
  Firing    : boolean absolute $C59;   { fire key held this frame              }
  AltFire   : boolean absolute $C5C;   { secondary-fire flag                   }
  RisePend  : boolean absolute $C6E;   { a wall-row rise is pending            }
  RiseCount : word    absolute $C4B;   { rise countdown 12..2                  }
  WallTone  : word    absolute $C45;   { running tone counter (rise audio)     }
  ShotX     : byte    absolute $C20;   { last-drawn corridor probe X           }
  ShotY     : byte    absolute $C21;   { last-drawn corridor probe Y           }

{ ============================ STATIC DATA TABLES ========================== }
{ file offset = mem_addr - 0x100; all sprite streams are byte-exact.          }

const
  { Title-music note->frequency table @ EXE mem 0x2CD9 (7 words).
    0 = rest/mute sentinel; 1..6 = A3,B3,C4,D4,E4,F4. }
  NoteFreq : array[0..6] of word =
    ($7FFF, 220, 247, 262, 294, 330, 349);

  { Title melody @ EXE mem 0x2CE6, read 1-based by NoteIndex (1..32).
    Element 0 is never read. Each byte indexes NoteFreq. }
  TitleMelody : array[0..32] of byte =
    ($01,
     1,5,1,5,1,6,1,6, 1,5,1,5,1,6,1,6,
     1,5,1,5,1,6,1,6, 1,5,4,3,4,3,2,3);

  { Border-flash duty pattern @ EXE mem 0x2D06, indexed by NoteIndex and $0F. }
  FlashDuty : array[0..15] of byte =
    (4,4,4,4,4, 1,1,1,1, 4,4,4,4, 1,1,1);

  { ---- Per-round sprite selector (round dispatch @ EXE 0xC381) ----
    Index into the 15 enemy sprite sets; -1 marks a WARP round. }
  RoundSprite : array[1..42] of integer = (
     0,  1,  2, -1,   3,  4,  5, -1,   6,  7,  8, -1,   9,  4,  5, -1,
    10, 11,  8, -1,   6, 12, 13, -1,   2,  5,  8, -1,   1, 12,  2, -1,
     6,  0, -1, -1,  -1,  3, -1, -1,  -1, 14);

  { ---- Per-round movement-mode selector (Mode var [0xC19]) ----
    6 always coincides with a warp round (RoundSprite[r] = -1). }
  RoundMode : array[1..42] of byte = (
    1, 4, 4, 6,  1, 5, 3, 6,  4, 1, 3, 6,  2, 5, 3, 6,
    4, 5, 3, 6,  4, 4, 4, 6,  4, 3, 3, 6,  4, 4, 4, 6,
    4, 1, 6, 6,  6, 1, 6, 6,  6, 2);

{ ---------------------------------------------------------------------------
  THE 15 ENEMY SPRITES — raw (colour,advance) streams, exactly as stored in
  the EXE. Each pixel record is 4 bytes (colour,$00,advance,$00); a frame ends
  at the record whose advance byte = $00. PlotIcon replays the stream:
  'colour' is the packed 2-pixel CGA cell (hi nibble = left, lo = right;
  nibble 0 = transparent); 'advance' moves the B800 write pointer
  ($02=+1 cell, $04=+2, $06=+3; $9a/$9c/$9e=next row back 3/2/1; $a0/$a2=next
  row same/forward; $00=terminator).
  --------------------------------------------------------------------------- }
const
  { sprite 0 = BIRD (rounds 1,34) — symmetric flapper }
  BirdF0 : array[0..23] of byte = (  { EXE 0x2D27, 6px }
    $b0,$00,$06,$00, $b0,$00,$9a,$00, $0b,$00,$02,$00,
    $b0,$00,$02,$00, $bb,$00,$9e,$00, $0b,$00,$00,$00);
  BirdF1 : array[0..3] of byte = ($00,$00,$00,$00);   { EXE 0x2D3F, blank }
  BirdF2 : array[0..3] of byte = ($00,$00,$00,$00);   { EXE 0x2D43, blank }
  BirdF3 : array[0..19] of byte = (  { EXE 0x2D47, 5px }
    $cc,$00,$02,$00, $c0,$00,$02,$00, $cc,$00,$02,$00,
    $c0,$00,$9c,$00, $0c,$00,$00,$00);

  { sprite 9 = BUG (round 13) — 2 frames }
  BugF0 : array[0..55] of byte = (  { EXE 0x2DB6, 14px }
    $04,$00,$02,$00, $40,$00,$9e,$00, $44,$00,$02,$00, $44,$00,$9e,$00,
    $a4,$00,$02,$00, $a4,$00,$9e,$00, $44,$00,$02,$00, $44,$00,$9e,$00,
    $44,$00,$02,$00, $44,$00,$9e,$00, $40,$00,$02,$00, $40,$00,$9e,$00,
    $40,$00,$02,$00, $40,$00,$00,$00);
  BugF1 : array[0..55] of byte = (  { EXE 0x2DEE, 14px }
    $04,$00,$02,$00, $40,$00,$9e,$00, $44,$00,$02,$00, $44,$00,$9e,$00,
    $4a,$00,$02,$00, $4a,$00,$9e,$00, $44,$00,$02,$00, $44,$00,$9e,$00,
    $44,$00,$02,$00, $44,$00,$9e,$00, $04,$00,$02,$00, $04,$00,$9e,$00,
    $04,$00,$02,$00, $04,$00,$00,$00);

  { sprite 5 = PYRAMID (rounds 7,15,26) }
  PyramidF0 : array[0..59] of byte = (  { EXE 0x2E26, 15px }
    $00,$00,$02,$00, $66,$00,$9e,$00, $06,$00,$02,$00, $66,$00,$02,$00,
    $60,$00,$9c,$00, $66,$00,$02,$00, $66,$00,$02,$00, $66,$00,$9c,$00,
    $66,$00,$02,$00, $66,$00,$02,$00, $66,$00,$9c,$00, $06,$00,$02,$00,
    $66,$00,$02,$00, $60,$00,$9e,$00, $66,$00,$00,$00);
  PyramidF1 : array[0..59] of byte = (  { EXE 0x2E62, 15px }
    $00,$00,$02,$00, $66,$00,$9e,$00, $06,$00,$02,$00, $66,$00,$02,$00,
    $60,$00,$9c,$00, $66,$00,$02,$00, $cc,$00,$02,$00, $66,$00,$9c,$00,
    $66,$00,$02,$00, $cc,$00,$02,$00, $66,$00,$9c,$00, $06,$00,$02,$00,
    $66,$00,$02,$00, $60,$00,$9e,$00, $66,$00,$00,$00);
  PyramidF2 : array[0..59] of byte = (  { EXE 0x2E9E, 15px }
    $00,$00,$02,$00, $66,$00,$9e,$00, $06,$00,$02,$00, $cc,$00,$02,$00,
    $60,$00,$9c,$00, $6c,$00,$02,$00, $cc,$00,$02,$00, $c6,$00,$9c,$00,
    $6c,$00,$02,$00, $cc,$00,$02,$00, $c6,$00,$9c,$00, $06,$00,$02,$00,
    $cc,$00,$02,$00, $60,$00,$9e,$00, $66,$00,$00,$00);
  PyramidF3 : array[0..59] of byte = (  { EXE 0x2EDA, 15px }
    $00,$00,$02,$00, $cc,$00,$9e,$00, $0c,$00,$02,$00, $cc,$00,$02,$00,
    $c0,$00,$9c,$00, $cc,$00,$02,$00, $cc,$00,$02,$00, $cc,$00,$9c,$00,
    $cc,$00,$02,$00, $cc,$00,$02,$00, $cc,$00,$9c,$00, $0c,$00,$02,$00,
    $cc,$00,$02,$00, $c0,$00,$9e,$00, $cc,$00,$00,$00);

  { sprite 1 = ROBOT (rounds 2,29) — 3 real frames }
  RobotF0 : array[0..83] of byte = (  { EXE 0x2F1D, 21px }
    $0f,$00,$02,$00, $ff,$00,$02,$00, $00,$00,$9c,$00, $ff,$00,$02,$00,
    $ff,$00,$02,$00, $f0,$00,$9c,$00, $f0,$00,$02,$00, $f0,$00,$02,$00,
    $f0,$00,$9c,$00, $ff,$00,$02,$00, $ff,$00,$02,$00, $f0,$00,$9c,$00,
    $f0,$00,$02,$00, $00,$00,$02,$00, $f0,$00,$9c,$00, $ff,$00,$02,$00,
    $ff,$00,$02,$00, $f0,$00,$9c,$00, $ff,$00,$02,$00, $0f,$00,$02,$00,
    $f0,$00,$00,$00);
  RobotF1 : array[0..95] of byte = (  { EXE 0x2F71, 24px }
    $0f,$00,$02,$00, $ff,$00,$02,$00, $00,$00,$9c,$00, $ff,$00,$02,$00,
    $ff,$00,$02,$00, $f0,$00,$9c,$00, $f0,$00,$02,$00, $f0,$00,$02,$00,
    $f0,$00,$9c,$00, $ff,$00,$02,$00, $ff,$00,$02,$00, $f0,$00,$9c,$00,
    $f0,$00,$02,$00, $00,$00,$02,$00, $f0,$00,$9c,$00, $ff,$00,$02,$00,
    $ff,$00,$02,$00, $f0,$00,$9c,$00, $0f,$00,$02,$00, $0f,$00,$02,$00,
    $00,$00,$9c,$00, $ff,$00,$02,$00, $0f,$00,$02,$00, $f0,$00,$00,$00);
  RobotF2 : array[0..107] of byte = (  { EXE 0x2FD1, 27px }
    $0f,$00,$02,$00, $ff,$00,$02,$00, $00,$00,$9c,$00, $ff,$00,$02,$00,
    $ff,$00,$02,$00, $f0,$00,$9c,$00, $f0,$00,$02,$00, $f0,$00,$02,$00,
    $f0,$00,$9c,$00, $ff,$00,$02,$00, $ff,$00,$02,$00, $f0,$00,$9c,$00,
    $f0,$00,$02,$00, $00,$00,$02,$00, $f0,$00,$9c,$00, $ff,$00,$02,$00,
    $ff,$00,$02,$00, $f0,$00,$9c,$00, $0f,$00,$02,$00, $0f,$00,$02,$00,
    $00,$00,$9c,$00, $0f,$00,$02,$00, $0f,$00,$02,$00, $00,$00,$9c,$00,
    $ff,$00,$02,$00, $0f,$00,$02,$00, $f0,$00,$00,$00);

  { sprite 11 = BAT (round 18) }
  BatF0 : array[0..23] of byte = (  { EXE 0x307E, 6px }
    $b0,$00,$06,$00, $b0,$00,$9a,$00, $0b,$00,$04,$00,
    $0b,$00,$9e,$00, $bb,$00,$02,$00, $b0,$00,$00,$00);
  BatF1 : array[0..31] of byte = (  { EXE 0x3096, 8px }
    $00,$00,$a2,$00, $00,$00,$a0,$00, $bb,$00,$02,$00, $b0,$00,$9c,$00,
    $0b,$00,$04,$00, $0b,$00,$9c,$00, $b0,$00,$06,$00, $b0,$00,$00,$00);
  BatF2 : array[0..23] of byte = (  { EXE 0x30B6, 6px }
    $a0,$00,$06,$00, $a0,$00,$9a,$00, $0a,$00,$04,$00,
    $0a,$00,$9e,$00, $aa,$00,$02,$00, $a0,$00,$00,$00);
  BatF3 : array[0..31] of byte = (  { EXE 0x30CE, 8px }
    $00,$00,$a2,$00, $00,$00,$a0,$00, $aa,$00,$02,$00, $a0,$00,$9c,$00,
    $0a,$00,$04,$00, $0a,$00,$9c,$00, $a0,$00,$06,$00, $a0,$00,$00,$00);

  { sprite 7 = WALKER (round 10) }
  WalkerF0 : array[0..47] of byte = (  { EXE 0x30EE, 12px }
    $99,$00,$02,$00, $99,$00,$9e,$00, $99,$00,$02,$00, $99,$00,$9e,$00,
    $40,$00,$02,$00, $00,$00,$9e,$00, $40,$00,$02,$00, $00,$00,$9e,$00,
    $99,$00,$02,$00, $99,$00,$9e,$00, $99,$00,$02,$00, $99,$00,$00,$00);
  WalkerF1 : array[0..47] of byte = (  { EXE 0x311E, 12px }
    $99,$00,$02,$00, $99,$00,$9e,$00, $99,$00,$02,$00, $99,$00,$9e,$00,
    $04,$00,$02,$00, $00,$00,$9e,$00, $04,$00,$02,$00, $00,$00,$9e,$00,
    $99,$00,$02,$00, $99,$00,$9e,$00, $99,$00,$02,$00, $99,$00,$00,$00);
  WalkerF2 : array[0..47] of byte = (  { EXE 0x314E, 12px }
    $99,$00,$02,$00, $99,$00,$9e,$00, $99,$00,$02,$00, $99,$00,$9e,$00,
    $00,$00,$02,$00, $40,$00,$9e,$00, $00,$00,$02,$00, $40,$00,$9e,$00,
    $99,$00,$02,$00, $99,$00,$9e,$00, $99,$00,$02,$00, $99,$00,$00,$00);
  WalkerF3 : array[0..47] of byte = (  { EXE 0x317E, 12px }
    $99,$00,$02,$00, $99,$00,$9e,$00, $99,$00,$02,$00, $99,$00,$9e,$00,
    $00,$00,$02,$00, $04,$00,$9e,$00, $00,$00,$02,$00, $04,$00,$9e,$00,
    $99,$00,$02,$00, $99,$00,$9e,$00, $99,$00,$02,$00, $99,$00,$00,$00);

  { sprite 8 = LANDER (rounds 11,19,27) — 2 frames }
  LanderF0 : array[0..23] of byte = (  { EXE 0x31AE, 6px }
    $00,$00,$02,$00, $77,$00,$a0,$00, $77,$00,$a0,$00,
    $77,$00,$a0,$00, $77,$00,$a0,$00, $77,$00,$00,$00);
  LanderF1 : array[0..31] of byte = (  { EXE 0x31C6, 8px }
    $00,$00,$02,$00, $77,$00,$a0,$00, $77,$00,$a0,$00, $77,$00,$9e,$00,
    $07,$00,$04,$00, $70,$00,$9c,$00, $70,$00,$04,$00, $07,$00,$00,$00);

  { sprite 3 = SPINNER (rounds 5,38) }
  SpinnerF0 : array[0..11] of byte = (  { EXE 0x31E6, 3px }
    $0a,$00,$a0,$00, $0c,$00,$a0,$00, $0a,$00,$00,$00);
  SpinnerF1 : array[0..15] of byte = (  { EXE 0x31F2, 4px }
    $00,$00,$02,$00, $a0,$00,$9e,$00, $0c,$00,$a0,$00, $a0,$00,$00,$00);
  SpinnerF2 : array[0..11] of byte = (  { EXE 0x3202, 3px }
    $00,$00,$a0,$00, $ac,$00,$02,$00, $a0,$00,$00,$00);
  SpinnerF3 : array[0..11] of byte = (  { EXE 0x320E, 3px }
    $a0,$00,$a0,$00, $0c,$00,$a2,$00, $a0,$00,$00,$00);

  { sprite 12 = WORM (rounds 22,30) — 2 frames }
  WormF0 : array[0..55] of byte = (  { EXE 0x321A, 14px }
    $e0,$00,$02,$00, $cc,$00,$02,$00, $cc,$00,$9c,$00, $0e,$00,$04,$00,
    $aa,$00,$02,$00, $a0,$00,$9a,$00, $e0,$00,$06,$00, $0a,$00,$9a,$00,
    $0e,$00,$04,$00, $aa,$00,$02,$00, $a0,$00,$9a,$00, $e0,$00,$02,$00,
    $cc,$00,$02,$00, $cc,$00,$00,$00);
  WormF1 : array[0..55] of byte = (  { EXE 0x3252, 14px }
    $0e,$00,$02,$00, $cc,$00,$02,$00, $cc,$00,$9c,$00, $e0,$00,$04,$00,
    $aa,$00,$02,$00, $a0,$00,$9a,$00, $0e,$00,$06,$00, $0a,$00,$9a,$00,
    $e0,$00,$04,$00, $aa,$00,$02,$00, $a0,$00,$9a,$00, $0e,$00,$02,$00,
    $cc,$00,$02,$00, $cc,$00,$00,$00);

  { sprite 2 = GEM (rounds 3,25,31) — colour-cycles a/9/f/6 hues }
  GemF0 : array[0..31] of byte = (  { EXE 0x32CB, 8px }
    $0a,$00,$02,$00, $a0,$00,$9e,$00, $a9,$00,$02,$00, $9a,$00,$9e,$00,
    $a9,$00,$02,$00, $9a,$00,$9e,$00, $0a,$00,$02,$00, $a0,$00,$00,$00);
  GemF1 : array[0..31] of byte = (  { EXE 0x32EB, 8px }
    $09,$00,$02,$00, $90,$00,$9e,$00, $9f,$00,$02,$00, $f9,$00,$9e,$00,
    $9f,$00,$02,$00, $f9,$00,$9e,$00, $09,$00,$02,$00, $90,$00,$00,$00);
  GemF2 : array[0..31] of byte = (  { EXE 0x330B, 8px }
    $0f,$00,$02,$00, $f0,$00,$9e,$00, $f6,$00,$02,$00, $6f,$00,$9e,$00,
    $f6,$00,$02,$00, $6f,$00,$9e,$00, $0f,$00,$02,$00, $f0,$00,$00,$00);
  GemF3 : array[0..31] of byte = (  { EXE 0x332B, 8px }
    $06,$00,$02,$00, $60,$00,$9e,$00, $6a,$00,$02,$00, $a6,$00,$9e,$00,
    $6a,$00,$02,$00, $a6,$00,$9e,$00, $06,$00,$02,$00, $60,$00,$00,$00);

  { sprite 10 = MASK (round 17) }
  MaskF0 : array[0..59] of byte = (  { EXE 0x3387, 15px }
    $0e,$00,$02,$00, $ee,$00,$9e,$00, $e1,$00,$02,$00, $11,$00,$02,$00,
    $e0,$00,$9c,$00, $ef,$00,$02,$00, $1f,$00,$02,$00, $e0,$00,$9c,$00,
    $e1,$00,$02,$00, $11,$00,$02,$00, $e0,$00,$9c,$00, $0e,$00,$02,$00,
    $0e,$00,$9e,$00, $e0,$00,$04,$00, $e0,$00,$00,$00);
  MaskF1 : array[0..55] of byte = (  { EXE 0x33C3, 14px }
    $0e,$00,$02,$00, $ee,$00,$9e,$00, $e1,$00,$02,$00, $11,$00,$02,$00,
    $e0,$00,$9c,$00, $ef,$00,$02,$00, $1f,$00,$02,$00, $e0,$00,$9c,$00,
    $e1,$00,$02,$00, $11,$00,$02,$00, $e0,$00,$9c,$00, $ee,$00,$02,$00,
    $0e,$00,$02,$00, $e0,$00,$00,$00);
  MaskF2 : array[0..59] of byte = (  { EXE 0x33FB, 15px }
    $0e,$00,$02,$00, $ee,$00,$9e,$00, $e1,$00,$02,$00, $11,$00,$02,$00,
    $e0,$00,$9c,$00, $ef,$00,$02,$00, $1f,$00,$02,$00, $e0,$00,$9c,$00,
    $e1,$00,$02,$00, $11,$00,$02,$00, $e0,$00,$9c,$00, $0e,$00,$02,$00,
    $0e,$00,$9e,$00, $0e,$00,$02,$00, $0e,$00,$00,$00);
  MaskF3 : array[0..51] of byte = (  { EXE 0x3437, 13px }
    $0e,$00,$02,$00, $ee,$00,$9e,$00, $e1,$00,$02,$00, $11,$00,$02,$00,
    $e0,$00,$9c,$00, $ef,$00,$02,$00, $1f,$00,$02,$00, $e0,$00,$9c,$00,
    $e1,$00,$02,$00, $11,$00,$02,$00, $e0,$00,$9c,$00, $0e,$00,$02,$00,
    $ee,$00,$00,$00);

  { sprite 13 = BALL (round 23) — dissolving sphere }
  BallF0 : array[0..31] of byte = (  { EXE 0x34A7, 8px }
    $0f,$00,$02,$00, $f0,$00,$9e,$00, $ff,$00,$02,$00, $ff,$00,$9e,$00,
    $ff,$00,$02,$00, $ff,$00,$9e,$00, $0f,$00,$02,$00, $f0,$00,$00,$00);
  BallF1 : array[0..31] of byte = (  { EXE 0x34C7, 8px }
    $0f,$00,$02,$00, $f0,$00,$9e,$00, $f0,$00,$02,$00, $f0,$00,$9e,$00,
    $0f,$00,$02,$00, $f0,$00,$9e,$00, $0f,$00,$02,$00, $f0,$00,$00,$00);
  BallF2 : array[0..31] of byte = (  { EXE 0x34E7, 8px }
    $0f,$00,$02,$00, $00,$00,$9e,$00, $00,$00,$02,$00, $f0,$00,$9e,$00,
    $0f,$00,$02,$00, $00,$00,$9e,$00, $00,$00,$02,$00, $f0,$00,$00,$00);
  BallF3 : array[0..31] of byte = (  { EXE 0x3507, 8px }
    $00,$00,$02,$00, $00,$00,$9e,$00, $00,$00,$02,$00, $f0,$00,$9e,$00,
    $0f,$00,$02,$00, $00,$00,$9e,$00, $00,$00,$02,$00, $00,$00,$00,$00);

  { sprite 6 = ANTBOT (rounds 9,21,33) }
  AntbotF0 : array[0..35] of byte = (  { EXE 0x3563, 9px }
    $33,$00,$02,$00, $30,$00,$9e,$00, $c3,$00,$02,$00, $c0,$00,$9e,$00,
    $cc,$00,$02,$00, $c0,$00,$9e,$00, $c3,$00,$02,$00, $c0,$00,$9e,$00,
    $03,$00,$00,$00);
  AntbotF1 : array[0..35] of byte = (  { EXE 0x3587, 9px }
    $33,$00,$02,$00, $30,$00,$9e,$00, $03,$00,$a0,$00, $c3,$00,$02,$00,
    $c0,$00,$9e,$00, $cc,$00,$02,$00, $c0,$00,$9e,$00, $c3,$00,$02,$00,
    $c0,$00,$00,$00);
  AntbotF2 : array[0..39] of byte = (  { EXE 0x35AB, 10px }
    $33,$00,$02,$00, $30,$00,$9e,$00, $03,$00,$a0,$00, $03,$00,$a0,$00,
    $c3,$00,$02,$00, $c0,$00,$9e,$00, $cc,$00,$02,$00, $c0,$00,$9e,$00,
    $c0,$00,$02,$00, $c0,$00,$00,$00);
  AntbotF3 : array[0..39] of byte = (  { EXE 0x35D3, 10px }
    $33,$00,$02,$00, $30,$00,$9e,$00, $03,$00,$a0,$00, $03,$00,$a0,$00,
    $c3,$00,$02,$00, $c0,$00,$9e,$00, $cc,$00,$02,$00, $c0,$00,$9e,$00,
    $c0,$00,$02,$00, $c0,$00,$00,$00);

  { sprite 4 = EYE (rounds 6,14) — pupil sweeps left<->right }
  EyeF0 : array[0..51] of byte = (  { EXE 0x3637, 13px }
    $0e,$00,$02,$00, $ee,$00,$02,$00, $e0,$00,$9c,$00, $aa,$00,$02,$00,
    $ee,$00,$02,$00, $ee,$00,$9c,$00, $aa,$00,$02,$00, $ee,$00,$02,$00,
    $ee,$00,$9c,$00, $0e,$00,$02,$00, $ee,$00,$02,$00, $e0,$00,$9e,$00,
    $ee,$00,$00,$00);
  EyeF1 : array[0..51] of byte = (  { EXE 0x366B, 13px }
    $0e,$00,$02,$00, $ee,$00,$02,$00, $e0,$00,$9c,$00, $ea,$00,$02,$00,
    $ae,$00,$02,$00, $ee,$00,$9c,$00, $ea,$00,$02,$00, $ae,$00,$02,$00,
    $ee,$00,$9c,$00, $0e,$00,$02,$00, $ee,$00,$02,$00, $e0,$00,$9e,$00,
    $ee,$00,$00,$00);
  EyeF2 : array[0..51] of byte = (  { EXE 0x369F, 13px }
    $0e,$00,$02,$00, $ee,$00,$02,$00, $e0,$00,$9c,$00, $ee,$00,$02,$00,
    $ea,$00,$02,$00, $ae,$00,$9c,$00, $ee,$00,$02,$00, $ea,$00,$02,$00,
    $ae,$00,$9c,$00, $0e,$00,$02,$00, $ee,$00,$02,$00, $e0,$00,$9e,$00,
    $ee,$00,$00,$00);
  EyeF3 : array[0..51] of byte = (  { EXE 0x36D3, 13px }
    $0e,$00,$02,$00, $ee,$00,$02,$00, $e0,$00,$9c,$00, $ae,$00,$02,$00,
    $ee,$00,$02,$00, $ea,$00,$9c,$00, $ae,$00,$02,$00, $ee,$00,$02,$00,
    $ea,$00,$9c,$00, $0e,$00,$02,$00, $ee,$00,$02,$00, $e0,$00,$9e,$00,
    $ee,$00,$00,$00);

  { sprite 14 = BOSS (round 42) — 2 frames }
  BossF0 : array[0..87] of byte = (  { EXE 0x3707, 22px }
    $00,$00,$04,$00, $0a,$00,$a0,$00, $b0,$00,$9e,$00, $07,$00,$02,$00,
    $70,$00,$9e,$00, $cc,$00,$02,$00, $cc,$00,$9c,$00, $0e,$00,$02,$00,
    $ee,$00,$02,$00, $e0,$00,$9c,$00, $dd,$00,$02,$00, $dd,$00,$02,$00,
    $dd,$00,$9c,$00, $0e,$00,$02,$00, $ee,$00,$02,$00, $e0,$00,$9e,$00,
    $cc,$00,$02,$00, $cc,$00,$9e,$00, $07,$00,$02,$00, $70,$00,$a0,$00,
    $b0,$00,$a0,$00, $0a,$00,$00,$00);
  BossF1 : array[0..83] of byte = (  { EXE 0x375F, 21px }
    $a0,$00,$a0,$00, $0b,$00,$a0,$00, $07,$00,$02,$00, $70,$00,$9e,$00,
    $cc,$00,$02,$00, $cc,$00,$9e,$00, $0e,$00,$02,$00, $ee,$00,$02,$00,
    $e0,$00,$9c,$00, $dd,$00,$02,$00, $dd,$00,$02,$00, $dd,$00,$9c,$00,
    $0e,$00,$02,$00, $ee,$00,$02,$00, $e0,$00,$9c,$00, $cc,$00,$02,$00,
    $cc,$00,$9e,$00, $07,$00,$02,$00, $70,$00,$9e,$00, $0b,$00,$a0,$00,
    $a0,$00,$00,$00);

  { THE PLAYER SHIP — not a table sprite; the draw prim @ EXE 0x3B7A writes
    6 fixed CGA cell bytes at fixed B800 offsets (see DrawShip). Reproduced
    here as the literal (cell byte, si-advance) sequence for reference. }
  ShipBytes : array[0..5] of byte =
    ($bb, $ab, $ff, $ba, $bf, $fb);
  ShipDelta : array[0..5] of integer =
    ($9e, $02, $02, $9c, $04, 0);   { last cell has no advance }

{ Forward declarations for routines referenced before their definition. }
procedure WaitRetrace; forward;
procedure PlotIcon(x, y : integer; seg, ofs : word); forward;
procedure PlotCell(x, y, colour : integer); forward;
procedure DrawShip(x, y : integer); forward;
procedure Sound(freq : word); forward;
procedure NoSound; forward;
procedure Delay(ms : word); forward;
function  Rnd(n : integer) : integer; forward;

procedure SpawnFormation; forward;
procedure ClearWave; forward;
procedure InitWarpField; forward;
procedure ReadInput; forward;
procedure MovePlayer; forward;
procedure MovePlayerHoriz; forward;
procedure ShowGameOver; forward;
procedure EndRound; forward;
procedure WaitTicks(n : word); forward;
procedure AnimateBackground; forward;
procedure AdvanceWave; forward;
procedure PlayerHitFx; forward;
procedure TwinkleStars; forward;
procedure SpawnFaller(x, y : byte); forward;
procedure FireBullet(x, y, dx, dy, colour : integer); forward;
procedure FirePlayerShot; forward;
procedure FireAlt; forward;
procedure ExplodePlayer; forward;
procedure MoveBounceSetup(idx : byte); forward;
procedure KeyClick; forward;

{ ==================================== SOUND =============================== }

{ Sound @ 0x057F — program PIT channel 2 to freq Hz on the PC speaker. }
procedure Sound(freq : word);
var divisor : word;
begin
  { freq<=18 plays nothing (guards divide overflow / sub-audible). }
  if freq <= 18 then exit;                        {?: jae 0x5a5 -> straight ret}
  divisor := 1193181 div freq;                    { dword 0x001234DD div bx }
  { only (re)arm the squarewave mode register if the gate was previously off
    (avoids a click mid-note). }
  if (Port[SpkPort] and 3) = 0 then begin
    Port[SpkPort] := Port[SpkPort] or 3;          { gate2 + speaker-data on }
    Port[PitMode] := $B6;                         { ch2, lo/hi, mode3 square }
  end;
  Port[PitCh2] := Lo(divisor);                    { 0x42 low byte }
  Port[PitCh2] := Hi(divisor);                    { 0x42 high byte }
end;

{ NoSound @ 0x05A6 — drop gate2 + speaker-data bits, silencing the speaker. }
procedure NoSound;
begin
  Port[SpkPort] := Port[SpkPort] and $FC;         { clear bits 0,1 }
end;

{ Delay(ms) @ 0x01F7 — TP3 runtime calibrated busy-wait (stub). }
procedure Delay;
begin
  {?: calibrated empty loop, count word @ DS:0x0012 — machine-speed dependent;
      left as a stub per the reconstruction brief. }
end;

{ Rnd(n) @ TP runtime RAND 0x10BB — TP3 Random(n) wrapper (stub for linkage). }
function Rnd;
begin
  {?: the Borland TP3 LCG; reproduced only as the contract entry point. }
  Rnd := 0;
end;

{ TickMusic @ 0x45BE — advance the title tune by ONE note-slot per call.
  Maintains a triangular pitch-slide that bends each note, sounds
  NoteFreq[TitleMelody[NoteIndex]] scaled by the slide, then optionally
  flashes the CGA border on the beat. (This is the title-music updater the
  flow also reuses as AdvanceWave during the death-wave animation.) }
procedure TickMusic;
var
  note : byte;
begin
  NoteIndex := NoteIndex + 1;                       { 0x45C5 }

  if NoteIndex = 33 then begin                      { 0x45D5 wrap @ 0x21 }
    NoteIndex := 1;
    if SlidePos >= 2  then SlideStep := -SlideStep; { 0x45E3 }
    if SlidePos <= -2 then SlideStep := -SlideStep; { 0x45F6 }
    SlidePos := SlidePos + SlideStep;               { 0x4609 }
  end;

  { 0x4613: skip the silent zero-crossing of the triangle wave. }
  while SlidePos = 0 do                              {?: zero-skip fold }
    SlidePos := SlidePos + SlideStep;

  note := TitleMelody[NoteIndex];

  if SlidePos > 0 then
    CurFreq := NoteFreq[note] * SlidePos             { 0x464E imul }
  else
    CurFreq := NoteFreq[note] div SlidePos;          { 0x466D idiv {?: <0} }

  Sound(CurFreq or MuteMask);                        { 0x4691 }

  if FlashOn <> 0 then                               { 0x469B }
    Port[CgaStatus - 1] := FlashDuty[NoteIndex and $0F];   {?: out 0x3D9}
end;

{ AdvanceWave @ 0x45BE — alias of TickMusic, called once per death-wave step. }
procedure AdvanceWave;
begin
  TickMusic;
end;

{ SetMute @ 0xB3B7 / 0xB3C8 — F9 mutes, F10 un-mutes. 0x7FFF saturates the
  divisor so nothing audible plays. }
procedure SetMute(on : boolean);
begin
  if on then MuteMask := $7FFF    { F9  @ 0xB3B7 }
        else MuteMask := 0;       { F10 @ 0xB3C8 }
end;

{ ============================= GRAPHICS PRIMITIVES ======================== }
{ CGA 160x100x16, segment B800, vsync-locked. Lores-Toolbox dispatch jump
  table @ 0x3968..0x3989 (3-byte near JMP per entry):
      0x3971 -> 0x3B2A  PlotCell(x,y,colour)
      0x3974 -> 0x3B7A  DrawShip(x,y)
      0x397A -> 0x3CFE  ReadCell (warp corridor probe)
      0x397D -> 0x3DAE  PlotIcon(x,y,seg,ofs) }

{ WaitRetrace @ inlined everywhere — the canonical CGA vsync busy-wait. First
  spins WHILE the vsync bit (3DA bit0) is set, then UNTIL it is set again, so
  every write lands on a fresh leading edge of retrace and never tears. }
procedure WaitRetrace;
var s : byte;
begin
  repeat s := Port[CgaStatus] until (s and 1) = 0;
  repeat s := Port[CgaStatus] until (s and 1) <> 0;
end;

{ PlotCell(x,y,colour) @ 0x3B2A — XOR the low byte of colour into the odd
  (colour) cell of B800 at cell = y*RowBytes + x*2 + 1, both phases vsync. }
procedure PlotCell;
var
  cell : word;
  old  : byte;
begin
  cell := y * RowBytes + x * 2;
  cell := cell + 1;
  WaitRetrace;
  old := Mem[$B800:cell];
  old := old xor Lo(colour);
  WaitRetrace;
  Mem[$B800:cell] := old;
end;

{ DrawShip(x,y) @ 0x3B7A — a HARDCODED 6-cell XOR sprite painted around the
  reference cell = y*RowBytes + x*2 + 1. Each step: vsync, XOR a fixed colour
  byte into B800[cell], vsync, store; then advance cell by a fixed delta.
  Colour/advance program (verbatim 0x3BA5..0x3C5C). }
procedure DrawShip;
var
  cell : word;
  old  : byte;

  procedure Stamp(colour : byte);
  begin
    WaitRetrace;
    old := Mem[$B800:cell];
    old := old xor colour;
    WaitRetrace;
    Mem[$B800:cell] := old;
  end;

begin
  cell := y * RowBytes + x * 2;
  cell := cell + 1;
  Stamp($BB);  cell := cell + $9E;   { next row }
  Stamp($AB);  cell := cell + 2;
  Stamp($FF);  cell := cell + 2;
  Stamp($BA);  cell := cell + $9C;   { next row }
  Stamp($BF);  cell := cell + 4;
  Stamp($FB);                        { last cell, no advance }
end;

{ PlotIcon(x,y; seg,ofs) @ 0x3DAE — the XOR sprite blitter. cell starts at
  y*RowBytes + x*2 + 1. Reads (colourWord, advanceWord) pairs from seg:ofs,
  ofs += 2 after each word; XORs Lo(colour) into B800[cell] (vsync both
  phases); stops when Lo(advance)=0, else cell += Lo(advance). }
procedure PlotIcon;
var
  cell   : word;
  colour : byte;
  adv    : byte;
  old    : byte;
begin
  cell := y * RowBytes + x * 2;
  cell := cell + 1;
  repeat
    colour := Mem[seg:ofs];
    ofs    := ofs + 2;
    adv    := Mem[seg:ofs];
    ofs    := ofs + 2;
    WaitRetrace;
    old := Mem[$B800:cell];
    old := old xor colour;
    WaitRetrace;
    Mem[$B800:cell] := old;
    if adv <> 0 then
      cell := cell + adv;
  until adv = 0;
end;

{ ReadCell(x,y) @ 0x3CFE (table entry 0x397A) — probe the 7-cell magenta
  stripe column at (x,y) for the warp maze. The corridor is "clear" only when
  every stripe byte matches its expected pattern value ($BB $AB $FF $BA $BF
  $FB); any mismatch means the ship ran into solid wall. Each read is gated on
  a CGA retrace poll. Returns 0 = clear, nonzero = wall hit. ret 4. }
function ReadCell(x, y : integer) : integer;
var
  base : word;

  function PeekWall(ofs : word) : byte;
  begin
    WaitRetrace;
    PeekWall := Mem[$B800:ofs];
  end;

begin
  base := y * RowBytes + x * 2 + 1;
  if (PeekWall(base)        = $BB) and
     (PeekWall(base + $9E)  = $AB) and
     (PeekWall(base + $A0)  = $FF) and
     (PeekWall(base + $A2)  = $BA) and
     (PeekWall(base + $13E) = $BF) and
     (PeekWall(base + $142) = $FB) then
    ReadCell := 0                { all stripe bytes matched: corridor clear }
  else
    ReadCell := 1;               { any mismatch: solid wall => collision }
end;

{ ======================= SPRITE / ICON SYSTEM & BUILDERS ================== }
{ Round->builder dispatch, frame-pointer registration, per-round sprite setup.}

{ ClearWave @ 0x6809 — zero the Wave[] array (stub; owned by init). }
procedure ClearWave;
var i : integer;
begin
  {?: the EXE zeroes the 46-record Wave block; reconstructed as a clear loop. }
  for i := 0 to 45 do begin
    Wave[i].X := 0; Wave[i].Y := 0;
    Wave[i].DrawnX := 0; Wave[i].DrawnY := 0;
    Wave[i].DX := 0; Wave[i].DY := 0;
    Wave[i].Frame := 0;
  end;
  EnemyCount := 0;
end;

{ CopyFrames — generic helper modelled on the in-lined copy loops inside the
  builders (0x9F2A..0xA013). Copies nWords 16-bit entries from four CS-relative
  source tables into the four RAM frame buffers, ready for PlotIcon.
  {?: each builder hand-inlines this; the originals were almost certainly one
      shared proc the compiler expanded per call site.} }
procedure CopyFrames(src0, src1, src2, src3 : word; nWords : integer);
var i : integer;
begin
  for i := 0 to nWords - 1 do
    MemW[DSeg : Frame0Buf + i*2] := MemW[CSeg : src0 + i*2];
  for i := 0 to nWords - 1 do
    MemW[DSeg : Frame1Buf + i*2] := MemW[CSeg : src1 + i*2];
  for i := 0 to nWords - 1 do
    MemW[DSeg : Frame2Buf + i*2] := MemW[CSeg : src2 + i*2];
  for i := 0 to nWords - 1 do
    MemW[DSeg : Frame3Buf + i*2] := MemW[CSeg : src3 + i*2];
end;

{ BuildBird @ 0x9F23 — the representative full builder (rounds 1,34). Copies
  four 16-word frame tables, copies a per-column height table, clears the
  wave, then programs the round parameters (Mode:=1, framecount:=4,
  Bottom:=$5F, per-round floor tweak). }
procedure BuildBird;
var i : integer;
begin
  for i := 0 to 15 do
    MemW[DSeg : Frame0Buf + i*2] := MemW[CSeg : BirdSrc0 + i*2];
  for i := 0 to 15 do
    MemW[DSeg : Frame1Buf + i*2] := MemW[CSeg : BirdSrc1 + i*2];
  for i := 0 to 15 do
    MemW[DSeg : Frame2Buf + i*2] := MemW[CSeg : BirdSrc2 + i*2];
  for i := 0 to 15 do
    MemW[DSeg : Frame3Buf + i*2] := MemW[CSeg : BirdSrc3 + i*2];

  ClearWave;                          { 0xA019 }

  Mem[DSeg : $C30] := 4;              { 0xA01C  spawn/grid param }
  Mem[DSeg : $C31] := 2;             { 0xA022 }
  Mem[DSeg : $C32] := 6;             { 0xA028  column/height-table count }
  Mem[DSeg : $C17] := 0;             { 0xA02E }
  Mem[DSeg : $C27] := 4;             { 0xA034  framecount := 4 }
  Mem[DSeg : $C35] := $5F;           { 0xA03A  Bottom := 95 }

  for i := 0 to Mem[DSeg : $C32] do  { 0xA040 copy [0xC32]+1 height bytes }
    Mem[DSeg : HeightBuf + i] := Mem[CSeg : BirdHgtSrc + i];

  MemW[CSeg : $2DAE] := $FFFF;        { 0xA07A  builder-ready flag := true }
  Mode := 1;                          { 0xA084  Mode := 1 }

  if Round = 1 then                   { 0xA087 per-round floor tweak }
    Mem[DSeg : $C33] := 0
  else
    Mem[DSeg : $C33] := $66;

  Mem[DSeg : $C36] := 1;             { 0xA0A3 }
  Mem[DSeg : $C67] := 0;             { 0xA0A9 }
  Mem[DSeg : $C68] := 0;             { 0xA0B2 }
end;

{ BuildRobot @ 0x7C7B (rounds 2,29). Copies three frames of differing length
  (42/48/54 words from cs:0x2F1D/2F71/2FD1), plus a 13-entry word/word/byte
  table (0x303D/3057/3071), clears the wave, sets Mode:=4, Bottom:=$5D. }
procedure BuildRobot;
var i : integer;
begin
  Mem[DSeg : $C1B] := 0;             { 0x7C86 }
  Mem[DSeg : $C17] := 0;             { 0x7C8C }

  for i := 0 to $29 do                { frame 0 : 42 words }
    MemW[DSeg : Frame0Buf + i*2] := MemW[CSeg : $2F1D + i*2];
  for i := 0 to $2F do                { frame 1 : 48 words }
    MemW[DSeg : Frame1Buf + i*2] := MemW[CSeg : $2F71 + i*2];
  for i := 0 to $35 do                { frame 2 : 54 words }
    MemW[DSeg : Frame2Buf + i*2] := MemW[CSeg : $2FD1 + i*2];

  ClearWave;                          { 0x7D43 }

  for i := 0 to $C do begin            { 0x7D46 copy 13 paired entries }
    MemW[DSeg : $BD5 + i*2] := MemW[CSeg : $303D + i*2];
    MemW[DSeg : $BEF + i*2] := MemW[CSeg : $3057 + i*2];
    Mem [DSeg : $C09 + i]   := Mem [CSeg : $3071 + i];
  end;

  Mem[DSeg : $C38] := $C;            { 0x7DB4 }
  Mem[DSeg : $C30] := 4;             { 0x7DBA (paired setup 0x7C26) }
  Mem[DSeg : $C31] := 5;
  Mem[DSeg : $C27] := 4;             { framecount := 4 }
  Mode             := 4;
  Mem[DSeg : $C33] := $99;
  Mem[DSeg : $C67] := 1;
  Mem[DSeg : $C68] := 0;
  Mem[DSeg : $C69] := 0;
  Mem[DSeg : $C6A] := 0;
  Mem[DSeg : $C73] := 0;
  Mem[DSeg : $C71] := 0;
  Mem[DSeg : $C35] := $5D;           { Bottom := 93 }
end;

{ SetupWarp @ 0x6365 — builds NO enemy sprite. Calls the warp-field
  initialiser, sets Mode:=6, clears the warp progress counter. Used by all
  warp rounds (4,8,12,16,20,24,28,32,35,36,37,39,40,41). }
procedure SetupWarp;
begin
  InitWarpField;                      { 0x6372 }
  Mode := 6;                          { 0x6375 }
  RisePend := false;                  { 0x637B  [0xC6E] := 0 }
end;

{ The remaining 13 builders. Each copies its frame tables through CopyFrames
  and programs its movement Mode (per the per-family table). The exact CS
  source offsets/word counts are placeholders pending per-builder disassembly;
  only Mode and call sites are byte-confirmed via the dispatch table. {?:} }
procedure BuildGem;     begin CopyFrames(0,0,0,0,0); Mode := 4; end;  { 0x7944  rounds 3,25,31 }
procedure BuildSpinner; begin CopyFrames(0,0,0,0,0); Mode := 1; end;  { 0x9DA6  rounds 5,38 }
procedure BuildEye;     begin CopyFrames(0,0,0,0,0); Mode := 5; end;  { 0x69D2  rounds 6,14 }
procedure BuildPyramid; begin CopyFrames(0,0,0,0,0); Mode := 3; end;  { 0x881C  rounds 7,15,26 }
procedure BuildAntBot;  begin CopyFrames(0,0,0,0,0); Mode := 4; end;  { 0x73DA  rounds 9,21,33 }
procedure BuildWalker;  begin CopyFrames(0,0,0,0,0); Mode := 1; end;  { 0x6847  round 10 }
procedure BuildLander;  begin CopyFrames(0,0,0,0,0); Mode := 3; end;  { 0x874E  rounds 11,19,27 }
procedure BuildBug;     begin CopyFrames(0,0,0,0,0); Mode := 2; end;  { 0x92B2  round 13 }
procedure BuildMask;    begin CopyFrames(0,0,0,0,0); Mode := 4; end;  { 0x7776  round 17 }
procedure BuildBat;     begin CopyFrames(0,0,0,0,0); Mode := 5; end;  { 0x6B02  round 18 }
procedure BuildWorm;    begin CopyFrames(0,0,0,0,0); Mode := 4; end;  { 0x7B27  rounds 22,30 }
procedure BuildBall;    begin CopyFrames(0,0,0,0,0); Mode := 4; end;  { 0x75A8  round 23 }
procedure BuildBoss;    begin CopyFrames(0,0,0,0,0); Mode := 2; end;  { 0x91C2  round 42 }

{ DispatchRound @ 0xC381 — the round->builder selector, reconstructed from the
  long 'cmp ax,N; je; call builder' chain (0xC388..0xC6D2) as a case. Rounds
  not listed fall through to the warp setup (all Mode-6 rounds). ret 2. }
procedure DispatchRound(R : byte);
begin
  case R of
     1, 34       : BuildBird;       { -> 0x9F23 }
     2, 29       : BuildRobot;      { -> 0x7C7B }
     3, 25, 31   : BuildGem;        { -> 0x7944 }
     5, 38       : BuildSpinner;    { -> 0x9DA6 }
     6, 14       : BuildEye;        { -> 0x69D2 }
     7, 15, 26   : BuildPyramid;    { -> 0x881C }
     9, 21, 33   : BuildAntBot;     { -> 0x73DA }
    10           : BuildWalker;     { -> 0x6847 }
    11, 19, 27   : BuildLander;     { -> 0x874E }
    13           : BuildBug;        { -> 0x92B2 }
    17           : BuildMask;       { -> 0x7776 }
    18           : BuildBat;        { -> 0x6B02 }
    22, 30       : BuildWorm;       { -> 0x7B27 }
    23           : BuildBall;       { -> 0x75A8 }
    42           : BuildBoss;       { -> 0x91C2 }
     4,  8, 12, 16, 20, 24, 28, 32,
    35, 36, 37, 39, 40, 41 : SetupWarp;   { -> 0x6365 }
  end;
end;

{ BuildRound — convenience wrapper used by the main flow. }
procedure BuildRound(R : byte);
begin
  DispatchRound(R);
end;

{ ============================== SPAWN / FORMATION ========================= }

{ SpriteId table the spawner indexes by Frame: byte ptr [Frame + 0xBCE]. }
var
  SpriteId : array[0..255] of byte absolute $BCE;   {?: shares HeightBuf base }
  XBias    : array[0..255] of integer;              { cs:[i*2 + 0x2DAC] bias  }

{ SpawnEnemy(Ay,Ax) @ 0xA0BC — append one enemy to Wave[] at cell (Ax,Ay).
  Pascal pushes Ay then Ax; ret 4. Record stride 10, base DS:0x260. }
procedure SpawnEnemy(Ay, Ax : byte);
var
  E     : ^TEnemy;
  SprId : byte;
begin
  if EnemyCount <= 99 then begin
    E := @Wave[EnemyCount];
    E^.X      := Ax;
    E^.Y      := Ay;
    E^.DrawnX := E^.X;
    E^.DrawnY := E^.Y;

    if Rnd(2) = 1 then E^.DX := 1 else E^.DX := -1;
    if Rnd(2) = 1 then E^.DY := 1 else E^.DY := -1;

    if SpawnScaleHi <> 0 then                  { [0xC67] }
      E^.DY := E^.DY * Rnd(4);

    if SpawnZeroVel <> 0 then begin            { [0xC68] }
      E^.DX := 0;
      E^.DY := 0;
      if Rnd(2) = 0 then
        E^.DY := E^.DY * Rnd(5) - 2            {?: DY is 0 here, yields -2 }
      else
        E^.DX := E^.DX * Rnd(5) - 2;           {?: DX is 0 here, yields -2 }
    end;

    E^.Frame := Rnd(FrameMax) + 1;             { Rnd([0xC32])+1 }

    SprId := SpriteId[E^.Frame];               { byte [Frame + 0xBCE] }

    PlotIcon(E^.X + XBias[SprId], E^.Y,
             FrameSeg[SprId], FrameOfs[SprId]);

    EnemyCount := EnemyCount + 1;
  end;
end;

{ SpawnFormation @ 0xA9DE — the constant 3-row, 20-enemy block.
  Top row 7 @ Y=15, mid row 6 @ Y=25, bottom row 7 @ Y=35; X = base + j*8. }
procedure SpawnFormation;
var j : byte;
begin
  for j := 0 to 6 do SpawnEnemy(15, j * 8 + $10);   { top: 7 enemies }
  for j := 0 to 5 do SpawnEnemy(25, j * 8 + $14);   { middle: 6 enemies }
  for j := 0 to 6 do SpawnEnemy(35, j * 8 + $10);   { bottom: 7 enemies }
end;

{ Per-mode spawn entry points (stubs for the modes other than 1; only
  SpawnFormation is fully reconstructed). {?: 0x9CE3/0x8A6C/0x860D/0x731D} }
procedure SpawnMode2; begin SpawnFormation; end;   { 0x9CE3 {?:} }
procedure SpawnMode3; begin SpawnFormation; end;   { 0x8A6C {?:} }
procedure SpawnMode4; begin SpawnFormation; end;   { 0x860D {?:} }
procedure SpawnMode5; begin SpawnFormation; end;   { 0x731D {?:} }

{ SpawnDispatch @ 0xAC05 — Mode -> per-mode spawn routine. Modes 0/6.. spawn
  nothing (warp rounds build no table-driven enemies). }
procedure SpawnDispatch;
begin
  case Mode of
    1: SpawnFormation;   { 0xA9DE }
    2: SpawnMode2;       { 0x9CE3 }
    3: SpawnMode3;       { 0x8A6C }
    4: SpawnMode4;       { 0x860D }
    5: SpawnMode5;       { 0x731D }
  end;
end;

{ ===================== ENEMY MOVEMENT & PLAY LOOPS ======================== }
{ Forward decls for the homing body referenced by MoveHome. }
procedure MoveHomeStep(idx : byte); forward;

{ SpawnFaller @ 0x894C — spawn one mode-3 faller at (x,y) (stub-aliased). }
procedure SpawnFaller(x, y : byte);
begin
  SpawnEnemy(y, x);   {?: mode-3 entry reuses the row spawner }
end;

{ FireBullet @ 0x6753 — emit an enemy bullet (stub; owned by weapons). }
procedure FireBullet(x, y, dx, dy, colour : integer);
begin
  {?: enqueues a bullet sprite at (x,y) with velocity (dx,dy); see 0x6753. }
end;

{ MoveBounceSetup @ 0xA28E — pre-step setup for MoveBounce (stub). }
procedure MoveBounceSetup(idx : byte);
begin
  {?: pre-advance bookkeeping shared by the swoop animation. }
end;

{ MoveFall @ 0x8B5F — mode-3 faller (Y+=DY, random respawn at top). }
procedure MoveFall(idx : byte);
var e : ^TEnemy;
begin
  e := @Wave[idx];
  e^.DrawnX := e^.X;
  e^.DrawnY := e^.Y;
  if e^.Y >= Bottom then begin                 { reached floor -> respawn }
    e^.Y := TopRow;
    e^.X := Rnd(RightWall - 2) + 2;
    e^.DY := Rnd(4) + 1;
    PlotIcon(e^.DrawnX, e^.DrawnY,
             FrameSeg[e^.Frame], FrameOfs[e^.Frame]);
    e^.Frame := 1;
    PlotIcon(e^.X, e^.Y,
             FrameSeg[e^.Frame], FrameOfs[e^.Frame]);
    e^.DrawnX := e^.X;
    e^.DrawnY := e^.Y;
    if HitFlash > 0 then HitFlash := HitFlash - 1;
  end
  else begin                                   { still falling }
    if FallSwoopGt and (e^.Y > $3A) and (e^.Frame = 1) then begin
      PlotIcon(e^.X, e^.Y, FrameSeg[1], FrameOfs[1]);
      PlotIcon(e^.X, e^.Y, FrameSeg[2], FrameOfs[2]);
      e^.Frame := 2;
    end;
    e^.Y := e^.Y + e^.DY;
    if FallDriftGt then begin
      e^.X := e^.X + e^.DX;
      if e^.X >= RightWall then e^.X := LeftWall
      else if e^.X <= LeftWall then e^.X := RightWall;
    end;
    Sound(($7D0 - (e^.Y shl 4)) or MuteMask);  { falling whine }
  end;
end;

{ MoveBounce @ 0xA445 — mode-1 swooping bird (frame-table driven). Advances
  the frame, reads a per-frame X column delta and a CS row table, redraws,
  and fires a bullet straight down when horizontally aligned with the player.
  {?: RowTab[Frame] is the CS:0x2DAC screen-row table; BulletColor=[0xC33].} }
procedure MoveBounce(idx : byte);
var
  e        : ^TEnemy;
  frm, prv : byte;
begin
  MoveBounceSetup(idx);
  e := @Wave[idx];
  e^.Frame := e^.Frame + 1;
  if e^.Frame = BounceFrames + 1 then
    e^.Frame := 1;
  frm := BounceDX[e^.Frame];
  prv := BounceDX[e^.Frame - 1];
  PlotIcon(e^.DrawnX, Mem[CSeg : $2DAC + prv],   { erase old (RowTab[prv]) }
           FrameSeg[prv], FrameOfs[prv]);
  PlotIcon(e^.DrawnY, Mem[CSeg : $2DAC + frm],   { draw new (RowTab[frm]) }
           FrameSeg[frm], FrameOfs[frm]);
  if e^.X + 2 = PlayerX then
    FireBullet(e^.X + 3, e^.Y + 3, 0, 2, Mem[DSeg : $C33])
  else if e^.X < PlayerX then
    FireBullet(e^.X + 3, e^.Y + 3, 1, 2, Mem[DSeg : $C33])
  else
    FireBullet(e^.X + 3, e^.Y + 3, -1, 2, Mem[DSeg : $C33]);
end;

{ MoveHome dispatch @ 0x970D — pre-step (the homing body) then redraw. }
procedure MoveHome(idx : byte);
var e : ^TEnemy;
begin
  MoveHomeStep(idx);                  { 0x9720 -> 0x93A2 }
  e := @Wave[idx];
  PlotIcon(e^.DrawnX, e^.DrawnY,
           FrameSeg[e^.Frame], FrameOfs[e^.Frame]);
  {?: 0x9779.. continues the same erase/redraw idiom finishing the dive. }
end;

{ MoveHomeStep @ 0x93A2 — the real homing AI body: step X toward the player,
  bounce inside the box, descend, respawn at top with a random heading. }
procedure MoveHomeStep;
var e : ^TEnemy;
begin
  e := @Wave[idx];
  e^.DrawnX := e^.X;
  e^.DrawnY := e^.Y;
  { 0x93E1: if within striking column and not yet committed (DX=0), dive. }
  if not ((e^.X - PlayerX >= 2) and (e^.X - PlayerX <= -3) and (e^.DX <> 0))
  then begin                                   {?: compound TP bool-as-int }
    e^.DX := 0;
    e^.DY := 2;
    PlotIcon(e^.X, e^.Y, FrameSeg[e^.Frame], FrameOfs[e^.Frame]);
    e^.Frame := 3;
    PlotIcon(e^.X, e^.Y, FrameSeg[3], FrameOfs[3]);
  end;
  { 0x94D0 horizontal bounce inside [LeftWall,RightWall] }
  if (e^.X >= RightWall) or (e^.X <= LeftWall) then begin
    e^.DX := -e^.DX;
    PlotIcon(e^.X, e^.Y, FrameSeg[e^.Frame], FrameOfs[e^.Frame]);
    if e^.DX < 0 then e^.Frame := 1 else e^.Frame := 2;
    PlotIcon(e^.X, e^.Y, FrameSeg[e^.Frame], FrameOfs[e^.Frame]);
  end;
  { 0x95B3 vertical bounce / respawn at top }
  if e^.Y >= $3C then e^.DY := -e^.DY;          {?: reflect off mid-line }
  if e^.Y <= TopRow then begin
    e^.DY := 1;
    if e^.DX = 0 then begin
      if Rnd(2) = 1 then e^.DX := 1 else e^.DX := -1;
    end;
    PlotIcon(e^.X, e^.Y, FrameSeg[e^.Frame], FrameOfs[e^.Frame]);
    if e^.DX < 0 then e^.Frame := 1 else e^.Frame := 2;
    PlotIcon(e^.X, e^.Y, FrameSeg[e^.Frame], FrameOfs[e^.Frame]);
  end;
  e^.X := e^.X + e^.DX;
  e^.Y := e^.Y + e^.DY;
end;

{ MoveWrap @ 0x7E11 — mode-4 table-driven marcher with toroidal wrap. }
procedure MoveWrap(idx : byte);
var e : ^TEnemy;
begin
  e := @Wave[idx];
  Sound(($C8 + (e^.Frame shl 5)) or MuteMask);  { step-tone }
  e^.DrawnX := e^.X;
  e^.DrawnY := e^.Y;
  { vertical step: only on odd indices when the gate is set }
  if WrapFireGate and ((idx and 1) <> 0) then
    e^.Y := e^.Y - WrapYVel[e^.Frame]
  else
    e^.Y := e^.Y + WrapYVel[e^.Frame];
  e^.X := e^.X + WrapXVel[e^.Frame];
  if WrapResetGt and (e^.Frame = $B) then begin
    e^.X := Rnd($3C) + $A;
    e^.Y := Rnd($50) + $A;
  end;
  if WrapJitterX then
    e^.X := e^.X + Rnd(3) - 1;
  { toroidal wrap }
  if e^.Y < TopRow    then e^.Y := Bottom;
  if e^.Y > Bottom    then e^.Y := TopRow;
  if e^.X < LeftWall  then e^.X := RightWall;
  if e^.X > RightWall then e^.X := LeftWall;
  PlotIcon(e^.DrawnX, e^.DrawnY,
           FrameSeg[WrapIcon[e^.Frame]], FrameOfs[WrapIcon[e^.Frame]]);
  e^.Frame := e^.Frame + 1;
  Sound(($C8 + (e^.Frame shl 5)) or MuteMask);
  if e^.Frame > WrapFrames then e^.Frame := 0;
  PlotIcon(e^.X, e^.Y,
           FrameSeg[WrapIcon[e^.Frame]], FrameOfs[WrapIcon[e^.Frame]]);
end;

{ MoveChaos @ 0x6C32 — mode-5 bouncing enemy with random direction flips. }
procedure MoveChaos(idx : byte);
var e : ^TEnemy;
begin
  e := @Wave[idx];
  e^.DrawnX := e^.X;
  e^.DrawnY := e^.Y;
  e^.X := e^.X + e^.DX;
  e^.Y := e^.Y + e^.DY;
  if e^.X < LeftWall  then begin e^.X := LeftWall;  e^.DX := -e^.DX; end;
  if e^.X > RightWall then begin e^.X := RightWall; e^.DX := -e^.DX; end;
  if e^.Y < TopRow then e^.Y := TopRow;
  if e^.Y > $5A    then e^.Y := $3C;
  if Rnd(5) = 0 then e^.DX := -e^.DX;
  if Rnd(5) = 0 then e^.DY := -e^.DY;
  PlotIcon(e^.DrawnX, e^.DrawnY,
           FrameSeg[e^.Frame], FrameOfs[e^.Frame]);
  e^.Frame := e^.Frame + 1;
  if ChaosFire then begin                       { firing variant: 4-frame }
    if e^.Frame = 5 then e^.Frame := 1;
    Sound(($370 - (e^.Y shl 3)) or MuteMask);
  end
  else begin                                    { non-firing: 2-frame flap }
    if (e^.Frame = 3) or (e^.Frame = 5) then
      e^.Frame := e^.Frame - 2;
    Sound(($370 + (e^.Frame shl 4)) or MuteMask);
  end;
  PlotIcon(e^.X, e^.Y,
           FrameSeg[e^.Frame], FrameOfs[e^.Frame]);
end;

{ UpdateEnemyBlock @ 0x8DE2 — mode-3 per-enemy update used by PlayBlock.
  Runs the faller, erases/redraws, and randomly spawns a new faller. }
procedure UpdateEnemyBlock(idx : byte);
var e : ^TEnemy;
begin
  MoveFall(idx);
  e := @Wave[idx];
  PlotIcon(e^.DrawnX, e^.DrawnY,
           FrameSeg[e^.Frame], FrameOfs[e^.Frame]);
  PlotIcon(e^.X, e^.Y,
           FrameSeg[e^.Frame], FrameOfs[e^.Frame]);
  if (Rnd($14) = 0) and (EnemyCount < 7) then
    SpawnFaller(Rnd(RightWall - 2) + 2, TopRow);
end;

{ PlayBlock @ 0xC099 — play loop for mode 3 (falling block waves). }
procedure PlayBlock;
begin
  while Playing do begin
    InputCtr := InputCtr + 1;
    if InputCtr = InputPeriod then
      MovePlayer;
    CadenceCtr := CadenceCtr + 1;
    if CadenceCtr = Cadence then begin
      CadenceCtr := 0;
      UpdateEnemyBlock(RRIndex);
      RRIndex := RRIndex + 1;
      if RRIndex = EnemyCount then begin
        ActiveFlag := 0;
        RRIndex := 0;
      end;
    end;
    ReadInput;
    if GameOverReq then ShowGameOver
    else if RoundDoneReq then EndRound
    else WaitTicks(1);
    AnimateBackground;
    if PlayerDead then begin
      WaveStep := (WaveStep + 1) mod $23;
      if WaveStep = 0 then AdvanceWave;
    end;
    if HitFlash <> 0 then PlayerHitFx;
  end;
end;

{ PlayDive @ 0xC1CF — play loop for modes 1,2,4,5 (diving waves). Dispatches
  the per-enemy mover on Mode [0xC19]. Polls input twice per iteration. }
procedure PlayDive;
begin
  while Playing do begin
    CadenceCtr := CadenceCtr + 1;
    if CadenceCtr = Cadence then begin
      CadenceCtr := 0;
      case Mode of
        1 : MoveBounce(RRIndex);
        2 : MoveHome(RRIndex);
        4 : MoveWrap(RRIndex);
        5 : MoveChaos(RRIndex);
      end;
      RRIndex := RRIndex + 1;
      if RRIndex = EnemyCount then begin
        ActiveFlag := 0;
        RRIndex := 0;
      end;
    end;
    ReadInput;
    InputCtr := InputCtr + 1;
    if InputCtr = InputPeriod then
      MovePlayer;
    if GameOverReq then ShowGameOver
    else if RoundDoneReq then EndRound
    else WaitTicks(1);
    AnimateBackground;
    StarCtr := StarCtr + 1;
    if StarCtr = StarPeriod then begin
      StarCtr := 0;
      TwinkleStars;
    end;
    if PlayerDead then begin
      WaveStep := (WaveStep + 1) mod $23;
      if WaveStep = 0 then AdvanceWave;
    end;
    ReadInput;                          { re-poll before looping }
  end;
end;

{ ===================================== PLAYER ============================= }

{ KeyClick @ 0x105A6 — short key-press click (stub; TP runtime/CRT helper). }
procedure KeyClick;
begin
  NoSound;   {?: the EXE emits a brief click; reconstructed minimally. }
end;

{ ToggleSomething @ 0xAE7B — F2 phasor toggle (stub). }
procedure ToggleSomething; begin {?: 0xAE7B} end;
{ SelfDestruct @ 0xBAE1 — Bksp self-destruct (stub; PlayerDie does the work). }
procedure SelfDestructKey; begin {?: 0xBAE1} end;

{ ReadInput / PlayerInput @ 0xBD77 — decode the custom INT9 key latch [0x0F9E].
  Edge-detected against PrevScan; make-codes set a flag, the matching
  break-code (scan|0x80) clears it. }
procedure ReadInput;
begin
  CurScan := byte(MemW[$0000 : $0F9E]);   { copy INT9 latch -> [0xC2E] }
  if CurScan <> PrevScan then begin
    case CurScan of
      $4B: begin GoLeft  := true;  GoRight := false; end;  { Left make }
      $CB: GoLeft  := false;                               { Left break }
      $4D: begin GoRight := true;  GoLeft  := false; end;  { Right make }
      $CD: GoRight := false;                               { Right break }
      $48: begin GoUp    := true;  GoDown  := false; end;  { Up make }
      $C8: GoUp    := false;                               { Up break }
      $50: begin GoDown  := true;  GoUp    := false; end;  { Down make }
      $D0: GoDown  := false;                               { Down break }

      $01: begin Playing := true; AbortFlag := true; end;  { Esc: quit }

      $3B: FireHeld := true;          { F1 make: fire held }
      $BB: FireHeld := false;         { F1 break }

      $39: begin                      { Spacebar: pause toggle }
             KeyClick;
             repeat until byte(MemW[$0000 : $0F9E]) <> $39;
             repeat until byte(MemW[$0000 : $0F9E]) <= $7F;
             Sound(CurFreq or MuteMask);
           end;

      $3C: begin KeyClick; ToggleSomething; end;   { F2 phasor }
      $0E: begin KeyClick; SelfDestructKey; end;   { Bksp self-destruct }

      $43: MuteMask := $7FFF;         { F9: mute all voices }
      $44: MuteMask := 0;             { F10: un-mute }
    end;
    PrevScan := CurScan;
  end;
end;

{ MovePlayerHoriz @ 0xB640 — nudge PlayerX one cell toward the field edge,
  XOR-erasing at the old cell and redrawing each step. }
procedure MovePlayerHoriz;
begin
  while PlayerX > PlayfieldLeft do begin
    PlayerX := PlayerX - 1;
    DrawShip(PlayerX,     PlayerY);
    DrawShip(PlayerX + 1, PlayerY);
  end;
  while PlayerX < PlayfieldLeft do begin     {?: right clamp via [0xC3C] }
    PlayerX := PlayerX + 1;
    DrawShip(PlayerX,     PlayerY);
    DrawShip(PlayerX - 1, PlayerY);
  end;
  ShipNewX := PlayerX;
  ShipNewY := PlayerY;
end;

{ MovePlayer / MovePlayerVert @ 0xBC57 — evaluate held direction flags, compute
  the proposed cell (ShipNewX/Y), clamp to the playfield, redraw, then emit a
  position-pitch click. }
procedure MovePlayer;
begin
  HitTopFlag := 0;

  if GoLeft  and (PlayerX >  2)     then ShipNewX := PlayerX - 1;
  if GoRight and (PlayerX < $4D)    then ShipNewX := PlayerX + 1;
  if GoUp    and (PlayerY >  2)     then ShipNewY := PlayerY - 1;
  if GoDown  and (PlayerY < Bottom) then ShipNewY := PlayerY + 1;

  DrawShip(PlayerX,  PlayerY);          { erase old }
  DrawShip(ShipNewX, ShipNewY);         { draw new }
  PlayerX := ShipNewX;
  PlayerY := ShipNewY;
  {?: positional thruster click (call 0x1397A test then NoSound-click) omitted }
end;

{ PlayerDie / SelfDestruct @ 0xBB0C — descending death sound and a big outward
  blast. Death pitch = 1000 - PlayerY*8, OR'd with MuteMask; the ship sprite is
  fanned out in all enabled directions; finally a floor-mapped tone. }
procedure PlayerDie;
begin
  Sound((1000 - (PlayerY shl 3)) or MuteMask);   { death sound }
  HitTopFlag := 0;

  if GoLeft  and (PlayerX >  2) then begin       { left fan }
    PlayerX := PlayerX - 1;
    DrawShip(PlayerX, PlayerY);
    DrawShip(PlayerX + 1, PlayerY);
  end;
  if GoRight and (PlayerX < $4D) then begin      { right fan }
    PlayerX := PlayerX + 1;
    DrawShip(PlayerX, PlayerY);
    DrawShip(PlayerX - 1, PlayerY);
  end;
  if PlayerY > 9 then begin                      { upward fan }
    PlayerY := PlayerY - 1;
    DrawShip(PlayerX, PlayerY);
    DrawShip(PlayerX, PlayerY + 1);
  end
  else begin                                     { reached top: end-of-life }
    RisePend := true;                            { [0xC6E] := 1 }
    Playing  := true;
  end;

  Sound((((PlayerY mod 8) + $14) * $14) or MuteMask);   { final blast tone }
end;

{ ExplodePlayer @ 0xB42E — the big death blast used by the warp loop (stub
  delegating to the player-death effect). }
procedure ExplodePlayer;
begin
  PlayerDie;   {?: 0xB42E drives the same explosion fan as PlayerDie. }
end;

{ FirePlayerShot @ 0xAC79 — primary fire (stub; weapons subsystem). }
procedure FirePlayerShot; begin {?: 0xAC79} end;
{ FireAlt @ 0xAE2C — secondary fire (stub; weapons subsystem). }
procedure FireAlt; begin {?: 0xAE2C} end;

{ ================================= WARP ROUNDS ============================ }
{ The static rising-ship maze (Mode 6). The magenta striped wall is drawn once
  and does NOT scroll; the ship sits low and "rises" one wall-row at a time as
  the round is cleared. Hitting an un-cleared wall cell kills the player. }

{ InitWarpField @ 0x6116 — lay the initial static maze (stub). }
procedure InitWarpField;
begin
  {?: paints the starting magenta-stripe field; details owned by 0x6116. }
end;

{ DrawWallRow @ 0x62D6 — lay one new row of the magenta striped wall.
  XOR-blits the stripe across columns 8..$50 at scan-row WallRow (one cell per
  retrace), then snaps the ship back to its launch position (X=$26,Y=$5C) and
  re-stamps it — which is what makes the ship appear to "rise". }
procedure DrawWallRow;
var col : integer;
begin
  WaitRetrace;
  PlotCell(PlayerX, PlayerY, 0);            { erase ship before redrawing wall }

  for col := 8 to $50 do begin
    WaitRetrace;
    PlotCell(col, WallRow, WallStripeCell); {?: 0x3968 striped-cell writer }
  end;

  PlayerY := $5C;
  PlayerX := $26;
  ShotX   := PlayerX;
  ShotY   := PlayerY;
  WaitRetrace;
  PlotCell(PlayerX, PlayerY, 0);            { re-draw the ship }
end;

{ PlayWarp @ 0xBF19 — main loop for a travel/warp round. Static maze; the ship
  rises as it survives. Each iteration: read input; cadence-gated horizontal
  move; fire/idle; pacing delay; collision-probe the ship cell; if a rise is
  pending, walk RiseCount 12->2 drawing wall rows with a rising PIT tone and
  clear the round when it reaches RiseTop. }
procedure PlayWarp;
var
  toneStep : byte;
  n        : integer;
begin
  while Playing do begin
    ReadInput;                                       { 0xBD77 }

    { cadence-gated horizontal movement }
    CadCount := CadCount + 1;
    if CadCount = CadLimit * 2 then
      MovePlayerHoriz;                               { 0xBB0C }

    { weapon / idle }
    if Firing then
      FirePlayerShot                                 { 0xAC79 }
    else if AltFire then
      FireAlt                                        { 0xAE2C }
    else
      Delay(1);

    Delay(10);                                        { frame pacing }

    { collision with the static wall }
    if ReadCell(PlayerX, PlayerY) > 0 then           { 0x397A, ret 4 }
      Playing := true;        { nonzero => wall hit; flag the death path }

    if Playing then begin
      if RisePend then begin
        { the rise: walk RiseCount 12->2, one wall row per step, each with
          an upward-sweeping tone. }
        n := (RiseStart - RiseCount) + 6;
        if n - RiseStart >= 0 then begin
          toneStep := 0;
          repeat
            WallTone := WallTone + 1;
            DrawWallRow;
            Sound(((toneStep + 5) * 100) or MuteMask);
            toneStep := toneStep + 1;
          until (n - RiseStart) - toneStep + 1 = 0;  {?: dec cx; je trip count}
        end;

        NoSound;

        RiseCount := RiseCount - 1;
        if RiseCount < RiseTop then
          RiseCount := RiseTop;                       { clamp at floor }
        DrawWallRow;
        { round considered cleared once RiseCount has reached RiseTop }
      end
      else begin
        { no rise pending: the genuine death branch }
        ExplodePlayer;                                { 0xB42E }
        Delay(1000);
        Playing := false;
      end;
    end;
  end;
  {?: 0xC070..0xC076 are stray bytes (branch landing pad / data), not live
      flow; the loop continues at 0xC08F -> 0xBF21. }
end;

{ =========================== ROUND FLOW / MAIN ============================ }

{ InitVideo — set CGA lores 160x100x16 mode and clear B800 (stub). }
procedure InitVideo;
begin
  {?: BIOS set-mode + framebuffer clear; details in the TP3 startup path. }
end;

{ InitSound — silence the speaker at startup. }
procedure InitSound;
begin
  NoSound;
  MuteMask := 0;
end;

{ ShowTitle — title screen with the scrolling music (stub driving TickMusic). }
procedure ShowTitle;
begin
  {?: paints the title art and spins TickMusic per frame until a key starts. }
end;

{ ShowGameOver @ 0xAC79-area / EndRound @ 0xAE2C / WaitTicks @ 0x101F7 /
  AnimateBackground @ 0x63E4 / PlayerHitFx @ 0x8AB5 / TwinkleStars @ 0x661E:
  flow helpers the play loops poll. Minimal faithful stubs. }
procedure ShowGameOver;    begin Playing := false; end;     {?: 0xAC79 }
procedure EndRound;        begin Playing := false; end;     {?: 0xAE2C }
procedure WaitTicks;       begin Delay(n);          end;     {?: 0x101F7 }
procedure AnimateBackground; begin {?: 0x63E4} end;
procedure PlayerHitFx;     begin {?: 0x8AB5} end;
procedure TwinkleStars;    begin {?: 0x661E} end;

{ StartRound — common per-round setup: build sprites for the round, position
  the ship and reset the round flags, then spawn the formation. }
procedure StartRound(R : byte);
begin
  Round := R;
  Mode  := RoundMode[R];        { the per-round movement mode [0xC19] }
  BuildRound(R);                { dispatch to the matching builder }
  PlayerX := $26;
  PlayerY := $5C;
  CadenceCtr := 0;
  RRIndex    := 0;
  Playing    := true;
  PlayerDead := false;
  GameOverReq := false;
  RoundDoneReq := false;
  SpawnDispatch;                { Mode-driven enemy spawn (formation etc.) }
end;

{ -------------------------------- main body ------------------------------ }
begin
  InitVideo;                    { CGA lores 160x100x16 }
  InitSound;                    { speaker off, un-muted }
  ShowTitle;                    { title screen + scrolling music }

  for Round := 1 to 42 do begin
    StartRound(Round);
    if Mode = 6 then
      PlayWarp                  { travel/warp round }
    else if Mode = 3 then
      PlayBlock                 { falling-block wave }
    else
      PlayDive;                 { diving waves (modes 1,2,4,5) }
  end;

  NoSound;
end.
