modemwars/annotations/20_game_mainProgram0800_515D.json
2026-08-23 02:09:40 -05:00

3418 lines
113 KiB
JSON

{
"chunk": "game_mainProgram0800_515D",
"unit": "game/mainProgram0800",
"unitLabels": {
"game/mainProgram0800": {
"538B": "cmdDroneShotDown"
}
},
"notes": {
"515D": {
"unit": "game/mainProgram0800",
"line": "read the single argument byte into cmdParam0 and step mergedCmdReadIndex past this 2-byte command",
"routine": [
"cmdClearTarget - Handler of command $86 CLEAR TARGET (2 bytes: BOOMER unit). getBoomerTarget turns the unit",
"index into that unit's ordinal among all BOOMERs and the matching entry of boomerTargetTable is zeroed, so",
"the BOOMER stops holding fire for a particular enemy. The carry that getBoomerTarget returns is not tested,",
"so a non-BOOMER argument would clear a slot chosen by a leftover Y; only the unit menu of a BOOMER offers",
"the item, so that never happens in practice.",
"In: mergedCmdBuffer $908F / mergedCmdReadIndex $90B7 (the queued command), cmdParam0 after readCmdArg1",
"Out: boomerTargetTable $91DC[ordinal] = 0; mergedCmdReadIndex += 2; X = unit, Y = ordinal",
"Called from: commandHandlerTable slot for $86 ($4D36), dispatched by executeTurnCommands at $4EEE"
]
},
"5160": {
"unit": "game/mainProgram0800",
"line": "X = the BOOMER's unit index"
},
"5162": {
"unit": "game/mainProgram0800",
"line": "$3C74: for a BOOMER (unitTypeTable & 7 == 2) returns Y = its ordinal among all BOOMERs"
},
"5165": {
"unit": "game/mainProgram0800",
"line": "0 = no target and no lock"
},
"5167": {
"unit": "game/mainProgram0800",
"line": "clear this BOOMER's slot of the 20-entry boomerTargetTable at $91DC"
},
"516A": {
"unit": "game/mainProgram0800",
"line": "done - CLEAR TARGET touches nothing but the target table"
},
"516B": {
"unit": "game/mainProgram0800",
"line": "read the two arguments: cmdParam1 = the BOOMER, cmdParam2 = the target unit; read index += 3",
"routine": [
"cmdSetTarget - Handler of command $87 SET TARGET (3 bytes: BOOMER unit, target unit). It only rearranges",
"the parameter bytes so that setBoomerTargetCmd ($3C50) finds the BOOMER in cmdParam0 and the target in",
"cmdParam2, then jumps there; the result is boomerTargetTable[BOOMER ordinal] = target | $80. The command is",
"sent by sub_3C20 when the player puts the cursor on an enemy unit with a BOOMER selected. The EOR #$80 on",
"cmdFromOpponent at $5171 is undone again at $517C without anything reading it in between, and the store at",
"$5177 is immediately overwritten at $5180 - both are leftovers from an earlier version.",
"In: the queued command in mergedCmdBuffer; cmdParam1 = BOOMER, cmdParam2 = target after readCmdArgs2",
"Out: boomerTargetTable $91DC[ordinal] = target | $80 via $3C50; cmdParam0 = BOOMER; read index += 3",
"Called from: commandHandlerTable slot for $87 ($4D38)"
]
},
"516E": {
"unit": "game/mainProgram0800",
"line": "take a copy of the own/remote flag of the command being executed"
},
"5170": {
"unit": "game/mainProgram0800",
"line": "push it"
},
"5171": {
"unit": "game/mainProgram0800",
"line": "flip bit 7 = 'this command came from the opponent' ..."
},
"5173": {
"unit": "game/mainProgram0800",
"line": "... and store the flipped value back; nothing between here and $517C looks at it, so the flip does nothing (leftover)"
},
"5175": {
"unit": "game/mainProgram0800",
"line": "A = the target unit index"
},
"5177": {
"unit": "game/mainProgram0800",
"line": "cmdParam0 = target - overwritten again at $5180, so this store is dead"
},
"5179": {
"unit": "game/mainProgram0800",
"line": "write cmdParam2 back over itself - a no-op"
},
"517B": {
"unit": "game/mainProgram0800",
"line": "pull the untouched own/remote flag"
},
"517C": {
"unit": "game/mainProgram0800",
"line": "undo the EOR of $5171"
},
"517E": {
"unit": "game/mainProgram0800",
"line": "A = the BOOMER's unit index"
},
"5180": {
"unit": "game/mainProgram0800",
"line": "setBoomerTargetCmd wants the BOOMER in cmdParam0 and the target in cmdParam2"
},
"5182": {
"unit": "game/mainProgram0800",
"line": "$3C50: boomerTargetTable[BOOMER ordinal] = cmdParam2 | $80 (target unit plus the 'locked' bit)"
},
"5185": {
"unit": "game/mainProgram0800",
"line": "read the two arguments: cmdParam1 = unit index, cmdParam2 = group byte (id in bits 2-4, side in bit 5)",
"routine": [
"cmdJoinGroup - Handler of command $88 JOIN GROUP (3 bytes: unit, group byte). It jumps to joinUnitToGroup",
"($4AD4), which makes the unit follow the group's movement and sets unitFlagsTable = (old & 3) | group | $40.",
"The eight instructions at $5188-$5197 are a shuffle whose only lasting effect is cmdParam0 = group byte, and",
"nothing downstream reads cmdParam0 - the whole block is dead code.",
"In: the queued command; cmdParam1 = unit, cmdParam2 = group byte (id bits 2-4, side bit 5)",
"Out: unitFlagsTable $F708[unit] and unitDisplayFlagsTable $FB54[unit] via $4AD4; read index += 3",
"Called from: commandHandlerTable slot for $88 ($4D3A)"
]
},
"5188": {
"unit": "game/mainProgram0800",
"line": "A = the unit index",
"block": [
"Eight instructions of register shuffling whose only lasting effect is cmdParam0 = the group byte, and",
"nothing reads cmdParam0 afterwards. Dead code left over from an earlier calling convention."
]
},
"518A": {
"unit": "game/mainProgram0800",
"line": "cmdParam0 = unit - overwritten again at $5192"
},
"518C": {
"unit": "game/mainProgram0800",
"line": "read the same byte straight back"
},
"518E": {
"unit": "game/mainProgram0800",
"line": "store the unit index back into cmdParam1 - a no-op"
},
"5190": {
"unit": "game/mainProgram0800",
"line": "A = the group byte"
},
"5192": {
"unit": "game/mainProgram0800",
"line": "cmdParam0 = group byte; nothing downstream ever reads cmdParam0, so even this is pointless"
},
"5194": {
"unit": "game/mainProgram0800",
"line": "read it back again"
},
"5196": {
"unit": "game/mainProgram0800",
"line": "store the group byte back into cmdParam2 - another no-op"
},
"5198": {
"unit": "game/mainProgram0800",
"line": "$4AD4: make unit cmdParam1 follow group cmdParam2 and set unitFlagsTable = (old & 3) | group | $40"
},
"519B": {
"unit": "game/mainProgram0800",
"line": "cmdParam0 = the unit index; read index += 2",
"routine": [
"cmdLeaveGroup - Handler of command $89 (2 bytes: unit): clears bit 6 ('in a group') of the unit's",
"unitFlagsTable entry and tail-jumps to syncLocalGroupFlags ($4C6B), which mirrors bits 2-6 into",
"unitDisplayFlagsTable so the overview map stops drawing the unit's group marker. Sent by",
"removeSelectedUnitFromGroup ($4AF2), the GROUP menu item REMOVED.",
"In: the queued command; cmdParam0 = unit index",
"Out: unitFlagsTable $F708[unit] &= $BF, unitDisplayFlagsTable $FB54[unit] refreshed; read index += 2",
"Called from: commandHandlerTable slot for $89 ($4D3C)"
]
},
"519E": {
"unit": "game/mainProgram0800",
"line": "X = the unit"
},
"51A0": {
"unit": "game/mainProgram0800",
"line": "$F708 flags: facing bits 0-1, group id bits 2-4, side bit 5, 'in a group' bit 6"
},
"51A3": {
"unit": "game/mainProgram0800",
"line": "clear bit 6 - the unit no longer belongs to a group"
},
"51A5": {
"unit": "game/mainProgram0800",
"line": "write the flags back"
},
"51A8": {
"unit": "game/mainProgram0800",
"line": "$4C6B: copy bits 2-6 into unitDisplayFlagsTable so the map stops drawing the group marker"
},
"51AB": {
"unit": "game/mainProgram0800",
"line": "read three arguments: cmdParam0 = unit, cmdParam1 = column, cmdParam2 = row; read index += 4",
"routine": [
"cmdPlaceUnit - Handler of command $8A (4 bytes: unit, column, row): teleports a unit during the deployment",
"phase. The unit's cloak bits (unitPathColTable & $C0) and blitz bits (unitPathRowTable & $C0) are first",
"saved into the ORA operands at $51C8 and $51E4, then the new column is written to unitColTable, to",
"unitPathColTable (keeping the cloak state) and to unitDestColTable with the 'arrived' bit 7, bit 7 of",
"unitDisplayFlagsTable is set, and the new row goes to unitRowTable, unitDestRowTable and unitPathRowTable",
"(keeping the blitz state). The $F000 map itself is not touched here.",
"In: the queued command; cmdParam0 = unit, cmdParam1/cmdParam2 = column/row (mirrored when remote)",
"Out: $F640, $F6A4, $F7D0, $F834, $F898, $F8FC and $FB54 of the unit; self-modifies $51C9 and $51E5",
"Called from: commandHandlerTable slot for $8A ($4D3E)"
]
},
"51AE": {
"unit": "game/mainProgram0800",
"line": "a remote player sends his own coordinates: convert them with 39-v so both machines agree"
},
"51B1": {
"unit": "game/mainProgram0800",
"line": "Y = the unit index into every one of the $64-spaced unit arrays"
},
"51B3": {
"unit": "game/mainProgram0800",
"line": "$F898 waypoint column: bits 0-5 column, bits 6-7 = cloak ordered / cloak active",
"block": [
"Save the two flag pairs that live in the top bits of the waypoint bytes, so the rewrite below can put them",
"back: cloak in unitPathColTable bits 6-7, blitz in unitPathRowTable bits 6-7."
]
},
"51B6": {
"unit": "game/mainProgram0800",
"line": "keep only the two cloak bits"
},
"51B8": {
"unit": "game/mainProgram0800",
"line": "patch them into the ORA immediate at $51C8 so the rewrite below preserves them"
},
"51BB": {
"unit": "game/mainProgram0800",
"line": "$F8FC waypoint row: bits 0-5 row, bits 6-7 = blitz ordered / blitz active"
},
"51BE": {
"unit": "game/mainProgram0800",
"line": "keep only the two blitz bits"
},
"51C0": {
"unit": "game/mainProgram0800",
"line": "patch them into the ORA immediate at $51E4"
},
"51C3": {
"unit": "game/mainProgram0800",
"line": "A = the new map column",
"block": [
"Column: current position, waypoint (cloak preserved) and destination (marked as already reached)."
]
},
"51C5": {
"unit": "game/mainProgram0800",
"line": "$F640 = the unit's current column"
},
"51C8": {
"unit": "game/mainProgram0800",
"line": "immediate operand patched at $51B8 with the saved cloak bits"
},
"51CA": {
"unit": "game/mainProgram0800",
"line": "waypoint column = the new column with the cloak state untouched"
},
"51CD": {
"unit": "game/mainProgram0800",
"line": "strip the cloak bits off again"
},
"51CF": {
"unit": "game/mainProgram0800",
"line": "bit 7 of unitDestColTable means 'already standing on its destination'"
},
"51D1": {
"unit": "game/mainProgram0800",
"line": "$F7D0 destination column = the new column, marked as reached"
},
"51D4": {
"unit": "game/mainProgram0800",
"line": "$FB54 draw flags of the unit"
},
"51D7": {
"unit": "game/mainProgram0800",
"line": "bit 7 = idle / at destination, the copy the drawing code looks at"
},
"51D9": {
"unit": "game/mainProgram0800",
"line": "write the draw flags back"
},
"51DC": {
"unit": "game/mainProgram0800",
"line": "A = the new map row",
"block": [
"Row: current position, destination and waypoint (blitz preserved)."
]
},
"51DE": {
"unit": "game/mainProgram0800",
"line": "$F6A4 = the unit's current row"
},
"51E1": {
"unit": "game/mainProgram0800",
"line": "$F834 destination row = the same row (rows carry no flag bit)"
},
"51E4": {
"unit": "game/mainProgram0800",
"line": "immediate operand patched at $51C0 with the saved blitz bits"
},
"51E6": {
"unit": "game/mainProgram0800",
"line": "waypoint row = the new row with the blitz state untouched"
},
"51E9": {
"unit": "game/mainProgram0800",
"line": "note that the $F000 map cell is never updated here - the setup screen redraws the board itself"
},
"51EA": {
"unit": "game/mainProgram0800",
"line": "command $A6: the sender chose the flipped map variant",
"routine": [
"cmdSetCustomMapSeed - Handler of command $A6 (4 bytes: three seed bytes): sets mapFlipFlag ($0B9D) = 1 and",
"falls into cmdSetMapSeed. Overlay A sends $A6 instead of $8B at $79BD when the player picked the flipped",
"(180-degree rotated) view of the map, so both machines build the battlefield the same way round.",
"In: the queued command",
"Out: mapFlipFlag = 1, then everything cmdSetMapSeed does",
"Called from: the last entry of commandHandlerTable ($4D76, command $A6)"
]
},
"51EC": {
"unit": "game/mainProgram0800",
"line": "always taken - join the shared body"
},
"51EE": {
"unit": "game/mainProgram0800",
"line": "command $8B: the map in its generated orientation",
"routine": [
"cmdSetMapSeed - Handler of command $8B (4 bytes: three seed bytes): mapFlipFlag = 0, the three arguments are",
"copied into the 24-bit map seed $0B95-$0B97, and bit 7 of gamePhase is cleared. The player who owns the map",
"sends this at the start of a session; the other machine's waitForMapSeedCommand ($7C1C) spins on gamePhase",
"bit 7 until this handler runs, after which both generate the identical 40x40 battlefield.",
"In: the queued command; cmdParam0/1/2 = the seed bytes after readCmdArgs3",
"Out: mapFlipFlag $0B9D, mapSeed $0B95-$0B97, gamePhase zp_B1 &= $7F; read index += 4",
"Called from: commandHandlerTable slot for $8B ($4D40), and by fall-through from cmdSetCustomMapSeed"
]
},
"51F0": {
"unit": "game/mainProgram0800",
"line": "$0B9D: 0 = map as generated, 1 = rotated 180 degrees (the F1 TO FLIP option on the map screen)"
},
"51F3": {
"unit": "game/mainProgram0800",
"line": "read the three seed bytes into cmdParam0/1/2; read index += 4"
},
"51F6": {
"unit": "game/mainProgram0800",
"line": "seed byte 0"
},
"51F8": {
"unit": "game/mainProgram0800",
"line": "$0B95: byte 0 of the 24-bit battlefield seed, which is the 5-letter map name packed 5 bits per letter"
},
"51FB": {
"unit": "game/mainProgram0800",
"line": "seed byte 1"
},
"51FD": {
"unit": "game/mainProgram0800",
"line": "$0B96"
},
"5200": {
"unit": "game/mainProgram0800",
"line": "seed byte 2"
},
"5202": {
"unit": "game/mainProgram0800",
"line": "$0B97"
},
"5205": {
"unit": "game/mainProgram0800",
"line": "zp_B1 game phase"
},
"5207": {
"unit": "game/mainProgram0800",
"line": "clear bit 7 = 'still waiting for the map seed'"
},
"5209": {
"unit": "game/mainProgram0800",
"line": "this releases waitForMapSeedCommand ($7C1C) on the receiving machine"
},
"520B": {
"unit": "game/mainProgram0800",
"line": "both machines now hold the same seed and will generate the identical battlefield"
},
"520C": {
"unit": "game/mainProgram0800",
"line": "a 1-byte command: just step the read index over it",
"routine": [
"cmdComcenRepairedOrSetupReady - Handler of the 1-byte command $9E, which means two different things. While",
"the battle runs (gamePhase = 0) it is the result of a successful COMCEN repair from the REPAIR console tab",
"($1812): the stun counter in the low nibble of the sending side's COMCEN unitStunTable entry is cleared.",
"In every other phase it is the READY / phase-sync acknowledgement of sendPhaseSyncAndWait ($7BF9): the",
"sender's bit of gamePhase is cleared with getPauseClearMask, input is locked out for 32 frames, a fake",
"SPACE key event is injected so any 'press space' loop falls through, and the chat state is reset.",
"In: gamePhase zp_B1, cmdFromOpponent zp_76, playerSide $0B9F, perSideValueTable $0C8A",
"Out: battle: unitStunTable $F960[comcen] &= $F0. Setup: zp_B1 masked, $0B7D = $20, $90EA = $20, $929C = 0.",
" mergedCmdReadIndex += 1 in both cases",
"Called from: commandHandlerTable slot for $9E ($4D66)"
]
},
"520F": {
"unit": "game/mainProgram0800",
"line": "zp_B1 is zero only while the battle itself is running"
},
"5211": {
"unit": "game/mainProgram0800",
"line": "battle: this is a successful COMCEN repair"
},
"5213": {
"unit": "game/mainProgram0800",
"line": "setup: A = the AND mask that clears the sending side's 'not ready' bit"
},
"5216": {
"unit": "game/mainProgram0800",
"line": "apply it to the phase byte"
},
"5218": {
"unit": "game/mainProgram0800",
"line": "once both bits are gone sendPhaseSyncAndWait ($7BF9) stops waiting"
},
"521A": {
"unit": "game/mainProgram0800",
"line": "$20 = 32"
},
"521C": {
"unit": "game/mainProgram0800",
"line": "$0B7D: ignore keyboard and joystick for the next 32 frames"
},
"521F": {
"unit": "game/mainProgram0800",
"line": "inject key code $20 (SPACE) so any 'press space' wait loop falls through at once"
},
"5222": {
"unit": "game/mainProgram0800",
"line": "0"
},
"5224": {
"unit": "game/mainProgram0800",
"line": "$929C: abandon any chat line that was being typed"
},
"5227": {
"unit": "game/mainProgram0800",
"line": "phase sync done"
},
"5228": {
"unit": "game/mainProgram0800",
"line": "$0B9F = the side this machine plays, 0 or 1",
"block": [
"Battle phase: command $9E means the sending side's COMCEN has finished repairing itself, so clear its",
"stun counter."
]
},
"522B": {
"unit": "game/mainProgram0800",
"line": "who sent the repair?"
},
"522D": {
"unit": "game/mainProgram0800",
"line": "our own command: our own COMCEN was repaired"
},
"522F": {
"unit": "game/mainProgram0800",
"line": "remote command: the other side's COMCEN"
},
"5231": {
"unit": "game/mainProgram0800",
"line": "X = the sender's absolute side"
},
"5232": {
"unit": "game/mainProgram0800",
"line": "$0C8A = {$31,$63}: unit 49 is side 0's COMCEN, unit 99 is side 1's"
},
"5235": {
"unit": "game/mainProgram0800",
"line": "$F960: low nibble = stun/knock-out counter, bit 4 spare, bits 5-7 = dig state"
},
"5238": {
"unit": "game/mainProgram0800",
"line": "clear the stun counter - the COMCEN is back on line"
},
"523A": {
"unit": "game/mainProgram0800",
"line": "write it back, leaving the dig bits alone"
},
"523D": {
"unit": "game/mainProgram0800",
"line": "done"
},
"523E": {
"unit": "game/mainProgram0800",
"line": "this handler runs inside the packet merge loop, so stash its X and Y into the operands at $532B/$532D",
"routine": [
"cmdSessionControl - Handler of command $8C (2 bytes: reason). It is never dispatched out of the merged",
"buffer: both packet mergers call it directly ($4F36 and $4F9C) with A = the reason byte while the packets",
"are being merged, so the effect is immediate and the bytes are not copied into the film. Reason 0 = the game",
"is being abandoned: gameOverFlags = $C0, the message queue is cleared, 'GAME ABORTED.' is shown for a remote",
"abort, the end-of-game countdown is armed, and the dispatcher is abandoned by pulling two return addresses",
"off the stack. Reasons 1 and 2 return at once ($8C,$02 is the menu READY packet handled elsewhere). Reason 3",
"= link timeout, reason 5 = deliberate hang-up, anything else (4) = resume. Everything except reason 0 is",
"skipped during film playback.",
"In: A = reason byte, X/Y = the merge loop's registers, cmdFromOpponent zp_76, $0B9B, $0BA8",
"Out: pauseState zp_BE, $92A1, $0B7D, $929C, $92C9, $923F, zp_72, the message queue; returns through $532A",
" with X/Y restored, or never returns at all for reason 0",
"Called from: mergeIncomingPacket $4F36 and mergeOutgoingPacket $4F9C"
]
},
"5241": {
"unit": "game/mainProgram0800",
"line": "A holds the reason byte the merger fetched out of the packet"
},
"5243": {
"unit": "game/mainProgram0800",
"line": "reasons 3 and up are the pause / hang-up / resume group"
},
"5245": {
"unit": "game/mainProgram0800",
"line": "branch to the pause group"
},
"5247": {
"unit": "game/mainProgram0800",
"line": "reason 0 = the game is being abandoned"
},
"5249": {
"unit": "game/mainProgram0800",
"line": "handle the abort"
},
"524B": {
"unit": "game/mainProgram0800",
"line": "reason 1 is unused; reason 2 is the menu-level READY packet handled by readyForNewGameHandshake"
},
"524C": {
"unit": "game/mainProgram0800",
"line": "$C0 = game finished, no result computed",
"block": [
"Reason 0 - the game is being abandoned, by us or by the opponent."
]
},
"524E": {
"unit": "game/mainProgram0800",
"line": "$923F game-over flags"
},
"5251": {
"unit": "game/mainProgram0800",
"line": "98 ticks of end-of-game countdown when no battle was running"
},
"5253": {
"unit": "game/mainProgram0800",
"line": "$0BA8 is non-zero whenever no battle is in progress"
},
"5256": {
"unit": "game/mainProgram0800",
"line": "no battle: keep the $62 countdown and say nothing"
},
"5258": {
"unit": "game/mainProgram0800",
"line": "a battle was running: throw away everything queued for the status line"
},
"525B": {
"unit": "game/mainProgram0800",
"line": "who aborted?"
},
"525D": {
"unit": "game/mainProgram0800",
"line": "our own abort - the local menu code has already set the countdown"
},
"525F": {
"unit": "game/mainProgram0800",
"line": "message $03"
},
"5261": {
"unit": "game/mainProgram0800",
"line": "'GAME ABORTED.' - the opponent quit on us"
},
"5264": {
"unit": "game/mainProgram0800",
"line": "113 ticks so the message can be read before the menu returns"
},
"5266": {
"unit": "game/mainProgram0800",
"line": "$92C9 counts down to the return to the main menu"
},
"5269": {
"unit": "game/mainProgram0800",
"line": "discard the return address of this handler ...",
"block": [
"Abandon the whole dispatcher. This handler was entered as JSR cmdSessionControl from inside",
"mergeIncomingPacket/mergeOutgoingPacket, which themselves were entered as JSR from serviceCommandExchange,",
"so four PLAs drop both return addresses and the JMP below returns straight to serviceCommandExchange's",
"own caller."
]
},
"526A": {
"unit": "game/mainProgram0800",
"line": "(second byte)"
},
"526B": {
"unit": "game/mainProgram0800",
"line": "... and that of mergeIncomingPacket / mergeOutgoingPacket ..."
},
"526C": {
"unit": "game/mainProgram0800",
"line": "... so the RTS below lands in the caller of serviceCommandExchange instead"
},
"526D": {
"unit": "game/mainProgram0800",
"line": "comm function 2 = acknowledge and consume the finished exchange"
},
"526F": {
"unit": "game/mainProgram0800",
"line": "$E000; its RTS unwinds two levels because of the four PLAs above"
},
"5272": {
"unit": "game/mainProgram0800",
"line": "$0B9B bit 7 = a game film is being replayed",
"block": [
"Reasons 3 and up - the pause, hang-up and resume group. All of them are ignored during film playback."
]
},
"5275": {
"unit": "game/mainProgram0800",
"line": "during playback the pause / resume traffic is ignored"
},
"5277": {
"unit": "game/mainProgram0800",
"line": "0"
},
"5279": {
"unit": "game/mainProgram0800",
"line": "cancel any chat line being typed"
},
"527C": {
"unit": "game/mainProgram0800",
"line": "$3C = 60 frames, about one second"
},
"527E": {
"unit": "game/mainProgram0800",
"line": "hold the player's input off while the pause state changes"
},
"5281": {
"unit": "game/mainProgram0800",
"line": "reason 3 = link timeout"
},
"5283": {
"unit": "game/mainProgram0800",
"line": "not the timeout reason"
},
"5285": {
"unit": "game/mainProgram0800",
"line": "zp_BE = 3: both sides counted as paused"
},
"5287": {
"unit": "game/mainProgram0800",
"line": "message $19"
},
"5289": {
"unit": "game/mainProgram0800",
"line": "'TIMEOUT. RUN/STOP TO RESUME.' stays on the line until someone resumes"
},
"528C": {
"unit": "game/mainProgram0800",
"line": "always taken - go and queue the message"
},
"528E": {
"unit": "game/mainProgram0800",
"line": "reason 5 = the link was hung up on purpose"
},
"5290": {
"unit": "game/mainProgram0800",
"line": "not the hang-up reason"
},
"5292": {
"unit": "game/mainProgram0800",
"line": "bit 7 of pauseState = link dropped (DISCONNECT / SET VOICE)"
},
"5294": {
"unit": "game/mainProgram0800",
"line": "always taken"
},
"5296": {
"unit": "game/mainProgram0800",
"line": "any other reason (4 = RESUME) clears only the sender's own bit"
},
"5298": {
"unit": "game/mainProgram0800",
"line": "A = $FD for our resume, $FE for his, $FC in a solo game"
},
"529B": {
"unit": "game/mainProgram0800",
"line": "clear that bit of the pause state"
},
"529D": {
"unit": "game/mainProgram0800",
"line": "store the new pause state"
},
"529F": {
"unit": "game/mainProgram0800",
"line": "someone is still paused - leave the message up"
},
"52A1": {
"unit": "game/mainProgram0800",
"line": "A = 0: no persistent message any more"
},
"52A4": {
"unit": "game/mainProgram0800",
"line": "zp_72 = 0 so the next message appears immediately"
},
"52A6": {
"unit": "game/mainProgram0800",
"line": "message $1B 'RESUMED'"
},
"52A8": {
"unit": "game/mainProgram0800",
"line": "put it on the status line"
},
"52AB": {
"unit": "game/mainProgram0800",
"line": "leave through the shared tail that restores the merge loop's X and Y"
},
"52AE": {
"unit": "game/mainProgram0800",
"line": "mask that clears both pause bits at once",
"routine": [
"getPauseClearMask - Returns the AND mask that clears the resuming side's bit of a two-bit ready/pause byte.",
"cmdSessionControl applies it to pauseState (zp_BE) and cmdComcenRepairedOrSetupReady applies it to gamePhase",
"(zp_B1). In a solo-trainer game there is no independent opponent, so both bits are cleared at once ($FC);",
"otherwise an own command clears bit 0 ($FD) and a remote command clears bit 1 ($FE), and in the remote case",
"the message 'OPPONENT READY.' is queued if his bit was still set.",
"In: X = the pause/phase byte before this command, isSoloTrainer $0BA5, cmdFromOpponent zp_76",
"Out: A = $FC, $FD or $FE; may queue message $04; Y clobbered",
"Called from: cmdSessionControl $5298 and cmdComcenRepairedOrSetupReady $5213"
]
},
"52B0": {
"unit": "game/mainProgram0800",
"line": "$0BA5 bit 7 = PRACTICE WITH SOLO TRAINER"
},
"52B3": {
"unit": "game/mainProgram0800",
"line": "solo: there is no independent opponent, so clear both bits together"
},
"52B5": {
"unit": "game/mainProgram0800",
"line": "mask that clears bit 0 = this machine's bit"
},
"52B7": {
"unit": "game/mainProgram0800",
"line": "who sent the command?"
},
"52B9": {
"unit": "game/mainProgram0800",
"line": "our own command: bit 0 is the one to clear"
},
"52BB": {
"unit": "game/mainProgram0800",
"line": "A = the pause/phase byte as it was before this command (passed in X)"
},
"52BC": {
"unit": "game/mainProgram0800",
"line": "bit 1 = the opponent was still flagged as not ready"
},
"52BE": {
"unit": "game/mainProgram0800",
"line": "he was already ready - nothing to announce"
},
"52C0": {
"unit": "game/mainProgram0800",
"line": "message $04"
},
"52C2": {
"unit": "game/mainProgram0800",
"line": "'OPPONENT READY.'"
},
"52C5": {
"unit": "game/mainProgram0800",
"line": "mask that clears bit 1 = the opponent's bit"
},
"52C7": {
"unit": "game/mainProgram0800",
"line": "A = the AND mask"
},
"52C8": {
"unit": "game/mainProgram0800",
"line": "read straight out of the received packet at $E020; Y points at the heading byte",
"routine": [
"cmdDroneMove - Handler of command $8D (4 bytes: heading, column, row), the opponent's live drone telemetry.",
"Only mergeIncomingPacket calls it, with Y pointing at the heading byte inside the received packet $E020;",
"mergeOutgoingPacket deals with our own $8D by re-centring the tactical view instead, which is why the",
"commandHandlerTable slot for $8D is a dummy - the byte never reaches the merged buffer. The handler mirrors",
"the 2x2 drone origin into our coordinate frame and, while the COMCEN radar screen is showing and the drone",
"has not already finished, parks sprite 0 on the drone's cell with the shape for the reversed heading.",
"In: Y = index of the heading byte in $E020, cmdFromOpponent zp_76, droneArrivalFlags $92F9, $90FB",
"Out: cmdParam0/1/2, droneControlFlags $920D bit 0, $9022, $92F6/$92F7, $9010/$9018, $91CC, $9008 and",
" sprite 0's shape; X/Y restored from the operands patched by saveRegsForCmdReturn",
"Called from: mergeIncomingPacket $4F28"
]
},
"52CB": {
"unit": "game/mainProgram0800",
"line": "heading 0-7 of the opponent's drone"
},
"52CD": {
"unit": "game/mainProgram0800",
"line": "next packet byte"
},
"52CE": {
"unit": "game/mainProgram0800",
"line": "fetch it"
},
"52D1": {
"unit": "game/mainProgram0800",
"line": "drone map column in the sender's coordinate frame"
},
"52D3": {
"unit": "game/mainProgram0800",
"line": "next packet byte"
},
"52D4": {
"unit": "game/mainProgram0800",
"line": "fetch it"
},
"52D7": {
"unit": "game/mainProgram0800",
"line": "drone map row in the sender's coordinate frame"
},
"52D9": {
"unit": "game/mainProgram0800",
"line": "remember the merge loop's X and Y for the common exit at $532A"
},
"52DC": {
"unit": "game/mainProgram0800",
"line": "mirror into our frame and step back one cell, because the drone occupies a 2x2 block"
},
"52DF": {
"unit": "game/mainProgram0800",
"line": "$92F9 tracks the life of the drone"
},
"52E2": {
"unit": "game/mainProgram0800",
"line": "bit 4 = it reached its target, bit 5 = its kill burst has already been played"
},
"52E4": {
"unit": "game/mainProgram0800",
"line": "the drone is finished - ignore stale telemetry"
},
"52E6": {
"unit": "game/mainProgram0800",
"line": "$920D drone state flags"
},
"52E9": {
"unit": "game/mainProgram0800",
"line": "bit 0 = an enemy drone is currently being tracked on our display"
},
"52EB": {
"unit": "game/mainProgram0800",
"line": "store the flags back"
},
"52EE": {
"unit": "game/mainProgram0800",
"line": "$90FB: 0 = battlefield, 1 = COMCEN radar screen, 2 = drone screen, 3 = menus"
},
"52F1": {
"unit": "game/mainProgram0800",
"line": "is the radar screen showing?"
},
"52F3": {
"unit": "game/mainProgram0800",
"line": "the enemy drone blip only exists on the radar screen"
},
"52F5": {
"unit": "game/mainProgram0800",
"line": "$9022 shadow of VIC_SPR_ENA",
"block": [
"Move sprite 0, the enemy drone blip, to the drone's cell on the 21x17 radar grid. The sprite is switched",
"off first so that a drone which has flown off the grid simply disappears."
]
},
"52F8": {
"unit": "game/mainProgram0800",
"line": "clear bit 0 - hide sprite 0 until we know the drone is inside the grid"
},
"52FA": {
"unit": "game/mainProgram0800",
"line": "store the sprite enable shadow back"
},
"52FD": {
"unit": "game/mainProgram0800",
"line": "X = drone column"
},
"52FF": {
"unit": "game/mainProgram0800",
"line": "$92F6 = the tracked drone's map column"
},
"5302": {
"unit": "game/mainProgram0800",
"line": "Y = drone row"
},
"5304": {
"unit": "game/mainProgram0800",
"line": "$92F7 = its map row"
},
"5307": {
"unit": "game/mainProgram0800",
"line": "$340B: converts map cell X,Y into the sprite X/Y of that cell's centre on the 21x17 radar grid; C=1 when off-grid"
},
"530A": {
"unit": "game/mainProgram0800",
"line": "off the visible grid - leave sprite 0 switched off"
},
"530C": {
"unit": "game/mainProgram0800",
"line": "$9018 = sprite 0 Y position shadow"
},
"530F": {
"unit": "game/mainProgram0800",
"line": "$9010 = sprite 0 X position shadow"
},
"5312": {
"unit": "game/mainProgram0800",
"line": "0"
},
"5314": {
"unit": "game/mainProgram0800",
"line": "$91CC = 0 so loadDirectionShapeSprite fills sprite 0's shape at $8A00"
},
"5317": {
"unit": "game/mainProgram0800",
"line": "the heading as the sender sees it"
},
"5319": {
"unit": "game/mainProgram0800",
"line": "clear carry for the add"
},
"531A": {
"unit": "game/mainProgram0800",
"line": "+4 of the 8 compass points = the opposite direction ..."
},
"531C": {
"unit": "game/mainProgram0800",
"line": "past the eighth point?"
},
"531E": {
"unit": "game/mainProgram0800",
"line": "no wrap needed"
},
"5320": {
"unit": "game/mainProgram0800",
"line": "... modulo 8; reversing the heading is exactly what the 180-degree map mirror demands"
},
"5322": {
"unit": "game/mainProgram0800",
"line": "$3340: copy shape A out of the decompressed set at $0200 into sprite 0 and enable it"
},
"5325": {
"unit": "game/mainProgram0800",
"line": "VIC colour 1 = white"
},
"5327": {
"unit": "game/mainProgram0800",
"line": "$9008 = sprite 0 colour shadow"
},
"532A": {
"unit": "game/mainProgram0800",
"line": "immediate operand patched by saveRegsForCmdReturn: restore the merge loop's X"
},
"532C": {
"unit": "game/mainProgram0800",
"line": "immediate operand patched by saveRegsForCmdReturn: restore the merge loop's Y"
},
"532E": {
"unit": "game/mainProgram0800",
"line": "back into the middle of the packet merge loop"
},
"532F": {
"unit": "game/mainProgram0800",
"line": "write X into the LDX # operand of the shared exit at $532A",
"routine": [
"saveRegsForCmdReturn - Two-instruction helper for the handlers that are called from the middle of the packet",
"merge loop instead of from the table dispatcher: it copies X and Y into the LDX/LDY immediate operands at",
"$532B and $532D, so the shared exit at $532A hands the merger back exactly the loop registers it had.",
"In: X, Y",
"Out: self-modifies $532B and $532D",
"Called from: cmdSessionControl $523E and cmdDroneMove $52D9"
]
},
"5332": {
"unit": "game/mainProgram0800",
"line": "write Y into the LDY # operand at $532C"
},
"5335": {
"unit": "game/mainProgram0800",
"line": "done - the exit will now hand the caller back its own registers"
},
"5336": {
"unit": "game/mainProgram0800",
"line": "cmdParam1 = target column, cmdParam2 = target row; read index += 3",
"routine": [
"cmdDroneDetonate - Handler of command $8E (3 bytes: column, row). For our own command it clears the",
"'strike pending' bit 5 of droneControlFlags. If droneArrivalFlags bit 7 is already set the drone died or",
"arrived before the order was exchanged, so the detonation is cancelled by overwriting the $8E byte in the",
"merged buffer with $0E in place - bit 7 clear makes executeTurnCommands skip it while $0E & $3F still gives",
"length 3, so the film keeps a correctly sized no-op. Otherwise the drone is marked detonated, the",
"coordinates are mirrored, sound $11 plays, applyDroneBlast damages the 2x2 blast area and the blast-ring",
"sprite is drawn; a remote detonation finally clears our copy of his drone state through the tail at $537E.",
"In: the queued command, cmdFromOpponent zp_76, droneArrivalFlags $92F9, filmPlaybackMode $0B9B",
"Out: $92F9, $920D, the command byte in mergedCmdBuffer, sound, blast damage and the marker sprite",
"Called from: commandHandlerTable slot for $8E ($4D46)"
]
},
"5339": {
"unit": "game/mainProgram0800",
"line": "whose drone is detonating?"
},
"533B": {
"unit": "game/mainProgram0800",
"line": "the opponent's - nothing local to clean up"
},
"533D": {
"unit": "game/mainProgram0800",
"line": "$920D drone state flags"
},
"5340": {
"unit": "game/mainProgram0800",
"line": "clear bit 5 = 'a strike of ours is pending' (strikeWithDrone set it at $8187)"
},
"5342": {
"unit": "game/mainProgram0800",
"line": "store the flags back"
},
"5345": {
"unit": "game/mainProgram0800",
"line": "$92F9 bit 7 = the drone has already finished its flight"
},
"5348": {
"unit": "game/mainProgram0800",
"line": "still airborne, so the strike counts"
},
"534A": {
"unit": "game/mainProgram0800",
"line": "readCmdArgs2 left the read index 3 past the command byte ...",
"block": [
"The drone had already finished when the detonate order came round, so cancel the command in place instead",
"of executing it."
]
},
"534D": {
"unit": "game/mainProgram0800",
"line": "$0E has bit 7 clear so executeTurnCommands skips it, and $0E & $3F still yields length 3"
},
"534F": {
"unit": "game/mainProgram0800",
"line": "$908C+readIndex is mergedCmdBuffer[command byte]: neuter the $8E in place, so the film records a correctly sized no-op"
},
"5352": {
"unit": "game/mainProgram0800",
"line": "always taken (A = $0E)"
},
"5354": {
"unit": "game/mainProgram0800",
"line": "$40 = the drone has detonated",
"block": [
"A real detonation: mark the drone as burst, damage the 2x2 blast area and draw the ring."
]
},
"5356": {
"unit": "game/mainProgram0800",
"line": "$92F9 drone life flags"
},
"5359": {
"unit": "game/mainProgram0800",
"line": "mirror the 2x2 blast origin into our frame for a remote strike"
},
"535C": {
"unit": "game/mainProgram0800",
"line": "$0B9B bit 7 = film playback"
},
"535F": {
"unit": "game/mainProgram0800",
"line": "during playback everything is audible"
},
"5361": {
"unit": "game/mainProgram0800",
"line": "whose strike is it?"
},
"5363": {
"unit": "game/mainProgram0800",
"line": "our own strike is silent here - the drone screen already made its noise"
},
"5365": {
"unit": "game/mainProgram0800",
"line": "sound $11 = the drone explosion"
},
"5367": {
"unit": "game/mainProgram0800",
"line": "hand it to the sound driver"
},
"536A": {
"unit": "game/mainProgram0800",
"line": "$19 = 25 points of blast damage"
},
"536C": {
"unit": "game/mainProgram0800",
"line": "overlay $72A1: damage whatever stands in the four cells of the 2x2 blast"
},
"536F": {
"unit": "game/mainProgram0800",
"line": "blast column"
},
"5371": {
"unit": "game/mainProgram0800",
"line": "blast row"
},
"5373": {
"unit": "game/mainProgram0800",
"line": "marker style 1 = the drone burst ring (offsets 8,9 and colour 4 from the table at $33C2)"
},
"5375": {
"unit": "game/mainProgram0800",
"line": "$33C8: put the blast-ring sprite on that cell and start its 90-frame timer"
},
"5378": {
"unit": "game/mainProgram0800",
"line": "join the shared tail"
},
"537B": {
"unit": "game/mainProgram0800",
"line": "1-byte command $8F: step the read index over it",
"routine": [
"cmdDroneRelease - Handler of the 1-byte command $8F, queued by releaseDroneControl ($8230) when the player",
"lets go of the drone. It only steps the read index and falls into the tail shared with cmdDroneDetonate,",
"which for a remote command clears bits 0-5 of droneControlFlags - our copy of the opponent's drone state -",
"while bits 6-7, which describe our own drone flight, are preserved.",
"In: cmdFromOpponent zp_76, droneControlFlags $920D",
"Out: mergedCmdReadIndex += 1; $920D &= $C0 for a remote command",
"Called from: commandHandlerTable slot for $8F ($4D48); the tail at $537E also from cmdDroneDetonate"
]
},
"537E": {
"unit": "game/mainProgram0800",
"line": "shared tail of $8E and $8F: whose drone was it?"
},
"5380": {
"unit": "game/mainProgram0800",
"line": "our own drone state belongs to the drone screen, not here"
},
"5382": {
"unit": "game/mainProgram0800",
"line": "$920D drone state flags"
},
"5385": {
"unit": "game/mainProgram0800",
"line": "keep bits 6-7 (our own flight) and clear bits 0-5, our copy of his drone state"
},
"5387": {
"unit": "game/mainProgram0800",
"line": "store the flags back"
},
"538A": {
"unit": "game/mainProgram0800",
"line": "done"
},
"538B": {
"unit": "game/mainProgram0800",
"line": "1-byte command $92: step the read index over it",
"routine": [
"cmdDroneShotDown - Handler of the 1-byte command $92. The only place that ever queues it is",
"checkStrikeHitsDrone ($79CC), which runs right after a COMCEN missile is fired and asks whether the aim",
"point falls on one of the 2x2 cells the tracked drone occupies: $92 therefore means 'a missile has shot the",
"drone down', not 'the drone arrived'. It clears bits 6-7 of droneControlFlags (no drone flight in progress",
"any more) and, for our own command, sets bit 4 of droneArrivalFlags so showDroneKillBurst ($79DD) plays the",
"kill effect on the next frame. Both packet mergers additionally set droneArrivalFlags = $80 the moment a $92",
"is seen in either packet, and sendOutgoingPacket drops a queued $92 when the drone detonated first.",
"In: cmdFromOpponent zp_76, droneControlFlags $920D, droneArrivalFlags $92F9",
"Out: $920D &= $3F; $92F9 |= $10 for an own command; mergedCmdReadIndex += 1",
"Called from: commandHandlerTable slot for $92 ($4D4E)",
"(the survey called this cmdDroneArrived; renamed because $92 is the shot-down report, and the drone",
"reaching its target is reported with $8E/$8F instead)"
]
},
"538E": {
"unit": "game/mainProgram0800",
"line": "$920D drone state flags"
},
"5391": {
"unit": "game/mainProgram0800",
"line": "clear bits 6-7: no drone flight is in progress any more"
},
"5393": {
"unit": "game/mainProgram0800",
"line": "store the flags back"
},
"5396": {
"unit": "game/mainProgram0800",
"line": "who fired the missile that killed the drone?"
},
"5398": {
"unit": "game/mainProgram0800",
"line": "the opponent scored the kill - nothing else to record here"
},
"539A": {
"unit": "game/mainProgram0800",
"line": "$92F9 drone life flags"
},
"539D": {
"unit": "game/mainProgram0800",
"line": "bit 4 = we shot a drone down; showDroneKillBurst ($79DD) picks this up next frame and plays the effect"
},
"539F": {
"unit": "game/mainProgram0800",
"line": "store the flags back"
},
"53A2": {
"unit": "game/mainProgram0800",
"line": "done"
},
"53A3": {
"unit": "game/mainProgram0800",
"line": "1-byte command $97: step the read index over it",
"routine": [
"cmdDroneLaunch - Handler of the 1-byte command $97: droneArrivalFlags = 0 for a fresh drone in the air. The",
"opponent's launch also raises the 'DRONE ALERT!' message with sound through overlay B's showDroneAlert.",
"Either way, if that side still has drones one is spent and markComcenRevealed exposes the launching COMCEN",
"for six rounds - the price of flying a drone.",
"In: cmdFromOpponent zp_76, dronesLeft $92AE",
"Out: $92F9 = 0, $92AE[side] decremented, COMCEN reveal timer and visibility bits via $403B",
"Called from: commandHandlerTable slot for $97 ($4D58)"
]
},
"53A6": {
"unit": "game/mainProgram0800",
"line": "index 0 = this machine's allowances"
},
"53A8": {
"unit": "game/mainProgram0800",
"line": "$92F9 = 0: a fresh drone is in the air"
},
"53AB": {
"unit": "game/mainProgram0800",
"line": "who launched?"
},
"53AD": {
"unit": "game/mainProgram0800",
"line": "our own launch - the drone screen has already told the player"
},
"53AF": {
"unit": "game/mainProgram0800",
"line": "overlay $820F: sound 7 plus message $20 'DRONE ALERT!'"
},
"53B2": {
"unit": "game/mainProgram0800",
"line": "index 1 = the opponent's allowances"
},
"53B4": {
"unit": "game/mainProgram0800",
"line": "$92AE[side] = drones that side still has"
},
"53B7": {
"unit": "game/mainProgram0800",
"line": "already out of drones - nothing to charge"
},
"53B9": {
"unit": "game/mainProgram0800",
"line": "spend one drone"
},
"53BC": {
"unit": "game/mainProgram0800",
"line": "$403B: launching exposes that side's COMCEN for 6 rounds (X selects the side)"
},
"53BF": {
"unit": "game/mainProgram0800",
"line": "done"
},
"53C0": {
"unit": "game/mainProgram0800",
"line": "read the two payload bytes into cmdParam1/cmdParam2; read index += 3",
"routine": [
"cmdMissileStrike - Handler of command $90 (3 bytes), which carries two different payloads. During a setup",
"phase (gamePhase non-zero) the two bytes are the sender's drone and missile allowances and are stored in",
"dronesLeft/missilesLeft for that side - this is what overlay A's editDronesAndMissiles sends. During the",
"battle they are the target cell of a COMCEN missile: if the firing side still has missiles and the game has",
"not ended, one missile is spent, markComcenRevealed exposes the firing COMCEN, the coordinates are mirrored",
"for a remote shot and the unit standing on the cell takes 14 points of damage, halved to 7 for a COMCEN.",
"The blast-ring sprite is drawn in either case and sound $0C plays unless it is our own live shot.",
"In: the queued command, gamePhase zp_B1, cmdFromOpponent zp_76, gameEndReason $0BA8, the $F000 map",
"Out: $92AE/$92B0[side], unit energy and score via $3B22, the blast-ring sprite, sound",
"Called from: commandHandlerTable slot for $90 ($4D4A)"
]
},
"53C3": {
"unit": "game/mainProgram0800",
"line": "zp_B1 is zero only while the battle runs"
},
"53C5": {
"unit": "game/mainProgram0800",
"line": "battle: the payload is a target cell"
},
"53C7": {
"unit": "game/mainProgram0800",
"line": "setup phase: the payload is the sender's drone/missile allowance; index 0 = us",
"block": [
"Setup phase: the two bytes are the sender's drone and missile allowances, not a target cell."
]
},
"53C9": {
"unit": "game/mainProgram0800",
"line": "who sent it?"
},
"53CB": {
"unit": "game/mainProgram0800",
"line": "our own settings"
},
"53CD": {
"unit": "game/mainProgram0800",
"line": "index 1 = the opponent"
},
"53CE": {
"unit": "game/mainProgram0800",
"line": "first byte = number of drones"
},
"53D0": {
"unit": "game/mainProgram0800",
"line": "$92AE[side]"
},
"53D3": {
"unit": "game/mainProgram0800",
"line": "second byte = number of missiles"
},
"53D5": {
"unit": "game/mainProgram0800",
"line": "$92B0[side]"
},
"53D8": {
"unit": "game/mainProgram0800",
"line": "allowances stored"
},
"53D9": {
"unit": "game/mainProgram0800",
"line": "battle: index 0 = our own missile stock",
"block": [
"Battle phase: the two bytes are the target cell of a COMCEN missile."
]
},
"53DB": {
"unit": "game/mainProgram0800",
"line": "who fired?"
},
"53DD": {
"unit": "game/mainProgram0800",
"line": "our own shot needs no mirroring"
},
"53DF": {
"unit": "game/mainProgram0800",
"line": "turn his aim point into our coordinates (39-v)"
},
"53E2": {
"unit": "game/mainProgram0800",
"line": "index 1 = the opponent's missile stock"
},
"53E4": {
"unit": "game/mainProgram0800",
"line": "$92B0[side]"
},
"53E7": {
"unit": "game/mainProgram0800",
"line": "the firing side has no missiles left - ignore the shot"
},
"53E9": {
"unit": "game/mainProgram0800",
"line": "$0BA8 is non-zero once the game has ended"
},
"53EC": {
"unit": "game/mainProgram0800",
"line": "no shots after the final whistle"
},
"53EE": {
"unit": "game/mainProgram0800",
"line": "spend one missile"
},
"53F1": {
"unit": "game/mainProgram0800",
"line": "firing gives the firing COMCEN away for 6 rounds"
},
"53F4": {
"unit": "game/mainProgram0800",
"line": "target column"
},
"53F6": {
"unit": "game/mainProgram0800",
"line": "$28 = 40, the width of the battlefield"
},
"53F8": {
"unit": "game/mainProgram0800",
"line": "off the map: draw the ring but hit nothing"
},
"53FA": {
"unit": "game/mainProgram0800",
"line": "target row"
},
"53FC": {
"unit": "game/mainProgram0800",
"line": "$28 = 40, the height of the battlefield"
},
"53FE": {
"unit": "game/mainProgram0800",
"line": "off the map"
},
"5400": {
"unit": "game/mainProgram0800",
"line": "$25FA: A = the $F000 map byte of column X, row Y"
},
"5403": {
"unit": "game/mainProgram0800",
"line": "bit 7 clear = plain terrain, nobody is standing there"
},
"5405": {
"unit": "game/mainProgram0800",
"line": "bits 0-6 = the index of the unit occupying the cell"
},
"5407": {
"unit": "game/mainProgram0800",
"line": "Y = the victim"
},
"5408": {
"unit": "game/mainProgram0800",
"line": "$0E = 14 points of missile damage"
},
"540A": {
"unit": "game/mainProgram0800",
"line": "unit 49 = side 0's COMCEN"
},
"540C": {
"unit": "game/mainProgram0800",
"line": "halve it"
},
"540E": {
"unit": "game/mainProgram0800",
"line": "unit 99 = side 1's COMCEN"
},
"5410": {
"unit": "game/mainProgram0800",
"line": "an ordinary unit takes the full 14"
},
"5412": {
"unit": "game/mainProgram0800",
"line": "a COMCEN only takes half, 7 points"
},
"5413": {
"unit": "game/mainProgram0800",
"line": "$3B22: subtract the energy, score it, explode the unit or knock the COMCEN out"
},
"5416": {
"unit": "game/mainProgram0800",
"line": "blast column"
},
"5418": {
"unit": "game/mainProgram0800",
"line": "blast row"
},
"541A": {
"unit": "game/mainProgram0800",
"line": "marker style 0 = the missile blast ring (offsets 10,8 and colour 2 from the table at $33C2)"
},
"541C": {
"unit": "game/mainProgram0800",
"line": "$33C8: draw the ring sprite on the target cell"
},
"541F": {
"unit": "game/mainProgram0800",
"line": "whose shot?"
},
"5421": {
"unit": "game/mainProgram0800",
"line": "the opponent's shot is always audible"
},
"5423": {
"unit": "game/mainProgram0800",
"line": "$0B9B bit 7 = film playback"
},
"5426": {
"unit": "game/mainProgram0800",
"line": "our own shot is silent in live play - the radar screen made its own noise"
},
"5428": {
"unit": "game/mainProgram0800",
"line": "sound $0C = missile impact"
},
"542A": {
"unit": "game/mainProgram0800",
"line": "hand it to the sound driver"
},
"542D": {
"unit": "game/mainProgram0800",
"line": "done"
},
"542E": {
"unit": "game/mainProgram0800",
"line": "cmdParam0 = the unit index; bit 7 set means 'the whole group this unit belongs to'",
"routine": [
"cmdSelfDestruct - Handler of command $91 (2 bytes: unit; bit 7 of the argument means 'the whole group this",
"unit belongs to'). A plain unit index falls straight into destroyUnit. For a group the match value",
"(unitFlagsTable & $3C) | $40 is patched into the CMP operand at $544D and units 99..0 are scanned, calling",
"destroyUnit on every member; because the $3C mask keeps the side bit 5, a group can never span both armies.",
"In: the queued command; cmdParam0 = unit index, bit 7 = group mode; unitFlagsTable $F708",
"Out: unitTypeTable $F76C entries set to $FF; self-modifies $544E; cmdParam0 reused as the loop counter",
"Called from: commandHandlerTable slot for $91 ($4D4C)"
]
},
"5431": {
"unit": "game/mainProgram0800",
"line": "look at the argument"
},
"5433": {
"unit": "game/mainProgram0800",
"line": "a plain unit index: destroy just that one and return through destroyUnit"
},
"5435": {
"unit": "game/mainProgram0800",
"line": "strip the 'whole group' flag to get the unit index"
},
"5437": {
"unit": "game/mainProgram0800",
"line": "Y = the named unit"
},
"5438": {
"unit": "game/mainProgram0800",
"line": "its $F708 flags"
},
"543B": {
"unit": "game/mainProgram0800",
"line": "group id in bits 2-4 plus the side bit 5, so a group can never span both armies"
},
"543D": {
"unit": "game/mainProgram0800",
"line": "bit 6 = 'in a group', which every member carries"
},
"543F": {
"unit": "game/mainProgram0800",
"line": "patch the CMP immediate of the scan loop with the group signature to look for"
},
"5442": {
"unit": "game/mainProgram0800",
"line": "$63 = 99, the highest unit index",
"block": [
"Scan units 99..0 and destroy every living member whose group signature matches the one patched into the",
"CMP at $544D. cmdParam0 doubles as the loop counter because destroyUnit reads its victim from there."
]
},
"5444": {
"unit": "game/mainProgram0800",
"line": "reuse cmdParam0 as the loop counter, because destroyUnit reads its victim from there"
},
"5446": {
"unit": "game/mainProgram0800",
"line": "Y = the unit currently being tested"
},
"5448": {
"unit": "game/mainProgram0800",
"line": "its $F708 flags"
},
"544B": {
"unit": "game/mainProgram0800",
"line": "group id, side and in-group bit"
},
"544D": {
"unit": "game/mainProgram0800",
"line": "immediate operand patched at $543F with the group signature"
},
"544F": {
"unit": "game/mainProgram0800",
"line": "not a member of the group"
},
"5451": {
"unit": "game/mainProgram0800",
"line": "blow this member up"
},
"5454": {
"unit": "game/mainProgram0800",
"line": "step down one unit"
},
"5456": {
"unit": "game/mainProgram0800",
"line": "keep going until unit 0 has been tested"
},
"5458": {
"unit": "game/mainProgram0800",
"line": "the whole group is gone"
},
"5459": {
"unit": "game/mainProgram0800",
"line": "Y = the unit to destroy (also the loop counter of cmdSelfDestruct)",
"routine": [
"destroyUnit - Removes unit cmdParam0 from play. A COMCEN (unit 49 or 99) is never allowed to self-destruct",
"and returns unchanged. Everything else takes 52 points of damage through sub_3B52 - more than the 50-point",
"maximum energy, so the kill is certain and runs through the normal explosion, sound, score and redraw path -",
"and then has its slot freed outright by writing $FF into unitTypeTable.",
"In: cmdParam0 zp_73 = unit index",
"Out: unitTypeTable $F76C[unit] = $FF plus all the side effects of sub_3B52; Y = unit index",
"Called from: cmdSelfDestruct (branch at $5433 and JSR at $5451)"
]
},
"545B": {
"unit": "game/mainProgram0800",
"line": "unit 49 = side 0's COMCEN"
},
"545D": {
"unit": "game/mainProgram0800",
"line": "a COMCEN may never self-destruct"
},
"545F": {
"unit": "game/mainProgram0800",
"line": "unit 99 = side 1's COMCEN"
},
"5461": {
"unit": "game/mainProgram0800",
"line": "likewise protected"
},
"5463": {
"unit": "game/mainProgram0800",
"line": "$34 = 52 points, more than the 50-point maximum energy, so the kill is certain"
},
"5465": {
"unit": "game/mainProgram0800",
"line": "the damage path of applyHitToUnit without its attacker/SPY preamble: explosion, sound, score, redraw"
},
"5468": {
"unit": "game/mainProgram0800",
"line": "reload the unit index (sub_3B52 clobbers Y)"
},
"546A": {
"unit": "game/mainProgram0800",
"line": "$FF = free slot, bit 7 set = dead"
},
"546C": {
"unit": "game/mainProgram0800",
"line": "$F76C: release the record so nothing draws or scans it again"
},
"546F": {
"unit": "game/mainProgram0800",
"line": "done"
},
"5470": {
"unit": "game/mainProgram0800",
"line": "cmdParam0 = unit, cmdParam1/cmdParam2 = goal column/row; read index += 4",
"routine": [
"cmdMoveKeepGroup - Handler of command $98 MOVE TO GOAL (4 bytes: unit, column, row), the order issued to",
"each member of a group in turn by $4C0B. It reads and mirrors the arguments and jumps to",
"setGroupMemberDestination ($4C59), which counts the order off pendingGroupOrderCount when the unit belongs",
"to the group being ordered and then sets the destination through $34D5 without disturbing the group bits -",
"unlike command $80, which drops the unit out of its group.",
"In: the queued command; cmdParam0 = unit, cmdParam1/cmdParam2 = goal column/row",
"Out: destination tables of the unit, pendingGroupOrderCount $929E; read index += 4",
"Called from: commandHandlerTable slot for $98 ($4D5A)"
]
},
"5473": {
"unit": "game/mainProgram0800",
"line": "mirror a remote player's goal into our coordinates"
},
"5476": {
"unit": "game/mainProgram0800",
"line": "$4C59: count the order off pendingGroupOrderCount and set the destination without touching the group bits"
},
"5479": {
"unit": "game/mainProgram0800",
"line": "cmdParam1 = the unit, cmdParam2 = the energy to add; read index += 3",
"routine": [
"cmdAddEnergy - Handler of command $9A (3 bytes: unit, amount), sent by the COMCEN energy repair on the",
"REPAIR tab. Unless the game has already ended by a COMCEN knock-out ($0BA8 bit 7) the amount is added to",
"bits 0-5 of the unit's unitEnergyTable entry and saturated at 50; the two visibility bits 6-7 are saved into",
"the ORA operand at $5498 and put back afterwards.",
"In: the queued command; cmdParam1 = unit, cmdParam2 = energy to add; gameEndReason $0BA8",
"Out: unitEnergyTable $F9C4[unit]; self-modifies $5499; read index += 3",
"Called from: commandHandlerTable slot for $9A ($4D5E)"
]
},
"547C": {
"unit": "game/mainProgram0800",
"line": "Y = the unit being repaired"
},
"547E": {
"unit": "game/mainProgram0800",
"line": "$0BA8 bit 7 set = a COMCEN has been knocked out"
},
"5481": {
"unit": "game/mainProgram0800",
"line": "no repairs once the game has been decided"
},
"5483": {
"unit": "game/mainProgram0800",
"line": "$F9C4: bits 0-5 energy 0-50, bits 6-7 = spotted / visible flags"
},
"5486": {
"unit": "game/mainProgram0800",
"line": "keep the whole byte in X"
},
"5487": {
"unit": "game/mainProgram0800",
"line": "isolate the two visibility bits"
},
"5489": {
"unit": "game/mainProgram0800",
"line": "patch them into the ORA immediate at $5498 so they survive the rewrite"
},
"548C": {
"unit": "game/mainProgram0800",
"line": "get the byte back"
},
"548D": {
"unit": "game/mainProgram0800",
"line": "the current energy on its own"
},
"548F": {
"unit": "game/mainProgram0800",
"line": "clear carry for the add"
},
"5490": {
"unit": "game/mainProgram0800",
"line": "add the repair amount"
},
"5492": {
"unit": "game/mainProgram0800",
"line": "$32 = 50 = full energy"
},
"5494": {
"unit": "game/mainProgram0800",
"line": "still below the cap"
},
"5496": {
"unit": "game/mainProgram0800",
"line": "saturate at 50"
},
"5498": {
"unit": "game/mainProgram0800",
"line": "immediate operand patched at $5489 with the saved visibility bits"
},
"549A": {
"unit": "game/mainProgram0800",
"line": "store the repaired energy"
},
"549D": {
"unit": "game/mainProgram0800",
"line": "done"
},
"549E": {
"unit": "game/mainProgram0800",
"line": "cmdParam0 = the group byte (id in bits 2-4, side in bit 5); read index += 2",
"routine": [
"cmdHaltGroup - Handler of command $9B (2 bytes: group byte), the group HALT order. Units 99..0 are scanned",
"and for every living member (unitFlagsTable & $7C equal to the group byte | $40) the current position is",
"copied into the destination and waypoint, the two 'arrived' bits (unitDestColTable bit 7 and",
"unitDisplayFlagsTable bit 7) are set, the cloak bits of the waypoint column are preserved through the ORA",
"operand patched at $54CF, and the blitz bits of the waypoint row are dropped because a plain row value is",
"written over them.",
"In: the queued command; cmdParam0 = group byte; unitTypeTable $F76C, unitFlagsTable $F708",
"Out: $F7D0, $F834, $F898, $F8FC and $FB54 of every member; self-modifies $54D0; read index += 2",
"Called from: commandHandlerTable slot for $9B ($4D60)"
]
},
"54A1": {
"unit": "game/mainProgram0800",
"line": "the group byte"
},
"54A3": {
"unit": "game/mainProgram0800",
"line": "bit 6 = 'in a group', so the byte can be compared with unitFlagsTable & $7C directly"
},
"54A5": {
"unit": "game/mainProgram0800",
"line": "keep it as the match value"
},
"54A7": {
"unit": "game/mainProgram0800",
"line": "start at unit 99 and walk down to 0",
"block": [
"Walk units 99..0 and stop every living member of the group where it stands."
]
},
"54A9": {
"unit": "game/mainProgram0800",
"line": "$F76C, bit 7 set = dead or free slot"
},
"54AC": {
"unit": "game/mainProgram0800",
"line": "skip dead units"
},
"54AE": {
"unit": "game/mainProgram0800",
"line": "the unit's $F708 flags"
},
"54B1": {
"unit": "game/mainProgram0800",
"line": "group id, side and in-group bit"
},
"54B3": {
"unit": "game/mainProgram0800",
"line": "does it belong to the group being halted?"
},
"54B5": {
"unit": "game/mainProgram0800",
"line": "no - next unit"
},
"54B7": {
"unit": "game/mainProgram0800",
"line": "the member's current column"
},
"54BA": {
"unit": "game/mainProgram0800",
"line": "patch it into the ORA immediate at $54CF"
},
"54BD": {
"unit": "game/mainProgram0800",
"line": "bit 7 = standing on its destination"
},
"54BF": {
"unit": "game/mainProgram0800",
"line": "$F7D0 destination column = where the unit already is"
},
"54C2": {
"unit": "game/mainProgram0800",
"line": "$FB54 draw flags"
},
"54C5": {
"unit": "game/mainProgram0800",
"line": "bit 7 = idle / at destination"
},
"54C7": {
"unit": "game/mainProgram0800",
"line": "store the draw flags back"
},
"54CA": {
"unit": "game/mainProgram0800",
"line": "$F898 waypoint column"
},
"54CD": {
"unit": "game/mainProgram0800",
"line": "keep only the cloak bits 6-7"
},
"54CF": {
"unit": "game/mainProgram0800",
"line": "immediate operand patched at $54BA with the member's current column"
},
"54D1": {
"unit": "game/mainProgram0800",
"line": "waypoint = current position, cloak state untouched"
},
"54D4": {
"unit": "game/mainProgram0800",
"line": "the member's current row"
},
"54D7": {
"unit": "game/mainProgram0800",
"line": "$F834 destination row = where the unit already is"
},
"54DA": {
"unit": "game/mainProgram0800",
"line": "$F8FC waypoint row likewise; a row is below 64, so this also clears the blitz bits 6-7"
},
"54DD": {
"unit": "game/mainProgram0800",
"line": "step down one unit"
},
"54DE": {
"unit": "game/mainProgram0800",
"line": "continue until unit 0 has been tested"
},
"54E0": {
"unit": "game/mainProgram0800",
"line": "the whole group has stopped"
},
"54E1": {
"unit": "game/mainProgram0800",
"line": "cmdParam0 = mode, cmdParam1/cmdParam2 = column/row; read index += 4",
"routine": [
"cmdSetRecycler - Handler of command $9C (4 bytes: mode, column, row), sent by overlay A's chooseRecycler",
"($8436) during the setup of the game types that have a recycler. recyclerMode[side] takes the mode, with",
"side = the sender's absolute side (playerSide, EOR 1 when the command is remote); for modes 0 and 1 the",
"coordinates are mirrored for a remote sender and stored in recyclerCol/recyclerRow[side]. Our own recycler",
"is additionally painted onto the overview map, the enemy one is not - it has to be spotted first.",
"In: the queued command; cmdParam0 = mode, cmdParam1/2 = column/row; playerSide $0B9F, cmdFromOpponent",
"Out: $92A6/$92A8/$92AA indexed by side, map redraw via $3F27; self-modifies $550C; read index += 4",
"Called from: commandHandlerTable slot for $9C ($4D62)"
]
},
"54E4": {
"unit": "game/mainProgram0800",
"line": "$0B9F = the side this machine plays"
},
"54E7": {
"unit": "game/mainProgram0800",
"line": "who sent the command?"
},
"54E9": {
"unit": "game/mainProgram0800",
"line": "our own choice"
},
"54EB": {
"unit": "game/mainProgram0800",
"line": "remote: the sender is the other side"
},
"54ED": {
"unit": "game/mainProgram0800",
"line": "Y = the sender's absolute side, the index of the per-side recycler tables"
},
"54EE": {
"unit": "game/mainProgram0800",
"line": "also stash it in the LDY immediate at $550B, because Y is reused before the draw call"
},
"54F1": {
"unit": "game/mainProgram0800",
"line": "the chosen recycler mode"
},
"54F3": {
"unit": "game/mainProgram0800",
"line": "$92A6[side]"
},
"54F6": {
"unit": "game/mainProgram0800",
"line": "modes 2 and up carry no coordinates"
},
"54F8": {
"unit": "game/mainProgram0800",
"line": "skip the mirror for those"
},
"54FA": {
"unit": "game/mainProgram0800",
"line": "mirror a remote placement into our coordinate frame"
},
"54FD": {
"unit": "game/mainProgram0800",
"line": "recycler column"
},
"54FF": {
"unit": "game/mainProgram0800",
"line": "$92A8[side]"
},
"5502": {
"unit": "game/mainProgram0800",
"line": "recycler row"
},
"5504": {
"unit": "game/mainProgram0800",
"line": "$92AA[side]"
},
"5507": {
"unit": "game/mainProgram0800",
"line": "whose recycler is it?"
},
"5509": {
"unit": "game/mainProgram0800",
"line": "the enemy recycler stays hidden until it is spotted"
},
"550B": {
"unit": "game/mainProgram0800",
"line": "immediate operand patched at $54EE with our own side index"
},
"550D": {
"unit": "game/mainProgram0800",
"line": "$3F27: write the recycler glyph $67 into the map cell (or under whichever unit stands there)"
},
"5510": {
"unit": "game/mainProgram0800",
"line": "done"
},
"5511": {
"unit": "game/mainProgram0800",
"line": "cmdParam0 = the sender's side-preference token (0 or $FF from the setup screen, or a random tie-break byte)",
"routine": [
"cmdChooseSide - Handler of command $9D (2 bytes: the sender's side-preference token), the side-selection",
"handshake. In a solo game playerSide is simply forced to 0. In a modem game only the opponent's copy of the",
"command matters: our own token was parked in $90F8 by sendChooseSide, and the player with the higher token",
"becomes side 1 (the carry of the CPY is rotated into A). gamePhase is then set to $DF, whose clear bit 5",
"releases the wait loop at $797C. If both tokens are identical nobody wins: a fresh random token with bit 7",
"set is drawn and the code falls into sendChooseSide to send command $9D again, leaving gamePhase untouched",
"so the loop keeps waiting. That only terminates because the two machines' game RNGs have drifted apart",
"before a game starts (they are forced into step only once the map seed is exchanged).",
"In: the queued command; cmdParam0 = his token, selectedUnit $90F8 = ours, isSoloTrainer $0BA5, zp_76",
"Out: playerSide $0B9F = 0 or 1 and gamePhase zp_B1 = $DF, or a retry through sendChooseSide",
"Called from: commandHandlerTable slot for $9D ($4D64)"
]
},
"5514": {
"unit": "game/mainProgram0800",
"line": "$0BA5 bit 7 = PRACTICE WITH SOLO TRAINER"
},
"5517": {
"unit": "game/mainProgram0800",
"line": "modem game: settle it against the opponent"
},
"5519": {
"unit": "game/mainProgram0800",
"line": "solo: the human always plays side 0"
},
"551B": {
"unit": "game/mainProgram0800",
"line": "always taken"
},
"551D": {
"unit": "game/mainProgram0800",
"line": "which copy of the command is this?"
},
"551F": {
"unit": "game/mainProgram0800",
"line": "our own copy tells us nothing we did not already know"
},
"5521": {
"unit": "game/mainProgram0800",
"line": "build the resulting side number in A"
},
"5523": {
"unit": "game/mainProgram0800",
"line": "$90F8 holds our own token, parked there by sendChooseSide"
},
"5526": {
"unit": "game/mainProgram0800",
"line": "compare it with his; carry set means our token is the higher one"
},
"5528": {
"unit": "game/mainProgram0800",
"line": "identical tokens - nobody wins, so re-pick"
},
"552A": {
"unit": "game/mainProgram0800",
"line": "A = the carry: 1 when our token was higher, so we take side 1"
},
"552B": {
"unit": "game/mainProgram0800",
"line": "$0B9F is 0 or 1 from now on"
},
"552E": {
"unit": "game/mainProgram0800",
"line": "$DF clears bit 5 of the phase byte ..."
},
"5530": {
"unit": "game/mainProgram0800",
"line": "... which is the bit the side-selection loop at $797C spins on"
},
"5532": {
"unit": "game/mainProgram0800",
"line": "the side handshake is finished"
},
"5533": {
"unit": "game/mainProgram0800",
"line": "$FD5D: draw a fresh token; the two RNG states have drifted apart before a game starts, so the values probably differ"
},
"5536": {
"unit": "game/mainProgram0800",
"line": "bit 7 marks it as a tie-break retry token"
},
"5538": {
"unit": "game/mainProgram0800",
"line": "park it in playerSide, from where sendChooseSide picks it up below (fall-through)"
},
"553B": {
"unit": "game/mainProgram0800",
"line": "our own preference token (0 or $FF from the setup screen, or a tie-break byte)",
"routine": [
"sendChooseSide - The sending half of the $9D exchange: copies playerSide into cmdParam0 and into",
"selectedUnit $90F8 (where cmdChooseSide later finds our own token) and queues the 2-byte command $9D. At",
"this point playerSide still holds a preference token - 0 or $FF chosen by the setup screen, or a random",
"byte with bit 7 set after a tie - not yet a side number.",
"In: playerSide $0B9F",
"Out: cmdParam0 zp_73, selectedUnit $90F8, two bytes appended to the outgoing command ring",
"Called from: overlay A's side-selection screen ($7976), and by fall-through from cmdChooseSide's tie path"
]
},
"553E": {
"unit": "game/mainProgram0800",
"line": "the argument byte of command $9D"
},
"5540": {
"unit": "game/mainProgram0800",
"line": "$90F8 doubles as the 'our own pick' scratch that cmdChooseSide compares against"
},
"5543": {
"unit": "game/mainProgram0800",
"line": "command $9D CHOOSE SIDE"
},
"5545": {
"unit": "game/mainProgram0800",
"line": "append the 2-byte command to the outgoing ring"
},
"5548": {
"unit": "game/mainProgram0800",
"line": "$20 = unitStunTable bits 5-7 set to 001, 'digging in'",
"routine": [
"cmdDigIn - Handler of command $9F DIG IN (2 bytes: unit): calls setUnitDigBits with $20, i.e.",
"unitStunTable bits 5-7 = 001. The per-unit step code then advances those bits by $20 for every round the",
"unit stays still until it counts as fortified.",
"In: the queued command; cmdParam0 = unit after setUnitDigBits",
"Out: unitStunTable $F960[unit] = (old & $1F) | $20",
"Called from: commandHandlerTable slot for $9F ($4D68)"
]
},
"554A": {
"unit": "game/mainProgram0800",
"line": "always taken"
},
"554C": {
"unit": "game/mainProgram0800",
"line": "0 = unitStunTable bits 5-7 cleared, neither digging nor dug in",
"routine": [
"cmdDigOut - Handler of command $A0 DIG OUT (2 bytes: unit): setUnitDigBits with 0 clears the dig-progress",
"bits 5-7 of unitStunTable, and bit 6 of unitTypeTable - the 'fortified' flag the combat code consults - is",
"cleared as well.",
"In: the queued command; cmdParam0 = unit",
"Out: unitStunTable $F960[unit] &= $1F, unitTypeTable $F76C[unit] &= $BF",
"Called from: commandHandlerTable slot for $A0 ($4D6A)"
]
},
"554E": {
"unit": "game/mainProgram0800",
"line": "clear the dig bits and leave Y = the unit"
},
"5551": {
"unit": "game/mainProgram0800",
"line": "$F76C of that unit"
},
"5554": {
"unit": "game/mainProgram0800",
"line": "clear bit 6 = 'fortified', the flag the combat code consults"
},
"5556": {
"unit": "game/mainProgram0800",
"line": "store the type byte back"
},
"5559": {
"unit": "game/mainProgram0800",
"line": "done"
},
"555A": {
"unit": "game/mainProgram0800",
"line": "patch the new dig bits into the ORA immediate at $5567",
"routine": [
"setUnitDigBits - Shared prologue of cmdDigIn and cmdDigOut: patches the new dig bits into the ORA operand",
"at $5567, reads the unit argument, leaves it in Y and falls into applyDigBitsToUnit.",
"In: A = the new bits 5-7 ($20 or $00); the queued command",
"Out: self-modifies $5568; cmdParam0 and Y = the unit; unitStunTable[unit] rewritten",
"Called from: cmdDigIn (branch at $554A) and cmdDigOut ($554E)"
]
},
"555D": {
"unit": "game/mainProgram0800",
"line": "cmdParam0 = the unit index; read index += 2"
},
"5560": {
"unit": "game/mainProgram0800",
"line": "Y = the unit, and it stays there for cmdDigOut"
},
"5562": {
"unit": "game/mainProgram0800",
"line": "$F960: low nibble = stun counter, bit 4 spare, bits 5-7 = dig state",
"routine": [
"applyDigBitsToUnit - The two-line worker shared by the single-unit and the group dig handlers:",
"unitStunTable[Y] = (old & $1F) | patchedBits, so the low-nibble stun counter and bit 4 survive while the",
"dig-state bits 5-7 are replaced wholesale.",
"In: Y = unit index; the ORA operand at $5568 patched by setUnitDigBits or by the group entry at $5573",
"Out: unitStunTable $F960[Y]",
"Called from: the group loop at $558F, and by fall-through from setUnitDigBits"
]
},
"5565": {
"unit": "game/mainProgram0800",
"line": "keep the stun counter and bit 4"
},
"5567": {
"unit": "game/mainProgram0800",
"line": "immediate operand patched at $555A or $5573 with the new dig bits"
},
"5569": {
"unit": "game/mainProgram0800",
"line": "store the new dig state"
},
"556C": {
"unit": "game/mainProgram0800",
"line": "done"
},
"556D": {
"unit": "game/mainProgram0800",
"line": "$20 = dig bits 001 for every member",
"routine": [
"cmdGroupDigIn - Handler of command $A1 (2 bytes: group byte): patches the dig bits to $20 and runs the",
"shared group loop that follows.",
"In: the queued command; cmdParam0 = group byte",
"Out: unitStunTable $F960 of every living member = (old & $1F) | $20",
"Called from: commandHandlerTable slot for $A1 ($4D6C)"
]
},
"556F": {
"unit": "game/mainProgram0800",
"line": "always taken"
},
"5571": {
"unit": "game/mainProgram0800",
"line": "0 = clear the dig bits of every member",
"routine": [
"cmdGroupDigOut - Handler of command $A2 (2 bytes: group byte) and the entry of the shared group dig loop:",
"patches the dig bits to 0, reads the group byte, ORs $40 into it so it can be compared with",
"unitFlagsTable & $7C, and walks units 99..0 calling applyDigBitsToUnit for every living member. Unlike",
"cmdDigOut it never clears bit 6 of unitTypeTable, so a group dug in as a group can only be un-fortified one",
"unit at a time.",
"In: the queued command; cmdParam0 = group byte; unitTypeTable $F76C, unitFlagsTable $F708",
"Out: unitStunTable $F960 of every member; self-modifies $5568; read index += 2",
"Called from: commandHandlerTable slot for $A2 ($4D6E), and by branch from cmdGroupDigIn"
]
},
"5573": {
"unit": "game/mainProgram0800",
"line": "patch the ORA immediate shared with applyDigBitsToUnit"
},
"5576": {
"unit": "game/mainProgram0800",
"line": "cmdParam0 = the group byte; read index += 2"
},
"5579": {
"unit": "game/mainProgram0800",
"line": "the group byte"
},
"557B": {
"unit": "game/mainProgram0800",
"line": "bit 6 = 'in a group', so it matches unitFlagsTable & $7C directly"
},
"557D": {
"unit": "game/mainProgram0800",
"line": "keep it as the match value"
},
"557F": {
"unit": "game/mainProgram0800",
"line": "walk units 99 down to 0"
},
"5581": {
"unit": "game/mainProgram0800",
"line": "$F76C, bit 7 set = dead or free slot"
},
"5584": {
"unit": "game/mainProgram0800",
"line": "skip dead units"
},
"5586": {
"unit": "game/mainProgram0800",
"line": "the unit's $F708 flags"
},
"5589": {
"unit": "game/mainProgram0800",
"line": "group id, side and in-group bit"
},
"558B": {
"unit": "game/mainProgram0800",
"line": "is it a member of this group?"
},
"558D": {
"unit": "game/mainProgram0800",
"line": "no - next unit"
},
"558F": {
"unit": "game/mainProgram0800",
"line": "rewrite this member's dig bits"
},
"5592": {
"unit": "game/mainProgram0800",
"line": "step down one unit"
},
"5593": {
"unit": "game/mainProgram0800",
"line": "continue until unit 0 has been tested"
},
"5595": {
"unit": "game/mainProgram0800",
"line": "note that, unlike cmdDigOut, this never clears bit 6 of unitTypeTable"
},
"5596": {
"unit": "game/mainProgram0800",
"line": "cmdParam1 = the group leader's unit index, cmdParam2 = rotation mode 0-2; read index += 3",
"routine": [
"cmdSetFormation - Handler of command $A3 (3 bytes: group leader unit, rotation mode 0-2): reads the two",
"arguments and jumps to rotateGroupFormation ($4C84), which transforms every member's offset from the",
"leader's destination with the rule pair at $4C7E/$4C81 and re-issues the destinations.",
"In: the queued command; cmdParam1 = leader unit index, cmdParam2 = rotation mode",
"Out: the destinations of all the leader's group members; read index += 3",
"Called from: commandHandlerTable slot for $A3 ($4D70)"
]
},
"5599": {
"unit": "game/mainProgram0800",
"line": "$4C84: rotate every member's offset from the leader using the rules at $4C7E"
},
"559C": {
"unit": "game/mainProgram0800",
"line": "cmdParam0 = the options byte; read index += 2",
"routine": [
"cmdSetGameType - Handler of command $A4 (2 bytes): stores the argument into gameTypeOptions ($0BA3), which",
"carries the game type in bits 0-2, the UNIT MENUS and DAMAGE switches in bits 3-4, CUSTOM rules in bit 6 and",
"the custom-map flag in bit 7. Sent by overlay A's runMapTypeMenu ($833E) so both machines agree on the",
"scenario before the map is generated.",
"In: the queued command; cmdParam0 = the options byte",
"Out: gameTypeOptions $0BA3; read index += 2",
"Called from: commandHandlerTable slot for $A4 ($4D72)"
]
},
"559F": {
"unit": "game/mainProgram0800",
"line": "the options byte"
},
"55A1": {
"unit": "game/mainProgram0800",
"line": "$0BA3: bits 0-2 game type, bit 3 UNIT MENUS, bit 4 DAMAGE, bit 6 CUSTOM rules, bit 7 custom map"
},
"55A4": {
"unit": "game/mainProgram0800",
"line": "both machines now agree on the scenario"
},
"55A5": {
"unit": "game/mainProgram0800",
"line": "cmdParam1 = option kind, cmdParam2 = value; read index += 3",
"routine": [
"cmdSetupOption - Handler of command $A5 (3 bytes: kind, value), the generic carrier of overlay A's",
"custom-rules editors. Kind 0 (HANDICAP) adds value*10 to the 16-bit counter $92A2/$92A4 indexed by the",
"sender's absolute side; because that counter holds the points scored against a side, this is a head start",
"credited to the sender's opponent, and the index has to be restored from the operand at $55C1 because",
"multiply8x8 returns with X = 0. Kind 1 (TERRAIN PTS) stores the value in $92AC for the sender's opponent.",
"Kind 2 and above - only kind 3, COMCEN SPEED, is ever sent - stores it in $92B4 indexed 0 = us, 1 = him.",
"In: the queued command; cmdParam1 = kind, cmdParam2 = value; playerSide $0B9F, cmdFromOpponent zp_76",
"Out: $92A2/$92A4, $92AC or $92B4; self-modifies $55C2; read index += 3",
"Called from: commandHandlerTable slot for $A5 ($4D74)"
]
},
"55A8": {
"unit": "game/mainProgram0800",
"line": "$0B9F = the side this machine plays"
},
"55AB": {
"unit": "game/mainProgram0800",
"line": "who sent the option?"
},
"55AD": {
"unit": "game/mainProgram0800",
"line": "our own setting"
},
"55AF": {
"unit": "game/mainProgram0800",
"line": "remote: the other side"
},
"55B1": {
"unit": "game/mainProgram0800",
"line": "X = the sender's absolute side"
},
"55B2": {
"unit": "game/mainProgram0800",
"line": "stash it in the LDX immediate at $55C1, because multiply8x8 returns with X = 0"
},
"55B5": {
"unit": "game/mainProgram0800",
"line": "the option kind"
},
"55B7": {
"unit": "game/mainProgram0800",
"line": "kinds 1 and up"
},
"55B9": {
"unit": "game/mainProgram0800",
"line": "kind 0 = HANDICAP: the value counts in units of ten points",
"block": [
"Kind 0 - HANDICAP: add value*10 to the 16-bit score already credited against the sender's side."
]
},
"55BB": {
"unit": "game/mainProgram0800",
"line": "multiplier 10"
},
"55BD": {
"unit": "game/mainProgram0800",
"line": "$FCCD: A = low byte and Y = high byte of value*10"
},
"55C0": {
"unit": "game/mainProgram0800",
"line": "clear carry for the 16-bit add"
},
"55C1": {
"unit": "game/mainProgram0800",
"line": "immediate operand patched at $55B2: restore the sender's side index"
},
"55C3": {
"unit": "game/mainProgram0800",
"line": "$92A2/$92A3 = the 16-bit points scored against that side, so this is a head start for his opponent"
},
"55C6": {
"unit": "game/mainProgram0800",
"line": "store the new low byte"
},
"55C9": {
"unit": "game/mainProgram0800",
"line": "product high byte"
},
"55CA": {
"unit": "game/mainProgram0800",
"line": "$92A4/$92A5 = the high halves of the two side scores"
},
"55CD": {
"unit": "game/mainProgram0800",
"line": "store the new high byte"
},
"55D0": {
"unit": "game/mainProgram0800",
"line": "done"
},
"55D1": {
"unit": "game/mainProgram0800",
"line": "kind 2 and up?",
"block": [
"Kind 1 - TERRAIN PTS: the per-cell bonus for holding enemy ground, stored against the sender's opponent."
]
},
"55D3": {
"unit": "game/mainProgram0800",
"line": "yes - COMCEN SPEED and friends"
},
"55D5": {
"unit": "game/mainProgram0800",
"line": "kind 1 = TERRAIN PTS"
},
"55D6": {
"unit": "game/mainProgram0800",
"line": "the bonus is credited against the sender's opponent"
},
"55D8": {
"unit": "game/mainProgram0800",
"line": "X = that side"
},
"55D9": {
"unit": "game/mainProgram0800",
"line": "the value"
},
"55DB": {
"unit": "game/mainProgram0800",
"line": "$92AC[side]: points per cell of enemy ground held, read at $47A9"
},
"55DE": {
"unit": "game/mainProgram0800",
"line": "done"
},
"55DF": {
"unit": "game/mainProgram0800",
"line": "kind 2 and up (only kind 3, COMCEN SPEED, is ever sent): index 0 = us",
"block": [
"Kind 2 and up - in practice only kind 3, COMCEN SPEED, indexed 0 = us / 1 = the opponent."
]
},
"55E1": {
"unit": "game/mainProgram0800",
"line": "who sent it?"
},
"55E3": {
"unit": "game/mainProgram0800",
"line": "our own setting"
},
"55E5": {
"unit": "game/mainProgram0800",
"line": "index 1 = the opponent"
},
"55E6": {
"unit": "game/mainProgram0800",
"line": "the value"
},
"55E8": {
"unit": "game/mainProgram0800",
"line": "$92B4/$92B5; the value 4 is what makes $34E1 refuse to move a COMCEN at all"
},
"55EB": {
"unit": "game/mainProgram0800",
"line": "done"
},
"55EC": {
"unit": "game/mainProgram0800",
"line": "$929F counts commands queued but not yet executed; this entry is used by the 1-byte commands",
"routine": [
"queueCmdBytePending - Entry used by the 1-byte commands ($8F, $92, $97, $9E): counts one command in",
"pendingOwnCmdCount and falls into queueCmdByte to append the single byte.",
"In: A = the command byte",
"Out: pendingOwnCmdCount $929F incremented, one byte appended to the outgoing ring",
"Called from: $1814 in this file, and $79CC/$809A/$823C in both $6F00 overlays"
]
},
"55EF": {
"unit": "game/mainProgram0800",
"line": "$0B9B bit 7 = a film is being replayed",
"routine": [
"queueCmdByte - Appends A to the 40-byte outgoing command ring at $9065, wrapping the write index $908D at",
"40, and always leaves an $FF end marker one byte past the new tail. It does nothing at all while a game",
"film is being replayed, since the player issues no commands then.",
"In: A = byte, outgoingCmdWriteIndex $908D, filmPlaybackMode $0B9B",
"Out: outgoingCmdRing $9065[], $908D advanced; Y clobbered",
"Called from: queueCmd4/queueCmd3/queueCmd2 ($560A, $5612, $5617, $561C, $5622, $5627)"
]
},
"55F2": {
"unit": "game/mainProgram0800",
"line": "during playback the player issues no commands, so drop the byte"
},
"55F4": {
"unit": "game/mainProgram0800",
"line": "$908D = the write cursor of the 40-byte ring at $9065"
},
"55F7": {
"unit": "game/mainProgram0800",
"line": "store the byte in the ring"
},
"55FA": {
"unit": "game/mainProgram0800",
"line": "advance the cursor"
},
"55FB": {
"unit": "game/mainProgram0800",
"line": "$28 = 40 bytes in the ring"
},
"55FD": {
"unit": "game/mainProgram0800",
"line": "no wrap needed"
},
"55FF": {
"unit": "game/mainProgram0800",
"line": "wrap back to the start"
},
"5601": {
"unit": "game/mainProgram0800",
"line": "save the new write cursor"
},
"5604": {
"unit": "game/mainProgram0800",
"line": "$FF = end-of-queue marker"
},
"5606": {
"unit": "game/mainProgram0800",
"line": "always keep a terminator one byte past the last command byte"
},
"5609": {
"unit": "game/mainProgram0800",
"line": "done"
},
"560A": {
"unit": "game/mainProgram0800",
"line": "4-byte command: queue the command code held in A ...",
"routine": [
"queueCmd4 - Queues a 4-byte command: the code in A followed by cmdParam0, cmdParam1 and cmdParam2 (used for",
"$80 MOVE, $81, $8A, $8B, $8D, $98, $9C, $A6). It queues the code and cmdParam0 itself and then falls into",
"queueCmd3, which counts the command once and queues the remaining two arguments.",
"In: A = command code, cmdParam0/1/2 zp_73-zp_75",
"Out: four bytes in the outgoing ring, pendingOwnCmdCount $929F incremented once",
"Called from: $183D, $349C, $4A4A, $4C0D, $5646 and overlay A ($79BF, $8438)"
]
},
"560D": {
"unit": "game/mainProgram0800",
"line": "... then cmdParam0, and fall into queueCmd3 for the rest"
},
"560F": {
"unit": "game/mainProgram0800",
"line": "count the command once (queueCmd4 arrives here having already queued its code)",
"routine": [
"queueCmd3 - Queues a 3-byte command: the code in A followed by cmdParam1 and cmdParam2 (the commands of",
"length 3 are $87, $88, $8E, $90, $9A, $A3 and $A5). It is also the tail of queueCmd4, which arrives here",
"with A = cmdParam0 and its command code already in the ring, which is why the count is incremented here.",
"In: A = command code (or cmdParam0 when entered from queueCmd4), cmdParam1/cmdParam2",
"Out: three bytes in the outgoing ring, pendingOwnCmdCount $929F incremented once",
"Called from: $1939, $3C48, $4ACD, $6630 and both $6F00 overlays ($7667, $81A4, $84E9, $850E, $8544, $85D8)"
]
},
"5612": {
"unit": "game/mainProgram0800",
"line": "queue the command code, or cmdParam0 when entered from queueCmd4"
},
"5615": {
"unit": "game/mainProgram0800",
"line": "second argument"
},
"5617": {
"unit": "game/mainProgram0800",
"line": "queue it"
},
"561A": {
"unit": "game/mainProgram0800",
"line": "third argument"
},
"561C": {
"unit": "game/mainProgram0800",
"line": "queue it and return to the caller"
},
"561F": {
"unit": "game/mainProgram0800",
"line": "count the command in pendingOwnCmdCount",
"routine": [
"queueCmd2 - Queues a 2-byte command: the code in A followed by cmdParam0 (the commands of length 2 are the",
"unit, group and reason commands $82-$86, $89, $8C, $91, $93-$96, $9B, $9D, $9F-$A2 and $A4).",
"In: A = command code, cmdParam0 zp_73",
"Out: two bytes in the outgoing ring, pendingOwnCmdCount $929F incremented once",
"Called from: $1AA9, $1C22, $20B8, $4B01, $5545, $5FA3, $62DE, $64E1, $65CD, $8340 and $C7A7"
]
},
"5622": {
"unit": "game/mainProgram0800",
"line": "queue the command code held in A"
},
"5625": {
"unit": "game/mainProgram0800",
"line": "its single argument"
},
"5627": {
"unit": "game/mainProgram0800",
"line": "queue it and return to the caller"
},
"562A": {
"unit": "game/mainProgram0800",
"line": "number of bytes already placed in the outgoing packet by sendOutgoingPacket"
},
"562B": {
"unit": "game/mainProgram0800",
"line": "$920D bit 6 = our own drone is under joystick control",
"routine": [
"sendOutgoingPacket - Builds the packet for the next lock-step exchange and hands it to the comm module.",
"If our drone is under joystick control, is not paused and has not finished, it is first stepped one cell and",
"an $8D heading/column/row report is queued. Whole commands are then copied out of the outgoing ring into the",
"comm module's transmit buffer at $E027 as long as the packet stays below 5 bytes; a $92 is dropped while",
"droneArrivalFlags bit 6 says the drone detonated instead of arriving, and an $8E found while no drone flight",
"is in progress flushes the entire ring and sends an empty packet. Finally $E01F is set to the packet length",
"and the comm module is asked to transmit.",
"In: outgoingCmdRing $9065 with $908D/$908E, droneControlFlags $920D, droneArrivalFlags $92F9, pauseState",
"Out: $E027.., $E01F, outgoingCmdReadIndex $908E, outPacketFill $562A; self-modifies $566B",
"Called from: executeTurnCommands $4F02, disconnectFromOpponent $1C25 and overlay A $7979"
]
},
"562E": {
"unit": "game/mainProgram0800",
"line": "no drone of ours to steer"
},
"5630": {
"unit": "game/mainProgram0800",
"line": "zp_BE is non-zero while either side is paused"
},
"5632": {
"unit": "game/mainProgram0800",
"line": "do not step the drone while the game is paused"
},
"5634": {
"unit": "game/mainProgram0800",
"line": "$92F9 bit 7 = the drone has already finished its flight"
},
"5637": {
"unit": "game/mainProgram0800",
"line": "nothing left to report"
},
"5639": {
"unit": "game/mainProgram0800",
"line": "overlay $731E: turn droneHeading toward the joystick and move the drone one cell"
},
"563C": {
"unit": "game/mainProgram0800",
"line": "overlay $81FE: cmdParam1/cmdParam2 = droneViewOrigin + (6,5) = the drone's own map cell"
},
"563F": {
"unit": "game/mainProgram0800",
"line": "$920B = the current 0-7 compass heading"
},
"5642": {
"unit": "game/mainProgram0800",
"line": "first argument of the report"
},
"5644": {
"unit": "game/mainProgram0800",
"line": "command $8D DRONE POSITION"
},
"5646": {
"unit": "game/mainProgram0800",
"line": "queue heading, column and row"
},
"5649": {
"unit": "game/mainProgram0800",
"line": "$908E = the read cursor of the outgoing ring",
"block": [
"Fill the outgoing packet from the command ring. Only whole commands are copied and the packet holds at",
"most 4 command bytes, so a long burst of orders is spread over several exchanges."
]
},
"564C": {
"unit": "game/mainProgram0800",
"line": "0"
},
"564E": {
"unit": "game/mainProgram0800",
"line": "the packet starts out empty"
},
"5651": {
"unit": "game/mainProgram0800",
"line": "has the read cursor caught the write cursor?"
},
"5654": {
"unit": "game/mainProgram0800",
"line": "the ring is empty - send what we have"
},
"5656": {
"unit": "game/mainProgram0800",
"line": "the next command byte in the ring"
},
"5659": {
"unit": "game/mainProgram0800",
"line": "patch it into the LDA # at $566A so the special-case tests below can look at it"
},
"565C": {
"unit": "game/mainProgram0800",
"line": "$4D9F: total length of the command from commandLengthTable, also left in $92EE"
},
"565F": {
"unit": "game/mainProgram0800",
"line": "clear carry for the add"
},
"5660": {
"unit": "game/mainProgram0800",
"line": "how long the packet would be with this command in it"
},
"5663": {
"unit": "game/mainProgram0800",
"line": "a packet carries at most 4 command bytes"
},
"5665": {
"unit": "game/mainProgram0800",
"line": "it would not fit - leave it in the ring until next exchange"
},
"5667": {
"unit": "game/mainProgram0800",
"line": "X = the write offset inside the packet buffer"
},
"566A": {
"unit": "game/mainProgram0800",
"line": "immediate operand patched at $5659 with the command byte"
},
"566C": {
"unit": "game/mainProgram0800",
"line": "$92 = 'the drone reached its target'",
"block": [
"Two special cases are filtered out here rather than at queue time, because whether they are still valid",
"only becomes clear when the packet is built."
]
},
"566E": {
"unit": "game/mainProgram0800",
"line": "not a $92"
},
"5670": {
"unit": "game/mainProgram0800",
"line": "$92F9 bit 6 = the drone detonated instead of being shot down"
},
"5673": {
"unit": "game/mainProgram0800",
"line": "bit 6 clear: the shot-down report is still valid, so copy it"
},
"5675": {
"unit": "game/mainProgram0800",
"line": "drop the stale $92: back the packet cursor up so the INX at $5696 cancels out"
},
"5676": {
"unit": "game/mainProgram0800",
"line": "step over it in the ring without copying anything"
},
"5679": {
"unit": "game/mainProgram0800",
"line": "$8E = 'detonate the drone'"
},
"567B": {
"unit": "game/mainProgram0800",
"line": "not an $8E"
},
"567D": {
"unit": "game/mainProgram0800",
"line": "$920D bit 7 = a drone flight of ours really is in progress"
},
"5680": {
"unit": "game/mainProgram0800",
"line": "it is - copy the detonate command normally"
},
"5682": {
"unit": "game/mainProgram0800",
"line": "stale detonate: throw the whole ring away by moving the read cursor to the write cursor ..."
},
"5685": {
"unit": "game/mainProgram0800",
"line": "... and send an empty packet this exchange"
},
"5687": {
"unit": "game/mainProgram0800",
"line": "always taken"
},
"5689": {
"unit": "game/mainProgram0800",
"line": "copy one byte of the command",
"block": [
"Copy the remaining bytes of this command from the ring into the packet, wrapping the ring index at 40."
]
},
"568C": {
"unit": "game/mainProgram0800",
"line": "$E027 is the comm module's 7-byte transmit buffer"
},
"568F": {
"unit": "game/mainProgram0800",
"line": "advance the ring cursor"
},
"5690": {
"unit": "game/mainProgram0800",
"line": "$28 = 40 bytes in the ring"
},
"5692": {
"unit": "game/mainProgram0800",
"line": "no wrap needed"
},
"5694": {
"unit": "game/mainProgram0800",
"line": "wrap back to the start"
},
"5696": {
"unit": "game/mainProgram0800",
"line": "advance the packet cursor"
},
"5697": {
"unit": "game/mainProgram0800",
"line": "$92EE counts the bytes of this command down"
},
"569A": {
"unit": "game/mainProgram0800",
"line": "copy the rest of the command"
},
"569C": {
"unit": "game/mainProgram0800",
"line": "remember how full the packet is"
},
"569F": {
"unit": "game/mainProgram0800",
"line": "is there room for at least one more byte?"
},
"56A1": {
"unit": "game/mainProgram0800",
"line": "yes - look at the next command in the ring"
},
"56A3": {
"unit": "game/mainProgram0800",
"line": "X = the finished packet length"
},
"56A6": {
"unit": "game/mainProgram0800",
"line": "the ring is consumed up to here"
},
"56A9": {
"unit": "game/mainProgram0800",
"line": "$E01F = the number of bytes the comm module should transmit"
},
"56AC": {
"unit": "game/mainProgram0800",
"line": "comm function 1 = request a send"
},
"56AE": {
"unit": "game/mainProgram0800",
"line": "$E000; the modem driver queues the packet, the solo trainer throws it away"
},
"56B1": {
"unit": "game/mainProgram0800",
"line": "the comm module's carrier state byte",
"routine": [
"pollMenuSession - Reached while a link is up but no game is running (commSessionState $0B9C bit 7): if the",
"carrier is gone the session is torn down, otherwise the menu-level packet handler runs. Note that the",
"commandHandlerTable slot for command $99 also points here, but nothing in the game ever queues a $99, so",
"that entry is unreachable filler.",
"In: the comm module's carrier byte $E03C",
"Out: falls into handleMenuSessionPacket, or jumps to disconnectFromOpponent",
"Called from: processCommandExchange $4DCE and $4E3E"
]
},
"56B4": {
"unit": "game/mainProgram0800",
"line": "bit 6 = carrier present"
},
"56B6": {
"unit": "game/mainProgram0800",
"line": "still connected: service the menu-level traffic"
},
"56B8": {
"unit": "game/mainProgram0800",
"line": "$1C17: carrier lost - tear the session down and hang up"
},
"56BB": {
"unit": "game/mainProgram0800",
"line": "10 frames",
"routine": [
"handleMenuSessionPacket - Menu-level receive path: re-arms the 10-frame link poll delay and returns unless a",
"packet has arrived. A 2-byte [$8C,$05] means the opponent hung up, so we hang up too; [$8C,$02] means he is",
"ready for a new game and puts 'OPPONENT READY FOR NEW GAME.' on the status line, rate limited by the middle",
"byte of the frame counter so the message cannot repeat every exchange. Every other packet just falls into",
"completeExchange to be consumed.",
"In: $E01D, $E01E, $E020/$E021, frameCounterMid zp_46",
"Out: linkPollDelay $9163 = 10, message $1F, zp_46 reset; falls into completeExchange",
"Called from: pollMenuSession (branch at $56B6) and the text engine's pause handler $C737"
]
},
"56BD": {
"unit": "game/mainProgram0800",
"line": "$9163: do not poll the link again for 10 frames"
},
"56C0": {
"unit": "game/mainProgram0800",
"line": "$E01D exchange flags"
},
"56C3": {
"unit": "game/mainProgram0800",
"line": "bit 7 = a packet from the opponent has arrived"
},
"56C5": {
"unit": "game/mainProgram0800",
"line": "nothing received - come back next poll"
},
"56C6": {
"unit": "game/mainProgram0800",
"line": "$E01E = length of the received packet"
},
"56C9": {
"unit": "game/mainProgram0800",
"line": "only 2-byte menu packets mean anything here"
},
"56CB": {
"unit": "game/mainProgram0800",
"line": "wrong length - just finish the exchange"
},
"56CD": {
"unit": "game/mainProgram0800",
"line": "first byte of the received packet"
},
"56D0": {
"unit": "game/mainProgram0800",
"line": "$8C = session control"
},
"56D2": {
"unit": "game/mainProgram0800",
"line": "not a session-control packet"
},
"56D4": {
"unit": "game/mainProgram0800",
"line": "the reason byte"
},
"56D7": {
"unit": "game/mainProgram0800",
"line": "reason 5 = the opponent hung up"
},
"56D9": {
"unit": "game/mainProgram0800",
"line": "not a hang-up"
},
"56DB": {
"unit": "game/mainProgram0800",
"line": "$1B33: drop our end of the line too"
},
"56DE": {
"unit": "game/mainProgram0800",
"line": "reason 2 = READY FOR A NEW GAME"
},
"56E0": {
"unit": "game/mainProgram0800",
"line": "some other reason - ignore it"
},
"56E2": {
"unit": "game/mainProgram0800",
"line": "zp_46, the middle byte of the free-running frame counter, is abused as a crude rate limit"
},
"56E4": {
"unit": "game/mainProgram0800",
"line": "about 3*256 frames since the last announcement"
},
"56E6": {
"unit": "game/mainProgram0800",
"line": "too soon - say nothing this time"
},
"56E8": {
"unit": "game/mainProgram0800",
"line": "message $1F"
},
"56EA": {
"unit": "game/mainProgram0800",
"line": "'OPPONENT READY FOR NEW GAME.'"
},
"56ED": {
"unit": "game/mainProgram0800",
"line": "0"
},
"56EF": {
"unit": "game/mainProgram0800",
"line": "restart the rate limit (and, as a side effect, the frame counter's middle byte)"
},
"56F1": {
"unit": "game/mainProgram0800",
"line": "$E01D exchange flags",
"routine": [
"completeExchange - Makes sure the packet exchange currently in flight finishes. Nothing pending returns at",
"once; an already completed exchange is consumed; if our own packet is out or a send is already requested it",
"only waits; otherwise an empty packet is sent to give the opponent something to answer. It then spins for up",
"to $4B ticks of linkWaitTimer, servicing the status line, until the exchange completes, and consumes it. A",
"timeout hangs the modem up.",
"In: $E01D exchange flags, linkSessionState $0B9C",
"Out: $E01F, linkWaitTimer zp_7F, the comm module's exchange state; or jumps to hangUpModem",
"Called from: $1B2A, readyForNewGameHandshake $572B, handleMenuSessionPacket (four branches) and $EE06"
]
},
"56F4": {
"unit": "game/mainProgram0800",
"line": "nothing in flight - nothing to finish"
},
"56F6": {
"unit": "game/mainProgram0800",
"line": "look at the flags"
},
"56F7": {
"unit": "game/mainProgram0800",
"line": "bit 6 = the exchange has completed"
},
"56F9": {
"unit": "game/mainProgram0800",
"line": "just consume it"
},
"56FB": {
"unit": "game/mainProgram0800",
"line": "look at the flags again"
},
"56FC": {
"unit": "game/mainProgram0800",
"line": "bit 5 = our packet has already gone out"
},
"56FE": {
"unit": "game/mainProgram0800",
"line": "only his answer is missing - wait for it"
},
"5700": {
"unit": "game/mainProgram0800",
"line": "look at the flags again"
},
"5701": {
"unit": "game/mainProgram0800",
"line": "bit 0 = a send has already been requested"
},
"5703": {
"unit": "game/mainProgram0800",
"line": "wait for it"
},
"5705": {
"unit": "game/mainProgram0800",
"line": "0"
},
"5707": {
"unit": "game/mainProgram0800",
"line": "$E01F = 0: nothing to say, so send a zero-length packet"
},
"570A": {
"unit": "game/mainProgram0800",
"line": "A carries the session state down to the comm module"
},
"570D": {
"unit": "game/mainProgram0800",
"line": "comm function 1 = request a send"
},
"570F": {
"unit": "game/mainProgram0800",
"line": "hand it over"
},
"5712": {
"unit": "game/mainProgram0800",
"line": "$4B = 75 ticks; zp_7F is decremented every 8th frame, so roughly ten seconds"
},
"5714": {
"unit": "game/mainProgram0800",
"line": "arm the timeout"
},
"5716": {
"unit": "game/mainProgram0800",
"line": "$C4EF keeps the status line, chat and clock alive while we spin"
},
"5719": {
"unit": "game/mainProgram0800",
"line": "$E01D exchange flags"
},
"571C": {
"unit": "game/mainProgram0800",
"line": "bit 6: the exchange completed"
},
"571E": {
"unit": "game/mainProgram0800",
"line": "the timeout counts down in the raster IRQ"
},
"5720": {
"unit": "game/mainProgram0800",
"line": "keep waiting"
},
"5722": {
"unit": "game/mainProgram0800",
"line": "the opponent never answered - hang up the modem"
},
"5725": {
"unit": "game/mainProgram0800",
"line": "comm function 2 = acknowledge and clear the finished exchange"
},
"5727": {
"unit": "game/mainProgram0800",
"line": "$E000, then return to our caller"
},
"572A": {
"unit": "game/mainProgram0800",
"line": "nothing to do"
},
"572B": {
"unit": "game/mainProgram0800",
"line": "make sure no half-finished exchange is left over from the previous game",
"routine": [
"readyForNewGameHandshake - Run before a new game over an existing link: finishes any pending exchange, puts",
"'WAITING FOR OPPONENT...' up as a persistent message and then repeatedly sends [$8C,$02] and waits for the",
"exchange to complete. When the opponent's packet is also [$8C,$02] the session state becomes 2 (game",
"session), the message is cleared and the status line is wiped; [$8C,$05] means he hung up; anything else is",
"consumed and the READY packet is sent again.",
"In: $E01D/$E01E/$E020/$E021",
"Out: linkSessionState $0B9C = 2, persistentMessageId $92A1 cleared; or jumps to hangUpModem",
"Called from: overlay A $795A"
]
},
"572E": {
"unit": "game/mainProgram0800",
"line": "message $0B"
},
"5730": {
"unit": "game/mainProgram0800",
"line": "'WAITING FOR OPPONENT...' stays on the status line for the whole handshake"
},
"5733": {
"unit": "game/mainProgram0800",
"line": "show it now"
},
"5736": {
"unit": "game/mainProgram0800",
"line": "put $8C,$02 = READY FOR A NEW GAME in the transmit buffer"
},
"5739": {
"unit": "game/mainProgram0800",
"line": "comm function 1 = request a send"
},
"573B": {
"unit": "game/mainProgram0800",
"line": "hand it over"
},
"573E": {
"unit": "game/mainProgram0800",
"line": "service the status line while waiting"
},
"5741": {
"unit": "game/mainProgram0800",
"line": "$E01D exchange flags"
},
"5744": {
"unit": "game/mainProgram0800",
"line": "spin until bit 6 says the exchange completed"
},
"5746": {
"unit": "game/mainProgram0800",
"line": "length of the packet that came back"
},
"5749": {
"unit": "game/mainProgram0800",
"line": "a menu packet is exactly 2 bytes"
},
"574B": {
"unit": "game/mainProgram0800",
"line": "anything else: consume it and try again"
},
"574D": {
"unit": "game/mainProgram0800",
"line": "first byte of his packet"
},
"5750": {
"unit": "game/mainProgram0800",
"line": "$8C = session control"
},
"5752": {
"unit": "game/mainProgram0800",
"line": "not session control - try again"
},
"5754": {
"unit": "game/mainProgram0800",
"line": "his reason byte"
},
"5757": {
"unit": "game/mainProgram0800",
"line": "2 = he is ready for a new game as well"
},
"5759": {
"unit": "game/mainProgram0800",
"line": "not a READY - check for a hang-up"
},
"575B": {
"unit": "game/mainProgram0800",
"line": "2 = a game session has been agreed"
},
"575D": {
"unit": "game/mainProgram0800",
"line": "$0B9C session state"
},
"5760": {
"unit": "game/mainProgram0800",
"line": "0"
},
"5762": {
"unit": "game/mainProgram0800",
"line": "take the WAITING message off the line"
},
"5765": {
"unit": "game/mainProgram0800",
"line": "comm function 2 = consume the finished exchange"
},
"5767": {
"unit": "game/mainProgram0800",
"line": "hand it over"
},
"576A": {
"unit": "game/mainProgram0800",
"line": "$C39F wipes the status line and returns to overlay A's caller"
},
"576D": {
"unit": "game/mainProgram0800",
"line": "reason 5 = he hung up instead"
},
"576F": {
"unit": "game/mainProgram0800",
"line": "some other reason - consume it and resend"
},
"5771": {
"unit": "game/mainProgram0800",
"line": "$1B33: drop our end of the line too"
},
"5774": {
"unit": "game/mainProgram0800",
"line": "comm function 2 = consume whatever it was"
},
"5776": {
"unit": "game/mainProgram0800",
"line": "hand it over"
},
"5779": {
"unit": "game/mainProgram0800",
"line": "and send our READY packet again"
},
"577C": {
"unit": "game/mainProgram0800",
"line": "command $8C session control",
"routine": [
"buildReadyPacket - Fills the comm module's transmit buffer with the 2-byte packet $8C,$02 (session control,",
"reason 2 = READY FOR A NEW GAME) and sets the transmit length $E01F to 2.",
"In: none",
"Out: $E027 = $8C, $E028 = $02, $E01F = 2",
"Called from: readyForNewGameHandshake $5736"
]
},
"577E": {
"unit": "game/mainProgram0800",
"line": "first byte of the comm module's transmit buffer"
},
"5781": {
"unit": "game/mainProgram0800",
"line": "reason 2 = READY FOR A NEW GAME"
},
"5783": {
"unit": "game/mainProgram0800",
"line": "second byte of the transmit buffer"
},
"5786": {
"unit": "game/mainProgram0800",
"line": "$E01F = 2 bytes to transmit (the same value as the reason byte only by coincidence)"
},
"5789": {
"unit": "game/mainProgram0800",
"line": "the packet is ready to be sent"
},
"578A": {
"unit": "game/mainProgram0800",
"line": "$92EB = number of command bytes merged for this exchange",
"routine": [
"recordExchangeToFilm - Appends one lock-step exchange to the game film held in the RAM under the I/O area.",
"Nothing is written when the exchange carried no commands, when the battle is not running, or when the film",
"buffer is full. A record is [ (splitIndex << 4) | length ] [ time stamp, 1 or 2 bytes ] [ length command",
"bytes ]; the split index says how many of the bytes belong to the first player, so playback can restore the",
"own/remote flag exactly. Every byte goes through writeFilmByte, which banks the I/O area out around the",
"store, and filmPtr is advanced by the length of the record at the end.",
"In: mergedCmdLength $92EB, mergedCmdSplitIndex $92EC, mergedCmdBuffer $908F, filmPtr zp_BC/zp_BD,",
" filmEndPtr $92F1/$92F2, gamePhase zp_B1",
"Out: film bytes written, filmPtr advanced; X = length, Y = record size",
"Called from: executeTurnCommands $4EFA"
]
},
"578D": {
"unit": "game/mainProgram0800",
"line": "nothing happened - do not waste a film record"
},
"578F": {
"unit": "game/mainProgram0800",
"line": "zp_B1 is zero only during the battle"
},
"5791": {
"unit": "game/mainProgram0800",
"line": "the setup screens and menus are not filmed"
},
"5793": {
"unit": "game/mainProgram0800",
"line": "$591E: C = 1 once filmPtr has reached filmEndPtr"
},
"5796": {
"unit": "game/mainProgram0800",
"line": "the film buffer in the RAM under the I/O area is full"
},
"5798": {
"unit": "game/mainProgram0800",
"line": "X = index into mergedCmdBuffer"
},
"579A": {
"unit": "game/mainProgram0800",
"line": "Y = offset within the film record being written"
},
"579C": {
"unit": "game/mainProgram0800",
"line": "$92EC = how many of the merged bytes belong to the first player"
},
"579F": {
"unit": "game/mainProgram0800",
"line": "shift it ..."
},
"57A0": {
"unit": "game/mainProgram0800",
"line": "... into ..."
},
"57A1": {
"unit": "game/mainProgram0800",
"line": "... the high ..."
},
"57A2": {
"unit": "game/mainProgram0800",
"line": "... nibble of the header byte"
},
"57A3": {
"unit": "game/mainProgram0800",
"line": "low nibble = the total length; both always fit in four bits"
},
"57A6": {
"unit": "game/mainProgram0800",
"line": "$5893 stores at (filmPtr),Y with $01 = $34 so the RAM under $D000 is visible"
},
"57A9": {
"unit": "game/mainProgram0800",
"line": "next byte of the record"
},
"57AA": {
"unit": "game/mainProgram0800",
"line": "$57BE: one or two bytes of clock delta plus the sub-tick index; it advances Y itself"
},
"57AD": {
"unit": "game/mainProgram0800",
"line": "copy the merged command bytes verbatim - this is what playback re-executes",
"block": [
"Copy the exchange's command bytes verbatim; playback feeds exactly these bytes back through",
"executeTurnCommands, which is why a film replays identically whether the opponent was a person or the",
"solo trainer."
]
},
"57B0": {
"unit": "game/mainProgram0800",
"line": "write it to the film"
},
"57B3": {
"unit": "game/mainProgram0800",
"line": "next film byte"
},
"57B4": {
"unit": "game/mainProgram0800",
"line": "next command byte"
},
"57B5": {
"unit": "game/mainProgram0800",
"line": "all of them written?"
},
"57B8": {
"unit": "game/mainProgram0800",
"line": "no - keep copying"
},
"57BA": {
"unit": "game/mainProgram0800",
"line": "$57E7: filmPtr += Y, the length of the record just written"
},
"57BD": {
"unit": "game/mainProgram0800",
"line": "done"
}
},
"dataTypes": {
"562A": [
"byte",
1
]
},
"zpComments": {
"73": "first argument byte of the command being queued or executed: unit index, group byte, session-control reason, recycler mode or map-seed byte 0",
"74": "second argument byte of the command: usually the map column, but also the unit index of the 3-byte group commands ($87, $88, $9A, $A3) and map-seed byte 1",
"75": "third argument byte of the command: usually the map row, but also the target unit, group byte, option value or map-seed byte 2",
"76": "bit 7 set while the command being merged or executed came from the opponent: its coordinates are mirrored with 39-v, its side is playerSide EOR 1, and most sound/message side effects are suppressed for our own copy",
"7F": "countdown decremented every 8th frame; completeExchange and the link waits load it with $4B (about ten seconds) and hang the modem up if the exchange has not completed by then",
"B1": "game phase: 0 only while the battle runs. Bit 7 = still waiting for the map seed (cleared by command $8B/$A6), bit 5 = side selection still open (cleared to $DF by command $9D), bits 0-1 = the two sides' 'not ready' bits cleared one at a time by command $9E through getPauseClearMask",
"BE": "pause state: bit 0 = this machine paused, bit 1 = the opponent paused, the whole byte is 3 after a link timeout and $80 after a deliberate hang-up. Bits are cleared one side at a time by command $8C reason 4 through getPauseClearMask; while it is non-zero the drone is not stepped",
"BC": "pointer (lo) into the game film buffer in the RAM under the I/O area; recordExchangeToFilm writes each exchange at (filmPtr),Y and then adds the record length",
"BD": "high byte of filmPtr; compareFilmPtrToEnd stops recording when the pair reaches filmEndPtr $92F1/$92F2"
}
}