{ "chunk": "game_ovl_6F00_A_7C29", "unit": "game/ovl_6F00_A", "routines": [ { "addr": "7C2E", "name": "applyGameTypeDefaults", "summary": "Loads the per-side game parameters for the selected game type from the two 5x5 default tables at $892C (side 0) and $8945 (side 1) in the $8800 strings overlay. The type ($0BA3 & 7) is first folded into a table column 0-4 (0 SCRIMAGE, 1 QB SNEAK, 2 THE BOMB/FACE-OFF/SLUGGERS, 3 FULL WAR, 4 DEFENDER); the column also indexes setupPhaseModeTable ($7C29) -> $92C6. Rows 0-1 (terrain points $92AC, recycler mode $92A6) are stored per absolute side, rows 2-4 (comcen speed class $92B4, drones $92AE, missiles $92B0) per relative index (0 = own side, 1 = opponent). Then QB SNEAK removes units 0-6 from play and THE BOMB removes units 0-98, re-creates unit 49 as a COMCEN (type 4) and gives both sides one extra drone. Called once from the game start code ($79E9) after the game type has been chosen.", "inputs": "D_0BA3 bits 0-2, D_0B9F, tables $892C/$8945, D_7C29", "outputs": "D_92C6, D_92AC/D_92A6 (per side), D_92B4/D_92AE/D_92B0 (own, opponent), D_F76C/D_FAF0 entries set to $FF for removed units, D_F79D=4, zp_18, zp_48/49; self-modifies $7C86 and $7CC0", "confidence": "high" }, { "addr": "7CA2", "name": "removeUnitsUpToY", "summary": "Loop that marks units Y down to 0 as absent: writes $FF into unitTypeTable ($F76C) and unitUnderCell ($FAF0). Entered with Y=6 (QB SNEAK) or Y=$62 (THE BOMB) by applyGameTypeDefaults; the RTS at $7CAD is also the shared exit of applyGameTypeDefaults.", "inputs": "Y = highest unit index to remove", "outputs": "D_F76C[0..Y] = D_FAF0[0..Y] = $FF; Y = $FF", "confidence": "high" }, { "addr": "7CAE", "name": "selectSideDefaultsTable", "summary": "Points zp_48/zp_49 at the game-type defaults table of side X: $892C for side 0, $8945 for side 1 (5 rows of 5 bytes each, in the $8800 strings overlay).", "inputs": "X = side 0/1", "outputs": "zp_48/zp_49 = table pointer", "confidence": "high" }, { "addr": "7CBF", "name": "readDefaultAndAdvanceRow", "summary": "Reads byte [column] of the current 5-byte table row through zp_48 (the LDY #$FF operand at $7CC0 is patched with the game-type column by applyGameTypeDefaults) and advances zp_48/zp_49 to the next row (+5).", "inputs": "zp_48/zp_49 = current row, patched column index", "outputs": "A = table value, zp_48/zp_49 += 5", "confidence": "high" }, { "addr": "7CD1", "name": "startFilmPlayback", "summary": "Game-start path taken when filmPlaybackFlag ($0B9B) bit 7 is set (WATCH GAME FILM). Restores the game parameters from the film header in RAM under I/O at $D000 (read with sub_5874, zp_BC/BD already = $D000): byte 4 -> linkTypeAtStart ($92FE), 5 -> playerSide ($0B9F), $0C -> gameTypeOptions ($0BA3), $11-$24 -> the two 10-byte player names at $FFDE, $2B-$2D -> mapSeed ($0B95), $2E bit 0 -> mapOrientationFlag ($0B9D) and bits 1-2 -> playsFirstFlag ($0BA1), 6-$0B -> settings text ($0BA9). Then patches the side-test branches (sub_7BC6), regenerates the map from the seed (sub_6F04), rotates it 180 degrees if the film side differs from the map orientation (sub_EC58), restores the film's saved unit/score state (sub_EE4A), writes 'CUSTOM' into the settings text when bit 6 is set (sub_86C3), places all units (placeAllUnitsOnMap), homes the view (sub_EC1C), redraws the screen, sets lastQuarterFlag and draws the opponent's recycler, then joins the common game start at L_7B8F.", "inputs": "film header at $D000 (zp_BC/zp_BD = $D000), D_0B9B bit 7", "outputs": "D_0BA8=0, D_92FC=0, D_92FE, D_0B9F, D_0BA3, D_FFDE..D_FFF1, D_0B95-D_0B97, D_0B9D, D_0BA1, D_0BA9-D_0BAE, D_90FB=0, D_92FA=$FF, map at $F000 regenerated; jumps to L_7B8F", "confidence": "high" }, { "addr": "7D70", "name": "initUnitOrderLists", "summary": "Fills the two 100-entry unit order lists unitBaseOrder ($9164) and unitProcessOrder ($90FD) with the identity permutation 0..99 (99 down to 0). Called by sub_7BB2 before the lists are shuffled with the scenario RNG.", "inputs": "none", "outputs": "D_9164[0..99] = D_90FD[0..99] = index; Y=$FF", "confidence": "high" }, { "addr": "7D7D", "name": "placeAllUnitsOnMap", "summary": "Calls placeUnitOnMap (sub_3DDA) for every unit 99 down to 0 (unit index passed in X and zp_18). Used at game start ($7B83) and when a film is replayed (startFilmPlayback).", "inputs": "unit tables", "outputs": "map cells at $F000 updated for all live units; zp_18 = $FF", "confidence": "high" }, { "addr": "7D8B", "name": "setGameClockForType", "summary": "Looks up the game length in rounds for the game type ($0BA3 & 7) in gameClockByTypeTable ($78C3: 63,63,31,127,127,254,254) and stores it in gameClock ($91CB); also returns it in A (the caller at $7B9E shifts it right twice to get the quarter length $92EF).", "inputs": "D_0BA3 bits 0-2, D_78C3", "outputs": "A = D_91CB = rounds", "confidence": "high" }, { "addr": "7D98", "name": "clearUnitsAndRecyclersFromMap", "summary": "Removes every unit 99..0 from the map (removeUnitFromMap restores the terrain under it) and then blanks the recycler cell of both sides via clearRecyclerCell. Called from the game start sequence ($7B7A) before the units are re-placed.", "inputs": "unit tables, D_92A8/D_92AA", "outputs": "map at $F000 without units and recyclers", "confidence": "high" }, { "addr": "7DA7", "name": "clearRecyclerCell", "summary": "If side X has a recycler (recyclerCol $92A8,x bit 7 clear) computes the map address of (recyclerCol, recyclerRow) with readMapCell and writes the blank terrain byte $40 into it.", "inputs": "X = side, D_92A8/D_92AA", "outputs": "map cell := $40, zp_48/zp_49", "confidence": "high" }, { "addr": "7DB8", "name": "waitForSpaceKey", "summary": "Clears lastKeyCode, chatState, inputLockoutTimer and chatInputLength, then loops serviceCommandExchange + nextGameRandom until the space key ($A0) is seen in lastKeyCode ($9248). Afterwards clears the message queue, sets inputLockoutTimer to 30 frames and resets lastKeyCode. Entered by JMP from showModemSetupPrompt ($0C9D) so the RTS returns to that routine's caller; stirring the RNG while waiting randomises the map seed.", "inputs": "D_9248 (set by the IRQ keyboard scan)", "outputs": "D_9248=$FF, D_929C=0, D_9277=0, D_0B7D=$1E, message queue cleared, RNG advanced", "confidence": "high" }, { "addr": "7DE3", "name": "handleMainMenuChoice", "summary": "Acts on the main-menu item in menuSelectedItem ($91D5), entered by JMP from runMainMenu ($1C78). 0 COMPETE WITH MODEM OPPONENT: linkTypeFlag $0BA5=0, $0B9B=0. 1 PRACTICE WITH SOLO TRAINER: $0BA5=$FF, $0B9B=0. 2 WATCH GAME FILM: requires a valid film in memory (checkFilmInMemory), keeps $0BA5, $0B9B=$82. These three jump to startGameFromSetup ($0AC8). 3 SAVE GAME FILM (needs a valid film) and 4 LOAD GAME FILM: prompt for the data disk, suspend the comm module, checkDiskAndLoadFilmDirectory (returns on failure), resume, set defaultMainMenuItem to 2 (WATCH GAME FILM), filmDirectoryPage=0, filmMenuMode=item-3, draw the directory page and run runSaveFilmMenu or runLoadFilmMenu, then promptInsertGameDisk and return.", "inputs": "D_91D5, D_0BA5, film in $D000", "outputs": "D_0BA5, D_0B9B, D_92FD=2, D_7FA1=0, D_7FA2; jumps to $0AC8 for items 0-2", "confidence": "high" }, { "addr": "7E39", "name": "setModeFlagsAndStartGame", "summary": "Tail of handleMainMenuChoice: stores X into linkTypeFlag ($0BA5) and A into filmPlaybackFlag ($0B9B) and jumps to startGameFromSetup ($0AC8).", "inputs": "X = new $0BA5, A = new $0B9B", "outputs": "D_0BA5, D_0B9B; jumps to $0AC8", "confidence": "high" }, { "addr": "7E4F", "name": "checkDiskAndLoadFilmDirectory", "summary": "Identifies the disk in the drive and loads the film directory. First tries the game disk silently (checkDiskId X=0, A=$80); if that fails tries the data disk with messages (X=2, A=0) and additionally requires BAM bytes $0203-$028F to be all zero (otherwise 'NOT A DATA DISK' via L_100D). With a valid disk it falls into loadFilmDirectory (track 18 sectors 2-3 -> $0300-$04FF). diskIdIndex ($92F0) is left 0 for the game disk and 2 for a data disk, which later selects 1 or 3 directory pages.", "inputs": "disk in drive", "outputs": "C=1 no usable disk (message shown), C=0 directory loaded at $0300; D_92F0, D_0200 BAM buffer", "confidence": "high" }, { "addr": "7E74", "name": "writeFilmDirectory", "summary": "Waits 8 frames, then writes the film directory $0300-$04FF (2 sectors) to track 18 sectors 2-3 with writeSectors. A write failure goes to diskErrorExit.", "inputs": "directory at $0300", "outputs": "track 18 s2-3 rewritten; C=0", "confidence": "high" }, { "addr": "7E8B", "name": "loadFilmDirectory", "summary": "Reads track 18 sectors 2-3 into $0300-$04FF with loadSectorsRetry (the 30 x 16-byte film names); failure -> diskErrorExit. Tail of checkDiskAndLoadFilmDirectory.", "inputs": "none", "outputs": "$0300-$04FF = film directory, C=0", "confidence": "high" }, { "addr": "7EA0", "name": "drawFilmDirectoryPage", "summary": "Draws one page of the film directory menu: clears the menu window, prints the centred title 'SAVE GAME FILM' ($CC66) or 'LOAD GAME FILM' ($CC75) depending on filmMenuMode, then from the page pointer (setFilmDirectoryPagePtr) lists 10 entries as right-aligned slot number + space + 16-character name, then ' (MORE)' ($EF81) when a data disk is in use and ' (EXIT)' ($CAB2). Sets up the selector: menuItemCount 11 or 12, window left $0B width $12, menuFirstRow 9, menuSelectedItem 0, colour $60.", "inputs": "D_7FA1, D_7FA2, D_92F0, directory at $0300", "outputs": "screen text, D_91D4, D_91D5=0, D_91D6=9, zp_3B=$0B, zp_3C=$12, zp_3D=$60, zp_48/zp_49, D_90F8 and D_9220 used as counters", "confidence": "high" }, { "addr": "7F3F", "name": "runLoadFilmMenu", "summary": "LOAD GAME FILM: runs the menu selector (sub_5FD1, C=1) on the directory page. Item 10 with a data disk = next page (nextFilmDirectoryPage, then restart); item 10/11 otherwise = exit. For a slot 0-9: suspends the comm module, converts the slot to a track (filmSlotToTrack), reads the track's 16 sectors one at a time into $0200 and copies each page under the I/O area to $D000-$DFFF (copyPageUnderIo with patched pointers $58C3/$58C6), resumes the comm module and validates the film (checkFilmInMemory); a valid film clears the message queue.", "inputs": "D_91D5, D_92F0, D_7FA1, disk", "outputs": "$D000-$DFFF = film, D_91D5 = track, zp_18/zp_19, D_58C3/4, D_58C6/7", "confidence": "high" }, { "addr": "7FA6", "name": "setFilmDirectoryPagePtr", "summary": "zp_48/zp_49 = $0300 + filmDirectoryPage * $A0 (10 entries of 16 bytes per page) and stores the page's first slot number (filmPageFirstSlotTable: 1, 11, 21) in $90F8 for the numbering loop.", "inputs": "D_7FA1, D_7FA3", "outputs": "zp_48/zp_49, D_90F8", "confidence": "high" }, { "addr": "7FCB", "name": "nextFilmDirectoryPage", "summary": "Advances filmDirectoryPage modulo 3 and redraws the page (jumps to drawFilmDirectoryPage).", "inputs": "D_7FA1", "outputs": "D_7FA1, redrawn page", "confidence": "high" }, { "addr": "7FDD", "name": "runSaveFilmMenu", "summary": "SAVE GAME FILM slot picker with inline name entry - a variant of the generic menu selector sub_5FD1. Highlights the current slot; while a slot 0-9 is selected keys are fed to the text-entry handler sub_C7D5 (max 16 chars into the line buffer $9278, length in $92E6) and the typed name is echoed on the slot's row with a cursor block ($11 in screen RAM). Joystick up/down moves the bar (with auto-repeat via setJoystickRepeatDelay), which discards the typed text. Fire or RETURN accepts: item 10 on a data disk = next page and restart, otherwise exit (EXIT); for a slot with typed text the name is copied into the 16-byte directory entry (space padded, bit 7 on the last byte), the row is redrawn and saveFilmToSlot writes directory and film.", "inputs": "D_91D4, D_91D5, D_91D6, D_7FA1, keyboard/joystick (D_90EA, zp_5D), zp_6C/zp_6E timers", "outputs": "Y = D_91D5 (track after a save), D_92E6=0, D_92C5, D_9278, directory entry, disk; zp_4A/zp_4B = entry pointer", "confidence": "high" }, { "addr": "810C", "name": "printFilmSlotEntry", "summary": "Positions the text cursor on the current menu row (menuFirstRow + menuSelectedItem, column 0) with colour A, clears the line and prints a space followed by the entry text: the 16-byte name at zp_4A/zp_4B for slots 0-9, ' (MORE)' for item 10 on a data disk, else ' (EXIT)'.", "inputs": "A = text colour, D_91D5, D_91D6, D_92F0, zp_4A/zp_4B", "outputs": "screen text, zp_3D, zp_3F=0", "confidence": "high" }, { "addr": "8143", "name": "filmSlotToTrack", "summary": "Converts the selected directory slot into a disk track: page*10 + item + 2, plus 1 when the result is >= 18 so the directory track is skipped (slots 0-29 -> tracks 2-17 and 19-32). The track replaces menuSelectedItem ($91D5) and is returned in A.", "inputs": "D_7FA1, D_91D5", "outputs": "A = D_91D5 = track", "confidence": "high" }, { "addr": "815E", "name": "saveFilmToSlot", "summary": "Suspends the comm module, converts the slot to a track, rewrites the directory (writeFilmDirectory) and then copies the 16 pages of the film at $D000-$DFFF (RAM under I/O) one at a time to $0200 and writes each as sector 0-15 of the track with writeSectors. Finally resumes the comm module.", "inputs": "D_91D5 slot, film at $D000", "outputs": "track sectors 0-15 written, D_91D5 = track, zp_18/zp_19, D_58C3/4, D_58C6/7", "confidence": "high" }, { "addr": "81A5", "name": "promptInsertGameDisk", "summary": "Patches 'GAME' ($EFD4) into the message 'INSERT xxxx DISK AND PRESS SPACE.' (promptInsertDiskAndWaitSpace), then waitForGameDiskInserted and resumeCommAfterDiskAccess. End of the save/load film flow.", "inputs": "none", "outputs": "game disk present, comm module resumed", "confidence": "high" }, { "addr": "81B2", "name": "promptInsertDataDisk", "summary": "Entry of promptInsertDiskAndWaitSpace with A/Y = $EFD0 ('DATA').", "inputs": "none", "outputs": "see promptInsertDiskAndWaitSpace", "confidence": "high" }, { "addr": "81B6", "name": "promptInsertDiskAndWaitSpace", "summary": "Copies the 4-character disk name at A/Y into the message template at $EFB6 ('INSERT DATA DISK AND PRESS SPACE.' at $EFAF, in the comm-module tail), installs that string as message $1E (sub_C924), shows it (statusMessageId $92A1 = $1E, sub_C566), waits for the space key while servicing the link, clicks, clears the message id and erases the message line (sub_C39F).", "inputs": "A/Y = address of a 4-byte name ('DATA' $EFD0 or 'GAME' $EFD4)", "outputs": "D_EFB6..D_EFB9 patched, message slot $1E, D_92A1, D_9248", "confidence": "high" }, { "addr": "81F4", "name": "checkFilmInMemory", "summary": "Validates the film header at $D000 with sub_58DE (checksum of bytes 0-$32 in byte $33 and version byte $2F == $0B94). Valid: C=0. Invalid: queues message 9 'NOT A GAME FILM!', waits about 4 seconds (waitFramesServicingComm) and returns C=1.", "inputs": "film header at $D000", "outputs": "C flag, zp_BC/zp_BD = $D000, A = header byte 4 when valid", "confidence": "high" }, { "addr": "81FE", "name": "waitFramesServicingComm", "summary": "Sets joyRepeatTimer zp_6C to $F0 and calls serviceCommandExchange until the IRQ has counted it down (about 4 seconds), then returns C=1. Also called from the main program's queue flush at $563C (that call lands in different code when overlay B is resident).", "inputs": "none", "outputs": "C=1, zp_6C=0", "confidence": "high" }, { "addr": "820B", "name": "beginGameTypeSelection", "summary": "Entry used by the side that picks the game ($79A1, side 0): clears the message queue, sets currentScreen to 0, clears the CUSTOM-ON message id patch ($868D) and installs 'CUSTOM OFF' ($EFD8) as message $1E, then falls into runGameTypeMenu.", "inputs": "none", "outputs": "D_90FB=0, message slot $1E, $868D=0", "confidence": "high" }, { "addr": "821F", "name": "runGameTypeMenu", "summary": "Game-type selection screen. Points the CUSTOM message patch at $EFDF, derives customModeFlag ($92F3 bit 7) from $0BA3 bit 6, clears and redraws the screen, resets the selection (resetSelectionAndView), clears the info panel; in solo-trainer mode toggles the display set (sub_EEDD) and with custom rules shows the CUSTOM status (sub_8673). Prints 'GAME TYPE:' ($CCBD) and the 7 type names ($CCC8..) and runs the selector starting at the current type. The choice selects a preset from gameTypePresetTable ($78CA): with custom rules the preset (or the unchanged $0BA3 when the same type is re-chosen) gets bit 6 and two ON/OFF sub-menus follow ('UNIT MENUS' -> bit 3, 'DAMAGE' -> bit 4 via sub_860B). Continues in runMapTypeMenu. Also reached by JMP from $2E07 (screen redraw, relevant to the overlay-B routine at this address).", "inputs": "D_0BA3, D_0BA5, D_92F3, menu input", "outputs": "D_0BA3, D_92F3, D_91D4=7, D_91D5, zp_B1=$BF (custom path), zp_3D/zp_42=9; self-modifies $829E, $8319, $869A/$869B", "confidence": "high" }, { "addr": "8279", "name": "printGameTypeNamesLoop", "summary": "Not a routine in variant A: loop body of runGameTypeMenu that prints the next type name (sub_C137) and a newline 7 times (zp_18 counts down). The label exists because variant B has a routine at $8279.", "inputs": "zp_18", "outputs": "screen text", "confidence": "high" }, { "addr": "82A4", "name": "applyCustomPresetTail", "summary": "Not a routine in variant A: continuation inside runGameTypeMenu that ORs the custom bit $40 into the chosen preset, stores it in $0BA3 and runs the UNIT MENUS and DAMAGE toggles. Variant B has a routine at this address.", "inputs": "A = preset", "outputs": "D_0BA3, zp_B1=$BF", "confidence": "high" }, { "addr": "82BF", "name": "runMapTypeMenu", "summary": "Second half of the game setup menus: resets the selection, clears the info panel, prints 'MAP TYPE' ($EF77) with the items STANDARD/CUSTOM ($CC11) preselected from $0BA3 bit 7, and runs the selector; the result becomes $0BA3 bit 7. Then sends the game type to the opponent (sendGameTypeCommand $A4), copies the scenario layout for the type (sub_ECF3), sets gamePhase zp_B1=$FF and playsFirstFlag=1 when $0BA3 changed against pendingGameTypeOptions ($0BA4). CUSTOM map -> jumps to the map editor at L_76AD. STANDARD map -> derives mapSeed ($0B95-$0B97) from the RNG state zp_57-zp_59 (low 5 bits forced to 1 for symmetric types; for QB SNEAK/DEFENDER only when this side plays first). The main program also calls $82BF from resolveAttack ($3A30), which targets the overlay-B routine at this address.", "inputs": "D_0BA3, D_0BA4, D_0BA1, zp_57-zp_59, menu input", "outputs": "D_0BA3, D_0BA1, D_0B95-D_0B97, zp_B1=$FF, zp_73, command $A4 queued", "confidence": "high" }, { "addr": "833C", "name": "sendGameTypeCommand", "summary": "Stores A in cmdParam0 (zp_73) and queues command $A4 with that single parameter (sub_561F) - tells the opponent the chosen gameTypeOptions byte.", "inputs": "A = game type options byte", "outputs": "zp_73, command queued", "confidence": "high" }, { "addr": "8343", "name": "printMenuTitle", "summary": "Sets menuSelectedItem to X (the preselected item), prints the string at A/Y centred in the current window (sub_C121), sets the window left column to $1C, positions the cursor and records the cursor row as menuFirstRow ($91D6).", "inputs": "X = initial item, A/Y = title string", "outputs": "D_91D5, D_91D6, zp_3B=$1C", "confidence": "high" }, { "addr": "8356", "name": "runMenuWidth10", "summary": "Runs the menu selector with a highlight width of 10 (falls into runMenuWithWidth).", "inputs": "menu variables", "outputs": "A = D_91D5 = chosen item", "confidence": "high" }, { "addr": "8358", "name": "runMenuWithWidth", "summary": "Sets the text window width zp_3C to A, moves the left column one to the left, and runs the generic selector sub_5FD1 with C=0; returns the chosen item in A (from $91D5).", "inputs": "A = width, D_91D4, D_91D5, D_91D6", "outputs": "A = D_91D5, zp_3B--, zp_3C", "confidence": "high" }, { "addr": "8364", "name": "resetSelectionAndView", "summary": "Clears the selection state for the setup menus: unitDrawOverride $9242 = $FE, selectedUnit $90F8 = $FE (none), currentScreen $90FB = 0, and moves the view origin zp_A5/zp_A6 to (45,45), i.e. completely off the 40x40 map so the viewport is blank.", "inputs": "none", "outputs": "D_9242, D_90F8, D_90FB, zp_A5, zp_A6", "confidence": "high" }, { "addr": "8378", "name": "chooseRecycler", "summary": "Recycler setup (called from the options flow at $85B6 for game types >= 4). In solo mode both recycler modes are forced to 2 (NONE). If recyclerMenuEnabled ($8450) is set, shows the 'RECYCLER' menu with FULL/HALF/NONE (printOptionWord) preselected from recyclerMode[side]; otherwise keeps the current mode and returns immediately when it is NONE. NONE sends command $9C with column/row $FF. Otherwise prints 'LOCATING'/'RECYCLER', homes the view (sub_EC1C), shows the cursor and viewport and lets the player pick a map cell (runTargetCursorLoop with cursorTargetMode=$FF) until a cell whose terrain is blank ($40, looking under a unit if one stands there) is chosen; then hides sprites, resets the view and sends command $9C (mode, col, row) via sub_560A.", "inputs": "D_0BA5, D_0B9F, D_92A6, D_8450, player input, D_91D8/D_91D9", "outputs": "D_92A6 (solo), D_91D5, zp_73-zp_75, command $9C queued, D_9022=0, zp_BB=$FF; self-modifies $8433", "confidence": "high" }, { "addr": "843B", "name": "printOptionWordLine", "summary": "Prints option word A (0 FULL, 1 HALF, 2 NONE via printOptionWord) followed by a newline.", "inputs": "A = option index", "outputs": "screen text", "confidence": "high" }, { "addr": "8441", "name": "clearInfoPanelSetRow", "summary": "Sets text colour zp_3D and message colour zp_42 to 8, clears the info panel (clearInfoPanel) and sets the text cursor to row A (sub_C097).", "inputs": "A = cursor row", "outputs": "zp_3D=zp_42=8, zp_40=A, cursor positioned", "confidence": "high" } ], "variables": [ { "addr": "18", "scope": "zp", "name": "tempByte18", "meaning": "scratch: player loop index in applyGameTypeDefaults, unit index for placeAllUnitsOnMap, sector counter in the film save/load loops, name length copy, type-name counter", "confidence": "high" }, { "addr": "19", "scope": "zp", "name": "tempByte19", "meaning": "scratch: disk track of the film slot during save/load", "confidence": "high" }, { "addr": "3B", "scope": "zp", "name": "textWindowLeft", "meaning": "text window left column ($C000 text engine)", "confidence": "high" }, { "addr": "3C", "scope": "zp", "name": "textWindowWidth", "meaning": "text window width; also the width of the menu highlight bar", "confidence": "high" }, { "addr": "3D", "scope": "zp", "name": "textColour", "meaning": "colour byte for printed text / menu highlight EOR", "confidence": "high" }, { "addr": "3F", "scope": "zp", "name": "textCursorCol", "meaning": "text cursor column relative to the window", "confidence": "high" }, { "addr": "40", "scope": "zp", "name": "textCursorRow", "meaning": "text cursor row", "confidence": "high" }, { "addr": "42", "scope": "zp", "name": "messageTextColour", "meaning": "text colour used while a message is displayed", "confidence": "high" }, { "addr": "48", "scope": "zp", "name": "genericPtr", "meaning": "pointer (lo): defaults table row in applyGameTypeDefaults, film directory page in the film menus, map cell pointer from readMapCell", "confidence": "high" }, { "addr": "49", "scope": "zp", "name": "genericPtrHi", "meaning": "high byte of genericPtr", "confidence": "high" }, { "addr": "4A", "scope": "zp", "name": "filmEntryPtr", "meaning": "pointer (lo) to the selected 16-byte film directory entry in runSaveFilmMenu/printFilmSlotEntry", "confidence": "high" }, { "addr": "4B", "scope": "zp", "name": "filmEntryPtrHi", "meaning": "high byte of filmEntryPtr", "confidence": "high" }, { "addr": "57", "scope": "zp", "name": "rngState0", "meaning": "game RNG state byte 0; copied (with low 5 bits set) into mapSeed by runMapTypeMenu", "confidence": "high" }, { "addr": "58", "scope": "zp", "name": "rngState1", "meaning": "game RNG state byte 1 -> mapSeed byte 1", "confidence": "high" }, { "addr": "59", "scope": "zp", "name": "rngState2", "meaning": "game RNG state byte 2 -> mapSeed byte 2", "confidence": "high" }, { "addr": "5D", "scope": "zp", "name": "joyDirection", "meaning": "joystick direction bits (active low, $0F centred) polled by the IRQ", "confidence": "high" }, { "addr": "6C", "scope": "zp", "name": "joyRepeatTimer", "meaning": "frame countdown gating joystick auto-repeat in the menus; also the 4-second wait of waitFramesServicingComm", "confidence": "high" }, { "addr": "6E", "scope": "zp", "name": "menuBlinkTimer", "meaning": "frame countdown (20) used to blink the menu highlight bar", "confidence": "high" }, { "addr": "73", "scope": "zp", "name": "cmdParam0", "meaning": "first parameter of an outgoing command (game type for $A4, recycler mode for $9C)", "confidence": "high" }, { "addr": "74", "scope": "zp", "name": "cmdParam1", "meaning": "second command parameter (recycler column for $9C)", "confidence": "high" }, { "addr": "75", "scope": "zp", "name": "cmdParam2", "meaning": "third command parameter (recycler row for $9C)", "confidence": "high" }, { "addr": "A5", "scope": "zp", "name": "viewOriginCol", "meaning": "map column of the view's left edge (45 = off map in resetSelectionAndView)", "confidence": "high" }, { "addr": "A6", "scope": "zp", "name": "viewOriginRow", "meaning": "map row of the view's top edge", "confidence": "high" }, { "addr": "B1", "scope": "zp", "name": "gamePhase", "meaning": "0 = battle; nonzero setup/menu phases ($BF custom setup, $FF after the map is chosen)", "confidence": "medium" }, { "addr": "B6", "scope": "zp", "name": "screenCellPtr", "meaning": "screen RAM pointer of the current text cell; runSaveFilmMenu writes the cursor block $11 through it", "confidence": "high" }, { "addr": "BB", "scope": "zp", "name": "cursorTargetMode", "meaning": "$FF = free map-cell pick for runTargetCursorLoop (recycler placement)", "confidence": "high" }, { "addr": "BC", "scope": "zp", "name": "ioRamPtr", "meaning": "pointer (lo) used by sub_5874/sub_5893 to read/write RAM under the I/O area; $D000 = film header", "confidence": "high" }, { "addr": "BD", "scope": "zp", "name": "ioRamPtrHi", "meaning": "high byte of ioRamPtr", "confidence": "high" }, { "addr": "0200", "scope": "abs", "name": "bamBuffer", "meaning": "BAM sector read by checkDiskId; a data disk must have bytes $0203-$028F all zero", "confidence": "high" }, { "addr": "0300", "scope": "abs", "name": "filmDirectory", "meaning": "$0300-$04DF: 30 film names of 16 bytes (3 pages of 10), loaded from / written to track 18 sectors 2-3", "confidence": "high" }, { "addr": "0500", "scope": "abs", "name": "messagePtrTableLo", "meaning": "low bytes of the status message string pointers (message $1E is patched dynamically here)", "confidence": "high" }, { "addr": "0524", "scope": "abs", "name": "messagePtrTableHi", "meaning": "high bytes of the status message string pointers", "confidence": "high" }, { "addr": "0B7D", "scope": "abs", "name": "inputLockoutTimer", "meaning": "frame countdown during which the IRQ ignores input; 30 after waitForSpaceKey", "confidence": "high" }, { "addr": "0B94", "scope": "abs", "name": "filmFormatVersion", "meaning": "byte compared with film header byte $2F by sub_58DE", "confidence": "medium" }, { "addr": "0B95", "scope": "abs", "name": "mapSeed", "meaning": "3-byte map generator seed ($0B95-$0B97); film header bytes $2B-$2D", "confidence": "high" }, { "addr": "0B9B", "scope": "abs", "name": "filmPlaybackFlag", "meaning": "bit 7 = play back the film in $D000 ($82 set by WATCH GAME FILM: bits 0-1 = view both sides)", "confidence": "high" }, { "addr": "0B9D", "scope": "abs", "name": "mapOrientationFlag", "meaning": "0/1: side for which the map at $F000 is currently oriented; rotated 180 degrees (sub_EC58) when it differs from playerSide; film header byte $2E bit 0", "confidence": "medium" }, { "addr": "0B9F", "scope": "abs", "name": "playerSide", "meaning": "local side 0/1; film header byte 5", "confidence": "high" }, { "addr": "0BA1", "scope": "abs", "name": "playsFirstFlag", "meaning": "nonzero = this side plays first; film header byte $2E bits 1-2", "confidence": "high" }, { "addr": "0BA3", "scope": "abs", "name": "gameTypeOptions", "meaning": "bits 0-2 game type, bit 3 UNIT MENUS option, bit 4 DAMAGE option, bit 6 custom rules, bit 7 custom map; film header byte $0C", "confidence": "high" }, { "addr": "0BA4", "scope": "abs", "name": "pendingGameTypeOptions", "meaning": "previous/pending copy of gameTypeOptions; a difference sets playsFirstFlag", "confidence": "high" }, { "addr": "0BA5", "scope": "abs", "name": "soloTrainerFlag", "meaning": "bit 7 set = PRACTICE WITH SOLO TRAINER (comm module build 1 from track 35 + its $EC00 extension from track 29 act as the AI opponent); 0 = COMPETE WITH MODEM OPPONENT; film header byte 4", "confidence": "high" }, { "addr": "0BA8", "scope": "abs", "name": "noGameInProgress", "meaning": "0 while a game runs (cleared by startFilmPlayback)", "confidence": "high" }, { "addr": "0BA9", "scope": "abs", "name": "settingsText", "meaning": "6-byte settings text; film header bytes 6-$0B; sub_86C3 writes CUSTOM into the $0332 copy", "confidence": "medium" }, { "addr": "58C3", "scope": "abs", "name": "copySrcPtr", "meaning": "self-modified source operand of copyPageUnderIo sub_58B2 (lo/hi at $58C3/$58C4)", "confidence": "high" }, { "addr": "58C6", "scope": "abs", "name": "copyDstPtr", "meaning": "self-modified destination operand of copyPageUnderIo (lo/hi at $58C6/$58C7)", "confidence": "high" }, { "addr": "7FA1", "scope": "abs", "name": "filmDirectoryPage", "meaning": "current film directory page 0-2 (10 slots each)", "confidence": "high" }, { "addr": "7FA2", "scope": "abs", "name": "filmMenuMode", "meaning": "0 = SAVE GAME FILM, 1 = LOAD GAME FILM (main-menu item - 3)", "confidence": "high" }, { "addr": "8450", "scope": "abs", "name": "recyclerMenuEnabled", "meaning": "nonzero makes chooseRecycler show the FULL/HALF/NONE menu (set by the options editor at $8564/$859C)", "confidence": "medium" }, { "addr": "892C", "scope": "abs", "name": "sideDefaultsTableSide0", "meaning": "5 rows x 5 columns (column = game-type class): terrain points, recycler mode, comcen speed class, drones, missiles for side 0; in the $8800 strings overlay", "confidence": "high" }, { "addr": "8945", "scope": "abs", "name": "sideDefaultsTableSide1", "meaning": "same layout as $892C for side 1", "confidence": "high" }, { "addr": "90F8", "scope": "abs", "name": "selectedUnit", "meaning": "selected unit index ($FE = none); reused as the slot-number counter by drawFilmDirectoryPage", "confidence": "high" }, { "addr": "90FB", "scope": "abs", "name": "currentScreen", "meaning": "active screen id (0 = battlefield)", "confidence": "high" }, { "addr": "90FD", "scope": "abs", "name": "unitProcessOrder", "meaning": "100-byte unit processing order list", "confidence": "high" }, { "addr": "9164", "scope": "abs", "name": "unitBaseOrder", "meaning": "100-byte base unit order list", "confidence": "high" }, { "addr": "91CB", "scope": "abs", "name": "gameClock", "meaning": "game length in rounds (from gameClockByTypeTable)", "confidence": "high" }, { "addr": "91D4", "scope": "abs", "name": "menuItemCount", "meaning": "number of items for the menu selector", "confidence": "high" }, { "addr": "91D5", "scope": "abs", "name": "menuSelectedItem", "meaning": "current/selected menu item; filmSlotToTrack overwrites it with the disk track", "confidence": "high" }, { "addr": "91D6", "scope": "abs", "name": "menuFirstRow", "meaning": "screen row of the first menu item", "confidence": "high" }, { "addr": "91D8", "scope": "abs", "name": "pickedCellX", "meaning": "map column returned by runTargetCursorLoop (recycler position)", "confidence": "high" }, { "addr": "91D9", "scope": "abs", "name": "pickedCellY", "meaning": "map row returned by runTargetCursorLoop", "confidence": "high" }, { "addr": "9022", "scope": "abs", "name": "spriteEnableShadow", "meaning": "shadow of VIC $D015 (cleared after the recycler is placed)", "confidence": "high" }, { "addr": "9220", "scope": "abs", "name": "loopCounter9220", "meaning": "generic down counter (10 directory entries in drawFilmDirectoryPage)", "confidence": "medium" }, { "addr": "9242", "scope": "abs", "name": "unitDrawOverride", "meaning": "$FE written by resetSelectionAndView", "confidence": "medium" }, { "addr": "9248", "scope": "abs", "name": "lastKeyCode", "meaning": "last key pressed, $A0 = space, $FF = none", "confidence": "high" }, { "addr": "9277", "scope": "abs", "name": "chatInputLength", "meaning": "chat input length, cleared by waitForSpaceKey", "confidence": "medium" }, { "addr": "9278", "scope": "abs", "name": "textInputBuffer", "meaning": "line buffer edited by the key handler sub_C7D5 (terminator $9E); holds the film name in runSaveFilmMenu", "confidence": "high" }, { "addr": "929C", "scope": "abs", "name": "chatState", "meaning": "chat typing state, cleared by waitForSpaceKey", "confidence": "medium" }, { "addr": "92A1", "scope": "abs", "name": "statusMessageId", "meaning": "id of the status message on screen ($1E = dynamic slot used for the INSERT DISK / CUSTOM prompts, 0 = none)", "confidence": "high" }, { "addr": "92A6", "scope": "abs", "name": "recyclerMode", "meaning": "per side: 0 FULL, 1 HALF, 2 NONE", "confidence": "high" }, { "addr": "92A8", "scope": "abs", "name": "recyclerCol", "meaning": "per side map column of the recycler, bit 7 = none", "confidence": "high" }, { "addr": "92AA", "scope": "abs", "name": "recyclerRow", "meaning": "per side map row of the recycler", "confidence": "high" }, { "addr": "92AC", "scope": "abs", "name": "terrainPointsBySide", "meaning": "per side TERRAIN PTS option (0-2, defaults table row 0)", "confidence": "high" }, { "addr": "92AE", "scope": "abs", "name": "droneCount", "meaning": "2 bytes (own, opponent): DRONES option (defaults table row 3; THE BOMB adds one)", "confidence": "high" }, { "addr": "92B0", "scope": "abs", "name": "missileCount", "meaning": "2 bytes (own, opponent): MISSILES option (defaults table row 4)", "confidence": "high" }, { "addr": "92B4", "scope": "abs", "name": "comcenSpeedClass", "meaning": "2 bytes (own, opponent): COMCEN SPEED class (defaults table row 2, later mapped through $78BF)", "confidence": "high" }, { "addr": "92C5", "scope": "abs", "name": "menuHighlightOn", "meaning": "1 while the menu highlight bar is drawn (toggled by sub_60EA)", "confidence": "high" }, { "addr": "92C6", "scope": "abs", "name": "setupPhaseMode", "meaning": "from setupPhaseModeTable: 0 = no unit setup phase (SCRIMAGE, QB SNEAK), 1 = both sides set up (THE BOMB..FULL WAR), $80 = only side 0 sets up (DEFENDER); tested by $C470", "confidence": "medium" }, { "addr": "92E6", "scope": "abs", "name": "filmNameLength", "meaning": "number of characters typed into textInputBuffer for the film name (0 = keep the old name)", "confidence": "high" }, { "addr": "92F0", "scope": "abs", "name": "diskIdIndex", "meaning": "0 after the game disk was identified, 2 after a data disk (enables the 3-page film directory)", "confidence": "high" }, { "addr": "92F3", "scope": "abs", "name": "customModeFlag", "meaning": "bit 7 = custom rules active ($0BA3 bit 6 at menu entry, toggled by '*' during setup, shown as CUSTOM ON/OFF)", "confidence": "medium" }, { "addr": "92FA", "scope": "abs", "name": "lastQuarterFlag", "meaning": "$FF written by startFilmPlayback", "confidence": "low" }, { "addr": "92FC", "scope": "abs", "name": "revealAllUnitsFlag", "meaning": "bit 7 = show all units; cleared at film start", "confidence": "high" }, { "addr": "92FD", "scope": "abs", "name": "defaultMainMenuItem", "meaning": "initial main-menu item; set to 2 (WATCH GAME FILM) after a film save/load", "confidence": "high" }, { "addr": "92FE", "scope": "abs", "name": "linkTypeAtStart", "meaning": "copy of soloTrainerFlag at game start; film header byte 4", "confidence": "high" }, { "addr": "EFB6", "scope": "abs", "name": "insertDiskNameField", "meaning": "4 characters inside 'INSERT DATA DISK AND PRESS SPACE.' ($EFAF, comm-module tail from track 34) overwritten with DATA/GAME", "confidence": "high" }, { "addr": "F76C", "scope": "abs", "name": "unitTypeTable", "meaning": "per-unit type byte, $FF = unit absent (set for removed units)", "confidence": "high" }, { "addr": "F79D", "scope": "abs", "name": "unitTypeComcen0", "meaning": "unitTypeTable entry of unit 49 (side 0 COMCEN), restored to type 4 in THE BOMB", "confidence": "high" }, { "addr": "FA70", "scope": "abs", "name": "unitUnderCellAlias", "meaning": "unitUnderCell indexed by map byte ($80|unit)", "confidence": "high" }, { "addr": "FAF0", "scope": "abs", "name": "unitUnderCell", "meaning": "per-unit terrain byte under the unit, $FF = absent", "confidence": "high" }, { "addr": "FFDE", "scope": "abs", "name": "playerNames", "meaning": "2 x 10 bytes ($FFDE own name from the BAM, $FFE8 opponent/trainer name); film header bytes $11-$24", "confidence": "medium" }, { "addr": "D000", "scope": "abs", "name": "filmHeader", "meaning": "game film in RAM under I/O: header bytes 4 link type, 5 side, 6-$0B settings text, $0C game type options, $11-$24 names, $2B-$2D map seed, $2E orientation/plays-first, $2F version, $33 checksum of 0-$32, $34.. saved scores/unit state; 16 pages saved per slot", "confidence": "high" } ], "dataBlocks": [ { "addr": "7C29", "length": 5, "type": "byteTable", "name": "setupPhaseModeTable", "description": "Indexed by the game-type class 0-4 of applyGameTypeDefaults: {0,0,1,1,$80} -> $92C6 (0 no setup phase, 1 both sides deploy, $80 only side 0 deploys)." }, { "addr": "7FA1", "length": 1, "type": "byteTable", "name": "filmDirectoryPage", "description": "Variable: current film directory page (0-2)." }, { "addr": "7FA2", "length": 1, "type": "byteTable", "name": "filmMenuMode", "description": "Variable: 0 = save film, 1 = load film." }, { "addr": "7FA3", "length": 3, "type": "byteTable", "name": "filmPageFirstSlotTable", "description": "{1,11,21}: slot number printed for the first entry of each directory page." } ], "misclassified": [], "insights": [ "Chunk boundaries: the spec's line range 2026-2895 does not match the address range $7C29-$844E (file lines 2080-3198); this survey covers the address range $7C29-$844E.", "Game films: a recorded game lives in the 4 KB of RAM under the I/O area at $D000-$DFFF (accessed with $01=$34 by sub_5874/sub_5893/sub_58B2). Header layout as written by $7A93-$7AD7 and read back by startFilmPlayback: +4 soloTrainerFlag, +5 playerSide, +6..+$0B settings text, +$0C gameTypeOptions, +$11..+$24 the two 10-byte player names ($FFDE/$FFE8), +$2B..+$2D mapSeed, +$2E = playsFirst<<1 | mapOrientation, +$2F format byte (must equal $0B94), +$33 checksum of bytes 0-$32, +$34.. scores/unit state restored by sub_EE4A.", "Film disk format: track 18 sectors 2-3 ($0300-$04DF) hold a directory of 30 names x 16 bytes (3 pages of 10, bit 7 terminates a name). Slot n of page p is stored on track 2 + 10p + n (+1 when >= 18, so tracks 2-17 and 19-32), 16 sectors = the whole $D000-$DFFF block. On the game disk only page 0 (tracks 2-11, the $01 filler tracks) is offered; a data disk (id 'EA', BAM bytes $0203-$028F all zero) gets all three pages via the ' (MORE)' item. checkDiskId leaves diskIdIndex $92F0 = 2 for a data disk and the directory code keys the page count on it.", "Main menu items (strings at $CC1F..): 0 COMPETE WITH MODEM OPPONENT -> $0BA5=0; 1 PRACTICE WITH SOLO TRAINER -> $0BA5=$FF; 2 WATCH GAME FILM -> $0B9B=$82; 3 SAVE GAME FILM; 4 LOAD GAME FILM; 5 DISCONNECT/SET VOICE. Hence $0BA5 bit 7 selects the SOLO TRAINER: comm-module build 1 (track 35) is the AI opponent, not a null-modem driver, and overlay A additionally loads track 29 s0-3 to $EC00 ($7B24) only when that bit is set (AI extension that references the $8800-$89FF work area and unit tables).", "Game-type presets (gameTypePresetTable $78CA, 7 bytes): SCRIMAGE $00, QB SNEAK $01, THE BOMB $02, FACE-OFF $13, SLUGGERS $14, FULL WAR $1D, DEFENDER $1E -> $0BA3 (bit 3 UNIT MENUS, bit 4 DAMAGE). Game length per type (gameClockByTypeTable $78C3): 63,63,31,127,127,254,254 rounds. Per-side defaults come from two 5x5 tables at $892C/$8945 in the $8800 strings overlay (rows: TERRAIN PTS, RECYCLER, COMCEN SPEED, DRONES, MISSILES; columns: SCRIMAGE, QB SNEAK, BOMB/FACE-OFF/SLUGGERS, FULL WAR, DEFENDER). QB SNEAK removes units 0-6; THE BOMB leaves only the two comcens (units 49 and 99) plus drones/missiles.", "The label bootReceiveSectorData ($C0EF) is stale for the runtime $C000 overlay: there $C0EF is 'print A copies of the pad character zp_3E' (repeats sub_C0C0), which is what $7F12/$7F1E call with A=3.", "Status-message slot $1E is a dynamic slot: sub_C924(X=$1E, A/Y) points it at 'INSERT xxxx DISK AND PRESS SPACE.' ($EFAF, xxxx patched at $EFB6 with DATA $EFD0 or GAME $EFD4), 'CUSTOM OFF' ($EFD8, with OFF/ON patched at $EFDF) or 'DESTROY OFF' ($EFE2). These strings live in the comm-module tail loaded from track 34 s12-15 together with the option names MAP TYPE $EF77, (MORE) $EF81, LOCATING $EF89, RECYCLER $EF92, UNIT MENUS $EF9B, DAMAGE $EFA7. Message 9 = 'NOT A GAME FILM!'.", "sub_8279 and sub_82A4 are routine starts only in overlay variant B; in variant A they are mid-routine labels of runGameTypeMenu. Conversely the main program calls $81FE ($563C), $821F ($2E07) and $82BF ($3A30) which are routine starts in both variants with unrelated code - those main-program call sites are meant for the variant-B code (battle engine), while the variant-A routines at these addresses belong to the setup menus.", "Setup-menu conventions used throughout overlay A: printMenuTitle (X = preselected item, A/Y title) + menuItemCount + runMenuWithWidth wrap the generic selector sub_5FD1; resetSelectionAndView ($9242=$FE, $90F8=$FE, view origin (45,45)) blanks the viewport; commands $A4 (game type), $9C (recycler mode/col/row) and $A5 (numeric options, next chunk) are sent to the opponent with sub_561F/sub_560A/sub_560F." ] }