{
 "chunk": "game_mainProgram0800_4A02",
 "unit": "game/mainProgram0800",
 "unitLabels": {
  "game/mainProgram0800": {
   "4C81": "formationRotateRulesDy"
  }
 },
 "notes": {
  "4A02": {
   "routine": [
    "followGroupMovement - Gives the unit that is joining a group the movement the rest of the group already has: averageGroupMoveVector ($4987) returns the average (destination - position) vector of the members and the unit's new destination becomes its own cell plus that vector. The destination is applied through the shared destination setter sub_34D5, which clamps it to the map and maintains the at-destination bit. A group with no members leaves the unit untouched.",
    "In:  Y = index of the joining unit; cmdParam2 ($75) = group key (unitFlagsTable bits 2-5), read by averageGroupMoveVector; unit position tables $F640/$F6A4",
    "Out: cmdParam1/cmdParam2 = the new destination cell, written into unitDestColTable/unitDestRowTable by sub_34D5; scratch18/scratch19 = average column/row step, scratch1D = saved unit index",
    "Called from: joinUnitToGroup ($4ADE) only, i.e. the executor of command $88 JOIN GROUP"
   ],
   "unit": "game/mainProgram0800",
   "line": "remember the joining unit: averageGroupMoveVector needs Y for its own 100-unit scan"
  },
  "4A0F": {
   "block": [
    "The next two instructions look like a bug: the test was meant to be 'is the average vector",
    "(dx,dy) zero?', but TYA here loads the unit index rather than the average row step that was",
    "just stored in scratch19.  The result is that the stand-still path below is only reachable for",
    "unit index 0, and then only when the average column step is zero as well.  It is harmless in",
    "the usual case (adding zero to the unit's own cell gives the same destination), except for",
    "unit 0 joining a group whose average dx is 0 and dy is not: that unit is told to stand still."
   ],
   "unit": "game/mainProgram0800",
   "line": "A = the unit index (this was probably meant to be LDA scratch19, the average row step)"
  },
  "4A14": {
   "block": [
    "Stand-still: the destination becomes the unit's own cell."
   ],
   "unit": "game/mainProgram0800",
   "line": "column of the cell the unit stands on"
  },
  "4A21": {
   "block": [
    "Follow the group: destination = own cell + the group's average movement vector."
   ],
   "unit": "game/mainProgram0800",
   "line": "average row step of the group"
  },
  "4A35": {
   "routine": [
    "sendGroupMoveToCursor - Fire-button action of the battlefield target cursor while a whole group is selected (uiLevel $9209 >= 2). It queues command $81 with the selected unit as the group leader and the cell under the cursor (view origin + 3 columns, + 2 rows - the centre of the 7x5 tactical view) as the ordered position, then refreshes the leader's local at-destination flag. Unlike the single-unit order $80 it leaves the unit's member bit alone, so the leader stays in its group.",
    "In:  selectedUnit ($90F8) = the group leader, viewOriginCol/viewOriginRow (zp_A5/zp_A6) = top-left cell of the tactical view",
    "Out: cmdParam0/1/2 = unit, column, row; 4 bytes ($81,unit,col,row) queued in the outgoing command ring by queueCmd4 ($560A); unitDisplayFlags bit 7 of the leader refreshed; C=0 always (the caller tests C)",
    "Called from: runTargetCursorLoop ($2218), the fire-click branch taken when uiLevel is 2 or more"
   ],
   "unit": "game/mainProgram0800",
   "line": "the cursor sits 3 cells right of the view origin (centre column of the 7-cell wide view)"
  },
  "4A52": {
   "routine": [
    "moveGroupKeepingFormation - Executor of command $81, run in lock step on both machines. It works out the offset between the leader's current destination and the newly ordered cell and subtracts that offset from the destination of every live member of the leader's group, so the leader ends up exactly on the ordered cell and the relative shape of the formation is preserved. Membership is taken from the authoritative unitFlagsTable, not from the UI copy. Members whose shifted destination falls off the map are clamped by sub_34D5, which does distort the formation at the map edges.",
    "In:  cmdParam0 ($73) = leader unit index, cmdParam1/cmdParam2 = ordered column/row (already mirrored for a remote command by mirrorCmdCoordsIfRemote)",
    "Out: unitDestColTable/unitDestRowTable of every member rewritten through sub_34D5; cmdParam0 is overwritten with the group key, fillEndPtr (zp_4A) is the 99..0 loop counter; self-modifies the SBC operands at $4A8E (column offset) and $4A98 (row offset)",
    "Called from: cmdMoveGroupRelative ($5020), the handler of command $81"
   ],
   "unit": "game/mainProgram0800",
   "line": "Y = the leader unit named by the command"
  },
  "4A77": {
   "block": [
    "Walk all 100 unit slots from 99 down to 0 and shift every live member of the leader's group",
    "by the same offset.  zp_4A holds the index; Y is reloaded from it because sub_34D5 is called",
    "inside the loop."
   ],
   "unit": "game/mainProgram0800",
   "line": "index of the unit being considered"
  },
  "4AA3": {
   "routine": [
    "addSelectedUnitToGroup - The 'ADDED' half of the group membership editor. isPointWithinGroupRange rejects a unit that is more than 8 cells from the group's current bounding box - then 'TOO FAR!' is shown and C=1 returned. Otherwise the local flag copy is updated at once so the viewport highlights the unit immediately, and command $88 is queued so that both machines perform the join in lock step.",
    "In:  selectedGroupKey ($9206) = group key (group number in bits 2-4, side in bit 5), selectedUnit ($90F8)",
    "Out: C=0 accepted: unitDisplayFlags[unit] = (old & $83) | key | $40 and 3 bytes ($88, unit, key) queued by queueCmd3 ($560F); C=1 rejected, with the TOO FAR! message and sound 3",
    "Called from: the JOIN GROUP menu ($61C6) and the group membership editor ($6483)"
   ],
   "unit": "game/mainProgram0800",
   "line": "group being edited (bits 2-4 group number, bit 5 side)"
  },
  "4AD4": {
   "routine": [
    "joinUnitToGroup - Executor of command $88. It first gives the unit the group's current movement through followGroupMovement, then writes the group key and the member bit into the authoritative unitFlagsTable and mirrors the new bits into the display copy with syncLocalGroupFlags. Only the two facing bits survive the mask, so joining a group also clears the unit's pending first-strike bit 7, and the side bit comes from the command argument rather than from the unit.",
    "In:  cmdParam1 ($74) = unit index, cmdParam2 ($75) = group key, both fetched by readCmdArgs2 in cmdJoinGroup",
    "Out: unitFlagsTable[unit] = (old & $03) | key | $40 and unitDisplayFlags[unit] resynced; the unit's destination is set to the group's average movement; self-modifies the LDX operand at $4AE2 and the ORA operand at $4AE9",
    "Called from: cmdJoinGroup ($5198), the handler of command $88"
   ],
   "unit": "game/mainProgram0800",
   "line": "unit index argument of command $88"
  },
  "4AF2": {
   "routine": [
    "removeSelectedUnitFromGroup - The 'REMOVED' half of the membership editor. It clears the member bit 6 in the selected unit's local flag copy straight away so the viewport highlight updates on the next frame, and queues the 2-byte command $89, whose executor cmdLeaveGroup ($519B) clears the same bit in the authoritative unitFlagsTable on both machines and re-syncs the copy.",
    "In:  selectedUnit ($90F8)",
    "Out: unitDisplayFlags[unit] &= $BF, cmdParam0 = unit, 2 bytes ($89, unit) queued by queueCmd2 ($561F)",
    "Called from: the group membership editor ($64B1)"
   ],
   "unit": "game/mainProgram0800",
   "line": "the unit the cursor is on"
  },
  "4B04": {
   "routine": [
    "isPointWithinGroupRange - Range test that stops a group from spreading over more than 8 cells. It builds the group's bounding box with computeGroupBounds and then demands that the tested point be less than 9 cells from each of the four edges of that box (which also bounds the box once the point has been added). Y below $80 tests unit Y's map position against the members' positions - used when a unit joins; Y = $80 tests the cell picked with the target cursor against the members' destinations - used when a member is re-positioned. An empty group always passes.",
    "In:  A = group key, Y = unit index or $80; pickedCellCol/pickedCellRow ($91D8/$91D9) when Y = $80",
    "Out: C=0 within range, C=1 too far; scratch18-scratch1D clobbered (bounding box and mode); self-modifies the LDY operand at $4B0F",
    "Called from: addSelectedUnitToGroup ($4AA9) and orderGroupNewPosition ($4BEE)"
   ],
   "unit": "game/mainProgram0800",
   "line": "patch the LDY # at $4B0F with the mode: a unit index, or $80 = use the picked cell"
  },
  "4B22": {
   "block": [
    "Four edge tests, each |point - edge| < 9: right edge, left edge, bottom edge, top edge of the",
    "bounding box.  Any distance of 9 or more fails the whole test."
   ],
   "unit": "game/mainProgram0800",
   "line": "column of the tested point"
  },
  "4B5E": {
   "routine": [
    "computeGroupBounds - Scans all 100 unit slots and accumulates the member count and the bounding box of one group. Membership is tested on the local flag copy unitDisplayFlagsTable ((flags & $7C) == key | $40) and dead slots are skipped. Bit 7 of the mode byte (the Y register on entry) selects whether the box is built from the members' current map cells or from their ordered destinations. An empty group leaves min = $FF and max = 0.",
    "In:  A = group key (group number and side), Y = mode (bit 7 set = use destinations); unitTypeTable, unitDisplayFlagsTable, unitColTable/unitRowTable or unitDestColTable/unitDestRowTable",
    "Out: A = scratch1A = member count, X = scratch18 = min column, Y = scratch19 = min row, scratch1B = max column, scratch1C = max row, scratch1D = the mode byte; self-modifies the CMP operand at $4B80",
    "Called from: isPointWithinGroupRange ($4B07) and buildGroupFormationSprite ($30C5), which draws the group's formation ghost from the destination box"
   ],
   "unit": "game/mainProgram0800",
   "line": "add the in-a-group bit to the group key"
  },
  "4B75": {
   "block": [
    "One pass over the 100 unit slots: count the members of the group and grow the bounding box",
    "around either their positions or their destinations."
   ],
   "unit": "game/mainProgram0800",
   "line": "type byte of this slot"
  },
  "4BBF": {
   "routine": [
    "showTooFarMessage - Error feedback shared by the two group range checks: selects and clears the 14-column info panel (screen columns 25-38, rows 12-21), plays sound 3 (the rejection beep) and prints 'TOO FAR!' from the $C000 text overlay on screen row 16, in whatever colour the caller left in textColour.",
    "In:  textColour (zp_3D) and textFillGlyph (zp_3E) as set by the caller",
    "Out: info panel window selected and blanked, sound 3 queued, the message printed (tail call into printString)",
    "Called from: addSelectedUnitToGroup ($4AAE) and orderGroupNewPosition ($4BF3)"
   ],
   "unit": "game/mainProgram0800",
   "line": "select the info panel window (columns 25-38, rows 12-21) and blank it"
  },
  "4BD3": {
   "routine": [
    "orderGroupNewPosition - NEW POSITION action of the custom-formation editor. It highlights just the selected unit, redraws the viewport and runs the target cursor loop, which in this mode (cursorTargetMode negative) only reports the picked cell in pickedCellCol/pickedCellRow. If the pick was not aborted and lies within 8 cells of the group's destination box it queues command $98 unit,column,row, refreshes the local at-destination flag, counts the order in the pending-order pair $929D/$929E and prints 'RE-POSITIONED'; otherwise it shows TOO FAR! and re-centres the viewport on the unit. It always ends by restoring the editor's cursor state, because the cursor loop zeroes cursorTargetMode when the pick is aborted.",
    "In:  selectedUnit ($90F8), selectedGroupKey ($9206), pickedCellCol/pickedCellRow ($91D8/$91D9) filled by runTargetCursorLoop, checkUiContinue ($C451)",
    "Out: command $98 queued (queueCmd4) or a message shown; highlightedUnit/highlightedGroupKey, lastOrderedGroupKey/pendingGroupOrderCount, cursorTargetMode = $80, textColour/textPadColour = 4; self-modifies the string pointer operands at $4C46/$4C48",
    "Called from: the group membership/formation editor ($64AB)"
   ],
   "unit": "game/mainProgram0800",
   "line": "$FF = no group highlighted ..."
  },
  "4BFC": {
   "block": [
    "The pick was accepted: send command $98 (move keeping group membership) for this unit."
   ],
   "unit": "game/mainProgram0800",
   "line": "column picked with the cursor"
  },
  "4C13": {
   "block": [
    "Count how many re-position orders are outstanding for this group.  The editor at $65E7 waits",
    "for pendingGroupOrderCount to come back to zero (each executed $98 decrements it in",
    "setGroupMemberDestination) before it lets the whole group be given a new goal."
   ],
   "unit": "game/mainProgram0800",
   "line": "the unit just ordered"
  },
  "4C2D": {
   "block": [
    "Confirm the order with a centred 'RE-POSITIONED' in the info panel."
   ],
   "unit": "game/mainProgram0800",
   "line": "low byte of $CB99 = 'RE-POSITIONED'"
  },
  "4C4F": {
   "block": [
    "Common tail: the target cursor loop zeroes cursorTargetMode when the pick was aborted, so the",
    "editor's 'free cell pick' mode and its two cursor sprites are set up again here."
   ],
   "unit": "game/mainProgram0800",
   "line": "$80 = free cell pick mode"
  },
  "4C59": {
   "routine": [
    "setGroupMemberDestination - Executor of command $98. If the unit's group key matches the group remembered in lastOrderedGroupKey it decrements pendingGroupOrderCount - this is the acknowledgement the formation editor waits for - and then sets the unit's destination to the ordered cell through sub_34D5. Unlike command $80 it never touches the group bits, so the re-positioned unit stays a member of its group.",
    "In:  cmdParam0 ($73) = unit index, cmdParam1/cmdParam2 = column/row (already mirrored for a remote command), lastOrderedGroupKey ($929D)",
    "Out: unitDestColTable/unitDestRowTable of the unit written via sub_34D5; pendingGroupOrderCount ($929E) decremented when the group matches",
    "Called from: cmdMoveKeepGroup ($5476), the handler of command $98"
   ],
   "unit": "game/mainProgram0800",
   "line": "the unit named by the command"
  },
  "4C6B": {
   "routine": [
    "syncLocalGroupFlags - Copies the authoritative group bits of unit X (unitFlagsTable & $7C: group number, side and member bit) into the local flag copy unitDisplayFlagsTable, keeping that byte's own bits 0, 1 and 7. Called after every command that changes group membership and when a unit is created or reset, so the UI copy can never drift away from the simulation.",
    "In:  X = unit index",
    "Out: unitDisplayFlags[X] = (old & $83) | (unitFlagsTable[X] & $7C); self-modifies the ORA operand at $4C79",
    "Called from: the move executor ($34D0), unit creation ($3DFA), joinUnitToGroup ($4AEF) and cmdLeaveGroup ($51A8)"
   ],
   "unit": "game/mainProgram0800",
   "line": "authoritative flag byte of the unit"
  },
  "4C7E": {
   "block": [
    "formationRotateRules - two 3-byte rule sets that turn the FORMATION menu item into a coordinate",
    "transform for rotateGroupFormation.  Index = the rotation mode carried by command $A3 (0 SWEEP",
    "LEFT, 1 SWEEP RIGHT, 2 REVERSE).  $4C7E[mode] describes what happens to the member's column",
    "offset dx, $4C81[mode] what happens to its row offset dy: bit 7 = negate the component, bit 0 =",
    "store it as the new row offset ($75) instead of the new column offset ($74).  The three modes",
    "are therefore (dy,-dx) = 90 degrees anticlockwise, (-dy,dx) = 90 degrees clockwise and",
    "(-dx,-dy) = 180 degrees."
   ],
   "unit": "game/mainProgram0800",
   "line": "rule for the column offset: mode 0 negate+store as row, mode 1 store as row, mode 2 negate+store as column"
  },
  "4C84": {
   "routine": [
    "rotateGroupFormation - Executor of command $A3 - the SWEEP LEFT / SWEEP RIGHT / REVERSE items of the FORMATION menu. It rotates the whole formation about the leader's destination cell: for every live member of the leader's group the offset (dx,dy) from that cell is transformed with the two rule bytes of formationRotateRules and the new destination leader+offset is applied with sub_34D5. During the setup phase the resulting row is clamped into the issuing player's own half of the map, so a rotation can never push units across the middle line.",
    "In:  cmdParam1 ($74) = leader unit index, cmdParam2 ($75) = rotation mode 0-2 (both from readCmdArgs2), cmdFromOpponent ($76) bit 7, gamePhase (zp_B1)",
    "Out: unitDestColTable/unitDestRowTable of every member rewritten through sub_34D5; cmdParam1/cmdParam2 are reused as the working destination; ten self-modified immediates: $4CC4 loop index, $4CD0 group key, $4CDA/$4CFE leader column, $4CDC/$4CE5 column rule, $4CEF/$4D05 leader row, $4CF1/$4CFA row rule",
    "Called from: cmdSetFormation ($5599), the handler of command $A3"
   ],
   "unit": "game/mainProgram0800",
   "line": "the leader unit named by the command"
  },
  "4CC3": {
   "block": [
    "Walk all 100 unit slots.  For every live member of the leader's group: take its offset from",
    "the leader's destination, run the offset through the rotation rules, add it back to the",
    "leader's destination and order the member to the resulting cell."
   ],
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4CC0 and decremented at $4D24: the unit being considered"
  },
  "4D09": {
   "block": [
    "During the setup phase each side may only place units in its own half of the map, so a",
    "rotation that would carry a unit across the middle line (row 20) is clamped.  The local",
    "player always owns rows 20-39 of his own view, the opponent rows 0-19."
   ],
   "unit": "game/mainProgram0800",
   "line": "0 = the battle is running, non-zero = setup/menu phase"
  },
  "4D2A": {
   "block": [
    "commandHandlerTable - 39 little-endian handler addresses, one per command byte $80-$A6.  The",
    "dispatcher in executeTurnCommands indexes it with (command & $3F) * 2 and copies the entry into",
    "the operand of the JSR at $4EEE.  The $8D entry is $FFFF (rendered as irqVectorHi) because that",
    "command is consumed by the packet mergers and never reaches the dispatcher.  Handlers marked",
    "'immediate' below are likewise executed while the packets are merged."
   ],
   "unit": "game/mainProgram0800",
   "line": "$80 MOVE unit (4 bytes: unit, column, row)"
  },
  "4D78": {
   "block": [
    "commandLengthTable - total length in bytes (the command byte included) of each command",
    "$80-$A6, indexed by command & $3F.  getCommandLength is the only reader; the packet mergers,",
    "the dispatcher, the outgoing packet builder and the comm module all use it to step over a",
    "command they do not want to look inside."
   ],
   "unit": "game/mainProgram0800",
   "line": "$80-$87: move 4, group move 4, cloak on/off and group cloak on/off 2 each, clear target 2, set target 3"
  },
  "4D9F": {
   "routine": [
    "getCommandLength - Looks the total length (command byte plus its parameters) of command A up in commandLengthTable and leaves it in currentCommandLength as well as in A.",
    "In:  A = command byte $80-$A6 (bit 7 is ignored)",
    "Out: A = currentCommandLength ($92EE) = 1..4, X = the table index (command & $3F)",
    "Called from: the dispatcher ($4ECF), advancePacketCursor ($4F5F), the outgoing packet builder ($565C) and the solo trainer ($EA4C, $EA92)"
   ],
   "unit": "game/mainProgram0800",
   "line": "strip bit 7: commands $80-$A6 become indices $00-$26"
  },
  "4DA9": {
   "routine": [
    "serviceCommandExchange - Per-frame entry point of the lock-step command exchange, called from the main loop of every screen and from the overlays. It services the status-message system and then, unless the 10-frame throttle timer linkPollDelay is still running, falls into processCommandExchange. During film playback the throttle is ignored so records are consumed as fast as their time stamps allow.",
    "In:  filmPlaybackMode ($0B9B) bit 7, linkPollDelay ($9163) counted down by the raster IRQ",
    "Out: as processCommandExchange, plus the status line refreshed",
    "Called from: the map/battlefield/console loops ($1AB0, $21B2, $600D), waitForExchangeIdle ($5915), the map generator overlay and the text engine ($C76E)"
   ],
   "unit": "game/mainProgram0800",
   "line": "let the status line show/expire the current message"
  },
  "4DB7": {
   "routine": [
    "processCommandExchange - The heart of the lock-step protocol. During film playback it reads records from the film buffer whose time stamp (exchange slot commandSliceIndex plus the game clock) has come, copies their command bytes into the merged command buffer and executes them; a record header of $FE stops the film where it is and $FF runs the end-of-film handler. In live play it waits for the comm module to report a completed exchange ($E01D bit 6), then merges the packet just sent and the packet just received into one buffer - always with player 0's commands first, so both machines execute the same sequence - and falls into executeTurnCommands.",
    "In:  filmPlaybackMode ($0B9B), linkSessionState ($0B9C), gameEndReason ($0BA8), playerSide ($0B9F), the comm module's $E01D/$E03B, film pointer zp_BC/zp_BD, commandSliceIndex ($923C), gameClock ($91CB)",
    "Out: mergedCmdBuffer filled and executed; mergedCmdLength/mergedCmdSplitIndex/linkPollDelay/linkTimeoutCounter/mergeFillIndex set; self-modifies $4DE9 (a copy of the time-stamp byte), $4DF4 (the previous record's clock) and $4E20 (offset of the first command byte in the record)",
    "Called from: serviceCommandExchange (fall-through and branches) and updateGameFrame ($41EE), which calls it once every ten units of the round"
   ],
   "unit": "game/mainProgram0800",
   "line": "bit 7 = a game film is being replayed"
  },
  "4DBC": {
   "block": [
    "Film playback.  Records are consumed from the film buffer until one is found whose time stamp",
    "is still in the future; several records may share one time stamp, so this is a loop."
   ],
   "unit": "game/mainProgram0800",
   "line": "0 while the game runs"
  },
  "4DC9": {
   "block": [
    "Nothing to execute this frame.  If a link session exists (a film can be watched while the",
    "modem link is still up) give the menu-level packet handler a turn."
   ],
   "unit": "game/mainProgram0800",
   "line": "0 = no link, $80 = link up at menu level, 2 = game session"
  },
  "4DD2": {
   "block": [
    "Read the next film record.  Layout: [ splitIndex<<4 | length ] [ 1 or 2 time-stamp bytes ]",
    "[ length command bytes ].  The time-stamp byte holds the exchange slot within the round in its",
    "low nibble and the negated clock delta in its high nibble; a zero high nibble means the next",
    "byte is the absolute game clock.  Header $FE = the film ends here, $FF = the recorded game was",
    "aborted (a real header can never reach $EF because a merged buffer holds at most 14 bytes)."
   ],
   "unit": "game/mainProgram0800",
   "line": "offset 0 of the record"
  },
  "4E23": {
   "block": [
    "Copy the record's command bytes out of the film buffer into the merged command buffer."
   ],
   "unit": "game/mainProgram0800",
   "line": "next byte of the record"
  },
  "4E39": {
   "block": [
    "Live play.  Everything below runs once per completed packet exchange with the opponent",
    "(or with the solo trainer, which fakes the same exchange)."
   ],
   "unit": "game/mainProgram0800",
   "line": "link state: 0 none, 2 game session agreed, $80 link up at menu level, $FF lost"
  },
  "4E4B": {
   "block": [
    "Wait for the comm module to report 'this frame's packets have been swapped' (bit 6 of the",
    "exchange flag byte $E01D).  Normally the routine simply returns and is called again next",
    "frame; once the watchdog has been waiting for about half a minute checkLinkTimeout returns",
    "C=1 and the game busy-waits here with PHONE TROUBLE on the status line until the packet",
    "arrives or the watchdog gives up and returns to the main menu."
   ],
   "unit": "game/mainProgram0800",
   "line": "$E01D bit 6 = the exchange is complete"
  },
  "4E56": {
   "block": [
    "Both packets are available: reset the timers and merge the two command streams."
   ],
   "unit": "game/mainProgram0800",
   "line": "sound currently playing on the voice"
  },
  "4E91": {
   "routine": [
    "executeTurnCommands - Runs every command of one exchange. It walks the merged buffer from index 0 to mergedCmdLength, keeping cmdFromOpponent up to date: the owner flag starts at 'remote' or 'own' depending on which side this machine plays and is toggled when the read index reaches mergedCmdSplitIndex, the boundary between the two players' blocks. Own commands decrement the outstanding-order count. A byte with bit 7 set is dispatched through commandHandlerTable with the self-modified JSR at $4EEE; a byte with bit 7 clear is a cancelled command (a handler may overwrite its own command byte, e.g. $8E becomes $0E at $534F) and is skipped by its table length. In live play the whole exchange is then appended to the game film, the packet is acknowledged and the next outgoing packet is built and sent.",
    "In:  mergedCmdBuffer ($908F), mergedCmdLength ($92EB), mergedCmdSplitIndex ($92EC), playerSide ($0B9F), filmPlaybackMode ($0B9B)",
    "Out: the game state changed by the handlers; mergedCmdReadIndex, mergedCmdOwnerFlag, cmdFromOpponent, exchangeActivityFlag bit 0 and pendingOwnCmdCount updated; in live play the film grows and the next packet is transmitted",
    "Called from: processCommandExchange - by fall-through for a live exchange and by JSR at $4E33 for a film record"
   ],
   "unit": "game/mainProgram0800",
   "line": "start at the first byte ..."
  },
  "4EA0": {
   "block": [
    "Command loop.  Every command in the buffer is executed on both machines in exactly this",
    "order, which is what keeps the two simulations identical."
   ],
   "unit": "game/mainProgram0800",
   "line": "index of the next command byte"
  },
  "4ECF": {
   "block": [
    "A byte with bit 7 clear is a command that was cancelled after it was queued (a handler can",
    "clear bit 7 of its own byte in the buffer, e.g. cmdDroneDetonate writes $0E over its $8E).",
    "Its length is still looked up so the reader can step over the whole command."
   ],
   "unit": "game/mainProgram0800",
   "line": "look the length up anyway - the low 6 bits still identify the command"
  },
  "4EDB": {
   "block": [
    "Dispatch: copy the handler address out of commandHandlerTable into the operand of the JSR",
    "below and call it.  The handler itself reads its parameters with readCmdArg1/2/3, which also",
    "advance mergedCmdReadIndex to the next command."
   ],
   "unit": "game/mainProgram0800",
   "line": "the command byte"
  },
  "4EF4": {
   "block": [
    "End of the exchange.  While a film is being replayed there is nothing to record and no packet",
    "to answer, so the routine simply returns."
   ],
   "unit": "game/mainProgram0800",
   "line": "bit 7 = film playback"
  },
  "4F05": {
   "routine": [
    "mergeIncomingPacket - Appends the packet just received from the opponent ($E020, $E01E bytes) to the merged command buffer, marking the block as remote. Commands are copied whole, their length taken from commandLengthTable. Three command bytes are special: $97 (drone launched) also clears the drone flags, $92 (drone arrived) sets them, while $8D (the opponent's drone position report) and $8C (session control: abort, pause, resume) are executed here and now and never enter the buffer; the abort case of $8C even unwinds this loop by dropping two return addresses.",
    "In:  the comm module's receive buffer $E020-$E026 and its length $E01E, mergeFillIndex (zp_69) = bytes already in the merged buffer",
    "Out: mergedCmdBuffer filled from mergeFillIndex on, mergeFillIndex advanced, packetReadIndex (zp_6A) used as the cursor, cmdFromOpponent = $80, droneArrivalFlags possibly changed",
    "Called from: processCommandExchange ($4E7B for player 0, $4E81 for player 1)"
   ],
   "unit": "game/mainProgram0800",
   "line": "number of bytes the opponent sent this exchange"
  },
  "4F12": {
   "block": [
    "Walk the received packet command by command."
   ],
   "unit": "game/mainProgram0800",
   "line": "offset of the next command in the packet"
  },
  "4F47": {
   "block": [
    "Copy the whole command (byte plus parameters) into the merged buffer."
   ],
   "unit": "game/mainProgram0800",
   "line": "next byte of the command"
  },
  "4F5E": {
   "routine": [
    "advancePacketCursor - Helper of both packet mergers: keeps the command byte in A, looks its length up and leaves packetReadIndex pointing at the next command in the packet.",
    "In:  A = command byte, Y = its offset inside the packet",
    "Out: A unchanged, packetReadIndex (zp_6A) = Y + length, currentCommandLength = the length",
    "Called from: mergeIncomingPacket ($4F17) and mergeOutgoingPacket ($4F7D)"
   ],
   "unit": "game/mainProgram0800",
   "line": "save the command byte - getCommandLength destroys A"
  },
  "4F6B": {
   "routine": [
    "mergeOutgoingPacket - Appends the copy of the packet this machine just sent ($E027, $E01F bytes) to the merged buffer, marking the block as own, so the local commands are executed at exactly the same point in the sequence as on the opponent's machine. $8D is not copied - it only re-centres the viewport on the drone and closes one outstanding order - and $8C is executed at once, like in the incoming merger.",
    "In:  the comm module's transmit copy $E027-$E02D and its length $E01F, mergeFillIndex (zp_69)",
    "Out: mergedCmdBuffer filled, mergeFillIndex advanced, packetReadIndex used as the cursor, cmdFromOpponent = 0, viewOriginCol/viewOriginRow or pendingOwnCmdCount possibly changed",
    "Called from: processCommandExchange ($4E73 for player 0, $4E89 for player 1)"
   ],
   "unit": "game/mainProgram0800",
   "line": "number of bytes in the packet we just sent"
  },
  "4F78": {
   "block": [
    "Walk our own packet command by command."
   ],
   "unit": "game/mainProgram0800",
   "line": "offset of the next command"
  },
  "4FAD": {
   "block": [
    "Copy the whole command into the merged buffer."
   ],
   "unit": "game/mainProgram0800",
   "line": "next byte of the command"
  },
  "4FC4": {
   "routine": [
    "readCmdArgs3 - Argument reader for the 4-byte commands $80, $81, $8A, $8B, $98, $9C and $A6: takes the first parameter into cmdParam0 and falls through into readCmdArgs2, which picks up the other two. The read index ends up on the next command, matching commandLengthTable.",
    "In:  mergedCmdReadIndex ($90B7) = offset of the command byte in mergedCmdBuffer",
    "Out: cmdParam0/cmdParam1/cmdParam2 ($73/$74/$75) = the three parameters, mergedCmdReadIndex advanced by 4, A/Y clobbered",
    "Called from: cmdMoveUnit ($5011), cmdMoveGroupRelative ($501A), cmdPlaceUnit ($51AB), cmdSetMapSeed ($51F3), cmdMoveKeepGroup ($5470) and cmdSetRecycler ($54E1)"
   ],
   "unit": "game/mainProgram0800",
   "line": "offset of the command byte"
  },
  "4FD0": {
   "routine": [
    "readCmdArgs2 - Argument reader for the 3-byte commands $87, $88, $8E, $90, $9A, $A3 and $A5, and the tail of readCmdArgs3. It reads the two bytes that follow the command byte into cmdParam1/cmdParam2 and leaves the read index on the next command (advance 3, or 4 when entered from readCmdArgs3).",
    "In:  mergedCmdReadIndex ($90B7), mergedCmdBuffer ($908F)",
    "Out: cmdParam1/cmdParam2 ($74/$75), mergedCmdReadIndex advanced, A/Y clobbered",
    "Called from: cmdSetTarget ($516B), cmdJoinGroup ($5185), cmdDroneDetonate ($5336), cmdMissileStrike ($53C0), cmdAddEnergy ($5479), cmdSetFormation ($5596), cmdSetupOption ($55A5) and readCmdArgs3"
   ],
   "unit": "game/mainProgram0800",
   "line": "current read offset (already past parameter 1 when entered from $4FC4)"
  },
  "4FE4": {
   "routine": [
    "mirrorCmdCoordsIfRemote - Point mirror for the map coordinates carried by a command. Both machines run one shared simulation while each player sees his own side at the bottom of the map, so every coordinate that arrives over the link has to be rotated 180 degrees (39-x, 39-y) before it is used. Own commands are left alone.",
    "In:  cmdFromOpponent (zp_76) bit 7, cmdParam1 = column, cmdParam2 = row",
    "Out: cmdParam1/cmdParam2 replaced by 39-column/39-row for a remote command, untouched for an own one; A clobbered",
    "Called from: cmdMoveUnit, cmdMoveGroupRelative, cmdPlaceUnit, cmdMissileStrike, cmdMoveKeepGroup, cmdSetRecycler and mirrorCmdCoordsIfRemoteDec"
   ],
   "unit": "game/mainProgram0800",
   "line": "did this command come from the opponent?"
  },
  "4FF7": {
   "routine": [
    "readCmdArg1 - Argument reader for all the 2-byte commands - the per-unit and per-group orders $82-$86, $89, $8C, $91, $93-$96, $9B, $9D, $9F-$A2 and $A4. It reads the single byte after the command byte into cmdParam0 and steps the read index to the next command.",
    "In:  mergedCmdReadIndex ($90B7), mergedCmdBuffer ($908F)",
    "Out: cmdParam0 ($73) = the unit index or group key, mergedCmdReadIndex advanced by 2, A/Y clobbered",
    "Called from: the cloak/blitz/dig/group handlers ($5031, $504B, $5062, $507E, $50FA, $515D, $519B, $542E, $549E, $5511, $555D, $5576, $559C)"
   ],
   "unit": "game/mainProgram0800",
   "line": "offset of the command byte"
  },
  "5005": {
   "routine": [
    "mirrorCmdCoordsIfRemoteDec - Coordinate mirror for the drone, which covers a 2x2 block of cells and is addressed by its top-left corner. For a remote command it mirrors the corner and then decrements both coordinates, giving 38-x / 38-y, which is the top-left corner of the mirrored block. No-op for an own command.",
    "In:  cmdFromOpponent (zp_76), cmdParam1/cmdParam2 = the drone's top-left cell",
    "Out: cmdParam1/cmdParam2 = 38-column / 38-row for a remote command; A clobbered",
    "Called from: cmdDroneMove ($52DC) and cmdDroneDetonate ($5359)"
   ],
   "unit": "game/mainProgram0800",
   "line": "did this command come from the opponent?"
  },
  "5011": {
   "routine": [
    "cmdMoveUnit - Handler of command $80 MOVE (4 bytes: unit, column, row), the ordinary move order given by clicking a cell with a single unit selected. It reads and mirrors the arguments and tail-jumps into applyMoveOrderOld ($34C6), which drops the unit out of any group, re-syncs its display flags and sets the destination.",
    "In:  the merged command buffer and its read index; cmdFromOpponent for the mirroring",
    "Out: unit destination/waypoint tables updated via $34C6",
    "Called from: reached only through commandHandlerTable, entry $80"
   ],
   "unit": "game/mainProgram0800",
   "line": "unit, column and row into cmdParam0/1/2"
  },
  "501A": {
   "routine": [
    "cmdMoveGroupRelative - Handler of command $81 (4 bytes: unit, column, row) - the move order given while a whole group is selected. It reads and mirrors the arguments and tail-jumps into moveGroupKeepingFormation, which shifts every member of the named unit's group by the same offset so the formation is kept.",
    "In:  the merged command buffer and its read index; cmdFromOpponent",
    "Out: destinations of all members of the leader's group",
    "Called from: reached only through commandHandlerTable, entry $81"
   ],
   "unit": "game/mainProgram0800",
   "line": "leader unit, ordered column and row"
  },
  "5023": {
   "routine": [
    "cmdCloakOn - Handler of command $82 CLOAK ON (2 bytes: unit). Cloak and blitz are mutually exclusive, so the unit's blitz bits are cleared first by calling cmdBlitzOff, which consumes the same argument; the read index is therefore saved in a self-modified operand and restored before the argument is read again. SPY units (type 3) are permanently cloaked and are rejected, everybody else gets bits 6-7 of unitPathColTable set (cloak ordered plus cloak active) and falls into the shared tail at $5058.",
    "In:  the merged command buffer; unitTypeTable for the SPY test",
    "Out: unitPathColTable[unit] |= $C0 and unitPathRowTable[unit] &= $3F; pendingRedrawUnit and comcenCloakedFlag through the tail; self-modifies the LDA operand at $502D",
    "Called from: reached only through commandHandlerTable, entry $82"
   ],
   "unit": "game/mainProgram0800",
   "line": "current read index in the merged buffer"
  },
  "504B": {
   "routine": [
    "cmdCloakOff - Handler of command $84 CLOAK OFF (2 bytes: unit); also used as a subroutine by cmdBlitzOn. It clears bits 6-7 of unitPathColTable. The shared tail at $5058 notes the unit in pendingRedrawUnit for an own command only - the opponent's cloaking must not trigger a redraw here - and then falls into updateComcenCloakLock. There is no SPY exemption on this side, unlike in the group handler.",
    "In:  cmdParam0 after readCmdArg1, cmdFromOpponent",
    "Out: unitPathColTable[unit] &= $3F; pendingRedrawUnit ($923B) = unit for an own command; comcenCloakedFlag refreshed",
    "Called from: commandHandlerTable entry $84, and cmdBlitzOn ($5076)"
   ],
   "unit": "game/mainProgram0800",
   "line": "cmdParam0 = the unit"
  },
  "5058": {
   "block": [
    "Shared tail of the single-unit cloak handlers: only our own cloak changes are worth a local",
    "redraw, and the console's inspect lock has to follow the state of our own comcen."
   ],
   "unit": "game/mainProgram0800",
   "line": "did this command come from the opponent?"
  },
  "5062": {
   "routine": [
    "cmdBlitzOff - Handler of command $95 BLITZ OFF (2 bytes: unit); also used as a subroutine by cmdCloakOn. It clears bits 6-7 of unitPathRowTable, the blitz ordered/active pair.",
    "In:  the merged command buffer and its read index",
    "Out: unitPathRowTable[unit] &= $3F; cmdParam0 = unit, Y = unit",
    "Called from: commandHandlerTable entry $95, and cmdCloakOn ($5029)"
   ],
   "unit": "game/mainProgram0800",
   "line": "cmdParam0 = the unit"
  },
  "5070": {
   "routine": [
    "cmdBlitzOn - Handler of command $93 BLITZ ON (2 bytes: unit), the mirror image of cmdCloakOn. It saves and restores the read index around a call to cmdCloakOff (blitz cancels cloak), rejects SPY units, sets bits 6-7 of unitPathRowTable and tail-jumps into applyBlitzStepCost, which pays one energy point for the extra 25 movement points.",
    "In:  the merged command buffer; unitTypeTable for the SPY test",
    "Out: unitPathRowTable[unit] |= $C0, unitPathColTable[unit] &= $3F, energy and movement points through $3EE2; self-modifies the LDA operand at $507A",
    "Called from: reached only through commandHandlerTable, entry $93"
   ],
   "unit": "game/mainProgram0800",
   "line": "current read index"
  },
  "5098": {
   "routine": [
    "cmdGroupCloakOn - Handler of command $83 (2 bytes: group key). It runs cmdGroupBlitzOff for the same group first (the two states are exclusive), saving and restoring the read index around it, then enters the patch block of the shared group routine at $50D4 with X = $80 so the body works on unitPathColTable with OR $C0 and sets bit 7 of the group's status entry.",
    "In:  the merged command buffer; the group key argument carries the group number in bits 2-4 and the side in bit 5",
    "Out: unitPathColTable of every living non-SPY member |= $C0, unitPathRowTable &= $3F, groupStatusTable[group] |= $80, pendingRedrawGroup and comcenCloakedFlag; self-modifies the LDA operand at $50A2",
    "Called from: reached only through commandHandlerTable, entry $83"
   ],
   "unit": "game/mainProgram0800",
   "line": "current read index"
  },
  "50AA": {
   "routine": [
    "cmdGroupBlitzOn - Handler of command $94 (2 bytes: group key). It runs cmdGroupCloakOff for the group first, then enters the shared patch block at $50C1 with X = $C0 and A = $40, so the body works on unitPathRowTable with OR $C0, calls applyBlitzStepCost for every member and sets groupStatusTable[group] = (old & $BF) | $40.",
    "In:  the merged command buffer",
    "Out: unitPathRowTable of every living non-SPY member |= $C0, one energy point and 25 movement points per member, groupStatusTable[group] bit 6 set; self-modifies the LDA operand at $50B4",
    "Called from: reached only through commandHandlerTable, entry $94"
   ],
   "unit": "game/mainProgram0800",
   "line": "current read index"
  },
  "50BE": {
   "routine": [
    "cmdGroupBlitzOff - Handler of command $96 (2 bytes: group key) and, at $50C1, the blitz half of the patch block that configures the shared group routine. With X = A = 0 it selects: no blitz cost, group status OR $00, group status AND $BF and table unitPathRowTable, so every member's blitz bits are cleared.",
    "In:  the merged command buffer",
    "Out: unitPathRowTable of every member &= $3F, groupStatusTable[group] &= $BF; patches the operands at $512D, $514B and $514D and the table operands via applyGroupCloakOrBlitz",
    "Called from: commandHandlerTable entry $96, and cmdGroupCloakOn ($509E)"
   ],
   "unit": "game/mainProgram0800",
   "line": "X = 0: no bits to set in the members' waypoint byte"
  },
  "50D2": {
   "routine": [
    "cmdGroupCloakOff - Handler of command $85 (2 bytes: group key) and, at $50D4, the cloak half of the patch block. With X = 0 it selects: no blitz cost, group status AND $7F, group status OR $00 and table unitPathColTable, so the cloak bits of every member are cleared. Entered with X = $80 by cmdGroupCloakOn, which turns the same block into the cloak-on variant.",
    "In:  the merged command buffer; X = 0 (cloak off) or $80 (cloak on)",
    "Out: unitPathColTable of every member &= $3F (or |= $C0), groupStatusTable[group] bit 7 cleared or set, pendingRedrawGroup for an own command, comcenCloakedFlag refreshed",
    "Called from: commandHandlerTable entry $85, cmdGroupBlitzOn ($50B0) and cmdGroupCloakOn ($50A8)"
   ],
   "unit": "game/mainProgram0800",
   "line": "X = 0: clear the cloak bits"
  },
  "50EB": {
   "routine": [
    "applyGroupCloakOrBlitz - The shared, heavily self-modified body of the four group cloak/blitz handlers; it is only ever reached by branching, never by JSR. It stores the chosen table address into the LDA/STA operands of the unit loop, reads the group key argument, notes the group in pendingRedrawGroup for an own cloak command, and walks all 100 unit slots skipping dead units, non-members and SPYs. For every member it optionally charges the blitz cost and rewrites table[unit] = (old & $3F) | patchedBits. Finally it updates the group's entry in groupStatusTable and falls into updateComcenCloakLock.",
    "In:  the patched operands at $512D (blitz cost gate), $5134/$513B (table address), $5139 (per-unit bits), $514B/$514D (group status mask and bits); cmdParam0 = group key after readCmdArg1; unitFlagsTable and unitTypeTable",
    "Out: cloak or blitz bits of every matching unit, groupStatusTable[group], pendingRedrawGroup ($923A), comcenCloakedFlag ($9245)",
    "Called from: the four group handlers $83/$85/$94/$96 by branch ($50D0 and $50E7)"
   ],
   "unit": "game/mainProgram0800",
   "line": "patch the ORA # at $5139: the bits every member gets ($00 or $C0)"
  },
  "5115": {
   "block": [
    "One pass over all 100 unit slots.  Dead slots, units of other groups and SPYs are skipped;",
    "SPYs are permanently cloaked and can never blitz, so they are exempt from both orders."
   ],
   "unit": "game/mainProgram0800",
   "line": "type byte of this slot"
  },
  "5140": {
   "block": [
    "Finally record the new state in the group's own entry so that the console and the group menu",
    "can show CLOAKED / BLITZ without scanning the units again."
   ],
   "unit": "game/mainProgram0800",
   "line": "the group key with its member bit"
  },
  "5151": {
   "routine": [
    "updateComcenCloakLock - The common tail of every cloak and blitz handler and the only writer of comcenCloakedFlag: it copies bit 7 of the local player's own comcen entry in unitPathColTable, so $9245 bit 7 is set exactly while that comcen is cloaked. The console reads it to lock the inspection panels ($2064, $2096) and both $6F00 overlays read it as well.",
    "In:  comcenUnit ($9236) = 49 or 99, unitPathColTable; no arguments",
    "Out: comcenCloakedFlag ($9245) = unitPathColTable[own comcen] & $80; A and Y clobbered",
    "Called from: the fall-through from applyGroupCloakOrBlitz, the single-unit cloak tail ($505F) and both $6F00 overlays ($72E7)"
   ],
   "unit": "game/mainProgram0800",
   "line": "index of this player's own comcen (49 for side 0, 99 for side 1)"
  },
  "4A04": {
   "unit": "game/mainProgram0800",
   "line": "average (destination - position) over the members of group cmdParam2: A = member count, X = average column step, Y = average row step"
  },
  "4A07": {
   "unit": "game/mainProgram0800",
   "line": "branch if the group has no members at all (count 0): leave this unit's destination alone"
  },
  "4A09": {
   "unit": "game/mainProgram0800",
   "line": "keep the group's average column step (signed, may be negative)"
  },
  "4A0B": {
   "unit": "game/mainProgram0800",
   "line": "keep the group's average row step"
  },
  "4A0D": {
   "unit": "game/mainProgram0800",
   "line": "point Y back at the joining unit"
  },
  "4A10": {
   "unit": "game/mainProgram0800",
   "line": "OR in the average column step and test the pair for zero"
  },
  "4A12": {
   "unit": "game/mainProgram0800",
   "line": "branch unless both bytes are zero - true for every unit except index 0 with a zero average column step"
  },
  "4A17": {
   "unit": "game/mainProgram0800",
   "line": "cmdParam1 = destination column for sub_34D5"
  },
  "4A19": {
   "unit": "game/mainProgram0800",
   "line": "row of the cell the unit stands on"
  },
  "4A1C": {
   "unit": "game/mainProgram0800",
   "line": "cmdParam2 = destination row"
  },
  "4A1E": {
   "unit": "game/mainProgram0800",
   "line": "go and apply the destination"
  },
  "4A23": {
   "unit": "game/mainProgram0800",
   "line": "plain add, no carry in"
  },
  "4A24": {
   "unit": "game/mainProgram0800",
   "line": "new destination row = own row + average row step"
  },
  "4A27": {
   "unit": "game/mainProgram0800",
   "line": "cmdParam2 = destination row"
  },
  "4A29": {
   "unit": "game/mainProgram0800",
   "line": "average column step of the group"
  },
  "4A2B": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "4A2C": {
   "unit": "game/mainProgram0800",
   "line": "new destination column = own column + average column step"
  },
  "4A2F": {
   "unit": "game/mainProgram0800",
   "line": "cmdParam1 = destination column"
  },
  "4A31": {
   "unit": "game/mainProgram0800",
   "line": "shared destination setter: clamps to columns/rows 0-39, writes unitDestColTable/unitDestRowTable of unit Y, refreshes the at-destination bit 7 in unitDisplayFlagsTable and stands the unit up (clears the dug-in bit 6 of unitTypeTable)"
  },
  "4A34": {
   "unit": "game/mainProgram0800",
   "line": "back to joinUnitToGroup"
  },
  "4A37": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "4A38": {
   "unit": "game/mainProgram0800",
   "line": "column of the cell under the cursor"
  },
  "4A3A": {
   "unit": "game/mainProgram0800",
   "line": "command argument 2 = ordered column"
  },
  "4A3C": {
   "unit": "game/mainProgram0800",
   "line": "the cursor sits 2 cells below the view origin (centre row of the 5-cell high view)"
  },
  "4A3E": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "4A3F": {
   "unit": "game/mainProgram0800",
   "line": "row of the cell under the cursor"
  },
  "4A41": {
   "unit": "game/mainProgram0800",
   "line": "command argument 3 = ordered row"
  },
  "4A43": {
   "unit": "game/mainProgram0800",
   "line": "the unit the player selected acts as the group's leader"
  },
  "4A46": {
   "unit": "game/mainProgram0800",
   "line": "command argument 1 = leader unit index"
  },
  "4A48": {
   "unit": "game/mainProgram0800",
   "line": "command $81 = move group keeping formation (4 bytes: unit, column, row)"
  },
  "4A4A": {
   "unit": "game/mainProgram0800",
   "line": "queue $81 with its three arguments; it runs on both machines when the exchange is executed"
  },
  "4A4D": {
   "unit": "game/mainProgram0800",
   "line": "immediate local feedback: set bit 7 of unitDisplayFlags[leader] if the ordered cell is the one it already stands on, clear it otherwise"
  },
  "4A50": {
   "unit": "game/mainProgram0800",
   "line": "C=0 = the order was accepted"
  },
  "4A51": {
   "unit": "game/mainProgram0800",
   "line": "back to the target cursor loop"
  },
  "4A54": {
   "unit": "game/mainProgram0800",
   "line": "leader's current destination column"
  },
  "4A57": {
   "unit": "game/mainProgram0800",
   "line": "drop bit 7, the at-destination/idle marker kept in the same byte"
  },
  "4A59": {
   "unit": "game/mainProgram0800",
   "line": "borrow clear for a true subtract"
  },
  "4A5A": {
   "unit": "game/mainProgram0800",
   "line": "column offset = leader destination column - ordered column"
  },
  "4A5C": {
   "unit": "game/mainProgram0800",
   "line": "patch the SBC operand at $4A8E: every member is shifted by this column offset"
  },
  "4A5F": {
   "unit": "game/mainProgram0800",
   "line": "leader's current destination row"
  },
  "4A62": {
   "unit": "game/mainProgram0800",
   "line": "drop the idle marker"
  },
  "4A64": {
   "unit": "game/mainProgram0800",
   "line": "borrow clear"
  },
  "4A65": {
   "unit": "game/mainProgram0800",
   "line": "row offset = leader destination row - ordered row"
  },
  "4A67": {
   "unit": "game/mainProgram0800",
   "line": "patch the SBC operand at $4A98 with the row offset"
  },
  "4A6A": {
   "unit": "game/mainProgram0800",
   "line": "authoritative flag byte of the leader"
  },
  "4A6D": {
   "unit": "game/mainProgram0800",
   "line": "keep group number (bits 2-4) and side (bit 5)"
  },
  "4A6F": {
   "unit": "game/mainProgram0800",
   "line": "add the in-a-group bit 6 so the value can be compared against flags & $7C"
  },
  "4A71": {
   "unit": "game/mainProgram0800",
   "line": "cmdParam0 now carries the group key instead of the leader index"
  },
  "4A73": {
   "unit": "game/mainProgram0800",
   "line": "start the scan at unit 99, the last slot"
  },
  "4A75": {
   "unit": "game/mainProgram0800",
   "line": "zp_4A is the loop counter over the 100 unit slots"
  },
  "4A79": {
   "unit": "game/mainProgram0800",
   "line": "its type byte; bit 7 marks a dead or empty slot"
  },
  "4A7C": {
   "unit": "game/mainProgram0800",
   "line": "skip dead units"
  },
  "4A7E": {
   "unit": "game/mainProgram0800",
   "line": "authoritative flags of this unit"
  },
  "4A81": {
   "unit": "game/mainProgram0800",
   "line": "group number, side and the in-a-group bit"
  },
  "4A83": {
   "unit": "game/mainProgram0800",
   "line": "does it belong to the leader's group?"
  },
  "4A85": {
   "unit": "game/mainProgram0800",
   "line": "no: try the next slot"
  },
  "4A87": {
   "unit": "game/mainProgram0800",
   "line": "member's current destination column"
  },
  "4A8A": {
   "unit": "game/mainProgram0800",
   "line": "without the idle marker"
  },
  "4A8C": {
   "unit": "game/mainProgram0800",
   "line": "borrow clear"
  },
  "4A8D": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4A5C: subtract the group's column offset"
  },
  "4A8F": {
   "unit": "game/mainProgram0800",
   "line": "cmdParam1 = the member's new destination column"
  },
  "4A91": {
   "unit": "game/mainProgram0800",
   "line": "member's current destination row"
  },
  "4A94": {
   "unit": "game/mainProgram0800",
   "line": "without the idle marker"
  },
  "4A96": {
   "unit": "game/mainProgram0800",
   "line": "borrow clear"
  },
  "4A97": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4A67: subtract the group's row offset"
  },
  "4A99": {
   "unit": "game/mainProgram0800",
   "line": "cmdParam2 = the member's new destination row"
  },
  "4A9B": {
   "unit": "game/mainProgram0800",
   "line": "apply it to unit Y (clamped to 0-39; the leader lands exactly on the ordered cell)"
  },
  "4A9E": {
   "unit": "game/mainProgram0800",
   "line": "next lower unit index"
  },
  "4AA0": {
   "unit": "game/mainProgram0800",
   "line": "loop until slot 0 has been handled"
  },
  "4AA2": {
   "unit": "game/mainProgram0800",
   "line": "back to the command dispatcher"
  },
  "4AA6": {
   "unit": "game/mainProgram0800",
   "line": "Y below $80 means: test this unit's map cell against the members' map cells"
  },
  "4AA9": {
   "unit": "game/mainProgram0800",
   "line": "refuse a unit further than 8 cells from the group's bounding box"
  },
  "4AAC": {
   "unit": "game/mainProgram0800",
   "line": "C=0: it is close enough, add it"
  },
  "4AAE": {
   "unit": "game/mainProgram0800",
   "line": "C=1: beep and print 'TOO FAR!' in the info panel"
  },
  "4AB1": {
   "unit": "game/mainProgram0800",
   "line": "return with C=1, nothing changed"
  },
  "4AB4": {
   "unit": "game/mainProgram0800",
   "line": "the unit to add"
  },
  "4AB7": {
   "unit": "game/mainProgram0800",
   "line": "its local flag copy, the one the viewport and the editor read"
  },
  "4ABA": {
   "unit": "game/mainProgram0800",
   "line": "keep the two facing bits and bit 7 (at destination); drop any old group bits"
  },
  "4ABC": {
   "unit": "game/mainProgram0800",
   "line": "insert the new group number and side"
  },
  "4ABF": {
   "unit": "game/mainProgram0800",
   "line": "and the in-a-group bit"
  },
  "4AC1": {
   "unit": "game/mainProgram0800",
   "line": "instant local feedback; the authoritative $F708 only changes when command $88 executes"
  },
  "4AC4": {
   "unit": "game/mainProgram0800",
   "line": "command argument 1 = unit index"
  },
  "4AC6": {
   "unit": "game/mainProgram0800",
   "line": "the group it joins"
  },
  "4AC9": {
   "unit": "game/mainProgram0800",
   "line": "command argument 2 = group key"
  },
  "4ACB": {
   "unit": "game/mainProgram0800",
   "line": "command $88 = join group (3 bytes: unit, group key)"
  },
  "4ACD": {
   "unit": "game/mainProgram0800",
   "line": "queue it for both machines"
  },
  "4AD0": {
   "unit": "game/mainProgram0800",
   "line": "C=0 = added"
  },
  "4AD1": {
   "unit": "game/mainProgram0800",
   "line": "back to the editor"
  },
  "4AD2": {
   "unit": "game/mainProgram0800",
   "line": "C=1 = rejected as too far away"
  },
  "4AD3": {
   "unit": "game/mainProgram0800",
   "line": "back to the editor"
  },
  "4AD6": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDX # operand at $4AE2 with it - followGroupMovement clobbers X and Y"
  },
  "4AD9": {
   "unit": "game/mainProgram0800",
   "line": "group key argument"
  },
  "4ADB": {
   "unit": "game/mainProgram0800",
   "line": "patch the ORA # operand at $4AE9 with the group key"
  },
  "4ADE": {
   "unit": "game/mainProgram0800",
   "line": "let the new member pick up the group's average movement (Y still holds the unit index)"
  },
  "4AE1": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4AD6: the joining unit"
  },
  "4AE3": {
   "unit": "game/mainProgram0800",
   "line": "its authoritative flag byte"
  },
  "4AE6": {
   "unit": "game/mainProgram0800",
   "line": "keep only the facing bits 0-1; the old group bits and the first-strike bit 7 are dropped"
  },
  "4AE8": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4ADB: group number and side from the command"
  },
  "4AEA": {
   "unit": "game/mainProgram0800",
   "line": "mark the unit as being in a group"
  },
  "4AEC": {
   "unit": "game/mainProgram0800",
   "line": "this is the copy both machines agree on"
  },
  "4AEF": {
   "unit": "game/mainProgram0800",
   "line": "mirror the new group bits into the display copy $FB54 and return from there"
  },
  "4AF5": {
   "unit": "game/mainProgram0800",
   "line": "its local flag copy"
  },
  "4AF8": {
   "unit": "game/mainProgram0800",
   "line": "clear bit 6 so it no longer counts as a group member for the display"
  },
  "4AFA": {
   "unit": "game/mainProgram0800",
   "line": "store it back for immediate feedback"
  },
  "4AFD": {
   "unit": "game/mainProgram0800",
   "line": "command $89 = leave group (2 bytes: unit)"
  },
  "4AFF": {
   "unit": "game/mainProgram0800",
   "line": "command argument = unit index"
  },
  "4B01": {
   "unit": "game/mainProgram0800",
   "line": "queue it and return from queueCmd2"
  },
  "4B07": {
   "unit": "game/mainProgram0800",
   "line": "A and Y are still the arguments: build the group's bounding box (positions or destinations)"
  },
  "4B0A": {
   "unit": "game/mainProgram0800",
   "line": "number of members found"
  },
  "4B0C": {
   "unit": "game/mainProgram0800",
   "line": "an empty group has no box - accept anything"
  },
  "4B0E": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4B04"
  },
  "4B10": {
   "unit": "game/mainProgram0800",
   "line": "index below $80: measure the unit's own cell"
  },
  "4B12": {
   "unit": "game/mainProgram0800",
   "line": "$80: measure the cell picked with the target cursor"
  },
  "4B15": {
   "unit": "game/mainProgram0800",
   "line": "its row"
  },
  "4B18": {
   "unit": "game/mainProgram0800",
   "line": "on to the four edge tests"
  },
  "4B1B": {
   "unit": "game/mainProgram0800",
   "line": "X = column of the unit being tested"
  },
  "4B1E": {
   "unit": "game/mainProgram0800",
   "line": "its row"
  },
  "4B21": {
   "unit": "game/mainProgram0800",
   "line": "Y = row of the unit being tested"
  },
  "4B23": {
   "unit": "game/mainProgram0800",
   "line": "borrow clear"
  },
  "4B24": {
   "unit": "game/mainProgram0800",
   "line": "distance to the rightmost member"
  },
  "4B26": {
   "unit": "game/mainProgram0800",
   "line": "already positive"
  },
  "4B28": {
   "unit": "game/mainProgram0800",
   "line": "otherwise negate to get the absolute value"
  },
  "4B2A": {
   "unit": "game/mainProgram0800",
   "line": "(two's complement: EOR $FF then +1)"
  },
  "4B2C": {
   "unit": "game/mainProgram0800",
   "line": "a group may never span more than 8 cells"
  },
  "4B2E": {
   "unit": "game/mainProgram0800",
   "line": "9 or more away from the right edge: too far"
  },
  "4B30": {
   "unit": "game/mainProgram0800",
   "line": "column of the tested point again"
  },
  "4B31": {
   "unit": "game/mainProgram0800",
   "line": "borrow clear"
  },
  "4B32": {
   "unit": "game/mainProgram0800",
   "line": "distance to the leftmost member"
  },
  "4B34": {
   "unit": "game/mainProgram0800",
   "line": "already positive"
  },
  "4B36": {
   "unit": "game/mainProgram0800",
   "line": "negate"
  },
  "4B38": {
   "unit": "game/mainProgram0800",
   "line": "+1 completes the two's complement"
  },
  "4B3A": {
   "unit": "game/mainProgram0800",
   "line": "same 8-cell limit"
  },
  "4B3C": {
   "unit": "game/mainProgram0800",
   "line": "too far to the right of the left edge"
  },
  "4B3E": {
   "unit": "game/mainProgram0800",
   "line": "row of the tested point"
  },
  "4B3F": {
   "unit": "game/mainProgram0800",
   "line": "borrow clear"
  },
  "4B40": {
   "unit": "game/mainProgram0800",
   "line": "distance to the bottom-most member"
  },
  "4B42": {
   "unit": "game/mainProgram0800",
   "line": "already positive"
  },
  "4B44": {
   "unit": "game/mainProgram0800",
   "line": "negate"
  },
  "4B46": {
   "unit": "game/mainProgram0800",
   "line": "+1"
  },
  "4B48": {
   "unit": "game/mainProgram0800",
   "line": "8-cell limit"
  },
  "4B4A": {
   "unit": "game/mainProgram0800",
   "line": "too far below the bottom edge"
  },
  "4B4C": {
   "unit": "game/mainProgram0800",
   "line": "row of the tested point again"
  },
  "4B4D": {
   "unit": "game/mainProgram0800",
   "line": "borrow clear"
  },
  "4B4E": {
   "unit": "game/mainProgram0800",
   "line": "distance to the top-most member"
  },
  "4B50": {
   "unit": "game/mainProgram0800",
   "line": "already positive"
  },
  "4B52": {
   "unit": "game/mainProgram0800",
   "line": "negate"
  },
  "4B54": {
   "unit": "game/mainProgram0800",
   "line": "+1"
  },
  "4B56": {
   "unit": "game/mainProgram0800",
   "line": "8-cell limit"
  },
  "4B58": {
   "unit": "game/mainProgram0800",
   "line": "too far above the top edge"
  },
  "4B5A": {
   "unit": "game/mainProgram0800",
   "line": "C=0: the point is within 8 cells of all four edges"
  },
  "4B5B": {
   "unit": "game/mainProgram0800",
   "line": "accepted"
  },
  "4B5C": {
   "unit": "game/mainProgram0800",
   "line": "C=1: too far from the group"
  },
  "4B5D": {
   "unit": "game/mainProgram0800",
   "line": "rejected"
  },
  "4B60": {
   "unit": "game/mainProgram0800",
   "line": "patch the CMP # at $4B80 with the key every member must match"
  },
  "4B63": {
   "unit": "game/mainProgram0800",
   "line": "remember the mode: bit 7 set = measure destinations, clear = measure positions"
  },
  "4B65": {
   "unit": "game/mainProgram0800",
   "line": "zero"
  },
  "4B67": {
   "unit": "game/mainProgram0800",
   "line": "member count = 0"
  },
  "4B69": {
   "unit": "game/mainProgram0800",
   "line": "max column = 0 (only a member can raise it)"
  },
  "4B6B": {
   "unit": "game/mainProgram0800",
   "line": "max row = 0"
  },
  "4B6D": {
   "unit": "game/mainProgram0800",
   "line": "$FF"
  },
  "4B6F": {
   "unit": "game/mainProgram0800",
   "line": "min column = $FF so the first member always lowers it"
  },
  "4B71": {
   "unit": "game/mainProgram0800",
   "line": "min row = $FF"
  },
  "4B73": {
   "unit": "game/mainProgram0800",
   "line": "scan unit slots 99 down to 0"
  },
  "4B78": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = dead or empty slot, skip it"
  },
  "4B7A": {
   "unit": "game/mainProgram0800",
   "line": "the UI copy of the flags - this is what the membership editor manipulates"
  },
  "4B7D": {
   "unit": "game/mainProgram0800",
   "line": "group number, side and the in-a-group bit"
  },
  "4B7F": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4B60: is this unit a member of the group?"
  },
  "4B81": {
   "unit": "game/mainProgram0800",
   "line": "no: next slot"
  },
  "4B83": {
   "unit": "game/mainProgram0800",
   "line": "count the member"
  },
  "4B85": {
   "unit": "game/mainProgram0800",
   "line": "its map column"
  },
  "4B88": {
   "unit": "game/mainProgram0800",
   "line": "test bit 7 of the mode byte"
  },
  "4B8A": {
   "unit": "game/mainProgram0800",
   "line": "positions requested: keep the map column"
  },
  "4B8C": {
   "unit": "game/mainProgram0800",
   "line": "destinations requested: use the ordered column instead"
  },
  "4B8F": {
   "unit": "game/mainProgram0800",
   "line": "without the at-destination marker in bit 7"
  },
  "4B91": {
   "unit": "game/mainProgram0800",
   "line": "beyond the rightmost column so far?"
  },
  "4B93": {
   "unit": "game/mainProgram0800",
   "line": "no"
  },
  "4B95": {
   "unit": "game/mainProgram0800",
   "line": "grow the box to the right"
  },
  "4B97": {
   "unit": "game/mainProgram0800",
   "line": "left of the leftmost column so far?"
  },
  "4B99": {
   "unit": "game/mainProgram0800",
   "line": "no"
  },
  "4B9B": {
   "unit": "game/mainProgram0800",
   "line": "grow the box to the left"
  },
  "4B9D": {
   "unit": "game/mainProgram0800",
   "line": "its map row"
  },
  "4BA0": {
   "unit": "game/mainProgram0800",
   "line": "test the mode byte again"
  },
  "4BA2": {
   "unit": "game/mainProgram0800",
   "line": "positions requested"
  },
  "4BA4": {
   "unit": "game/mainProgram0800",
   "line": "destinations requested: use the ordered row"
  },
  "4BA7": {
   "unit": "game/mainProgram0800",
   "line": "without the at-destination marker"
  },
  "4BA9": {
   "unit": "game/mainProgram0800",
   "line": "below the bottom-most row so far?"
  },
  "4BAB": {
   "unit": "game/mainProgram0800",
   "line": "no"
  },
  "4BAD": {
   "unit": "game/mainProgram0800",
   "line": "grow the box downwards"
  },
  "4BAF": {
   "unit": "game/mainProgram0800",
   "line": "above the top-most row so far?"
  },
  "4BB1": {
   "unit": "game/mainProgram0800",
   "line": "no"
  },
  "4BB3": {
   "unit": "game/mainProgram0800",
   "line": "grow the box upwards"
  },
  "4BB5": {
   "unit": "game/mainProgram0800",
   "line": "next lower slot"
  },
  "4BB6": {
   "unit": "game/mainProgram0800",
   "line": "loop until slot 0 has been examined"
  },
  "4BB8": {
   "unit": "game/mainProgram0800",
   "line": "return the member count in A (Z=1 for an empty group)"
  },
  "4BBA": {
   "unit": "game/mainProgram0800",
   "line": "X = minimum column of the box"
  },
  "4BBC": {
   "unit": "game/mainProgram0800",
   "line": "Y = minimum row of the box"
  },
  "4BBE": {
   "unit": "game/mainProgram0800",
   "line": "the maxima stay in scratch1B/scratch1C"
  },
  "4BC2": {
   "unit": "game/mainProgram0800",
   "line": "sound 3 = the short rejection beep"
  },
  "4BC4": {
   "unit": "game/mainProgram0800",
   "line": "queue it with the sound driver"
  },
  "4BC7": {
   "unit": "game/mainProgram0800",
   "line": "screen row 16, in the middle of the info panel"
  },
  "4BC9": {
   "unit": "game/mainProgram0800",
   "line": "move the text cursor there"
  },
  "4BCC": {
   "unit": "game/mainProgram0800",
   "line": "low byte of the string address"
  },
  "4BCE": {
   "unit": "game/mainProgram0800",
   "line": "$CB31 = '  TOO FAR!' in the text overlay"
  },
  "4BD0": {
   "unit": "game/mainProgram0800",
   "line": "print it and return from printString"
  },
  "4BD5": {
   "unit": "game/mainProgram0800",
   "line": "... so only the single selected unit is drawn highlighted"
  },
  "4BD8": {
   "unit": "game/mainProgram0800",
   "line": "the unit being re-positioned"
  },
  "4BDB": {
   "unit": "game/mainProgram0800",
   "line": "highlight it in the viewport"
  },
  "4BDE": {
   "unit": "game/mainProgram0800",
   "line": "redraw the 7x5 tactical view with the new highlight"
  },
  "4BE1": {
   "unit": "game/mainProgram0800",
   "line": "run the target cursor; cursorTargetMode is negative here, so it only reports the picked cell in $91D8/$91D9 and returns"
  },
  "4BE4": {
   "unit": "game/mainProgram0800",
   "line": "may the modal UI continue? (0 = a screen change, SPACE or the end of the game aborted it)"
  },
  "4BE7": {
   "unit": "game/mainProgram0800",
   "line": "aborted: skip the order and just restore the cursor"
  },
  "4BE9": {
   "unit": "game/mainProgram0800",
   "line": "$80 = compare the picked cell with the members' destinations, not their positions"
  },
  "4BEB": {
   "unit": "game/mainProgram0800",
   "line": "the group being edited"
  },
  "4BEE": {
   "unit": "game/mainProgram0800",
   "line": "is the picked cell within 8 cells of the group's destination box?"
  },
  "4BF1": {
   "unit": "game/mainProgram0800",
   "line": "C=0: accepted"
  },
  "4BF3": {
   "unit": "game/mainProgram0800",
   "line": "C=1: beep and print 'TOO FAR!'"
  },
  "4BF6": {
   "unit": "game/mainProgram0800",
   "line": "scroll the viewport back so the unit is under the cursor again"
  },
  "4BF9": {
   "unit": "game/mainProgram0800",
   "line": "join the common tail"
  },
  "4BFF": {
   "unit": "game/mainProgram0800",
   "line": "command argument 2 = column"
  },
  "4C01": {
   "unit": "game/mainProgram0800",
   "line": "row picked with the cursor"
  },
  "4C04": {
   "unit": "game/mainProgram0800",
   "line": "command argument 3 = row"
  },
  "4C06": {
   "unit": "game/mainProgram0800",
   "line": "the unit being re-positioned"
  },
  "4C09": {
   "unit": "game/mainProgram0800",
   "line": "command argument 1 = unit index"
  },
  "4C0B": {
   "unit": "game/mainProgram0800",
   "line": "command $98 = set destination without leaving the group (4 bytes)"
  },
  "4C0D": {
   "unit": "game/mainProgram0800",
   "line": "queue it for both machines"
  },
  "4C10": {
   "unit": "game/mainProgram0800",
   "line": "local feedback: refresh bit 7 of unitDisplayFlags[unit] (at destination or not)"
  },
  "4C15": {
   "unit": "game/mainProgram0800",
   "line": "its local flag copy"
  },
  "4C18": {
   "unit": "game/mainProgram0800",
   "line": "group number and side, without the member bit"
  },
  "4C1A": {
   "unit": "game/mainProgram0800",
   "line": "same group as the previous NEW POSITION order?"
  },
  "4C1D": {
   "unit": "game/mainProgram0800",
   "line": "no: start a new count"
  },
  "4C1F": {
   "unit": "game/mainProgram0800",
   "line": "yes: one more order outstanding for this group"
  },
  "4C22": {
   "unit": "game/mainProgram0800",
   "line": "on to the message"
  },
  "4C25": {
   "unit": "game/mainProgram0800",
   "line": "remember the group these orders belong to"
  },
  "4C28": {
   "unit": "game/mainProgram0800",
   "line": "this is the first outstanding order ..."
  },
  "4C2A": {
   "unit": "game/mainProgram0800",
   "line": "... for the new group"
  },
  "4C2F": {
   "unit": "game/mainProgram0800",
   "line": "high byte"
  },
  "4C31": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDA # operand at $4C46 with the string pointer low byte"
  },
  "4C34": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDY # operand at $4C48 with the high byte"
  },
  "4C37": {
   "unit": "game/mainProgram0800",
   "line": "colour 4 (purple) for the confirmation"
  },
  "4C39": {
   "unit": "game/mainProgram0800",
   "line": "text colour"
  },
  "4C3B": {
   "unit": "game/mainProgram0800",
   "line": "and the colour of the blanks that pad the centred line"
  },
  "4C3D": {
   "unit": "game/mainProgram0800",
   "line": "blank the info panel"
  },
  "4C40": {
   "unit": "game/mainProgram0800",
   "line": "screen row 16"
  },
  "4C42": {
   "unit": "game/mainProgram0800",
   "line": "move the cursor there"
  },
  "4C45": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4C31"
  },
  "4C47": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4C34"
  },
  "4C49": {
   "unit": "game/mainProgram0800",
   "line": "print the string centred in the 14-column panel"
  },
  "4C4C": {
   "unit": "game/mainProgram0800",
   "line": "scroll the viewport back onto the unit"
  },
  "4C51": {
   "unit": "game/mainProgram0800",
   "line": "restore it for the next round of the editor"
  },
  "4C53": {
   "unit": "game/mainProgram0800",
   "line": "sprite 0 = the hollow square cell cursor"
  },
  "4C56": {
   "unit": "game/mainProgram0800",
   "line": "sprite 2 = the centre box of the viewport, and return from there"
  },
  "4C5B": {
   "unit": "game/mainProgram0800",
   "line": "its local flag copy"
  },
  "4C5E": {
   "unit": "game/mainProgram0800",
   "line": "group number and side"
  },
  "4C60": {
   "unit": "game/mainProgram0800",
   "line": "is it the group the local editor is waiting for?"
  },
  "4C63": {
   "unit": "game/mainProgram0800",
   "line": "no: this order was not counted"
  },
  "4C65": {
   "unit": "game/mainProgram0800",
   "line": "one of the outstanding re-position orders has now been executed"
  },
  "4C68": {
   "unit": "game/mainProgram0800",
   "line": "set the destination (clamped, at-destination bit maintained) and return from there; the group bits are left alone"
  },
  "4C6E": {
   "unit": "game/mainProgram0800",
   "line": "group number (bits 2-4), side (bit 5) and the in-a-group bit 6"
  },
  "4C70": {
   "unit": "game/mainProgram0800",
   "line": "patch the ORA # operand at $4C79 with those bits"
  },
  "4C73": {
   "unit": "game/mainProgram0800",
   "line": "the local copy the display reads"
  },
  "4C76": {
   "unit": "game/mainProgram0800",
   "line": "keep its facing bits 0-1 and the at-destination bit 7"
  },
  "4C78": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4C70: the group bits from the simulation"
  },
  "4C7A": {
   "unit": "game/mainProgram0800",
   "line": "store the merged byte"
  },
  "4C7D": {
   "unit": "game/mainProgram0800",
   "line": "back to the caller"
  },
  "4C81": {
   "unit": "game/mainProgram0800",
   "line": "rule for the row offset: mode 0 store as column, mode 1 negate+store as column, mode 2 negate+store as row"
  },
  "4C86": {
   "unit": "game/mainProgram0800",
   "line": "leader's destination column - the formation turns about this cell"
  },
  "4C89": {
   "unit": "game/mainProgram0800",
   "line": "drop the at-destination marker in bit 7"
  },
  "4C8B": {
   "unit": "game/mainProgram0800",
   "line": "patch the SBC at $4CDA: leader column, used to get each member's dx"
  },
  "4C8E": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDA # at $4CFE: same value, used to rebuild the absolute column"
  },
  "4C91": {
   "unit": "game/mainProgram0800",
   "line": "leader's destination row"
  },
  "4C94": {
   "unit": "game/mainProgram0800",
   "line": "drop the at-destination marker"
  },
  "4C96": {
   "unit": "game/mainProgram0800",
   "line": "patch the SBC at $4CEF: leader row, used to get each member's dy"
  },
  "4C99": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDA # at $4D05: same value for the absolute row"
  },
  "4C9C": {
   "unit": "game/mainProgram0800",
   "line": "authoritative flags of the leader"
  },
  "4C9F": {
   "unit": "game/mainProgram0800",
   "line": "group number (bits 2-4) and side (bit 5)"
  },
  "4CA1": {
   "unit": "game/mainProgram0800",
   "line": "add the in-a-group bit so it can be compared with flags & $7C"
  },
  "4CA3": {
   "unit": "game/mainProgram0800",
   "line": "patch the CMP # at $4CD0 with the group key"
  },
  "4CA6": {
   "unit": "game/mainProgram0800",
   "line": "rotation mode 0-2 (0 SWEEP LEFT, 1 SWEEP RIGHT, 2 REVERSE)"
  },
  "4CA8": {
   "unit": "game/mainProgram0800",
   "line": "rule byte for the column offset"
  },
  "4CAB": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDX # at $4CDC: bit 7 of the rule decides whether dx is negated"
  },
  "4CAE": {
   "unit": "game/mainProgram0800",
   "line": "bit 0 of the rule = which parameter byte the transformed value goes to"
  },
  "4CB0": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDX # at $4CE5: 0 = store as column, 1 = store as row"
  },
  "4CB3": {
   "unit": "game/mainProgram0800",
   "line": "rule byte for the row offset"
  },
  "4CB6": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDX # at $4CF1: bit 7 = negate dy"
  },
  "4CB9": {
   "unit": "game/mainProgram0800",
   "line": "bit 0 of that rule"
  },
  "4CBB": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDX # at $4CFA: destination parameter for the transformed dy"
  },
  "4CBE": {
   "unit": "game/mainProgram0800",
   "line": "start at unit slot 99"
  },
  "4CC0": {
   "unit": "game/mainProgram0800",
   "line": "the loop index lives in the LDY # operand at $4CC4 (Y is needed by sub_34D5)"
  },
  "4CC5": {
   "unit": "game/mainProgram0800",
   "line": "its type byte"
  },
  "4CC8": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = dead or empty slot, skip it"
  },
  "4CCA": {
   "unit": "game/mainProgram0800",
   "line": "authoritative flags"
  },
  "4CCD": {
   "unit": "game/mainProgram0800",
   "line": "group number, side and member bit"
  },
  "4CCF": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4CA3: a member of the leader's group?"
  },
  "4CD1": {
   "unit": "game/mainProgram0800",
   "line": "no: next slot"
  },
  "4CD3": {
   "unit": "game/mainProgram0800",
   "line": "member's destination column"
  },
  "4CD6": {
   "unit": "game/mainProgram0800",
   "line": "without the at-destination marker"
  },
  "4CD8": {
   "unit": "game/mainProgram0800",
   "line": "borrow clear"
  },
  "4CD9": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4C8B: dx = member column - leader column"
  },
  "4CDB": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4CAB: only the N flag of the rule byte matters here"
  },
  "4CDD": {
   "unit": "game/mainProgram0800",
   "line": "rule bit 7 clear: keep dx as it is"
  },
  "4CDF": {
   "unit": "game/mainProgram0800",
   "line": "rule bit 7 set: negate dx ..."
  },
  "4CE1": {
   "unit": "game/mainProgram0800",
   "line": "... carry clear ..."
  },
  "4CE2": {
   "unit": "game/mainProgram0800",
   "line": "... +1 completes the two's complement"
  },
  "4CE4": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4CB0: 0 = the transformed dx becomes the new column offset, 1 = the new row offset"
  },
  "4CE6": {
   "unit": "game/mainProgram0800",
   "line": "store it into cmdParam1 ($74) or cmdParam2 ($75)"
  },
  "4CE8": {
   "unit": "game/mainProgram0800",
   "line": "member's destination row"
  },
  "4CEB": {
   "unit": "game/mainProgram0800",
   "line": "without the at-destination marker"
  },
  "4CED": {
   "unit": "game/mainProgram0800",
   "line": "borrow clear"
  },
  "4CEE": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4C96: dy = member row - leader row"
  },
  "4CF0": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4CB6: N flag of the row rule"
  },
  "4CF2": {
   "unit": "game/mainProgram0800",
   "line": "rule bit 7 clear: keep dy"
  },
  "4CF4": {
   "unit": "game/mainProgram0800",
   "line": "negate dy ..."
  },
  "4CF6": {
   "unit": "game/mainProgram0800",
   "line": "... carry clear ..."
  },
  "4CF7": {
   "unit": "game/mainProgram0800",
   "line": "... +1"
  },
  "4CF9": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4CBB: which parameter byte receives the transformed dy"
  },
  "4CFB": {
   "unit": "game/mainProgram0800",
   "line": "store it into cmdParam1 or cmdParam2"
  },
  "4CFD": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4C8E: leader's destination column"
  },
  "4CFF": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "4D00": {
   "unit": "game/mainProgram0800",
   "line": "absolute column = leader column + transformed column offset"
  },
  "4D02": {
   "unit": "game/mainProgram0800",
   "line": "cmdParam1 = the member's new destination column"
  },
  "4D04": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4C99: leader's destination row"
  },
  "4D06": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "4D07": {
   "unit": "game/mainProgram0800",
   "line": "absolute row = leader row + transformed row offset"
  },
  "4D0B": {
   "unit": "game/mainProgram0800",
   "line": "in battle a rotation may use the whole map"
  },
  "4D0D": {
   "unit": "game/mainProgram0800",
   "line": "did this command come from the opponent?"
  },
  "4D0F": {
   "unit": "game/mainProgram0800",
   "line": "no: it is our own order"
  },
  "4D11": {
   "unit": "game/mainProgram0800",
   "line": "remote order: rows must stay in the top half (0-19)"
  },
  "4D13": {
   "unit": "game/mainProgram0800",
   "line": "already there"
  },
  "4D15": {
   "unit": "game/mainProgram0800",
   "line": "clamp to row 19, the last row of the opponent's half"
  },
  "4D17": {
   "unit": "game/mainProgram0800",
   "line": "always taken"
  },
  "4D19": {
   "unit": "game/mainProgram0800",
   "line": "own order: rows must stay in the bottom half (20-39)"
  },
  "4D1B": {
   "unit": "game/mainProgram0800",
   "line": "already there"
  },
  "4D1D": {
   "unit": "game/mainProgram0800",
   "line": "clamp to row 20, the first row of our own half"
  },
  "4D1F": {
   "unit": "game/mainProgram0800",
   "line": "cmdParam2 = the member's new destination row"
  },
  "4D21": {
   "unit": "game/mainProgram0800",
   "line": "order unit Y to that cell (clamped to the map, at-destination bit maintained)"
  },
  "4D24": {
   "unit": "game/mainProgram0800",
   "line": "next lower unit slot, straight in the LDY operand"
  },
  "4D27": {
   "unit": "game/mainProgram0800",
   "line": "loop until slot 0 has been handled"
  },
  "4D29": {
   "unit": "game/mainProgram0800",
   "line": "back to the command dispatcher"
  },
  "4D2C": {
   "unit": "game/mainProgram0800",
   "line": "$81 move group keeping formation (4 bytes)"
  },
  "4D2E": {
   "unit": "game/mainProgram0800",
   "line": "$82 cloak on, single unit (2 bytes)"
  },
  "4D30": {
   "unit": "game/mainProgram0800",
   "line": "$83 cloak on, whole group (2 bytes)"
  },
  "4D32": {
   "unit": "game/mainProgram0800",
   "line": "$84 cloak off, single unit (2 bytes)"
  },
  "4D34": {
   "unit": "game/mainProgram0800",
   "line": "$85 cloak off, whole group (2 bytes)"
  },
  "4D36": {
   "unit": "game/mainProgram0800",
   "line": "$86 clear a boomer's target (2 bytes)"
  },
  "4D38": {
   "unit": "game/mainProgram0800",
   "line": "$87 set a boomer's target (3 bytes)"
  },
  "4D3A": {
   "unit": "game/mainProgram0800",
   "line": "$88 join group (3 bytes: unit, group key)"
  },
  "4D3C": {
   "unit": "game/mainProgram0800",
   "line": "$89 leave group (2 bytes: unit)"
  },
  "4D3E": {
   "unit": "game/mainProgram0800",
   "line": "$8A place unit at a cell (4 bytes) - the setup phase"
  },
  "4D40": {
   "unit": "game/mainProgram0800",
   "line": "$8B set the map seed (4 bytes: 3 seed bytes)"
  },
  "4D42": {
   "unit": "game/mainProgram0800",
   "line": "$8C session control: abort, pause, resume, disconnect (2 bytes: reason)"
  },
  "4D44": {
   "unit": "game/mainProgram0800",
   "line": "$8D drone position report - never dispatched: the mergers run it at once, so the slot holds $FFFF"
  },
  "4D46": {
   "unit": "game/mainProgram0800",
   "line": "$8E detonate the drone (3 bytes: column, row)"
  },
  "4D48": {
   "unit": "game/mainProgram0800",
   "line": "$8F release drone control (1 byte)"
  },
  "4D4A": {
   "unit": "game/mainProgram0800",
   "line": "$90 comcen missile strike (3 bytes: column, row)"
  },
  "4D4C": {
   "unit": "game/mainProgram0800",
   "line": "$91 self destruct, unit or its whole group (2 bytes)"
  },
  "4D4E": {
   "unit": "game/mainProgram0800",
   "line": "$92 the drone reached its target (1 byte)"
  },
  "4D50": {
   "unit": "game/mainProgram0800",
   "line": "$93 blitz on, single unit (2 bytes)"
  },
  "4D52": {
   "unit": "game/mainProgram0800",
   "line": "$94 blitz on, whole group (2 bytes)"
  },
  "4D54": {
   "unit": "game/mainProgram0800",
   "line": "$95 blitz off, single unit (2 bytes)"
  },
  "4D56": {
   "unit": "game/mainProgram0800",
   "line": "$96 blitz off, whole group (2 bytes)"
  },
  "4D58": {
   "unit": "game/mainProgram0800",
   "line": "$97 drone launched (1 byte)"
  },
  "4D5A": {
   "unit": "game/mainProgram0800",
   "line": "$98 move but keep group membership (4 bytes)"
  },
  "4D5C": {
   "unit": "game/mainProgram0800",
   "line": "$99 menu-level link poll (1 byte) - the handler is the link keep-alive itself"
  },
  "4D5E": {
   "unit": "game/mainProgram0800",
   "line": "$9A add energy to a unit (3 bytes: unit, amount)"
  },
  "4D60": {
   "unit": "game/mainProgram0800",
   "line": "$9B halt every member of a group (2 bytes)"
  },
  "4D62": {
   "unit": "game/mainProgram0800",
   "line": "$9C set the recycler mode and cell (4 bytes)"
  },
  "4D64": {
   "unit": "game/mainProgram0800",
   "line": "$9D choose a side (2 bytes)"
  },
  "4D66": {
   "unit": "game/mainProgram0800",
   "line": "$9E comcen repaired / setup finished (1 byte)"
  },
  "4D68": {
   "unit": "game/mainProgram0800",
   "line": "$9F dig in, single unit (2 bytes)"
  },
  "4D6A": {
   "unit": "game/mainProgram0800",
   "line": "$A0 dig out, single unit (2 bytes)"
  },
  "4D6C": {
   "unit": "game/mainProgram0800",
   "line": "$A1 dig in, whole group (2 bytes)"
  },
  "4D6E": {
   "unit": "game/mainProgram0800",
   "line": "$A2 dig out, whole group (2 bytes)"
  },
  "4D70": {
   "unit": "game/mainProgram0800",
   "line": "$A3 rotate the formation (3 bytes: leader, mode 0-2)"
  },
  "4D72": {
   "unit": "game/mainProgram0800",
   "line": "$A4 set the game type / option byte (2 bytes)"
  },
  "4D74": {
   "unit": "game/mainProgram0800",
   "line": "$A5 set one setup option (3 bytes: kind, value)"
  },
  "4D76": {
   "unit": "game/mainProgram0800",
   "line": "$A6 set a custom map seed (4 bytes) - $51EA, rendered as raw bytes because the table was typed one entry short"
  },
  "4D80": {
   "unit": "game/mainProgram0800",
   "line": "$88-$8F: join group 3, leave group 2, place unit 4, map seed 4, session control 2, drone report 4, detonate 3, release drone 1"
  },
  "4D88": {
   "unit": "game/mainProgram0800",
   "line": "$90-$97: missile 3, self destruct 2, drone arrived 1, blitz on 2, group blitz on 2, blitz off 2, group blitz off 2, drone launched 1"
  },
  "4D90": {
   "unit": "game/mainProgram0800",
   "line": "$98-$9F: move keeping group 4, menu poll 1, add energy 3, halt group 2, recycler 4, choose side 2, setup ready 1, dig in 2"
  },
  "4D98": {
   "unit": "game/mainProgram0800",
   "line": "$A0-$A6: dig out 2, group dig in 2, group dig out 2, formation 3, game type 2, setup option 3, custom map seed 4"
  },
  "4DA1": {
   "unit": "game/mainProgram0800",
   "line": "index the length table with it"
  },
  "4DA2": {
   "unit": "game/mainProgram0800",
   "line": "1, 2, 3 or 4 bytes"
  },
  "4DA5": {
   "unit": "game/mainProgram0800",
   "line": "the mergers and the dispatcher use this to skip to the next command"
  },
  "4DA8": {
   "unit": "game/mainProgram0800",
   "line": "length also returned in A"
  },
  "4DAC": {
   "unit": "game/mainProgram0800",
   "line": "is a game film being played back?"
  },
  "4DAF": {
   "unit": "game/mainProgram0800",
   "line": "yes: never throttle, the film's own time stamps do the pacing"
  },
  "4DB1": {
   "unit": "game/mainProgram0800",
   "line": "frames still to wait before the next packet exchange"
  },
  "4DB4": {
   "unit": "game/mainProgram0800",
   "line": "timer expired: try to exchange"
  },
  "4DB6": {
   "unit": "game/mainProgram0800",
   "line": "still throttled, come back next frame"
  },
  "4DBA": {
   "unit": "game/mainProgram0800",
   "line": "clear: this is a live game, use the link"
  },
  "4DBF": {
   "unit": "game/mainProgram0800",
   "line": "the game has ended: stop feeding records, only keep the link alive"
  },
  "4DC1": {
   "unit": "game/mainProgram0800",
   "line": "has the film pointer reached the recorded end?"
  },
  "4DC4": {
   "unit": "game/mainProgram0800",
   "line": "no: there is another record to look at"
  },
  "4DC6": {
   "unit": "game/mainProgram0800",
   "line": "end of the buffer: restore the final scores and clock and start the end sequence"
  },
  "4DCC": {
   "unit": "game/mainProgram0800",
   "line": "no link at all: just return"
  },
  "4DCE": {
   "unit": "game/mainProgram0800",
   "line": "service the menu-level link (carrier check plus keep-alive) and return from there"
  },
  "4DD1": {
   "unit": "game/mainProgram0800",
   "line": "nothing to do this frame"
  },
  "4DD4": {
   "unit": "game/mainProgram0800",
   "line": "read it from the film buffer in the RAM hidden under the I/O area"
  },
  "4DD7": {
   "unit": "game/mainProgram0800",
   "line": "park the header byte in mergedCmdLength for now"
  },
  "4DDA": {
   "unit": "game/mainProgram0800",
   "line": "offset 1"
  },
  "4DDB": {
   "unit": "game/mainProgram0800",
   "line": "the time-stamp byte"
  },
  "4DDE": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDA # at $4DE9 so the byte can be re-read without touching the film again"
  },
  "4DE1": {
   "unit": "game/mainProgram0800",
   "line": "low nibble = the exchange slot within the round (commandSliceIndex, one exchange per ten units processed)"
  },
  "4DE3": {
   "unit": "game/mainProgram0800",
   "line": "only replay this record when the round has reached the same slot it was recorded in"
  },
  "4DE6": {
   "unit": "game/mainProgram0800",
   "line": "this record belongs to another slot: not yet"
  },
  "4DE8": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4DDE: the time-stamp byte again"
  },
  "4DEA": {
   "unit": "game/mainProgram0800",
   "line": "shift the high nibble down ..."
  },
  "4DEB": {
   "unit": "game/mainProgram0800",
   "line": "..."
  },
  "4DEC": {
   "unit": "game/mainProgram0800",
   "line": "..."
  },
  "4DED": {
   "unit": "game/mainProgram0800",
   "line": "... A = the clock delta code"
  },
  "4DEE": {
   "unit": "game/mainProgram0800",
   "line": "0 = the long form: an absolute clock byte follows"
  },
  "4DF0": {
   "unit": "game/mainProgram0800",
   "line": "sign-extend the 4-bit delta to a negative byte"
  },
  "4DF2": {
   "unit": "game/mainProgram0800",
   "line": "the writer subtracted one extra, add it back"
  },
  "4DF3": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4E01: the clock of the previous record, giving this record's clock"
  },
  "4DF5": {
   "unit": "game/mainProgram0800",
   "line": "join the clock comparison"
  },
  "4DF8": {
   "unit": "game/mainProgram0800",
   "line": "long form: offset 2"
  },
  "4DF9": {
   "unit": "game/mainProgram0800",
   "line": "read the absolute game clock of the record"
  },
  "4DFC": {
   "unit": "game/mainProgram0800",
   "line": "is this record due now?"
  },
  "4DFF": {
   "unit": "game/mainProgram0800",
   "line": "no, it is in the future: come back next frame"
  },
  "4E01": {
   "unit": "game/mainProgram0800",
   "line": "remember this clock as the base for the next record's delta"
  },
  "4E04": {
   "unit": "game/mainProgram0800",
   "line": "step past the time stamp"
  },
  "4E05": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDY # at $4E20: offset of the first command byte in this record"
  },
  "4E08": {
   "unit": "game/mainProgram0800",
   "line": "the header byte again"
  },
  "4E0B": {
   "unit": "game/mainProgram0800",
   "line": "$FE = the recorded game reached its own end"
  },
  "4E0D": {
   "unit": "game/mainProgram0800",
   "line": "freeze here: return every frame so the last picture stays up"
  },
  "4E0F": {
   "unit": "game/mainProgram0800",
   "line": "$FF = the recorded game was aborted: run the end-of-film handler"
  },
  "4E11": {
   "unit": "game/mainProgram0800",
   "line": "keep the header byte"
  },
  "4E12": {
   "unit": "game/mainProgram0800",
   "line": "low nibble = number of command bytes in the record"
  },
  "4E14": {
   "unit": "game/mainProgram0800",
   "line": "that is the length of the merged buffer for this exchange"
  },
  "4E17": {
   "unit": "game/mainProgram0800",
   "line": "header byte back"
  },
  "4E18": {
   "unit": "game/mainProgram0800",
   "line": "shift the high nibble down ..."
  },
  "4E19": {
   "unit": "game/mainProgram0800",
   "line": "..."
  },
  "4E1A": {
   "unit": "game/mainProgram0800",
   "line": "..."
  },
  "4E1B": {
   "unit": "game/mainProgram0800",
   "line": "... A = where player 1's commands start"
  },
  "4E1C": {
   "unit": "game/mainProgram0800",
   "line": "the dispatcher flips the owner flag at this index"
  },
  "4E1F": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $4E05: first command byte of the record"
  },
  "4E21": {
   "unit": "game/mainProgram0800",
   "line": "write into the merged buffer from index 0"
  },
  "4E26": {
   "unit": "game/mainProgram0800",
   "line": "into the merged command buffer"
  },
  "4E29": {
   "unit": "game/mainProgram0800",
   "line": "next film offset"
  },
  "4E2A": {
   "unit": "game/mainProgram0800",
   "line": "next buffer index"
  },
  "4E2B": {
   "unit": "game/mainProgram0800",
   "line": "all command bytes copied?"
  },
  "4E2E": {
   "unit": "game/mainProgram0800",
   "line": "no: keep copying"
  },
  "4E30": {
   "unit": "game/mainProgram0800",
   "line": "advance the film pointer by the whole record (Y = its length)"
  },
  "4E33": {
   "unit": "game/mainProgram0800",
   "line": "execute the commands exactly as a live exchange would"
  },
  "4E36": {
   "unit": "game/mainProgram0800",
   "line": "look for another record with the same time stamp"
  },
  "4E3C": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 clear: a game session is running, do the full exchange"
  },
  "4E3E": {
   "unit": "game/mainProgram0800",
   "line": "menu level: only the carrier check and the menu packet handler, then return"
  },
  "4E41": {
   "unit": "game/mainProgram0800",
   "line": "returns without coming back here if the game is already over"
  },
  "4E44": {
   "unit": "game/mainProgram0800",
   "line": "the end-of-game countdown is running: no more exchanges"
  },
  "4E46": {
   "unit": "game/mainProgram0800",
   "line": "comm module flag: is the link (UART or trainer) running at all?"
  },
  "4E49": {
   "unit": "game/mainProgram0800",
   "line": "no link: nothing to exchange"
  },
  "4E4E": {
   "unit": "game/mainProgram0800",
   "line": "both packets are in: go and merge them"
  },
  "4E50": {
   "unit": "game/mainProgram0800",
   "line": "arm/advance the link watchdog (it counts in the raster IRQ)"
  },
  "4E53": {
   "unit": "game/mainProgram0800",
   "line": "C=1 = already overdue: spin here instead of giving the frame back"
  },
  "4E55": {
   "unit": "game/mainProgram0800",
   "line": "not ready yet, try again next frame"
  },
  "4E59": {
   "unit": "game/mainProgram0800",
   "line": "$15 = the 'waiting for the opponent' sound"
  },
  "4E5B": {
   "unit": "game/mainProgram0800",
   "line": "some other sound: leave it alone"
  },
  "4E5D": {
   "unit": "game/mainProgram0800",
   "line": "sound 0 = silence"
  },
  "4E5F": {
   "unit": "game/mainProgram0800",
   "line": "stop the waiting sound now that the opponent answered"
  },
  "4E62": {
   "unit": "game/mainProgram0800",
   "line": "10 frames ..."
  },
  "4E64": {
   "unit": "game/mainProgram0800",
   "line": "... before the next exchange is attempted"
  },
  "4E67": {
   "unit": "game/mainProgram0800",
   "line": "0 also clears bit 7 ..."
  },
  "4E69": {
   "unit": "game/mainProgram0800",
   "line": "... which stops the IRQ advancing the link watchdog"
  },
  "4E6C": {
   "unit": "game/mainProgram0800",
   "line": "the merged command buffer starts out empty"
  },
  "4E6E": {
   "unit": "game/mainProgram0800",
   "line": "which side is this machine playing?"
  },
  "4E71": {
   "unit": "game/mainProgram0800",
   "line": "player 1: the opponent's commands go first"
  },
  "4E73": {
   "unit": "game/mainProgram0800",
   "line": "player 0: our own commands go into the buffer first"
  },
  "4E76": {
   "unit": "game/mainProgram0800",
   "line": "bytes written so far"
  },
  "4E78": {
   "unit": "game/mainProgram0800",
   "line": "= the index at which player 1's commands start"
  },
  "4E7B": {
   "unit": "game/mainProgram0800",
   "line": "then append the packet received from the opponent"
  },
  "4E7E": {
   "unit": "game/mainProgram0800",
   "line": "on to the dispatcher"
  },
  "4E81": {
   "unit": "game/mainProgram0800",
   "line": "player 1: the opponent is player 0, so his commands go first"
  },
  "4E84": {
   "unit": "game/mainProgram0800",
   "line": "bytes written so far"
  },
  "4E86": {
   "unit": "game/mainProgram0800",
   "line": "= the index at which player 1's (our own) commands start"
  },
  "4E89": {
   "unit": "game/mainProgram0800",
   "line": "then append the copy of what we just sent"
  },
  "4E8C": {
   "unit": "game/mainProgram0800",
   "line": "total number of bytes in the merged buffer"
  },
  "4E8E": {
   "unit": "game/mainProgram0800",
   "line": "and fall straight into executeTurnCommands"
  },
  "4E93": {
   "unit": "game/mainProgram0800",
   "line": "... of the merged command buffer"
  },
  "4E96": {
   "unit": "game/mainProgram0800",
   "line": "which side is this machine?"
  },
  "4E99": {
   "unit": "game/mainProgram0800",
   "line": "player 0: the first block of commands is our own (owner flag 0)"
  },
  "4E9B": {
   "unit": "game/mainProgram0800",
   "line": "player 1: the first block belongs to player 0, i.e. the opponent"
  },
  "4E9D": {
   "unit": "game/mainProgram0800",
   "line": "owner of the block currently being executed"
  },
  "4EA3": {
   "unit": "game/mainProgram0800",
   "line": "past the end of the merged buffer?"
  },
  "4EA6": {
   "unit": "game/mainProgram0800",
   "line": "yes: the exchange has been executed"
  },
  "4EA8": {
   "unit": "game/mainProgram0800",
   "line": "activity flag read by the round timer"
  },
  "4EAB": {
   "unit": "game/mainProgram0800",
   "line": "bit 0 = at least one command was executed this round"
  },
  "4EAD": {
   "unit": "game/mainProgram0800",
   "line": "in the setup phase the clock only ticks while something happens"
  },
  "4EB0": {
   "unit": "game/mainProgram0800",
   "line": "have we reached the boundary between the two players' commands?"
  },
  "4EB3": {
   "unit": "game/mainProgram0800",
   "line": "no: same owner as the previous command"
  },
  "4EB5": {
   "unit": "game/mainProgram0800",
   "line": "current owner"
  },
  "4EB8": {
   "unit": "game/mainProgram0800",
   "line": "flip between own ($00) and opponent ($80)"
  },
  "4EBA": {
   "unit": "game/mainProgram0800",
   "line": "everything from here belongs to the other player"
  },
  "4EBD": {
   "unit": "game/mainProgram0800",
   "line": "owner of this command"
  },
  "4EC0": {
   "unit": "game/mainProgram0800",
   "line": "the handlers test bit 7 of this to mirror coordinates and to suppress local-only side effects"
  },
  "4EC2": {
   "unit": "game/mainProgram0800",
   "line": "a command from the opponent: nothing of ours to account for"
  },
  "4EC4": {
   "unit": "game/mainProgram0800",
   "line": "one of our own queued orders has now been executed"
  },
  "4EC7": {
   "unit": "game/mainProgram0800",
   "line": "index of the command byte"
  },
  "4ECA": {
   "unit": "game/mainProgram0800",
   "line": "the command byte itself"
  },
  "4ECD": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 set = a live command, dispatch it"
  },
  "4ED2": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "4ED3": {
   "unit": "game/mainProgram0800",
   "line": "skip the whole command ..."
  },
  "4ED6": {
   "unit": "game/mainProgram0800",
   "line": "... without executing it"
  },
  "4ED9": {
   "unit": "game/mainProgram0800",
   "line": "always taken (the index can never wrap in a 14-byte buffer)"
  },
  "4EDE": {
   "unit": "game/mainProgram0800",
   "line": "$80-$A6 becomes index $00-$26"
  },
  "4EE0": {
   "unit": "game/mainProgram0800",
   "line": "two bytes per table entry"
  },
  "4EE1": {
   "unit": "game/mainProgram0800",
   "line": "X = byte offset into commandHandlerTable"
  },
  "4EE2": {
   "unit": "game/mainProgram0800",
   "line": "low byte of the handler address"
  },
  "4EE5": {
   "unit": "game/mainProgram0800",
   "line": "patch the JSR operand at $4EEF"
  },
  "4EE8": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the handler address"
  },
  "4EEB": {
   "unit": "game/mainProgram0800",
   "line": "patch the JSR operand at $4EF0"
  },
  "4EEE": {
   "unit": "game/mainProgram0800",
   "line": "call the handler (the $FFFF shown here is only the unpatched placeholder from the $8D table slot)"
  },
  "4EF1": {
   "unit": "game/mainProgram0800",
   "line": "on to the next command"
  },
  "4EF7": {
   "unit": "game/mainProgram0800",
   "line": "live game: record and answer"
  },
  "4EF9": {
   "unit": "game/mainProgram0800",
   "line": "film playback: done"
  },
  "4EFA": {
   "unit": "game/mainProgram0800",
   "line": "append this exchange (header, time stamp, command bytes) to the film in the RAM under I/O"
  },
  "4EFD": {
   "unit": "game/mainProgram0800",
   "line": "comm module function 2 = acknowledge/consume the completed exchange"
  },
  "4EFF": {
   "unit": "game/mainProgram0800",
   "line": "clears $E01D/$E01E/$E01F so the next exchange can start"
  },
  "4F02": {
   "unit": "game/mainProgram0800",
   "line": "build the next outgoing packet from the command ring and hand it to the comm module"
  },
  "4F08": {
   "unit": "game/mainProgram0800",
   "line": "empty packet: nothing to merge"
  },
  "4F0A": {
   "unit": "game/mainProgram0800",
   "line": "$80 = these commands come from the opponent ..."
  },
  "4F0C": {
   "unit": "game/mainProgram0800",
   "line": "... so cmdDroneMove and cmdSessionControl mirror their coordinates"
  },
  "4F0E": {
   "unit": "game/mainProgram0800",
   "line": "start at the first byte of the packet"
  },
  "4F10": {
   "unit": "game/mainProgram0800",
   "line": "zp_6A is the read cursor inside the packet"
  },
  "4F14": {
   "unit": "game/mainProgram0800",
   "line": "its command byte"
  },
  "4F17": {
   "unit": "game/mainProgram0800",
   "line": "look up its length and point zp_6A at the following command"
  },
  "4F1A": {
   "unit": "game/mainProgram0800",
   "line": "$97 = the opponent launched his drone"
  },
  "4F1C": {
   "unit": "game/mainProgram0800",
   "line": "not $97"
  },
  "4F1E": {
   "unit": "game/mainProgram0800",
   "line": "clear all drone state ..."
  },
  "4F20": {
   "unit": "game/mainProgram0800",
   "line": "... a new drone flight starts; the $97 command itself is copied into the buffer as well"
  },
  "4F23": {
   "unit": "game/mainProgram0800",
   "line": "$8D = the opponent's drone position report"
  },
  "4F25": {
   "unit": "game/mainProgram0800",
   "line": "not $8D"
  },
  "4F27": {
   "unit": "game/mainProgram0800",
   "line": "step to its first parameter byte"
  },
  "4F28": {
   "unit": "game/mainProgram0800",
   "line": "run it immediately (it only moves the drone sprite and the view) and do not copy it"
  },
  "4F2B": {
   "unit": "game/mainProgram0800",
   "line": "on to the next command"
  },
  "4F2E": {
   "unit": "game/mainProgram0800",
   "line": "$8C = session control (abort, pause, resume, disconnect)"
  },
  "4F30": {
   "unit": "game/mainProgram0800",
   "line": "not $8C"
  },
  "4F32": {
   "unit": "game/mainProgram0800",
   "line": "step to the reason byte"
  },
  "4F33": {
   "unit": "game/mainProgram0800",
   "line": "read it"
  },
  "4F36": {
   "unit": "game/mainProgram0800",
   "line": "act on it at once - an abort must work even mid-buffer - and do not copy it"
  },
  "4F39": {
   "unit": "game/mainProgram0800",
   "line": "on to the next command"
  },
  "4F3C": {
   "unit": "game/mainProgram0800",
   "line": "$92 = the opponent's drone reached its target"
  },
  "4F3E": {
   "unit": "game/mainProgram0800",
   "line": "not $92"
  },
  "4F40": {
   "unit": "game/mainProgram0800",
   "line": "remember it in the drone flags ..."
  },
  "4F42": {
   "unit": "game/mainProgram0800",
   "line": "... the drone screen watches this byte"
  },
  "4F45": {
   "unit": "game/mainProgram0800",
   "line": "X = where in the merged buffer this command goes"
  },
  "4F4A": {
   "unit": "game/mainProgram0800",
   "line": "into the merged command buffer"
  },
  "4F4D": {
   "unit": "game/mainProgram0800",
   "line": "the buffer grew by one byte"
  },
  "4F4F": {
   "unit": "game/mainProgram0800",
   "line": "next buffer position"
  },
  "4F50": {
   "unit": "game/mainProgram0800",
   "line": "next packet position"
  },
  "4F51": {
   "unit": "game/mainProgram0800",
   "line": "one byte of this command done"
  },
  "4F54": {
   "unit": "game/mainProgram0800",
   "line": "keep going until the whole command has been copied"
  },
  "4F56": {
   "unit": "game/mainProgram0800",
   "line": "the cursor advancePacketCursor left"
  },
  "4F58": {
   "unit": "game/mainProgram0800",
   "line": "reached the end of the packet?"
  },
  "4F5B": {
   "unit": "game/mainProgram0800",
   "line": "no: next command"
  },
  "4F5D": {
   "unit": "game/mainProgram0800",
   "line": "the received packet has been merged"
  },
  "4F5F": {
   "unit": "game/mainProgram0800",
   "line": "A = its total length, also stored in currentCommandLength"
  },
  "4F62": {
   "unit": "game/mainProgram0800",
   "line": "offset of this command"
  },
  "4F63": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "4F64": {
   "unit": "game/mainProgram0800",
   "line": "plus its length"
  },
  "4F67": {
   "unit": "game/mainProgram0800",
   "line": "= offset of the next command in the packet"
  },
  "4F69": {
   "unit": "game/mainProgram0800",
   "line": "hand the command byte back"
  },
  "4F6A": {
   "unit": "game/mainProgram0800",
   "line": "the caller compares it against the special command bytes"
  },
  "4F6E": {
   "unit": "game/mainProgram0800",
   "line": "we sent nothing: nothing to merge"
  },
  "4F70": {
   "unit": "game/mainProgram0800",
   "line": "0 = these are our own commands ..."
  },
  "4F72": {
   "unit": "game/mainProgram0800",
   "line": "... so their coordinates are already in our own frame of reference"
  },
  "4F74": {
   "unit": "game/mainProgram0800",
   "line": "start at the first byte"
  },
  "4F76": {
   "unit": "game/mainProgram0800",
   "line": "zp_6A is the read cursor inside the packet"
  },
  "4F7A": {
   "unit": "game/mainProgram0800",
   "line": "its command byte"
  },
  "4F7D": {
   "unit": "game/mainProgram0800",
   "line": "look up its length and point zp_6A at the next command"
  },
  "4F80": {
   "unit": "game/mainProgram0800",
   "line": "$8D = our own drone position report"
  },
  "4F82": {
   "unit": "game/mainProgram0800",
   "line": "not $8D"
  },
  "4F84": {
   "unit": "game/mainProgram0800",
   "line": "column the drone screen chose ..."
  },
  "4F87": {
   "unit": "game/mainProgram0800",
   "line": "... becomes the tactical view origin ..."
  },
  "4F89": {
   "unit": "game/mainProgram0800",
   "line": "... together with the row ..."
  },
  "4F8C": {
   "unit": "game/mainProgram0800",
   "line": "... so the view follows the drone"
  },
  "4F8E": {
   "unit": "game/mainProgram0800",
   "line": "the report counted as an outstanding order when it was queued"
  },
  "4F91": {
   "unit": "game/mainProgram0800",
   "line": "and it is not copied into the merged buffer"
  },
  "4F94": {
   "unit": "game/mainProgram0800",
   "line": "$8C = session control"
  },
  "4F96": {
   "unit": "game/mainProgram0800",
   "line": "not $8C"
  },
  "4F98": {
   "unit": "game/mainProgram0800",
   "line": "step to the reason byte"
  },
  "4F99": {
   "unit": "game/mainProgram0800",
   "line": "read it"
  },
  "4F9C": {
   "unit": "game/mainProgram0800",
   "line": "act on our own pause/abort at once, then also count it off"
  },
  "4F9F": {
   "unit": "game/mainProgram0800",
   "line": "and skip the copy"
  },
  "4FA2": {
   "unit": "game/mainProgram0800",
   "line": "$92 = our drone reached its target"
  },
  "4FA4": {
   "unit": "game/mainProgram0800",
   "line": "not $92"
  },
  "4FA6": {
   "unit": "game/mainProgram0800",
   "line": "set the drone flag ..."
  },
  "4FA8": {
   "unit": "game/mainProgram0800",
   "line": "... the drone screen watches this byte; the command is copied as well"
  },
  "4FAB": {
   "unit": "game/mainProgram0800",
   "line": "X = where in the merged buffer this command goes"
  },
  "4FB0": {
   "unit": "game/mainProgram0800",
   "line": "into the merged buffer"
  },
  "4FB3": {
   "unit": "game/mainProgram0800",
   "line": "next packet position"
  },
  "4FB4": {
   "unit": "game/mainProgram0800",
   "line": "next buffer position"
  },
  "4FB5": {
   "unit": "game/mainProgram0800",
   "line": "the buffer grew by one byte"
  },
  "4FB7": {
   "unit": "game/mainProgram0800",
   "line": "one byte of this command done"
  },
  "4FBA": {
   "unit": "game/mainProgram0800",
   "line": "until the whole command has been copied"
  },
  "4FBC": {
   "unit": "game/mainProgram0800",
   "line": "the cursor advancePacketCursor left"
  },
  "4FBE": {
   "unit": "game/mainProgram0800",
   "line": "end of our packet?"
  },
  "4FC1": {
   "unit": "game/mainProgram0800",
   "line": "no: next command"
  },
  "4FC3": {
   "unit": "game/mainProgram0800",
   "line": "our own packet has been merged"
  },
  "4FC7": {
   "unit": "game/mainProgram0800",
   "line": "first parameter follows it"
  },
  "4FC8": {
   "unit": "game/mainProgram0800",
   "line": "read it"
  },
  "4FCB": {
   "unit": "game/mainProgram0800",
   "line": "cmdParam0, usually the unit or group"
  },
  "4FCD": {
   "unit": "game/mainProgram0800",
   "line": "count that byte, then fall into readCmdArgs2 for the remaining two"
  },
  "4FD3": {
   "unit": "game/mainProgram0800",
   "line": "next byte"
  },
  "4FD4": {
   "unit": "game/mainProgram0800",
   "line": "read it"
  },
  "4FD7": {
   "unit": "game/mainProgram0800",
   "line": "cmdParam1, usually the map column"
  },
  "4FD9": {
   "unit": "game/mainProgram0800",
   "line": "next byte"
  },
  "4FDA": {
   "unit": "game/mainProgram0800",
   "line": "read it"
  },
  "4FDD": {
   "unit": "game/mainProgram0800",
   "line": "cmdParam2, usually the map row"
  },
  "4FDF": {
   "unit": "game/mainProgram0800",
   "line": "step past it"
  },
  "4FE0": {
   "unit": "game/mainProgram0800",
   "line": "the read index now points at the next command byte"
  },
  "4FE3": {
   "unit": "game/mainProgram0800",
   "line": "back to the handler"
  },
  "4FE6": {
   "unit": "game/mainProgram0800",
   "line": "no: our own coordinates are already in the local frame"
  },
  "4FE8": {
   "unit": "game/mainProgram0800",
   "line": "the column"
  },
  "4FEA": {
   "unit": "game/mainProgram0800",
   "line": "39 - column"
  },
  "4FED": {
   "unit": "game/mainProgram0800",
   "line": "back into cmdParam1"
  },
  "4FEF": {
   "unit": "game/mainProgram0800",
   "line": "the row"
  },
  "4FF1": {
   "unit": "game/mainProgram0800",
   "line": "39 - row"
  },
  "4FF4": {
   "unit": "game/mainProgram0800",
   "line": "back into cmdParam2"
  },
  "4FF6": {
   "unit": "game/mainProgram0800",
   "line": "coordinates are now in this machine's frame of reference"
  },
  "4FFA": {
   "unit": "game/mainProgram0800",
   "line": "its single parameter follows"
  },
  "4FFB": {
   "unit": "game/mainProgram0800",
   "line": "read it"
  },
  "4FFE": {
   "unit": "game/mainProgram0800",
   "line": "cmdParam0 = unit index or group key"
  },
  "5000": {
   "unit": "game/mainProgram0800",
   "line": "step past it"
  },
  "5001": {
   "unit": "game/mainProgram0800",
   "line": "the read index now points at the next command"
  },
  "5004": {
   "unit": "game/mainProgram0800",
   "line": "back to the handler"
  },
  "5007": {
   "unit": "game/mainProgram0800",
   "line": "no: leave the coordinates alone"
  },
  "5009": {
   "unit": "game/mainProgram0800",
   "line": "mirror the corner cell (39-x, 39-y)"
  },
  "500C": {
   "unit": "game/mainProgram0800",
   "line": "the mirrored 2x2 block starts one column earlier ..."
  },
  "500E": {
   "unit": "game/mainProgram0800",
   "line": "... and one row earlier"
  },
  "5010": {
   "unit": "game/mainProgram0800",
   "line": "back to the drone handler"
  },
  "5014": {
   "unit": "game/mainProgram0800",
   "line": "a move ordered by the opponent is expressed in his own view of the map"
  },
  "5017": {
   "unit": "game/mainProgram0800",
   "line": "leave the group, refresh the flags and set the destination"
  },
  "501D": {
   "unit": "game/mainProgram0800",
   "line": "mirror the ordered cell for a command from the opponent"
  },
  "5020": {
   "unit": "game/mainProgram0800",
   "line": "shift the whole group by leader-destination minus ordered cell"
  },
  "5026": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDA # at $502D with it - cmdBlitzOff will consume the argument too"
  },
  "5029": {
   "unit": "game/mainProgram0800",
   "line": "clear the unit's blitz bits first: a unit cannot be cloaked and blitzing at once"
  },
  "502C": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5026: the saved read index"
  },
  "502E": {
   "unit": "game/mainProgram0800",
   "line": "rewind so the same argument can be read again"
  },
  "5031": {
   "unit": "game/mainProgram0800",
   "line": "cmdParam0 = the unit"
  },
  "5034": {
   "unit": "game/mainProgram0800",
   "line": "Y = unit index"
  },
  "5036": {
   "unit": "game/mainProgram0800",
   "line": "its type byte"
  },
  "5039": {
   "unit": "game/mainProgram0800",
   "line": "keep only the type, dropping the dead and dug-in bits"
  },
  "503B": {
   "unit": "game/mainProgram0800",
   "line": "type 3 = SPY"
  },
  "503D": {
   "unit": "game/mainProgram0800",
   "line": "spies are always cloaked: nothing to do"
  },
  "503F": {
   "unit": "game/mainProgram0800",
   "line": "waypoint column byte, whose top two bits hold the cloak state"
  },
  "5042": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = cloak active, bit 6 = cloak ordered"
  },
  "5044": {
   "unit": "game/mainProgram0800",
   "line": "the unit is now cloaked"
  },
  "5047": {
   "unit": "game/mainProgram0800",
   "line": "join the shared tail"
  },
  "504A": {
   "unit": "game/mainProgram0800",
   "line": "a SPY needs no cloak order"
  },
  "504E": {
   "unit": "game/mainProgram0800",
   "line": "Y = unit index"
  },
  "5050": {
   "unit": "game/mainProgram0800",
   "line": "waypoint column byte"
  },
  "5053": {
   "unit": "game/mainProgram0800",
   "line": "clear bits 6-7: cloak off"
  },
  "5055": {
   "unit": "game/mainProgram0800",
   "line": "store it back"
  },
  "505A": {
   "unit": "game/mainProgram0800",
   "line": "yes: no local redraw"
  },
  "505C": {
   "unit": "game/mainProgram0800",
   "line": "ask the per-frame tick loop to redraw this unit"
  },
  "505F": {
   "unit": "game/mainProgram0800",
   "line": "refresh 'my comcen is cloaked' and return from there"
  },
  "5065": {
   "unit": "game/mainProgram0800",
   "line": "Y = unit index"
  },
  "5067": {
   "unit": "game/mainProgram0800",
   "line": "waypoint row byte, whose top two bits hold the blitz state"
  },
  "506A": {
   "unit": "game/mainProgram0800",
   "line": "clear bits 6-7: no longer blitzing"
  },
  "506C": {
   "unit": "game/mainProgram0800",
   "line": "store it back"
  },
  "506F": {
   "unit": "game/mainProgram0800",
   "line": "back to the dispatcher (or to cmdCloakOn)"
  },
  "5073": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDA # at $507A - cmdCloakOff consumes the argument as well"
  },
  "5076": {
   "unit": "game/mainProgram0800",
   "line": "clear the cloak bits first: a unit cannot be cloaked and blitzing at once"
  },
  "5079": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5073: the saved read index"
  },
  "507B": {
   "unit": "game/mainProgram0800",
   "line": "rewind so the argument can be read again"
  },
  "507E": {
   "unit": "game/mainProgram0800",
   "line": "cmdParam0 = the unit"
  },
  "5081": {
   "unit": "game/mainProgram0800",
   "line": "Y = unit index"
  },
  "5083": {
   "unit": "game/mainProgram0800",
   "line": "its type byte"
  },
  "5086": {
   "unit": "game/mainProgram0800",
   "line": "keep only the type"
  },
  "5088": {
   "unit": "game/mainProgram0800",
   "line": "type 3 = SPY"
  },
  "508A": {
   "unit": "game/mainProgram0800",
   "line": "spies cannot blitz"
  },
  "508C": {
   "unit": "game/mainProgram0800",
   "line": "waypoint row byte"
  },
  "508F": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = blitz active, bit 6 = blitz ordered"
  },
  "5091": {
   "unit": "game/mainProgram0800",
   "line": "the unit is now blitzing"
  },
  "5094": {
   "unit": "game/mainProgram0800",
   "line": "charge the energy and grant the extra movement points, then return from there"
  },
  "5097": {
   "unit": "game/mainProgram0800",
   "line": "a SPY cannot blitz: nothing to do"
  },
  "509B": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDA # at $50A2 - the blitz handler consumes the argument as well"
  },
  "509E": {
   "unit": "game/mainProgram0800",
   "line": "clear the group's blitz bits first"
  },
  "50A1": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $509B: the saved read index"
  },
  "50A3": {
   "unit": "game/mainProgram0800",
   "line": "rewind so the group key can be read again"
  },
  "50A6": {
   "unit": "game/mainProgram0800",
   "line": "X = $80: cloak variant, group status bit 7"
  },
  "50A8": {
   "unit": "game/mainProgram0800",
   "line": "always taken: enter the cloak patch block"
  },
  "50AD": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDA # at $50B4 - the cloak handler consumes the argument as well"
  },
  "50B0": {
   "unit": "game/mainProgram0800",
   "line": "clear the group's cloak bits first"
  },
  "50B3": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $50AD: the saved read index"
  },
  "50B5": {
   "unit": "game/mainProgram0800",
   "line": "rewind so the group key can be read again"
  },
  "50B8": {
   "unit": "game/mainProgram0800",
   "line": "per-unit bits to OR in: blitz ordered + active"
  },
  "50BA": {
   "unit": "game/mainProgram0800",
   "line": "bit 6 of the group's status entry = the group is blitzing"
  },
  "50BC": {
   "unit": "game/mainProgram0800",
   "line": "always taken"
  },
  "50C0": {
   "unit": "game/mainProgram0800",
   "line": "A = 0 as well"
  },
  "50C1": {
   "unit": "game/mainProgram0800",
   "line": "patch the ORA # at $514D: bits to set in the group status entry"
  },
  "50C4": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDA # at $512D: non-zero here means 'charge the blitz cost per member'"
  },
  "50C7": {
   "unit": "game/mainProgram0800",
   "line": "keep everything but bit 6 ..."
  },
  "50C9": {
   "unit": "game/mainProgram0800",
   "line": "... patch the AND # at $514B (bit 6 = the group is blitzing)"
  },
  "50CC": {
   "unit": "game/mainProgram0800",
   "line": "low byte of unitPathRowTable ($F8FC), the blitz table"
  },
  "50CE": {
   "unit": "game/mainProgram0800",
   "line": "its high byte"
  },
  "50D0": {
   "unit": "game/mainProgram0800",
   "line": "always taken: run the shared body"
  },
  "50D4": {
   "unit": "game/mainProgram0800",
   "line": "0 = no blitz cost in this variant ..."
  },
  "50D6": {
   "unit": "game/mainProgram0800",
   "line": "... patch the LDA # at $512D"
  },
  "50D9": {
   "unit": "game/mainProgram0800",
   "line": "keep everything but bit 7 ..."
  },
  "50DB": {
   "unit": "game/mainProgram0800",
   "line": "... patch the AND # at $514B (bit 7 = the group is cloaked)"
  },
  "50DE": {
   "unit": "game/mainProgram0800",
   "line": "patch the ORA # at $514D: $00 for cloak off, $80 for cloak on"
  },
  "50E1": {
   "unit": "game/mainProgram0800",
   "line": "low byte of unitPathColTable ($F898), the cloak table"
  },
  "50E3": {
   "unit": "game/mainProgram0800",
   "line": "its high byte"
  },
  "50E5": {
   "unit": "game/mainProgram0800",
   "line": "which variant was requested?"
  },
  "50E7": {
   "unit": "game/mainProgram0800",
   "line": "cloak off: the members' bits are cleared (OR $00)"
  },
  "50E9": {
   "unit": "game/mainProgram0800",
   "line": "cloak on: OR $C0 into every member's waypoint byte"
  },
  "50EE": {
   "unit": "game/mainProgram0800",
   "line": "patch the low byte of the LDA abs,y at $5134 ..."
  },
  "50F1": {
   "unit": "game/mainProgram0800",
   "line": "... and of the STA abs,y at $513B"
  },
  "50F4": {
   "unit": "game/mainProgram0800",
   "line": "patch the high byte of the LDA at $5135 ..."
  },
  "50F7": {
   "unit": "game/mainProgram0800",
   "line": "... and of the STA at $513C ($F898 cloak or $F8FC blitz)"
  },
  "50FA": {
   "unit": "game/mainProgram0800",
   "line": "cmdParam0 = the group key argument"
  },
  "50FD": {
   "unit": "game/mainProgram0800",
   "line": "the group status AND mask that was patched in"
  },
  "5100": {
   "unit": "game/mainProgram0800",
   "line": "$7F means the cloak variant is running"
  },
  "5102": {
   "unit": "game/mainProgram0800",
   "line": "blitz: no redraw needed, cloaking is what changes the picture"
  },
  "5104": {
   "unit": "game/mainProgram0800",
   "line": "did this command come from the opponent?"
  },
  "5106": {
   "unit": "game/mainProgram0800",
   "line": "yes: the opponent's cloaking must not redraw our screen"
  },
  "5108": {
   "unit": "game/mainProgram0800",
   "line": "our own group ..."
  },
  "510A": {
   "unit": "game/mainProgram0800",
   "line": "... is handed to the tick loop to be redrawn"
  },
  "510D": {
   "unit": "game/mainProgram0800",
   "line": "the group key"
  },
  "510F": {
   "unit": "game/mainProgram0800",
   "line": "add the in-a-group bit so it matches unitFlagsTable & $7C"
  },
  "5111": {
   "unit": "game/mainProgram0800",
   "line": "and keep it there for the loop"
  },
  "5113": {
   "unit": "game/mainProgram0800",
   "line": "scan unit slots 99 down to 0"
  },
  "5118": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = dead or empty, skip it"
  },
  "511A": {
   "unit": "game/mainProgram0800",
   "line": "authoritative flags"
  },
  "511D": {
   "unit": "game/mainProgram0800",
   "line": "group number, side and member bit"
  },
  "511F": {
   "unit": "game/mainProgram0800",
   "line": "a member of the group named by the command?"
  },
  "5121": {
   "unit": "game/mainProgram0800",
   "line": "no: next slot"
  },
  "5123": {
   "unit": "game/mainProgram0800",
   "line": "type byte again"
  },
  "5126": {
   "unit": "game/mainProgram0800",
   "line": "keep only the type"
  },
  "5128": {
   "unit": "game/mainProgram0800",
   "line": "type 3 = SPY"
  },
  "512A": {
   "unit": "game/mainProgram0800",
   "line": "spies are exempt from group cloak and blitz orders"
  },
  "512C": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $50C4/$50D6: non-zero only for the blitz-on variant"
  },
  "512E": {
   "unit": "game/mainProgram0800",
   "line": "zero: no cost to charge"
  },
  "5130": {
   "unit": "game/mainProgram0800",
   "line": "blitz on: pay one energy point for 25 extra movement points"
  },
  "5133": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $50EE/$50F4: the member's cloak ($F898) or blitz ($F8FC) byte"
  },
  "5136": {
   "unit": "game/mainProgram0800",
   "line": "keep the waypoint coordinate in bits 0-5"
  },
  "5138": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $50EB: set both state bits, or none"
  },
  "513A": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $50F1/$50F7: store the byte back"
  },
  "513D": {
   "unit": "game/mainProgram0800",
   "line": "next lower slot"
  },
  "513E": {
   "unit": "game/mainProgram0800",
   "line": "loop until slot 0 has been examined"
  },
  "5142": {
   "unit": "game/mainProgram0800",
   "line": "group number and side ..."
  },
  "5144": {
   "unit": "game/mainProgram0800",
   "line": "... shifted down ..."
  },
  "5145": {
   "unit": "game/mainProgram0800",
   "line": "... to give the 0-15 index (group 0-7 of side 0, 8-15 of side 1)"
  },
  "5146": {
   "unit": "game/mainProgram0800",
   "line": "Y = index into groupStatusTable"
  },
  "5147": {
   "unit": "game/mainProgram0800",
   "line": "current status: speed class in bits 0-2, terrain class in bits 3-5, blitz bit 6, cloak bit 7"
  },
  "514A": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $50C9/$50DB: clear the bit this command owns"
  },
  "514C": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $50C1/$50DE: and set it if the order was 'on'"
  },
  "514E": {
   "unit": "game/mainProgram0800",
   "line": "store the new group status"
  },
  "5154": {
   "unit": "game/mainProgram0800",
   "line": "its waypoint column byte"
  },
  "5157": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = the comcen is cloaked right now"
  },
  "5159": {
   "unit": "game/mainProgram0800",
   "line": "the console and the overlays test this byte"
  },
  "515C": {
   "unit": "game/mainProgram0800",
   "line": "back to the dispatcher"
  }
 },
 "dataTypes": {
  "4C7E": [
   "byte",
   3
  ],
  "4C81": [
   "byte",
   3
  ],
  "4D2A": [
   "addr",
   78
  ],
  "4D78": [
   "byte",
   39
  ]
 },
 "zpComments": {
  "4A": "end address (lo) compared by advanceFillPointer ($C3F9); reused as the film-directory entry pointer, the trainer's plan column pointer and the 99..0 unit loop counter of moveGroupKeepingFormation ($4A52)",
  "69": "number of bytes already placed in mergedCmdBuffer while the own and the received packet are merged into one turn; also the index at which the second player's block starts",
  "6A": "read offset into the packet currently being merged; advancePacketCursor ($4F5E) sets it to the offset of the next command"
 }
}