{
 "chunk": "game_comcenScreens6F00_8601",
 "unit": "game/comcenScreens6F00",
 "unitLabels": {
  "game/comcenScreens6F00": {
   "8601": "printNumberRightAligned",
   "860B": "countNumberPadChars",
   "8615": "emitPaddedNumber",
   "8621": "padCountPatch",
   "8626": "valueMidPatch",
   "8628": "valueLowPatch",
   "862D": "setTextColumn",
   "8632": "drawComcenSchematic",
   "8654": "primeRleRunCounter",
   "8656": "rleEmitLoop",
   "8666": "rleSaveRunCount",
   "866D": "rleStoreByte",
   "868B": "schematicDone",
   "868C": "strDrone",
   "8691": "strLaunch",
   "8697": "strStrike",
   "869D": "strFire",
   "86A1": "strDroneAlertTwoLine",
   "86AA": "strDroneAlertEnd",
   "86AC": "statsTabLabels",
   "86C3": "statsLabelsSpies",
   "86FB": "filmViewWordTable",
   "8707": "msgEndOfFilm",
   "8713": "msgUnitRecycled",
   "8721": "msgShowViewTemplate",
   "8722": "msgShowViewBody",
   "8726": "msgShowViewWord",
   "872A": "msgSpeedTemplate",
   "872E": "msgSpeedBody",
   "8730": "msgSpeedDigit",
   "8731": "msgSpottedRecycler",
   "8742": "msgComcenStunned",
   "8751": "msgSpottedEnemyComcen",
   "8756": "msgSpottedComcenBody",
   "8766": "msgSpyUnderAttack",
   "8777": "msgSpottedEnemySpy",
   "8789": "msgWeLostOnPoints",
   "8795": "msgWeLostOnPointsEnd",
   "879B": "msgWeWonOnPoints",
   "87AC": "msgItWasADraw",
   "87BA": "msgWeWereKnockedOut",
   "87CE": "msgWeWonByAKnockout",
   "87E3": "msgGameOver",
   "87ED": "msgDroneAlert",
   "87F9": "overlayTailPadding",
   "87FF": "overlayVariantFlag"
  }
 },
 "dataTypes": {
  "868C": [
   "text",
   5
  ],
  "8691": [
   "text",
   6
  ],
  "8697": [
   "text",
   6
  ],
  "869D": [
   "text",
   4
  ],
  "86A1": [
   "text",
   11
  ],
  "86AC": [
   "text",
   79
  ],
  "86FB": [
   "grid",
   12,
   4
  ],
  "8707": [
   "text",
   12
  ],
  "8713": [
   "text",
   14
  ],
  "8721": [
   "text",
   9
  ],
  "872A": [
   "text",
   7
  ],
  "8731": [
   "text",
   17
  ],
  "8742": [
   "text",
   15
  ],
  "8751": [
   "text",
   21
  ],
  "8766": [
   "text",
   17
  ],
  "8777": [
   "text",
   18
  ],
  "8789": [
   "text",
   18
  ],
  "879B": [
   "text",
   17
  ],
  "87AC": [
   "text",
   14
  ],
  "87BA": [
   "text",
   20
  ],
  "87CE": [
   "text",
   21
  ],
  "87E3": [
   "text",
   10
  ],
  "87ED": [
   "text",
   12
  ],
  "87F9": [
   "byte",
   6
  ],
  "87FF": [
   "byte",
   1
  ]
 },
 "zpComments": {
  "1A": "general scratch: third math temp, right column of a frame box, low byte of the trainer's 16-bit scores; also the 10-row counter of drawComcenSchematic ($8650)",
  "1B": "general scratch: bottom row of a frame box, division remainder (lo), max column returned by computeGroupBounds; also holds the RLE repeat count while drawComcenSchematic decodes a run ($8666)",
  "7E": "high (third) byte of the 24-bit value passed to printDecimal; the conversion clears it again ($C1DD), which is why printNumberRightAligned ($8601) can print an 8-bit value without ever setting it"
 },
 "notes": {
  "8601": {
   "unit": "game/comcenScreens6F00",
   "routine": [
    "printNumberRightAligned - prints the unsigned byte in A as decimal, right-aligned in a three-character field that starts at text column X of the current text row.  It works out how many blank glyphs have to go in front (2 for 0-9, 1 for 10-99, none for 100-255), patches that count and the value into three immediate operands further down ($8622, $8627, $8629), positions the text cursor and then prints padding + number through the resident $C000 text engine.",
    "Only the low byte is ever printed: the LDY #$00 at the entry wipes whatever the caller left in Y, which makes the CPY #$00 at $8607 and the whole $8626/$8627 patch vestiges of an earlier 16-bit version of this routine (see the notes on those lines).",
    "In:  A = value 0-255, X = column relative to textWindowLeft (zp_3B).  Also uses zp_40 (text row), zp_3D (colour), zp_3E (fill glyph, 0 = blank) and needs decimalValueHigh zp_7E to be 0 - it always is, because convertToDecimalDigits clears it at $C1DD after every conversion.",
    "Out: up to 3 cells printed and the cursor left just past the number; zp_3F = X; the three immediate operands at $8622/$8627/$8629 patched; A, X, Y and zp_18-zp_1F clobbered (printDecimal uses them).",
    "Called from: drawStatsTab only - $8557 (the GRUNTS/RIDERS/BOOMERS/SPIES counts, both columns), $8579/$8581 (DRONES), $858B/$8593 (MISSILES), $85A2/$85AD (COMSPEED), $85BD/$85C8 (TER PTS).  XREF also lists the same call sites under game/mapGenerator6F00; that is a label-merge artifact - variant A has completely different code at $8557 and at $8601 (bytes 'a9 fe 8d f8 90' there)."
   ],
   "block": [
    "Number printer for the console STATS tab.  Right alignment is done the cheap way: count the",
    "digits first, print that many blanks, then let printDecimal ($C17D) emit the digits with no",
    "leading zeros.  The value and the pad count are carried to the print calls in self-modified",
    "immediate operands rather than on the stack, because updateCursorPointers ($C099) preserves only",
    "X and Y, not A."
   ],
   "line": "high byte of the number to print: always 0 here - the STATS fields are all 8-bit counts"
  },
  "8603": {
   "unit": "game/comcenScreens6F00",
   "line": "X held the target column (relative to textWindowLeft zp_3B); make it the text cursor column zp_3F"
  },
  "8605": {
   "unit": "game/comcenScreens6F00",
   "line": "X now counts the blank glyphs to print in front of the number; start by assuming none"
  },
  "8607": {
   "unit": "game/comcenScreens6F00",
   "line": "dead test: would leave the pad count at 0 when a 16-bit caller passed a non-zero high byte in Y, but $8601 has just forced Y = 0"
  },
  "8609": {
   "unit": "game/comcenScreens6F00",
   "line": "branch never taken (Y is always 0) - leftover of the 16-bit version of this routine"
  },
  "860B": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "Pad count: how many blanks are needed so that the digits end at the right edge of the",
    "three-character field.  X = 3 - number of decimal digits.",
    "In variant B this is just an internal label; the JSRs to $860B in XREF come from variant A,",
    "where a different routine (its OFF/ON option editor) starts on this byte."
   ],
   "line": "is the value 100 or more, i.e. does it already fill all three columns?"
  },
  "860D": {
   "unit": "game/comcenScreens6F00",
   "line": "100-255: three digits, no padding at all"
  },
  "860F": {
   "unit": "game/comcenScreens6F00",
   "line": "under 100: at least one blank in front"
  },
  "8610": {
   "unit": "game/comcenScreens6F00",
   "line": "is the value 10 or more?"
  },
  "8612": {
   "unit": "game/comcenScreens6F00",
   "line": "10-99: two digits, so the one blank counted above is enough"
  },
  "8614": {
   "unit": "game/comcenScreens6F00",
   "line": "0-9: a single digit, so two blanks in front"
  },
  "8615": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "Self-modifying code.  The three immediates below are written here on every call:",
    "  $8622 (operand of the LDA at $8621) = number of blanks to print",
    "  $8627 (operand of the LDY at $8626) = middle byte handed to printDecimal - always 0",
    "  $8629 (operand of the LDA at $8628) = the value to print",
    "Nothing else in either overlay writes these three bytes."
   ],
   "line": "patch the blank count into the LDA #imm at $8621 that feeds printFillChars"
  },
  "8618": {
   "unit": "game/comcenScreens6F00",
   "line": "patch the (always zero) middle byte into the LDY #imm at $8626 that feeds printDecimal"
  },
  "861B": {
   "unit": "game/comcenScreens6F00",
   "line": "patch the value itself into the LDA #imm at $8628"
  },
  "861E": {
   "unit": "game/comcenScreens6F00",
   "line": "$C099: absolute column = zp_3F + textWindowLeft zp_3B, row = zp_40; sets the bitmap pointer zp_B4/B5 and the screen pointer zp_B6/B7.  It preserves X and Y but destroys A - which is why the value had to be patched in above"
  },
  "8621": {
   "unit": "game/comcenScreens6F00",
   "line": "operand $8622 patched at $8615: 0, 1 or 2 leading blanks"
  },
  "8623": {
   "unit": "game/comcenScreens6F00",
   "line": "printFillChars at $C0EF of the resident $C000 text engine (the listing shows the boot loader's name for the same address): prints the fill glyph zp_3E - normally 0, the blank - A times, advancing the cursor one cell each time"
  },
  "8626": {
   "unit": "game/comcenScreens6F00",
   "line": "operand $8627 patched at $8618: middle byte of printDecimal's 24-bit value, always 0"
  },
  "8628": {
   "unit": "game/comcenScreens6F00",
   "line": "operand $8629 patched at $861B: low byte = the number to print"
  },
  "862A": {
   "unit": "game/comcenScreens6F00",
   "line": "tail-call printDecimal $C17D (A = low, Y = middle, zp_7E = high): converts by repeated division by 10 and prints the digits without leading zeros, then returns to drawStatsTab"
  },
  "862D": {
   "unit": "game/comcenScreens6F00",
   "routine": [
    "setTextColumn - moves the text cursor to column A of the current row and recomputes the bitmap and screen-RAM pointers for that character cell by tail-calling updateCursorPointers ($C099), which leaves X and Y untouched.",
    "In:  A = column relative to textWindowLeft (zp_3B; 10 while the STATS tab is being drawn), zp_40 = text row.",
    "Out: zp_3F = A, zp_B4/B5 = bitmap address of the cell, zp_B6/B7 = screen RAM address; A clobbered, X and Y preserved.",
    "Called from: drawStatsTab only - $8536 (the US/THEM column of each unit-count row), $85CF and $85DD (the two RECYCLER words) and $85F7 (the MAP ID column).  The game/mapGenerator6F00 callers in XREF are label-merge artifacts; variant A holds unrelated code at $862D."
   ],
   "block": [
    "Two-instruction helper used wherever the STATS tab jumps to a fixed column without printing a",
    "number: column 9 is the US column, column 14 the THEM column (both relative to textWindowLeft 10)."
   ],
   "line": "new cursor column, counted from the left edge of the text window (zp_3B)"
  },
  "862F": {
   "unit": "game/comcenScreens6F00",
   "line": "$C099 recomputes zp_B4/B5 and zp_B6/B7 for cell (zp_3B + zp_3F, zp_40) and returns straight to the caller"
  },
  "8632": {
   "unit": "game/comcenScreens6F00",
   "routine": [
    "drawComcenSchematic - unpacks the RLE-compressed comcen picture at comcenSchematicRle ($7C6E) straight into the hires bitmap for the console REPAIR tab: a 20x10 character (160x80 pixel) side view of the tracked comcen vehicle with its dish and antenna, drawn at character row 10, column 13.",
    "Stream format (the same one unpackRleTo0200 $67CB uses): byte 0 is the escape marker ($13 here); afterwards any byte other than the marker is a literal, and 'marker, count, value' emits value count times, with count 0 meaning 256.  Runs are allowed to cross the 160-byte row boundary, which is why the run value is stacked over the pointer advance at $8678/$8686.",
    "In:  the compressed stream at $7C6E-$7FCD.  No registers.",
    "Out: 10 x 160 bytes written into the bitmap from $A000 + 10*320 + 13*8 = $ACE8 with a 320-byte row stride; rleMarkerByte $9246 = $13, rleUnpackFlag $9247 = 0, fetchRleByte's self-modified operand $681B/$681C left just past the end of the stream; zp_1A (row counter), zp_1B (run count), zp_B4/B5 (bitmap pointer) and zp_B6/B7 (set but unused) clobbered.",
    "Called from: drawRepairTab ($176B) in the main program, its only caller.  Since the routine lives in overlay B, the console REPAIR tab implicitly requires variant B to be resident."
   ],
   "block": [
    "Set up the decoder: escape marker, source pointer and destination cell.  Note that this routine",
    "does not use the general unpacker at $67CB - that one always writes 512 bytes to $0200 - but it",
    "does share its byte fetcher (fetchRleByte $681A) and its marker byte ($9246)."
   ],
   "line": "index 0 of the stream, and the zero about to be stored in $9247"
  },
  "8634": {
   "unit": "game/comcenScreens6F00",
   "line": "$9247 = 0, exactly as unpackRleTo0200 does at $67D3 - but no code in any unit ever reads this byte, so it is a dead leftover"
  },
  "8637": {
   "unit": "game/comcenScreens6F00",
   "line": "byte 0 of the compressed stream is the escape marker ($13 for this picture)"
  },
  "863A": {
   "unit": "game/comcenScreens6F00",
   "line": "keep the marker in $9246 for the compare at $865E (shared with the main program's unpacker)"
  },
  "863D": {
   "unit": "game/comcenScreens6F00",
   "line": "high byte of the source address..."
  },
  "863F": {
   "unit": "game/comcenScreens6F00",
   "line": "...poked into the high half of fetchRleByte's self-modified 'lda $XXXX' operand at $681B/$681C"
  },
  "8642": {
   "unit": "game/comcenScreens6F00",
   "line": "low byte: decoding starts at $7C6F, one past the marker byte"
  },
  "8644": {
   "unit": "game/comcenScreens6F00",
   "line": "complete the patched source pointer = $7C6F"
  },
  "8647": {
   "unit": "game/comcenScreens6F00",
   "line": "left edge of the picture: character column 13, just right of the 22-column map panel"
  },
  "8649": {
   "unit": "game/comcenScreens6F00",
   "line": "top edge: character row 10, under the REPAIR tab heading"
  },
  "864B": {
   "unit": "game/comcenScreens6F00",
   "line": "$2AA3: zp_B4/B5 = $A000 + row*320 + col*8 (bitmap cell), zp_B6/B7 = $8C00 + row*40 + col (screen RAM cell, not used here)"
  },
  "864E": {
   "unit": "game/comcenScreens6F00",
   "line": "the picture is 10 character rows tall"
  },
  "8650": {
   "unit": "game/comcenScreens6F00",
   "line": "zp_1A counts the rows still to emit"
  },
  "8652": {
   "unit": "game/comcenScreens6F00",
   "line": "Y = byte offset inside the current character row, 0..159"
  },
  "8654": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "Decoder loop.  X is the number of copies of A still to store: it is decremented on entry to each",
    "pass, and only when it reaches 0 is a new item pulled from the stream.  Y walks the 160 bytes of",
    "one picture row (20 cells x 8 scanlines, stored cell by cell), zp_1A counts the rows.",
    "primeRleRunCounter is entered once, by falling through from $8652; the per-row restart at $8689 jumps back to rleEmitLoop with X and A intact.  It is a branch target only in variant A."
   ],
   "line": "prime the run counter with 1 so that the very first pass fetches from the stream"
  },
  "8656": {
   "unit": "game/comcenScreens6F00",
   "line": "one byte of the current run consumed"
  },
  "8657": {
   "unit": "game/comcenScreens6F00",
   "line": "run not exhausted: store the same value again without touching the stream"
  },
  "8659": {
   "unit": "game/comcenScreens6F00",
   "line": "$681A: fetch the next stream byte into A (and X) and bump the source pointer"
  },
  "865C": {
   "unit": "game/comcenScreens6F00",
   "line": "assume a literal byte: exactly one store"
  },
  "865E": {
   "unit": "game/comcenScreens6F00",
   "line": "is this byte the escape marker that introduces a run?"
  },
  "8661": {
   "unit": "game/comcenScreens6F00",
   "line": "not the marker: plain literal, store it once"
  },
  "8663": {
   "unit": "game/comcenScreens6F00",
   "line": "marker seen - the next byte is the repeat count (0 counts as 256)"
  },
  "8666": {
   "unit": "game/comcenScreens6F00",
   "line": "park the repeat count while the value byte is fetched (this label is a branch target only in variant A)"
  },
  "8668": {
   "unit": "game/comcenScreens6F00",
   "line": "third byte of the run: the value to repeat"
  },
  "866B": {
   "unit": "game/comcenScreens6F00",
   "line": "X = repeat count; this pass stores the value once and X-1 further passes repeat it"
  },
  "866D": {
   "unit": "game/comcenScreens6F00",
   "line": "write one bitmap byte; eight consecutive bytes are the eight scanlines of one character cell"
  },
  "866F": {
   "unit": "game/comcenScreens6F00",
   "line": "next byte of the picture row"
  },
  "8670": {
   "unit": "game/comcenScreens6F00",
   "line": "160 bytes = 20 character cells x 8 scanlines = one full picture row"
  },
  "8672": {
   "unit": "game/comcenScreens6F00",
   "line": "row not finished yet, keep decoding"
  },
  "8674": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "End of a picture row: step the bitmap pointer down one character row.  A (the current run value)",
    "and X (the remaining repeat count) are carried across untouched, so a run may span rows."
   ],
   "line": "one of the 10 picture rows done"
  },
  "8676": {
   "unit": "game/comcenScreens6F00",
   "line": "all 10 rows drawn - the picture is complete"
  },
  "8678": {
   "unit": "game/comcenScreens6F00",
   "line": "save the current run value across the pointer arithmetic"
  },
  "8679": {
   "unit": "game/comcenScreens6F00",
   "line": "clear carry for the 16-bit add below"
  },
  "867A": {
   "unit": "game/comcenScreens6F00",
   "line": "advance the bitmap pointer..."
  },
  "867C": {
   "unit": "game/comcenScreens6F00",
   "line": "...by $0140 = 320 bytes = one character row of the 40-column bitmap ($40 into the low byte..."
  },
  "867E": {
   "unit": "game/comcenScreens6F00",
   "line": "...store the new low byte"
  },
  "8680": {
   "unit": "game/comcenScreens6F00",
   "line": "...carry into the high byte..."
  },
  "8682": {
   "unit": "game/comcenScreens6F00",
   "line": "...plus 1, completing the +$140"
  },
  "8684": {
   "unit": "game/comcenScreens6F00",
   "line": "zp_B4/B5 now points at the leftmost cell of the next picture row (still column 13)"
  },
  "8686": {
   "unit": "game/comcenScreens6F00",
   "line": "restore the run value so a run that crossed the row boundary keeps storing the same byte"
  },
  "8687": {
   "unit": "game/comcenScreens6F00",
   "line": "back to the left edge of the picture row"
  },
  "8689": {
   "unit": "game/comcenScreens6F00",
   "line": "always taken (Y was just loaded with 0): continue decoding into the new row"
  },
  "868B": {
   "unit": "game/comcenScreens6F00",
   "line": "return to drawRepairTab, which then paints the colour cells over the picture"
  },
  "868C": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "==================================================================",
    "From here to the end of the overlay ($868C-$87FE) variant B contains no code at all: five user-",
    "interface captions, the 79-byte STATS tab label blob, a 12-byte word table for the film status",
    "line and 16 bit-7-terminated message strings, then six padding zeroes and the variant flag.",
    "Every routine-looking label inside this area belongs to variant A, which keeps its custom-",
    "scenario dialog code at $868C-$879E - XREF merges both variants into one namespace, so names",
    "such as setDestroyModeMessage ($86AA), showCustomSummaryMessage ($86C3), setPersistentMessage",
    "($8722), appendOnOffWord ($872E), showScenarioBannerAndWaitForButton ($8756) and",
    "printCentredTableEntry ($8795), as well as the D_/L_ labels sprinkled through the strings, mean",
    "nothing here and only mark where variant A branches or stores.  The only two interior addresses",
    "that matter with variant B resident are $8726 and $8730, both patched by the film replay key",
    "handler at $40B2.",
    "A byte compare of extracted/runtimeImage_A.bin and _B.bin shows $868C-$879E differing,",
    "$879F-$87FE identical in both variants and $87FF differing ($00 in A, $FF in B).",
    "==================================================================",
    "",
    "strDrone - 'DRONE', bit 7 on the final E.  Caption of the drones-left box on the DRONE screen,",
    "printed by overlay B code at $7018-$7033 with textColour $CF at column 32, row 7."
   ],
   "line": "'D' of DRONE"
  },
  "868D": {
   "unit": "game/comcenScreens6F00",
   "line": "'R' - label from variant A, which stores here at $8213/$86AC"
  },
  "868E": {
   "unit": "game/comcenScreens6F00",
   "line": "'ONE' with bit 7 on the E as the string terminator"
  },
  "8691": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "strLaunch - 'LAUNCH', caption under the 2x2 LAUNCH button icon of the DRONE screen; printed at",
    "$70B6-$70C5 at column 32, row 14."
   ],
   "line": "6 bytes, terminated by 'H' | $80"
  },
  "8697": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "strStrike - 'STRIKE', caption under the 2x2 STRIKE button icon of the DRONE screen; printed by",
    "the code at $70D9-$70EF with textColour $C7 at column 32, row 18."
   ],
   "line": "'STR'"
  },
  "869A": {
   "unit": "game/comcenScreens6F00",
   "line": "'I' - variant A writes here ($8221/$86BA); no meaning in variant B"
  },
  "869B": {
   "unit": "game/comcenScreens6F00",
   "line": "'KE' with bit 7 on the E"
  },
  "869D": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "strFire - 'FIRE', caption of the missile FIRE button on the main game screen; printed at",
    "$7B2E-$7B3D at column 31, row 12."
   ],
   "line": "4 bytes, terminated by 'E' | $80"
  },
  "86A1": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "strDroneAlertTwoLine - 'DRONE' + CR + 'ALERT': the two-line label of the top-right DRONE ALERT",
    "panel, printed at $7B67-$7B7A with textWindowLeft 31, column 0, row 3.  The $0D is the text",
    "engine's newline, so the two words land on consecutive rows.  What flashes is the panel colour",
    "(droneAlertColour $7B43, $02 or $20), not this text."
   ],
   "line": "'DRONE', carriage return, 'ALE'"
  },
  "86AA": {
   "unit": "game/comcenScreens6F00",
   "line": "'RT' with bit 7 on the T (terminator)"
  },
  "86AC": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "statsTabLabels - the entire left-hand label column of the console STATS tab as ONE bit-7-",
    "terminated string with embedded carriage returns ($0D), printed by a single printString call at",
    "$161D-$1621 in the main program (textWindowLeft 10, column 3, right after ownerNameString $0B7F).",
    "Each $0D drops one row, so the leading $0D lands the first label on row 9:",
    "  row 9 GRUNTS, 10 RIDERS, 11 BOOMERS, 12 SPIES, (13 blank), 14 DRONES, 15 MISSILES,",
    "  (16 blank), 17 COMSPEED, 18 TER PTS, 19 RECYCLER, 20 MAP ID.",
    "The matching numbers are printed separately by drawStatsTab ($8518) into column 9 (US) and",
    "column 14 (THEM).  79 bytes, $86AC-$86FA."
   ],
   "line": "CR, 'GRUNTS', CR, 'RIDERS', CR, 'BOOMERS', CR"
  },
  "86C3": {
   "unit": "game/comcenScreens6F00",
   "line": "'SP' - split here only because variant A has a routine entry on this byte"
  },
  "86C5": {
   "unit": "game/comcenScreens6F00",
   "line": "'IES' CR CR 'DRO' - end of the SPIES row, a blank row, then DRONES"
  },
  "86CD": {
   "unit": "game/comcenScreens6F00",
   "line": "'NES' CR 'MISS'"
  },
  "86D5": {
   "unit": "game/comcenScreens6F00",
   "line": "'ILES' CR CR 'CO' - end of MISSILES, a blank row, then COMSPEED"
  },
  "86DD": {
   "unit": "game/comcenScreens6F00",
   "line": "'MS'"
  },
  "86DF": {
   "unit": "game/comcenScreens6F00",
   "line": "'PEED' CR 'TER'"
  },
  "86E7": {
   "unit": "game/comcenScreens6F00",
   "line": "' PTS' CR 'REC'"
  },
  "86EF": {
   "unit": "game/comcenScreens6F00",
   "line": "'YCLE'"
  },
  "86F3": {
   "unit": "game/comcenScreens6F00",
   "line": "'R' CR 'MAP I' + 'D' | $80: last label, bit 7 ends the whole blob"
  },
  "86FB": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "filmViewWordTable - three fixed 4-byte words used to complete the 'SHOW ****' status line during",
    "game-film playback: 'US' (padded with two zeroes), 'THEM', 'BOTH'.  The film key handler indexes",
    "it with (playModeFlags $0B9B & 3) * 4 at $4116-$4120 and copies four bytes over $8726-$8729",
    "($4123) before queueing message 6.  Each word carries its own bit-7 terminator, so the two",
    "padding zeroes of the 'US' entry are copied but never printed.  Only indices 0-2 occur: the CLR/",
    "HOME handler wraps playModeFlags from $83 back to $80 at $4110-$4116."
   ],
   "line": "entry 0 'US' | $80 plus two pad bytes, then entry 1 'THEM' | $80"
  },
  "8703": {
   "unit": "game/comcenScreens6F00",
   "line": "entry 2 'BOTH' with bit 7 on the H"
  },
  "8707": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "Message strings.  From here on every block is one entry of the message table at $0500 (low bytes)",
    "/ $0524 (high bytes), with the sound played for it in the parallel table at $0612 ($80 = silent).",
    "Messages are queued by id through queueMessage ($C566) or queueMessageUnlessFilm ($C560), the",
    "latter dropping the message during solo-trainer or film playback.",
    "",
    "msgEndOfFilm - 'END OF FILM.', message id 2, sound 3; queued at $59FC after setFilmPtrToEnd when",
    "a recorded game film has been replayed to its last frame."
   ],
   "line": "'END OF FILM'"
  },
  "8712": {
   "unit": "game/comcenScreens6F00",
   "line": "'.' with bit 7 set = terminator"
  },
  "8713": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "msgUnitRecycled - 'UNIT RECYCLED.', message id 5, sound 4; queued with queueMessageUnlessFilm at",
    "$3EA3 after initUnitRecord, but only when the recycled unit's owner (zp_1A) is the local player."
   ],
   "line": "'U'"
  },
  "8714": {
   "unit": "game/comcenScreens6F00",
   "line": "'NIT RECY'"
  },
  "871C": {
   "unit": "game/comcenScreens6F00",
   "line": "'CLED' + '.' | $80"
  },
  "8721": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "msgShowViewTemplate - 'SHOW ****', message id 6, sound 3: the film playback status line.  The",
    "four '*' at $8726-$8729 are overwritten with a filmViewWordTable entry (US / THEM / BOTH) at",
    "$4123 before the message is queued at $412E, and that entry supplies the bit-7 terminator.",
    "As stored on disk the template has NO terminator, so printing it unpatched would run straight on",
    "through 'SPEED *' and only stop at the '!' of 'SPOTTED RECYCLER!' ($8741)."
   ],
   "line": "'S'"
  },
  "8722": {
   "unit": "game/comcenScreens6F00",
   "line": "'HOW ' - the space before the patched word"
  },
  "8726": {
   "unit": "game/comcenScreens6F00",
   "line": "four-byte patch field: receives 'US'+$80+2 pad / 'THEM'|$80 / 'BOTH'|$80 from filmViewWordTable, written by the film key handler at $4123"
  },
  "872A": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "msgSpeedTemplate - 'SPEED *', message id 7, sound 3: shown when a digit key changes the film",
    "replay speed.  Pressing '1'-'9' sets stepIntervalFrames $92E3 = 8 - (key - $B1) and stores the raw",
    "key code $B1-$B9 into $8730 at $40DE; because those codes already have bit 7 set they double as",
    "the string terminator.  Unterminated as stored on disk, like the SHOW template above."
   ],
   "line": "'SPEE'"
  },
  "872E": {
   "unit": "game/comcenScreens6F00",
   "line": "'D' and a space"
  },
  "8730": {
   "unit": "game/comcenScreens6F00",
   "line": "one-byte patch field: the pressed digit's key code $B1-$B9 (screen code with bit 7 = terminator), written at $40DE"
  },
  "8731": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "msgSpottedRecycler - 'SPOTTED RECYCLER!', message id 8, sound 4; queued with",
    "queueMessageUnlessFilm at $3F8C right after drawRecyclerOnMap / drawMapCell, i.e. the moment the",
    "recycler cell first becomes visible to the local player."
   ],
   "line": "'SPO'"
  },
  "8734": {
   "unit": "game/comcenScreens6F00",
   "line": "'TTED REC'"
  },
  "873C": {
   "unit": "game/comcenScreens6F00",
   "line": "'YCLE'"
  },
  "8740": {
   "unit": "game/comcenScreens6F00",
   "line": "'R' + '!' | $80"
  },
  "8742": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "msgComcenStunned - 'COMCEN STUNNED!', message id $0C, sound 4; queued at $0741-$0743 by the",
    "game-tick code in the $0400 block when $9162 reads $FF.  The same text is also printed straight",
    "into the console status area at column 5 by $17C4-$17D4 for as long as comcenStunnedFlag ($922A)",
    "bit 7 is set."
   ],
   "line": "15 bytes, terminated by '!' | $80"
  },
  "8751": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "msgSpottedEnemyComcen - 'SPOTTED ENEMY COMCEN!', message id $0D, sound 4; queued with",
    "queueMessageUnlessFilm at $36EF when a newly visible enemy unit's type field ($F76C,y & 7) is 4",
    "(COMCEN).  Types 0-2 (GRUNT, RIDER, BOOMER) are spotted silently."
   ],
   "line": "'SPOT'"
  },
  "8755": {
   "unit": "game/comcenScreens6F00",
   "line": "'T'"
  },
  "8756": {
   "unit": "game/comcenScreens6F00",
   "line": "'ED ENEMY'"
  },
  "875E": {
   "unit": "game/comcenScreens6F00",
   "line": "' COMCEN'"
  },
  "8765": {
   "unit": "game/comcenScreens6F00",
   "line": "'!' | $80 terminator"
  },
  "8766": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "msgSpyUnderAttack - 'SPY UNDER ATTACK!', message id $0E, sound 4; queued with",
    "queueMessageUnlessFilm at $3B3B when the unit being hit is type 3 (SPY) and its energy field",
    "($F9C4,y & $3F) is still $32 = 50, i.e. full - so it fires once, on the first hit only."
   ],
   "line": "17 bytes, terminated by '!' | $80"
  },
  "8777": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "msgSpottedEnemySpy - 'SPOTTED ENEMY SPY!', message id $0F, sound 4; queued with",
    "queueMessageUnlessFilm at $36E7, the sibling of the COMCEN case above in the visibility code."
   ],
   "line": "18 bytes, terminated by '!' | $80"
  },
  "8789": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "The five end-of-game result messages.  computeGameResult ($28F1) picks one of them into",
    "persistentMessageId and queues it at $29A0.",
    "",
    "msgWeLostOnPoints - 'WE LOST ON POINTS!', message id $11, sound $0E: the time-out result when the",
    "LOSING side ($2963-$296B: X, the side whose accumulated damage score is higher) is the local",
    "player.  If it is the other side the code does INY and picks $12 instead.",
    "Variant A stores 22 bytes of its own code over $8789-$879E, so this string is only intact while",
    "variant B is resident - which is fine, because results are only shown during the battle."
   ],
   "line": "'W'"
  },
  "878A": {
   "unit": "game/comcenScreens6F00",
   "line": "'E LOST O'"
  },
  "8792": {
   "unit": "game/comcenScreens6F00",
   "line": "'N P'"
  },
  "8795": {
   "unit": "game/comcenScreens6F00",
   "line": "'OINTS' + '!' | $80"
  },
  "879B": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "msgWeWonOnPoints - 'WE WON ON POINTS!', message id $12, sound $0D: the time-out win (losing side",
    "!= local player).  Its first four bytes 'WE W' ($879B-$879E) are the last of the bytes variant A",
    "overlays; from $879F on the two overlay variants are byte-identical to the end of the block."
   ],
   "line": "'WE W' - the last four bytes that variant A clobbers"
  },
  "879F": {
   "unit": "game/comcenScreens6F00",
   "line": "'ON ON POINTS' + '!' | $80; identical in both overlay variants"
  },
  "87AC": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "msgItWasADraw - 'IT WAS A DRAW.', message id $13 and the only result message with no sound",
    "($80 in the sound table).  Chosen when the 16-bit score difference is exactly zero at time-out",
    "($2917-$2929) or when gameOverFlags bits 5-6 are both set, i.e. both comcens were knocked out",
    "($297A); both sides are then given 250 points."
   ],
   "line": "14 bytes, terminated by '.' | $80"
  },
  "87BA": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "msgWeWereKnockedOut - 'WE WERE KNOCKED OUT!', message id $14, sound $0E; selected at $298D-$2995",
    "when the knocked-out side equals the local player.  The loser is given 500 points, the winner",
    "150."
   ],
   "line": "20 bytes, terminated by '!' | $80"
  },
  "87CE": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "msgWeWonByAKnockout - 'WE WON BY A KNOCKOUT!', message id $15, sound $0D: the enemy comcen was",
    "destroyed (the INY sibling of message $14).",
    "The solo trainer's stores to 'msgWeWonByAKnockout,y' at $EB29/$EB53 do NOT corrupt this string:",
    "there Y is an absolute unit index of the AI side, 50-99, so the writes land at $8800-$8831 - the",
    "trainer's per-unit order-timestamp table, which it addresses through the base $87CE."
   ],
   "line": "21 bytes, terminated by '!' | $80"
  },
  "87E3": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "msgGameOver - 'GAME OVER.', message id $16, sound $19; queued first at $2901-$2903 on the",
    "time-out path, ahead of the points result message."
   ],
   "line": "10 bytes, terminated by '.' | $80"
  },
  "87ED": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "msgDroneAlert - 'DRONE ALERT!'.  This one has no fixed id: overlay A installs its address into",
    "run-time message slot $20 at $7BA6-$7BAC (ldx #$20 / lda #$ED / ldy #$87 / jsr setMessageSlot",
    "$C924) just before jumping into the opponent module's battle-start entry.  The pointer survives",
    "the swap to overlay B because this string sits in the tail that both variants hold identically.",
    "During the battle showDroneAlert ($820F) plays sound 7 and queues id $20 whenever the opponent",
    "launches a drone, unless playModeFlags $0B9B bit 7 (solo trainer / film playback) is set."
   ],
   "line": "12 bytes, terminated by '!' | $80"
  },
  "87F9": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "overlayTailPadding - six unused $00 bytes between the last string and the variant flag.  Nothing",
    "reads them; they are identical in both overlay variants and exist only to push the flag to the",
    "very last byte of the block."
   ],
   "line": "filler up to $87FE"
  },
  "87FF": {
   "unit": "game/comcenScreens6F00",
   "block": [
    "overlayVariantFlag - identity byte of whichever $6F00-$87FF overlay is currently resident.  The",
    "overlay loader tests bit 7 at $1036 and $1080 to decide whether the variant it wants is already",
    "in memory or has to be pulled from disk (variant A = track 31 s0-16 + track 32 s0-7, variant B =",
    "track 33 s0-16 + track 32 s8-15)."
   ],
   "line": "$FF = variant B (comcen missile and drone screens) is resident; variant A (map generator and setup) stores $00 here"
  }
 }
}
