{
 "chunk": "game_mainProgram0800_3023",
 "unit": "game/mainProgram0800",
 "unitLabels": {
  "game/mainProgram0800": {
   "3232": "spritePlotOperation",
   "3238": "circlePointIndex",
   "32DA": "circlePointX0",
   "32DB": "circlePointX1",
   "32DC": "circlePointX2",
   "32DD": "circlePointX3",
   "32DE": "circlePointX4",
   "32DF": "circlePointX5",
   "32E0": "circlePointX6",
   "32E1": "circlePointX7",
   "32E2": "circlePointY0",
   "32E3": "circlePointY1",
   "32E4": "circlePointY2",
   "32E5": "circlePointY3",
   "32E6": "circlePointY4",
   "32E7": "circlePointY5",
   "32E8": "circlePointY6",
   "32E9": "circlePointY7",
   "33C2": "explosionMarkerXOffset",
   "33C4": "explosionMarkerYOffset",
   "33C6": "explosionMarkerRingCount",
   "33C8": "showExplosionMarker",
   "3405": "startExplosionMarkerTimer",
   "341F": "updateExplosionMarker",
   "3424": "loadExplosionMarkerCol",
   "3436": "loadExplosionMarkerRow",
   "3458": "drawRingsInSprite4",
   "34C6": "applyMoveOrder",
   "34D5": "setUnitDestination",
   "355D": "clampMapCoord"
  }
 },
 "notes": {
  "3023": {
   "unit": "game/mainProgram0800",
   "routine": [
    "mapCellToSpriteXY - Converts a battlefield cell (X = column 0-39, Y = row 0-39) into the sprite",
    "coordinates that put a marker on that cell of the overview (mini) map in the left panel. While a",
    "group formation box is being dragged (groupBoxActive $920A bit 7 set) the cell is first shifted by",
    "the box's top-left offsets, so the formation ghost sprite is anchored at the corner of the box",
    "instead of at the cursor cell, and the result is nudged one pixel right and down to line up with",
    "the 4x4 pixel blocks the overview map is drawn with.",
    "In:  X = map column, Y = map row, groupBoxActive $920A, groupBoxLeftOffset $922F, groupBoxTopOffset $9231",
    "Out: X = sprite X coordinate, Y = sprite Y coordinate; A clobbered",
    "Called from: positionMarkerSprite ($2585), positionCursorSprite ($3055), the unit-follow code ($483F)"
   ],
   "line": "$920A = $FF while a group formation box is being dragged around the map"
  },
  "3026": {
   "unit": "game/mainProgram0800",
   "line": "branch if no group box is active: convert the cell as it stands"
  },
  "3028": {
   "unit": "game/mainProgram0800",
   "line": "group box: start from the requested map column"
  },
  "3029": {
   "unit": "game/mainProgram0800",
   "line": "clear carry for the offset addition"
  },
  "302A": {
   "unit": "game/mainProgram0800",
   "line": "$922F = column offset of the box's left edge from the cursor cell (may be negative)"
  },
  "302D": {
   "unit": "game/mainProgram0800",
   "line": "X = column of the top-left cell of the box"
  },
  "302E": {
   "unit": "game/mainProgram0800",
   "line": "same for the row"
  },
  "302F": {
   "unit": "game/mainProgram0800",
   "line": "clear carry for the row offset addition"
  },
  "3030": {
   "unit": "game/mainProgram0800",
   "line": "$9231 = row offset of the box's top edge from the cursor cell"
  },
  "3033": {
   "unit": "game/mainProgram0800",
   "line": "Y = row of the top-left cell of the box"
  },
  "3034": {
   "unit": "game/mainProgram0800",
   "line": "convert that corner cell to overview-map sprite coordinates"
  },
  "3037": {
   "unit": "game/mainProgram0800",
   "line": "+1 pixel in X: the 4x4 cell blocks are drawn one pixel into their cell"
  },
  "3038": {
   "unit": "game/mainProgram0800",
   "line": "+1 pixel in Y, likewise"
  },
  "3039": {
   "unit": "game/mainProgram0800",
   "line": "return with the shifted sprite coordinates"
  },
  "303A": {
   "unit": "game/mainProgram0800",
   "routine": [
    "mapCellToSpriteXYRaw - The plain cell-to-sprite conversion for the overview map: spriteX =",
    "column*4 + $1F, spriteY = row*4 + $39. The whole 40x40 battlefield is drawn in the left panel at",
    "4x4 pixels per cell starting at screen pixel (7,7); $1F and $39 are that origin plus the VIC sprite",
    "coordinate offsets ($18 horizontally, $32 vertically).",
    "In:  X = map column 0-39, Y = map row 0-39",
    "Out: X = sprite X coordinate, Y = sprite Y coordinate; A clobbered",
    "Called from: mapCellToSpriteXY ($3026/$3034), showExplosionMarker ($33E5), the shot-line setup ($3A70, $3A7B)"
   ],
   "line": "map column 0-39"
  },
  "303B": {
   "unit": "game/mainProgram0800",
   "line": "column*2"
  },
  "303C": {
   "unit": "game/mainProgram0800",
   "line": "column*4 - the overview map uses 4 pixels per cell"
  },
  "303D": {
   "unit": "game/mainProgram0800",
   "line": "clear carry before adding the map origin"
  },
  "303E": {
   "unit": "game/mainProgram0800",
   "line": "sprite X of overview column 0 = 24 (sprite origin) + 7 (left edge of the map)"
  },
  "3040": {
   "unit": "game/mainProgram0800",
   "line": "return the sprite X in X"
  },
  "3041": {
   "unit": "game/mainProgram0800",
   "line": "map row 0-39"
  },
  "3042": {
   "unit": "game/mainProgram0800",
   "line": "row*2"
  },
  "3043": {
   "unit": "game/mainProgram0800",
   "line": "row*4"
  },
  "3044": {
   "unit": "game/mainProgram0800",
   "line": "clear carry before adding the map origin"
  },
  "3045": {
   "unit": "game/mainProgram0800",
   "line": "sprite Y of overview row 0 = 50 (sprite origin) + 7 (top edge of the map)"
  },
  "3047": {
   "unit": "game/mainProgram0800",
   "line": "return the sprite Y in Y"
  },
  "3048": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "3049": {
   "unit": "game/mainProgram0800",
   "routine": [
    "positionCursorSprite - Puts the sprite named by activeSpriteIndex ($91CC) over the cursor cell of",
    "the overview map. The cursor cell is always the middle of the 7x5 tactical view, i.e. (viewOriginCol",
    "+ 3, viewOriginRow + 2). Nothing happens when activeSpriteIndex is negative (no cursor sprite).",
    "In:  viewOriginCol zp_A5, viewOriginRow zp_A6, activeSpriteIndex $91CC, group-box state",
    "Out: spriteXShadow/spriteYShadow of that sprite, spriteEnableShadow bit set; A/X/Y clobbered",
    "Called from: the viewport scroller ($1D46), battlefield entry ($2012), buildGroupFormationSprite",
    "     ($31A0, by JMP) and the map generator overlay ($83F2)"
   ],
   "line": "map column of the left edge of the 7x5 tactical view"
  },
  "304B": {
   "unit": "game/mainProgram0800",
   "line": "clear carry before stepping to the cursor column"
  },
  "304C": {
   "unit": "game/mainProgram0800",
   "line": "+3 = the middle column of the seven, where the cursor sits"
  },
  "304E": {
   "unit": "game/mainProgram0800",
   "line": "X = cursor column"
  },
  "304F": {
   "unit": "game/mainProgram0800",
   "line": "map row of the top edge of the view"
  },
  "3051": {
   "unit": "game/mainProgram0800",
   "line": "clear carry before stepping to the cursor row"
  },
  "3052": {
   "unit": "game/mainProgram0800",
   "line": "+2 = the middle row of the five"
  },
  "3054": {
   "unit": "game/mainProgram0800",
   "line": "Y = cursor row"
  },
  "3055": {
   "unit": "game/mainProgram0800",
   "line": "cell -> overview sprite coordinates (shifted when a group box is active)"
  },
  "3058": {
   "unit": "game/mainProgram0800",
   "line": "keep the sprite X byte"
  },
  "3059": {
   "unit": "game/mainProgram0800",
   "line": "$91CC = which sprite is currently acting as the cursor"
  },
  "305C": {
   "unit": "game/mainProgram0800",
   "line": "negative = no cursor sprite wanted, leave everything alone"
  },
  "305E": {
   "unit": "game/mainProgram0800",
   "line": "$9010+n: shadow of the VIC sprite X register, copied out by the raster IRQ"
  },
  "3061": {
   "unit": "game/mainProgram0800",
   "line": "sprite Y"
  },
  "3062": {
   "unit": "game/mainProgram0800",
   "line": "$9018+n: shadow of the VIC sprite Y register"
  },
  "3065": {
   "unit": "game/mainProgram0800",
   "line": "$2F85[n] = 1 << n, the enable bit of this sprite"
  },
  "3068": {
   "unit": "game/mainProgram0800",
   "line": "turn the sprite on (unless sprites are suppressed)"
  },
  "306B": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "306C": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawLineInPathSprite - Draws a straight line into the shape of sprite 3 ($8AC0), which is the game's",
    "path/shot-line sprite. It uses the resident high-memory Bresenham stepper ($FBB8 init, $FBFA step),",
    "but here its zp_92-zp_95 coordinates are pixel positions inside the 24x21 sprite, not map cells.",
    "A negative argument makes the walker skip two pixels after every plotted one, giving a dotted line",
    "(used for a BOOMER's arcing shot).",
    "In:  A = 0 solid / $80 dotted; lineCurRow/Col zp_92/zp_93 = start pixel, lineEndRow/Col zp_94/zp_95 = end pixel",
    "Out: pixels set in $8AC0-$8AFF; the line-walker state is consumed, zp_92/zp_93 left at the end point",
    "Called from: the shot-line builder ($3B11) and the drone heading indicator in overlay B ($8266)"
   ],
   "line": "patch the LDA # at $307C with the dot flag: 0 = solid, $80 = dotted"
  },
  "306F": {
   "unit": "game/mainProgram0800",
   "line": "$FBB8: set up the Bresenham walk from (zp_93,zp_92) to (zp_95,zp_94)"
  },
  "3072": {
   "unit": "game/mainProgram0800",
   "block": [
    "Walk the line one pixel at a time, plotting into sprite 3."
   ],
   "line": "set the pixel the walker is standing on"
  },
  "3075": {
   "unit": "game/mainProgram0800",
   "line": "steps still to be taken"
  },
  "3077": {
   "unit": "game/mainProgram0800",
   "line": "branch when the end point has been reached"
  },
  "3079": {
   "unit": "game/mainProgram0800",
   "line": "$FBFA: advance the walker one pixel"
  },
  "307C": {
   "unit": "game/mainProgram0800",
   "line": "dot flag patched at $306C"
  },
  "307E": {
   "unit": "game/mainProgram0800",
   "line": "solid line: plot every pixel"
  },
  "3080": {
   "unit": "game/mainProgram0800",
   "line": "dotted line: skip this pixel..."
  },
  "3083": {
   "unit": "game/mainProgram0800",
   "line": "...and the next one, so only every third pixel is drawn"
  },
  "3086": {
   "unit": "game/mainProgram0800",
   "line": "plot the pixel we landed on"
  },
  "3089": {
   "unit": "game/mainProgram0800",
   "line": "line finished"
  },
  "308A": {
   "unit": "game/mainProgram0800",
   "routine": [
    "plotPathCross - Marks the end of a path/shot line by plotting the four orthogonal neighbours of the",
    "walker's current pixel into sprite 3: left, up, down and (by falling through into",
    "plotPathSpritePixel) right. Together with the line's own last pixel this makes a small cross over",
    "the target.",
    "In:  lineCurRow zp_92, lineCurCol zp_93 = the pixel to mark",
    "Out: 4 more pixels set in $8AC0; zp_93 left one greater than on entry",
    "Called from: the shot-line builder ($3B14), immediately after drawLineInPathSprite"
   ],
   "line": "one pixel to the left of the end point"
  },
  "308C": {
   "unit": "game/mainProgram0800",
   "line": "plot it"
  },
  "308F": {
   "unit": "game/mainProgram0800",
   "line": "back to the end column"
  },
  "3091": {
   "unit": "game/mainProgram0800",
   "line": "one pixel above the end point"
  },
  "3093": {
   "unit": "game/mainProgram0800",
   "line": "plot it"
  },
  "3096": {
   "unit": "game/mainProgram0800",
   "line": "back to the end row"
  },
  "3098": {
   "unit": "game/mainProgram0800",
   "line": "one pixel below the end point"
  },
  "309A": {
   "unit": "game/mainProgram0800",
   "line": "plot it"
  },
  "309D": {
   "unit": "game/mainProgram0800",
   "line": "back to the end row"
  },
  "309F": {
   "unit": "game/mainProgram0800",
   "line": "one pixel to the right - fall through and plot that one too"
  },
  "30A1": {
   "unit": "game/mainProgram0800",
   "routine": [
    "plotPathSpritePixel - Sets one pixel in the shape of sprite 3 at $8AC0. A sprite row is 3 bytes, so",
    "the byte index is row*3 + column/8 and the bit is taken from the $80>>n mask table at $C008. The",
    "column/8 part is patched into the ADC # at $30B3 rather than added with a register.",
    "In:  lineCurRow zp_92 = pixel row 0-20, lineCurCol zp_93 = pixel column 0-23",
    "Out: one bit set in $8AC0-$8AFF; A/X/Y clobbered, the operand at $30B4 patched",
    "Called from: drawLineInPathSprite ($3072), plotPathCross ($308C/$3093/$309A and by fall-through),",
    "     and directly by overlay B ($7852, $785E)"
   ],
   "line": "pixel column 0-23 inside the sprite"
  },
  "30A3": {
   "unit": "game/mainProgram0800",
   "line": "column/2"
  },
  "30A4": {
   "unit": "game/mainProgram0800",
   "line": "column/4"
  },
  "30A5": {
   "unit": "game/mainProgram0800",
   "line": "column/8 = which of the three bytes of a sprite row holds the pixel"
  },
  "30A6": {
   "unit": "game/mainProgram0800",
   "line": "patch the ADC # at $30B3 with that byte offset"
  },
  "30A9": {
   "unit": "game/mainProgram0800",
   "line": "the column again"
  },
  "30AB": {
   "unit": "game/mainProgram0800",
   "line": "bit number 0-7 inside the byte"
  },
  "30AD": {
   "unit": "game/mainProgram0800",
   "line": "index into the bit mask table"
  },
  "30AE": {
   "unit": "game/mainProgram0800",
   "line": "pixel row 0-20"
  },
  "30B0": {
   "unit": "game/mainProgram0800",
   "line": "row*2"
  },
  "30B1": {
   "unit": "game/mainProgram0800",
   "line": "row*3 = offset of the start of that sprite row (3 bytes per row)"
  },
  "30B3": {
   "unit": "game/mainProgram0800",
   "line": "+ byte within the row, patched at $30A6"
  },
  "30B5": {
   "unit": "game/mainProgram0800",
   "line": "Y = byte offset inside the 63-byte shape"
  },
  "30B6": {
   "unit": "game/mainProgram0800",
   "line": "$8AC0 = shape of sprite 3, the path/shot line sprite"
  },
  "30B9": {
   "unit": "game/mainProgram0800",
   "line": "$C008[bit] = $80 >> bit"
  },
  "30BC": {
   "unit": "game/mainProgram0800",
   "line": "set the pixel"
  },
  "30BF": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "30C0": {
   "unit": "game/mainProgram0800",
   "routine": [
    "buildGroupFormationSprite - Builds the 'formation ghost' shown while a whole group is being ordered",
    "to a new place: every living member of the selected group is drawn as one 2x2-pixel dot into sprite",
    "0, laid out exactly as the group's ordered destinations are laid out on the map. Because sprite 0",
    "is X- and Y-expanded, one dot covers the 4x4 pixels the overview map gives a cell. The bounding box",
    "of the destinations is turned into offsets relative to the dragged unit, so the ghost follows the",
    "cursor, groupBoxActive is set, the view is scrolled until the box fits on the map and the ghost is",
    "positioned.",
    "In:  selectedGroupKey $9206, selectedUnit $90F8, unit arrays $F76C/$FB54/$F7D0/$F834",
    "Out: sprite 0 shape, spriteBuildBuffer $9025, groupBox offsets $922E-$9231, groupBoxActive $920A = $FF,",
    "     spritePlotMode $91F3 = $40, X/Y expand shadows bits 0-1 set, view origin possibly moved",
    "Called from: runTargetCursorLoop ($21A2), when the group being ordered has 2 or more members"
   ],
   "line": "$9206 = group id bits of the group being ordered"
  },
  "30C3": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = measure the members' ordered destinations, not their current cells"
  },
  "30C5": {
   "unit": "game/mainProgram0800",
   "line": "$4B5E -> zp_18/zp_19 = min column/row, zp_1B/zp_1C = max column/row"
  },
  "30C8": {
   "unit": "game/mainProgram0800",
   "line": "clear the 64-byte shape build buffer"
  },
  "30CA": {
   "unit": "game/mainProgram0800",
   "line": "with zeroes"
  },
  "30CC": {
   "unit": "game/mainProgram0800",
   "line": "$9025 = scratch sprite shape used while the ghost is assembled"
  },
  "30CF": {
   "unit": "game/mainProgram0800",
   "line": "next byte down"
  },
  "30D0": {
   "unit": "game/mainProgram0800",
   "line": "loop over all 64 bytes"
  },
  "30D2": {
   "unit": "game/mainProgram0800",
   "line": "bit 6 = 'set the pixel'"
  },
  "30D4": {
   "unit": "game/mainProgram0800",
   "line": "$91F3 tells plotSpritePixel which operation to use"
  },
  "30D7": {
   "unit": "game/mainProgram0800",
   "line": "max column of the group's destinations"
  },
  "30D9": {
   "unit": "game/mainProgram0800",
   "line": "$922E = right edge of the group box"
  },
  "30DC": {
   "unit": "game/mainProgram0800",
   "line": "min column"
  },
  "30DE": {
   "unit": "game/mainProgram0800",
   "line": "$922F = left edge of the group box"
  },
  "30E1": {
   "unit": "game/mainProgram0800",
   "line": "max row"
  },
  "30E3": {
   "unit": "game/mainProgram0800",
   "line": "$9230 = bottom edge of the group box"
  },
  "30E6": {
   "unit": "game/mainProgram0800",
   "line": "min row"
  },
  "30E8": {
   "unit": "game/mainProgram0800",
   "line": "$9231 = top edge of the group box"
  },
  "30EB": {
   "unit": "game/mainProgram0800",
   "line": "group id bits again"
  },
  "30EE": {
   "unit": "game/mainProgram0800",
   "line": "bit 6 = 'this unit is in a group'"
  },
  "30F0": {
   "unit": "game/mainProgram0800",
   "line": "the value unitDisplayFlags bits 2-6 must have for a member of this group"
  },
  "30F2": {
   "unit": "game/mainProgram0800",
   "line": "start at unit 99, the last unit record"
  },
  "30F4": {
   "unit": "game/mainProgram0800",
   "line": "keep the loop counter in memory (X is needed for the pixel plotter)"
  },
  "30F6": {
   "unit": "game/mainProgram0800",
   "block": [
    "Walk units 99..0 and plot a 2x2 dot for every living member of the group at its",
    "ordered destination, relative to the top-left corner of the group box."
   ],
   "line": "$F76C[unit] = type byte, bit 7 set = destroyed"
  },
  "30F9": {
   "unit": "game/mainProgram0800",
   "line": "skip dead units"
  },
  "30FB": {
   "unit": "game/mainProgram0800",
   "line": "$FB54[unit] = local flags copy"
  },
  "30FE": {
   "unit": "game/mainProgram0800",
   "line": "keep the group id (bits 2-4) and the in-a-group bit (6)"
  },
  "3100": {
   "unit": "game/mainProgram0800",
   "line": "does it belong to the group being ordered?"
  },
  "3102": {
   "unit": "game/mainProgram0800",
   "line": "no - try the next unit"
  },
  "3104": {
   "unit": "game/mainProgram0800",
   "line": "$F834[unit] = ordered destination row, bit 7 = already standing there"
  },
  "3107": {
   "unit": "game/mainProgram0800",
   "line": "drop that flag, keep the row"
  },
  "3109": {
   "unit": "game/mainProgram0800",
   "line": "set carry for the subtraction"
  },
  "310A": {
   "unit": "game/mainProgram0800",
   "line": "row inside the group box"
  },
  "310D": {
   "unit": "game/mainProgram0800",
   "line": "x2: two sprite pixels per map cell (the sprite is Y-expanded)"
  },
  "310E": {
   "unit": "game/mainProgram0800",
   "line": "Y = pixel row of this member's dot"
  },
  "310F": {
   "unit": "game/mainProgram0800",
   "line": "stash it, plotSpritePixel needs Y for each of the four pixels"
  },
  "3111": {
   "unit": "game/mainProgram0800",
   "line": "$F7D0[unit] = ordered destination column"
  },
  "3114": {
   "unit": "game/mainProgram0800",
   "line": "drop the 'already there' flag"
  },
  "3116": {
   "unit": "game/mainProgram0800",
   "line": "set carry for the subtraction"
  },
  "3117": {
   "unit": "game/mainProgram0800",
   "line": "column inside the group box"
  },
  "311A": {
   "unit": "game/mainProgram0800",
   "line": "x2: two sprite pixels per map cell (the sprite is X-expanded)"
  },
  "311B": {
   "unit": "game/mainProgram0800",
   "line": "X = pixel column of this member's dot"
  },
  "311C": {
   "unit": "game/mainProgram0800",
   "line": "stash it too"
  },
  "311E": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 set = plot into spriteBuildBuffer $9025 instead of a real sprite"
  },
  "3120": {
   "unit": "game/mainProgram0800",
   "line": "top-left pixel of the 2x2 dot"
  },
  "3123": {
   "unit": "game/mainProgram0800",
   "line": "pixel column again"
  },
  "3125": {
   "unit": "game/mainProgram0800",
   "line": "one pixel to the right"
  },
  "3126": {
   "unit": "game/mainProgram0800",
   "line": "same pixel row"
  },
  "3128": {
   "unit": "game/mainProgram0800",
   "line": "again into the build buffer"
  },
  "312A": {
   "unit": "game/mainProgram0800",
   "line": "top-right pixel"
  },
  "312D": {
   "unit": "game/mainProgram0800",
   "line": "pixel column"
  },
  "312F": {
   "unit": "game/mainProgram0800",
   "line": "pixel row"
  },
  "3131": {
   "unit": "game/mainProgram0800",
   "line": "one pixel down"
  },
  "3132": {
   "unit": "game/mainProgram0800",
   "line": "build buffer"
  },
  "3134": {
   "unit": "game/mainProgram0800",
   "line": "bottom-left pixel"
  },
  "3137": {
   "unit": "game/mainProgram0800",
   "line": "pixel column"
  },
  "3139": {
   "unit": "game/mainProgram0800",
   "line": "one right"
  },
  "313A": {
   "unit": "game/mainProgram0800",
   "line": "pixel row"
  },
  "313C": {
   "unit": "game/mainProgram0800",
   "line": "one down"
  },
  "313D": {
   "unit": "game/mainProgram0800",
   "line": "build buffer"
  },
  "313F": {
   "unit": "game/mainProgram0800",
   "line": "bottom-right pixel - the dot is complete"
  },
  "3142": {
   "unit": "game/mainProgram0800",
   "line": "step to the next unit"
  },
  "3144": {
   "unit": "game/mainProgram0800",
   "line": "reload the loop counter"
  },
  "3146": {
   "unit": "game/mainProgram0800",
   "line": "keep going until unit 0 has been handled"
  },
  "3148": {
   "unit": "game/mainProgram0800",
   "line": "64 bytes to copy"
  },
  "314A": {
   "unit": "game/mainProgram0800",
   "block": [
    "Publish the finished shape and set up the sprite geometry."
   ],
   "line": "the assembled ghost"
  },
  "314D": {
   "unit": "game/mainProgram0800",
   "line": "$8A00 = shape of sprite 0"
  },
  "3150": {
   "unit": "game/mainProgram0800",
   "line": "next byte down"
  },
  "3151": {
   "unit": "game/mainProgram0800",
   "line": "loop"
  },
  "3153": {
   "unit": "game/mainProgram0800",
   "line": "$9023 = shadow of VIC_SPR_EXP_X"
  },
  "3156": {
   "unit": "game/mainProgram0800",
   "line": "double the width of sprites 0 and 1"
  },
  "3158": {
   "unit": "game/mainProgram0800",
   "line": "store the shadow back"
  },
  "315B": {
   "unit": "game/mainProgram0800",
   "line": "$9024 = shadow of VIC_SPR_EXP_Y"
  },
  "315E": {
   "unit": "game/mainProgram0800",
   "line": "double the height too, so one 2x2 dot covers a whole 4x4 overview cell"
  },
  "3160": {
   "unit": "game/mainProgram0800",
   "line": "store the shadow back"
  },
  "3163": {
   "unit": "game/mainProgram0800",
   "block": [
    "Turn the absolute box corners into offsets from the dragged unit's destination so that",
    "mapCellToSpriteXY can keep the box under the cursor while the player scrolls."
   ],
   "line": "$90F8 = the unit the player is actually dragging"
  },
  "3166": {
   "unit": "game/mainProgram0800",
   "line": "its ordered destination column"
  },
  "3169": {
   "unit": "game/mainProgram0800",
   "line": "without the 'already there' flag"
  },
  "316B": {
   "unit": "game/mainProgram0800",
   "line": "keep it"
  },
  "316D": {
   "unit": "game/mainProgram0800",
   "line": "its ordered destination row"
  },
  "3170": {
   "unit": "game/mainProgram0800",
   "line": "without the flag"
  },
  "3172": {
   "unit": "game/mainProgram0800",
   "line": "keep it"
  },
  "3174": {
   "unit": "game/mainProgram0800",
   "line": "left edge of the box"
  },
  "3177": {
   "unit": "game/mainProgram0800",
   "line": "set carry for the subtraction"
  },
  "3178": {
   "unit": "game/mainProgram0800",
   "line": "minus the dragged unit's column"
  },
  "317A": {
   "unit": "game/mainProgram0800",
   "line": "$922F is now an offset relative to the cursor"
  },
  "317D": {
   "unit": "game/mainProgram0800",
   "line": "right edge"
  },
  "3180": {
   "unit": "game/mainProgram0800",
   "line": "set carry for the subtraction"
  },
  "3181": {
   "unit": "game/mainProgram0800",
   "line": "minus the dragged unit's column"
  },
  "3183": {
   "unit": "game/mainProgram0800",
   "line": "$922E likewise"
  },
  "3186": {
   "unit": "game/mainProgram0800",
   "line": "top edge"
  },
  "3189": {
   "unit": "game/mainProgram0800",
   "line": "set carry for the subtraction"
  },
  "318A": {
   "unit": "game/mainProgram0800",
   "line": "minus the dragged unit's row"
  },
  "318C": {
   "unit": "game/mainProgram0800",
   "line": "$9231 likewise"
  },
  "318F": {
   "unit": "game/mainProgram0800",
   "line": "bottom edge"
  },
  "3192": {
   "unit": "game/mainProgram0800",
   "line": "set carry for the subtraction"
  },
  "3193": {
   "unit": "game/mainProgram0800",
   "line": "minus the dragged unit's row"
  },
  "3195": {
   "unit": "game/mainProgram0800",
   "line": "$9230 likewise"
  },
  "3198": {
   "unit": "game/mainProgram0800",
   "line": "flag value"
  },
  "319A": {
   "unit": "game/mainProgram0800",
   "line": "$920A: from now on mapCellToSpriteXY applies the box offsets"
  },
  "319D": {
   "unit": "game/mainProgram0800",
   "line": "$1F4E: scroll the view until the whole box fits inside the 40x40 map"
  },
  "31A0": {
   "unit": "game/mainProgram0800",
   "line": "place the ghost over the cursor cell and switch it on, then return to the caller"
  },
  "31A3": {
   "unit": "game/mainProgram0800",
   "routine": [
    "enableSprites - ORs the sprite mask in A into spriteEnableShadow ($9022), the shadow the raster IRQ",
    "copies into VIC_SPR_ENA. The store is skipped while comcenStunStatus ($922A) bit 6 marks the comcen",
    "knock-out blackout round, so no sprite can reappear during it; A still returns the combined mask.",
    "In:  A = sprite bit mask (1 << sprite number)",
    "Out: spriteEnableShadow updated unless suppressed; A = old shadow OR the mask",
    "Called from: everywhere a cursor, marker or ghost is switched on - $2539, $2566, $2FF0, $3068,",
    "     $333D, $3380, $33AE, $3402, $344A, $3B19, $3D04, $484A, the $C000 overlay and both $6F00 overlays"
   ],
   "line": "$9022 = shadow of VIC_SPR_ENA, one bit per sprite"
  },
  "31A6": {
   "unit": "game/mainProgram0800",
   "line": "$922A = comcen stun/knock-out status"
  },
  "31A9": {
   "unit": "game/mainProgram0800",
   "line": "bit 6 set = knock-out blackout round: keep every sprite off"
  },
  "31AB": {
   "unit": "game/mainProgram0800",
   "line": "the raster IRQ writes this to $D015 next frame"
  },
  "31AE": {
   "unit": "game/mainProgram0800",
   "line": "A holds the combined mask either way"
  },
  "31AF": {
   "unit": "game/mainProgram0800",
   "routine": [
    "plotSpritePixel - Sets, clears or toggles one pixel of a sprite shape. The operation is chosen by",
    "spritePlotMode ($91F3): bit 7 = toggle (EOR), bit 6 = set (ORA), neither = clear (AND with the",
    "inverted mask). It works by patching the opcode and the mask of the read-modify-write instruction",
    "at $3232. A negative sprite number selects the 64-byte build buffer at $9025 instead of the real",
    "sprite data at $8A00 + n*64.",
    "In:  A = sprite number 0-7 (bit 7 set = use $9025), X = pixel column 0-23, Y = pixel row 0-20,",
    "     spritePlotMode $91F3",
    "Out: one pixel modified; spritePlotPtr zp_98/zp_99 = byte pointer, spritePlotWork clobbered;",
    "     self-modifies $31EE (sprite number), $3232 (opcode) and $3233 (mask)",
    "Called from: plotCircleOctantPoints ($32CE) and, through setSpritePixel, buildGroupFormationSprite"
   ],
   "line": "$91F3 selects the operation"
  },
  "31B2": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 set = toggle the pixel"
  },
  "31B4": {
   "unit": "game/mainProgram0800",
   "line": "bit 6 set = set the pixel"
  },
  "31B6": {
   "unit": "game/mainProgram0800",
   "line": "clear mode: stash the sprite number in the LDX # operand at $31EE"
  },
  "31B9": {
   "unit": "game/mainProgram0800",
   "line": "opcode of AND #imm"
  },
  "31BB": {
   "unit": "game/mainProgram0800",
   "line": "patch the read-modify-write instruction at $3232"
  },
  "31BE": {
   "unit": "game/mainProgram0800",
   "line": "always taken ($29 is never zero)"
  },
  "31C0": {
   "unit": "game/mainProgram0800",
   "line": "toggle mode: stash the sprite number"
  },
  "31C3": {
   "unit": "game/mainProgram0800",
   "line": "opcode of EOR #imm"
  },
  "31C5": {
   "unit": "game/mainProgram0800",
   "line": "patch the operation"
  },
  "31C8": {
   "unit": "game/mainProgram0800",
   "line": "always taken"
  },
  "31CA": {
   "unit": "game/mainProgram0800",
   "routine": [
    "setSpritePixel - Entry point of plotSpritePixel that forces the ORA (set) operation. Note that the",
    "mask is still inverted further down unless spritePlotMode has bit 6 or 7 set, so callers set",
    "spritePlotMode = $40 first; buildGroupFormationSprite does exactly that.",
    "In:  A = sprite number (bit 7 = build buffer), X = pixel column, Y = pixel row",
    "Out: the pixel is set",
    "Called from: buildGroupFormationSprite ($3120, $312A, $3134, $313F) and by branch from $31B4"
   ],
   "line": "stash the sprite number in the LDX # operand at $31EE"
  },
  "31CD": {
   "unit": "game/mainProgram0800",
   "line": "opcode of ORA #imm"
  },
  "31CF": {
   "unit": "game/mainProgram0800",
   "line": "patch the operation at $3232"
  },
  "31D2": {
   "unit": "game/mainProgram0800",
   "block": [
    "Shared body: build a pointer to the byte holding the pixel and the bit mask to apply."
   ],
   "line": "keep the pixel column"
  },
  "31D5": {
   "unit": "game/mainProgram0800",
   "line": "the sprite number stashed above"
  },
  "31D8": {
   "unit": "game/mainProgram0800",
   "line": "0-7 = a real sprite"
  },
  "31DA": {
   "unit": "game/mainProgram0800",
   "line": "negative: use the scratch shape buffer at $9025..."
  },
  "31DC": {
   "unit": "game/mainProgram0800",
   "line": "low byte of the pointer"
  },
  "31DE": {
   "unit": "game/mainProgram0800",
   "line": "high byte $90"
  },
  "31E0": {
   "unit": "game/mainProgram0800",
   "line": "pointer = $9025"
  },
  "31E2": {
   "unit": "game/mainProgram0800",
   "line": "...which needs no per-sprite offset"
  },
  "31E5": {
   "unit": "game/mainProgram0800",
   "line": "real sprite: base of the sprite data block"
  },
  "31E7": {
   "unit": "game/mainProgram0800",
   "line": "low byte"
  },
  "31E9": {
   "unit": "game/mainProgram0800",
   "line": "high byte $8A"
  },
  "31EB": {
   "unit": "game/mainProgram0800",
   "line": "pointer = $8A00"
  },
  "31ED": {
   "unit": "game/mainProgram0800",
   "line": "sprite number, patched at $31B6/$31C0/$31CA"
  },
  "31EF": {
   "unit": "game/mainProgram0800",
   "line": "add the sprite's 64-byte offset"
  },
  "31F1": {
   "unit": "game/mainProgram0800",
   "line": "clear carry for the 16-bit addition of the sprite offset"
  },
  "31F2": {
   "unit": "game/mainProgram0800",
   "line": "$2F95[n] = low byte of n*64"
  },
  "31F5": {
   "unit": "game/mainProgram0800",
   "line": "store it back"
  },
  "31F7": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the pointer"
  },
  "31F9": {
   "unit": "game/mainProgram0800",
   "line": "$2F9D[n] = high byte of n*64"
  },
  "31FC": {
   "unit": "game/mainProgram0800",
   "line": "pointer now addresses the shape of sprite n"
  },
  "31FE": {
   "unit": "game/mainProgram0800",
   "line": "the pixel column"
  },
  "3201": {
   "unit": "game/mainProgram0800",
   "line": "keep a copy in X"
  },
  "3202": {
   "unit": "game/mainProgram0800",
   "line": "bit number inside the byte"
  },
  "3204": {
   "unit": "game/mainProgram0800",
   "line": "save it for the mask lookup"
  },
  "3207": {
   "unit": "game/mainProgram0800",
   "line": "column again"
  },
  "3208": {
   "unit": "game/mainProgram0800",
   "line": "column/2"
  },
  "3209": {
   "unit": "game/mainProgram0800",
   "line": "column/4"
  },
  "320A": {
   "unit": "game/mainProgram0800",
   "line": "column/8 = byte 0, 1 or 2 of a sprite row"
  },
  "320B": {
   "unit": "game/mainProgram0800",
   "line": "clear carry before folding the byte offset into the pointer"
  },
  "320C": {
   "unit": "game/mainProgram0800",
   "line": "fold it into the pointer"
  },
  "320E": {
   "unit": "game/mainProgram0800",
   "line": "low byte"
  },
  "3210": {
   "unit": "game/mainProgram0800",
   "line": "no carry out"
  },
  "3212": {
   "unit": "game/mainProgram0800",
   "line": "carry into the high byte"
  },
  "3214": {
   "unit": "game/mainProgram0800",
   "line": "bit number"
  },
  "3217": {
   "unit": "game/mainProgram0800",
   "line": "$C008[bit] = $80 >> bit"
  },
  "321A": {
   "unit": "game/mainProgram0800",
   "line": "which operation was chosen?"
  },
  "321D": {
   "unit": "game/mainProgram0800",
   "line": "toggle: EOR with the plain mask"
  },
  "321F": {
   "unit": "game/mainProgram0800",
   "line": "set: ORA with the plain mask"
  },
  "3221": {
   "unit": "game/mainProgram0800",
   "line": "clear: AND with the inverted mask"
  },
  "3223": {
   "unit": "game/mainProgram0800",
   "line": "patch the immediate operand of the operation at $3232"
  },
  "3226": {
   "unit": "game/mainProgram0800",
   "line": "pixel row 0-20"
  },
  "3227": {
   "unit": "game/mainProgram0800",
   "line": "keep it"
  },
  "322A": {
   "unit": "game/mainProgram0800",
   "line": "row*2"
  },
  "322B": {
   "unit": "game/mainProgram0800",
   "line": "clear carry for row*2 + row"
  },
  "322C": {
   "unit": "game/mainProgram0800",
   "line": "row*3 = byte index of that sprite row (3 bytes per row)"
  },
  "322F": {
   "unit": "game/mainProgram0800",
   "line": "Y = index into the shape"
  },
  "3230": {
   "unit": "game/mainProgram0800",
   "line": "read the shape byte"
  },
  "3232": {
   "unit": "game/mainProgram0800",
   "line": "patched: ORA/AND/EOR with the patched mask"
  },
  "3234": {
   "unit": "game/mainProgram0800",
   "line": "write the modified byte back"
  },
  "3236": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "3237": {
   "unit": "game/mainProgram0800",
   "block": [
    "Two work bytes living inside the code."
   ],
   "line": "scratch used by plotSpritePixel: pixel column, then bit number, then pixel row"
  },
  "3238": {
   "unit": "game/mainProgram0800",
   "line": "which of the eight mirrored circle points plotCircleOctantPoints is plotting (7 down to 0)"
  },
  "3239": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawCircleInSprite - Draws a circle of radius X (1-10) centred on pixel (12,10) - the middle of a",
    "24x21 sprite - into sprite A, using 8-way octant symmetry. The dx/dy point lists of each radius live",
    "in $9F69.. and are reached through the pointer tables at $9F37/$9F41 (dx) and $9F4B/$9F55 (dy); the",
    "index of the last point of the octant comes from $9F5F. spritePlotMode decides whether the pixels",
    "are set, cleared or toggled. Radius 1 degenerates into a filled 3x3 dot.",
    "In:  A = sprite number (bit 7 = build buffer), X = radius 1-10, spritePlotMode $91F3",
    "Out: the circle is plotted; activeSpriteIndex $91CC = A, circleStepsLeft $91F2 = $FF,",
    "     circleDxPtr/circleDyPtr zp_AD-zp_B0 point at the two lists",
    "Called from: drawRingsInSprite4 ($3471), the unit blob markers ($3D13, $3D43) and overlay B's",
    "     range rings ($7190-$71DA)"
   ],
   "line": "$91CC: plotCircleOctantPoints will plot into this sprite"
  },
  "323C": {
   "unit": "game/mainProgram0800",
   "line": "the tables are indexed by radius-1"
  },
  "323D": {
   "unit": "game/mainProgram0800",
   "line": "$9F37[r-1] = low byte of the dx list"
  },
  "3240": {
   "unit": "game/mainProgram0800",
   "line": "zp_AD"
  },
  "3242": {
   "unit": "game/mainProgram0800",
   "line": "$9F41[r-1] = high byte (always $9F)"
  },
  "3245": {
   "unit": "game/mainProgram0800",
   "line": "zp_AE"
  },
  "3247": {
   "unit": "game/mainProgram0800",
   "line": "$9F4B[r-1] = low byte of the dy list"
  },
  "324A": {
   "unit": "game/mainProgram0800",
   "line": "zp_AF"
  },
  "324C": {
   "unit": "game/mainProgram0800",
   "line": "$9F55[r-1] = high byte"
  },
  "324F": {
   "unit": "game/mainProgram0800",
   "line": "zp_B0"
  },
  "3251": {
   "unit": "game/mainProgram0800",
   "line": "$9F5F[r-1] = index of the last point of the octant"
  },
  "3254": {
   "unit": "game/mainProgram0800",
   "line": "$91F2 = octant point counter"
  },
  "3257": {
   "unit": "game/mainProgram0800",
   "block": [
    "Walk the octant point list from the last entry down to entry 0."
   ],
   "line": "mirror point Y into the 8 symmetric coordinates"
  },
  "325A": {
   "unit": "game/mainProgram0800",
   "line": "plot those 8 pixels"
  },
  "325D": {
   "unit": "game/mainProgram0800",
   "line": "previous point of the octant"
  },
  "3260": {
   "unit": "game/mainProgram0800",
   "line": "reload it as the index"
  },
  "3263": {
   "unit": "game/mainProgram0800",
   "line": "loop until point 0 has been plotted"
  },
  "3265": {
   "unit": "game/mainProgram0800",
   "line": "circle complete"
  },
  "3266": {
   "unit": "game/mainProgram0800",
   "routine": [
    "computeCircleOctantPoints - Expands one octant point (dx,dy) of a circle into the coordinates of all",
    "eight mirrored points, writing them into circlePointX0-7 ($32DA) and circlePointY0-7 ($32E2). The",
    "centre is pixel (12,10), the middle of a 24x21 sprite.",
    "In:  Y = index into the octant lists, circleDxPtr zp_AD/zp_AE, circleDyPtr zp_AF/zp_B0",
    "Out: $32DA-$32E9 filled with 8 X and 8 Y coordinates",
    "Called from: drawCircleInSprite ($3257)"
   ],
   "line": "prepare the additions"
  },
  "3267": {
   "unit": "game/mainProgram0800",
   "line": "12 = centre column of a 24-pixel-wide sprite"
  },
  "3269": {
   "unit": "game/mainProgram0800",
   "line": "+dx of this octant point"
  },
  "326B": {
   "unit": "game/mainProgram0800",
   "line": "X of mirrored point 0"
  },
  "326E": {
   "unit": "game/mainProgram0800",
   "line": "X of mirrored point 7"
  },
  "3271": {
   "unit": "game/mainProgram0800",
   "line": "prepare the subtractions"
  },
  "3272": {
   "unit": "game/mainProgram0800",
   "line": "centre column again"
  },
  "3274": {
   "unit": "game/mainProgram0800",
   "line": "12-dx"
  },
  "3276": {
   "unit": "game/mainProgram0800",
   "line": "X of mirrored point 3"
  },
  "3279": {
   "unit": "game/mainProgram0800",
   "line": "X of mirrored point 4"
  },
  "327C": {
   "unit": "game/mainProgram0800",
   "line": "clear carry for the next addition"
  },
  "327D": {
   "unit": "game/mainProgram0800",
   "line": "centre column"
  },
  "327F": {
   "unit": "game/mainProgram0800",
   "line": "12+dy - the octant swap that mirrors about the diagonal"
  },
  "3281": {
   "unit": "game/mainProgram0800",
   "line": "X of mirrored point 1"
  },
  "3284": {
   "unit": "game/mainProgram0800",
   "line": "X of mirrored point 6"
  },
  "3287": {
   "unit": "game/mainProgram0800",
   "line": "set carry for the next subtraction"
  },
  "3288": {
   "unit": "game/mainProgram0800",
   "line": "centre column"
  },
  "328A": {
   "unit": "game/mainProgram0800",
   "line": "12-dy"
  },
  "328C": {
   "unit": "game/mainProgram0800",
   "line": "X of mirrored point 2"
  },
  "328F": {
   "unit": "game/mainProgram0800",
   "line": "X of mirrored point 5"
  },
  "3292": {
   "unit": "game/mainProgram0800",
   "line": "clear carry for the next addition"
  },
  "3293": {
   "unit": "game/mainProgram0800",
   "line": "10 = centre row of a 21-pixel-high sprite"
  },
  "3295": {
   "unit": "game/mainProgram0800",
   "line": "10+dy"
  },
  "3297": {
   "unit": "game/mainProgram0800",
   "line": "Y of mirrored point 0"
  },
  "329A": {
   "unit": "game/mainProgram0800",
   "line": "Y of mirrored point 3"
  },
  "329D": {
   "unit": "game/mainProgram0800",
   "line": "set carry for the next subtraction"
  },
  "329E": {
   "unit": "game/mainProgram0800",
   "line": "centre row"
  },
  "32A0": {
   "unit": "game/mainProgram0800",
   "line": "10-dy"
  },
  "32A2": {
   "unit": "game/mainProgram0800",
   "line": "Y of mirrored point 4"
  },
  "32A5": {
   "unit": "game/mainProgram0800",
   "line": "Y of mirrored point 7"
  },
  "32A8": {
   "unit": "game/mainProgram0800",
   "line": "clear carry for the next addition"
  },
  "32A9": {
   "unit": "game/mainProgram0800",
   "line": "centre row"
  },
  "32AB": {
   "unit": "game/mainProgram0800",
   "line": "10+dx"
  },
  "32AD": {
   "unit": "game/mainProgram0800",
   "line": "Y of mirrored point 1"
  },
  "32B0": {
   "unit": "game/mainProgram0800",
   "line": "Y of mirrored point 2"
  },
  "32B3": {
   "unit": "game/mainProgram0800",
   "line": "set carry for the next subtraction"
  },
  "32B4": {
   "unit": "game/mainProgram0800",
   "line": "centre row"
  },
  "32B6": {
   "unit": "game/mainProgram0800",
   "line": "10-dx"
  },
  "32B8": {
   "unit": "game/mainProgram0800",
   "line": "Y of mirrored point 5"
  },
  "32BB": {
   "unit": "game/mainProgram0800",
   "line": "Y of mirrored point 6"
  },
  "32BE": {
   "unit": "game/mainProgram0800",
   "line": "all eight points ready"
  },
  "32BF": {
   "unit": "game/mainProgram0800",
   "routine": [
    "plotCircleOctantPoints - Plots the eight mirrored points prepared by computeCircleOctantPoints into",
    "the sprite named by activeSpriteIndex, counting circlePointIndex ($3238) down from 7 to 0.",
    "In:  circlePointX0-7 $32DA, circlePointY0-7 $32E2, activeSpriteIndex $91CC, spritePlotMode $91F3",
    "Out: 8 pixels plotted; circlePointIndex = $FF",
    "Called from: drawCircleInSprite ($325A)"
   ],
   "line": "eight symmetric points per octant step"
  },
  "32C1": {
   "unit": "game/mainProgram0800",
   "line": "keep the point index in memory, X is needed by the plotter"
  },
  "32C4": {
   "unit": "game/mainProgram0800",
   "block": [
    "Plot the eight mirrored points of the current octant step."
   ],
   "line": "$32E2[n] = Y coordinate of point n"
  },
  "32C7": {
   "unit": "game/mainProgram0800",
   "line": "$32DA[n] = X coordinate of point n"
  },
  "32CA": {
   "unit": "game/mainProgram0800",
   "line": "X = pixel column"
  },
  "32CB": {
   "unit": "game/mainProgram0800",
   "line": "the sprite chosen by drawCircleInSprite"
  },
  "32CE": {
   "unit": "game/mainProgram0800",
   "line": "set/clear/toggle the pixel"
  },
  "32D1": {
   "unit": "game/mainProgram0800",
   "line": "next point"
  },
  "32D4": {
   "unit": "game/mainProgram0800",
   "line": "reload the index"
  },
  "32D7": {
   "unit": "game/mainProgram0800",
   "line": "loop until point 0 has been plotted"
  },
  "32D9": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "32DA": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sixteen RAM bytes inside the code: the eight X coordinates ($32DA-$32E1) and the eight Y",
    "coordinates ($32E2-$32E9) of the mirrored points of one circle step. Written by",
    "computeCircleOctantPoints and read, indexed 0-7, by plotCircleOctantPoints."
   ],
   "line": "X coordinate of mirrored circle point 0"
  },
  "32DB": {
   "unit": "game/mainProgram0800",
   "line": "X coordinate of mirrored circle point 1"
  },
  "32DC": {
   "unit": "game/mainProgram0800",
   "line": "X coordinate of mirrored circle point 2"
  },
  "32DD": {
   "unit": "game/mainProgram0800",
   "line": "X coordinate of mirrored circle point 3"
  },
  "32DE": {
   "unit": "game/mainProgram0800",
   "line": "X coordinate of mirrored circle point 4"
  },
  "32DF": {
   "unit": "game/mainProgram0800",
   "line": "X coordinate of mirrored circle point 5"
  },
  "32E0": {
   "unit": "game/mainProgram0800",
   "line": "X coordinate of mirrored circle point 6"
  },
  "32E1": {
   "unit": "game/mainProgram0800",
   "line": "X coordinate of mirrored circle point 7"
  },
  "32E2": {
   "unit": "game/mainProgram0800",
   "line": "Y coordinate of mirrored circle point 0"
  },
  "32E3": {
   "unit": "game/mainProgram0800",
   "line": "Y coordinate of mirrored circle point 1"
  },
  "32E4": {
   "unit": "game/mainProgram0800",
   "line": "Y coordinate of mirrored circle point 2"
  },
  "32E5": {
   "unit": "game/mainProgram0800",
   "line": "Y coordinate of mirrored circle point 3"
  },
  "32E6": {
   "unit": "game/mainProgram0800",
   "line": "Y coordinate of mirrored circle point 4"
  },
  "32E7": {
   "unit": "game/mainProgram0800",
   "line": "Y coordinate of mirrored circle point 5"
  },
  "32E8": {
   "unit": "game/mainProgram0800",
   "line": "Y coordinate of mirrored circle point 6"
  },
  "32E9": {
   "unit": "game/mainProgram0800",
   "line": "Y coordinate of mirrored circle point 7"
  },
  "32EA": {
   "unit": "game/mainProgram0800",
   "routine": [
    "hideSprite1 - Clears bit 1 of the sprite enable shadow and writes the result straight to",
    "VIC_SPR_ENA, so the unit marker sprite disappears in the current frame instead of at the next",
    "raster interrupt.",
    "In:  spriteEnableShadow $9022",
    "Out: $9022 and $D015 with bit 1 cleared; A clobbered",
    "Called from: the battlefield cursor and selection code ($229F, $22F4, $2359, $253C, $2540, $3D4E)"
   ],
   "line": "$9022 = shadow of VIC_SPR_ENA"
  },
  "32ED": {
   "unit": "game/mainProgram0800",
   "line": "clear bit 1 = sprite 1, the unit marker on the overview map"
  },
  "32EF": {
   "unit": "game/mainProgram0800",
   "line": "keep the shadow in step"
  },
  "32F2": {
   "unit": "game/mainProgram0800",
   "line": "write $D015 at once so the marker vanishes immediately"
  },
  "32F5": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "32F6": {
   "unit": "game/mainProgram0800",
   "routine": [
    "hideSprite0Dead - Dead code: a byte-for-byte copy of hideSprite1 that clears bit 0 (sprite 0)",
    "instead of bit 1. No traced code refers to $32F6, and callers that want sprite 0 off clear the",
    "shadow themselves, so this is a leftover of an earlier build.",
    "In:  none (never called)",
    "Out: would clear bit 0 of $9022 and $D015"
   ],
   "line": "dead code: sprite enable shadow"
  },
  "32F9": {
   "unit": "game/mainProgram0800",
   "line": "clear bit 0 = sprite 0"
  },
  "32FB": {
   "unit": "game/mainProgram0800",
   "line": "store the shadow"
  },
  "32FE": {
   "unit": "game/mainProgram0800",
   "line": "write it to $D015"
  },
  "3301": {
   "unit": "game/mainProgram0800",
   "line": "return"
  },
  "3302": {
   "unit": "game/mainProgram0800",
   "routine": [
    "showCrosshairSprite - Loads the 63-byte diamond crosshair (the second shape of the pair at $9EA4,",
    "bytes $9EE3-$9F21) into sprite 2 and falls into the shared body at $3308, which paints it black,",
    "doubles it in both directions, parks it over the centre cell of the 7x5 tactical view and enables it.",
    "In:  none",
    "Out: sprite 2 shape/colour/size/position set, sprite 2 enabled",
    "Called from: runTargetCursorLoop ($21AB) when the player is choosing an attack target"
   ],
   "line": "$7D = last byte of shape 1 ($9EE3-$9F21), the diamond crosshair"
  },
  "3304": {
   "unit": "game/mainProgram0800",
   "line": "always taken"
  },
  "3306": {
   "unit": "game/mainProgram0800",
   "routine": [
    "showCursorBoxSprite - Same as showCrosshairSprite but copies the first shape of the pair ($9EA4-",
    "$9EE2), the hollow box that frames the cursor cell of the tactical view.",
    "In:  none",
    "Out: sprite 2 holds the box, black, expanded, at sprite X 256 / Y $4D, enabled",
    "Called from: battlefield entry ($1FEB, $2260) and the order-cancel path ($4C56, by JMP)"
   ],
   "line": "$3E = last byte of shape 0 ($9EA4-$9EE2), the hollow cursor box"
  },
  "3308": {
   "unit": "game/mainProgram0800",
   "block": [
    "Shared body: copy 63 bytes backwards into sprite 2, then make it a black, double-sized",
    "frame sitting over the centre cell of the 7x5 tactical view."
   ],
   "line": "63 bytes, copied from the end backwards"
  },
  "330A": {
   "unit": "game/mainProgram0800",
   "line": "$9EA4 holds the two 63-byte shapes back to back"
  },
  "330D": {
   "unit": "game/mainProgram0800",
   "line": "$8A80 = shape of sprite 2"
  },
  "3310": {
   "unit": "game/mainProgram0800",
   "line": "previous source byte"
  },
  "3311": {
   "unit": "game/mainProgram0800",
   "line": "previous destination byte"
  },
  "3312": {
   "unit": "game/mainProgram0800",
   "line": "loop over all 63 bytes"
  },
  "3314": {
   "unit": "game/mainProgram0800",
   "line": "colour 0"
  },
  "3316": {
   "unit": "game/mainProgram0800",
   "line": "$900A = sprite 2 colour shadow: black"
  },
  "3319": {
   "unit": "game/mainProgram0800",
   "line": "$9023 = X expansion shadow"
  },
  "331C": {
   "unit": "game/mainProgram0800",
   "line": "double the width of sprite 2"
  },
  "331E": {
   "unit": "game/mainProgram0800",
   "line": "store it back"
  },
  "3321": {
   "unit": "game/mainProgram0800",
   "line": "$9024 = Y expansion shadow"
  },
  "3324": {
   "unit": "game/mainProgram0800",
   "line": "double the height too, so the frame covers a whole 16x16 view cell"
  },
  "3326": {
   "unit": "game/mainProgram0800",
   "line": "store it back"
  },
  "3329": {
   "unit": "game/mainProgram0800",
   "line": "low byte of the sprite X coordinate"
  },
  "332B": {
   "unit": "game/mainProgram0800",
   "line": "$9012 = sprite 2 X shadow"
  },
  "332E": {
   "unit": "game/mainProgram0800",
   "line": "$9020 = shadow of the sprite X MSB register"
  },
  "3331": {
   "unit": "game/mainProgram0800",
   "line": "set bit 2: sprite 2 X coordinate becomes 256"
  },
  "3333": {
   "unit": "game/mainProgram0800",
   "line": "store it back"
  },
  "3336": {
   "unit": "game/mainProgram0800",
   "line": "sprite Y 77"
  },
  "3338": {
   "unit": "game/mainProgram0800",
   "line": "$901A: the centre cell of the 7x5 tactical view"
  },
  "333B": {
   "unit": "game/mainProgram0800",
   "line": "sprite 2 enable bit"
  },
  "333D": {
   "unit": "game/mainProgram0800",
   "line": "switch it on and return to the caller"
  },
  "3340": {
   "unit": "game/mainProgram0800",
   "routine": [
    "loadDirectionShapeSprite - Copies sprite shape number A (0-7, one per facing direction) from the",
    "decompressed shape buffer at $0200 + A*64 into sprite 0 ($8A00) when activeSpriteIndex is 0, or into",
    "sprite 5 ($8B40) otherwise, and enables that sprite. The buffer is filled by the RLE decompressor",
    "at $67CB from the compressed shape sets at $FD70/$FE24.",
    "In:  A = shape index 0-7, activeSpriteIndex $91CC = target sprite (0 or 5)",
    "Out: 63 bytes of sprite 0 or 5 rewritten and the sprite enabled; spritePlotPtr zp_98/zp_99 clobbered;",
    "     self-modifies the destination address of the STA at $3373",
    "Called from: the unit direction display ($5322) and overlay B ($802A, $810B, $829E)"
   ],
   "line": "shape number in the high byte = shape*256"
  },
  "3342": {
   "unit": "game/mainProgram0800",
   "line": "low byte starts at 0"
  },
  "3344": {
   "unit": "game/mainProgram0800",
   "line": "16-bit shift right..."
  },
  "3346": {
   "unit": "game/mainProgram0800",
   "line": "...through the low byte"
  },
  "3347": {
   "unit": "game/mainProgram0800",
   "line": "and again"
  },
  "3349": {
   "unit": "game/mainProgram0800",
   "line": "shape*64"
  },
  "334A": {
   "unit": "game/mainProgram0800",
   "line": "low byte of the source pointer"
  },
  "334C": {
   "unit": "game/mainProgram0800",
   "line": "no extra low offset"
  },
  "334E": {
   "unit": "game/mainProgram0800",
   "line": "$0200 = base of the decompressed shape buffer"
  },
  "3350": {
   "unit": "game/mainProgram0800",
   "line": "clear carry for the 16-bit pointer addition"
  },
  "3351": {
   "unit": "game/mainProgram0800",
   "line": "add the shape offset"
  },
  "3353": {
   "unit": "game/mainProgram0800",
   "line": "source pointer low byte"
  },
  "3355": {
   "unit": "game/mainProgram0800",
   "line": "base page $02"
  },
  "3356": {
   "unit": "game/mainProgram0800",
   "line": "plus the high byte of shape*64"
  },
  "3358": {
   "unit": "game/mainProgram0800",
   "line": "source pointer = $0200 + shape*64"
  },
  "335A": {
   "unit": "game/mainProgram0800",
   "line": "default destination low byte"
  },
  "335C": {
   "unit": "game/mainProgram0800",
   "line": "default destination high byte: $8A00 = sprite 0"
  },
  "335E": {
   "unit": "game/mainProgram0800",
   "line": "$91CC = which sprite to load"
  },
  "3361": {
   "unit": "game/mainProgram0800",
   "line": "sprite 0 keeps the default"
  },
  "3363": {
   "unit": "game/mainProgram0800",
   "line": "otherwise $8B40..."
  },
  "3365": {
   "unit": "game/mainProgram0800",
   "line": "...= the shape of sprite 5"
  },
  "3367": {
   "unit": "game/mainProgram0800",
   "line": "patch the low byte of the STA absolute,x at $3373"
  },
  "336A": {
   "unit": "game/mainProgram0800",
   "line": "patch its high byte"
  },
  "336D": {
   "unit": "game/mainProgram0800",
   "line": "63 bytes to copy"
  },
  "336F": {
   "unit": "game/mainProgram0800",
   "line": "same index for source and destination"
  },
  "3371": {
   "unit": "game/mainProgram0800",
   "line": "read from the shape buffer"
  },
  "3373": {
   "unit": "game/mainProgram0800",
   "line": "patched to $8A00,x or $8B40,x; the $FFFF resting value is never used"
  },
  "3376": {
   "unit": "game/mainProgram0800",
   "line": "previous destination byte"
  },
  "3377": {
   "unit": "game/mainProgram0800",
   "line": "previous source byte"
  },
  "3378": {
   "unit": "game/mainProgram0800",
   "line": "loop over all 63 bytes"
  },
  "337A": {
   "unit": "game/mainProgram0800",
   "line": "which sprite was loaded"
  },
  "337D": {
   "unit": "game/mainProgram0800",
   "line": "$2F85[n] = 1 << n"
  },
  "3380": {
   "unit": "game/mainProgram0800",
   "line": "switch it on and return to the caller"
  },
  "3383": {
   "unit": "game/mainProgram0800",
   "routine": [
    "showComcenCrossMarker - Puts the small cross marker at $9F22 into rows 7-13 of sprite 2, colours it",
    "light red and parks it on the comcen's cell of overlay B's 21x17 radar grid (grid column 10, row 8 -",
    "the grid is always drawn centred on the comcen).",
    "In:  none",
    "Out: sprite 2 shape/colour/position set and enabled, gridCursorCol zp_67 = 10, gridCursorRow zp_68 = 8",
    "Called from: overlay B ($75B6) while the RADAR/MISSILE screen is being built"
   ],
   "line": "sprite 2"
  },
  "3385": {
   "unit": "game/mainProgram0800",
   "line": "blank its 64 shape bytes first"
  },
  "3388": {
   "unit": "game/mainProgram0800",
   "line": "21 bytes = 7 sprite rows of 3 bytes"
  },
  "338A": {
   "unit": "game/mainProgram0800",
   "line": "$9F22 = the small cross/plus shape"
  },
  "338D": {
   "unit": "game/mainProgram0800",
   "line": "$8A95 = sprite 2 shape + $15, i.e. row 7 of the sprite"
  },
  "3390": {
   "unit": "game/mainProgram0800",
   "line": "previous byte"
  },
  "3391": {
   "unit": "game/mainProgram0800",
   "line": "loop over the 21 bytes"
  },
  "3393": {
   "unit": "game/mainProgram0800",
   "line": "colour $0A = light red"
  },
  "3395": {
   "unit": "game/mainProgram0800",
   "line": "$900A = sprite 2 colour shadow"
  },
  "3398": {
   "unit": "game/mainProgram0800",
   "line": "grid column 10 - where the own comcen always sits"
  },
  "339A": {
   "unit": "game/mainProgram0800",
   "line": "zp_67 = radar grid cursor column"
  },
  "339C": {
   "unit": "game/mainProgram0800",
   "line": "grid row 8"
  },
  "339E": {
   "unit": "game/mainProgram0800",
   "line": "zp_68 = radar grid cursor row"
  },
  "33A0": {
   "unit": "game/mainProgram0800",
   "line": "overlay B range test; its carry result is never examined here, so the call is a leftover"
  },
  "33A3": {
   "unit": "game/mainProgram0800",
   "line": "grid cell -> sprite coordinates"
  },
  "33A6": {
   "unit": "game/mainProgram0800",
   "line": "$9012 = sprite 2 X shadow"
  },
  "33A9": {
   "unit": "game/mainProgram0800",
   "line": "$901A = sprite 2 Y shadow"
  },
  "33AC": {
   "unit": "game/mainProgram0800",
   "line": "sprite 2 enable bit"
  },
  "33AE": {
   "unit": "game/mainProgram0800",
   "line": "switch it on and return to the caller"
  },
  "33B1": {
   "unit": "game/mainProgram0800",
   "routine": [
    "gridCellToSpriteCoords - Converts a cell of overlay B's 21x17 radar grid (one map cell per 8x8",
    "character cell) into sprite coordinates: spriteX = column*8 + $27, spriteY = row*8 + $43. The grid",
    "starts at character position (3,3); the two constants fold in the VIC sprite origin (24,50) and the",
    "offset of the marker shapes inside their sprite.",
    "In:  X = grid column 0-20, Y = grid row 0-16",
    "Out: X = sprite X, Y = sprite Y; A clobbered",
    "Called from: showComcenCrossMarker ($33A3), getUnitSpriteCentreOnGrid ($3410) and overlay B",
    "     ($7709, $77BC)"
   ],
   "line": "grid column"
  },
  "33B2": {
   "unit": "game/mainProgram0800",
   "line": "column*2"
  },
  "33B3": {
   "unit": "game/mainProgram0800",
   "line": "column*4"
  },
  "33B4": {
   "unit": "game/mainProgram0800",
   "line": "column*8 - the radar grid uses one 8x8 character cell per map cell"
  },
  "33B5": {
   "unit": "game/mainProgram0800",
   "line": "clear carry before adding the grid origin"
  },
  "33B6": {
   "unit": "game/mainProgram0800",
   "line": "sprite X of grid column 0"
  },
  "33B8": {
   "unit": "game/mainProgram0800",
   "line": "return it in X"
  },
  "33B9": {
   "unit": "game/mainProgram0800",
   "line": "grid row"
  },
  "33BA": {
   "unit": "game/mainProgram0800",
   "line": "row*2"
  },
  "33BB": {
   "unit": "game/mainProgram0800",
   "line": "row*4"
  },
  "33BC": {
   "unit": "game/mainProgram0800",
   "line": "row*8"
  },
  "33BD": {
   "unit": "game/mainProgram0800",
   "line": "clear carry before adding the grid origin"
  },
  "33BE": {
   "unit": "game/mainProgram0800",
   "line": "sprite Y of grid row 0"
  },
  "33C0": {
   "unit": "game/mainProgram0800",
   "line": "return it in Y"
  },
  "33C1": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "33C2": {
   "unit": "game/mainProgram0800",
   "block": [
    "Three 2-entry tables indexed by explosion type: 0 = comcen missile strike, 1 = drone",
    "detonation. They give the sprite offsets that centre the ring blob on the target cell and",
    "the number of concentric rings that make the blob."
   ],
   "line": "X offsets: 10 pixels for a missile strike, 8 for a drone blast"
  },
  "33C4": {
   "unit": "game/mainProgram0800",
   "line": "matching Y offsets: 8 and 9 pixels"
  },
  "33C6": {
   "unit": "game/mainProgram0800",
   "line": "ring counts: 2 rings for a missile strike, 4 for the bigger drone blast"
  },
  "33C8": {
   "unit": "game/mainProgram0800",
   "routine": [
    "showExplosionMarker - Shows the explosion marker for a weapon effect at map cell (X,Y). On the",
    "battlefield screen it draws a blob of concentric rings into sprite 4 and puts it straight on the",
    "4x4-per-cell overview map; a missile strike that arrives while a drone is in the air instead just",
    "records the cell in the two patched operands of updateExplosionMarker, which keeps the marker in",
    "step with the scrolling drone camera window. Either way markerSpriteTimer is set to 90 frames, and",
    "the per-frame update in updateGameFrame switches sprite 4 off when it expires. The survey called",
    "this routine dead; it is not - it is called by two command handlers.",
    "In:  A = effect type (0 = comcen missile strike, 1 = drone detonation), X = map column, Y = map row;",
    "     droneControlFlags $920D, currentScreen $90FB",
    "Out: sprite 4 shape/position/colour, markerSpriteTimer $923E = 90; self-modifies $33EA (the effect",
    "     index) and, on the drone path, $3425/$3437 (the marker cell)",
    "Called from: cmdDroneDetonate ($5375, A=1) and cmdMissileStrike ($541C, A=0)"
   ],
   "line": "patch the LDX # at $33E9 with the effect index (0 missile, 1 drone)"
  },
  "33CB": {
   "unit": "game/mainProgram0800",
   "line": "is this the comcen missile strike?"
  },
  "33CD": {
   "unit": "game/mainProgram0800",
   "line": "no - a drone blast always goes on the overview map"
  },
  "33CF": {
   "unit": "game/mainProgram0800",
   "line": "$920D = drone control flags"
  },
  "33D2": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 clear = no drone in the air, so the drone camera is not on screen"
  },
  "33D4": {
   "unit": "game/mainProgram0800",
   "line": "start the 90-frame marker countdown"
  },
  "33D7": {
   "unit": "game/mainProgram0800",
   "line": "remember the blast column in the LDA # of updateExplosionMarker"
  },
  "33DA": {
   "unit": "game/mainProgram0800",
   "line": "and the blast row"
  },
  "33DD": {
   "unit": "game/mainProgram0800",
   "line": "position sprite 4 inside the drone camera window and return"
  },
  "33E0": {
   "unit": "game/mainProgram0800",
   "block": [
    "Battlefield path: build the ring blob and drop it straight on the overview map."
   ],
   "line": "$90FB = which screen is showing; 0 = the battlefield"
  },
  "33E3": {
   "unit": "game/mainProgram0800",
   "line": "not on the battlefield - there is nothing to draw on"
  },
  "33E5": {
   "unit": "game/mainProgram0800",
   "line": "blast cell -> overview map sprite coordinates"
  },
  "33E8": {
   "unit": "game/mainProgram0800",
   "line": "sprite X of the cell"
  },
  "33E9": {
   "unit": "game/mainProgram0800",
   "line": "effect index, patched at $33C8"
  },
  "33EB": {
   "unit": "game/mainProgram0800",
   "line": "set carry for the centring subtraction"
  },
  "33EC": {
   "unit": "game/mainProgram0800",
   "line": "shift left so the ring blob is centred on the cell"
  },
  "33EF": {
   "unit": "game/mainProgram0800",
   "line": "$9014 = sprite 4 X shadow"
  },
  "33F2": {
   "unit": "game/mainProgram0800",
   "line": "sprite Y of the cell"
  },
  "33F3": {
   "unit": "game/mainProgram0800",
   "line": "set carry for the centring subtraction"
  },
  "33F4": {
   "unit": "game/mainProgram0800",
   "line": "shift up by the matching Y offset"
  },
  "33F7": {
   "unit": "game/mainProgram0800",
   "line": "$901C = sprite 4 Y shadow"
  },
  "33FA": {
   "unit": "game/mainProgram0800",
   "line": "how many concentric rings this effect uses"
  },
  "33FD": {
   "unit": "game/mainProgram0800",
   "line": "draw them into sprite 4"
  },
  "3400": {
   "unit": "game/mainProgram0800",
   "line": "sprite 4 enable bit"
  },
  "3402": {
   "unit": "game/mainProgram0800",
   "line": "switch the marker on"
  },
  "3405": {
   "unit": "game/mainProgram0800",
   "routine": [
    "startExplosionMarkerTimer - Two-instruction tail shared by both paths of showExplosionMarker: arms",
    "the explosion marker for 90 frames (about 1.5 seconds).",
    "In:  none",
    "Out: markerSpriteTimer $923E = $5A",
    "Called from: showExplosionMarker ($33D4) and by fall-through from $3402"
   ],
   "line": "$5A = 90 frames"
  },
  "3407": {
   "unit": "game/mainProgram0800",
   "line": "$923E; updateGameFrame counts it down and hides sprite 4 at zero"
  },
  "340A": {
   "unit": "game/mainProgram0800",
   "line": "return (also the exit taken when the battlefield screen is not showing)"
  },
  "340B": {
   "unit": "game/mainProgram0800",
   "routine": [
    "getUnitSpriteCentreOnGrid - Maps a battlefield cell onto overlay B's radar grid and returns the",
    "sprite coordinates of the centre of that grid cell, or C=1 when the cell lies outside the 21x17",
    "grid drawn around the comcen.",
    "In:  X = map column, Y = map row",
    "Out: C=0 with X/Y = sprite coordinates of the cell centre, or C=1 and X/Y meaningless",
    "Called from: the unit tracking code ($5307) and overlay B ($7A03)"
   ],
   "line": "overlay B $7A13: map cell -> radar grid cell relative to the comcen, C=1 when off-grid"
  },
  "340E": {
   "unit": "game/mainProgram0800",
   "line": "outside the 21x17 grid: return with C=1"
  },
  "3410": {
   "unit": "game/mainProgram0800",
   "line": "grid cell -> sprite coordinates of its top-left"
  },
  "3413": {
   "unit": "game/mainProgram0800",
   "line": "sprite Y"
  },
  "3414": {
   "unit": "game/mainProgram0800",
   "line": "clear carry before adding the half-cell offset"
  },
  "3415": {
   "unit": "game/mainProgram0800",
   "line": "+4 = half a character cell, i.e. the centre of the cell"
  },
  "3417": {
   "unit": "game/mainProgram0800",
   "line": "back into Y"
  },
  "3418": {
   "unit": "game/mainProgram0800",
   "line": "sprite X"
  },
  "3419": {
   "unit": "game/mainProgram0800",
   "line": "clear carry before adding the half-cell offset"
  },
  "341A": {
   "unit": "game/mainProgram0800",
   "line": "+4 for the same reason"
  },
  "341C": {
   "unit": "game/mainProgram0800",
   "line": "back into X"
  },
  "341D": {
   "unit": "game/mainProgram0800",
   "line": "C=0 = the cell is on the grid"
  },
  "341E": {
   "unit": "game/mainProgram0800",
   "line": "return"
  },
  "341F": {
   "unit": "game/mainProgram0800",
   "routine": [
    "updateExplosionMarker - Keeps the explosion marker (sprite 4) on the right cell of the drone camera",
    "window, which is 14 columns by 12 rows of 8x8 pixel cells starting at the view origin. The marker",
    "cell lives in the two immediate operands at $3425 and $3437, patched by showExplosionMarker. When",
    "the countdown has expired or the cell has scrolled out of the window, sprite 4 is switched off",
    "instead. The survey believed nothing ever patched the operands; showExplosionMarker does.",
    "In:  markerSpriteTimer $923E, viewOriginCol zp_A5, viewOriginRow zp_A6, the operands at $3425/$3437",
    "Out: sprite 4 position shadows $9014/$901C and its enable bit; A clobbered",
    "Called from: showExplosionMarker ($33DD, by JMP) and overlay B's drone camera step ($827E)"
   ],
   "line": "$923E, counted down once per frame by updateGameFrame"
  },
  "3422": {
   "unit": "game/mainProgram0800",
   "line": "zero = no explosion pending: make sure sprite 4 is off"
  },
  "3424": {
   "unit": "game/mainProgram0800",
   "line": "blast column, patched at $33D7"
  },
  "3426": {
   "unit": "game/mainProgram0800",
   "line": "set carry for the subtraction of the view origin"
  },
  "3427": {
   "unit": "game/mainProgram0800",
   "line": "column relative to the left edge of the drone camera window"
  },
  "3429": {
   "unit": "game/mainProgram0800",
   "line": "the window is 14 cells wide"
  },
  "342B": {
   "unit": "game/mainProgram0800",
   "line": "outside it (this also catches negative differences): hide the marker"
  },
  "342D": {
   "unit": "game/mainProgram0800",
   "line": "x2"
  },
  "342E": {
   "unit": "game/mainProgram0800",
   "line": "x4"
  },
  "342F": {
   "unit": "game/mainProgram0800",
   "line": "x8 - the drone camera draws one map cell per 8x8 character cell"
  },
  "3430": {
   "unit": "game/mainProgram0800",
   "line": "clear carry before adding the window origin"
  },
  "3431": {
   "unit": "game/mainProgram0800",
   "line": "sprite X that centres the ring blob on window column 0"
  },
  "3433": {
   "unit": "game/mainProgram0800",
   "line": "$9014 = sprite 4 X shadow"
  },
  "3436": {
   "unit": "game/mainProgram0800",
   "line": "blast row, patched at $33DA"
  },
  "3438": {
   "unit": "game/mainProgram0800",
   "line": "set carry for the subtraction of the view origin"
  },
  "3439": {
   "unit": "game/mainProgram0800",
   "line": "row relative to the top of the window"
  },
  "343B": {
   "unit": "game/mainProgram0800",
   "line": "the window is 12 cells high"
  },
  "343D": {
   "unit": "game/mainProgram0800",
   "line": "outside it: hide the marker"
  },
  "343F": {
   "unit": "game/mainProgram0800",
   "line": "x2"
  },
  "3440": {
   "unit": "game/mainProgram0800",
   "line": "x4"
  },
  "3441": {
   "unit": "game/mainProgram0800",
   "line": "x8"
  },
  "3442": {
   "unit": "game/mainProgram0800",
   "line": "clear carry before adding the window origin"
  },
  "3443": {
   "unit": "game/mainProgram0800",
   "line": "sprite Y that centres the blob on window row 0"
  },
  "3445": {
   "unit": "game/mainProgram0800",
   "line": "$901C = sprite 4 Y shadow"
  },
  "3448": {
   "unit": "game/mainProgram0800",
   "line": "sprite 4 enable bit"
  },
  "344A": {
   "unit": "game/mainProgram0800",
   "line": "switch it on and return to the caller"
  },
  "344D": {
   "unit": "game/mainProgram0800",
   "line": "$9022 = sprite enable shadow"
  },
  "3450": {
   "unit": "game/mainProgram0800",
   "line": "clear bit 4 = sprite 4, the explosion marker"
  },
  "3452": {
   "unit": "game/mainProgram0800",
   "line": "store it back (the raster IRQ writes $D015)"
  },
  "3455": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "3456": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawComcenRingSprite - Builds the comcen marker of the drone screen: two concentric rings (radius 2",
    "then radius 1, which is a filled 3x3 dot) in sprite 4, coloured with the flashing pair $12 so it",
    "alternates between white and red.",
    "In:  none",
    "Out: sprite 4 shape rebuilt, sprite4ColourShadow $900C = $12, sprite 4 left disabled here,",
    "     activeSpriteIndex $91CC = 4, spritePlotMode $91F3 = $40",
    "Called from: overlay B ($80E1), right after the view has been centred on the comcen"
   ],
   "line": "two concentric rings for the comcen marker"
  },
  "3458": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawRingsInSprite4 - Shared body of drawComcenRingSprite: clears sprite 4 and draws A concentric",
    "rings into it, radius A down to radius 1, then gives it the flashing colour pair $12. The ring count",
    "is held in the LDX # operand at $346E and counted down there.",
    "In:  A = number of rings / radius of the outermost one",
    "Out: sprite 4 shape and colour; sprite 4 disabled while it is rebuilt; self-modifies $346E",
    "Called from: showExplosionMarker ($33FD, A = 2 or 4) and by fall-through from drawComcenRingSprite"
   ],
   "line": "patch the LDX # at $346D with the outermost radius / ring count"
  },
  "345B": {
   "unit": "game/mainProgram0800",
   "line": "$9022 = sprite enable shadow"
  },
  "345E": {
   "unit": "game/mainProgram0800",
   "line": "clear bit 4: hide sprite 4 while its shape is rebuilt"
  },
  "3460": {
   "unit": "game/mainProgram0800",
   "line": "store it back"
  },
  "3463": {
   "unit": "game/mainProgram0800",
   "line": "bit 6 = 'set the pixel'"
  },
  "3465": {
   "unit": "game/mainProgram0800",
   "line": "$91F3 = plot mode for drawCircleInSprite"
  },
  "3468": {
   "unit": "game/mainProgram0800",
   "line": "sprite 4"
  },
  "346A": {
   "unit": "game/mainProgram0800",
   "line": "blank its 64 shape bytes"
  },
  "346D": {
   "unit": "game/mainProgram0800",
   "line": "current radius, patched at $3458 and decremented below"
  },
  "346F": {
   "unit": "game/mainProgram0800",
   "line": "draw into sprite 4"
  },
  "3471": {
   "unit": "game/mainProgram0800",
   "line": "one circle of that radius, centred on sprite pixel (12,10)"
  },
  "3474": {
   "unit": "game/mainProgram0800",
   "line": "next smaller radius"
  },
  "3477": {
   "unit": "game/mainProgram0800",
   "line": "loop down to radius 1, which fills the centre with a 3x3 dot"
  },
  "3479": {
   "unit": "game/mainProgram0800",
   "line": "$12 = flashing colour pair: alternates colour 2 (red) and colour 1 (white)"
  },
  "347B": {
   "unit": "game/mainProgram0800",
   "line": "$900C = sprite 4 colour shadow"
  },
  "347E": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "347F": {
   "unit": "game/mainProgram0800",
   "routine": [
    "orderSelectedUnitToCursor - Issues a MOVE order for the selected unit to the cell under the cursor:",
    "takes the unit out of its group, queues command $80 unit,col,row into the outgoing ring (so the",
    "engine, the opponent and the game film all see the same bytes) and refreshes the 'already at the",
    "destination' flag.",
    "In:  selectedUnit $90F8, viewOriginCol zp_A5, viewOriginRow zp_A6",
    "Out: cmdParam0-2 zp_73-zp_75 = unit/column/row, $FB54[unit] bit 6 cleared and bit 7 refreshed,",
    "     4 bytes queued for the command interpreter, C=0",
    "Called from: the fire-button handler of the battlefield cursor loop ($2212)"
   ],
   "line": "left edge column of the tactical view"
  },
  "3481": {
   "unit": "game/mainProgram0800",
   "line": "clear carry before stepping to the cursor column"
  },
  "3482": {
   "unit": "game/mainProgram0800",
   "line": "+3 = the cursor column"
  },
  "3484": {
   "unit": "game/mainProgram0800",
   "line": "zp_74 = command argument 1 (destination column)"
  },
  "3486": {
   "unit": "game/mainProgram0800",
   "line": "top edge row of the tactical view"
  },
  "3488": {
   "unit": "game/mainProgram0800",
   "line": "clear carry before stepping to the cursor row"
  },
  "3489": {
   "unit": "game/mainProgram0800",
   "line": "+2 = the cursor row"
  },
  "348B": {
   "unit": "game/mainProgram0800",
   "line": "zp_75 = command argument 2 (destination row)"
  },
  "348D": {
   "unit": "game/mainProgram0800",
   "line": "$90F8 = the unit being ordered"
  },
  "3490": {
   "unit": "game/mainProgram0800",
   "line": "$FB54[unit] = local display/group flags"
  },
  "3493": {
   "unit": "game/mainProgram0800",
   "line": "clear bit 6: a unit given its own order leaves its group"
  },
  "3495": {
   "unit": "game/mainProgram0800",
   "line": "store the flags back"
  },
  "3498": {
   "unit": "game/mainProgram0800",
   "line": "zp_73 = command argument 0 (unit index)"
  },
  "349A": {
   "unit": "game/mainProgram0800",
   "line": "command byte $80 = MOVE unit,col,row"
  },
  "349C": {
   "unit": "game/mainProgram0800",
   "line": "$560A: queue the 4 bytes for the engine, the link and the film"
  },
  "349F": {
   "unit": "game/mainProgram0800",
   "line": "set or clear the 'standing on its destination' bit"
  },
  "34A2": {
   "unit": "game/mainProgram0800",
   "line": "C=0 tells the cursor loop the order was accepted"
  },
  "34A3": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "34A4": {
   "unit": "game/mainProgram0800",
   "routine": [
    "updateUnitAtDestinationFlag - Sets bit 7 of unitDisplayFlags ($FB54) for the unit in cmdParam0 when",
    "its current cell is the same as the ordered destination in cmdParam1/cmdParam2, and clears it",
    "otherwise. The bit is what the console and the map drawing use to tell a moving unit from an idle one.",
    "In:  cmdParam0 zp_73 = unit, cmdParam1 zp_74 = destination column, cmdParam2 zp_75 = destination row",
    "Out: $FB54[unit] bit 7; self-modifies the ORA operand at $34C1",
    "Called from: orderSelectedUnitToCursor ($349F) and the command executors at $4A4D and $4C10"
   ],
   "line": "zp_73 = unit index"
  },
  "34A6": {
   "unit": "game/mainProgram0800",
   "line": "assume the unit still has somewhere to go"
  },
  "34A8": {
   "unit": "game/mainProgram0800",
   "line": "$F640[unit] = current map column"
  },
  "34AB": {
   "unit": "game/mainProgram0800",
   "line": "same as the ordered column?"
  },
  "34AD": {
   "unit": "game/mainProgram0800",
   "line": "no - it is still travelling"
  },
  "34AF": {
   "unit": "game/mainProgram0800",
   "line": "$F6A4[unit] = current map row"
  },
  "34B2": {
   "unit": "game/mainProgram0800",
   "line": "same as the ordered row?"
  },
  "34B4": {
   "unit": "game/mainProgram0800",
   "line": "no - still travelling"
  },
  "34B6": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = the unit is already standing on its destination"
  },
  "34B8": {
   "unit": "game/mainProgram0800",
   "line": "patch the ORA # at $34C0 with $80 or $00"
  },
  "34BB": {
   "unit": "game/mainProgram0800",
   "line": "$FB54[unit]"
  },
  "34BE": {
   "unit": "game/mainProgram0800",
   "line": "clear the old 'at destination' bit"
  },
  "34C0": {
   "unit": "game/mainProgram0800",
   "line": "patched: put the freshly computed bit back"
  },
  "34C2": {
   "unit": "game/mainProgram0800",
   "line": "store the flags"
  },
  "34C5": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "34C6": {
   "unit": "game/mainProgram0800",
   "routine": [
    "applyMoveOrder - Executor of command $80 MOVE (unit, column, row). It takes the unit out of its",
    "group, republishes the group bits into the local flags copy, and falls into setUnitDestination to",
    "store the clamped destination. The survey listed this as an unreferenced older version; it is in",
    "fact live - cmdMoveUnit jumps here from $5017 - and there is no other move executor in the build.",
    "In:  cmdParam0 zp_73 = unit, cmdParam1/cmdParam2 zp_74/zp_75 = destination (already mirrored for a",
    "     remote command)",
    "Out: $F708[unit] bit 6 cleared, $FB54[unit] group bits refreshed, then everything setUnitDestination does",
    "Called from: cmdMoveUnit ($5017, by JMP)"
   ],
   "line": "zp_73 = the unit being ordered"
  },
  "34C8": {
   "unit": "game/mainProgram0800",
   "line": "$F708[unit] = authoritative unit flags (facing, side, group)"
  },
  "34CB": {
   "unit": "game/mainProgram0800",
   "line": "clear bit 6: an individual move order takes the unit out of its group"
  },
  "34CD": {
   "unit": "game/mainProgram0800",
   "line": "store the flags back"
  },
  "34D0": {
   "unit": "game/mainProgram0800",
   "line": "$4C6B: copy the group bits into the $FB54 display flags"
  },
  "34D3": {
   "unit": "game/mainProgram0800",
   "line": "Y = unit index for the destination code below"
  },
  "34D5": {
   "unit": "game/mainProgram0800",
   "routine": [
    "setUnitDestination - Stores a destination for unit Y. A comcen whose speed option is class 4 (fixed)",
    "has its destination forced back to its own cell; a destination equal to the current cell is stored",
    "with bit 7 set, which marks the unit idle and is mirrored into unitDisplayFlags bit 7; anything else",
    "is clamped to the 40x40 map. The walking waypoint is reset to the unit's current cell (except for the",
    "solo trainer's own units, which keep theirs), and the order cancels any dig-in in progress.",
    "In:  Y = unit index, cmdParam1/cmdParam2 zp_74/zp_75 = destination column/row, gamePhase zp_B1,",
    "     comcenSpeedClass $92B4 / opponentComcenSpeedClass $92B5, comcenUnit $9236, soloTrainerAtGameStart $92FE",
    "Out: $F7D0/$F834 destination, $FB54 bit 7, $F898/$F8FC waypoint, $F960 bits 5-7 cleared,",
    "     $F76C bit 6 cleared; self-modifies the ORA operand at $3521",
    "Called from: the group formation move ($4A31, $4A9B), the destination command ($4D21), the group",
    "     order tail ($4C68, by JMP) and by fall-through from applyMoveOrder"
   ],
   "line": "zp_B1 = 0 only while the battle is actually running"
  },
  "34D7": {
   "unit": "game/mainProgram0800",
   "line": "in the setup, film and menu phases the comcen speed rule does not apply"
  },
  "34D9": {
   "unit": "game/mainProgram0800",
   "line": "unit 49 = player 0's comcen"
  },
  "34DB": {
   "unit": "game/mainProgram0800",
   "line": "it is a comcen"
  },
  "34DD": {
   "unit": "game/mainProgram0800",
   "line": "unit 99 = player 1's comcen"
  },
  "34DF": {
   "unit": "game/mainProgram0800",
   "line": "not a comcen: nothing special"
  },
  "34E1": {
   "unit": "game/mainProgram0800",
   "line": "$92B4 = the local player's COMCEN SPEED option"
  },
  "34E4": {
   "unit": "game/mainProgram0800",
   "line": "$9236 = index of the local player's comcen"
  },
  "34E7": {
   "unit": "game/mainProgram0800",
   "line": "it is our own comcen, use our own option"
  },
  "34E9": {
   "unit": "game/mainProgram0800",
   "line": "$92B5 = the opponent's comcen speed class"
  },
  "34EC": {
   "unit": "game/mainProgram0800",
   "line": "class 4 = the comcen may not move at all"
  },
  "34EE": {
   "unit": "game/mainProgram0800",
   "line": "any other class: the ordered destination stands"
  },
  "34F0": {
   "unit": "game/mainProgram0800",
   "line": "immobile comcen: destination column..."
  },
  "34F3": {
   "unit": "game/mainProgram0800",
   "line": "...forced to where it already stands"
  },
  "34F5": {
   "unit": "game/mainProgram0800",
   "line": "and the same for the row"
  },
  "34F8": {
   "unit": "game/mainProgram0800",
   "line": "zp_75"
  },
  "34FA": {
   "unit": "game/mainProgram0800",
   "block": [
    "Store the destination: equal to the current cell means 'idle', anything else is clamped to",
    "the map, and the idle bit is mirrored into the display flags."
   ],
   "line": "destination column"
  },
  "34FC": {
   "unit": "game/mainProgram0800",
   "line": "$F640[unit] = current column"
  },
  "34FF": {
   "unit": "game/mainProgram0800",
   "line": "different - the unit really has to move"
  },
  "3501": {
   "unit": "game/mainProgram0800",
   "line": "destination row"
  },
  "3503": {
   "unit": "game/mainProgram0800",
   "line": "$F6A4[unit] = current row"
  },
  "3506": {
   "unit": "game/mainProgram0800",
   "line": "different - the unit really has to move"
  },
  "3508": {
   "unit": "game/mainProgram0800",
   "line": "same cell: take the column again"
  },
  "350A": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = 'destination reached, nothing to do'"
  },
  "350C": {
   "unit": "game/mainProgram0800",
   "line": "always taken (bit 7 was just set)"
  },
  "350E": {
   "unit": "game/mainProgram0800",
   "line": "destination column"
  },
  "3510": {
   "unit": "game/mainProgram0800",
   "line": "keep it inside the 40x40 battlefield"
  },
  "3513": {
   "unit": "game/mainProgram0800",
   "line": "$F7D0[unit] = destination column (bit 7 = idle)"
  },
  "3516": {
   "unit": "game/mainProgram0800",
   "line": "isolate the idle bit"
  },
  "3518": {
   "unit": "game/mainProgram0800",
   "line": "patch the ORA # at $3520 with it"
  },
  "351B": {
   "unit": "game/mainProgram0800",
   "line": "$FB54[unit] = display flags"
  },
  "351E": {
   "unit": "game/mainProgram0800",
   "line": "clear the old idle bit"
  },
  "3520": {
   "unit": "game/mainProgram0800",
   "line": "patched: mirror the new idle bit into bit 7"
  },
  "3522": {
   "unit": "game/mainProgram0800",
   "line": "store the display flags"
  },
  "3525": {
   "unit": "game/mainProgram0800",
   "line": "destination row"
  },
  "3527": {
   "unit": "game/mainProgram0800",
   "line": "clamp it to the map as well"
  },
  "352A": {
   "unit": "game/mainProgram0800",
   "line": "$F834[unit] = destination row"
  },
  "352D": {
   "unit": "game/mainProgram0800",
   "line": "$92FE bit 7 = this game is against the solo trainer"
  },
  "3530": {
   "unit": "game/mainProgram0800",
   "line": "modem or film game: always reset the waypoint"
  },
  "3532": {
   "unit": "game/mainProgram0800",
   "line": "units 50-99 are the trainer's own units"
  },
  "3534": {
   "unit": "game/mainProgram0800",
   "line": "the trainer maintains their waypoints itself"
  },
  "3536": {
   "unit": "game/mainProgram0800",
   "block": [
    "Reset the walking waypoint to the cell the unit is standing on, keeping the two flag bits",
    "in the top of those bytes."
   ],
   "line": "$F898[unit] = waypoint column, bits 6-7 are flags"
  },
  "3539": {
   "unit": "game/mainProgram0800",
   "line": "keep only those two flag bits"
  },
  "353B": {
   "unit": "game/mainProgram0800",
   "line": "waypoint column = the cell the unit is standing on"
  },
  "353E": {
   "unit": "game/mainProgram0800",
   "line": "store it back"
  },
  "3541": {
   "unit": "game/mainProgram0800",
   "line": "$F8FC[unit] = waypoint row, bits 6-7 are flags"
  },
  "3544": {
   "unit": "game/mainProgram0800",
   "line": "keep the flag bits"
  },
  "3546": {
   "unit": "game/mainProgram0800",
   "line": "waypoint row = current row"
  },
  "3549": {
   "unit": "game/mainProgram0800",
   "line": "store it back"
  },
  "354C": {
   "unit": "game/mainProgram0800",
   "block": [
    "An order to move cancels any dig-in the unit had started or completed."
   ],
   "line": "$F960[unit]: low nibble stun counter, bits 5-7 dig progress"
  },
  "354F": {
   "unit": "game/mainProgram0800",
   "line": "clear bits 5-7 - the unit stops digging in"
  },
  "3551": {
   "unit": "game/mainProgram0800",
   "line": "store it back"
  },
  "3554": {
   "unit": "game/mainProgram0800",
   "line": "$F76C[unit] = type byte"
  },
  "3557": {
   "unit": "game/mainProgram0800",
   "line": "clear bit 6 = no longer dug in / fortified"
  },
  "3559": {
   "unit": "game/mainProgram0800",
   "line": "store it back"
  },
  "355C": {
   "unit": "game/mainProgram0800",
   "line": "the order is stored"
  },
  "355D": {
   "unit": "game/mainProgram0800",
   "routine": [
    "clampMapCoord - Clamps a signed map coordinate to the battlefield: negative becomes 0, anything from",
    "40 upwards becomes 39.",
    "In:  A = coordinate",
    "Out: A = 0..39",
    "Called from: setUnitDestination ($3510 for the column, $3527 for the row)"
   ],
   "line": "already zero or positive"
  },
  "355F": {
   "unit": "game/mainProgram0800",
   "line": "negative coordinates clamp to column/row 0"
  },
  "3561": {
   "unit": "game/mainProgram0800",
   "line": "40 = width and height of the battlefield"
  },
  "3563": {
   "unit": "game/mainProgram0800",
   "line": "inside the map"
  },
  "3565": {
   "unit": "game/mainProgram0800",
   "line": "39 = last valid column/row"
  },
  "3567": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "3568": {
   "unit": "game/mainProgram0800",
   "routine": [
    "markAllUnitsVisible - Sets bits 6 and 7 (spotted this cycle, currently visible) of unitEnergy for",
    "all 100 units, i.e. reveals the whole battlefield. Used at the end of a game and whenever no game",
    "is in progress.",
    "In:  none",
    "Out: $F9C4[0..99] bits 6-7 set; Y = $FF, A clobbered",
    "Called from: the game reset at $4018 and ageUnitVisibility ($3579, by branch)"
   ],
   "line": "unit 99 down to unit 0"
  },
  "356A": {
   "unit": "game/mainProgram0800",
   "line": "$F9C4[unit]: bits 0-5 energy, bit 6 spotted, bit 7 visible"
  },
  "356D": {
   "unit": "game/mainProgram0800",
   "line": "mark it both spotted and visible"
  },
  "356F": {
   "unit": "game/mainProgram0800",
   "line": "store it back"
  },
  "3572": {
   "unit": "game/mainProgram0800",
   "line": "previous unit"
  },
  "3573": {
   "unit": "game/mainProgram0800",
   "line": "loop over all 100 units"
  },
  "3575": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "3576": {
   "unit": "game/mainProgram0800",
   "routine": [
    "ageUnitVisibility - Run once per unit-processing cycle, before the spotting passes: every unit that",
    "was spotted during the last cycle (energy bit 6) stays visible (bit 7) and everything else fades",
    "out, and bit 6 is cleared ready for the coming scan. It also resets the unit-scan side filter to",
    "'accept every unit'. When the game is over it simply reveals everything instead.",
    "In:  gameEndReason $0BA8, unitEnergyTable $F9C4",
    "Out: $F9C4 visibility bits rewritten, the collectUnitsInRadius filter at $37FF/$3800 set to",
    "     compare against 0 with BCC; self-modifies $359C",
    "Called from: finishRound ($4660)"
   ],
   "line": "$0BA8 = nonzero once the game has ended"
  },
  "3579": {
   "unit": "game/mainProgram0800",
   "line": "game over: show every unit instead of ageing the flags"
  },
  "357B": {
   "unit": "game/mainProgram0800",
   "line": "compare value 0..."
  },
  "357D": {
   "unit": "game/mainProgram0800",
   "line": "...into the CMP # of the unit scan filter at $37FE"
  },
  "3580": {
   "unit": "game/mainProgram0800",
   "line": "opcode of BCC"
  },
  "3582": {
   "unit": "game/mainProgram0800",
   "line": "so the filter at $3800 accepts every unit index"
  },
  "3585": {
   "unit": "game/mainProgram0800",
   "line": "unit 0 upwards"
  },
  "3587": {
   "unit": "game/mainProgram0800",
   "block": [
    "Shift each unit's visibility: bit 6 (spotted last cycle) becomes bit 7 (visible now)."
   ],
   "line": "assume the unit stays visible"
  },
  "3589": {
   "unit": "game/mainProgram0800",
   "line": "$F9C4[unit]"
  },
  "358C": {
   "unit": "game/mainProgram0800",
   "line": "bit 6 = it was spotted during the last scan cycle"
  },
  "358E": {
   "unit": "game/mainProgram0800",
   "line": "it was - keep it visible"
  },
  "3590": {
   "unit": "game/mainProgram0800",
   "line": "it was not - it fades out of sight"
  },
  "3592": {
   "unit": "game/mainProgram0800",
   "line": "read the byte again"
  },
  "3595": {
   "unit": "game/mainProgram0800",
   "line": "keep the energy value, drop both visibility bits"
  },
  "3597": {
   "unit": "game/mainProgram0800",
   "line": "patch the ORA # at $359B with it"
  },
  "359A": {
   "unit": "game/mainProgram0800",
   "line": "$80 (visible) or $00 (hidden)"
  },
  "359B": {
   "unit": "game/mainProgram0800",
   "line": "patched energy value"
  },
  "359D": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = visible, bit 6 cleared ready for this cycle's scan"
  },
  "35A0": {
   "unit": "game/mainProgram0800",
   "line": "next unit"
  },
  "35A1": {
   "unit": "game/mainProgram0800",
   "line": "100 units"
  },
  "35A3": {
   "unit": "game/mainProgram0800",
   "line": "loop"
  },
  "35A5": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "35A6": {
   "unit": "game/mainProgram0800",
   "block": [
    "sightRangeByType - base range in cells, indexed by unit type for the spotting pass (entries",
    "0-4) and by type+5 for the attack pass (entries 5-9). The terrain bonus below is added on top",
    "and the sum is capped at 10."
   ],
   "line": "spotting: GRUNT 3, RIDER 3, BOOMER 3, SPY 5, COMCEN 3; attack: GRUNT 3, RIDER 3, BOOMER 6"
  },
  "35AE": {
   "unit": "game/mainProgram0800",
   "line": "attack range of SPY 5 and COMCEN 3 (entries 8 and 9)"
  },
  "35B0": {
   "unit": "game/mainProgram0800",
   "block": [
    "rangeDistanceSqTable - r*(r+1) for r = 0..10. scanVisibleEnemies compares the squared",
    "distance of a candidate against this, which rounds the square search box into a circle."
   ],
   "line": "r*(r+1) for r = 0..7: 0, 2, 6, 12, 20, 30, 42, 56"
  },
  "35B8": {
   "unit": "game/mainProgram0800",
   "line": "r*(r+1) for r = 8, 9, 10: 72, 90, 110"
  },
  "35BB": {
   "unit": "game/mainProgram0800",
   "block": [
    "terrainSightBonusTable - extra range given by the terrain class under the observer",
    "(0-7); doubled on the spotting pass, counted once on the attack pass."
   ],
   "line": "classes 0-7: 0, 0, 0, 1, 1, 2, 2, 0 extra cells of range"
  },
  "35C3": {
   "unit": "game/mainProgram0800",
   "routine": [
    "scanVisibleEnemies - Builds the list of enemy units that unit Y can see (X bit 7 clear, the spotting",
    "pass) or can shoot at (X bit 7 set, the attack-target pass). The effective range is",
    "sightRangeByType[type, +5 when attacking] plus the terrain bonus of the cell the unit stands on",
    "(doubled while spotting), capped at 10 cells. collectUnitsInRadius fills $0400 with the enemy units",
    "in the square and $0432 with their squared distances; this routine then walks that list and strikes",
    "off ($0400[n] = $FF) every candidate that is out of range, dead, not yet visible (attack pass),",
    "protected by the spy/hidden rules (spotting pass) or hidden behind a ridge. $0464[n] receives the",
    "direction 0-3 from the scanner to the candidate. Candidates that are not in the unit's preferred",
    "direction (zp_6B) are pushed one step further away, which biases a unit towards the target it is",
    "already facing. The first sighting of an enemy spy or comcen queues status message $0F / $0D.",
    "Only the head of the routine ($35C3-$366E) belongs to this chunk; it continues to $3745.",
    "In:  X = pass (bit 7: 0 = spotting, 1 = attack targets), Y = the scanning unit; unit arrays, the map,",
    "     the terrain class table at $077E",
    "Out: $0400 candidate list (entries struck off with $FF), $0432 squared distances, $0464 directions,",
    "     scanResultCount zp_2F = index of the last entry ($FF = none), fireTargetDistance $91F1 = range,",
    "     scanUnitTerrain/$9227, scanUnitType/$9228, scanUnitTerrainHeight/$9229; self-modifies $35F9",
    "     (range table index) and $362B (distance limit)",
    "Called from: the boomer target search ($3C8A) and the two scan passes of processUnitStep ($43AA",
    "     spotting, $43DC attack targets)"
   ],
   "line": "zp_20 = pass flag: bit 7 clear = who can I see, bit 7 set = who can I shoot at"
  },
  "35C5": {
   "unit": "game/mainProgram0800",
   "line": "zp_21 = the unit doing the scanning"
  },
  "35C7": {
   "unit": "game/mainProgram0800",
   "line": "$F76C[unit] = type byte"
  },
  "35CA": {
   "unit": "game/mainProgram0800",
   "line": "unit type 0-4 (GRUNT, RIDER, BOOMER, SPY, COMCEN)"
  },
  "35CC": {
   "unit": "game/mainProgram0800",
   "line": "$9228 = type of the scanning unit, used by the spy/hidden rules below"
  },
  "35CF": {
   "unit": "game/mainProgram0800",
   "line": "which pass is this?"
  },
  "35D1": {
   "unit": "game/mainProgram0800",
   "line": "spotting pass uses sightRangeByType entries 0-4"
  },
  "35D3": {
   "unit": "game/mainProgram0800",
   "line": "clear carry before shifting to the attack half of the table"
  },
  "35D4": {
   "unit": "game/mainProgram0800",
   "line": "attack pass uses entries 5-9"
  },
  "35D6": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDX # at $35F8 with that table index"
  },
  "35D9": {
   "unit": "game/mainProgram0800",
   "line": "$378B: set the collectUnitsInRadius filter to 'units of the other side only'"
  },
  "35DC": {
   "unit": "game/mainProgram0800",
   "line": "$FAF0[unit] = the terrain tile hidden under the unit"
  },
  "35DF": {
   "unit": "game/mainProgram0800",
   "line": "$9227 = that tile, kept for the callers"
  },
  "35E2": {
   "unit": "game/mainProgram0800",
   "line": "index it"
  },
  "35E3": {
   "unit": "game/mainProgram0800",
   "line": "$077E[tile] = terrain class 0-7"
  },
  "35E6": {
   "unit": "game/mainProgram0800",
   "line": "index the class tables with it"
  },
  "35E7": {
   "unit": "game/mainProgram0800",
   "line": "$38B3[class] = height, used by the line-of-sight walk"
  },
  "35EA": {
   "unit": "game/mainProgram0800",
   "line": "$9229 = height the observer is standing at"
  },
  "35ED": {
   "unit": "game/mainProgram0800",
   "line": "extra range for standing on high ground"
  },
  "35F0": {
   "unit": "game/mainProgram0800",
   "line": "flat ground: no bonus"
  },
  "35F2": {
   "unit": "game/mainProgram0800",
   "line": "which pass?"
  },
  "35F4": {
   "unit": "game/mainProgram0800",
   "line": "attack pass: the bonus counts once"
  },
  "35F6": {
   "unit": "game/mainProgram0800",
   "line": "spotting pass: high ground doubles the terrain bonus"
  },
  "35F7": {
   "unit": "game/mainProgram0800",
   "line": "prepare the addition"
  },
  "35F8": {
   "unit": "game/mainProgram0800",
   "line": "range table index, patched at $35D6"
  },
  "35FA": {
   "unit": "game/mainProgram0800",
   "line": "base range for this unit type and pass"
  },
  "35FD": {
   "unit": "game/mainProgram0800",
   "line": "10 cells is the hard limit"
  },
  "35FF": {
   "unit": "game/mainProgram0800",
   "line": "below it"
  },
  "3601": {
   "unit": "game/mainProgram0800",
   "line": "clamp to 10"
  },
  "3603": {
   "unit": "game/mainProgram0800",
   "line": "$91F1 = effective range of this scan"
  },
  "3606": {
   "unit": "game/mainProgram0800",
   "line": "index the squared-distance table with it"
  },
  "3607": {
   "unit": "game/mainProgram0800",
   "line": "r*(r+1) = the squared distance a target must stay below"
  },
  "360A": {
   "unit": "game/mainProgram0800",
   "line": "patch the CMP # at $362A with that limit"
  },
  "360D": {
   "unit": "game/mainProgram0800",
   "line": "$F640[unit] = the scanner's column"
  },
  "3610": {
   "unit": "game/mainProgram0800",
   "line": "$F6A4[unit] = its row"
  },
  "3613": {
   "unit": "game/mainProgram0800",
   "line": "Y = centre row"
  },
  "3614": {
   "unit": "game/mainProgram0800",
   "line": "half width of the square to search = the range"
  },
  "3617": {
   "unit": "game/mainProgram0800",
   "line": "$37AA: gather the enemy units in that square into $0400 / $0432"
  },
  "361A": {
   "unit": "game/mainProgram0800",
   "line": "zp_2F = index of the last candidate ($FF if none)"
  },
  "361C": {
   "unit": "game/mainProgram0800",
   "line": "zp_18 = index of the candidate being examined"
  },
  "361E": {
   "unit": "game/mainProgram0800",
   "line": "at least one candidate"
  },
  "3620": {
   "unit": "game/mainProgram0800",
   "line": "nothing in range: return with zp_2F = $FF"
  },
  "3623": {
   "unit": "game/mainProgram0800",
   "block": [
    "Candidate loop: zp_18 walks the list from the last entry down to entry 0. A candidate that",
    "fails any test is struck off at $3735 by writing $FF into $0400[n]."
   ],
   "line": "$0400[n] = index of the candidate unit"
  },
  "3626": {
   "unit": "game/mainProgram0800",
   "line": "Y = candidate unit"
  },
  "3627": {
   "unit": "game/mainProgram0800",
   "line": "$0432[n] = squared distance from the scanner"
  },
  "362A": {
   "unit": "game/mainProgram0800",
   "line": "patched at $360A with r*(r+1) for the effective range"
  },
  "362C": {
   "unit": "game/mainProgram0800",
   "line": "inside the circle"
  },
  "362E": {
   "unit": "game/mainProgram0800",
   "line": "outside it: strike this candidate off the list"
  },
  "3631": {
   "unit": "game/mainProgram0800",
   "line": "the scanning unit"
  },
  "3633": {
   "unit": "game/mainProgram0800",
   "line": "$375D: set up the line walker from scanner to candidate; A = direction 0-3"
  },
  "3636": {
   "unit": "game/mainProgram0800",
   "line": "candidate index again"
  },
  "3638": {
   "unit": "game/mainProgram0800",
   "line": "$0464[n] = direction of this candidate from the scanner"
  },
  "363B": {
   "unit": "game/mainProgram0800",
   "line": "zp_6B = the direction the unit prefers (where its current target is)"
  },
  "363D": {
   "unit": "game/mainProgram0800",
   "line": "same direction: no penalty"
  },
  "363F": {
   "unit": "game/mainProgram0800",
   "line": "how many candidates were found"
  },
  "3641": {
   "unit": "game/mainProgram0800",
   "line": "index 0 means a single candidate"
  },
  "3643": {
   "unit": "game/mainProgram0800",
   "line": "only one candidate: no penalty, take it whatever the direction"
  },
  "3645": {
   "unit": "game/mainProgram0800",
   "line": "squared distance again"
  },
  "3648": {
   "unit": "game/mainProgram0800",
   "line": "squared distance below 3 = an adjacent or diagonal cell"
  },
  "364A": {
   "unit": "game/mainProgram0800",
   "line": "point blank: no penalty"
  },
  "364C": {
   "unit": "game/mainProgram0800",
   "line": "otherwise..."
  },
  "364D": {
   "unit": "game/mainProgram0800",
   "line": "...count it as one step further away than it is"
  },
  "364F": {
   "unit": "game/mainProgram0800",
   "line": "store the penalised distance back"
  },
  "3652": {
   "unit": "game/mainProgram0800",
   "line": "did the penalty push it out of range?"
  },
  "3655": {
   "unit": "game/mainProgram0800",
   "line": "yes: strike it off"
  },
  "3657": {
   "unit": "game/mainProgram0800",
   "line": "$F76C[candidate] = type byte"
  },
  "365A": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = destroyed: strike it off"
  },
  "365C": {
   "unit": "game/mainProgram0800",
   "line": "candidate type 0-4"
  },
  "365E": {
   "unit": "game/mainProgram0800",
   "line": "which pass?"
  },
  "3660": {
   "unit": "game/mainProgram0800",
   "line": "spotting pass: apply the spy and hidden-unit rules"
  },
  "3662": {
   "unit": "game/mainProgram0800",
   "line": "attack pass: $F9C4[candidate] bit 7 = the target is currently visible"
  },
  "3665": {
   "unit": "game/mainProgram0800",
   "line": "you cannot shoot at something you have not spotted"
  },
  "3667": {
   "unit": "game/mainProgram0800",
   "line": "the range table index of this scan"
  },
  "366A": {
   "unit": "game/mainProgram0800",
   "line": "index 7 = BOOMER attacking; 5 and 6 are GRUNT and RIDER"
  },
  "366C": {
   "unit": "game/mainProgram0800",
   "line": "grunts and riders must have a clear line of sight"
  },
  "366E": {
   "unit": "game/mainProgram0800",
   "line": "boomers, spies and comcens fire without one: accept this candidate"
  }
 },
 "dataTypes": {
  "3237": [
   "byte",
   2
  ],
  "32DA": [
   "byte",
   16
  ],
  "33C2": [
   "byte",
   6
  ],
  "35A6": [
   "byte",
   10
  ],
  "35B0": [
   "byte",
   11
  ],
  "35BB": [
   "byte",
   8
  ]
 },
 "zpComments": {
  "18": "general scratch: multiply/divide operand, character row in the text plotters, min column returned by computeGroupBounds, sprite pixel column in buildGroupFormationSprite, index of the candidate under test in scanVisibleEnemies",
  "19": "general scratch, partner of scratch18: product/quotient, character column in the text plotters, min row returned by computeGroupBounds, sprite pixel row in buildGroupFormationSprite",
  "1A": "general scratch: third math temp, right column of a frame box, unit loop counter in buildGroupFormationSprite, low byte of the trainer's 16-bit scores",
  "1B": "general scratch: bottom row of a frame box, division remainder (lo), max column from computeGroupBounds, the group-membership byte units are matched against in buildGroupFormationSprite",
  "20": "general scratch: half width in magnifyWindow2x, the pass flag of scanVisibleEnemies (bit 7 set = attack-target pass, clear = spotting pass), unit type being counted on the STATS tab",
  "21": "general scratch: half height in magnifyWindow2x, index of the unit doing the scan in scanVisibleEnemies, terrain neighbour mask in the map generator",
  "92": "line stepper: current row - a map row while walking the battlefield, but a sprite pixel row 0-20 while a line is drawn into a sprite shape ($306C/$308A/$30A1); also the cursor row of the battlefield view",
  "93": "line stepper: current column - a map column, or a sprite pixel column 0-23 while a line is drawn into a sprite shape; also the cursor column of the battlefield view",
  "94": "line stepper: target row (a map row, or a sprite pixel row when the line is being drawn inside a sprite shape)",
  "95": "line stepper: target column (a map column, or a sprite pixel column when the line is being drawn inside a sprite shape)",
  "98": "pointer (lo) to the sprite shape byte being modified by plotSpritePixel; loadDirectionShapeSprite reuses it as the $0200 + shape*64 copy source"
 }
}
